Spaces:
Running
Running
Update src/services/geminiService.ts
Browse files- src/services/geminiService.ts +27 -21
src/services/geminiService.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
| 1 |
import { GoogleGenAI } from "@google/genai";
|
| 2 |
import { Product } from "../types";
|
| 3 |
|
| 4 |
-
//
|
| 5 |
-
const resizeImage = async (base64Str: string, maxWidth =
|
| 6 |
return new Promise((resolve) => {
|
| 7 |
const img = new Image();
|
| 8 |
img.src = base64Str;
|
|
@@ -26,9 +26,9 @@ const resizeImage = async (base64Str: string, maxWidth = 320): Promise<string> =
|
|
| 26 |
canvas.width = width;
|
| 27 |
canvas.height = height;
|
| 28 |
const ctx = canvas.getContext('2d');
|
| 29 |
-
//
|
| 30 |
ctx?.drawImage(img, 0, 0, width, height);
|
| 31 |
-
resolve(canvas.toDataURL('image/jpeg', 0.
|
| 32 |
};
|
| 33 |
});
|
| 34 |
};
|
|
@@ -40,7 +40,7 @@ const cleanBase64 = (data: string) => {
|
|
| 40 |
const urlToBase64 = async (url: string): Promise<string> => {
|
| 41 |
try {
|
| 42 |
const response = await fetch(url);
|
| 43 |
-
if (!response.ok) throw new Error(`
|
| 44 |
const blob = await response.blob();
|
| 45 |
return await new Promise((resolve, reject) => {
|
| 46 |
const reader = new FileReader();
|
|
@@ -57,11 +57,11 @@ const urlToBase64 = async (url: string): Promise<string> => {
|
|
| 57 |
canvas.width = img.width;
|
| 58 |
canvas.height = img.height;
|
| 59 |
const ctx = canvas.getContext('2d');
|
| 60 |
-
if (!ctx) return reject(new Error("Canvas
|
| 61 |
ctx.drawImage(img, 0, 0);
|
| 62 |
try { resolve(canvas.toDataURL('image/jpeg')); } catch (e) { reject(e); }
|
| 63 |
};
|
| 64 |
-
img.onerror = () => reject(new Error("
|
| 65 |
img.src = url;
|
| 66 |
});
|
| 67 |
}
|
|
@@ -69,19 +69,19 @@ const urlToBase64 = async (url: string): Promise<string> => {
|
|
| 69 |
|
| 70 |
export const generateTryOn = async (userImageBase64: string, product: Product): Promise<string> => {
|
| 71 |
const apiKey = process.env.API_KEY;
|
| 72 |
-
if (!apiKey) throw new Error("API Key
|
| 73 |
|
| 74 |
const ai = new GoogleGenAI({ apiKey });
|
| 75 |
|
| 76 |
-
// 1.
|
| 77 |
const productRaw = await urlToBase64(product.imageUrl);
|
| 78 |
const [smallUserImg, smallProductImg] = await Promise.all([
|
| 79 |
-
resizeImage(userImageBase64,
|
| 80 |
-
resizeImage(productRaw,
|
| 81 |
]);
|
| 82 |
|
| 83 |
-
//
|
| 84 |
-
const prompt = `
|
| 85 |
|
| 86 |
const requestParams = {
|
| 87 |
model: 'gemini-2.5-flash-image',
|
|
@@ -95,23 +95,29 @@ export const generateTryOn = async (userImageBase64: string, product: Product):
|
|
| 95 |
};
|
| 96 |
|
| 97 |
try {
|
|
|
|
| 98 |
const response = await ai.models.generateContent(requestParams);
|
| 99 |
-
const candidate = response.candidates?.[0];
|
| 100 |
|
| 101 |
-
if (
|
| 102 |
-
throw new Error(`AI
|
| 103 |
}
|
| 104 |
|
| 105 |
-
const part =
|
| 106 |
if (part?.inlineData?.data) {
|
| 107 |
return `data:${part.inlineData.mimeType || 'image/png'};base64,${part.inlineData.data}`;
|
| 108 |
}
|
| 109 |
|
| 110 |
-
throw new Error("
|
| 111 |
} catch (error: any) {
|
| 112 |
-
|
| 113 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
}
|
| 115 |
-
|
|
|
|
| 116 |
}
|
| 117 |
};
|
|
|
|
| 1 |
import { GoogleGenAI } from "@google/genai";
|
| 2 |
import { Product } from "../types";
|
| 3 |
|
| 4 |
+
// Atomic compression: 256px is the absolute floor for usable VLM performance
|
| 5 |
+
const resizeImage = async (base64Str: string, maxWidth = 256): Promise<string> => {
|
| 6 |
return new Promise((resolve) => {
|
| 7 |
const img = new Image();
|
| 8 |
img.src = base64Str;
|
|
|
|
| 26 |
canvas.width = width;
|
| 27 |
canvas.height = height;
|
| 28 |
const ctx = canvas.getContext('2d');
|
| 29 |
+
// Extreme compression (0.3) to minimize token footprint
|
| 30 |
ctx?.drawImage(img, 0, 0, width, height);
|
| 31 |
+
resolve(canvas.toDataURL('image/jpeg', 0.3));
|
| 32 |
};
|
| 33 |
});
|
| 34 |
};
|
|
|
|
| 40 |
const urlToBase64 = async (url: string): Promise<string> => {
|
| 41 |
try {
|
| 42 |
const response = await fetch(url);
|
| 43 |
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
| 44 |
const blob = await response.blob();
|
| 45 |
return await new Promise((resolve, reject) => {
|
| 46 |
const reader = new FileReader();
|
|
|
|
| 57 |
canvas.width = img.width;
|
| 58 |
canvas.height = img.height;
|
| 59 |
const ctx = canvas.getContext('2d');
|
| 60 |
+
if (!ctx) return reject(new Error("Canvas fail"));
|
| 61 |
ctx.drawImage(img, 0, 0);
|
| 62 |
try { resolve(canvas.toDataURL('image/jpeg')); } catch (e) { reject(e); }
|
| 63 |
};
|
| 64 |
+
img.onerror = () => reject(new Error("Image load fail"));
|
| 65 |
img.src = url;
|
| 66 |
});
|
| 67 |
}
|
|
|
|
| 69 |
|
| 70 |
export const generateTryOn = async (userImageBase64: string, product: Product): Promise<string> => {
|
| 71 |
const apiKey = process.env.API_KEY;
|
| 72 |
+
if (!apiKey) throw new Error("API Key configuration error.");
|
| 73 |
|
| 74 |
const ai = new GoogleGenAI({ apiKey });
|
| 75 |
|
| 76 |
+
// 1. Process images to atomic size
|
| 77 |
const productRaw = await urlToBase64(product.imageUrl);
|
| 78 |
const [smallUserImg, smallProductImg] = await Promise.all([
|
| 79 |
+
resizeImage(userImageBase64, 256),
|
| 80 |
+
resizeImage(productRaw, 256)
|
| 81 |
]);
|
| 82 |
|
| 83 |
+
// Prompt reduced to absolute core
|
| 84 |
+
const prompt = `Put ${product.name} from img 2 on person in img 1. Match lighting.`;
|
| 85 |
|
| 86 |
const requestParams = {
|
| 87 |
model: 'gemini-2.5-flash-image',
|
|
|
|
| 95 |
};
|
| 96 |
|
| 97 |
try {
|
| 98 |
+
console.log("Sending request to Gemini (Atomic Mode)...");
|
| 99 |
const response = await ai.models.generateContent(requestParams);
|
|
|
|
| 100 |
|
| 101 |
+
if (response.candidates?.[0]?.finishReason && response.candidates[0].finishReason !== 'STOP') {
|
| 102 |
+
throw new Error(`AI stopped: ${response.candidates[0].finishReason}`);
|
| 103 |
}
|
| 104 |
|
| 105 |
+
const part = response.candidates?.[0]?.content?.parts?.find(p => p.inlineData);
|
| 106 |
if (part?.inlineData?.data) {
|
| 107 |
return `data:${part.inlineData.mimeType || 'image/png'};base64,${part.inlineData.data}`;
|
| 108 |
}
|
| 109 |
|
| 110 |
+
throw new Error("Empty response from AI.");
|
| 111 |
} catch (error: any) {
|
| 112 |
+
// Log the full error to the console for the developer
|
| 113 |
+
console.error("FULL AI ERROR OBJECT:", JSON.stringify(error, null, 2));
|
| 114 |
+
|
| 115 |
+
const rawMessage = error.message || "Unknown Error";
|
| 116 |
+
|
| 117 |
+
if (rawMessage.includes("RESOURCE_EXHAUSTED") || rawMessage.includes("429")) {
|
| 118 |
+
throw new Error("RATE_LIMIT_EXCEEDED: Free tier allows 1 req/min. Please wait.");
|
| 119 |
}
|
| 120 |
+
|
| 121 |
+
throw new Error(`AI ERROR: ${rawMessage}`);
|
| 122 |
}
|
| 123 |
};
|