mirror of
https://github.com/ollama/ollama.git
synced 2026-04-17 19:54:03 +02:00
app/ui: re-validate image attachments when selected model changes (#15272)
This commit is contained in:
@@ -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}`}
|
||||||
|
|||||||
@@ -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", () => {
|
||||||
|
|||||||
@@ -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" };
|
||||||
|
|||||||
Reference in New Issue
Block a user