feat: improve AI resilience, add background enrichment loading states, and fix duplicate identifier in TagSelector
This commit is contained in:
@@ -36,12 +36,12 @@ export async function analyzeBottleMistral(input: any): Promise<AnalysisResponse
|
||||
|
||||
// 2. Auth & Credits
|
||||
supabase = await createClient();
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
if (!session || !session.user) {
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
if (!user) {
|
||||
return { success: false, error: 'Nicht autorisiert oder Session abgelaufen.' };
|
||||
}
|
||||
|
||||
const userId = session.user.id;
|
||||
const userId = user.id;
|
||||
const creditCheck = await checkCreditBalance(userId, 'gemini_ai');
|
||||
if (!creditCheck.allowed) {
|
||||
return {
|
||||
@@ -85,98 +85,98 @@ export async function analyzeBottleMistral(input: any): Promise<AnalysisResponse
|
||||
|
||||
const prompt = getSystemPrompt(tags.length > 0 ? tags.join(', ') : 'Keine Tags verfügbar', locale);
|
||||
|
||||
const startApi = performance.now();
|
||||
const chatResponse = await client.chat.complete({
|
||||
model: 'mistral-large-latest',
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: [
|
||||
{ type: 'text', text: prompt },
|
||||
{ type: 'image_url', imageUrl: dataUrl }
|
||||
]
|
||||
}
|
||||
],
|
||||
responseFormat: { type: 'json_object' },
|
||||
temperature: 0.1
|
||||
});
|
||||
const endApi = performance.now();
|
||||
|
||||
const startParse = performance.now();
|
||||
const rawContent = chatResponse.choices?.[0].message.content;
|
||||
if (!rawContent) throw new Error("Keine Antwort von Mistral");
|
||||
|
||||
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);
|
||||
const startApi = performance.now();
|
||||
const chatResponse = await client.chat.complete({
|
||||
model: 'mistral-large-latest',
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: [
|
||||
{ type: 'text', text: prompt },
|
||||
{ type: 'image_url', imageUrl: dataUrl }
|
||||
]
|
||||
}
|
||||
],
|
||||
responseFormat: { type: 'json_object' },
|
||||
temperature: 0.1
|
||||
});
|
||||
const endApi = performance.now();
|
||||
|
||||
const startParse = performance.now();
|
||||
const rawContent = chatResponse.choices?.[0].message.content;
|
||||
if (!rawContent) throw new Error("Keine Antwort von Mistral");
|
||||
|
||||
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];
|
||||
console.log('[Mistral AI] JSON Response:', jsonData);
|
||||
|
||||
const searchString = jsonData.search_string;
|
||||
delete jsonData.search_string;
|
||||
|
||||
if (typeof jsonData.abv === 'string') {
|
||||
jsonData.abv = parseFloat(jsonData.abv.replace('%', '').trim());
|
||||
}
|
||||
|
||||
if (jsonData.age) jsonData.age = parseInt(jsonData.age);
|
||||
if (jsonData.vintage) jsonData.vintage = parseInt(jsonData.vintage);
|
||||
|
||||
const validatedData = BottleMetadataSchema.parse(jsonData);
|
||||
const endParse = performance.now();
|
||||
|
||||
await trackApiUsage({
|
||||
userId: userId,
|
||||
apiType: 'gemini_ai',
|
||||
endpoint: 'mistral/mistral-large',
|
||||
success: true
|
||||
});
|
||||
|
||||
await deductCredits(userId, 'gemini_ai', 'Mistral AI analysis');
|
||||
|
||||
await supabase
|
||||
.from('vision_cache')
|
||||
.insert({ hash: imageHash, result: validatedData });
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: validatedData,
|
||||
search_string: searchString,
|
||||
perf: {
|
||||
apiDuration: endApi - startApi,
|
||||
parseDuration: endParse - startParse,
|
||||
uploadSize: uploadSize
|
||||
},
|
||||
raw: jsonData
|
||||
};
|
||||
|
||||
} catch (aiError: any) {
|
||||
console.warn('[MistralAnalysis] AI Analysis failed, providing fallback path:', aiError.message);
|
||||
|
||||
await trackApiUsage({
|
||||
userId: userId,
|
||||
apiType: 'gemini_ai',
|
||||
endpoint: 'mistral/mistral-large',
|
||||
success: false,
|
||||
errorMessage: aiError.message
|
||||
});
|
||||
|
||||
return {
|
||||
success: false,
|
||||
isAiError: true,
|
||||
error: aiError.message,
|
||||
imageHash: imageHash
|
||||
} as any;
|
||||
}
|
||||
|
||||
if (Array.isArray(jsonData)) jsonData = jsonData[0];
|
||||
console.log('[Mistral AI] JSON Response:', jsonData);
|
||||
|
||||
// Extract search_string before validation
|
||||
const searchString = jsonData.search_string;
|
||||
delete jsonData.search_string;
|
||||
|
||||
// Ensure abv is a number if it came as a string
|
||||
if (typeof jsonData.abv === 'string') {
|
||||
jsonData.abv = parseFloat(jsonData.abv.replace('%', '').trim());
|
||||
}
|
||||
|
||||
// Ensure age/vintage are numbers
|
||||
if (jsonData.age) jsonData.age = parseInt(jsonData.age);
|
||||
if (jsonData.vintage) jsonData.vintage = parseInt(jsonData.vintage);
|
||||
|
||||
const validatedData = BottleMetadataSchema.parse(jsonData);
|
||||
const endParse = performance.now();
|
||||
|
||||
// Track usage
|
||||
await trackApiUsage({
|
||||
userId: userId,
|
||||
apiType: 'gemini_ai',
|
||||
endpoint: 'mistral/mistral-large',
|
||||
success: true
|
||||
});
|
||||
|
||||
// Deduct credits
|
||||
await deductCredits(userId, 'gemini_ai', 'Mistral AI analysis');
|
||||
|
||||
// Store in Cache
|
||||
await supabase
|
||||
.from('vision_cache')
|
||||
.insert({ hash: imageHash, result: validatedData });
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: validatedData,
|
||||
search_string: searchString,
|
||||
perf: {
|
||||
apiDuration: endApi - startApi,
|
||||
parseDuration: endParse - startParse,
|
||||
uploadSize: uploadSize
|
||||
},
|
||||
raw: jsonData
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('Mistral Analysis Error:', 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'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
console.error('Mistral Analysis Global Error:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Mistral AI analysis failed.',
|
||||
|
||||
Reference in New Issue
Block a user