W6: export panel

components-export: ExportPanel (scope picker + request) + ExportJobRow (status,
download on ready, error verbatim; timer lives in the app per the polling
contract). Demo Export tab polls getExport app-side. Lib confirmed
self-contained (react + lucide only); README/tsconfig updated. Typechecks clean.

Co-Authored-By: Claude Fable 5 (build) <noreply@anthropic.com>
This commit is contained in:
jules
2026-07-07 10:05:46 +10:00
parent 40d2f94795
commit efbf4b2e61
4 changed files with 169 additions and 7 deletions

View File

@@ -7,6 +7,7 @@ import {
ClaimsReviewQueue,
CollectionForm,
CollectionList,
ExportPanel,
ObjectList,
ObjectViewer,
MockKnowledgeTransport,
@@ -14,6 +15,8 @@ import {
type Collection,
type CollectionInput,
type CollectionPatch,
type ExportJob,
type ExportScope,
type KnowledgeTransport,
type ObjectOutline,
type ObjectSummary,
@@ -27,7 +30,9 @@ type View =
| { kind: "object"; id: string };
export default function KnowledgeDemo() {
const [tab, setTab] = useState<"browse" | "review">("browse");
const [tab, setTab] = useState<"browse" | "review" | "export">("browse");
const [exports, setExports] = useState<ExportJob[]>([]);
const [exportBusy, setExportBusy] = useState(false);
const [view, setView] = useState<View>({ kind: "collections" });
const [collections, setCollections] = useState<Collection[]>([]);
const [pending, setPending] = useState<Record<string, number>>({});
@@ -56,6 +61,23 @@ export default function KnowledgeDemo() {
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();
}, []);
@@ -90,8 +112,19 @@ export default function KnowledgeDemo() {
<TabBtn active={tab === "review"} onClick={() => setTab("review")}>
Review{reviewCount > 0 ? ` (${reviewCount})` : ""}
</TabBtn>
<TabBtn active={tab === "export"} onClick={() => setTab("export")}>Export</TabBtn>
</div>
{tab === "export" && (
<ExportPanel
collections={collections}
jobs={exports}
busy={exportBusy}
onRequest={requestExport}
onDownload={(job) => job.download_url && window.open(job.download_url)}
/>
)}
{tab === "review" && (
<ClaimsReviewQueue
claims={claims}