/** * StatsDashboard — corpus-wide analytics rendered from /api/admin/stats. */ "use client"; import Link from "next/link"; import { useEffect, useState } from "react"; interface FsStats { documents_total: number; documents_rebuilt_v2: number; pages_total: number; chunks_on_disk: number; redactions_total: number; collections: Record; document_class: Record; content_classification: Record; entity_counts: Record; entities_total: number; } interface DbStats { ok: boolean; error?: string; core?: { docs: number; chunks: number; entities: number; mentions: number }; chunk_types?: Array<{ type: string; count: number }>; classifications?: Array<{ classification: string | null; count: number }>; top_docs_by_chunks?: Array<{ doc_id: string; count: number }>; ufo_anomaly_types?: Array<{ anomaly_type: string | null; count: number }>; cryptid_count?: number; embedded_count?: number; } const CLASS_COLOR: Record = { people: "#ff6ec7", organizations: "#ff8a4d", locations: "#3fde6a", events: "#ffa500", "uap-objects": "#ff3344", vehicles: "#5b9bd5", operations: "#9b5de5", concepts: "#06d6a0", }; export function StatsDashboard() { const [fs, setFs] = useState(null); const [db, setDb] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { fetch("/api/admin/stats") .then((r) => r.json()) .then((j: { fs: FsStats; db: DbStats }) => { setFs(j.fs); setDb(j.db); setLoading(false); }) .catch(() => setLoading(false)); }, []); if (loading) { return
carregando…
; } if (!fs) { return
stats indisponível
; } return (
{/* Top-line counters */}
{db?.ok && db.core && ( <> 0 ? "#00ff9c" : "#5a6678"} /> 0 ? "#7fdbff" : "#5a6678"} /> )}
{/* Entities by class */}

entidades por classe

{Object.entries(fs.entity_counts).map(([cls, n]) => { const color = CLASS_COLOR[cls] ?? "#7fdbff"; return (
{cls}
{n.toLocaleString()}
); })}
{/* Collections breakdown */}
b[1] - a[1])} /> b[1] - a[1])} />
{/* Content classification */}

conteúdo das páginas

{Object.entries(fs.content_classification) .sort((a, b) => b[1] - a[1]) .map(([tag, n]) => (
{tag}
{n.toLocaleString()}
))}
{/* DB-derived */} {db?.ok ? ( <> {(db.ufo_anomaly_types?.length ?? 0) > 0 && (

🛸 anomalias UFO detectadas · {db.ufo_anomaly_types?.reduce((s, r) => s + r.count, 0)} chunks

[r.anomaly_type ?? "(sem tipo)", r.count])} />
)} {(db.top_docs_by_chunks?.length ?? 0) > 0 && (

top 10 docs por chunks

    {db.top_docs_by_chunks!.map((d) => (
  • {d.doc_id} {d.count.toLocaleString()} chunks
  • ))}
)} {(db.chunk_types?.length ?? 0) > 0 && (

tipos de chunk

[r.type, r.count])} />
)} ) : (
⚠ DB stats indisponíveis — rode o indexer:{" "} python3 scripts/30-index-chunks-to-db.py {db?.error && (
erro: {db.error}
)}
)}
); } function Stat({ label, value, accent }: { label: string; value: string; accent?: string }) { return (
{label}
{value}
); } function Histogram({ title, color, data, }: { title: string; color: string; data: [string, number][]; }) { if (data.length === 0) return null; const max = Math.max(...data.map(([, n]) => n)); return (
{title && (

{title}

)}
    {data.map(([k, v]) => (
  • {k} {v.toLocaleString()}
  • ))}
); }