app/ui: re-validate image attachments when selected model changes (#15272)

This commit is contained in:
Matteo Celani
2026-04-10 23:03:51 +02:00
committed by GitHub
parent 8e6d86dbe3
commit 9517864603
3 changed files with 33 additions and 19 deletions

View File

@@ -480,13 +480,15 @@ function ChatForm({
return; return;
} }
// Prepare attachments for submission // Prepare attachments for submission, excluding unsupported images
const attachmentsToSend: FileAttachment[] = message.attachments.map( const attachmentsToSend: FileAttachment[] = message.attachments
(att) => ({ .filter(
(att) => hasVisionCapability || !isImageFile(att.filename),
)
.map((att) => ({
filename: att.filename, filename: att.filename,
data: att.data || new Uint8Array(0), // Empty data for existing files data: att.data || new Uint8Array(0), // Empty data for existing files
}), }));
);
const useWebSearch = const useWebSearch =
supportsWebSearch && webSearchEnabled && !cloudDisabled; supportsWebSearch && webSearchEnabled && !cloudDisabled;
@@ -736,10 +738,17 @@ function ChatForm({
)} )}
{(message.attachments.length > 0 || message.fileErrors.length > 0) && ( {(message.attachments.length > 0 || message.fileErrors.length > 0) && (
<div className="flex gap-2 overflow-x-auto px-3 pt pb-3 w-full scrollbar-hide"> <div className="flex gap-2 overflow-x-auto px-3 pt pb-3 w-full scrollbar-hide">
{message.attachments.map((attachment, index) => ( {message.attachments.map((attachment, index) => {
const isUnsupportedImage =
!hasVisionCapability && isImageFile(attachment.filename);
return (
<div <div
key={attachment.id} key={attachment.id}
className="group flex items-center gap-2 py-2 px-3 rounded-lg bg-neutral-50 dark:bg-neutral-700/50 hover:bg-neutral-100 dark:hover:bg-neutral-700 transition-colors flex-shrink-0" className={`group flex items-center gap-2 py-2 px-3 rounded-lg transition-colors flex-shrink-0 ${
isUnsupportedImage
? "bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800"
: "bg-neutral-50 dark:bg-neutral-700/50 hover:bg-neutral-100 dark:hover:bg-neutral-700"
}`}
> >
{isImageFile(attachment.filename) ? ( {isImageFile(attachment.filename) ? (
<ImageThumbnail <ImageThumbnail
@@ -764,9 +773,16 @@ function ChatForm({
/> />
</svg> </svg>
)} )}
<span className="text-sm text-neutral-700 dark:text-neutral-300 max-w-[150px] truncate"> <div className="flex flex-col min-w-0">
{attachment.filename} <span className={`text-sm max-w-36 truncate ${isUnsupportedImage ? "text-red-700 dark:text-red-300" : "text-neutral-700 dark:text-neutral-300"}`}>
</span> {attachment.filename}
</span>
{isUnsupportedImage && (
<span className="text-xs text-red-600 dark:text-red-400 opacity-75">
This model does not support images
</span>
)}
</div>
<button <button
type="button" type="button"
onClick={() => removeFile(index)} onClick={() => removeFile(index)}
@@ -788,7 +804,8 @@ function ChatForm({
</svg> </svg>
</button> </button>
</div> </div>
))} );
})}
{message.fileErrors.map((fileError, index) => ( {message.fileErrors.map((fileError, index) => (
<div <div
key={`error-${index}`} key={`error-${index}`}

View File

@@ -29,13 +29,15 @@ describe("fileValidation", () => {
expect(result.valid).toBe(true); expect(result.valid).toBe(true);
}); });
it("should reject WebP images when vision capability is disabled", () => { it("should accept images regardless of vision capability", () => {
// Vision capability check is handled at the UI layer (ChatForm),
// not at validation time, so users can switch models without
// needing to re-upload files.
const file = createMockFile("test.webp", 1024, "image/webp"); const file = createMockFile("test.webp", 1024, "image/webp");
const result = validateFile(file, { const result = validateFile(file, {
hasVisionCapability: false, hasVisionCapability: false,
}); });
expect(result.valid).toBe(false); expect(result.valid).toBe(true);
expect(result.error).toBe("This model does not support images");
}); });
it("should accept PNG images when vision capability is enabled", () => { it("should accept PNG images when vision capability is enabled", () => {

View File

@@ -63,7 +63,6 @@ export function validateFile(
const { const {
maxFileSize = 10, maxFileSize = 10,
allowedExtensions = [...TEXT_FILE_EXTENSIONS, ...IMAGE_EXTENSIONS], allowedExtensions = [...TEXT_FILE_EXTENSIONS, ...IMAGE_EXTENSIONS],
hasVisionCapability = false,
customValidator, customValidator,
} = options; } = options;
@@ -83,10 +82,6 @@ export function validateFile(
return { valid: false, error: "File type not supported" }; return { valid: false, error: "File type not supported" };
} }
if (IMAGE_EXTENSIONS.includes(fileExtension) && !hasVisionCapability) {
return { valid: false, error: "This model does not support images" };
}
// File size validation // File size validation
if (file.size > MAX_FILE_SIZE) { if (file.size > MAX_FILE_SIZE) {
return { valid: false, error: "File too large" }; return { valid: false, error: "File too large" };