StyleGenie-Tryon / src /services /geminiService.ts
Harsha1845's picture
Update src/services/geminiService.ts
6371453 verified
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<string> => {
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<string> => {
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<string> => {
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;
}
};