fix: immediate AI loading feedback + auto-fallback for deprecated model

- BulkAiAnalyzer: set isAnalyzing=true immediately on click (before
  context fetch), so the spinner appears instantly instead of after
  1-2 seconds of silence
- analyze/route.ts: if configured model returns 400/404 (e.g., deprecated
  google/gemini-flash-1.5), automatically retry with google/gemini-2.0-flash-001
- batch-analyze/route.ts: same fallback logic

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
MichaelandClaude Sonnet 4.6 committed 2026-04-03 11:34:14 +02:00
1 parent 2aa9cbecaa
commit bbb04a4d2a
3 files changed
+32 -9

No files matched your search

+16 -2
View File
@@ -54,8 +54,22 @@ export async function POST(req: NextRequest) {
}
}
const client = new OpenRouterClient({ apiKey, model });
const result = await client.analyzeProduct(enrichedBody);
const FALLBACK_MODEL = "google/gemini-2.0-flash-001";
let client = new OpenRouterClient({ apiKey, model });
let result;
try {
result = await client.analyzeProduct(enrichedBody);
} catch (modelErr) {
// If configured model fails (e.g., deprecated), retry with fallback
const msg = modelErr instanceof Error ? modelErr.message : "";
if (model !== FALLBACK_MODEL && (msg.includes("400") || msg.includes("404") || msg.includes("OpenRouter error"))) {
console.warn(`[AI] Model "${model}" failed (${msg}), retrying with fallback ${FALLBACK_MODEL}`);
client = new OpenRouterClient({ apiKey, model: FALLBACK_MODEL });
result = await client.analyzeProduct(enrichedBody);
} else {
throw modelErr;
}
}
return NextResponse.json(result);
} catch (err) {
+12 -6
View File
@@ -107,14 +107,12 @@ REPONDS EN JSON VALIDE uniquement :
const userPrompt = `${products.length} produits du rayon "${rayon}" (sur ${supplierStats?.totalProducts ?? products.length} au total).
${JSON.stringify(products, null, 2)}`;
const response = await fetch("https://openrouter.ai/api/v1/chat/completions", {
const FALLBACK_MODEL = "google/gemini-2.0-flash-001";
const tryModel = async (modelToTry: string) => fetch("https://openrouter.ai/api/v1/chat/completions", {
method: "POST",
headers: {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
headers: { "Authorization": `Bearer ${apiKey}`, "Content-Type": "application/json" },
body: JSON.stringify({
model,
model: modelToTry,
messages: [
{ role: "system", content: systemPrompt },
{ role: "user", content: userPrompt }
@@ -124,6 +122,14 @@ ${JSON.stringify(products, null, 2)}`;
}),
});
let response = await tryModel(model);
// Fallback if configured model is deprecated/not found
if (!response.ok && response.status === 400 && model !== FALLBACK_MODEL) {
console.warn(`[batch-analyze] Model "${model}" returned 400, retrying with fallback ${FALLBACK_MODEL}`);
response = await tryModel(FALLBACK_MODEL);
}
if (response.status === 429) {
const retryAfter = response.headers.get("Retry-After") || response.headers.get("x-ratelimit-reset-requests");
const waitSeconds = retryAfter ? parseInt(retryAfter, 10) : 60;
@@ -41,6 +41,10 @@ export function BulkAiAnalyzer() {
const { rows } = useGridStore.getState();
isCancelledRef.current = false;
// Show immediate visual feedback before any async work
setIsAnalyzing(true);
setProgress({ current: 0, total: 0, message: "Chargement du contexte...", errors: 0 });
// 1. Fetch AI Context for the Supplier
let supplierContext = "";
if (supplierCode) {
@@ -166,7 +170,6 @@ export function BulkAiAnalyzer() {
// 2c. Context profiling en micro-batches asynchrones
// Le score est déjà calculé par score-engine.ts (champ row.score sur chaque ProductRow).
setIsAnalyzing(true);
setProgress({ current: 0, total: payloadsWithSales.length, message: "Calcul du contexte...", errors: 0 });
const SCORING_BATCH_SIZE = 25;