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
- api.post<Analysis>(`/analysis/${type.replaceAll("_", "-")}`, { project_id: projectId, document_id: documentId }),
 
 
 
 
 
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
  }