Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 50 additions & 5 deletions apps/server/src/wsServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -844,7 +844,17 @@ export const createServer = Effect.fn(function* (): Effect.fn.Return<
relativePath: body.relativePath,
path,
});
const MAX_READ_SIZE = 1_048_576; // 1MB
const MAX_READ_SIZE_TEXT = 1_048_576; // 1MB
const MAX_READ_SIZE_IMAGE = 10_485_760; // 10MB
const IMAGE_EXTENSIONS_SET = new Set([
".png", ".jpg", ".jpeg", ".gif", ".bmp", ".webp",
".ico", ".tiff", ".tif", ".avif", ".svg",
]);
const fileExt = target.relativePath
.substring(target.relativePath.lastIndexOf("."))
.toLowerCase();
const isImageExt = IMAGE_EXTENSIONS_SET.has(fileExt);
const MAX_READ_SIZE = isImageExt ? MAX_READ_SIZE_IMAGE : MAX_READ_SIZE_TEXT;
const fileStat = yield* fileSystem.stat(target.absolutePath).pipe(
Effect.mapError(
(cause) =>
Expand All @@ -861,7 +871,7 @@ export const createServer = Effect.fn(function* (): Effect.fn.Return<
const sizeBytes = Number(fileStat.size);
if (sizeBytes > MAX_READ_SIZE) {
return yield* new RouteRequestError({
message: `File is too large to display (${(sizeBytes / 1024 / 1024).toFixed(1)}MB). Maximum supported size is 1MB.`,
message: `File is too large to display (${(sizeBytes / 1024 / 1024).toFixed(1)}MB). Maximum supported size is ${(MAX_READ_SIZE / 1024 / 1024).toFixed(0)}MB.`,
});
}
// Read raw bytes to detect binary files
Expand All @@ -875,13 +885,48 @@ export const createServer = Effect.fn(function* (): Effect.fn.Return<
);
// Check for null bytes in the first 8KB to detect binary files
const checkLength = Math.min(rawBytes.length, 8192);
let isBinary = false;
for (let i = 0; i < checkLength; i++) {
if (rawBytes[i] === 0) {
return yield* new RouteRequestError({
message: `File appears to be binary and cannot be displayed: ${target.relativePath}`,
});
isBinary = true;
break;
}
}
if (isBinary) {
// Check if the file is an image by extension
const IMAGE_EXTENSIONS: Record<string, string> = {
".png": "image/png",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".gif": "image/gif",
".bmp": "image/bmp",
".webp": "image/webp",
".ico": "image/x-icon",
".tiff": "image/tiff",
".tif": "image/tiff",
".avif": "image/avif",
".svg": "image/svg+xml",
};
const ext = target.relativePath
.substring(target.relativePath.lastIndexOf("."))
.toLowerCase();
const mimeType = IMAGE_EXTENSIONS[ext];
if (mimeType) {
// Return image as base64 data URL
const base64 = Buffer.from(rawBytes).toString("base64");
const imageDataUrl = `data:${mimeType};base64,${base64}`;
return {
relativePath: target.relativePath,
contents: "",
sizeBytes,
truncated: false,
imageDataUrl,
};
}
return yield* new RouteRequestError({
message: `File appears to be binary and cannot be displayed: ${target.relativePath}`,
});
}
const contents = new TextDecoder().decode(rawBytes);
return {
relativePath: target.relativePath,
Expand Down
16 changes: 15 additions & 1 deletion apps/web/src/components/CodeViewerPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,28 @@ const CodeViewerFileContent = memo(function CodeViewerFileContent(props: {
);
}

if (!query.data?.contents && query.data?.contents !== "") {
if (!query.data?.contents && query.data?.contents !== "" && !query.data?.imageDataUrl) {
return (
<div className="flex flex-1 items-center justify-center px-5 text-center text-xs text-muted-foreground/70">
No content available.
</div>
);
}

// Render image files
if (query.data?.imageDataUrl) {
return (
<div className="flex min-h-0 flex-1 items-center justify-center overflow-auto p-4">
<img
src={query.data.imageDataUrl}
alt={props.relativePath}
className="max-h-full max-w-full object-contain"
draggable={false}
/>
</div>
);
}

return (
<div className="min-h-0 flex-1 overflow-y-auto">
{query.data.truncated && (
Expand Down
2 changes: 2 additions & 0 deletions packages/contracts/src/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,5 +79,7 @@ export const ProjectReadFileResult = Schema.Struct({
contents: Schema.String,
sizeBytes: Schema.Number,
truncated: Schema.Boolean,
/** Base64 data URL for image files (e.g. "data:image/png;base64,...") */
imageDataUrl: Schema.optional(Schema.String),
});
export type ProjectReadFileResult = typeof ProjectReadFileResult.Type;
Loading