Skip to content

Commit 8189f0f

Browse files
committed
fix uppercase image extensions and improve binary file UI
1 parent 62d92cf commit 8189f0f

4 files changed

Lines changed: 188 additions & 86 deletions

File tree

packages/app/src/pages/session.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2798,8 +2798,13 @@ export default function Page() {
27982798
<Match when={state()?.loaded && isBinary()}>
27992799
<div class="h-full px-6 pb-42 flex flex-col items-center justify-center text-center gap-6">
28002800
<Mark class="w-14 opacity-10" />
2801-
<div class="text-14-regular text-text-weak max-w-56">
2802-
{language.t("session.files.binaryContent")}
2801+
<div class="flex flex-col gap-2 max-w-md">
2802+
<div class="text-14-semibold text-text-strong truncate">
2803+
{path()?.split("/").pop()}
2804+
</div>
2805+
<div class="text-14-regular text-text-weak">
2806+
{language.t("session.files.binaryContent")}
2807+
</div>
28032808
</div>
28042809
</div>
28052810
</Match>

packages/opencode/src/file/index.ts

Lines changed: 179 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,174 @@ export namespace File {
7373
})
7474
export type Content = z.infer<typeof Content>
7575

76+
const binaryExtensions = new Set([
77+
"exe",
78+
"dll",
79+
"pdb",
80+
"bin",
81+
"so",
82+
"dylib",
83+
"o",
84+
"a",
85+
"lib",
86+
"wav",
87+
"mp3",
88+
"ogg",
89+
"oga",
90+
"ogv",
91+
"ogx",
92+
"flac",
93+
"aac",
94+
"wma",
95+
"m4a",
96+
"weba",
97+
"mp4",
98+
"avi",
99+
"mov",
100+
"wmv",
101+
"flv",
102+
"webm",
103+
"mkv",
104+
"zip",
105+
"tar",
106+
"gz",
107+
"gzip",
108+
"bz",
109+
"bz2",
110+
"bzip",
111+
"bzip2",
112+
"7z",
113+
"rar",
114+
"xz",
115+
"lz",
116+
"z",
117+
"pdf",
118+
"doc",
119+
"docx",
120+
"ppt",
121+
"pptx",
122+
"xls",
123+
"xlsx",
124+
"dmg",
125+
"iso",
126+
"img",
127+
"vmdk",
128+
"ttf",
129+
"otf",
130+
"woff",
131+
"woff2",
132+
"eot",
133+
"sqlite",
134+
"db",
135+
"mdb",
136+
"apk",
137+
"ipa",
138+
"aab",
139+
"xapk",
140+
"app",
141+
"pkg",
142+
"deb",
143+
"rpm",
144+
"snap",
145+
"flatpak",
146+
"appimage",
147+
"msi",
148+
"msp",
149+
"jar",
150+
"war",
151+
"ear",
152+
"class",
153+
"kotlin_module",
154+
"dex",
155+
"vdex",
156+
"odex",
157+
"oat",
158+
"art",
159+
"wasm",
160+
"wat",
161+
"bc",
162+
"ll",
163+
"s",
164+
"ko",
165+
"sys",
166+
"drv",
167+
"efi",
168+
"rom",
169+
"com",
170+
"bat",
171+
"cmd",
172+
"ps1",
173+
"sh",
174+
"bash",
175+
"zsh",
176+
"fish",
177+
])
178+
179+
const imageExtensions = new Set([
180+
"png",
181+
"jpg",
182+
"jpeg",
183+
"gif",
184+
"bmp",
185+
"webp",
186+
"ico",
187+
"tif",
188+
"tiff",
189+
"svg",
190+
"svgz",
191+
"avif",
192+
"apng",
193+
"jxl",
194+
"heic",
195+
"heif",
196+
"raw",
197+
"cr2",
198+
"nef",
199+
"arw",
200+
"dng",
201+
"orf",
202+
"raf",
203+
"pef",
204+
"x3f",
205+
])
206+
207+
function isImageByExtension(filepath: string): boolean {
208+
const ext = path.extname(filepath).toLowerCase().slice(1)
209+
return imageExtensions.has(ext)
210+
}
211+
212+
function getImageMimeType(filepath: string): string {
213+
const ext = path.extname(filepath).toLowerCase().slice(1)
214+
const mimeTypes: Record<string, string> = {
215+
png: "image/png",
216+
jpg: "image/jpeg",
217+
jpeg: "image/jpeg",
218+
gif: "image/gif",
219+
bmp: "image/bmp",
220+
webp: "image/webp",
221+
ico: "image/x-icon",
222+
tif: "image/tiff",
223+
tiff: "image/tiff",
224+
svg: "image/svg+xml",
225+
svgz: "image/svg+xml",
226+
avif: "image/avif",
227+
apng: "image/apng",
228+
jxl: "image/jxl",
229+
heic: "image/heic",
230+
heif: "image/heif",
231+
}
232+
return mimeTypes[ext] || "image/" + ext
233+
}
234+
235+
function isBinaryByExtension(filepath: string): boolean {
236+
const ext = path.extname(filepath).toLowerCase().slice(1)
237+
return binaryExtensions.has(ext)
238+
}
239+
240+
function isImage(mimeType: string): boolean {
241+
return mimeType.startsWith("image/")
242+
}
243+
76244
async function shouldEncode(file: BunFile): Promise<boolean> {
77245
const type = file.type?.toLowerCase()
78246
log.info("shouldEncode", { type })
@@ -83,37 +251,13 @@ export namespace File {
83251

84252
const parts = type.split("/", 2)
85253
const top = parts[0]
86-
const rest = parts[1] ?? ""
87-
const sub = rest.split(";", 1)[0]
88254

89255
const tops = ["image", "audio", "video", "font", "model", "multipart"]
90256
if (tops.includes(top)) return true
91257

92-
const bins = [
93-
"zip",
94-
"gzip",
95-
"bzip",
96-
"compressed",
97-
"binary",
98-
"pdf",
99-
"msword",
100-
"powerpoint",
101-
"excel",
102-
"ogg",
103-
"exe",
104-
"dmg",
105-
"iso",
106-
"rar",
107-
]
108-
if (bins.some((mark) => sub.includes(mark))) return true
109-
110258
return false
111259
}
112260

113-
function isImage(mimeType: string): boolean {
114-
return mimeType.startsWith("image/")
115-
}
116-
117261
export const Event = {
118262
Edited: BusEvent.define(
119263
"file.edited",
@@ -280,64 +424,6 @@ export namespace File {
280424
}))
281425
}
282426

