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] 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