'use server'; import { geminiModel, SYSTEM_INSTRUCTION } from '@/lib/gemini'; import { BottleMetadataSchema, AnalysisResponse } from '@/types/whisky'; import { createClient } from '@/lib/supabase/server'; import { createHash } from 'crypto'; import { trackApiUsage } from './track-api-usage'; import { checkCreditBalance, deductCredits } from './credit-service'; export async function analyzeBottle(base64Image: string, tags?: string[], locale: string = 'de'): Promise { if (!process.env.GEMINI_API_KEY) { return { success: false, error: 'GEMINI_API_KEY is not configured.' }; } let supabase; // Declare supabase outside try block for error tracking access try { // Initialize Supabase client inside the try block supabase = await createClient(); console.log('[analyzeBottle] Initialized Supabase client'); // ... (auth and credit check remain same) ... const { data: { session } } = await supabase.auth.getSession(); if (!session || !session.user) { return { success: false, error: 'Nicht autorisiert oder Session abgelaufen.' }; } 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}.` }; } const base64Data = base64Image.split(',')[1] || base64Image; const imageHash = createHash('sha256').update(base64Data).digest('hex'); const { data: cachedResult } = await supabase .from('vision_cache') .select('result') .eq('hash', imageHash) .maybeSingle(); if (cachedResult) { return { success: true, data: cachedResult.result as any, }; } const instruction = SYSTEM_INSTRUCTION .replace('{AVAILABLE_TAGS}', tags ? tags.join(', ') : 'No tags available') .replace('{LANGUAGE}', locale === 'en' ? 'English' : 'German'); const result = await geminiModel.generateContent([ { inlineData: { data: base64Data, mimeType: 'image/jpeg', }, }, { text: instruction }, ]); const responseText = result.response.text(); let jsonData = JSON.parse(responseText); if (Array.isArray(jsonData)) { jsonData = jsonData[0]; } if (!jsonData) { throw new Error('Keine Daten in der KI-Antwort gefunden.'); } const validatedData = BottleMetadataSchema.parse(jsonData); // Track successful API call await trackApiUsage({ userId: userId, apiType: 'gemini_ai', endpoint: 'generateContent', success: true }); // Deduct credits after successful API call const creditDeduction = await deductCredits(userId, 'gemini_ai', 'Bottle analysis'); if (!creditDeduction.success) { console.error('Failed to deduct credits:', creditDeduction.error); // Don't fail the analysis if credit deduction fails } // 4. Store in Cache const { error: storeError } = await supabase .from('vision_cache') .insert({ hash: imageHash, result: validatedData }); if (storeError) { console.warn(`[AI Cache] Storage failed: ${storeError.message}`); } else { console.log(`[AI Cache] Stored new result for hash: ${imageHash}`); } return { success: true, data: validatedData, }; } catch (error) { console.error('Gemini 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: 'generateContent', success: false, errorMessage: error instanceof Error ? error.message : 'Unknown error' }); } } } catch (trackError) { console.error('Failed to track error:', trackError); } return { success: false, error: error instanceof Error ? error.message : 'An unknown error occurred during analysis.', }; } }