From bddfa2100f9b708c76c99167a180a05da5140e95 Mon Sep 17 00:00:00 2001 From: Daniel Alejandro Coll Tejeda Date: Wed, 5 Nov 2025 21:23:20 +0100 Subject: [PATCH 1/2] feat: add support for WebP images in Ollama's app --- app/cmd/app/webview.go | 2 +- app/ui/app/src/utils/fileValidation.test.ts | 106 ++++++++++++++++++++ app/ui/app/src/utils/fileValidation.ts | 9 +- app/ui/ui.go | 2 +- 4 files changed, 116 insertions(+), 3 deletions(-) create mode 100644 app/ui/app/src/utils/fileValidation.test.ts diff --git a/app/cmd/app/webview.go b/app/cmd/app/webview.go index 983fba9d..9332f10b 100644 --- a/app/cmd/app/webview.go +++ b/app/cmd/app/webview.go @@ -282,7 +282,7 @@ func (w *Webview) Run(path string) unsafe.Pointer { "go", "rs", "swift", "kt", "scala", "sh", "bat", "yaml", "yml", "toml", "ini", "cfg", "conf", "log", "rtf", } - imageExts := []string{"png", "jpg", "jpeg"} + imageExts := []string{"png", "jpg", "jpeg", "webp"} allowedExts := append(textExts, imageExts...) // Use native multiple file selection with extension filtering diff --git a/app/ui/app/src/utils/fileValidation.test.ts b/app/ui/app/src/utils/fileValidation.test.ts new file mode 100644 index 00000000..a5ea2783 --- /dev/null +++ b/app/ui/app/src/utils/fileValidation.test.ts @@ -0,0 +1,106 @@ +import { describe, it, expect } from "vitest"; +import { IMAGE_EXTENSIONS, validateFile } from "./fileValidation"; + +describe("fileValidation", () => { + describe("IMAGE_EXTENSIONS", () => { + it("should include all supported image formats including WebP", () => { + expect(IMAGE_EXTENSIONS).toContain("png"); + expect(IMAGE_EXTENSIONS).toContain("jpg"); + expect(IMAGE_EXTENSIONS).toContain("jpeg"); + expect(IMAGE_EXTENSIONS).toContain("gif"); + expect(IMAGE_EXTENSIONS).toContain("webp"); + }); + }); + + describe("validateFile", () => { + const createMockFile = ( + name: string, + size: number, + type: string, + ): File => { + const blob = new Blob(["test content"], { type }); + return new File([blob], name, { type }); + }; + + it("should accept WebP images when vision capability is enabled", () => { + const file = createMockFile("test.webp", 1024, "image/webp"); + const result = validateFile(file, { + hasVisionCapability: true, + }); + expect(result.valid).toBe(true); + }); + + it("should accept GIF images when vision capability is enabled", () => { + const file = createMockFile("test.gif", 1024, "image/gif"); + const result = validateFile(file, { + hasVisionCapability: true, + }); + expect(result.valid).toBe(true); + }); + + it("should reject WebP images when vision capability is disabled", () => { + const file = createMockFile("test.webp", 1024, "image/webp"); + const result = validateFile(file, { + hasVisionCapability: false, + }); + expect(result.valid).toBe(false); + expect(result.error).toBe("This model does not support images"); + }); + + it("should accept PNG images when vision capability is enabled", () => { + const file = createMockFile("test.png", 1024, "image/png"); + const result = validateFile(file, { + hasVisionCapability: true, + }); + expect(result.valid).toBe(true); + }); + + it("should accept JPEG images when vision capability is enabled", () => { + const file = createMockFile("test.jpg", 1024, "image/jpeg"); + const result = validateFile(file, { + hasVisionCapability: true, + }); + expect(result.valid).toBe(true); + }); + + it("should reject files that are too large", () => { + // Create a file with size property set correctly + const largeSize = 11 * 1024 * 1024; // 11MB + const content = new Uint8Array(largeSize); + const blob = new Blob([content], { type: "image/webp" }); + const file = new File([blob], "large.webp", { type: "image/webp" }); + + const result = validateFile(file, { + hasVisionCapability: true, + maxFileSize: 10, // 10MB limit + }); + expect(result.valid).toBe(false); + expect(result.error).toBe("File too large"); + }); + + it("should reject unsupported file types", () => { + const file = createMockFile("test.xyz", 1024, "application/xyz"); + const result = validateFile(file, { + hasVisionCapability: true, + }); + expect(result.valid).toBe(false); + expect(result.error).toBe("File type not supported"); + }); + + it("should respect custom validators", () => { + const file = createMockFile("test.webp", 1024, "image/webp"); + const result = validateFile(file, { + hasVisionCapability: true, + customValidator: () => ({ + valid: false, + error: "Custom error", + }), + }); + expect(result.valid).toBe(false); + expect(result.error).toBe("Custom error"); + }); + }); + + // Note: processFiles tests are skipped because FileReader is not available in the Node.js test environment + // These functions are tested in browser environment via integration tests +}); diff --git a/app/ui/app/src/utils/fileValidation.ts b/app/ui/app/src/utils/fileValidation.ts index b33499a2..91c76f75 100644 --- a/app/ui/app/src/utils/fileValidation.ts +++ b/app/ui/app/src/utils/fileValidation.ts @@ -41,7 +41,7 @@ export const TEXT_FILE_EXTENSIONS = [ "rtf", ]; -export const IMAGE_EXTENSIONS = ["png", "jpg", "jpeg"]; +export const IMAGE_EXTENSIONS = ["png", "jpg", "jpeg", "webp"]; export interface FileValidationOptions { maxFileSize?: number; // in MB @@ -84,6 +84,13 @@ export function validateFile( } if (IMAGE_EXTENSIONS.includes(fileExtension) && !hasVisionCapability) { + console.log("Image validation failed:", { + fileExtension, + fileName: file.name, + hasVisionCapability, + IMAGE_EXTENSIONS, + isImageExtension: IMAGE_EXTENSIONS.includes(fileExtension), + }); return { valid: false, error: "This model does not support images" }; } diff --git a/app/ui/ui.go b/app/ui/ui.go index 313c94f1..fce58895 100644 --- a/app/ui/ui.go +++ b/app/ui/ui.go @@ -1705,7 +1705,7 @@ func getStringFromMap(m map[string]any, key, defaultValue string) string { // isImageAttachment checks if a filename is an image file func isImageAttachment(filename string) bool { ext := strings.ToLower(filename) - return strings.HasSuffix(ext, ".png") || strings.HasSuffix(ext, ".jpg") || strings.HasSuffix(ext, ".jpeg") + return strings.HasSuffix(ext, ".png") || strings.HasSuffix(ext, ".jpg") || strings.HasSuffix(ext, ".jpeg") || strings.HasSuffix(ext, ".webp") } // ptr is a convenience function for &literal From a4a53692f89decbfdbfbb44756507413e310c7c3 Mon Sep 17 00:00:00 2001 From: Daniel Alejandro Coll Tejeda <62675074+macarronesc@users.noreply.github.com> Date: Thu, 6 Nov 2025 09:09:51 +0000 Subject: [PATCH 2/2] refactor: remove GIF support from image validation tests and logging --- app/ui/app/src/utils/fileValidation.test.ts | 9 --------- app/ui/app/src/utils/fileValidation.ts | 7 ------- 2 files changed, 16 deletions(-) diff --git a/app/ui/app/src/utils/fileValidation.test.ts b/app/ui/app/src/utils/fileValidation.test.ts index a5ea2783..94798cf2 100644 --- a/app/ui/app/src/utils/fileValidation.test.ts +++ b/app/ui/app/src/utils/fileValidation.test.ts @@ -7,7 +7,6 @@ describe("fileValidation", () => { expect(IMAGE_EXTENSIONS).toContain("png"); expect(IMAGE_EXTENSIONS).toContain("jpg"); expect(IMAGE_EXTENSIONS).toContain("jpeg"); - expect(IMAGE_EXTENSIONS).toContain("gif"); expect(IMAGE_EXTENSIONS).toContain("webp"); }); }); @@ -30,14 +29,6 @@ describe("fileValidation", () => { expect(result.valid).toBe(true); }); - it("should accept GIF images when vision capability is enabled", () => { - const file = createMockFile("test.gif", 1024, "image/gif"); - const result = validateFile(file, { - hasVisionCapability: true, - }); - expect(result.valid).toBe(true); - }); - it("should reject WebP images when vision capability is disabled", () => { const file = createMockFile("test.webp", 1024, "image/webp"); const result = validateFile(file, { diff --git a/app/ui/app/src/utils/fileValidation.ts b/app/ui/app/src/utils/fileValidation.ts index 91c76f75..047aa998 100644 --- a/app/ui/app/src/utils/fileValidation.ts +++ b/app/ui/app/src/utils/fileValidation.ts @@ -84,13 +84,6 @@ export function validateFile( } if (IMAGE_EXTENSIONS.includes(fileExtension) && !hasVisionCapability) { - console.log("Image validation failed:", { - fileExtension, - fileName: file.name, - hasVisionCapability, - IMAGE_EXTENSIONS, - isImageExtension: IMAGE_EXTENSIONS.includes(fileExtension), - }); return { valid: false, error: "This model does not support images" }; }