feat: Add bulk AI analyzer component to process product data and generate insights.

This commit is contained in:
Michael committed 2026-02-23 00:40:49 +01:00
1 parent 8c02771e46
commit 17498dcc10
4 files changed
+47 -8

No files matched your search

@@ -12,15 +12,18 @@ interface AiInsightBlockProps {
} }
export function AiInsightBlock({ row }: AiInsightBlockProps) { export function AiInsightBlock({ row }: AiInsightBlockProps) {
const { insights, analyzeProduct } = useAiCopilotStore(); const insight = useAiCopilotStore((s) => s.insights[row.codein]);
const setDraftGamme = useGridStore(state => state.setDraftGamme); const analyzeProduct = useAiCopilotStore((s) => s.analyzeProduct);
const insight = insights[row.codein]; const setLoading = useAiCopilotStore((s) => s.setLoading);
const setDraftGamme = useGridStore((s) => s.setDraftGamme);
const status = insight?.status ?? "idle"; const status = insight?.status ?? "idle";
const handleAnalyze = () => { const handleAnalyze = () => {
if (status === "loading") return; if (status === "loading") return;
// Vider la recommandation actuelle avant de relancer l'IA // Reset Visuel Immédiat
setLoading(row.codein);
setDraftGamme(row.codein, "Aucune"); setDraftGamme(row.codein, "Aucune");
const regScore = Object.values(row.sales12m || {}).filter(v => v > 0).length; const regScore = Object.values(row.sales12m || {}).filter(v => v > 0).length;
@@ -14,6 +14,7 @@ interface AiCopilotState {
insights: Record<string, AiInsight>; insights: Record<string, AiInsight>;
setInsight: (codein: string, insight: string, isDuplicate?: boolean) => void; setInsight: (codein: string, insight: string, isDuplicate?: boolean) => void;
setLoading: (codein: string) => void; setLoading: (codein: string) => void;
batchSetLoading: (codeins: string[]) => void;
setError: (codein: string, error: string) => void; setError: (codein: string, error: string) => void;
analyzeProduct: (payload: { analyzeProduct: (payload: {
codein: string; codein: string;
@@ -56,6 +57,16 @@ export const useAiCopilotStore = create<AiCopilotState>()(
})); }));
}, },
batchSetLoading: (codeins) => {
set((s) => {
const nextInsights = { ...s.insights };
codeins.forEach(codein => {
nextInsights[codein] = { codein, insight: "", status: "loading" };
});
return { insights: nextInsights };
});
},
setError: (codein, error) => { setError: (codein, error) => {
set((s) => ({ set((s) => ({
insights: { insights: {
@@ -12,8 +12,9 @@ export function BulkAiAnalyzer() {
const [progress, setProgress] = useState({ current: 0, total: 0, message: "", errors: 0 }); const [progress, setProgress] = useState({ current: 0, total: 0, message: "", errors: 0 });
const isCancelledRef = useRef(false); const isCancelledRef = useRef(false);
const batchSetDraftGamme = useGridStore((s) => s.batchSetDraftGamme);
const setDraftGamme = useGridStore((s) => s.setDraftGamme); const setDraftGamme = useGridStore((s) => s.setDraftGamme);
const { setInsight, setLoading, setError } = useAiCopilotStore(); const { setInsight, batchSetLoading, setError } = useAiCopilotStore();
const handleStop = () => { const handleStop = () => {
isCancelledRef.current = true; isCancelledRef.current = true;
@@ -111,6 +112,14 @@ export function BulkAiAnalyzer() {
const totalItems = productPayloads.length; const totalItems = productPayloads.length;
setProgress({ current: 0, total: totalItems, message: "Initialisation...", errors: 0 }); setProgress({ current: 0, total: totalItems, message: "Initialisation...", errors: 0 });
// Reset global ATOMIQUE et IMMÉDIAT
const codeins = productPayloads.map(p => p.codein);
batchSetLoading(codeins);
const gammeChanges: Record<string, GammeCode> = {};
codeins.forEach(c => gammeChanges[c] = "Aucune");
batchSetDraftGamme(gammeChanges);
const CONCURRENCY = 3; const CONCURRENCY = 3;
const remaining = [...productPayloads]; const remaining = [...productPayloads];
@@ -120,9 +129,6 @@ export function BulkAiAnalyzer() {
const payload = remaining.shift(); const payload = remaining.shift();
if (!payload) break; if (!payload) break;
setLoading(payload.codein);
setDraftGamme(payload.codein, "Aucune");
try { try {
let res: Response | null = null; let res: Response | null = null;
for (let i = 0; i < 3; i++) { for (let i = 0; i < 3; i++) {
+19
View File
@@ -25,6 +25,7 @@ interface GridState {
setDisplayDensity: (density: "compact" | "normal" | "comfortable") => void; setDisplayDensity: (density: "compact" | "normal" | "comfortable") => void;
setActiveGridQuery: (query: string) => void; setActiveGridQuery: (query: string) => void;
restoreSnapshot: (changes: Record<string, GammeCode>) => void; restoreSnapshot: (changes: Record<string, GammeCode>) => void;
batchSetDraftGamme: (changes: Record<string, GammeCode>) => void;
} }
function computeSummary(rows: ProductRow[], drafts: Record<string, GammeCode>): GridSummary { function computeSummary(rows: ProductRow[], drafts: Record<string, GammeCode>): GridSummary {
@@ -105,6 +106,24 @@ export const useGridStore = create<GridState>()(
restoreSnapshot: (changes) => { restoreSnapshot: (changes) => {
set({ draftChanges: changes, summary: computeSummary(get().rows, changes) }); set({ draftChanges: changes, summary: computeSummary(get().rows, changes) });
}, },
batchSetDraftGamme: (newChanges) => {
const { rows, draftChanges: oldDrafts } = get();
const updatedDrafts = { ...oldDrafts };
Object.entries(newChanges).forEach(([codein, gamme]) => {
const originalRow = rows.find(r => r.codein === codein);
const originalGamme = originalRow?.codeGamme;
if (gamme === originalGamme) {
delete updatedDrafts[codein];
} else {
updatedDrafts[codein] = gamme;
}
});
set({ draftChanges: updatedDrafts, summary: computeSummary(rows, updatedDrafts) });
},
}), }),
{ {
name: "collectflow-grid-storage", name: "collectflow-grid-storage",