Alessandro Tomassini
deploy(hf): overlay README/Dockerfile da huggingface/, senza docs/binari/model
c42a5a1
Raw
History Blame Contribute Delete
14.3 kB
/**
* Orchestrazione delle run (Analizza / Traccia di Debug, testo e PDF).
* Porting imperativo di public/js/app.js: legge gli store (ui/session/catalog),
* pilota i canali di feedback (feedbackStore) e applica i risultati a
* sessionStore. Tenuto fuori da React come l'app originale: i componenti
* reagiscono allo stato.
*/
import { errorDetail } from "@/api/client";
import {
detect,
detectDebug,
getDetectTimings,
getPdfJob,
getPdfLayout,
render as renderApi,
startCompare,
startDebug,
startDetect,
uploadPdf,
} from "@/api/endpoints";
import { runJob } from "@/api/jobs";
import type {
CompareResponse,
DebugResponse,
DetectRequest,
DetectResponse,
Timing,
} from "@/api/types";
import { INNER_MODES } from "@/constants";
import { isSimulating, selectedApprover, useUiStore } from "@/store/uiStore";
import { useSessionStore } from "@/store/sessionStore";
import { useFeedbackStore, type RunStatRow } from "@/store/feedbackStore";
import { useCatalogStore } from "@/store/catalogStore";
import { configSnapshot } from "@/utils/snapshot";
import { fmtElapsed } from "@/utils/format";
const ui = () => useUiStore.getState();
const session = () => useSessionStore.getState();
const fb = () => useFeedbackStore.getState();
// ---- Etichette / riepiloghi ----
function activeLayersLabel(): string {
const sel = ui().selectedLayers;
const ml = sel.filter((k) => k !== "rules");
if (!ml.length) return "solo regole (veloce)";
const slow = ml.some((k) => ["zero_shot", "embedding", "llm"].includes(k));
return `layer attivi: ${sel.join(", ")}` + (slow ? " — alcuni lenti, attendere" : "");
}
function runStatsEntries(): RunStatRow[] {
const sel = new Set(ui().selectedLayers);
const entries: RunStatRow[] = useCatalogStore
.getState()
.layers.filter((l) => sel.has(l.key))
.map((l) => ({ key: l.key, label: l.label, value: "—" }));
if (!entries.length) entries.push({ key: "rules", label: "Regole", value: "—" });
if (ui().verify) entries.push({ key: "giudice", label: "Giudice LLM", value: "—" });
return entries;
}
// ---- Canali di Run unificati ----
let runPrefix = "";
const Run = {
begin(label: string, text: string, sub: string) {
runPrefix = "";
fb().loadingShow(text, sub, label === "DEBUG" ? "debug" : "analyze");
fb().activityShow(label, text);
fb().sysMonStart(text);
fb().runStatsStart(runStatsEntries());
fb().runStatsSetPhase(text);
},
step(text?: string | null, sub?: string | null, p?: number | null) {
if (text) {
const msg = runPrefix + text;
fb().loadingStep(msg, sub ?? null, p ?? null);
fb().activityUpdate(p ?? null, msg);
fb().sysMonSetState(msg);
fb().runStatsSetPhase(msg);
} else {
fb().loadingStep(null, sub ?? null, p ?? null);
if (p != null) fb().activityUpdate(p, null);
}
},
setPrefix(p: string) {
runPrefix = p;
},
end() {
runPrefix = "";
fb().loadingHide();
fb().activityHide();
fb().sysMonStop();
},
};
function setStatus(msg: string, kind: "info" | "ok" | "error" = "info") {
session().setStatus(msg, kind);
}
function setTimings(timings: Timing[] | undefined) {
if (timings) {
session().setTimings(timings);
fb().runStatsSetTimings(timings);
}
}
// ---- Re-render economico (cambio modalità / gravità / correzioni) ----
export async function reRender(): Promise<void> {
const s = session();
if (!s.docId) return;
const mode = ui().mode;
const base = {
doc_id: s.docId,
enabled_severities: ui().enabledSeverities,
manual_ranges: s.manualRanges,
excluded_ranges: s.excludedRanges,
};
if (mode === "all") {
const renders = await Promise.all(
INNER_MODES.map((m) => renderApi({ ...base, mode: m.key })),
);
const map = Object.fromEntries(INNER_MODES.map((m, i) => [m.key, renders[i]]));
session().setRender(renders[0]);
session().setMultiRender(map);
session().showTabs(["highlight", "anon", "report", "json", "confronto"]);
} else {
const data = await renderApi({ ...base, mode });
session().setRender(data);
session().setMultiRender(null);
session().hideTab("confronto");
session().showTabs(["highlight", "anon", "report", "json"]);
}
session().ensureActiveVisible();
}
/**
* Variante "fire-and-forget" di reRender per i trigger della UI (cambio
* modalità/gravità, correzioni manuali). A differenza di reRender — usata nei
* flussi che gestiscono già l'errore e a cui l'eccezione deve propagare per
* interrompere la run — qui un fallimento (documento uscito dalla cache, backend
* irraggiungibile) viene riportato nella status-line invece di restare una
* promise rejection silenziosa.
*/
export function applyReRender(): void {
void reRender().catch((e) => {
setStatus("Errore nell'aggiornamento dell'anteprima: " + errorDetail(e), "error");
});
}
// ---- Analizza ----
export function runAnalyze(): void {
if (ui().source === "text") void analyzeText();
else void analyzePdf();
}
function detectBaseBody(): DetectRequest {
return {
text: ui().docText.trim(),
min_confidence: ui().minConfidence,
layers: ui().selectedLayers,
approver: selectedApprover(),
simulate: isSimulating(),
};
}
async function analyzeText(): Promise<void> {
if (ui().docText.trim() === "") {
setStatus("Inserisci del testo da analizzare.");
return;
}
const runId = session().bumpRunId();
fb().activityHide();
if (ui().verify) {
await analyzeTextThenJudge(runId);
return;
}
Run.begin("ANALISI", "Rilevamento dei dati sensibili…", activeLayersLabel());
try {
await analyzeCore();
ui().setSettingsCollapsed(true);
} catch (e) {
setStatus("Errore: " + errorDetail(e), "error");
} finally {
Run.end();
fb().runStatsFinish();
}
}
async function analyzeCore(): Promise<void> {
const text = ui().docText.trim();
if (!text) throw new Error("Inserisci del testo da analizzare.");
session().setRender(null);
session().setMultiRender(null);
session().setLlm(null);
session().hideTabs(["pdf", "metodi", "debug"]);
const body: DetectRequest = { ...detectBaseBody(), verify: ui().verify };
let data: DetectResponse;
if (body.verify) {
data = (await runJob(() => startDetect(body), (p, msg) =>
Run.step(msg || null, null, p),
)) as unknown as DetectResponse;
} else {
data = await detect(body);
}
session().setDoc(data.doc_id, false);
setTimings(data.timings);
setStatus(`${data.detections} rilevamenti trovati.`, "ok");
session().setLlm(data.llm ?? null);
// reset correzioni manuali per la nuova analisi
useSessionStore.setState({ manualRanges: [], excludedRanges: [] });
await reRender();
session().markAnalyzed(configSnapshot());
}
// Verifica LLM attiva → due fasi (risultati subito + giudice in background).
async function analyzeTextThenJudge(runId: number): Promise<void> {
session().setLlm(null);
session().hideTabs(["pdf", "metodi", "debug"]);
useSessionStore.setState({ manualRanges: [], excludedRanges: [] });
const baseBody = detectBaseBody();
Run.begin("ANALISI", "Rilevamento dei dati sensibili…", activeLayersLabel());
try {
const data = await detect({ ...baseBody, verify: false });
if (runId !== session().runId) return;
session().setDoc(data.doc_id, false);
setTimings(data.timings);
setStatus(`${data.detections} rilevamenti — giudice in corso…`);
await reRender();
ui().setSettingsCollapsed(true);
session().markAnalyzed(configSnapshot());
} catch (e) {
setStatus("Errore: " + errorDetail(e), "error");
Run.end();
fb().runStatsFinish();
return;
}
Run.end();
if (runId !== session().runId) return;
void runJudgeInBackground(baseBody, runId);
}
async function runJudgeInBackground(
baseBody: DetectRequest,
runId: number,
): Promise<void> {
fb().activityShow("GIUDICE", "avvio…");
fb().sysMonStart("Giudice LLM in corso…");
try {
const data = (await runJob(
() => startDetect({ ...baseBody, verify: true }),
(p, msg) => {
if (runId === session().runId) fb().activityUpdate(p, msg);
},
)) as unknown as DetectResponse;
if (runId !== session().runId) return;
session().setDoc(data.doc_id, false);
setTimings(data.timings);
setStatus(`${data.detections} rilevamenti — verificati dal giudice.`, "ok");
session().setLlm(data.llm ?? null);
await reRender();
fb().activityHide();
} catch (e) {
if (runId === session().runId) fb().activityError("errore: " + errorDetail(e));
} finally {
fb().sysMonStop();
if (runId === session().runId) fb().runStatsFinish();
}
}
// ---- PDF ----
async function analyzePdf(): Promise<void> {
session().bumpRunId();
Run.begin("PDF", "Caricamento e analisi del PDF…", activeLayersLabel());
try {
await analyzePdfCore();
} catch (e) {
setStatus("Errore: " + errorDetail(e), "error");
} finally {
Run.end();
fb().runStatsFinish();
}
}
async function analyzePdfCore(): Promise<void> {
const file = ui().pdfFile;
if (!file) throw new Error("Seleziona un file PDF.");
useSessionStore.setState({ manualRanges: [], excludedRanges: [] });
session().hideTabs(["metodi", "debug"]);
const form = new FormData();
form.append("file", file);
form.append("fast", ui().pdfFast ? "true" : "false");
form.append("min_confidence", String(ui().minConfidence));
form.append("layers", ui().selectedLayers.join(","));
form.append("verify", ui().verify ? "true" : "false");
form.append("approver", selectedApprover() || "");
form.append("force", "true");
Run.step("Caricamento…", null, 0.05);
const job = await uploadPdf(form);
await pollPdfJob(job.job_id);
}
async function pollPdfJob(jobId: string): Promise<void> {
for (;;) {
const job = await getPdfJob(jobId);
Run.step(job.message, null, job.progress);
if (job.state === "done" && job.doc_id) {
session().setDoc(job.doc_id, true);
Run.step("Completato", null, 1);
try {
const timings = await getDetectTimings(job.doc_id);
setTimings(timings);
} catch {
/* riepilogo tempi: best effort */
}
session().setPdfLayout(null);
try {
const layout = await getPdfLayout(job.doc_id);
session().setPdfLayout(layout.blocks?.length ? layout.blocks : null);
} catch {
/* fallback al testo piatto */
}
await reRender();
const total = session().render?.report.total ?? 0;
setStatus(`${total} rilevamenti nel PDF.`, "ok");
session().showTab("pdf");
session().activateTab("pdf");
ui().setSettingsCollapsed(true);
session().markAnalyzed(configSnapshot());
return;
}
if (job.state === "error") {
throw new Error(job.error || "elaborazione fallita");
}
await new Promise((r) => setTimeout(r, 1500));
}
}
// ---- Confronto metodologie + Debug ----
function detectionBody() {
const layers = ui().selectedLayers;
const docId = session().docId;
if (docId) {
return { doc_id: docId, min_confidence: ui().minConfidence, layers };
}
if (ui().source === "text") {
const text = ui().docText.trim();
if (!text) throw new Error("Inserisci del testo o analizza un documento prima.");
return { text, min_confidence: ui().minConfidence, layers };
}
throw new Error("Carica e analizza un PDF prima.");
}
async function compareCore(body: ReturnType<typeof detectionBody>): Promise<void> {
const data = (await runJob(
() => startCompare(body),
(p, msg) => {
if (msg) Run.step(msg, null, p);
else Run.step(null, null, p);
setStatus(runPrefix + (msg || ""));
},
)) as unknown as CompareResponse;
session().setCompare(data);
session().showTab("metodi");
}
async function debugCore(): Promise<void> {
const body = {
...detectionBody(),
verify: ui().verify,
approver: selectedApprover(),
simulate: isSimulating(),
};
let payload: DebugResponse;
if (body.verify) {
payload = (await runJob(
() => startDebug(body),
(p, msg) => {
if (msg) {
Run.step(msg, null, p);
setStatus(runPrefix + msg);
} else {
Run.step(null, null, p);
}
},
)) as unknown as DebugResponse;
} else {
payload = await detectDebug(body);
}
setTimings(payload.timings);
session().setDebug(payload);
session().showTab("debug");
}
export function debugTrace(): void {
void runDebugFlow();
}
async function runDebugFlow(): Promise<void> {
session().bumpRunId();
Run.begin("DEBUG", "Analisi completa in corso…", "passo 1 di 3");
try {
try {
await runDebugSteps();
} catch (e) {
const msg = errorDetail(e);
if (!/non in cache/i.test(msg)) throw e;
setStatus("Documento non più in cache: rianalizzo da zero…");
session().setDoc(null, session().isPdf);
await runDebugSteps();
}
session().activateTab("debug");
setStatus("Completato: analisi, confronto metodi e traccia di debug.", "ok");
ui().setSettingsCollapsed(true);
} catch (e) {
setStatus("Errore: " + errorDetail(e), "error");
} finally {
Run.end();
fb().runStatsFinish();
}
}
async function runDebugSteps(): Promise<void> {
// 1) Analizza (crea il doc e popola Evidenziato/Anonimizzato/Report/JSON).
Run.setPrefix("Passo 1/3 · ");
if (ui().source === "text") {
Run.step("Rilevamento dei dati sensibili…", activeLayersLabel(), 0.02);
await analyzeCore();
} else if (!session().docId) {
Run.step("Caricamento e analisi del PDF…", activeLayersLabel(), 0.02);
await analyzePdfCore();
}
// 2) Confronta le metodologie (popola Metodi).
Run.setPrefix("Passo 2/3 · ");
Run.step(
"Confronto tra le metodologie…",
"eseguo ogni layer separatamente (può richiedere tempo)",
0.02,
);
await compareCore(detectionBody());
// 3) Traccia di debug (popola Debug).
Run.setPrefix("Passo 3/3 · ");
Run.step(
"Costruzione della traccia di debug…",
"ricostruisco cluster, contributi e boost per ogni frase",
0.02,
);
await debugCore();
}
// ---- helper tempo (per i ticker dei componenti) ----
export { fmtElapsed };