From c41a4ae8396f1075087e747450916340578e0108 Mon Sep 17 00:00:00 2001 From: Shrimbly Date: Fri, 13 Feb 2026 21:56:01 +1300 Subject: [PATCH] fix: preserve image MIME types in workflow-images API to prevent duplicates Fixes duplicate image files in generations folder when Gemini returns JPEG. Changes: - POST handler now extracts MIME type from data URL and uses correct extension - GET handler searches multiple extensions (png, jpg, jpeg, gif, webp) - GET handler returns correct MIME type in data URL based on file extension Root cause: workflow-images/route.ts hardcoded .png extension regardless of actual image format, while save-generation/route.ts correctly preserved formats. This caused Gemini's JPEG images to be saved twice (once as .jpg, once as .png). Co-Authored-By: Claude Sonnet 4.5 --- src/app/api/workflow-images/route.ts | 60 ++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 16 deletions(-) diff --git a/src/app/api/workflow-images/route.ts b/src/app/api/workflow-images/route.ts index 6d65d6eb..be1c6bed 100644 --- a/src/app/api/workflow-images/route.ts +++ b/src/app/api/workflow-images/route.ts @@ -8,6 +8,24 @@ export const maxDuration = 300; // 5 minute timeout for large image operations const IMAGES_FOLDER = "inputs"; const LEGACY_IMAGES_FOLDER = ".images"; // For backward compatibility +// Helper to extract MIME type and extension from data URL +function getMimeAndExtension(dataUrl: string): { mime: string; extension: string } { + const match = dataUrl.match(/^data:(image\/\w+);base64,/); + if (match) { + const mime = match[1]; + const mimeToExt: Record = { + "image/png": "png", + "image/jpeg": "jpg", + "image/jpg": "jpg", + "image/gif": "gif", + "image/webp": "webp", + }; + return { mime, extension: mimeToExt[mime] || "png" }; + } + // Default to PNG if no MIME type found + return { mime: "image/png", extension: "png" }; +} + // POST: Save an image to the workflow's inputs or generations folder export async function POST(request: NextRequest) { let workflowPath: string | undefined; @@ -80,8 +98,9 @@ export async function POST(request: NextRequest) { ); } - // Construct file path - const filename = `${imageId}.png`; + // Extract MIME type and determine file extension + const { extension } = getMimeAndExtension(imageData); + const filename = `${imageId}.${extension}`; const filePath = path.join(targetFolder, filename); // Extract base64 data and convert to buffer @@ -157,8 +176,8 @@ export async function GET(request: NextRequest) { ); } - // Construct file path - check folders in order based on hint - const filename = `${imageId}.png`; + // Construct file path - check folders and extensions in order + const possibleExtensions = ["png", "jpg", "jpeg", "gif", "webp"]; const inputsFolder = path.join(workflowPath, IMAGES_FOLDER); const generationsFolder = path.join(workflowPath, "generations"); const legacyFolder = path.join(workflowPath, LEGACY_IMAGES_FOLDER); @@ -169,20 +188,26 @@ export async function GET(request: NextRequest) { : [inputsFolder, generationsFolder, legacyFolder]; let filePath: string | null = null; + let foundExtension = "png"; // Track which extension was found - // Check each folder in order + // Check each folder and extension combination in order for (const searchFolder of searchOrder) { - const candidatePath = path.join(searchFolder, filename); - try { - await fs.access(candidatePath); - filePath = candidatePath; - if (searchFolder === legacyFolder) { - logger.info('file.load', 'Found image in legacy .images folder', { filePath }); + for (const ext of possibleExtensions) { + const filename = `${imageId}.${ext}`; + const candidatePath = path.join(searchFolder, filename); + try { + await fs.access(candidatePath); + filePath = candidatePath; + foundExtension = ext; + if (searchFolder === legacyFolder) { + logger.info('file.load', 'Found image in legacy .images folder', { filePath }); + } + break; + } catch { + // File not found with this extension, try next } - break; - } catch { - // File not found in this folder, try next } + if (filePath) break; // Stop searching if file was found } if (!filePath) { @@ -202,9 +227,12 @@ export async function GET(request: NextRequest) { // Read the image file const buffer = await fs.readFile(filePath); - // Convert to base64 data URL + // Convert to base64 data URL with correct MIME type const base64 = buffer.toString("base64"); - const dataUrl = `data:image/png;base64,${base64}`; + const mimeType = foundExtension === "jpg" || foundExtension === "jpeg" + ? "image/jpeg" + : `image/${foundExtension}`; + const dataUrl = `data:${mimeType};base64,${base64}`; logger.info('file.load', 'Workflow image loaded successfully', { filePath,