feat: Switch to Mistral Large 3 (mistral-large-latest)

This commit is contained in:
2025-12-19 21:59:51 +01:00
parent 25b1378794
commit 2601a8f12f
6 changed files with 19 additions and 19 deletions

2
next-env.d.ts vendored
View File

@@ -1,6 +1,6 @@
/// <reference types="next" /> /// <reference types="next" />
/// <reference types="next/image-types/global" /> /// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts"; import "./.next/dev/types/routes.d.ts";
// NOTE: This file should not be edited // NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

View File

@@ -65,7 +65,7 @@ export default function CameraCapture({ onImageCaptured, onAnalysisComplete, onS
const [isDiscovering, setIsDiscovering] = useState(false); const [isDiscovering, setIsDiscovering] = useState(false);
const [originalFile, setOriginalFile] = useState<File | null>(null); const [originalFile, setOriginalFile] = useState<File | null>(null);
const [isAdmin, setIsAdmin] = useState(false); const [isAdmin, setIsAdmin] = useState(false);
const [aiProvider, setAiProvider] = useState<'gemini' | 'pixtral'>('gemini'); const [aiProvider, setAiProvider] = useState<'gemini' | 'mistral'>('gemini');
React.useEffect(() => { React.useEffect(() => {
const checkAdmin = async () => { const checkAdmin = async () => {
@@ -364,10 +364,10 @@ export default function CameraCapture({ onImageCaptured, onAnalysisComplete, onS
Gemini Gemini
</button> </button>
<button <button
onClick={() => setAiProvider('pixtral')} onClick={() => setAiProvider('mistral')}
className={`px-3 py-1 text-[10px] font-black uppercase tracking-widest rounded-lg transition-all ${aiProvider === 'pixtral' ? 'bg-white dark:bg-zinc-700 text-amber-600 shadow-sm' : 'text-zinc-400'}`} className={`px-3 py-1 text-[10px] font-black uppercase tracking-widest rounded-lg transition-all ${aiProvider === 'mistral' ? 'bg-white dark:bg-zinc-700 text-amber-600 shadow-sm' : 'text-zinc-400'}`}
> >
Pixtral 🇪🇺 Mistral 3 🇪🇺
</button> </button>
</div> </div>
)} )}

View File

@@ -5,7 +5,7 @@ export interface PendingScan {
temp_id: string; // Used to link tasting notes before sync temp_id: string; // Used to link tasting notes before sync
imageBase64: string; imageBase64: string;
timestamp: number; timestamp: number;
provider?: 'gemini' | 'pixtral'; provider?: 'gemini' | 'mistral';
locale?: string; locale?: string;
} }

View File

@@ -7,7 +7,7 @@ import { createHash } from 'crypto';
import { trackApiUsage } from './track-api-usage'; import { trackApiUsage } from './track-api-usage';
import { checkCreditBalance, deductCredits } from './credit-service'; import { checkCreditBalance, deductCredits } from './credit-service';
export async function analyzeBottlePixtral(base64Image: string, tags?: string[], locale: string = 'de'): Promise<AnalysisResponse & { search_string?: string }> { export async function analyzeBottleMistral(base64Image: string, tags?: string[], locale: string = 'de'): Promise<AnalysisResponse & { search_string?: string }> {
if (!process.env.MISTRAL_API_KEY) { if (!process.env.MISTRAL_API_KEY) {
return { success: false, error: 'MISTRAL_API_KEY is not configured.' }; return { success: false, error: 'MISTRAL_API_KEY is not configured.' };
} }
@@ -66,7 +66,7 @@ Antworte AUSSCHLIESSLICH mit gültigem JSON (kein Markdown, kein Text davor/dana
}`; }`;
const chatResponse = await client.chat.complete({ const chatResponse = await client.chat.complete({
model: 'pixtral-large-latest', model: 'mistral-large-latest',
messages: [ messages: [
{ {
role: 'user', role: 'user',
@@ -81,7 +81,7 @@ Antworte AUSSCHLIESSLICH mit gültigem JSON (kein Markdown, kein Text davor/dana
}); });
const rawContent = chatResponse.choices?.[0].message.content; const rawContent = chatResponse.choices?.[0].message.content;
if (!rawContent) throw new Error("Keine Antwort von Pixtral"); if (!rawContent) throw new Error("Keine Antwort von Mistral");
const jsonData = JSON.parse(rawContent as string); const jsonData = JSON.parse(rawContent as string);
@@ -104,12 +104,12 @@ Antworte AUSSCHLIESSLICH mit gültigem JSON (kein Markdown, kein Text davor/dana
await trackApiUsage({ await trackApiUsage({
userId: userId, userId: userId,
apiType: 'gemini_ai', // Keep as generic 'gemini_ai' for now or update schema later apiType: 'gemini_ai', // Keep as generic 'gemini_ai' for now or update schema later
endpoint: 'mistral/pixtral-large', endpoint: 'mistral/mistral-large',
success: true success: true
}); });
// Deduct credits // Deduct credits
await deductCredits(userId, 'gemini_ai', 'Pixtral AI analysis'); await deductCredits(userId, 'gemini_ai', 'Mistral AI analysis');
// Store in Cache // Store in Cache
await supabase await supabase
@@ -123,7 +123,7 @@ Antworte AUSSCHLIESSLICH mit gültigem JSON (kein Markdown, kein Text davor/dana
}; };
} catch (error) { } catch (error) {
console.error('Pixtral Analysis Error:', error); console.error('Mistral Analysis Error:', error);
// Track failed API call // Track failed API call
try { try {
@@ -133,7 +133,7 @@ Antworte AUSSCHLIESSLICH mit gültigem JSON (kein Markdown, kein Text davor/dana
await trackApiUsage({ await trackApiUsage({
userId: session.user.id, userId: session.user.id,
apiType: 'gemini_ai', apiType: 'gemini_ai',
endpoint: 'mistral/pixtral-large', endpoint: 'mistral/mistral-large',
success: false, success: false,
errorMessage: error instanceof Error ? error.message : 'Unknown error' errorMessage: error instanceof Error ? error.message : 'Unknown error'
}); });
@@ -145,7 +145,7 @@ Antworte AUSSCHLIESSLICH mit gültigem JSON (kein Markdown, kein Text davor/dana
return { return {
success: false, success: false,
error: error instanceof Error ? error.message : 'Pixtral AI analysis failed.', error: error instanceof Error ? error.message : 'Mistral AI analysis failed.',
}; };
} }
} }

View File

@@ -1,14 +1,14 @@
'use server'; 'use server';
import { analyzeBottle } from './analyze-bottle'; import { analyzeBottle } from './analyze-bottle';
import { analyzeBottlePixtral } from './analyze-bottle-pixtral'; import { analyzeBottleMistral } from './analyze-bottle-mistral';
import { searchBraveForWhiskybase } from './brave-search'; import { searchBraveForWhiskybase } from './brave-search';
import { getAllSystemTags } from './tags'; import { getAllSystemTags } from './tags';
import { supabase } from '@/lib/supabase'; import { supabase } from '@/lib/supabase';
import { supabaseAdmin } from '@/lib/supabase-admin'; import { supabaseAdmin } from '@/lib/supabase-admin';
import { AnalysisResponse, BottleMetadata } from '@/types/whisky'; import { AnalysisResponse, BottleMetadata } from '@/types/whisky';
export async function magicScan(base64Image: string, provider: 'gemini' | 'pixtral' = 'gemini', locale: string = 'de'): Promise<AnalysisResponse & { wb_id?: string }> { export async function magicScan(base64Image: string, provider: 'gemini' | 'mistral' = 'gemini', locale: string = 'de'): Promise<AnalysisResponse & { wb_id?: string }> {
try { try {
console.log('[magicScan] Starting scan process...'); console.log('[magicScan] Starting scan process...');
if (!supabase) { if (!supabase) {
@@ -22,8 +22,8 @@ export async function magicScan(base64Image: string, provider: 'gemini' | 'pixtr
// 1. AI Analysis // 1. AI Analysis
let aiResponse: any; let aiResponse: any;
if (provider === 'pixtral') { if (provider === 'mistral') {
aiResponse = await analyzeBottlePixtral(base64Image, tagNames, locale); aiResponse = await analyzeBottleMistral(base64Image, tagNames, locale);
} else { } else {
aiResponse = await analyzeBottle(base64Image, tagNames, locale); aiResponse = await analyzeBottle(base64Image, tagNames, locale);
} }

File diff suppressed because one or more lines are too long