// PURPOSE: Standalone demo of @crema/knowledge-ui over MockKnowledgeTransport. // Drop into a consuming app's route (e.g. routes/knowledge-demo.tsx) to // exercise every surface with fixtures and no running service. // =========================================================================== import { useEffect, useState } from "react"; import { ClaimsReviewQueue, CollectionForm, CollectionList, ExportPanel, ObjectList, ObjectViewer, MockKnowledgeTransport, type Claim, type Collection, type CollectionInput, type CollectionPatch, type ExportJob, type ExportScope, type KnowledgeTransport, type ObjectOutline, type ObjectSummary, } from "../src/index"; const transport: KnowledgeTransport = new MockKnowledgeTransport(); type View = | { kind: "collections" } | { kind: "collection"; slug: string } | { kind: "object"; id: string }; export default function KnowledgeDemo() { const [tab, setTab] = useState<"browse" | "review" | "export">("browse"); const [exports, setExports] = useState([]); const [exportBusy, setExportBusy] = useState(false); const [view, setView] = useState({ kind: "collections" }); const [collections, setCollections] = useState([]); const [pending, setPending] = useState>({}); const [objects, setObjects] = useState([]); const [outline, setOutline] = useState(null); const [claims, setClaims] = useState([]); const [busyId, setBusyId] = useState(); const [creating, setCreating] = useState(null); const [editing, setEditing] = useState(null); const [busy, setBusy] = useState(false); async function refresh() { const page = await transport.listCollections(); setCollections(page.collections); const queue = await transport.reviewQueue(); setClaims(queue); const counts: Record = {}; for (const c of queue) counts[c.collection] = (counts[c.collection] ?? 0) + 1; setPending(counts); } async function afterClaim(id: string, fn: () => Promise) { setBusyId(id); await fn(); setBusyId(undefined); void refresh(); } // App-side polling of in-flight exports (the timer lives here, not in the lib). async function requestExport(scope: ExportScope) { setExportBusy(true); const job = await transport.createExport(scope); setExports((prev) => [job, ...prev]); setExportBusy(false); poll(job.id); } function poll(id: string) { const tick = async () => { const job = await transport.getExport(id); setExports((prev) => prev.map((e) => (e.id === id ? job : e))); if (job.status === "pending" || job.status === "running") setTimeout(tick, 1500); }; setTimeout(tick, 800); } useEffect(() => { void refresh(); }, []); useEffect(() => { if (view.kind === "collection") void transport.listObjects(view.slug).then((p) => setObjects(p.objects)); if (view.kind === "object") void transport.outline(view.id).then(setOutline); }, [view]); async function onCreate(value: CollectionInput | CollectionPatch) { setBusy(true); await transport.createCollection(value as CollectionInput); setBusy(false); setCreating(null); void refresh(); } async function onEdit(value: CollectionInput | CollectionPatch) { if (!editing) return; setBusy(true); await transport.updateCollection(editing.slug, value as CollectionPatch); setBusy(false); setEditing(null); void refresh(); } const reviewCount = claims.length; return (
setTab("browse")}>Browse setTab("review")}> Review{reviewCount > 0 ? ` (${reviewCount})` : ""} setTab("export")}>Export
{tab === "export" && ( job.download_url && window.open(job.download_url)} /> )} {tab === "review" && ( afterClaim(id, () => transport.confirmClaim(id))} onReject={(id) => afterClaim(id, () => transport.rejectClaim(id))} onKeep={(winner, supersede) => afterClaim(winner, () => transport.confirmClaim(winner, { supersede }))} onDismissConflict={(id) => afterClaim(id, () => transport.dismissConflict(id))} onOpenSource={(objectId) => { setTab("browse"); setView({ kind: "object", id: objectId }); }} /> )} {tab === "browse" && view.kind !== "collections" && ( )} {tab === "browse" && view.kind === "collections" && ( <> {creating && (

New corpus

setCreating(null)} />
)} {editing && (

Corpus settings

setEditing(null)} />
)} setCreating(owner)} onOpen={(c) => setView({ kind: "collection", slug: c.slug })} /> )} {tab === "browse" && view.kind === "collection" && ( <>

{view.slug}

setView({ kind: "object", id: o.object_id })} /> )} {tab === "browse" && view.kind === "object" && outline && ( transport.readText(outline.object_id, sel)} resolveBlobUrl={() => transport.blobUrl(outline.object_id)} onOpenObject={(id) => setView({ kind: "object", id })} actions={{ onVerify: () => transport.updateObject(outline.object_id, { verified_at: new Date().toISOString() }), onDownload: () => transport.blobUrl(outline.object_id).then((u) => window.open(u)), }} /> )}
); } function TabBtn({ active, onClick, children }: { active: boolean; onClick: () => void; children: React.ReactNode }) { return ( ); }