Files
writer-work-flow/apps/web/components/chain/ChainProgress.tsx
Yaojia Wang a2dcd89881 feat(web): Scope B B1 — 多章工作流链前端 UI(发起/轮询/裁决续跑)
链页 app/projects/[id]/chains(RSC fetchProject)+ ChainPage/ChainStarter/
ChainProgress/ChainAdjudication;lib/chain 纯函数(result 收窄/相位映射/resume 组装)。
复用 useJobPoll 轮询、ConflictCard + decisions.ts 裁决、normalizeConflicts/latestReview、
friendlyError;nav 加 chains 入口。awaiting(interrupt)→ 读该章冲突渲 ConflictCard →
POST .../chains/runs/{job_id}/resume 续跑。gen:api 纳入 chains run/resume。
守 #5(链暂停=等裁决,token/正文不入 job result)。
2026-06-23 19:21:00 +02:00

64 lines
1.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import type { ChainPhase, ChainResultView } from "@/lib/chain/chain";
interface ChainProgressProps {
phase: ChainPhase;
progress: number;
result: ChainResultView;
}
const PHASE_LABEL: Record<ChainPhase, string> = {
running: "运行中",
awaiting: "等待裁决",
completed: "已完成",
failed: "已失败",
};
// 链进度视图:相位徽标 + 进度条 + 已写章号清单token/正文绝不展示result 只含元数据)。
export function ChainProgress({ phase, progress, result }: ChainProgressProps) {
return (
<section
className="flex flex-col gap-3 rounded border border-line bg-panel p-5"
aria-label="链进度"
>
<div className="flex items-center justify-between gap-3">
<h2 className="font-serif text-lg text-ink"></h2>
<span
className="rounded bg-cinnabar/10 px-2 py-0.5 text-xs text-cinnabar"
aria-live="polite"
>
{PHASE_LABEL[phase]}
</span>
</div>
<div
className="h-2 w-full overflow-hidden rounded bg-bg"
role="progressbar"
aria-valuenow={progress}
aria-valuemin={0}
aria-valuemax={100}
>
<div
className="h-full bg-cinnabar transition-all"
style={{ width: `${progress}%` }}
/>
</div>
<p className="text-sm text-ink-soft">
{result.written.length}
{result.written.length > 0 ? (
<span className="ml-1 font-mono text-ink">
{result.written.join("、")}
</span>
) : null}
</p>
{phase === "awaiting" && result.awaitingChapter !== null ? (
<p className="text-sm text-conflict">
{result.awaitingChapter}
</p>
) : null}
</section>
);
}