32 KiB
多智能体规划 · 后台挂起执行 + 进度可视化 —— 分步实现方案
目标:在「多智能体规划」第二步增加后台挂起能力——把当前任务挂到后台,自动一路跑到 最后一步(Step3 结果绘制完成)为止;历史记录下拉框里进行中的任务显示加载动画;点击进行中 的任务弹出一个大弹窗,里面是横向的智能体派活链路流程图(节点 = 各席位智能体,图标 + 名称,不同颜色,正在发言的节点和它的连接线带加载动画);弹窗里有「查看」按钮,点击进入正在 进行中的任务实时观察。
0. 现状与架构事实(动手前必读)
研究自现有代码,决定了整套方案的形态。关键结论:当前编排循环跑在前端,离开页面即中断。
0.1 前端现状
| 事实 | 位置 |
|---|---|
Step2 编排循环(leader → 顺序派 special → leader … 直到 dispatched===[] 共识 / MAX_CYCLES=8)完全由前端 hook 驱动 |
src/roundtable-planning/hooks/useStep2Orchestration.ts:992-1204(recommend)、1228-1439(chain) |
循环由 currentStep===2 的 useEffect 启停;离开 Step2 的 cleanup 会 orchestrationCancelRef.current=true + abortAllInFlight() 中断 |
useStep2Orchestration.ts:1468-1541(尤其 cleanup 1534-1537、1471-1477) |
共识判定:leaderRes.dispatched.length===0 → setHasConsensus(true) |
useStep2Orchestration.ts:1091-1099 |
进入 Step3:手动点按钮,前置 hasConsensus===true |
Step2Panel.tsx:370-381、RoundtablePlanningPage.tsx:586-595 |
Step3「结果绘制」:前端流式调 roundtable-report 智能体,写出 outputs/方案总览.html,phase→"done" 即完成 |
src/roundtable-planning/hooks/useStep3Report.ts:36,51,165,560-596,636-652 |
草稿持久化:/api/roundtable-drafts,含 step1/step2/step3 三段快照 + `furthestStep(1 |
2 |
| 历史下拉 UI(触发按钮 + 列表 + 重命名/删除) | RoundtablePlanningPage.tsx:771-892 |
| 自动保存触发点(Step1 回复完 / Step2 每轮气泡收尾 / Step3 报告保存 / 步骤前进) | useDraftPersistence.ts:248-316 |
加载草稿恢复:handleLoadDraft → getDraft → cancelAllStreams → hydrate → setCurrentStep(furthestStep) |
useDraftPersistence.ts:308-368 |
0.2 后端现状
| 事实 | 位置 |
|---|---|
/api/multi-agent/init:为各席位并行建 thread + 单例总控 roundtable-coordinator,SSE 返回 seat_ready/coordinator_ready/init_done(thread_ids) |
app/gateway/routers/multi_agent.py:647-823 |
/api/multi-agent/run/stream:驱动单轮 leader 或 special 的 LLM 执行,末尾 JSON 帧 status(派活列表 / [] 共识 / clarification / error) |
multi_agent.py:1076-1142 |
/api/multi-agent/report/init:为 Step3 报告建 thread |
multi_agent.py(report/init) |
后端不存在编排循环——leader 派活由前端解释后再逐个发 run/stream;后端只做单轮执行 + 把派活/完成「广播」追加进各 thread 历史消息 |
multi_agent.py:24-29,487-502,1108-1142 |
派活格式:agent_orchestration 工具调用 {agent_name, task}(旧式 bash 解析兼容) |
multi_agent.py:525-552 |
0.3 可复用模板:AI 写作的「后台多阶段流程」
AI 写作已经把「后台跑多阶段流程 + 前端 SSE 订阅 + 可 resume + 定时清理」跑通了,圆桌后台作业直接照搬这套:
| 模板要素 | 位置 |
|---|---|
LangGraph 图驱动多阶段(researching/writing_outline/writing_draft/reviewing/…),条件路由推进,interrupt() 暂停点等用户输入 |
packages/harness/deerflow/agents/ai_writing/graph.py、nodes/*(pause_material_confirm 等 45-290) |
Session 持久化表 ai_writing_sessions:id/user_id/title/status/draft_*/transcript(LONGTEXT)/...,status∈{in_progress,done,error} |
packages/harness/deerflow/persistence/ai_writing_sessions/model.py:14-39 |
transcript 用 PortableLongText(MySQL LONGTEXT,避开 64KB),时间用 BeijingDateTime |
同上 27-38 |
进度走 LangGraph Server 标准流 stream_mode=["messages-tuple","values"],graph 在 langgraph runtime 里跑,任意 worker 可 join stream(这是后台健壮性的关键) |
ai_writing.py:1-17、langgraph.json:16-18 |
Checkpointer 共享 AsyncSqliteSaver,落 .deer-flow/data/checkpoints.db(需挂数据卷) |
runtime/checkpointer/async_provider.py |
| Cleanup cron(leader 文件锁、活跃保护、先删 CP 再删 DB、admin 手动触发) | app/gateway/ai_writing_cleanup.py:26,34-59,111-135,193-215,307-367 |
多 worker 坑:模块级内存 dict(_event_queues)每 worker 一份会丢会话;SSE 进程内直调会整段 buffer。解法:状态进 DB+checkpointer、SSE 走真 TCP loopback 127.0.0.1(trust_env=False 禁代理) |
multi_agent.py:271-293、AIWritingPage.tsx 文件头注释 §3/§5 |
0.4 决策(已与产品确认)
- 后端真后台执行:把编排循环搬到后端 run,刷新 / 关页 / 换设备都不中断。
- 流程图节点 = 智能体派活链路:横向排列的席位节点,正在发言的节点 + 连接线加载动画。
⚠️ 本方案最大的工作量在 Phase 2:把目前跑在前端 TS 里的编排循环(
runOrchestration/runChainOrchestration) 移植成一个后端 LangGraph「圆桌总控编排图」。这是与 AI 写作一致、可在 LangGraph runtime 里 后台跑且天然 checkpoint 的健壮路径。下文每个阶段都给出验收标准,可独立提测。
1. 总体架构
┌─────────────────────────────── 前端 (frontend-web) ───────────────────────────────┐
│ Step2Panel ──「后台挂起」按钮──▶ POST /api/roundtable-jobs (start) │
│ HistoryDropdown ── 进行中任务显示 spinner ──▶ 点击 ──▶ DispatchChainModal(大弹窗) │
│ DispatchChainModal ── 订阅 GET /api/roundtable-jobs/{id}/stream(SSE) │
│ 横向派活链路流程图(节点=席位, 活动节点+连线动画) ──「查看」──▶ 进入任务实时观察 │
└───────────────────────────────────────────────────────────────────────────────────┘
│ SSE 进度 / 快照
┌─────────────────────────────── 后端 (backend) ─────────────────────────────────────┐
│ /api/roundtable-jobs 路由层(start/get/stream/resume/cancel) ── 仿 ai_writing.py │
│ │ 启动后台 run │
│ LangGraph「圆桌总控编排图」 roundtable_orchestrator ── 仿 ai_writing graph │
│ init → leader_call → parse_dispatch → dispatch_seats(顺序) → loop ↺ │
│ → consensus → report_draw(写 HTML) → done (clarification 点 interrupt()) │
│ │ 每次状态转移持久化进度 │
│ roundtable_jobs 表(status/phase/dispatch_chain/active_speaker/...) + 共享 checkpointer│
│ cleanup cron (仿 ai_writing_cleanup.py) │
└───────────────────────────────────────────────────────────────────────────────────┘
核心思路:后台作业 = 一个 LangGraph 图的一次 run。图节点内部复用现有的单轮执行能力
(multi_agent.py 里 leader/special 的 run 逻辑),把「解释派活 → 顺序派活 → 回到 leader」的
循环从前端搬进图节点。前端从「驱动者」退化为「观察者」:通过 SSE 订阅进度渲染。
2. 数据契约(前后端共享,先定义后实现)
2.1 作业状态机 JobStatus
queued # 已创建,等待 worker 拉起
running # 编排进行中(Step2 派活循环 / Step3 绘制)
awaiting_input # 命中 clarification,等用户回答(interrupt 挂起)
done # 已跑到 Step3 绘制完成
error # 出错终止
cancelled # 用户取消
2.2 作业阶段 JobPhase(细化给流程图/进度条用)
initializing # 建 thread / 总控就绪
leader_thinking # 总控在决策派活
dispatching # 正在顺序派活给席位(配合 active_speaker)
consensus # 共识达成(dispatched=[])
report_drawing # Step3 报告绘制中
report_done # 绘制完成
2.3 派活链路节点 DispatchChainNode(流程图核心数据)
interface DispatchChainNode {
agentId: string; // 席位 agent_id(小写)
name: string; // 显示名
avatarType: string; // 复用 getAvatarTypeFor(agentId, index) → 决定颜色
order: number; // 横向排列次序(chain 模式=链条顺序;recommend 模式=席位/发言顺序)
state: 'pending' | 'active' | 'done'; // 节点状态(驱动颜色/动画)
lastSpokeCycle?: number; // 最近发言轮次(可选,做 tooltip)
}
2.4 进度事件 JobProgressEvent(SSE 推送的归一化事件)
type JobProgressEvent =
| { type: 'status'; status: JobStatus }
| { type: 'phase'; phase: JobPhase; cycle: number }
| { type: 'chain'; nodes: DispatchChainNode[] } // 派活链路全量/增量
| { type: 'active'; agentId: string | null } // 当前发言席位(null=总控/无)
| { type: 'consensus'; percentage: number }
| { type: 'dialogue'; bubble: Step2DialogueLike } // 新增/收尾的对话气泡(供「查看」实时渲染)
| { type: 'clarification'; question: string } // 命中澄清
| { type: 'done'; step3: DraftStep3Snapshot } // 终态:最终报告快照
| { type: 'error'; detail: string };
SSE 通道可直接复用 LangGraph Server 的
values流(图状态快照),由前端 hook 把values映射成上述归一化事件;也可在路由层做映射后再下发。推荐前端映射(少一层后端耦合)。
2.5 作业记录 RoundtableJob(持久化 + 列表展示)
interface RoundtableJob {
id: string;
draftId: string; // 关联的草稿
userId: string;
status: JobStatus;
phase: JobPhase;
cycle: number; // 当前轮次(0..MAX_CYCLES)
dispatchChain: DispatchChainNode[];
activeAgentId: string | null;
consensusPercentage: number;
pendingClarification: string | null;
threadIds: Record<string,string> | null; // 复用 ThreadIdMap
coordinatorName: string | null;
orchestrationMode: 'recommend' | 'chain';
chain?: { id: string; title: string } | null;
error: string | null;
createdAt: string;
updatedAt: string;
}
Phase 1 —— 后端:圆桌作业持久化模型
目标:建一张 roundtable_jobs 表(仿 ai_writing_sessions),存作业状态/阶段/派活链路,
并在 roundtable_drafts 列表里能 join 出当前作业状态。
涉及文件(新增/改)
- 新增
packages/harness/deerflow/persistence/roundtable_jobs/model.py(ORM 模型,仿ai_writing_sessions/model.py:14-39) - 新增
.../roundtable_jobs/sql.py(Repository:create / get / update_progress / list_by_user / list_older_than / delete,仿ai_writing_sessions/sql.py) - 新增 alembic 迁移
persistence/migrations/versions/<date>_create_roundtable_jobs.py - 改
roundtable_drafts的列表查询:返回里带上status/jobId(LEFT JOIN roundtable_jobs,或在 draft 行上冗余一个status字段,二选一,见下)
步骤
- 字段设计(对齐 §2.5):
id(PK)、draft_id、user_id、status、phase、cycle(Int)、dispatch_chain(PortableLongText/JSON)、active_agent_id、consensus_percentage(Int)、pending_clarification(Text)、thread_ids(JSON)、coordinator_name、orchestration_mode、chain(JSON)、error(Text)、created_at/updated_at(BeijingDateTime)。 dispatch_chain/thread_ids/chain用PortableLongText存 JSON 字符串,repo 层序列化。- 列表 join:给
DraftMeta增加status?: JobStatus与jobId?: string。 推荐方案:草稿与作业 1:1(一个草稿最多一个活跃作业),在 draft 列表查询 LEFT JOINroundtable_jobs取最新作业的status。 - 索引:
(user_id, updated_at)、(draft_id)、(status)(cleanup/列表用)。
验收
- 迁移可
alembic upgrade head跑通,MySQL 上dispatch_chain为longtext。 - 单测:create→update_progress→get 往返;
list_by_user按updated_at倒序;list_older_than分批。
Phase 2 —— 后端:圆桌总控编排 LangGraph 图(核心 / 最大工作量)
目标:把前端的编排循环移植成一个后端 LangGraph 图,可在 LangGraph runtime 里后台跑、天然 checkpoint,一路跑到 Step3 绘制完成。
涉及文件(新增)
packages/harness/deerflow/agents/roundtable_orchestrator/graph.py(图装配,仿ai_writing/graph.py).../roundtable_orchestrator/state.py(图状态 schema:intent、selectedAgents、threadIds、coordinatorName、 mode/chain、dialogues、dispatchChain、activeAgentId、cycle、consensus、phase、step3 等).../roundtable_orchestrator/nodes/*.py(各节点)- 在
langgraph.json注册新 assistantroundtable_orchestrator(仿 ai_writing assistant 注册,参考langgraph.json:16-18)
图节点设计(把前端循环逐段翻译过来)
| 节点 | 职责 | 对应前端逻辑 |
|---|---|---|
init |
复用 _create_thread_direct 批量建席位 thread + 总控就绪(或接收前端已建好的 threadIds 直接用) |
useStep2Orchestration.ts:1485-1531 / multi_agent.py:647-823 |
leader_call |
跑一轮总控(agent_type=leader),拿 status(派活列表 / [] / clarification) |
单轮逻辑 multi_agent.py:1076-1142 |
route_after_leader(条件边) |
clarification→interrupt;有派活→dispatch_seats;[]→consensus |
useStep2Orchestration.ts:1091-1181 |
dispatch_seats |
顺序派活:对 dispatched 里每个席位依次跑 special run;每开始一个就更新 activeAgentId + 进度持久化 |
useStep2Orchestration.ts:1101-1181(务必串行,勿并行,详见前端 §5.4 注释) |
clarification_pause |
interrupt() 挂起,status→awaiting_input,写 pending_clarification |
仿 ai_writing pause_* 45-290 |
consensus |
setHasConsensus、consensus=100,落库后进入 report_draw |
1091-1099 |
report_draw |
跑 roundtable-report(先 report/init 建 thread),流式写 outputs/方案总览.html,产出 DraftStep3Snapshot |
useStep3Report.ts:91-149,636-652,560-596 |
done |
status→done、写 step3 进作业 + 回写草稿 step3 快照 |
RoundtablePlanningPage.tsx:613-617 |
关键实现要点
- 复用单轮执行:节点内部不要重写 LLM 调用,抽出
multi_agent.py里 leader/special/report 的 单轮 run 为可被图节点直接await的内部函数(去掉 HTTP 层,进程内调 LangGraph)。 - 派活广播:保留现有把「总控给 X 派了活 / X 完成」追加进各 thread 历史的广播
(
multi_agent.py:487-502),让席位读到全局上下文。 - MAX_CYCLES 兜底:图里维护
cycle,≥8 强制收敛(对齐roundtable-constants.ts:13)。 - chain 模式:
orchestration_mode==='chain'时按chain.seats固定顺序派活,跳过总控自由决策的 部分(对齐runChainOrchestration1228-1439)。 - 进度落库:每个节点结束
update_progress(jobId, {phase, cycle, dispatchChain, activeAgentId, consensus, dialogues_delta}),前端 SSE 才有实时数据。 - dialogues 累积:把每轮气泡写进图状态
dialogues,既供「查看」实时渲染,也供作业结束回写草稿 step2。
验收
- 用脚本(仿
scripts/probe_ai_writing_assistant.py)直接对图发起一次 run,不开前端,能从 intent + selectedAgents 一路跑到写出方案总览.html,作业status终态done。 - 命中 clarification 时图
interrupt,status=awaiting_input,pending_clarification有值。 - chain 模式按链条顺序派活,recommend 模式由总控自由派活,两条路径都能收敛。
Phase 3 —— 后端:作业控制 + 进度 SSE 接口
目标:暴露 start/get/stream/resume/cancel,后台 run 走 LangGraph Server(任意 worker 可 join)。
涉及文件(新增/改)
- 新增
app/gateway/routers/roundtable_jobs.py(仿ai_writing.py的 CRUD + 控制壳) - 在 gateway
app.py注册路由 - 新增
app/gateway/roundtable_jobs_cleanup.py(仿ai_writing_cleanup.py)+ 在启动处挂 cron
接口契约
| 方法 | 路径 | 说明 |
|---|---|---|
POST |
/api/roundtable-jobs |
入参:{draftId, intent, selectedAgents, threadIds?, coordinatorName?, model, orchestrationMode, chain?}。建作业记录(queued) + 启动图 run(LangGraph Server thread),返回 RoundtableJob |
GET |
/api/roundtable-jobs/{id} |
作业快照(轮询兜底 / 弹窗首帧) |
GET |
/api/roundtable-jobs/{id}/stream |
SSE 进度流:透传图 values/messages-tuple,或映射成 §2.4 事件下发。走真 TCP loopback(multi_agent.py:271-293),禁代理 |
POST |
/api/roundtable-jobs/{id}/resume |
入参 {answer},回填 clarification → 图 resume,status→running |
POST |
/api/roundtable-jobs/{id}/cancel |
取消图 run,status→cancelled |
GET |
/api/roundtable-jobs?draftId= |
按草稿查活跃作业(页面恢复用) |
POST |
/api/roundtable-jobs/admin/cleanup |
管理员手动清理(仿 ai_writing admin cleanup 307-367) |
多 worker 健壮性(务必遵守,照搬 ai_writing 教训)
- 作业状态只存 DB + checkpointer,不要用模块级内存 dict 存 per-session 状态。
- SSE 必须经真 TCP
127.0.0.1loopback,不能httpx.ASGITransport(app=...)进程内直调(会整段 buffer)。 - 图 run 跑在 LangGraph runtime(独立于处理 HTTP 的 worker),刷新/换 worker 都能重新 join stream。
- 数据卷:
.deer-flow/data/checkpoints.db必须挂出,否则容器重启丢状态。
验收
POST /start后立刻GET /{id}能拿到queued/running;关掉 SSE 再重连能继续收进度。- 杀掉发起 start 的 worker(或多 worker 轮询),
resume/stream仍命中同一作业。 - cleanup 干跑:活跃作业永不删,先删 CP 再删 DB。
Phase 4 —— 前端:作业 API 客户端 + 数据模型扩展
目标:前端有一层干净的作业 API 与类型,历史列表能拿到 status。
涉及文件(新增/改)
- 新增
src/roundtable-planning/api/roundtable-jobs.ts:export async function startJob(payload: StartJobPayload): Promise<RoundtableJob> export async function getJob(id: string): Promise<RoundtableJob> export function streamJob(id: string, on: (e: JobProgressEvent) => void): () => void // 返回 unsubscribe export async function resumeJob(id: string, answer: string): Promise<void> export async function cancelJob(id: string): Promise<void>streamJob复用项目现有 SSE/streamMultiAgent的封装方式(api/multi-agent.ts:403+),把 LangGraphvalues映射成 §2.4 的归一化事件。
- 改
src/roundtable-planning/api/drafts.ts:DraftMeta增status?: JobStatus; jobId?: string(drafts.ts:76-83+ 字段映射122-178加 snake_case 转换)。 - 新增
src/roundtable-planning/lib/job-types.ts:把 §2.1–2.5 的类型集中导出。
验收:pnpm typecheck 通过;getJob/streamJob 能拉到 Phase 3 的数据。
Phase 5 —— 前端:Step2「后台挂起」按钮
目标:Step2 顶栏/底栏加按钮,点击把当前任务交给后端后台跑,前端停止本地循环、转为观察者。
涉及文件(改)
src/roundtable-planning/components/Step2Panel.tsx(按钮放在现有「进入第三步」附近370-381)src/roundtable-planning/pages/RoundtablePlanningPage.tsx(接线 + 观察者模式)src/roundtable-planning/hooks/useStep2Orchestration.ts(暴露「停止本地循环并交后台」的方法)
步骤
- Step2Panel 加「⏼ 后台挂起」按钮,
onBackgroundSuspend回调。 - 页面
handleBackgroundSuspend:ensureDraft(2)拿到draftId(useDraftPersistence.tsensureDraft)。- 收集当前状态:
intentReady、selectedAgents、threadIds、coordinatorName、model、orchestrationMode、chain(全部来自step2/step1现有状态)。 startJob(...)→ 拿到jobId。- 停止前端本地编排:调用
step2.stopOrchestration()+ 置一个backgroundedJobId标记, 让currentStep===2的 useEffect 不再自启本地循环(在useStep2Orchestration.ts:1468-1541的启动条件里加&& !backgrounded)。 - toast「已转入后台,可离开页面,任务会自动跑到结果绘制完成」。
- 刷新历史列表(
drafts重新拉,进行中项即出现 spinner)。
- 观察者模式:若当前停留在 Step2 且该 draft 有 running 作业,订阅
streamJob,把进度事件渲染成 对话气泡/活动席位(复用现有 Step2 渲染),而不是本地驱动。
注意:避免「双跑」——后台已接管后,前端绝不能再并行驱动同一批 thread。用 backgroundedJobId
互斥;加载草稿时若发现有 running 作业,直接进观察者模式。
验收:点「后台挂起」→ 可立即切到别的任务/路由;回来仍在跑;不会出现前后端同时派活。
Phase 6 —— 前端:历史下拉框「进行中」加载动画
目标:历史下拉里 status==='running'(或 awaiting_input)的草稿条目显示加载动画;点击进行中
条目不直接 load,而是打开流程图弹窗。
涉及文件(改)
src/roundtable-planning/pages/RoundtablePlanningPage.tsx的HistoryDropdown(771-892)
步骤
- 列表项左侧名称前/后,按
d.status渲染状态标记:running→ 旋转的Loader2(animate-spin)+「进行中」。awaiting_input→ 呼吸点 +「待澄清」(琥珀色)。done/无 → 维持现状(显示Step {furthestStep})。error→ 红点 +「出错」。
- 点击行为分流:
running/awaiting_input→openDispatchChainModal(d.jobId)(Phase 7),不直接onLoad。- 其它 → 维持
onLoad(id)。
- 列表需要
status,确保 Phase 4 的DraftMeta.status已就位;下拉打开时refresh()拉最新。
验收:后台挂起后,历史下拉对应条目转圈;点击弹出流程图弹窗。
Phase 7 —— 前端:派活链路流程图弹窗(横向 / 大弹窗 / 动画)
目标:大弹窗,横向排列的智能体派活链路。节点 = 席位(图标 + 名称),不同颜色;正在发言的 节点和它的连接线带加载动画;含「查看」按钮。
涉及文件(新增)
src/roundtable-planning/components/DispatchChainModal.tsx
实现选型:链路是「总控 → 席位序列」的线性结构 + 需要对单个节点和单条连线做精确动画,
推荐手写 flex 横向布局 + CSS 动画连接线(轻、可控)。项目已装 @xyflow/react@12(带 animated
边)作为备选,但手写对本场景的「节点 loading + 连线流动」更直接。
结构(手写方案草图)
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="flex max-h-[88vh] flex-col gap-0 sm:max-w-5xl"> {/* 大弹窗 */}
<DialogHeader>… 任务标题 + 阶段标签(leader_thinking/dispatching/report_drawing…) …</DialogHeader>
{/* 横向链路:总控 ──▶ 席位1 ──▶ 席位2 ──▶ … ──▶ [共识] ──▶ [报告绘制] */}
<div className="custom-scrollbar overflow-x-auto py-8">
<div className="flex items-center gap-0 min-w-max px-6">
<CoordinatorNode active={phase==='leader_thinking'} />
{nodes.map((n,i)=>(
<Fragment key={n.agentId}>
<Connector active={n.state==='active'} /> {/* 活动连线:流动虚线动画 */}
<ChainNode node={n} /> {/* 活动节点:彩色+脉冲光环 */}
</Fragment>
))}
<Connector active={phase==='consensus'} />
<MilestoneNode label="共识" done={consensus===100} />
<Connector active={phase==='report_drawing'} />
<MilestoneNode label="报告绘制" active={phase==='report_drawing'} done={phase==='report_done'} />
</div>
</div>
<DialogFooter className="border-t pt-3 sm:justify-between">
<span className="text-xs text-muted-foreground">轮次 {cycle}/8 · {statusLabel}</span>
<Button onClick={onView}>查看</Button> {/* Phase 8 */}
</DialogFooter>
</DialogContent>
</Dialog>
节点颜色:avatarType = getAvatarTypeFor(agentId, order) → chainTagClasses(avatarType)
(复用 lib/business-chain.ts:56-72 + lib/roundtable-constants.ts),与业务链条卡片同一套色。
节点状态样式
pending:灰色描边、低透明度。active:席位色填充 + 外圈animate-ping/脉冲光环 + 图标旋转或呼吸。done:席位色实心 + 右上角 ✓。
连接线加载动画(active 连线):CSS 流动虚线(marching-ants)或渐变扫光:
/* roundtable-planning.css 追加 */
@keyframes chain-flow { to { background-position: 20px 0; } }
.chain-connector--active {
background-image: linear-gradient(90deg, var(--brand-500) 50%, transparent 50%);
background-size: 20px 100%;
animation: chain-flow 0.6s linear infinite;
}
数据来源:弹窗 mount 时 getJob(jobId) 取首帧,随后 streamJob(jobId, onEvent) 实时更新
nodes/activeAgentId/phase/consensus/cycle;done 事件后停止动画、全节点置 done。
弹窗关闭时 unsubscribe。
验收:弹窗够大、横向滚动;活动席位节点和它左侧连线在动;阶段推进时高亮顺移;done 后静止。
Phase 8 —— 前端:「查看」进入进行中任务并实时观察
目标:弹窗「查看」→ 进入该任务,停在 Step2 实时看后台进度(气泡/活动席位随 SSE 刷新), 跑完自动呈现 Step3 结果。
涉及文件(改)
RoundtablePlanningPage.tsx(接onView)useDraftPersistence.ts(handleLoadDraft增「带作业」分支)useStep2Orchestration.ts(观察者渲染)
步骤
onView:关弹窗 →handleLoadDraft(draftId)恢复该草稿到 Step2(useDraftPersistence.ts:308-368), 但跳过本地编排自启(识别到有 running 作业 → 进观察者模式,对齐 Phase 5 的互斥标记)。- 订阅
streamJob(jobId),把dialogue事件 append 进step2RoundtableDialogues、active事件 驱动activeSpeakerId高亮、consensus更新百分比。 - 命中
awaiting_input:在 Step2 弹出澄清输入(复用现有pendingClarificationUI),用户回答 →resumeJob(jobId, answer)(而非本地submitClarification)。 done事件:用返回的DraftStep3Snapshot走hydrateStep3+ 允许切到 Step3 查看报告 (useStep3Report.hydrateSaved708-767);作业列表该项变「已完成」。
验收:从历史「查看」进入能实时看到后台在跑;澄清能在前端回答并续跑;跑完能看到最终报告。
Phase 9 —— 边界、健壮性与收尾
- 双跑互斥:任一 draft 同一时刻只允许一个执行者(前端本地 or 后台作业)。加载草稿/进入 Step2
前先
GET /api/roundtable-jobs?draftId=,有 running 就进观察者模式。 - 后台命中澄清:
awaiting_input时历史下拉显示「待澄清」徽标;流程图弹窗显示「等待澄清」; 只有「查看」进去才能回答(或在弹窗里直接给一个回答框,调用resumeJob)。 - 取消:流程图弹窗/Step2 提供「停止后台」→
cancelJob,status→cancelled,前端可恢复本地编辑。 - 错误恢复:
error事件展示 detail;允许「重试」= 重新startJob(沿用已建 threadIds)。 - MAX_CYCLES / 预算:后台图内兜底(对齐前端
MAX_CYCLES=8),到顶强制收敛并照常进 Step3。 - 多 worker:严格遵守 Phase 3 的「状态进 DB+checkpointer、SSE 走 loopback」;部署若多 worker 需 sticky 或全靠 DB 态(推荐后者)。
- 清理:
roundtable_jobscleanup cron(活跃保护、先删 CP 再删 DB),与 draft 生命周期对齐。 - 历史列表性能:draft 列表 join 作业 status 时注意 N+1;用一次 LEFT JOIN 取最新作业。
端到端验证清单(提测前逐条过)
- Step2 点「后台挂起」→ 立刻可离开页面 / 切到别的任务,后台继续跑。
- 历史下拉里该任务显示加载动画(转圈)。
- 点击进行中任务 → 大弹窗、横向派活链路、活动节点+连线在动、节点不同颜色。
- 阶段推进(leader→派活→共识→报告绘制)时弹窗高亮顺移。
- 弹窗「查看」→ 进入任务实时观察气泡/活动席位。
- 后台命中澄清 → 列表「待澄清」+ 弹窗提示 + 可回答续跑(
resumeJob)。 - 一路自动跑到 Step3「绘制完成」,最终报告可在 Step3 查看,作业
done。 - 浏览器刷新 / 关页重开后,任务仍在后台跑、状态可恢复(真后台验证)。
- 取消后台 →
cancelled,无残留双跑。 - 多 worker(或杀 worker)下 stream/resume 不丢会话。
- cleanup 干跑不误删活跃作业。
工作量与排期建议
| 阶段 | 内容 | 粗估 | 依赖 |
|---|---|---|---|
| P0 | 数据契约定型(本文 §2) | 0.5d | — |
| P1 | 后端作业表 + repo + 迁移 | 1d | P0 |
| P2 | 后端编排图(移植循环) | 3–5d(核心难点) | P0,P1 |
| P3 | 后端控制 + SSE + cleanup | 1.5d | P1,P2 |
| P4 | 前端作业 API + 类型 + draft status | 0.5d | P3 |
| P5 | Step2 后台挂起按钮 + 互斥 | 1d | P4 |
| P6 | 历史下拉 loading 动画 | 0.5d | P4 |
| P7 | 派活链路流程图弹窗 | 1.5d | P4 |
| P8 | 「查看」实时观察 + 澄清续跑 | 1.5d | P5,P7 |
| P9 | 边界/健壮性/清理 | 1d | 全部 |
建议先打通 P1→P2→P3 的后端最小闭环(脚本验证图能后台跑到出报告),再做前端 P4–P8。 P2 是整条链路成败的关键,务必先用脱离前端的探针脚本(仿
scripts/probe_ai_writing_assistant.py)跑通。
附:关键参考代码索引
- 前端编排循环(待移植到后端):
useStep2Orchestration.ts:992-1204,1228-1439,1468-1541 - 共识判定 / 进 Step3:
useStep2Orchestration.ts:1091-1099、Step2Panel.tsx:370-381、RoundtablePlanningPage.tsx:586-595 - Step3 绘制完成标志:
useStep3Report.ts:51,165,560-596,636-652 - 草稿模型 / API / 自动保存 / 加载恢复:
api/drafts.ts:25-110,184-225、useDraftPersistence.ts:248-316,308-368 - 历史下拉 UI:
RoundtablePlanningPage.tsx:771-892 - 后端单轮执行 / 派活 / 广播:
multi_agent.py:647-823,1076-1142,525-552,487-502、loopback271-293 - AI 写作模板:
agents/ai_writing/graph.py、ai_writing_sessions/model.py:14-39、ai_writing.py、ai_writing_cleanup.py:307-367、AIWritingPage.tsx文件头注释 - 颜色体系复用:
lib/business-chain.ts:56-72、lib/roundtable-constants.ts(getAvatarTypeFor) - 流程图备选库:
@xyflow/react@12(已安装)