Skip to content

Commit 6912c35

Browse files
committed
add image helper for android/chrome
1 parent 8b0d6e0 commit 6912c35

1 file changed

Lines changed: 56 additions & 14 deletions

File tree

src/routes/chat/+layout.svelte

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -381,21 +381,63 @@
381381
// return supportsDocuments(currentModel);
382382
});
383383
384+
// Helper to check if file is an image (by MIME type or extension)
385+
// Chrome Android often returns empty file.type for images from gallery
386+
const IMAGE_EXTENSIONS = [
387+
'jpg',
388+
'jpeg',
389+
'png',
390+
'gif',
391+
'webp',
392+
'bmp',
393+
'svg',
394+
'heic',
395+
'heif',
396+
'avif',
397+
];
398+
const EXTENSION_TO_MIME: Record<string, string> = {
399+
jpg: 'image/jpeg',
400+
jpeg: 'image/jpeg',
401+
png: 'image/png',
402+
gif: 'image/gif',
403+
webp: 'image/webp',
404+
bmp: 'image/bmp',
405+
svg: 'image/svg+xml',
406+
heic: 'image/heic',
407+
heif: 'image/heif',
408+
avif: 'image/avif',
409+
};
410+
411+
function isImageFile(f: File): boolean {
412+
if (f.type.startsWith('image/')) return true;
413+
// Fallback: check extension for Chrome Android where MIME type may be empty
414+
const ext = f.name.split('.').pop()?.toLowerCase();
415+
return ext ? IMAGE_EXTENSIONS.includes(ext) : false;
416+
}
417+
418+
function getImageMimeType(f: File): string {
419+
if (f.type) return f.type;
420+
// Derive MIME type from extension for Chrome Android
421+
const ext = f.name.split('.').pop()?.toLowerCase();
422+
return ext ? (EXTENSION_TO_MIME[ext] ?? 'image/jpeg') : 'image/jpeg';
423+
}
424+
384425
async function handleFilesSelect(files: File[]) {
385426
if (!files.length || !session.current?.session.token) return;
386427
387-
const imageFiles = files.filter((f) => f.type.startsWith('image/'));
388-
const documentFiles = files.filter((f) => !f.type.startsWith('image/'));
428+
const imageFiles = files.filter(isImageFile);
429+
const documentFiles = files.filter((f) => !isImageFile(f));
389430
390431
if (imageFiles.length > 0) {
391432
isUploading = true;
392433
const uploadedImages: { url: string; storage_id: string; fileName?: string }[] = [];
393434
try {
394435
for (const file of imageFiles) {
436+
const mimeType = getImageMimeType(file);
395437
const compressedFile = await compressImage(file, 1024 * 1024);
396438
const uploadResult = await fetch('/api/storage', {
397439
method: 'POST',
398-
headers: { 'Content-Type': file.type },
440+
headers: { 'Content-Type': mimeType },
399441
credentials: 'include',
400442
body: compressedFile,
401443
});
@@ -965,17 +1007,17 @@
9651007
{/if}
9661008
<div class="relative flex flex-grow flex-row items-start">
9671009
<input
968-
{...fileUpload.input}
969-
bind:this={fileInput}
970-
oninput={(e) => {
971-
// Fallback for Chrome Mobile Android where onchange may not fire for images
972-
const input = e.currentTarget as HTMLInputElement;
973-
if (input.files && input.files.length > 0) {
974-
handleFilesSelect(Array.from(input.files));
975-
input.value = ''; // Clear to allow re-selection of same file
976-
}
977-
}}
978-
/>
1010+
{...fileUpload.input}
1011+
bind:this={fileInput}
1012+
oninput={(e) => {
1013+
// Fallback for Chrome Mobile Android where onchange may not fire for images
1014+
const input = e.currentTarget as HTMLInputElement;
1015+
if (input.files && input.files.length > 0) {
1016+
handleFilesSelect(Array.from(input.files));
1017+
input.value = ''; // Clear to allow re-selection of same file
1018+
}
1019+
}}
1020+
/>
9791021
<!-- svelte-ignore a11y_autofocus -->
9801022
<textarea
9811023
style={popover.trigger.style}

0 commit comments

Comments
 (0)