feat: robust FormData handling for AI services to improve N100 performance and stability

This commit is contained in:
2025-12-22 10:36:26 +01:00
parent 5e35710b67
commit 7d06ba7a57
5 changed files with 169 additions and 88 deletions

View File

@@ -8,13 +8,33 @@ import { createHash } from 'crypto';
import { trackApiUsage } from './track-api-usage';
import { checkCreditBalance, deductCredits } from './credit-service';
export async function analyzeBottleMistral(base64Image: string, tags?: string[], locale: string = 'de'): Promise<AnalysisResponse & { search_string?: string }> {
// WICHTIG: Wir akzeptieren jetzt FormData statt Strings
export async function analyzeBottleMistral(input: any): Promise<AnalysisResponse & { search_string?: string }> {
if (!process.env.MISTRAL_API_KEY) {
return { success: false, error: 'MISTRAL_API_KEY is not configured.' };
}
let supabase;
try {
// Helper to get value from either FormData or POJO
const getValue = (obj: any, key: string): any => {
if (obj && typeof obj.get === 'function') return obj.get(key);
if (obj && typeof obj[key] !== 'undefined') return obj[key];
return null;
};
// 1. Daten extrahieren
const file = getValue(input, 'file') as File;
const tagsString = getValue(input, 'tags') as string;
const locale = getValue(input, 'locale') || 'de';
if (!file) {
return { success: false, error: 'Kein Bild empfangen.' };
}
const tags = tagsString ? (typeof tagsString === 'string' ? JSON.parse(tagsString) : tagsString) : [];
// 2. Auth & Credits
supabase = await createClient();
const { data: { session } } = await supabase.auth.getSession();
if (!session || !session.user) {
@@ -22,18 +42,22 @@ export async function analyzeBottleMistral(base64Image: string, tags?: string[],
}
const userId = session.user.id;
const creditCheck = await checkCreditBalance(userId, 'gemini_ai');
if (!creditCheck.allowed) {
return {
success: false,
error: `Nicht genügend Credits. Du benötigst ${creditCheck.cost} Credits, hast aber nur ${creditCheck.balance}.`
error: `Nicht genügend Credits. Benötigt: ${creditCheck.cost}, Verfügbar: ${creditCheck.balance}.`
};
}
const base64Data = base64Image.split(',')[1] || base64Image;
const imageHash = createHash('sha256').update(base64Data).digest('hex');
// 3. Datei in Buffer umwandeln
const arrayBuffer = await file.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
// 4. Hash für Cache erstellen
const imageHash = createHash('sha256').update(buffer).digest('hex');
// Cache Check
const { data: cachedResult } = await supabase
.from('vision_cache')
.select('result')
@@ -47,10 +71,13 @@ export async function analyzeBottleMistral(base64Image: string, tags?: string[],
};
}
// 5. Für Mistral vorbereiten
const client = new Mistral({ apiKey: process.env.MISTRAL_API_KEY });
const dataUrl = `data:image/webp;base64,${base64Data}`;
const base64Data = buffer.toString('base64');
const mimeType = file.type || 'image/webp';
const dataUrl = `data:${mimeType};base64,${base64Data}`;
const prompt = getSystemPrompt(tags ? tags.join(', ') : 'Keine Tags verfügbar', locale);
const prompt = getSystemPrompt(tags.length > 0 ? tags.join(', ') : 'Keine Tags verfügbar', locale);
const chatResponse = await client.chat.complete({
model: 'mistral-large-latest',
@@ -70,7 +97,15 @@ export async function analyzeBottleMistral(base64Image: string, tags?: string[],
const rawContent = chatResponse.choices?.[0].message.content;
if (!rawContent) throw new Error("Keine Antwort von Mistral");
const jsonData = JSON.parse(rawContent as string);
let jsonData;
try {
jsonData = JSON.parse(rawContent as string);
} catch (e) {
const cleanedText = (rawContent as string).replace(/```json/g, '').replace(/```/g, '');
jsonData = JSON.parse(cleanedText);
}
if (Array.isArray(jsonData)) jsonData = jsonData[0];
// Extract search_string before validation
const searchString = jsonData.search_string;
@@ -90,7 +125,7 @@ export async function analyzeBottleMistral(base64Image: string, tags?: string[],
// Track usage
await trackApiUsage({
userId: userId,
apiType: 'gemini_ai', // Keep as generic 'gemini_ai' for now or update schema later
apiType: 'gemini_ai',
endpoint: 'mistral/mistral-large',
success: true
});
@@ -112,22 +147,17 @@ export async function analyzeBottleMistral(base64Image: string, tags?: string[],
} catch (error) {
console.error('Mistral Analysis Error:', error);
// Track failed API call
try {
if (supabase) {
const { data: { session } } = await supabase.auth.getSession();
if (session?.user) {
await trackApiUsage({
userId: session.user.id,
apiType: 'gemini_ai',
endpoint: 'mistral/mistral-large',
success: false,
errorMessage: error instanceof Error ? error.message : 'Unknown error'
});
}
if (supabase) {
const { data: { session } } = await supabase.auth.getSession();
if (session?.user) {
await trackApiUsage({
userId: session.user.id,
apiType: 'gemini_ai',
endpoint: 'mistral/mistral-large',
success: false,
errorMessage: error instanceof Error ? error.message : 'Unknown error'
});
}
} catch (trackError) {
console.error('Failed to track error:', trackError);
}
return {