import { lazy, Suspense, useEffect, useMemo, useState } from "react"; import type { ChangeDetail, CollectionSummaryResponse, DocumentComparisonResponse, ModifierStageResponse, RunStatus, } from "../shared/api.js"; import { fetchCollection, fetchDocument, fetchModifierStage } from "./api-client.js"; const DiffView = lazy(async () => { const module = await import("./DiffView.js"); return { default: module.DiffView }; }); interface AsyncState { loading: boolean; value: T | null; error: string | null; } const emptyState = (): AsyncState => ({ loading: true, value: null, error: null }); function statusLabel(status: RunStatus): string { switch (status) { case "success": return "成功"; case "failed": return "失败"; case "unstable": return "不稳定"; } } function shortHash(hash: string): string { return `${hash.slice(0, 8)}…${hash.slice(-6)}`; } function ErrorPanel({ message }: { message: string }) { return (
无法显示

{message}

); } function LoadingPanel() { return (

正在校验本地评审数据…

); } function ChangeList({ changes, onSelect, canJump, }: { changes: ChangeDetail[]; onSelect: (change: ChangeDetail) => void; canJump: boolean; }) { if (changes.length === 0) { return

这个 Modifier 运行过,但没有修改当前文档。

; } return (
    {changes.map((change) => (
  1. ))}
); } export function App() { const [collectionState, setCollectionState] = useState>(emptyState); const [selectedDocument, setSelectedDocument] = useState(null); const [documentState, setDocumentState] = useState>({ loading: false, value: null, error: null, }); const [selectedModifier, setSelectedModifier] = useState(null); const [stageState, setStageState] = useState>({ loading: false, value: null, error: null, }); const [focusRange, setFocusRange] = useState<{ start: number; end: number } | null>(null); useEffect(() => { const controller = new AbortController(); fetchCollection(controller.signal) .then((collection) => { setCollectionState({ loading: false, value: collection, error: null }); setSelectedDocument(collection.documents[0]?.document_id ?? null); }) .catch((error: unknown) => { if (!controller.signal.aborted) { setCollectionState({ loading: false, value: null, error: error instanceof Error ? error.message : "无法读取评审集合摘要。", }); } }); return () => controller.abort(); }, []); useEffect(() => { setSelectedModifier(null); setFocusRange(null); if (selectedDocument === null) { setDocumentState({ loading: false, value: null, error: null }); return undefined; } const controller = new AbortController(); setDocumentState(emptyState()); fetchDocument(selectedDocument, controller.signal) .then((document) => setDocumentState({ loading: false, value: document, error: null })) .catch((error: unknown) => { if (!controller.signal.aborted) { setDocumentState({ loading: false, value: null, error: error instanceof Error ? error.message : "无法读取文档。", }); } }); return () => controller.abort(); }, [selectedDocument]); useEffect(() => { if (selectedDocument === null || selectedModifier === null) { setStageState({ loading: false, value: null, error: null }); return undefined; } const controller = new AbortController(); setStageState(emptyState()); fetchModifierStage(selectedDocument, selectedModifier, controller.signal) .then((stage) => setStageState({ loading: false, value: stage, error: null })) .catch((error: unknown) => { if (!controller.signal.aborted) { setStageState({ loading: false, value: null, error: error instanceof Error ? error.message : "无法读取 Modifier 阶段。", }); } }); return () => controller.abort(); }, [selectedDocument, selectedModifier]); const visibleChanges = useMemo(() => { const document = documentState.value; if (document === null) { return []; } if (selectedModifier === null) { return document.changes; } return document.changes.filter((change) => change.modifier_position === selectedModifier); }, [documentState.value, selectedModifier]); const selectChange = (change: ChangeDetail): void => { setSelectedModifier(change.modifier_position); setFocusRange(change.editor_range); }; if (collectionState.loading) { return ; } if (collectionState.error !== null || collectionState.value === null) { return ; } const collection = collectionState.value; const document = documentState.value; const selectedSummary = collection.documents.find( (item) => item.document_id === selectedDocument, ); const selectedStage = stageState.value; const canCompare = document?.document.status === "success" && document.current_markdown !== null; const beforeText = selectedStage?.before_markdown ?? document?.input_markdown ?? ""; const afterText = selectedStage?.after_markdown ?? document?.current_markdown ?? ""; const beforeLabel = selectedStage === null ? "清洗前" : `Modifier ${selectedStage.modifier.modifier_position + 1} 执行前`; const afterLabel = selectedStage === null ? "清洗后" : `Modifier ${selectedStage.modifier.modifier_position + 1} 执行后`; return (
md

本地清洗评审器

{collection.collection.label}

{statusLabel(collection.collection.status)} {collection.summary.document_count} 份文档 {collection.summary.change_count} 条修改

当前文档

{selectedSummary?.source_label ?? "未选择"}

{selectedSummary === undefined ? null : (
{statusLabel(selectedSummary.status)} 输入 {shortHash(selectedSummary.input_sha256)} 当前 {shortHash(selectedSummary.current_sha256)}
)}
{documentState.loading || stageState.loading ? : null} {documentState.error !== null ? : null} {stageState.error !== null ? : null} {!documentState.loading && document !== null && document.document.status !== "success" ? (

没有正式清洗结果

{statusLabel(document.document.status)}文档只展示审计证据

partial output 不会在这里命名为清洗结果。

{document.errors.map((error) => (
{error.diagnostic_type} {error.message}
))} {document.residual_proposals.map((proposal) => (
最终复查仍有 {proposal.edits.length} 项候选编辑 {proposal.reason}
))}
) : null} {!documentState.loading && !stageState.loading && canCompare && stageState.error === null ? ( }> ) : null} {document !== null ? (

实际修改

{selectedModifier === null ? `全部 Modifier · ${visibleChanges.length} 条` : `${document.modifiers[selectedModifier]?.modifier_id ?? "Modifier"} · ${ visibleChanges.length } 条`}

{selectedStage === null ? null :

{selectedStage.modifier.applicability}

}
) : null}
); }