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)。
This commit is contained in:
80
apps/web/components/chain/ChainStarter.tsx
Normal file
80
apps/web/components/chain/ChainStarter.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
interface ChainStarterProps {
|
||||
// 发起一条链(起始章 + 章数);父层负责 POST run + 轮询。
|
||||
onStart: (startChapterNo: number, count: number) => void;
|
||||
// 链正在运行/等待裁决时禁用(避免重复发起)。
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
const DEFAULT_START = 1;
|
||||
const DEFAULT_COUNT = 3;
|
||||
const MAX_COUNT = 50;
|
||||
|
||||
// 链发起表单(净新):选起始章号 + 连续写几章 → 调 onStart。
|
||||
// count 1..50(对齐后端 ChainRunRequest Field 约束;前端先拦一道,越界后端 422 兜底)。
|
||||
export function ChainStarter({ onStart, disabled }: ChainStarterProps) {
|
||||
const [start, setStart] = useState(String(DEFAULT_START));
|
||||
const [count, setCount] = useState(String(DEFAULT_COUNT));
|
||||
|
||||
const startNo = Number.parseInt(start, 10);
|
||||
const countNo = Number.parseInt(count, 10);
|
||||
const valid =
|
||||
Number.isInteger(startNo) &&
|
||||
startNo >= 1 &&
|
||||
Number.isInteger(countNo) &&
|
||||
countNo >= 1 &&
|
||||
countNo <= MAX_COUNT;
|
||||
|
||||
return (
|
||||
<form
|
||||
className="flex flex-col gap-4 rounded border border-line bg-panel p-5"
|
||||
aria-label="发起多章链"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
if (valid && !disabled) onStart(startNo, countNo);
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<h2 className="font-serif text-lg text-ink">连续写多章</h2>
|
||||
<p className="mt-1 text-sm text-ink-soft">
|
||||
从指定章起循环「写章 → 四审 → 验收」;遇未决冲突会暂停等你裁决再续跑。
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
<label className="block text-sm text-ink-soft">
|
||||
起始章号
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
value={start}
|
||||
onChange={(e) => setStart(e.target.value)}
|
||||
className="mt-1 w-32 rounded border border-line bg-bg px-3 py-2 text-sm text-ink"
|
||||
aria-label="起始章号"
|
||||
/>
|
||||
</label>
|
||||
<label className="block text-sm text-ink-soft">
|
||||
连续章数(1..{MAX_COUNT})
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
max={MAX_COUNT}
|
||||
value={count}
|
||||
onChange={(e) => setCount(e.target.value)}
|
||||
className="mt-1 w-32 rounded border border-line bg-bg px-3 py-2 text-sm text-ink"
|
||||
aria-label="连续章数"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={disabled || !valid}
|
||||
className="self-start rounded bg-cinnabar px-4 py-2 text-sm text-white disabled:opacity-50"
|
||||
>
|
||||
{disabled ? "运行中…" : "发起多章链"}
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user