Spaces:
Running
Running
File size: 3,921 Bytes
370c714 06f9bb6 370c714 6371453 62ad79c 6371453 62ad79c 6371453 62ad79c 370c714 4b7f4e3 370c714 6371453 370c714 6371453 370c714 06f9bb6 370c714 6371453 370c714 6371453 370c714 6371453 370c714 6371453 81c9042 6371453 62ad79c 370c714 6371453 b41929f 6371453 4b7f4e3 6371453 06f9bb6 370c714 6371453 4b7f4e3 6371453 06f9bb6 6371453 370c714 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
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;
}
}; |