fix: resolve magic scan crash and implement context-aware AI languages
- 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
This commit is contained in:
@@ -26,7 +26,7 @@ interface CameraCaptureProps {
|
||||
}
|
||||
|
||||
export default function CameraCapture({ onImageCaptured, onAnalysisComplete, onSaveComplete }: CameraCaptureProps) {
|
||||
const { t } = useI18n();
|
||||
const { t, locale } = useI18n();
|
||||
const supabase = createClientComponentClient();
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
@@ -128,13 +128,14 @@ export default function CameraCapture({ onImageCaptured, onAnalysisComplete, onS
|
||||
await db.pending_scans.add({
|
||||
imageBase64: compressedBase64,
|
||||
timestamp: Date.now(),
|
||||
provider: aiProvider
|
||||
provider: aiProvider,
|
||||
locale: locale
|
||||
});
|
||||
setIsQueued(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await magicScan(compressedBase64, aiProvider);
|
||||
const response = await magicScan(compressedBase64, aiProvider, locale);
|
||||
|
||||
if (response.success && response.data) {
|
||||
setAnalysisResult(response.data);
|
||||
|
||||
@@ -37,7 +37,7 @@ export default function UploadQueue() {
|
||||
const itemId = `scan-${item.id}`;
|
||||
setCurrentProgress({ id: itemId, status: 'Analysiere Scan...' });
|
||||
try {
|
||||
const analysis = await analyzeBottle(item.imageBase64);
|
||||
const analysis = await analyzeBottle(item.imageBase64, undefined, item.locale);
|
||||
if (analysis.success && analysis.data) {
|
||||
setCurrentProgress({ id: itemId, status: 'Speichere Flasche...' });
|
||||
const save = await saveBottle(analysis.data, item.imageBase64, user.id);
|
||||
@@ -62,6 +62,7 @@ export default function UploadQueue() {
|
||||
try {
|
||||
const result = await saveTasting({
|
||||
...item.data,
|
||||
bottle_id: item.bottle_id,
|
||||
tasted_at: item.tasted_at
|
||||
});
|
||||
if (result.success) {
|
||||
|
||||
@@ -5,6 +5,7 @@ export interface PendingScan {
|
||||
imageBase64: string;
|
||||
timestamp: number;
|
||||
provider?: 'gemini' | 'nebius';
|
||||
locale?: string;
|
||||
}
|
||||
|
||||
export interface PendingTasting {
|
||||
@@ -45,8 +46,8 @@ export class WhiskyDexie extends Dexie {
|
||||
|
||||
constructor() {
|
||||
super('WhiskyVault');
|
||||
this.version(1).stores({
|
||||
pending_scans: '++id, timestamp',
|
||||
this.version(2).stores({
|
||||
pending_scans: '++id, timestamp, locale',
|
||||
pending_tastings: '++id, bottle_id, tasted_at',
|
||||
cache_tags: 'id, category, name',
|
||||
cache_buddies: 'id, name'
|
||||
|
||||
@@ -19,6 +19,8 @@ Extract precise metadata. If the image is NOT a whisky bottle or if you are very
|
||||
If a value is not visible, use null.
|
||||
Infer the 'Category' (e.g., Islay Single Malt) based on the Distillery if possible.
|
||||
Search specifically for a "Whiskybase ID" or "WB ID" on the label.
|
||||
IMPORTANT: Extract technical metadata (name, distillery, category) in English.
|
||||
The 'suggested_custom_tags' MUST be localized in {LANGUAGE}.
|
||||
|
||||
PART 2: SENSORY ANALYSIS (AUTO-FILL)
|
||||
Based on the identified bottle, select the most appropriate flavor tags.
|
||||
|
||||
@@ -9,7 +9,7 @@ import { createHash } from 'crypto';
|
||||
import { trackApiUsage } from './track-api-usage';
|
||||
import { checkCreditBalance, deductCredits } from './credit-service';
|
||||
|
||||
export async function analyzeBottleNebius(base64Image: string, tags?: string[]): Promise<AnalysisResponse & { search_string?: string }> {
|
||||
export async function analyzeBottleNebius(base64Image: string, tags?: string[], locale: string = 'de'): Promise<AnalysisResponse & { search_string?: string }> {
|
||||
const supabase = createServerActionClient({ cookies });
|
||||
|
||||
if (!process.env.NEBIUS_API_KEY) {
|
||||
@@ -48,7 +48,10 @@ export async function analyzeBottleNebius(base64Image: string, tags?: string[]):
|
||||
};
|
||||
}
|
||||
|
||||
const instruction = GEMINI_SYSTEM_INSTRUCTION.replace('{AVAILABLE_TAGS}', tags ? tags.join(', ') : 'No tags available') + "\nAdditionally, generate a 'search_string' field for Whiskybase in this format: 'site:whiskybase.com [Distillery] [Name] [Vintage]'. Include this field in the JSON object.";
|
||||
const instruction = GEMINI_SYSTEM_INSTRUCTION
|
||||
.replace('{AVAILABLE_TAGS}', tags ? tags.join(', ') : 'No tags available')
|
||||
.replace('{LANGUAGE}', locale === 'en' ? 'English' : 'German')
|
||||
+ "\nAdditionally, generate a 'search_string' field for Whiskybase in this format: 'site:whiskybase.com [Distillery] [Name] [Vintage]'. Include this field in the JSON object.";
|
||||
|
||||
const response = await aiClient.chat.completions.create({
|
||||
model: "Qwen/Qwen2.5-VL-72B-Instruct",
|
||||
|
||||
@@ -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, tags?: string[]): Promise<AnalysisResponse> {
|
||||
export async function analyzeBottle(base64Image: string, tags?: string[], locale: string = 'de'): Promise<AnalysisResponse> {
|
||||
const supabase = createServerActionClient({ cookies });
|
||||
|
||||
if (!process.env.GEMINI_API_KEY) {
|
||||
@@ -48,7 +48,9 @@ export async function analyzeBottle(base64Image: string, tags?: string[]): Promi
|
||||
};
|
||||
}
|
||||
|
||||
const instruction = SYSTEM_INSTRUCTION.replace('{AVAILABLE_TAGS}', tags ? tags.join(', ') : 'No tags available');
|
||||
const instruction = SYSTEM_INSTRUCTION
|
||||
.replace('{AVAILABLE_TAGS}', tags ? tags.join(', ') : 'No tags available')
|
||||
.replace('{LANGUAGE}', locale === 'en' ? 'English' : 'German');
|
||||
|
||||
const result = await geminiModel.generateContent([
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@ import { supabase } from '@/lib/supabase';
|
||||
import { supabaseAdmin } from '@/lib/supabase-admin';
|
||||
import { AnalysisResponse, BottleMetadata } from '@/types/whisky';
|
||||
|
||||
export async function magicScan(base64Image: string, provider: 'gemini' | 'nebius' = 'gemini'): Promise<AnalysisResponse & { wb_id?: string }> {
|
||||
export async function magicScan(base64Image: string, provider: 'gemini' | 'nebius' = 'gemini', locale: string = 'de'): Promise<AnalysisResponse & { wb_id?: string }> {
|
||||
try {
|
||||
// 0. Fetch available tags for constrained generation
|
||||
const systemTags = await getAllSystemTags();
|
||||
@@ -17,9 +17,9 @@ export async function magicScan(base64Image: string, provider: 'gemini' | 'nebiu
|
||||
// 1. AI Analysis
|
||||
let aiResponse: any;
|
||||
if (provider === 'nebius') {
|
||||
aiResponse = await analyzeBottleNebius(base64Image, tagNames);
|
||||
aiResponse = await analyzeBottleNebius(base64Image, tagNames, locale);
|
||||
} else {
|
||||
aiResponse = await analyzeBottle(base64Image, tagNames);
|
||||
aiResponse = await analyzeBottle(base64Image, tagNames, locale);
|
||||
}
|
||||
|
||||
if (!aiResponse.success || !aiResponse.data) {
|
||||
@@ -38,7 +38,10 @@ export async function magicScan(base64Image: string, provider: 'gemini' | 'nebiu
|
||||
const { data: cacheHit } = await supabase
|
||||
.from('global_products')
|
||||
.select('wb_id')
|
||||
.textSearch('search_vector', `'${searchString}'`, { config: 'simple' })
|
||||
.textSearch('search_vector', searchString, {
|
||||
config: 'simple',
|
||||
type: 'websearch'
|
||||
})
|
||||
.limit(1)
|
||||
.maybeSingle();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user