mirror of
https://github.com/open-webui/open-webui.git
synced 2026-10-05 02:41:34 +00:00
fix: iPhone HEIC photos are rejected by vision models (#31649)
iPhone photos were only converted to JPEG when the browser reported their type as exactly image/heic. Firefox reports image/heif and some browsers report no type at all, so those photos were uploaded as HEIC and vision models failed with an error. When conversion did run, the JPEG was still uploaded with the HEIC file name and type, so the model was told it was HEIC anyway. HEIC and HEIF photos are now converted whatever type the browser reports and are uploaded as a .jpg with the JPEG type, in chats, channels and notes. Fixes #28411
This commit is contained in:
parent
1058444d74
commit
6c322941d1
4 changed files with 22 additions and 10 deletions
|
|
@ -9,6 +9,7 @@
|
|||
import { config, mobile, settings, socket, user } from '$lib/stores';
|
||||
import {
|
||||
convertHeicToJpeg,
|
||||
isHeicImage,
|
||||
compressImage,
|
||||
extractInputVariables,
|
||||
getAge,
|
||||
|
|
@ -377,7 +378,7 @@
|
|||
return;
|
||||
}
|
||||
|
||||
if (file['type'].startsWith('image/')) {
|
||||
if (file['type'].startsWith('image/') || isHeicImage(file)) {
|
||||
const compressImageHandler = async (imageUrl, settings = {}, config = {}) => {
|
||||
// Quick shortcut so we don’t do unnecessary work.
|
||||
const settingsCompression =
|
||||
|
|
@ -415,6 +416,7 @@
|
|||
return imageUrl;
|
||||
};
|
||||
|
||||
const imageFile = isHeicImage(file) ? await convertHeicToJpeg(file) : file;
|
||||
let reader = new FileReader();
|
||||
|
||||
reader.onload = async (event) => {
|
||||
|
|
@ -424,12 +426,12 @@
|
|||
imageUrl = await compressImageHandler(imageUrl, $settings, $config);
|
||||
|
||||
const blob = await (await fetch(imageUrl)).blob();
|
||||
const compressedFile = new File([blob], file.name, { type: file.type });
|
||||
const compressedFile = new File([blob], imageFile.name, { type: imageFile.type });
|
||||
|
||||
uploadFileHandler(compressedFile, false);
|
||||
};
|
||||
|
||||
reader.readAsDataURL(file['type'] === 'image/heic' ? await convertHeicToJpeg(file) : file);
|
||||
reader.readAsDataURL(imageFile);
|
||||
} else {
|
||||
uploadFileHandler(file);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@
|
|||
|
||||
import {
|
||||
convertHeicToJpeg,
|
||||
isHeicImage,
|
||||
compressImage,
|
||||
createMessagesList,
|
||||
extractContentFromFile,
|
||||
|
|
@ -1123,7 +1124,7 @@
|
|||
return;
|
||||
}
|
||||
|
||||
if (isRasterImageContentType(file['type'])) {
|
||||
if (isRasterImageContentType(file['type']) || isHeicImage(file)) {
|
||||
if (visionCapableModels.length === 0) {
|
||||
toast.error($i18n.t('Selected model(s) do not support image inputs'));
|
||||
return;
|
||||
|
|
@ -1165,6 +1166,7 @@
|
|||
return imageUrl;
|
||||
};
|
||||
|
||||
const imageFile = isHeicImage(file) ? await convertHeicToJpeg(file) : file;
|
||||
let reader = new FileReader();
|
||||
|
||||
reader.onload = async (event) => {
|
||||
|
|
@ -1183,13 +1185,13 @@
|
|||
];
|
||||
} else {
|
||||
const blob = await (await fetch(imageUrl)).blob();
|
||||
const compressedFile = new File([blob], file.name, { type: file.type });
|
||||
const compressedFile = new File([blob], imageFile.name, { type: imageFile.type });
|
||||
|
||||
uploadFileHandler(compressedFile, false);
|
||||
}
|
||||
};
|
||||
|
||||
reader.readAsDataURL(file['type'] === 'image/heic' ? await convertHeicToJpeg(file) : file);
|
||||
reader.readAsDataURL(imageFile);
|
||||
} else {
|
||||
uploadFileHandler(file);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
dayjs.extend(duration);
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
import { compressImage, copyToClipboard, convertHeicToJpeg } from '$lib/utils';
|
||||
import { compressImage, copyToClipboard, convertHeicToJpeg, isHeicImage } from '$lib/utils';
|
||||
import { WEBUI_BASE_URL } from '$lib/constants';
|
||||
import { getFileById, uploadFile } from '$lib/apis/files';
|
||||
import { generateOpenAIChatCompletion } from '$lib/apis/openai';
|
||||
|
|
@ -627,7 +627,7 @@ ${content}
|
|||
return;
|
||||
}
|
||||
|
||||
if (file['type'].startsWith('image/')) {
|
||||
if (file['type'].startsWith('image/') || isHeicImage(file)) {
|
||||
const uploadImagePromise = new Promise(async (resolve, reject) => {
|
||||
let reader = new FileReader();
|
||||
reader.onload = async (event) => {
|
||||
|
|
@ -654,7 +654,7 @@ ${content}
|
|||
}
|
||||
};
|
||||
|
||||
reader.readAsDataURL(file['type'] === 'image/heic' ? await convertHeicToJpeg(file) : file);
|
||||
reader.readAsDataURL(isHeicImage(file) ? await convertHeicToJpeg(file) : file);
|
||||
});
|
||||
|
||||
return await uploadImagePromise;
|
||||
|
|
|
|||
|
|
@ -2107,10 +2107,18 @@ export const getAge = (birthDate) => {
|
|||
return age.toString();
|
||||
};
|
||||
|
||||
const HEIC_EXTENSION_PATTERN = /\.(heic|heif)$/i;
|
||||
|
||||
// Browsers label HEIC photos inconsistently (Firefox uses image/heif, some leave it empty).
|
||||
export const isHeicImage = (file: File) =>
|
||||
['image/heic', 'image/heif'].includes(file.type) || HEIC_EXTENSION_PATTERN.test(file.name);
|
||||
|
||||
export const convertHeicToJpeg = async (file: File) => {
|
||||
const { default: heic2any } = await import('heic2any');
|
||||
try {
|
||||
return await heic2any({ blob: file, toType: 'image/jpeg' });
|
||||
const jpegBlob = (await heic2any({ blob: file, toType: 'image/jpeg' })) as Blob;
|
||||
const jpegName = `${file.name.replace(HEIC_EXTENSION_PATTERN, '')}.jpg`;
|
||||
return new File([jpegBlob], jpegName, { type: 'image/jpeg' });
|
||||
} catch (err: any) {
|
||||
if (err?.message?.includes('already browser readable')) {
|
||||
return file;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue