mirror of
https://github.com/R0m1k3/CollectFlow.git
synced 2026-10-17 12:19:50 +02:00
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:
1 parent
2aa9cbecaa
commit
bbb04a4d2a
3 files changed
+32
-9
No files matched your search
@@ -54,8 +54,22 @@ export async function POST(req: NextRequest) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const client = new OpenRouterClient({ apiKey, model });
|
const FALLBACK_MODEL = "google/gemini-2.0-flash-001";
|
||||||
const result = await client.analyzeProduct(enrichedBody);
|
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);
|
return NextResponse.json(result);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -107,14 +107,12 @@ REPONDS EN JSON VALIDE uniquement :
|
|||||||
const userPrompt = `${products.length} produits du rayon "${rayon}" (sur ${supplierStats?.totalProducts ?? products.length} au total).
|
const userPrompt = `${products.length} produits du rayon "${rayon}" (sur ${supplierStats?.totalProducts ?? products.length} au total).
|
||||||
${JSON.stringify(products, null, 2)}`;
|
${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",
|
method: "POST",
|
||||||
headers: {
|
headers: { "Authorization": `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
||||||
"Authorization": `Bearer ${apiKey}`,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
model,
|
model: modelToTry,
|
||||||
messages: [
|
messages: [
|
||||||
{ role: "system", content: systemPrompt },
|
{ role: "system", content: systemPrompt },
|
||||||
{ role: "user", content: userPrompt }
|
{ 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) {
|
if (response.status === 429) {
|
||||||
const retryAfter = response.headers.get("Retry-After") || response.headers.get("x-ratelimit-reset-requests");
|
const retryAfter = response.headers.get("Retry-After") || response.headers.get("x-ratelimit-reset-requests");
|
||||||
const waitSeconds = retryAfter ? parseInt(retryAfter, 10) : 60;
|
const waitSeconds = retryAfter ? parseInt(retryAfter, 10) : 60;
|
||||||
|
|||||||
@@ -41,6 +41,10 @@ export function BulkAiAnalyzer() {
|
|||||||
const { rows } = useGridStore.getState();
|
const { rows } = useGridStore.getState();
|
||||||
isCancelledRef.current = false;
|
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
|
// 1. Fetch AI Context for the Supplier
|
||||||
let supplierContext = "";
|
let supplierContext = "";
|
||||||
if (supplierCode) {
|
if (supplierCode) {
|
||||||
@@ -166,7 +170,6 @@ export function BulkAiAnalyzer() {
|
|||||||
|
|
||||||
// 2c. Context profiling en micro-batches asynchrones
|
// 2c. Context profiling en micro-batches asynchrones
|
||||||
// Le score est déjà calculé par score-engine.ts (champ row.score sur chaque ProductRow).
|
// 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 });
|
setProgress({ current: 0, total: payloadsWithSales.length, message: "Calcul du contexte...", errors: 0 });
|
||||||
|
|
||||||
const SCORING_BATCH_SIZE = 25;
|
const SCORING_BATCH_SIZE = 25;
|
||||||
|
|||||||
Reference in new issue
Block a user