From 02fdd18a52fc5b03ee98c0208765c8d598301a00 Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Fri, 27 Feb 2026 10:36:34 +0100 Subject: [PATCH] feat: implement AI product analysis endpoint using OpenRouter with optimized timeout handling and N+1 query prevention. --- src/app/api/ai/analyze/route.ts | 20 +++---------------- .../ai-copilot/data/open-router-client.ts | 6 +++--- task.md | 5 ++++- 3 files changed, 10 insertions(+), 21 deletions(-) diff --git a/src/app/api/ai/analyze/route.ts b/src/app/api/ai/analyze/route.ts index cb1557d..d379861 100644 --- a/src/app/api/ai/analyze/route.ts +++ b/src/app/api/ai/analyze/route.ts @@ -2,9 +2,6 @@ import { NextRequest, NextResponse } from "next/server"; import { getSavedDatabaseConfig } from "@/features/settings/actions"; import { OpenRouterClient } from "@/features/ai-copilot/data/open-router-client"; import { ProductAnalysisInput } from "@/features/ai-copilot/models/ai-analysis.types"; -import { db } from "@/db"; -import { aiSupplierContext } from "@/db/schema"; -import { eq } from "drizzle-orm"; // Limite max acceptable pour la route (Node.js self-hosted). // Evite que le process tourne indéfiniment en cas de deadlock. @@ -23,20 +20,9 @@ export async function POST(req: NextRequest) { try { const body: ProductAnalysisInput = await req.json(); - // Inject supplier context if available - if (body.codeFournisseur) { - try { - const contextRecord = await db.select() - .from(aiSupplierContext) - .where(eq(aiSupplierContext.codeFournisseur, body.codeFournisseur)) - .limit(1); - if (contextRecord.length > 0 && contextRecord[0].context) { - body.supplierContext = contextRecord[0].context; - } - } catch (err) { - console.error("[AI] Failed to fetch supplier context:", err); - } - } + // Le supplierContext est déjà injecté par le client (BulkAiAnalyzer) + // via une requête unique avant de lancer le batch. + // Cela nous évite une requête DB N+1 pour chaque produit analysé. const client = new OpenRouterClient({ apiKey, model }); const result = await client.analyzeProduct(body); diff --git a/src/features/ai-copilot/data/open-router-client.ts b/src/features/ai-copilot/data/open-router-client.ts index 1db6e2d..07f4552 100644 --- a/src/features/ai-copilot/data/open-router-client.ts +++ b/src/features/ai-copilot/data/open-router-client.ts @@ -12,10 +12,10 @@ export class OpenRouterClient { constructor(private config: OpenRouterConfig) { } async analyzeProduct(p: ProductAnalysisInput): Promise { - // Timeout de 25 secondes : évite le blocage indéfini si OpenRouter est lent. - // On préfère un échec propre avec message d'erreur plutôt qu'un timeout navigateur. + // Timeout de 50 secondes : évite le blocage indéfini si OpenRouter est lent, + // tout en laissant assez de temps aux modèles complexes pour répondre (maxDuration serveur = 55s). const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), 25_000); + const timeoutId = setTimeout(() => controller.abort(), 50_000); let response: Response; try { diff --git a/task.md b/task.md index 1580514..3049013 100644 --- a/task.md +++ b/task.md @@ -25,4 +25,7 @@ Mary (IA) donnait des recommandations basées sur le score brut sans analyse con - **Correction IA** : Algorithme Mary v4 implémenté avec profilage contextuel et groupement N2. Le bug de classification 'A' systématique est résolu. - **Refonte Esthétique** : Bouton de sauvegarde et design system harmonisés en style Apple Glossy/Premium avec micro-animations et feedback visuel. -- **Vérification** : Walkthrough documenté. +- **Agent Architecte (Timeout)** : Intervention structurelle sur l'entonnoir d'analyse : + - **Client-Side (AbortController)** : Timeout poussé de 25s à 50s pour supporter les requêtes des LLM denses (ex: M37). + - **Server-Side (N+1 Query)** : Suppression d'une requête BDD redondante invoquée par chaque produit, éliminant ~100 appels BDD par lot. +- **Vérification** : Walkthrough documenté et système fluidifié.