- Fixed SQL syntax error in magicScan caused by single quotes - Implemented dynamic locale-aware AI suggestions (Technical: EN, Custom Tags: Localized) - Updated Dexie schema to version 2 (added locale to pending_scans) - Fixed missing bottle_id in UploadQueue synchronization - Installed missing dexie dependencies via pnpm
133 lines
4.5 KiB
TypeScript
133 lines
4.5 KiB
TypeScript
'use server';
|
|
|
|
import { geminiModel, SYSTEM_INSTRUCTION } from '@/lib/gemini';
|
|
import { BottleMetadataSchema, AnalysisResponse } from '@/types/whisky';
|
|
import { createServerActionClient } from '@supabase/auth-helpers-nextjs';
|
|
import { cookies } from 'next/headers';
|
|
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<AnalysisResponse> {
|
|
const supabase = createServerActionClient({ cookies });
|
|
|
|
if (!process.env.GEMINI_API_KEY) {
|
|
return { success: false, error: 'GEMINI_API_KEY is not configured.' };
|
|
}
|
|
|
|
try {
|
|
// ... (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 {
|
|
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.',
|
|
};
|
|
}
|
|
}
|