@crema/knowledge-ui: headless KB management components over an injected KnowledgeTransport. This commit: package + types (full API surface) + transport interface + MockKnowledgeTransport (fixtures for every surface) + _internal (cn, badges, formatters) + components-collections (CollectionList grouped by owner, CollectionCard, CollectionForm with org/read-only states) + demo + README + tsconfig.check.json. Typechecks clean. Co-Authored-By: Claude Fable 5 (build) <noreply@anthropic.com>
87 lines
3.0 KiB
TypeScript
87 lines
3.0 KiB
TypeScript
// 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 {
|
|
CollectionForm,
|
|
CollectionList,
|
|
MockKnowledgeTransport,
|
|
type Collection,
|
|
type CollectionInput,
|
|
type CollectionPatch,
|
|
type KnowledgeTransport,
|
|
} from "../src/index";
|
|
|
|
const transport: KnowledgeTransport = new MockKnowledgeTransport();
|
|
|
|
export default function KnowledgeDemo() {
|
|
const [collections, setCollections] = useState<Collection[]>([]);
|
|
const [pending, setPending] = useState<Record<string, number>>({});
|
|
const [creating, setCreating] = useState<null | "account" | "tenant">(null);
|
|
const [editing, setEditing] = useState<Collection | null>(null);
|
|
const [busy, setBusy] = useState(false);
|
|
|
|
async function refresh() {
|
|
const page = await transport.listCollections();
|
|
setCollections(page.collections);
|
|
const queue = await transport.reviewQueue();
|
|
const counts: Record<string, number> = {};
|
|
for (const c of queue) counts[c.collection] = (counts[c.collection] ?? 0) + 1;
|
|
setPending(counts);
|
|
}
|
|
|
|
useEffect(() => {
|
|
void refresh();
|
|
}, []);
|
|
|
|
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();
|
|
}
|
|
|
|
return (
|
|
<div className="mx-auto max-w-5xl p-6">
|
|
<header className="mb-6">
|
|
<h1 className="text-xl font-semibold text-foreground">Knowledge</h1>
|
|
<p className="text-sm text-muted-foreground">Your corpuses — browse, organise, and review.</p>
|
|
</header>
|
|
|
|
{creating && (
|
|
<div className="mb-6 rounded-xl border border-border bg-card p-4">
|
|
<h2 className="mb-3 font-medium">New corpus</h2>
|
|
<CollectionForm mode="create" ownerType={creating} orgName="Acme" canManageTenant busy={busy} onSubmit={onCreate} onCancel={() => setCreating(null)} />
|
|
</div>
|
|
)}
|
|
|
|
{editing && (
|
|
<div className="mb-6 rounded-xl border border-border bg-card p-4">
|
|
<h2 className="mb-3 font-medium">Corpus settings</h2>
|
|
<CollectionForm mode="edit" initial={editing} orgName="Acme" canManageTenant busy={busy} onSubmit={onEdit} onCancel={() => setEditing(null)} />
|
|
</div>
|
|
)}
|
|
|
|
<CollectionList
|
|
collections={collections}
|
|
pendingCounts={pending}
|
|
orgName="Acme"
|
|
canManageTenant
|
|
onCreate={(owner) => setCreating(owner)}
|
|
onOpen={(c) => setEditing(c)}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|