/** * IndexerStatus — sidebar widget for /admin/batch showing the next-step gap: * how many docs/chunks need to be indexed into Postgres. */ "use client"; import { useEffect, useState } from "react"; interface IndexerPayload { disk: { docs_on_disk: number; chunks_on_disk: number }; db: { documents_count: number; chunks_count: number; chunks_with_embedding: number; entities_count: number; entity_mentions_count: number; } | null; db_error: string | null; gap: { docs_to_index: number; chunks_to_index: number; chunks_without_embedding: number; ready_for_retrieval: boolean; } | null; } export function IndexerStatus() { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { let alive = true; async function tick() { try { const r = await fetch("/api/admin/indexer"); if (!r.ok) return; const j = (await r.json()) as IndexerPayload; if (alive) { setData(j); setLoading(false); } } catch { if (alive) setLoading(false); } } tick(); const i = setInterval(tick, 60_000); return () => { alive = false; clearInterval(i); }; }, []); if (loading && !data) { return
indexer status…
; } if (!data) return null; if (data.db_error) { return (
database offline
{data.db_error}

start Postgres + embed-service (compose), then apply migrations.

); } const db = data.db!; const gap = data.gap!; return (

retrieval index — Postgres + pgvector

entidades canônicas
{db.entities_count.toLocaleString()}
entity_mentions
{db.entity_mentions_count.toLocaleString()}
{gap.ready_for_retrieval ? "✓ hybrid_search está OPERACIONAL — agente já pode usar" : "⚠ aguardando embeddings — execute scripts/30-index-chunks-to-db.py para ativar retrieval"}
{(gap.docs_to_index > 0 || gap.chunks_to_index > 0) && (
▸ próximos comandos
{`# index chunks → Postgres + BGE-M3 embeddings
export DATABASE_URL='postgres://...'
export EMBED_SERVICE_URL='http://embed:8000'
python3 scripts/30-index-chunks-to-db.py --skip-existing

# materialize entity_mentions (links chunk ↔ entity)
python3 scripts/31-populate-entity-mentions.py`}
          
)}
); } function Card({ label, lhs, rhs, gap, }: { label: string; lhs: number; rhs: number; gap: number; }) { const ok = gap === 0; return (
{label}
{lhs.toLocaleString()} / {rhs.toLocaleString()}
{!ok && (
gap {gap.toLocaleString()}
)}
); }