From a3fb486477273938be4c1d42dce7276318ee2184 Mon Sep 17 00:00:00 2001 From: OpenSquared Date: Wed, 17 Jun 2026 00:10:41 +0200 Subject: [PATCH] feat: delete AI reports, Super Contexte versions, and KB entries - database.py: add delete_ai_report(), delete_reasoning_state(), delete_kb_entry() - reasoning.py: DELETE /api/reasoning/reports/{id} - knowledge.py: DELETE /api/knowledge/history/{id} and /entries/{id} - useApi.ts: useDeleteAiReport, useDeleteReasoningState, useDeleteKbEntry hooks - RapportIA.tsx: trash icon on hover in archived reports sidebar - SuperContexte.tsx: trash icon on hover for history versions and KB entries; both propagate onDelete through CategorySection down to KbEntry Co-Authored-By: Claude Sonnet 4.6 --- backend/routers/knowledge.py | 19 ++++++++ backend/routers/reasoning.py | 10 ++++ backend/services/database.py | 24 ++++++++++ frontend/src/hooks/useApi.ts | 27 +++++++++++ frontend/src/pages/RapportIA.tsx | 58 +++++++++++++++-------- frontend/src/pages/SuperContexte.tsx | 69 ++++++++++++++++++++++------ 6 files changed, 174 insertions(+), 33 deletions(-) diff --git a/backend/routers/knowledge.py b/backend/routers/knowledge.py index b8873a8..aadd4bd 100644 --- a/backend/routers/knowledge.py +++ b/backend/routers/knowledge.py @@ -8,6 +8,7 @@ from services.database import ( get_kb_entries, get_all_kb_entries, save_kb_entry, update_kb_entry_status, get_latest_reasoning_state, get_reasoning_history, get_reasoning_state_by_id, save_reasoning_state, list_ai_reports, get_mtm_trades_with_traces, _trade_maturity, + delete_reasoning_state, delete_kb_entry, ) router = APIRouter(prefix="/api/knowledge", tags=["knowledge"]) @@ -174,6 +175,24 @@ def get_state_version(state_id: int): return {"state": state} +@router.delete("/history/{state_id}") +def delete_state_version(state_id: int): + """Delete a reasoning state version by ID.""" + deleted = delete_reasoning_state(state_id) + if not deleted: + raise HTTPException(404, "Version introuvable") + return {"deleted": True, "id": state_id} + + +@router.delete("/entries/{entry_id}") +def delete_entry(entry_id: int): + """Permanently delete a KB entry.""" + deleted = delete_kb_entry(entry_id) + if not deleted: + raise HTTPException(404, "Entrée introuvable") + return {"deleted": True, "id": entry_id} + + @router.get("/entries") def list_entries(status: str = "all"): if status == "all": diff --git a/backend/routers/reasoning.py b/backend/routers/reasoning.py index 948f651..c60fd2a 100644 --- a/backend/routers/reasoning.py +++ b/backend/routers/reasoning.py @@ -21,6 +21,7 @@ from services.database import ( get_trade_entry_by_id, list_ai_reports, save_ai_report, + delete_ai_report, _trade_maturity, ) @@ -369,3 +370,12 @@ def get_report(report_id: int): if not report: raise HTTPException(404, f"Report {report_id} not found") return report + + +@router.delete("/reports/{report_id}") +def delete_report(report_id: int): + """Delete an archived AI report by ID.""" + deleted = delete_ai_report(report_id) + if not deleted: + raise HTTPException(404, f"Report {report_id} not found") + return {"deleted": True, "id": report_id} diff --git a/backend/services/database.py b/backend/services/database.py index f1d357a..4e4823d 100644 --- a/backend/services/database.py +++ b/backend/services/database.py @@ -1451,3 +1451,27 @@ def get_reasoning_state_by_id(state_id: int) -> Optional[Dict[str, Any]]: except Exception: d["synthesis"] = {} return d + + +def delete_ai_report(report_id: int) -> bool: + conn = get_conn() + cur = conn.execute("DELETE FROM ai_reports WHERE id=?", (report_id,)) + conn.commit() + conn.close() + return cur.rowcount > 0 + + +def delete_reasoning_state(state_id: int) -> bool: + conn = get_conn() + cur = conn.execute("DELETE FROM reasoning_state WHERE id=?", (state_id,)) + conn.commit() + conn.close() + return cur.rowcount > 0 + + +def delete_kb_entry(entry_id: int) -> bool: + conn = get_conn() + cur = conn.execute("DELETE FROM knowledge_base WHERE id=?", (entry_id,)) + conn.commit() + conn.close() + return cur.rowcount > 0 diff --git a/frontend/src/hooks/useApi.ts b/frontend/src/hooks/useApi.ts index c709188..3d125b7 100644 --- a/frontend/src/hooks/useApi.ts +++ b/frontend/src/hooks/useApi.ts @@ -546,3 +546,30 @@ export const usePatchKbEntryStatus = () => { onSuccess: () => qc.invalidateQueries({ queryKey: ['knowledge-entries'] }), }) } + +export const useDeleteAiReport = () => { + const qc = useQueryClient() + return useMutation({ + mutationFn: (id: number) => api.delete(`/reasoning/reports/${id}`).then(r => r.data), + onSuccess: () => qc.invalidateQueries({ queryKey: ['ai-reports-list'] }), + }) +} + +export const useDeleteReasoningState = () => { + const qc = useQueryClient() + return useMutation({ + mutationFn: (id: number) => api.delete(`/knowledge/history/${id}`).then(r => r.data), + onSuccess: () => { + qc.invalidateQueries({ queryKey: ['knowledge-history'] }) + qc.invalidateQueries({ queryKey: ['knowledge-state'] }) + }, + }) +} + +export const useDeleteKbEntry = () => { + const qc = useQueryClient() + return useMutation({ + mutationFn: (id: number) => api.delete(`/knowledge/entries/${id}`).then(r => r.data), + onSuccess: () => qc.invalidateQueries({ queryKey: ['knowledge-entries'] }), + }) +} diff --git a/frontend/src/pages/RapportIA.tsx b/frontend/src/pages/RapportIA.tsx index 7e5ea19..ca5bf28 100644 --- a/frontend/src/pages/RapportIA.tsx +++ b/frontend/src/pages/RapportIA.tsx @@ -1,7 +1,7 @@ import { useState } from 'react' -import { Brain, TrendingUp, TrendingDown, RefreshCw, Zap, AlertTriangle, BookOpen, Target, Eye, Clock, ChevronRight } from 'lucide-react' +import { Brain, TrendingUp, TrendingDown, RefreshCw, Zap, AlertTriangle, BookOpen, Target, Eye, Clock, ChevronRight, Trash2 } from 'lucide-react' import clsx from 'clsx' -import { usePortfolioReportData, useGeneratePortfolioReport, useAiReportsList, useAiReport } from '../hooks/useApi' +import { usePortfolioReportData, useGeneratePortfolioReport, useAiReportsList, useAiReport, useDeleteAiReport } from '../hooks/useApi' import { useQueryClient } from '@tanstack/react-query' const SCENARIO_META: Record = { @@ -202,6 +202,7 @@ export default function RapportIA() { const { mutate: generate, isPending: generating, data: freshReportData, reset: resetFresh } = useGeneratePortfolioReport() const { data: historyList } = useAiReportsList() const { data: historicReport } = useAiReport(selectedHistoryId) + const { mutate: deleteReport } = useDeleteAiReport() const history: any[] = (historyList as any)?.reports ?? [] @@ -247,29 +248,46 @@ export default function RapportIA() { const date = r.created_at?.slice(0, 16).replace('T', ' ') ?? '' const isActive = selectedHistoryId === r.id return ( - + + + ) })} diff --git a/frontend/src/pages/SuperContexte.tsx b/frontend/src/pages/SuperContexte.tsx index 34980f3..d9ba9f2 100644 --- a/frontend/src/pages/SuperContexte.tsx +++ b/frontend/src/pages/SuperContexte.tsx @@ -3,11 +3,12 @@ import { Brain, RefreshCw, Clock, ChevronDown, ChevronUp, AlertTriangle, Target, TrendingUp, TrendingDown, Shield, Zap, BookOpen, CheckCircle, XCircle, HelpCircle, - BarChart2, Activity, + BarChart2, Activity, Trash2, } from 'lucide-react' import { useKnowledgeState, useKnowledgeHistory, useKnowledgeStateVersion, useKnowledgeEntries, useSynthesizeKnowledge, usePatchKbEntryStatus, + useDeleteReasoningState, useDeleteKbEntry, } from '../hooks/useApi' // ─── Status badge ───────────────────────────────────────────────────────────── @@ -39,10 +40,14 @@ function ConfidenceBar({ value }: { value: number }) { } // ─── KB Entry card ──────────────────────────────────────────────────────────── -function KbEntry({ entry, onStatusChange }: { entry: any; onStatusChange: (id: number, s: string) => void }) { +function KbEntry({ entry, onStatusChange, onDelete }: { + entry: any + onStatusChange: (id: number, s: string) => void + onDelete: (id: number) => void +}) { const [open, setOpen] = useState(false) return ( -
+
@@ -54,6 +59,15 @@ function KbEntry({ entry, onStatusChange }: { entry: any; onStatusChange: (id: n +
{open && (
@@ -99,8 +113,13 @@ const CAT_META: Record void }) { + category, entries, onStatusChange, onDelete, +}: { + category: string + entries: any[] + onStatusChange: (id: number, s: string) => void + onDelete: (id: number) => void +}) { const [open, setOpen] = useState(true) const meta = CAT_META[category] || CAT_META['général'] const active = entries.filter(e => e.status !== 'invalidated').length @@ -120,7 +139,7 @@ function CategorySection({ {open && (
{entries.map(e => ( - + ))}
)} @@ -176,6 +195,8 @@ export default function SuperContexte() { const { data: versionData } = useKnowledgeStateVersion(selectedHistoryId) const synthesize = useSynthesizeKnowledge() const patchStatus = usePatchKbEntryStatus() + const deleteState = useDeleteReasoningState() + const deleteEntry = useDeleteKbEntry() const currentState = selectedHistoryId ? versionData?.state @@ -202,6 +223,10 @@ export default function SuperContexte() { patchStatus.mutate({ id, status }) } + const handleDeleteEntry = (id: number) => { + deleteEntry.mutate(id) + } + return (
{/* Header */} @@ -255,15 +280,32 @@ export default function SuperContexte() { Dernière version {history.map((h: any) => ( - + + +
))}
)} @@ -466,6 +508,7 @@ export default function SuperContexte() { category={cat} entries={entries} onStatusChange={handleStatusChange} + onDelete={handleDeleteEntry} /> ))}