283-
const binaryExtensions = new Set([
284-
"exe",
285-
"dll",
286-
"pdb",
287-
"bin",
288-
"so",
289-
"dylib",
290-
"o",
291-
"a",
292-
"lib",
293-
"wav",
294-
"mp3",
295-
"ogg",
296-
"flac",
297-
"aac",
298-
"wma",
299-
"m4a",
300-
"mp4",
301-
"avi",
302-
"mov",
303-
"wmv",
304-
"flv",
305-
"webm",
306-
"mkv",
307-
"zip",
308-
"tar",
309-
"gz",
310-
"bz2",
311-
"7z",
312-
"rar",
313-
"xz",
314-
"lz",
315-
"pdf",
316-
"doc",
317-
"docx",
318-
"ppt",
319-
"pptx",
320-
"xls",
321-
"xlsx",
322-
"dmg",
323-
"iso",
324-
"img",
325-
"vmdk",
326-
"ttf",
327-
"otf",
328-
"woff",
329-
"woff2",
330-
"eot",
331-
"sqlite",
332-
"db",
333-
"mdb",
334-
])
335-
336-
function isBinaryByExtension(filepath: string): boolean {
337-
const ext = path.extname(filepath).toLowerCase().slice(1)
338-
return binaryExtensions.has(ext)
339-
}
340-
341427
export async function read(file: string): Promise<Content> {
342428
using _ = log.time("read", { file })
343429
const project = Instance.project
@@ -350,6 +436,17 @@ export namespace File {
350436
}
351437

352438
// Fast path: check extension before any filesystem operations
439+
if (isImageByExtension(file)) {
440+
const bunFile = Bun.file(full)
441+
if (await bunFile.exists()) {
442+
const buffer = await bunFile.arrayBuffer().catch(() => new ArrayBuffer(0))
443+
const content = Buffer.from(buffer).toString("base64")
444+
const mimeType = getImageMimeType(file)
445+
return { type: "text", content, mimeType, encoding: "base64" }
446+
}
447+
return { type: "text", content: "" }
448+
}
449+
353450
if (isBinaryByExtension(file)) {
354451
return { type: "binary", content: "" }
355452
}

packages/sdk/js/src/gen/types.gen.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1554,7 +1554,7 @@ export type FileNode = {
15541554
}
15551555

15561556
export type FileContent = {
1557-
type: "text"
1557+
type: "text" | "binary"
15581558
content: string
15591559
diff?: string
15601560
patch?: {

packages/sdk/js/src/v2/gen/types.gen.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2062,7 +2062,7 @@ export type FileNode = {
20622062
}
20632063

20642064
export type FileContent = {
2065-
type: "text"
2065+
type: "text" | "binary"
20662066
content: string
20672067
diff?: string
20682068
patch?: {

0 commit comments

Comments
 (0)