Spaces:
Running
Running
Fabio Antonini Claude Sonnet 4.6 commited on
Commit ·
01c12ca
1
Parent(s): fb4aa20
feat: add Clear analyses button to project detail page
Browse files- Backend: DELETE /analysis/project/{project_id} endpoint
- Frontend: Cancella analisi button with confirm dialog (visible only when analyses exist)
- i18n: add clear_analyses and clear_analyses_confirm keys in it/en
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- backend/routers/analysis.py +12 -1
- frontend/src/i18n/en.json +2 -0
- frontend/src/i18n/it.json +2 -0
- frontend/src/lib/api.ts +1 -0
- frontend/src/pages/ProjectDetailPage.tsx +16 -2
backend/routers/analysis.py
CHANGED
|
@@ -29,7 +29,7 @@ from fastapi import APIRouter, Depends, HTTPException, status
|
|
| 29 |
from fastapi.responses import StreamingResponse
|
| 30 |
from PIL import Image
|
| 31 |
from pydantic import BaseModel
|
| 32 |
-
from sqlalchemy import select
|
| 33 |
from sqlalchemy.ext.asyncio import AsyncSession
|
| 34 |
|
| 35 |
from backend.auth.dependencies import get_current_user
|
|
@@ -283,3 +283,14 @@ async def list_analyses(
|
|
| 283 |
.order_by(Analysis.created_at.desc())
|
| 284 |
)
|
| 285 |
return result.scalars().all()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
from fastapi.responses import StreamingResponse
|
| 30 |
from PIL import Image
|
| 31 |
from pydantic import BaseModel
|
| 32 |
+
from sqlalchemy import delete, select
|
| 33 |
from sqlalchemy.ext.asyncio import AsyncSession
|
| 34 |
|
| 35 |
from backend.auth.dependencies import get_current_user
|
|
|
|
| 283 |
.order_by(Analysis.created_at.desc())
|
| 284 |
)
|
| 285 |
return result.scalars().all()
|
| 286 |
+
|
| 287 |
+
|
| 288 |
+
@router.delete("/project/{project_id}", status_code=status.HTTP_204_NO_CONTENT)
|
| 289 |
+
async def clear_analyses(
|
| 290 |
+
project_id: int,
|
| 291 |
+
db: AsyncSession = Depends(get_db),
|
| 292 |
+
current_user: User = Depends(get_current_user),
|
| 293 |
+
) -> None:
|
| 294 |
+
await _check_project_access(project_id, db, current_user)
|
| 295 |
+
await db.execute(delete(Analysis).where(Analysis.project_id == project_id))
|
| 296 |
+
await db.commit()
|
frontend/src/i18n/en.json
CHANGED
|
@@ -58,6 +58,8 @@
|
|
| 58 |
"pipeline": "Full forensic pipeline",
|
| 59 |
"dating": "Document dating"
|
| 60 |
},
|
|
|
|
|
|
|
| 61 |
"running": "Running analysis…",
|
| 62 |
"done": "Analysis complete"
|
| 63 |
},
|
|
|
|
| 58 |
"pipeline": "Full forensic pipeline",
|
| 59 |
"dating": "Document dating"
|
| 60 |
},
|
| 61 |
+
"clear_analyses": "Clear analyses",
|
| 62 |
+
"clear_analyses_confirm": "Delete all analyses for this case? This cannot be undone.",
|
| 63 |
"running": "Running analysis…",
|
| 64 |
"done": "Analysis complete"
|
| 65 |
},
|
frontend/src/i18n/it.json
CHANGED
|
@@ -58,6 +58,8 @@
|
|
| 58 |
"pipeline": "Perizia forense completa",
|
| 59 |
"dating": "Datazione documento"
|
| 60 |
},
|
|
|
|
|
|
|
| 61 |
"running": "Analisi in corso…",
|
| 62 |
"done": "Analisi completata",
|
| 63 |
"select_reference_doc": "Seleziona documento di riferimento",
|
|
|
|
| 58 |
"pipeline": "Perizia forense completa",
|
| 59 |
"dating": "Datazione documento"
|
| 60 |
},
|
| 61 |
+
"clear_analyses": "Cancella analisi",
|
| 62 |
+
"clear_analyses_confirm": "Eliminare tutte le analisi di questa perizia? L'operazione non è reversibile.",
|
| 63 |
"running": "Analisi in corso…",
|
| 64 |
"done": "Analisi completata",
|
| 65 |
"select_reference_doc": "Seleziona documento di riferimento",
|
frontend/src/lib/api.ts
CHANGED
|
@@ -156,6 +156,7 @@ export const analysisApi = {
|
|
| 156 |
runPipeline: (projectId: number, documentId: number) =>
|
| 157 |
api.post<Analysis>("/analysis/pipeline", { project_id: projectId, document_id: documentId }),
|
| 158 |
list: (projectId: number) => api.get<Analysis[]>(`/analysis/project/${projectId}`),
|
|
|
|
| 159 |
}
|
| 160 |
|
| 161 |
// RAG
|
|
|
|
| 156 |
runPipeline: (projectId: number, documentId: number) =>
|
| 157 |
api.post<Analysis>("/analysis/pipeline", { project_id: projectId, document_id: documentId }),
|
| 158 |
list: (projectId: number) => api.get<Analysis[]>(`/analysis/project/${projectId}`),
|
| 159 |
+
clearAll: (projectId: number) => api.delete(`/analysis/project/${projectId}`),
|
| 160 |
}
|
| 161 |
|
| 162 |
// RAG
|
frontend/src/pages/ProjectDetailPage.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
import { useEffect, useRef, useState } from "react"
|
| 2 |
import { useParams, useNavigate } from "react-router-dom"
|
| 3 |
import { useTranslation } from "react-i18next"
|
| 4 |
-
import { ArrowLeft, Upload, Play, Trash2, FileText, ChevronDown, ChevronUp } from "lucide-react"
|
| 5 |
import { Button } from "@/components/ui/button"
|
| 6 |
import { Badge } from "@/components/ui/badge"
|
| 7 |
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
|
@@ -119,6 +119,12 @@ export default function ProjectDetailPage() {
|
|
| 119 |
}
|
| 120 |
}
|
| 121 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
if (loading) return <div className="p-6 text-muted-foreground">{t("common.loading")}</div>
|
| 123 |
if (!project) return <div className="p-6">{t("common.error")}</div>
|
| 124 |
|
|
@@ -234,7 +240,15 @@ export default function ProjectDetailPage() {
|
|
| 234 |
|
| 235 |
{/* Right column: analyses results */}
|
| 236 |
<div className="lg:col-span-2 space-y-3">
|
| 237 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 238 |
{analyses.length === 0 ? (
|
| 239 |
<p className="text-sm text-muted-foreground">{t("project.no_analyses")}</p>
|
| 240 |
) : (
|
|
|
|
| 1 |
import { useEffect, useRef, useState } from "react"
|
| 2 |
import { useParams, useNavigate } from "react-router-dom"
|
| 3 |
import { useTranslation } from "react-i18next"
|
| 4 |
+
import { ArrowLeft, Upload, Play, Trash2, FileText, ChevronDown, ChevronUp, X } from "lucide-react"
|
| 5 |
import { Button } from "@/components/ui/button"
|
| 6 |
import { Badge } from "@/components/ui/badge"
|
| 7 |
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
|
|
|
| 119 |
}
|
| 120 |
}
|
| 121 |
|
| 122 |
+
async function handleClearAnalyses() {
|
| 123 |
+
if (!confirm(t("project.clear_analyses_confirm"))) return
|
| 124 |
+
await analysisApi.clearAll(projectId)
|
| 125 |
+
setAnalyses([])
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
if (loading) return <div className="p-6 text-muted-foreground">{t("common.loading")}</div>
|
| 129 |
if (!project) return <div className="p-6">{t("common.error")}</div>
|
| 130 |
|
|
|
|
| 240 |
|
| 241 |
{/* Right column: analyses results */}
|
| 242 |
<div className="lg:col-span-2 space-y-3">
|
| 243 |
+
<div className="flex items-center justify-between">
|
| 244 |
+
<h2 className="text-sm font-medium text-muted-foreground">{t("project.analyses")}</h2>
|
| 245 |
+
{analyses.length > 0 && (
|
| 246 |
+
<Button variant="ghost" size="sm" className="h-7 text-xs text-destructive hover:text-destructive gap-1" onClick={handleClearAnalyses}>
|
| 247 |
+
<X className="h-3 w-3" />
|
| 248 |
+
{t("project.clear_analyses")}
|
| 249 |
+
</Button>
|
| 250 |
+
)}
|
| 251 |
+
</div>
|
| 252 |
{analyses.length === 0 ? (
|
| 253 |
<p className="text-sm text-muted-foreground">{t("project.no_analyses")}</p>
|
| 254 |
) : (
|