"use client" // One collection's documents, live (umbrella TS6.4 and TS6.6): the page's view renders them and hands them // over as the seed, so the page arrives complete; this keeps them current through the organization's live // stream -- a document another member asks to index appears, and each moves from queued to indexed or // failed as the worker and the platform answer, without a reload. The rows come from // `knowledge.document.getMany`, under the same permission as the list; a topic is the whole project, so the // rows of the project's other collections are filtered out here. import type { ProjectSlugs } from "@conusai-kit/ui" import { eq, liveCollection, useDataClient, useLive } from "@conusai-kit/ui/data" import { type ResourceColumn, ResourceList } from "@conusai-kit/ui/patterns" import { type ReactElement, useState } from "react" import { DOCUMENT_COLLECTION, type DocumentView } from "../contract/index.js" export interface DocumentListLabels { readonly caption: string readonly file: string readonly status: string readonly error: string /** The message of each document status, by status. */ readonly statuses: Readonly> /** The words of each failure code a row can carry; an unknown code is shown as it is. */ readonly failures: Readonly> } export function useLiveDocuments( slugs: ProjectSlugs, collectionName: string, seed: readonly DocumentView[], ): readonly DocumentView[] { const client = useDataClient() // One collection per knowledge collection for the life of the page; the seed is the first render's. const [documents] = useState(() => liveCollection({ client, org: slugs.org, topic: { ws: slugs.workspace, project: slugs.project }, entity: DOCUMENT_COLLECTION.entity, key: DOCUMENT_COLLECTION.key, id: `${DOCUMENT_COLLECTION.entity}:${slugs.org}:${slugs.workspace}:${slugs.project}:${collectionName}`, seed, list: { procedure: DOCUMENT_COLLECTION.list, input: { ...slugs, collectionName }, rows: (out) => (out as { documents: DocumentView[] }).documents, }, getMany: { procedure: DOCUMENT_COLLECTION.getMany, input: { ...slugs }, rows: (out) => (out as { items: DocumentView[] }).items, }, }), ) const { data, isReady } = useLive(documents, (q, rows) => q .from({ document: rows }) .where(({ document }: { document: DocumentView }) => eq(document.collectionName, collectionName)), ) // Until the collection is ready the server's rows are the answer, so the first paint matches it. return isReady ? data : seed } export function CollectionDocuments({ slugs, collectionName, seed, labels, }: { slugs: ProjectSlugs collectionName: string seed: readonly DocumentView[] labels: DocumentListLabels }): ReactElement { const rows = useLiveDocuments(slugs, collectionName, seed) const columns: ResourceColumn[] = [ { id: "filename", header: labels.file, wrap: true }, { id: "status", header: labels.status, cell: (document) => labels.statuses[document.status] }, { id: "error", header: labels.error, wrap: true, cell: (document) => (document.error === null ? "" : (labels.failures[document.error] ?? document.error)), }, ] return ( document.documentId} rowLabel={(document) => document.filename} empty={{ title: labels.caption }} /> ) }