Spaces:
Sleeping
Sleeping
Fabio Antonini Claude Sonnet 4.6 commited on
Commit ·
818c0d8
1
Parent(s): c8c71aa
feat: signature verification UI and analysis route fixes
Browse files- Add reference document picker modal for signature verification
- Fix writer_identification path mapping (backend uses /writer not /writer-identification)
- Add missing common.cancel translation in it/en
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
frontend/src/i18n/en.json
CHANGED
|
@@ -93,6 +93,7 @@
|
|
| 93 |
"delete": "Delete",
|
| 94 |
"edit": "Edit",
|
| 95 |
"close": "Close",
|
|
|
|
| 96 |
"confirm": "Confirm",
|
| 97 |
"back": "Back",
|
| 98 |
"language": "Language"
|
|
|
|
| 93 |
"delete": "Delete",
|
| 94 |
"edit": "Edit",
|
| 95 |
"close": "Close",
|
| 96 |
+
"cancel": "Cancel",
|
| 97 |
"confirm": "Confirm",
|
| 98 |
"back": "Back",
|
| 99 |
"language": "Language"
|
frontend/src/i18n/it.json
CHANGED
|
@@ -59,7 +59,10 @@
|
|
| 59 |
"dating": "Datazione documento"
|
| 60 |
},
|
| 61 |
"running": "Analisi in corso…",
|
| 62 |
-
"done": "Analisi completata"
|
|
|
|
|
|
|
|
|
|
| 63 |
},
|
| 64 |
"rag": {
|
| 65 |
"title": "Consulente Forense IA",
|
|
@@ -93,6 +96,7 @@
|
|
| 93 |
"delete": "Elimina",
|
| 94 |
"edit": "Modifica",
|
| 95 |
"close": "Chiudi",
|
|
|
|
| 96 |
"confirm": "Conferma",
|
| 97 |
"back": "Indietro",
|
| 98 |
"language": "Lingua"
|
|
|
|
| 59 |
"dating": "Datazione documento"
|
| 60 |
},
|
| 61 |
"running": "Analisi in corso…",
|
| 62 |
+
"done": "Analisi completata",
|
| 63 |
+
"select_reference_doc": "Seleziona documento di riferimento",
|
| 64 |
+
"select_reference_doc_hint": "Scegli il documento che contiene la firma autentica da usare come riferimento.",
|
| 65 |
+
"select_reference_placeholder": "— seleziona documento —"
|
| 66 |
},
|
| 67 |
"rag": {
|
| 68 |
"title": "Consulente Forense IA",
|
|
|
|
| 96 |
"delete": "Elimina",
|
| 97 |
"edit": "Modifica",
|
| 98 |
"close": "Chiudi",
|
| 99 |
+
"cancel": "Annulla",
|
| 100 |
"confirm": "Conferma",
|
| 101 |
"back": "Indietro",
|
| 102 |
"language": "Lingua"
|
frontend/src/lib/api.ts
CHANGED
|
@@ -146,8 +146,13 @@ export const projectsApi = {
|
|
| 146 |
|
| 147 |
// Analysis
|
| 148 |
export const analysisApi = {
|
| 149 |
-
run: (type: string, projectId: number, documentId: number) =>
|
| 150 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
runPipeline: (projectId: number, documentId: number) =>
|
| 152 |
api.post<Analysis>("/analysis/pipeline", { project_id: projectId, document_id: documentId }),
|
| 153 |
list: (projectId: number) => api.get<Analysis[]>(`/analysis/project/${projectId}`),
|
|
|
|
| 146 |
|
| 147 |
// Analysis
|
| 148 |
export const analysisApi = {
|
| 149 |
+
run: (type: string, projectId: number, documentId: number) => {
|
| 150 |
+
const pathMap: Record<string, string> = { writer_identification: "writer" }
|
| 151 |
+
const path = pathMap[type] ?? type.replaceAll("_", "-")
|
| 152 |
+
return api.post<Analysis>(`/analysis/${path}`, { project_id: projectId, document_id: documentId })
|
| 153 |
+
},
|
| 154 |
+
runSignatureVerification: (projectId: number, documentId: number, referenceDocumentId: number) =>
|
| 155 |
+
api.post<Analysis>("/analysis/signature-verification", { project_id: projectId, document_id: documentId, reference_document_id: referenceDocumentId }),
|
| 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}`),
|
frontend/src/pages/ProjectDetailPage.tsx
CHANGED
|
@@ -57,6 +57,8 @@ export default function ProjectDetailPage() {
|
|
| 57 |
const [loading, setLoading] = useState(true)
|
| 58 |
const [selectedDoc, setSelectedDoc] = useState<number | null>(null)
|
| 59 |
const [runningType, setRunningType] = useState<string | null>(null)
|
|
|
|
|
|
|
| 60 |
const fileRef = useRef<HTMLInputElement>(null)
|
| 61 |
|
| 62 |
async function load() {
|
|
@@ -92,6 +94,10 @@ export default function ProjectDetailPage() {
|
|
| 92 |
|
| 93 |
async function handleRunAnalysis(type: string) {
|
| 94 |
if (!selectedDoc) return
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
setRunningType(type)
|
| 96 |
try {
|
| 97 |
const { data } = await analysisApi.run(type, projectId, selectedDoc)
|
|
@@ -101,10 +107,45 @@ export default function ProjectDetailPage() {
|
|
| 101 |
}
|
| 102 |
}
|
| 103 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
if (loading) return <div className="p-6 text-muted-foreground">{t("common.loading")}</div>
|
| 105 |
if (!project) return <div className="p-6">{t("common.error")}</div>
|
| 106 |
|
| 107 |
return (
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
<div className="p-6 max-w-5xl mx-auto space-y-6">
|
| 109 |
{/* Header */}
|
| 110 |
<div className="flex items-center gap-3">
|
|
@@ -202,5 +243,6 @@ export default function ProjectDetailPage() {
|
|
| 202 |
</div>
|
| 203 |
</div>
|
| 204 |
</div>
|
|
|
|
| 205 |
)
|
| 206 |
}
|
|
|
|
| 57 |
const [loading, setLoading] = useState(true)
|
| 58 |
const [selectedDoc, setSelectedDoc] = useState<number | null>(null)
|
| 59 |
const [runningType, setRunningType] = useState<string | null>(null)
|
| 60 |
+
const [refDoc, setRefDoc] = useState<number | null>(null)
|
| 61 |
+
const [showRefPicker, setShowRefPicker] = useState(false)
|
| 62 |
const fileRef = useRef<HTMLInputElement>(null)
|
| 63 |
|
| 64 |
async function load() {
|
|
|
|
| 94 |
|
| 95 |
async function handleRunAnalysis(type: string) {
|
| 96 |
if (!selectedDoc) return
|
| 97 |
+
if (type === "signature_verification") {
|
| 98 |
+
setShowRefPicker(true)
|
| 99 |
+
return
|
| 100 |
+
}
|
| 101 |
setRunningType(type)
|
| 102 |
try {
|
| 103 |
const { data } = await analysisApi.run(type, projectId, selectedDoc)
|
|
|
|
| 107 |
}
|
| 108 |
}
|
| 109 |
|
| 110 |
+
async function handleRunSigVerify() {
|
| 111 |
+
if (!selectedDoc || !refDoc) return
|
| 112 |
+
setShowRefPicker(false)
|
| 113 |
+
setRunningType("signature_verification")
|
| 114 |
+
try {
|
| 115 |
+
const { data } = await analysisApi.runSignatureVerification(projectId, selectedDoc, refDoc)
|
| 116 |
+
setAnalyses((a) => [data, ...a])
|
| 117 |
+
} finally {
|
| 118 |
+
setRunningType(null)
|
| 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 |
|
| 125 |
return (
|
| 126 |
+
<>
|
| 127 |
+
{showRefPicker && (
|
| 128 |
+
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
| 129 |
+
<div className="bg-background rounded-lg p-6 w-96 space-y-4 shadow-xl">
|
| 130 |
+
<h2 className="font-semibold">{t("project.select_reference_doc")}</h2>
|
| 131 |
+
<p className="text-sm text-muted-foreground">{t("project.select_reference_doc_hint")}</p>
|
| 132 |
+
<select
|
| 133 |
+
className="w-full border rounded-md px-3 py-2 text-sm"
|
| 134 |
+
value={refDoc ?? ""}
|
| 135 |
+
onChange={(e) => setRefDoc(Number(e.target.value))}
|
| 136 |
+
>
|
| 137 |
+
<option value="">{t("project.select_reference_placeholder")}</option>
|
| 138 |
+
{documents.filter((d) => d.id !== selectedDoc).map((d) => (
|
| 139 |
+
<option key={d.id} value={d.id}>{d.filename}</option>
|
| 140 |
+
))}
|
| 141 |
+
</select>
|
| 142 |
+
<div className="flex gap-2 justify-end">
|
| 143 |
+
<Button variant="outline" onClick={() => setShowRefPicker(false)}>{t("common.cancel")}</Button>
|
| 144 |
+
<Button disabled={!refDoc} onClick={handleRunSigVerify}>{t("project.run_analysis")}</Button>
|
| 145 |
+
</div>
|
| 146 |
+
</div>
|
| 147 |
+
</div>
|
| 148 |
+
)}
|
| 149 |
<div className="p-6 max-w-5xl mx-auto space-y-6">
|
| 150 |
{/* Header */}
|
| 151 |
<div className="flex items-center gap-3">
|
|
|
|
| 243 |
</div>
|
| 244 |
</div>
|
| 245 |
</div>
|
| 246 |
+
</>
|
| 247 |
)
|
| 248 |
}
|