feat: implement AI custom tag proposals

- AI now suggests dominant notes not in the system list (Part 3: Custom Suggestions)
- Updated TagSelector to show 'Neu anlegen?' buttons for AI-proposed custom tags
- Added suggested_custom_tags to bottles table and metadata schema
- Updated TastingNoteForm to handle both system and custom AI suggestions
This commit is contained in:
2025-12-19 13:20:13 +01:00
parent b2a1d292da
commit 74916aec73
12 changed files with 216 additions and 29 deletions

View File

@@ -8,7 +8,7 @@ import { createHash } from 'crypto';
import { trackApiUsage } from './track-api-usage';
import { checkCreditBalance, deductCredits } from './credit-service';
export async function analyzeBottle(base64Image: string): Promise<AnalysisResponse> {
export async function analyzeBottle(base64Image: string, tags?: string[]): Promise<AnalysisResponse> {
const supabase = createServerActionClient({ cookies });
if (!process.env.GEMINI_API_KEY) {
@@ -16,7 +16,7 @@ export async function analyzeBottle(base64Image: string): Promise<AnalysisRespon
}
try {
// Ensure user is authenticated for tracking/billing
// ... (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.' };
@@ -24,7 +24,6 @@ export async function analyzeBottle(base64Image: string): Promise<AnalysisRespon
const userId = session.user.id;
// Check credit balance before making API call
const creditCheck = await checkCreditBalance(userId, 'gemini_ai');
if (!creditCheck.allowed) {
return {
@@ -33,12 +32,9 @@ export async function analyzeBottle(base64Image: string): Promise<AnalysisRespon
};
}
// 1. Generate Hash for Caching
const base64Data = base64Image.split(',')[1] || base64Image;
const imageHash = createHash('sha256').update(base64Data).digest('hex');
console.log(`[AI Cache] Checking hash: ${imageHash}`);
// 2. Check Cache
const { data: cachedResult } = await supabase
.from('vision_cache')
.select('result')
@@ -46,16 +42,14 @@ export async function analyzeBottle(base64Image: string): Promise<AnalysisRespon
.maybeSingle();
if (cachedResult) {
console.log(`[AI Cache] Hit! hash: ${imageHash}`);
return {
success: true,
data: cachedResult.result as any,
};
}
console.log(`[AI Cache] Miss. Calling Gemini...`);
const instruction = SYSTEM_INSTRUCTION.replace('{AVAILABLE_TAGS}', tags ? tags.join(', ') : 'No tags available');
// 3. AI Analysis
const result = await geminiModel.generateContent([
{
inlineData: {
@@ -63,7 +57,7 @@ export async function analyzeBottle(base64Image: string): Promise<AnalysisRespon
mimeType: 'image/jpeg',
},
},
{ text: SYSTEM_INSTRUCTION },
{ text: instruction },
]);
const responseText = result.response.text();