import { useEffect, useRef, useState } from 'react' import { Link, useSearchParams } from 'react-router-dom' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' import { Card } from '@/components/ui/card' import { Textarea } from '@/components/ui/textarea' import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription } from '@/components/ui/sheet' import { PageHeading, Panel, Empty } from '@/components/shared' import DecisionChat from '@/components/decision-chat' import VersionNotice from '@/components/version-notice' import { useLocalDraft } from '@/lib/use-local-draft' import { useWorkbench } from '@/lib/context' import { ApiError, errorText, messageKey, request } from '@/lib/api' import { date, type Decision } from '@/lib/types' import { decisionStatus, decisionLabels, writable, submissionBody, confirmDraft, type DecisionDraft } from './decision-ui' type HistoryEntry = { id?: string | number; kind?: string; created?: number; revision?: number; data?: Record } function Status({ decision }: { decision: Decision }) { const status = decisionStatus(decision); return {decisionLabels[status]} } function Text({ label, text }: { label: string; text?: string }) { return text ?

{label}

{text}

: null } const historyLabels: Record = { version: '决策版本', submission: '提交选择', review: '主控检查', completion: '完成交接', binding: '绑定咨询会话', archive: '归档确认', linked: '关联决策', created: '建立决策', updated: '内容更新', revised: '版本更新', submitted: '提交选择', responded: '提交选择', reviewed: '检查结论', completed: '完成交接', bound: '绑定咨询会话', session_bound: '绑定咨询会话', archived: '归档确认', fact_requested: '请求核实', fact_answered: '核实回复' } function HistoryRecord({ entry }: { entry: HistoryEntry }) { const data = entry.data ?? {} const snapshot = data.snapshot && typeof data.snapshot === 'object' ? data.snapshot as Record : data.content && typeof data.content === 'object' ? data.content as Record : data const options = Array.isArray(snapshot.options) ? snapshot.options.filter((item): item is { id: string; label: string; description?: string } => Boolean(item && typeof item === 'object' && typeof item.id === 'string' && typeof item.label === 'string')) : [] return
  • {historyLabels[entry.kind ?? ''] || '处理记录'}{entry.revision ? ` · 版本 ${entry.revision}` : ''} · {date(entry.created)}

    {typeof data.option_id === 'string' &&

    当时选择:{options.find(option => option.id === data.option_id)?.label || data.option_id}

    } {Object.entries({ reason:'理由', comment:'提交意见', conclusion:'完成结论', handoff:'交接', impact:'影响', execution_plan:'执行计划', question:'问题', answer:'核实回复' }).map(([key,label]) => typeof data[key] === 'string' ? : null)} {snapshot !== data && typeof snapshot.question === 'string' && } {options.length > 0 &&
    查看当时全部方案
      {options.map(option =>
    • {option.label}

      {option.description}

    • )}
    }
  • } function Detail({ id, initial }: { id: string; initial?: Decision }) { const { token, refresh, state } = useWorkbench() const [decision, setDecision] = useState(initial), [history, setHistory] = useState([]), [error, setError] = useState(''), [notice, setNotice] = useState(''), [, redraw] = useState(0) const reload = useRef<() => Promise>(async () => {}), generation = useRef(0) useEffect(() => { const controller = new AbortController(); let pending = false const poll = async () => { if (pending || controller.signal.aborted) return pending = true; const seq = ++generation.current try { const current = await request(token, `/api/decisions/${encodeURIComponent(id)}`, undefined, controller.signal); if (!controller.signal.aborted && seq === generation.current) { setDecision(current); setError('') } const events = await request<{ history: HistoryEntry[] }>(token, `/api/decisions/${encodeURIComponent(id)}/history`, undefined, controller.signal); if (!controller.signal.aborted && seq === generation.current) setHistory(events.history) } catch (cause) { if (!controller.signal.aborted && seq === generation.current) setError(errorText(cause)) } finally { pending = false } } reload.current = poll; void poll(); const timer = setInterval(() => void poll(), 3000) return () => { controller.abort(); clearInterval(timer) } }, [id, token]) const revision = decision?.revision ?? 1 const local = useLocalDraft(state.instance_id && decision && Number.isInteger(decision.revision) && (decision.revision ?? 0) > 0 ? [state.instance_id, 'decision', id, String(revision)] : null, {option:null,comment:'',revision,conflict:false}, (v): v is DecisionDraft => Boolean(v && typeof v === 'object' && typeof (v as DecisionDraft).comment === 'string' && Number.isInteger((v as DecisionDraft).revision)), true) const draft = local.value const sending = useRef(false) function persist() { local.save(draft); redraw(n => n + 1) } const changed = Boolean(draft && (draft.revision !== revision || draft.conflict)) const editable = Boolean(decision && writable(decision) && !draft?.sending && local.ready) async function submit() { if (!decision || !draft || sending.current || !editable || changed || (!draft.option && !draft.comment.trim())) return const body = submissionBody(decision, draft) if (!body) return const signature = JSON.stringify(body) if (!draft.attempt || draft.attempt.signature !== signature) draft.attempt = { signature, key: messageKey() } if (!local.save(draft)) return sending.current = true draft.sending = true; redraw(n => n + 1); setNotice('') try { const result = await request(token, `/api/decisions/${encodeURIComponent(id)}/respond`, { ...body, idempotency_key: draft.attempt.key }) ++generation.current; setDecision(result); setNotice(''); local.clear() await refresh().catch(() => {}); await reload.current() } catch (cause) { if (cause instanceof ApiError && cause.status === 409) { draft.conflict = true; persist(); setNotice('内容或状态已变化,草稿已保留。请阅读最新内容并重新确认,未自动重交。'); await reload.current() } else setNotice(`${errorText(cause)} 草稿已保留,相同内容重试复用原提交记录。`) } finally { sending.current = false; draft.sending = false; redraw(n => n + 1) } } return <>{decision?.title ?? '读取决策…'}{decision?.summary || decision?.question || '查看方案、检查结论与本决策咨询。'}
    {error &&

    {error} 保留已读取内容。

    } {decision && draft ?
    {local.error &&

    {local.error}

    }{local.error && }{!writable(decision) && (draft.comment || draft.option) &&

    保留的本地草稿(只读,未自动提交)

    版本 {draft.revision} · 选项 {draft.option || '无'}

    {draft.comment}

    }
    版本 {revision}{decision.task_id && {state.tasks.find(task => task.id === decision.task_id)?.name || decision.task_id}}
    {decision.deadline && Number.isFinite(Date.parse(decision.deadline)) &&

    截止时间:{date(decision.deadline)}

    }

    选择提交后需检查;接受方案不代表权限授权,也不代表已经完成。

    {decision.options.length > 0 &&
    全部方案{decision.options.map(option => )}
    } {writable(decision) &&