@@ -0,0 +1,96 @@
|
||||
"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<Record<DocumentView["status"], string>>
|
||||
/** The words of each failure code a row can carry; an unknown code is shown as it is. */
|
||||
readonly failures: Readonly<Record<string, string>>
|
||||
}
|
||||
|
||||
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<DocumentView>({
|
||||
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<DocumentView>[] = [
|
||||
{ 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 (
|
||||
<ResourceList
|
||||
label={labels.caption}
|
||||
columns={columns}
|
||||
data={rows}
|
||||
rowId={(document) => document.documentId}
|
||||
rowLabel={(document) => document.filename}
|
||||
empty={{ title: labels.caption }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user