SPB Git
3.0 KB · 16 lines tsx
1import Link from "next/link";2import { sql } from "@/db";3import { requireUserPage } from "@/lib/auth/guard";4import { csrfToken } from "@/lib/auth/session";5import { ingestAction } from "@/lib/admin/actions";6import { Csrf, Notice, PageTitle } from "@/components/admin-ui";7import { STORAGE_DIR } from "@/lib/env";8export default async function Ingest({ searchParams }: { searchParams: Promise<Record<string, string | undefined>> }) {9  const sp = await searchParams; const u = await requireUserPage();10  const rows = await sql`SELECT c.*, f.pages, f.integrity_ok FROM ingest_candidates c LEFT JOIN files f ON f.id = c.file_id ORDER BY (c.status IN ('ready','queued','processing')) DESC, c.created_at DESC LIMIT 200`;11  const [last] = await sql`SELECT value FROM settings WHERE key = 'ingest.last_scan'`; const [dirs] = await sql`SELECT value FROM settings WHERE key = 'ingest.extra_dirs'`;12  return (<div><PageTitle title="Manuscript ingestion"><form action={ingestAction}><Csrf token={csrfToken(u)} /><input type="hidden" name="op" value="scan" /><button className="btn btn-primary btn-sm">Scan directories now</button></form></PageTitle><Notice sp={sp} />13    <p className="meta mb-4">Scanned directories: <code>{STORAGE_DIR}/inbox</code>{((dirs?.value as string[]) ?? []).map((d) => <span key={d}>, <code>{d}</code></span>)} (configure in Settings). Last scan: {last ? `${last.value.at} — ${last.value.found} PDF found, ${last.value.queued} new` : "never"}. Every candidate is checksummed, integrity-checked, text-extracted and checked for duplicates; metadata is a proposal that you must verify before approval. Nothing is published automatically.</p>14    <div className="card overflow-x-auto"><table className="data"><thead><tr><th>File</th><th>Status</th><th>Proposed title</th><th>WP no.</th><th>Flags</th><th>Duplicates</th><th>Created</th></tr></thead><tbody>{rows.map((c) => { const pr = c.proposal as Record<string, any>; return <tr key={c.id}><td className="text-xs"><Link href={`/admin/ingest/${c.id}`} className="text-navy no-underline hover:underline">{String(c.source_path).split("/").pop()}</Link><br /><span className="text-fg-3">{c.pages ?? "?"} p.</span></td><td><span className={`chip ${c.status === "ready" ? "chip-navy" : c.status === "rejected" ? "chip-burgundy" : ""}`}>{c.status}</span></td><td className="text-fg-2">{pr?.title ?? "—"}</td><td className="font-mono">{pr?.wp ? `${pr.wp.seriesCode}${pr.wp.number}` : "—"}</td><td className="text-xs">{(c.flags as { code: string }[]).map((f) => <span key={f.code} className="chip mr-1">{f.code}</span>)}</td><td className="text-xs">{(c.duplicates as { identifier: string; score: number }[]).slice(0, 2).map((d) => <span key={d.identifier} className="chip chip-burgundy mr-1">{d.identifier} {(d.score * 100).toFixed(0)}%</span>)}</td><td className="text-xs whitespace-nowrap">{String(c.created_at).slice(0, 16)}</td></tr>; })}</tbody></table>{rows.length === 0 && <p className="p-6 text-center meta">Queue is empty. Drop PDFs in the inbox directory and scan.</p>}</div></div>);15}16
Files · main
Menu

Theme