ConusAI vendor v0.1.0-dev.20261027+9b1131fb
ci / gate (push) Waiting to run

This commit is contained in:
ConusAI vendor v0.1.0-dev.20261027+9b1131fb
2026-10-27 18:05:17 +00:00
commit 3102273ec7
538 changed files with 80203 additions and 0 deletions
+238
View File
@@ -0,0 +1,238 @@
"use client"
// The review table: every extracted line with where it was read, and a person's decision on each --
// approve, edit or reject -- and a bulk approve for what is still pending. A decision is a call to
// the proposals module's own procedures (or `documents.line.edit`), so the permission, the
// transaction and the audit event are the kernel's, and this component adds none of its own.
//
// What is machine-proposed says so: the disclosure badge (EU AI Act Art. 50) heads the table, and
// each row names the model and extractor version that proposed it.
import { AiDisclosureBadge, EvidencePanel } from "@conusai-kit/ui"
import { type FormEvent, type ReactElement, useState } from "react"
import type { LineView } from "../contract/index.js"
import { REVIEW_PROCEDURES } from "../contract/review.js"
import type { ProjectSlugs } from "./slugs.js"
import { useTransport } from "./transport.js"
export interface ReviewLabels {
readonly caption: string
readonly empty: string
readonly label: string
readonly value: string
readonly page: string
readonly source: string
readonly confidence: string
readonly decision: string
readonly actions: string
/** Contain `{label}`, replaced with the line's label: the button's accessible name. */
readonly approve: string
readonly reject: string
readonly edit: string
readonly save: string
readonly cancel: string
readonly approveAll: string
/** Shown beside a line whose quote was not found on the page it cites. */
readonly needsReview: string
/** Shown instead of `needsReview` for a quote the model read off the image of a scanned page. */
readonly scannedQuote: string
/** Contains `{id}`, replaced with the reader's block id of the quote. */
readonly block: string
readonly edited: string
readonly sourceOnPage: string
readonly proposedBy: string
readonly aiDisclosure: string
/** The word before a page number ("Page"): the evidence panel prints it beside the number. */
readonly pageWord: string
readonly states: Readonly<Record<LineView["status"], string>>
/** The app's `app.failed` message, with `{detail}` still in it. */
readonly failed: string
}
/** `approveMany` takes at most the proposals policy's `maxBatch`; its default is 100. */
const BATCH = 100
export function LineReview({
slugs,
lines,
labels,
locale,
fileId,
}: {
slugs: ProjectSlugs
lines: readonly LineView[]
labels: ReviewLabels
/** 0.97 as "97 %": the locale's own number format. */
locale: string
fileId: string
}): ReactElement {
const { call, describe } = useTransport()
const [editing, setEditing] = useState<string | undefined>(undefined)
const [busy, setBusy] = useState(false)
const [problem, setProblem] = useState<string | undefined>(undefined)
const pending = lines.filter((line) => line.status === "pending")
// A line whose quote nobody could find on the page is never approved in bulk: a person looks at it.
const approvable = pending.filter((line) => line.verification === "verified")
/** Runs one decision, then reloads: the server renders the truth, including the counts and the export. */
const run = async (work: () => Promise<unknown>): Promise<void> => {
setBusy(true)
setProblem(undefined)
try {
await work()
window.location.reload()
} catch (error) {
setProblem(labels.failed.replace("{detail}", describe(error)))
setBusy(false)
}
}
const approve = (line: LineView) =>
run(() =>
call(REVIEW_PROCEDURES.approve, { ...slugs, proposalId: line.lineId, expectedVersion: line.expectedVersion }),
)
const reject = (line: LineView) => run(() => call(REVIEW_PROCEDURES.reject, { ...slugs, proposalId: line.lineId }))
const approveAll = () =>
run(async () => {
for (let at = 0; at < approvable.length; at += BATCH) {
await call(REVIEW_PROCEDURES.approveMany, {
...slugs,
proposalIds: approvable.slice(at, at + BATCH).map((line) => line.lineId),
expectations: Object.fromEntries(
approvable.slice(at, at + BATCH).map((line) => [line.lineId, { expectedVersion: line.expectedVersion }]),
),
})
}
})
const save = (line: LineView, event: FormEvent<HTMLFormElement>) => {
event.preventDefault()
const form = new FormData(event.currentTarget)
return run(() =>
call("documents.line.edit", {
...slugs,
lineId: line.lineId,
label: String(form.get("label") ?? ""),
value: String(form.get("value") ?? ""),
}),
)
}
if (lines.length === 0) {
return <p>{labels.empty}</p>
}
return (
<section aria-busy={busy}>
<p>
<AiDisclosureBadge label={labels.aiDisclosure} />
</p>
{approvable.length > 0 ? (
<button type="button" disabled={busy} onClick={approveAll}>
{labels.approveAll}
</button>
) : null}
{problem ? (
<p className="app-error" role="alert">
{problem}
</p>
) : null}
<table>
<caption>{labels.caption}</caption>
<thead>
<tr>
<th scope="col">{labels.label}</th>
<th scope="col">{labels.value}</th>
<th scope="col">{labels.page}</th>
<th scope="col">{labels.source}</th>
<th scope="col">{labels.confidence}</th>
<th scope="col">{labels.decision}</th>
<th scope="col">{labels.actions}</th>
</tr>
</thead>
<tbody>
{lines.map((line) => (
<tr key={line.lineId}>
{editing === line.lineId ? (
<td colSpan={7}>
<form onSubmit={(event) => save(line, event)}>
<div className="kit-field">
<label htmlFor={`label-${line.lineId}`}>{labels.label}</label>
<input
id={`label-${line.lineId}`}
name="label"
defaultValue={line.label}
required
maxLength={200}
/>
</div>
<div className="kit-field">
<label htmlFor={`value-${line.lineId}`}>{labels.value}</label>
<input
id={`value-${line.lineId}`}
name="value"
defaultValue={line.value}
required
maxLength={2000}
/>
</div>
<button type="submit" disabled={busy}>
{labels.save}
</button>
<button type="button" disabled={busy} onClick={() => setEditing(undefined)}>
{labels.cancel}
</button>
</form>
</td>
) : (
<>
<th scope="row">{line.label}</th>
<td>
{line.value}
{line.edited ? ` (${labels.edited})` : ""}
</td>
<td>{line.page}</td>
<td>
<EvidencePanel
evidence={{
fileId,
page: line.page,
quote: line.quote,
...(line.box ? { box: line.box } : {}),
}}
label={labels.sourceOnPage.replace("{page}", String(line.page))}
pageLabel={labels.pageWord}
/>
<small>{labels.proposedBy.replace("{model}", `${line.model} ${line.promptVersion}`)}</small>
{line.blockId ? <small>{` ${labels.block.replace("{id}", line.blockId)}`}</small> : null}
</td>
<td>{new Intl.NumberFormat(locale, { style: "percent" }).format(line.confidence)}</td>
<td>
{labels.states[line.status]}
{line.verification === "needs_review" ? (
<strong>{` ${line.note === "scanned_quote" ? labels.scannedQuote : labels.needsReview}`}</strong>
) : null}
</td>
<td>
{line.status === "pending" ? (
<>
<button type="button" disabled={busy} onClick={() => approve(line)}>
{labels.approve.replace("{label}", line.label)}
</button>
<button type="button" disabled={busy} onClick={() => setEditing(line.lineId)}>
{labels.edit.replace("{label}", line.label)}
</button>
<button type="button" disabled={busy} onClick={() => reject(line)}>
{labels.reject.replace("{label}", line.label)}
</button>
</>
) : null}
</td>
</>
)}
</tr>
))}
</tbody>
</table>
</section>
)
}