import { GoogleGenAI } from "@google/genai"; import { Product } from "../types"; // Micro-Resolution: 192px is extremely small but sufficient for many VLM tasks. // Using 0.15 quality to minimize the token payload significantly. const resizeImage = async (base64Str: string, maxWidth = 192): Promise => { return new Promise((resolve) => { const img = new Image(); img.src = base64Str; img.onload = () => { const canvas = document.createElement('canvas'); let width = img.width; let height = img.height; if (width > height) { if (width > maxWidth) { height *= maxWidth / width; width = maxWidth; } } else { if (height > maxWidth) { width *= maxWidth / height; height = maxWidth; } } canvas.width = width; canvas.height = height; const ctx = canvas.getContext('2d'); // Low quality (0.15) for maximum token savings ctx?.drawImage(img, 0, 0, width, height); resolve(canvas.toDataURL('image/jpeg', 0.15)); }; }); }; const cleanBase64 = (data: string) => { return data.replace(/^data:image\/\w+;base64,/i, ''); }; const urlToBase64 = async (url: string): Promise => { try { const response = await fetch(url); if (!response.ok) throw new Error(`H:${response.status}`); const blob = await response.blob(); return await new Promise((resolve, reject) => { const reader = new FileReader(); reader.onloadend = () => resolve(reader.result as string); reader.onerror = reject; reader.readAsDataURL(blob); }); } catch (error) { return new Promise((resolve, reject) => { const img = new Image(); img.crossOrigin = "Anonymous"; img.onload = () => { const canvas = document.createElement('canvas'); canvas.width = img.width; canvas.height = img.height; const ctx = canvas.getContext('2d'); if (!ctx) return reject(new Error("C-Fail")); ctx.drawImage(img, 0, 0); try { resolve(canvas.toDataURL('image/jpeg')); } catch (e) { reject(e); } }; img.onerror = () => reject(new Error("L-Fail")); img.src = url; }); } }; export const generateTryOn = async (userImageBase64: string, product: Product): Promise => { const apiKey = process.env.API_KEY; if (!apiKey) throw new Error("API Key config error."); const ai = new GoogleGenAI({ apiKey: apiKey }); // 1. Process images to micro resolution const productRaw = await urlToBase64(product.imageUrl); const [processedUserImg, processedProductImg] = await Promise.all([ resizeImage(userImageBase64, 192), resizeImage(productRaw, 192) ]); // Prompt reduced to the absolute minimum to save input tokens const prompt = `Put ${product.name} from img 2 on person in img 1.`; try { const response = await ai.models.generateContent({ model: 'gemini-2.5-flash-image', contents: { parts: [ { text: prompt }, { inlineData: { mimeType: 'image/jpeg', data: cleanBase64(processedUserImg) } }, { inlineData: { mimeType: 'image/jpeg', data: cleanBase64(processedProductImg) } } ] } }); const candidate = response.candidates?.[0]; if (candidate?.finishReason && candidate.finishReason !== 'STOP') { throw new Error(`Fin:${candidate.finishReason}`); } const part = candidate?.content?.parts?.find(p => p.inlineData); if (part?.inlineData?.data) { return `data:${part.inlineData.mimeType || 'image/png'};base64,${part.inlineData.data}`; } throw new Error("Empty AI response."); } catch (error: any) { const msg = error.message || ""; if (msg.includes("429") || msg.includes("RESOURCE_EXHAUSTED")) { throw new Error("FREE_LIMIT_HIT: Please wait for the cooldown timer."); } throw error; } };