401 lines
31 KiB
Markdown
401 lines
31 KiB
Markdown
# 业务链条模式 · 需求与分步实现文档
|
||
|
||
> 面向「智能体推荐弹窗」的扩展:在现有**智能推荐**之外,新增**业务链条选择**与**业务链条配置**两块能力;选定业务链条后,Step 2 圆桌研讨**按链条内的固定顺序**逐个发言(而非现在的总控派活)。
|
||
>
|
||
> 配套阅读:
|
||
> - 前端总览:`multi-agent-frontend-dev.md`(推荐弹窗 §6.2、Step 2 编排 §5.4、草稿持久化 §5.7)
|
||
> - 后端总览:`multi-agent-backend-dev.md`(Step 2 §6、草稿/历史持久化 §7A)
|
||
>
|
||
> 本文档约定:**「席位 / 智能体」**指一个 agent;**「业务链条 / chain」**指一组有序排列的 agent 配置,是可命名、可复用、可持久化的实体。
|
||
|
||
---
|
||
|
||
## 0. 需求原文拆解
|
||
|
||
用户原始诉求逐条拆成可实现条目:
|
||
|
||
| # | 原始诉求 | 拆解 |
|
||
|---|----------|------|
|
||
| 1 | 推荐弹窗要有「模式选择」 | 弹窗顶部加模式切换(智能推荐 / 业务链条),两种模式互斥,确认后都产出**有序**的 `SelectedAgent[]` |
|
||
| 2 | 「智能推荐」就是现在这个 | 现有 `RecommendAgentsDialog` 整体收进「智能推荐」模式,行为不变 |
|
||
| 3 | 「业务链条选择」是卡片列表 | 卡片 = 标题 + 一排智能体名 tag;tag 不同颜色;tag 之间有**向左箭头**连接,表达链条次序 |
|
||
| 4 | 业务链条需要一个**配置页面** | 左侧智能体列表(复用 `GET /api/agents`),右侧有序长条卡片(名称 + 描述);支持右侧内部拖拽排序、左→右拖入指定位置 |
|
||
| 5 | 配置页用弹窗还是新页面? | **本文档决策:独立路由页面**(理由见 §2.1) |
|
||
| 6 | 选定链条后,Step 2 用链条内智能体研讨 | 复用既有 Step 2 init/stream;**总控(leader)按链条顺序逐个派活**(§6) |
|
||
| 7 | 规划成分步可实现的需求文档 | 见 §7 分阶段实现 |
|
||
|
||
---
|
||
|
||
## 1. 目标与非目标
|
||
|
||
### 1.1 目标
|
||
- 推荐弹窗成为「**席位来源选择器**」:无论智能推荐还是业务链条,最终都向 `onConfirm` 交付一个**有序的 `{ agent_id, name }[]`** + 一个 `orchestrationMode`。
|
||
- 业务链条是**用户可创建、命名、保存、复用、删除**的有序智能体编排模板。
|
||
- Step 2 支持两种编排:
|
||
- `recommend` 模式 → 现有总控**自由派活**循环(不动)。
|
||
- `chain` 模式 → 总控仍是派活方,但**被约束按链条顺序逐个派活**(新增)。
|
||
|
||
### 1.2 非目标(本期不做,留扩展点)
|
||
- 链条内每个席位的 per-seat prompt/temperature 真实覆盖(仍仅前端记录,沿用现状)。
|
||
- 链条的分支 / 条件流转(只做线性顺序链)。
|
||
- 链条跨用户共享 / 市场(先做「本人私有链条」)。
|
||
- 链条版本管理 / 协作编辑。
|
||
|
||
---
|
||
|
||
## 2. 关键设计决策
|
||
|
||
### 2.1 决策 A:配置页面用「独立路由页面」而非弹窗 ✅
|
||
|
||
**结论:业务链条的「编辑/管理」用独立路由页面 `/page/workspace/roundtable/chains`;业务链条的「选择」仍内嵌在推荐弹窗里(卡片列表)。**
|
||
|
||
理由:
|
||
|
||
| 维度 | 弹窗 | 独立页面 ✅ |
|
||
|------|------|------------|
|
||
| 双栏 + 拖拽 + 长条卡片 | 弹窗宽度(`sm:max-w-3xl`)+ `max-h-[85vh]` 拖拽空间局促,左右栏挤 | 整屏宽度,拖拽手感好 |
|
||
| 复用入口 | 只能从推荐弹窗进 | 可从侧栏、推荐弹窗「管理链条」按钮、深链接进入 |
|
||
| 可深链接/刷新 | 不可 | `/roundtable/chains` 可直达、可分享 |
|
||
| 推荐弹窗内嵌编辑器 | 弹窗套弹窗 / 模式套编辑,状态复杂 | 关注点分离:弹窗只「选」,页面只「编」 |
|
||
|
||
**分工**:
|
||
- **推荐弹窗**:模式选择 + 业务链条**选择**(只读卡片列表,可点「去配置」跳到页面)。
|
||
- **配置页面**:业务链条的**增删改**(双栏拖拽编辑器 + 链条列表管理)。
|
||
|
||
> 备选:若产品坚持不加路由,可改为**全屏 Dialog(`max-w-[90vw] h-[90vh]`)**承载编辑器,从推荐弹窗内打开。结构与页面版一致,仅外壳不同。本文档按「独立页面」实现,§7 标注了切换成本。
|
||
|
||
### 2.2 决策 B:链条持久化 —— 后端 MySQL(确定) ✅
|
||
|
||
**结论:业务链条存后端 MySQL,仿 `roundtable_drafts`(§7A)新建 `roundtable_chains` 表 + `/api/roundtable-chains` 路由,按登录用户隔离、跨设备保留。** 不走 localStorage。
|
||
|
||
理由:链条是可复用、需跨设备/会话保留的模板,与草稿/推荐历史性质一致;草稿当年正是因 localStorage 换设备即丢才迁后端(§7A),链条不重复踩坑。
|
||
|
||
落地:
|
||
- 表/路由设计见 §5.2,是「复制 §7A 草稿持久化、把字段换成 chain」的机械工作。
|
||
- 前端 `api/chains.ts` 直接打后端接口(snake_case↔camelCase 在这层收口,仿 `api/drafts.ts`),**不提供 localStorage 实现**。
|
||
- 后端表必须先于前端 UI 落地(§7 阶段 0 即包含后端表/路由)。
|
||
|
||
### 2.3 决策 C:拖拽库选型 —— `@dnd-kit`
|
||
|
||
仓库**当前无任何拖拽库**(`package.json` 无 `@dnd-kit` / `react-dnd` / `sortablejs`;`@xyflow/react` 是节点图,不适合列表排序)。
|
||
|
||
| 方案 | 取舍 |
|
||
|------|------|
|
||
| **`@dnd-kit/core` + `@dnd-kit/sortable`**(推荐) | React 19 友好、可访问性好、跨容器拖入 + 列表排序 + 插入指示器开箱即用;新增 ~30KB gzip 依赖 |
|
||
| 原生 HTML5 DnD(零依赖兜底) | 不加依赖,但跨容器插入位置指示、键盘可达性都要手写,易出 bug |
|
||
|
||
**建议**:新增 `@dnd-kit/core`、`@dnd-kit/sortable`、`@dnd-kit/utilities`。若产品方禁止新增依赖,退回原生 HTML5 DnD(§7 阶段 2 标注差异)。
|
||
|
||
### 2.4 决策 D:Step 2「总控按链条顺序派活」,后端零改动 ✅
|
||
|
||
**形态:总控(leader)仍是唯一派活方,但每一轮被前端约束「只能派给链条里的下一个席位」,逐个走完整条链,最后由总控收口。** 区别于 recommend 模式的「总控自由决定派给谁」。
|
||
|
||
为什么后端无需改动:
|
||
- Step 2 后端 `streamMultiAgent({ agentType: "leader" })` 每轮返回 `dispatched = [[agent, task], ...]`;`agentType: "special"` 单独驱动某席位跑一轮。这两个原语已足够。
|
||
- 每个 special 跑完,后端 `_broadcast` 会把「子智能体 X 完成…交付内容为:…」append 进**其它所有席位 + 总控 thread** 的历史(后端 §6.4),所以链路上下文天然累积。
|
||
- chain 模式只是**改变前端怎么提示 leader、以及怎么校验它的派活**——把每轮 leader 的提示词约束成「请向链条第 i 位【X】派活」,并对返回的 `dispatched` 做「必须命中链条下一席位」的校验/纠偏。**后端路由、SOUL、表结构都不动。**
|
||
|
||
→ chain 模式 = 在 `useStep2Orchestration` 里新增 `runChainOrchestration`:按链条游标逐位调 leader(约束派给该位)→ 拿到 task → 调 special → 下一位 → 链尾后调一次 leader 收口。详见 §6。
|
||
|
||
> 与「前端绕过 leader 直接顺序 special」的区别:本方案**保留总控在每一步的协调/串接职责**(leader 结合前序进展为下一席位编写针对性任务、并在派活说明里承上启下),更符合「总控按链条顺序派活」的语义,圆桌可视化里总控气泡也照常出现。
|
||
|
||
---
|
||
|
||
## 3. 数据模型与类型
|
||
|
||
新增前端类型,建议放 `frontend-web/src/roundtable-planning/lib/business-chain.ts`:
|
||
|
||
```ts
|
||
/** 业务链条里的一个有序席位。 */
|
||
export interface ChainSeat {
|
||
agent_id: string;
|
||
/** 落库时的快照名/描述,避免 agent 改名后链条显示错乱;展示优先用实时 agent 数据,回退到快照。 */
|
||
name: string;
|
||
description?: string;
|
||
}
|
||
|
||
/** 一条可复用的业务链条。 */
|
||
export interface BusinessChain {
|
||
id: string;
|
||
title: string;
|
||
description?: string;
|
||
/** 有序席位列表;seats[0] 是第一个发言者。 */
|
||
seats: ChainSeat[];
|
||
createdAt?: string;
|
||
updatedAt?: string;
|
||
}
|
||
|
||
/** 推荐弹窗确认后回传给主页面的统一结果。 */
|
||
export interface SeatSelectionResult {
|
||
/** 有序席位(与现有 handleConfirmAgents 入参兼容)。 */
|
||
agents: Array<{ agent_id: string; name: string }>;
|
||
/** 决定 Step 2 走哪种编排。 */
|
||
mode: "recommend" | "chain";
|
||
/** chain 模式下带上来源链条(用于草稿快照 / Step 2 展示链名)。 */
|
||
chain?: Pick<BusinessChain, "id" | "title">;
|
||
}
|
||
```
|
||
|
||
**约束**:链条席位数沿用圆桌的 `2 ≤ N ≤ 8`(`MIN_SELECTED`/`MAX_SELECTED`),配置页保存时校验。
|
||
|
||
**tag 配色**:复用 `roundtable-constants.ts::getAvatarTypeFor(agentId, index)`,得到 `blue|purple|emerald|amber|pink|human`,再映射到已有的 avatar 配色 token(与圆桌可视化、Step 2 气泡同一套色板,视觉统一)。
|
||
|
||
---
|
||
|
||
## 4. 现有代码改动点速查
|
||
|
||
| 位置 | 改动 | 说明 |
|
||
|------|------|------|
|
||
| `components/RecommendAgentsDialog.tsx` | 包一层「模式选择」外壳,现有内容收进「智能推荐」Tab | `onConfirm` 签名从 `agents[]` 升级为 `SeatSelectionResult`(或加可选第二参 `mode`,见 §7 阶段 3 的兼容策略) |
|
||
| `pages/RoundtablePlanningPage.tsx` | `handleConfirmAgents` 接收 `mode`,存到新状态 `orchestrationMode` | 透传给 `useStep2Orchestration` |
|
||
| `hooks/useStep2Orchestration.ts` | 新增 `runChainOrchestration` + `orchestrationMode` 分支 | init effect 里按 mode 选择 `runOrchestration` 或 `runChainOrchestration` |
|
||
| `lib/roundtable-constants.ts` | 复用 `getAvatarTypeFor` / `SelectedAgent` | 不改,仅引用 |
|
||
| `hooks/useDraftPersistence.ts` + `api/drafts.ts` | Step2 快照新增 `orchestrationMode` + `chain` 字段 | 草稿能记住「这次是链条模式」 |
|
||
| `pages/WorkspaceRoutes.tsx` | 注册 `roundtable/chains` 路由 | 仿 `roundtable/planning` |
|
||
| `components/page-sidebar.tsx`(如有侧栏入口) | 加「业务链条」入口(可选) | 也可只从推荐弹窗进 |
|
||
| `api/chains.ts`(新增) | 链条 CRUD 客户端 | 直接打后端 `/api/roundtable-chains` |
|
||
| 后端 `roundtable_chains` | 表 + 路由(MySQL) | 仿 `roundtable_drafts`,**必做** |
|
||
|
||
---
|
||
|
||
## 5. 链条持久化 API 设计
|
||
|
||
### 5.1 前端客户端 `api/chains.ts`(直接打后端)
|
||
|
||
```ts
|
||
listChains(): Promise<BusinessChain[]> // 当前用户全部链条
|
||
getChain(id: string): Promise<BusinessChain>
|
||
createChain(input: Omit<BusinessChain,"id"|"createdAt"|"updatedAt">): Promise<BusinessChain>
|
||
updateChain(id: string, patch: Partial<BusinessChain>): Promise<BusinessChain>
|
||
deleteChain(id: string): Promise<void>
|
||
```
|
||
|
||
- 实现用 `apiFetch` 打 `/api/roundtable-chains`(§5.2),snake_case↔camelCase 在这层收口(仿 `api/drafts.ts`)。
|
||
- 不提供 localStorage 实现;后端表是前端 UI 的硬依赖。
|
||
|
||
### 5.2 后端表 + 路由(仿 §7A `roundtable_drafts`)
|
||
|
||
- 表 `roundtable_chains`:`id`(PK) / `user_id`(idx) / `title`(512) / `description`(text) / `seats`(`PortableLongText`,存 `ChainSeat[]` JSON) / `created_at` / `updated_at`(`BeijingDateTime`)。
|
||
- 路由 `app/gateway/routers/roundtable_chains.py`,前缀 `/api/roundtable-chains`:`GET ``(列表)/ `POST ``(建)/ `GET /{id}` / `PUT /{id}`(partial,`exclude_unset`)/ `DELETE /{id}`。
|
||
- 鉴权/属主:`_current_user_id(request)`,跨用户访问一律 404,与 `roundtable_drafts` 完全一致。
|
||
- 启动 `Base.metadata.create_all` 自动建表(MySQL 生产 / sqlite 本地走同一套 SQLAlchemy 代码,`PortableLongText` 各自降级),登记进 `persistence/models/__init__.py`,store 接到 `app.state` 并 `include_router`。
|
||
|
||
> 整体是「复制 §7A 的草稿持久化、把字段换成 chain」的机械工作,风险低。
|
||
|
||
---
|
||
|
||
## 6. Step 2 链条模式编排设计(`runChainOrchestration`)
|
||
|
||
### 6.1 与现有 `runOrchestration` 的差异
|
||
|
||
| 维度 | `runOrchestration`(recommend) | `runChainOrchestration`(chain,新增) |
|
||
|------|--------------------------------|----------------------------------------|
|
||
| 谁派活 | 总控 leader | 总控 leader(**不变**) |
|
||
| 派给谁由谁定 | leader 自由决定 | 前端按链条游标**约束 leader 只能派给下一席位** |
|
||
| 发言顺序 | leader 每轮自由派 | **链条数组的固定顺序** |
|
||
| 循环结构 | `while` 直到 leader 返回 `[]` 共识 / MAX_CYCLES | 按链条游标走一趟(每位:leader 派活 → special 交付) |
|
||
| 收口 | leader 返回 `[]` 即共识 | 链尾后再调一次 leader 做总结收口,产出 `lastLeaderContent` + `hasConsensus` |
|
||
|
||
> **核心**:leader 始终在场,只是**每轮的派活目标被前端钉死成链条的下一席位**——前端在 leader 的 `newMessage` 里写明「本轮请通过 `agent_orchestration` 仅向【X】派活」,并对 `leaderRes.dispatched` 做命中校验/纠偏。
|
||
|
||
### 6.2 伪代码
|
||
|
||
```
|
||
runChainOrchestration(seedMessage, ids, coord, chainSeats):
|
||
isOrchestrating = true
|
||
cancel = false
|
||
N = chainSeats.length
|
||
|
||
for (let i = 0; i < N; i++): // 链条游标,逐位推进
|
||
if cancel: break
|
||
seat = chainSeats[i]
|
||
seatMeta = agentNameToRole[seat.agent_id]
|
||
|
||
// ── (a) 总控派活给「链条下一席位」──────────────────────────
|
||
highlightSpeaker(null) // leader 阶段不高亮任何 sub
|
||
leaderBubble = appendStreamingDialogue("总控协调", confidence:"链条派活中")
|
||
leaderTask = buildChainLeaderTask({
|
||
seedMessage, // 任务目标/约束(首轮)
|
||
position: i, total: N,
|
||
nextSeat: seat, // 「本轮请仅向【X(seat.agent_id)】派活」
|
||
isFirst: i === 0,
|
||
})
|
||
leaderRes = await streamMultiAgent(leader, ids, coord, leaderTask, model=globalModel)
|
||
|
||
// 约束/纠偏:取 dispatched 中命中 seat.agent_id 的那条 task;
|
||
// 若 leader 没派或派错(派给非 seat、或派给多个),前端兜底:
|
||
// - 命中就用它给出的 task 文本(保留总控的承上启下措辞);
|
||
// - 没命中则用 leader 的正文 content 作为兜底 task,或回退到模板 task。
|
||
task = pickChainTask(leaderRes, seat)
|
||
// leader 若返回 clarification → 暂停链条,走与现状一致的澄清条(§6.3)
|
||
finalizeDialogue(leaderBubble, leaderRes.content, "派活说明")
|
||
|
||
// ── (b) 被派席位交付(special,顺序、不并行)──────────────────
|
||
markSpeakerStreaming(seatMeta.speakerId)
|
||
seatBubble = appendStreamingDialogue(seatMeta.displayName, confidence:"调度中")
|
||
perSeatModel = agentConfigs[seatMeta.speakerId]?.model || globalModel
|
||
specRes = await streamMultiAgent(special, ids, seat.agent_id, task, model=perSeatModel)
|
||
finalizeDialogue(seatBubble, specRes.content, "子智能体交付")
|
||
markSpeakerIdle(seatMeta.speakerId)
|
||
// 后端 _broadcast 已把本席位交付注入其余 thread 历史 → 下一席位天然可见
|
||
|
||
if cancel: return
|
||
|
||
// ── (c) 总控收口:综合全链交付,产出最终结论 ──────────────────
|
||
leaderBubble = appendStreamingDialogue("总控协调", confidence:"汇总链条结论")
|
||
leaderRes = await streamMultiAgent(leader, ids, coord,
|
||
"各链条席位已按顺序完成发言,请综合所有交付,输出最终方案,不要再派活。")
|
||
finalizeDialogue(leaderBubble, leaderRes.content, "全案盖印")
|
||
setLastLeaderContent(leaderRes.content)
|
||
setHasConsensus(true); setConsensusPercentage(100)
|
||
isOrchestrating = false
|
||
```
|
||
|
||
要点:
|
||
- **派活方仍是总控**,符合「总控按链条顺序派活」的语义;圆桌可视化里每位席位前都先出现一个总控派活气泡,再出现该席位交付气泡。
|
||
- **约束手段是 prompt + 前端校验,不动后端**:`buildChainLeaderTask` 把「本轮只能派给【X】」写进 leader 的 `newMessage`;`pickChainTask` 对 `leaderRes.dispatched` 做命中校验,派错/未派时兜底,保证链条游标严格推进。
|
||
- **复用现有三段式气泡 / 高亮 / abort 基建**(`appendStreamingDialogue` / `finalizeDialogue` / `markSpeakerStreaming` / `activeAbortersRef` / `orchestrationCancelRef`),只是驱动顺序不同。
|
||
- **special 仍是顺序**(不并行),与现状不变量一致(文件顶部注释 §1)。
|
||
- **收口 leader 调用默认开**(产出 Step 3 结论);若链条末位本身是「总结陈述」类,可关掉收口,直接拿末位交付当 `lastLeaderContent`。配置位 `chainNeedsLeaderSummary`(默认 true)。
|
||
|
||
### 6.3 澄清 / 挂起 / 恢复 / 干预
|
||
- **澄清**:链条中途某轮 leader 返回 `clarification` → 与现状一致,暂停链条、底部弹澄清条;用户回答后从**当前游标**继续(需记录 `chainCursor`)。
|
||
- **挂起/恢复**:`isRoundTableRunning` toggle 复用现有机制;恢复时从 `chainCursor` 续跑。MVP 若不做断点续跑,可先只支持「整体停止 / 从头重跑」,UI 文案说明(扩展点,§7 阶段 4 标注)。
|
||
- **干预**:可在两席位之间插入一条用户指令,由下一轮 leader 派活前消化。MVP 同上可后置。
|
||
|
||
### 6.4 多轮链条(扩展点,非 MVP)
|
||
单趟遍历是 MVP。若要「链条跑完后总控判断是否再来一轮」,可在收口 leader 处复用 `dispatched===[]?` 判定:收口 leader 若仍派活则回到链条头再跑一轮,直到共识或 MAX_CYCLES。MVP 不做,避免复杂度。
|
||
|
||
### 6.5 init 复用
|
||
chain 模式的 Step 2 init **完全复用** `initMultiAgent`:`agentNames = chainSeats.map(s => s.agent_id)`(**按链条顺序**传入,使 `buildRoleTemplates` 的 seat id 顺序 = 链条顺序,且后端注入 coordinator 的「可调度席位清单」也按此序)。init effect 里按 `orchestrationMode` 决定 init 完成后调 `runOrchestration` 还是 `runChainOrchestration`。
|
||
|
||
---
|
||
|
||
## 7. 分阶段实现计划
|
||
|
||
> 每阶段都可独立交付、独立验收。建议顺序:**数据层 → 配置页 → 弹窗模式 → 链条选择 → Step2 编排 → 持久化兼容与收尾**。
|
||
|
||
### 阶段 0 · 后端链条表 + 脚手架(1.5 天) ✅ 已完成
|
||
**目标**:打地基(含后端持久化),不含前端 UI。
|
||
- [x] **后端**:仿 §7A `roundtable_drafts` 新建 `roundtable_chains` 表 + `/api/roundtable-chains` 路由(§5.2):
|
||
- [x] `persistence/roundtable_chains/`(model + sql repo + `make_roundtable_chain_store`),登记进 `persistence/models/__init__.py`。
|
||
- [x] `app/gateway/routers/roundtable_chains.py`(GET 列表 / POST / GET{id} / PUT{id} / DELETE),鉴权属主同 `roundtable_drafts`;席位 2–8 + 去重校验。
|
||
- [x] `deps.py` 接 `app.state.roundtable_chain_store`,`app.py` `include_router`。
|
||
- [x] 后端测试 `tests/test_roundtable_chains.py`:CRUD + 部分更新 + 跨用户隔离(5 用例全过)。
|
||
- [x] **前端**:新增 `lib/business-chain.ts`(`ChainSeat` / `BusinessChain` / `SeatSelectionResult`,§3)+ `api/chains.ts`(直接打后端,§5.1)。
|
||
- [x] (决策 C)安装 `@dnd-kit/core` `@dnd-kit/sortable` `@dnd-kit/utilities`。
|
||
- **验收**:✅ 后端 5 用例通过;✅ 表注册进 `Base.metadata`、路由 `/api/roundtable-chains` 加载正常;✅ ruff 0 错;✅ 前端 typecheck 新增文件 0 错。
|
||
|
||
### 阶段 1 · 业务链条配置页面(2–3 天) ✅ 已完成
|
||
**目标**:能创建/编辑/保存/删除链条。
|
||
- [x] 路由:`WorkspaceRoutes.tsx` 注册 `roundtable/chains`(套 `WorkspaceLayout`;本页**不需要** `ChatRuntime`,无沙箱依赖)。
|
||
- [x] 页面 `roundtable-planning/pages/BusinessChainEditorPage.tsx`:
|
||
- [x] 顶部:链条列表下拉(`<select>` 列出已存链条 + 新建项)/ 新建按钮 / 标题+描述输入 / 保存 / 删除(`window.confirm`)。
|
||
- [x] **左栏**:候选智能体(`listAgents()` + `filterRecommendCandidates`),竖排卡片,可拖、可点「+」快捷加入;已加入的置灰「已加入」。
|
||
- [x] **右栏**:有序长条卡片(序号 tag + 名称 + 描述 + 移除),`SortableContext` 内拖拽排序;空态有占位提示。
|
||
- [x] 拖拽用 `@dnd-kit`:左栏 `useDraggable`,右栏 `useDroppable` + `SortableContext`/`useSortable`,`onDragEnd` 计算插入 index(pool→右栏按落点插入;seat→seat `arrayMove`);`DragOverlay` 跟手卡片。
|
||
- [x] 校验:`2 ≤ seats ≤ 8`(前端禁用保存 + toast,后端 `_validate_seats` 兜底),重复 agent 去重提示。
|
||
- [x] 保存走 `api/chains.ts`(create 用 `nanoid()` 客户端 id,update 走同一 id)。
|
||
- **验收**:✅ typecheck 0 新增错误(76 个预存量错误均在无关模块)。**人工验收待跑**:新建「情报→方案→风险→总结」链保存 → 刷新后下拉重载 → 右栏拖拽排序 → 左栏拖入指定位 → 2–8 边界禁用保存。
|
||
- **切换成本备注**:若改全屏 Dialog 方案,编辑器组件原样复用,仅去掉路由、外面套 `Dialog`。
|
||
- **入口说明**:圆桌本身未在侧栏(`sidebar-menu.ts` 入口被注释),本页暂经直链 `/page/workspace/roundtable/chains` 或顶栏「返回圆桌」往返;阶段 3 推荐弹窗「去配置」按钮会导航到此。
|
||
- **视觉(后续重画)**:本页**不**套圆桌 `roundtable-planning` scope,直接遵循《浅红色/深蓝色 UI 规范》——用 app 语义 token(光 `--primary` 红 hue27 / 暗 蓝 hue259,已内置自适应)+ 规范小圆角(输入/按钮/tag/卡片内项 2px = `R_CTRL`,分栏/面板 4px = `R_CARD`)+ 克制阴影(`PANEL_SHADOW`)。筛选框复用通用 `TagSearchBar`,配色随 app 中性 token,不再被红 scope 染色。
|
||
|
||
### 阶段 2 · 推荐弹窗「模式选择」外壳(1 天) ✅ 已完成
|
||
**目标**:弹窗顶部模式切换,智能推荐行为不变。
|
||
- [x] `RecommendAgentsDialog` 顶部加**分段按钮**模式切换:`智能推荐 | 业务链条`(active 态品牌色 + 卡底;DialogDescription 随模式切换文案)。
|
||
- [x] 现有全部内容原样收进「智能推荐」面板(`{mode === "recommend" && (<>…</>)}` 包裹,**零行为变更**)。
|
||
- [x] 「业务链条」面板放占位:居中提示 + 「去配置业务链条」按钮(`handleGoConfig` → 关弹窗 + 跳 `roundtable/chains`)。
|
||
- [x] `onConfirm` 统一升级为回传 `SeatSelectionResult`(含 `mode`):智能推荐确认回传 `{ agents, mode: "recommend" }`;主页面 `handleConfirmAgents` 同步改签名取 `result.agents`(`result.mode` 预留给阶段 4)。
|
||
- **验收**:✅ typecheck 0 新增错误(总数仍 76);智能推荐分支 JSX/逻辑原样保留(流式/勾选/进入研讨未改);模式切换只切显示、`state`/`selectedIds` 不卸载 → 候选池不丢。**人工验收待跑**:切到业务链条→再切回,推荐结果与勾选仍在。
|
||
|
||
### 阶段 3 · 业务链条选择卡片列表(1.5 天) ✅ 已完成
|
||
**目标**:弹窗「业务链条」面板里选链条。新增 `components/BusinessChainPicker.tsx`,替换阶段 2 的占位。
|
||
- [x] 进入该 Tab 时 `listChains()` 拉本人链条 + `listAgents()` 取存活 id/名称,渲染**卡片列表**:
|
||
- [x] 卡片标题(链条 title)+ 「N 席」徽章 + 选中 ✓;描述行。
|
||
- [x] 标题下一排**智能体名 tag**:按链条顺序**从左到右**、不同颜色(`getAvatarTypeFor`→`chainTagClasses`),tag 之间插 `→`(`ArrowRightIcon`)箭头连接(`seats[0]` 最左、先发言)。
|
||
- [x] 底部「配置链条」按钮(跳 `roundtable/chains`)。
|
||
- [x] 空态:无链条时引导「去配置页面创建第一条业务链条」+ 按钮。
|
||
- [x] 选中一条 → 高亮(`ring-primary`);底部「进入研讨」`onConfirm({ agents: 存活席位有序, mode:"chain", chain:{id,title} })`。
|
||
- [x] 链条里有 agent 已不存在(被删)→ 该 tag 置灰删除线 + `title` 提示 + 卡片下方 amber 警告;confirm 按**存活席位**过滤;存活 < `CHAIN_MIN_SEATS` 则禁用「进入研讨」并提示。agent 列表加载失败时 `existingIds=null` → 不误判删除。
|
||
- **验收**:✅ typecheck 0 新增错误(总数仍 76)。卡片渲染标题 + 彩色 tag + ← 箭头;选链 → `onConfirm(mode:"chain")` → `handleConfirmAgents` 按链条顺序 `setSelectedAgents` → Step 2 左侧「参与角色」顺序 = 链条顺序(**按顺序派活在阶段 4**)。**人工 UI 验收待跑**。
|
||
- **箭头方向**(已确认):tag 左→右排列、`→`(`ArrowRightIcon`)连接,`seats[0]` 最左、先发言,箭头顺读即链条研讨顺序。
|
||
|
||
### 阶段 4 · Step 2 链条顺序编排(2–3 天) ✅ 已完成
|
||
**目标**:chain 模式按链顺序逐席发言。**后端零改动。**
|
||
- [x] 主页面新增 `orchestrationMode` 状态,`handleConfirmAgents` 从 `SeatSelectionResult.mode` 写入(chain 模式 toast 带链名);`handleStartNewTask` 复位为 `recommend`;透传给 `useStep2Orchestration`。chain 顺序 = `selectedAgents`(确认时已按链条顺序写入),无需单独 `chainSeats`。
|
||
- [x] `useStep2Orchestration` 新增 `runChainOrchestration`(§6.2)+ `orchestrationModeRef`/`selectedAgentsRef`;复用现有三段式气泡/高亮/abort 基建。
|
||
- [x] 新增 `runActiveOrchestration` 分发器:init / 恢复 / 干预 / 澄清回复 4 处调用点统一改走它,按 `orchestrationModeRef` 选 `runOrchestration`(recommend)或 `runChainOrchestration`(chain)。
|
||
- [x] `runChainOrchestration`:按 `selectedAgents` 游标,每位先 leader 约束派活(`buildChainLeaderTask` 钉死目标席位 + `pickChainTask` 命中校验/纠偏,派错/未派都兜底推进)→ 再对**该席位**跑 special;中途 leader 若 `clarification` 则暂停(MVP 回复后整链重跑)。
|
||
- [x] `initMultiAgent` 的 `agentNames` 即 `roundtableAgentIds`(由 `selectedAgents` 派生,已是链条顺序,§6.5)。
|
||
- [x] 收口 leader(`finishedAllSeats` 时触发),产出 `lastLeaderContent` + `hasConsensus=true` + `consensusPercentage=100` 解锁 Step 3。
|
||
- **验收**:✅ typecheck 0 新增错误(总数仍 76);✅ 唯一直接引用 `runOrchestration` 的是 dispatcher。逻辑层:每席「总控派活气泡 → 席位交付气泡」、严格顺序、单 special 在跑、`_broadcast` 注入前序上下文、leader 纠偏推进、跑完解锁 Step 3、abort 可中断。**人工 UI 验收待跑**。
|
||
- **MVP 边界**:chain 模式的「挂起→断点续跑」「逐席间干预」未做断点续跑,`clarification`/恢复/干预当前为**整链重跑**(`chainCursor` 列为扩展点)。
|
||
|
||
### 阶段 5 · 草稿持久化兼容 + 收尾(1 天) ✅ 已完成
|
||
**目标**:草稿记住模式,回归不破。
|
||
- [x] `Step2Snapshot`(`useStep2Orchestration.ts`)+ `DraftStep2Snapshot`(`api/drafts.ts`)新增 `orchestrationMode?: "recommend"|"chain"` 与 `chain?: {id,title}|null`。
|
||
- [x] 恢复路径:`orchestrationMode`/`chainMeta` 是**主页面 state**,`getStep2Snapshot` 写入;**包裹版 `hydrateStep2`** 在加载时 `setOrchestrationMode(snapshot.orchestrationMode ?? "recommend")` + `setChainMeta(snapshot.chain ?? null)` 再调 `step2.hydrateFromDraft`;老草稿无字段 → 默认 `recommend`(向后兼容)。`handleStartNewTask` 复位两者。
|
||
- [x] 文档回写:`multi-agent-frontend-dev.md` §5.7(快照形状 + `orchestrationMode/chain` 说明)、§6.2(席位来源选择器导语);`multi-agent-backend-dev.md` 新增 §7B(`roundtable_chains` 表/路由)。
|
||
- **验收**:✅ `pnpm typecheck` 0 新增错误(总数仍 76)。逻辑:chain 会话保存草稿 → 重载 `orchestrationMode="chain"` + 链信息恢复;老草稿(无字段)默认 recommend 照常加载。**人工 UI 验收 + `pnpm build` 待跑**。
|
||
|
||
### 工期粗估
|
||
| 阶段 | 估时 |
|
||
|------|------|
|
||
| 0 后端链条表 + 脚手架 | 1.5d |
|
||
| 1 配置页 | 2–3d |
|
||
| 2 弹窗模式壳 | 1d |
|
||
| 3 链条选择卡片 | 1.5d |
|
||
| 4 Step2 链编排 | 2–3d |
|
||
| 5 持久化兼容收尾 | 1d |
|
||
| **合计** | **约 9–11 人日**(含后端 `roundtable_chains` 表/路由) |
|
||
|
||
---
|
||
|
||
## 8. 验收清单(端到端)
|
||
|
||
1. 推荐弹窗顶部有「智能推荐 / 业务链条」切换;智能推荐与现状逐像素一致。
|
||
2. 配置页 `/page/workspace/roundtable/chains` 左栏列出智能体、右栏有序长条卡片;左→右拖入指定位、右栏内拖拽排序均生效;2–8 校验生效;保存/重载/删除正常。
|
||
3. 业务链条卡片:标题 + 彩色智能体 tag + ← 箭头连接,渲染正确。
|
||
4. 选定链条 → Step 2 席位顺序 = 链条顺序,并**按顺序逐个发言**,后序能看到前序交付。
|
||
5. chain 跑完解锁 Step 3;草稿记住 chain 模式并可重载。
|
||
6. 删除链条中引用的 agent 后,选择端有降级提示,不崩。
|
||
|
||
---
|
||
|
||
## 9. 风险与开放问题
|
||
|
||
| # | 项 | 说明 / 建议 |
|
||
|---|----|-------------|
|
||
| 1 | 配置页:弹窗 vs 页面 | 本文档定为**独立页面**(§2.1)。若产品要弹窗,编辑器组件可复用,仅换外壳。**请确认。** |
|
||
| 2 | 链条持久化 | **已定:后端 MySQL `roundtable_chains`**(§2.2 / §5.2),仿草稿持久化。 |
|
||
| 3 | Step 2 链编排 | **已定:总控按链条顺序派活**(§2.4 / §6),后端零改动。 |
|
||
| 4 | 新增 `@dnd-kit` 依赖 | 若禁止新增依赖,退原生 HTML5 DnD(手感与可达性下降)。**请确认。** |
|
||
| 5 | ← 箭头方向 / tag 阅读顺序 | 默认 `seats[0]` 在最左、先发言;箭头仅作视觉连接。**请确认是否要「从右往左读」。** |
|
||
| 6 | chain 模式是否需要收口 leader 总结 | 默认需要(产出 Step 3 结论)。若末位席位自带总结可关掉。 |
|
||
| 7 | leader 派活纠偏策略 | leader 派错/未派时前端兜底推进游标(§6.2 `pickChainTask`);可加 toast 提示「已按链条强制推进」。 |
|
||
| 8 | chain 模式断点续跑 / 逐席干预 | MVP 不做,列为扩展点(§6.3 / §7 阶段 4)。 |
|
||
| 9 | 链条是否私有 | 本期仅本人私有(仿草稿按 `user_id` 隔离)。共享/市场留扩展。 |
|
||
|
||
---
|
||
|
||
## 10. 路径速查(新增/改动)
|
||
|
||
| 资源 | 路径 | 状态 |
|
||
|------|------|------|
|
||
| 链条类型 | `frontend-web/src/roundtable-planning/lib/business-chain.ts` | 新增 |
|
||
| 链条 API 客户端 | `frontend-web/src/roundtable-planning/api/chains.ts` | 新增 |
|
||
| 配置页 | `frontend-web/src/roundtable-planning/pages/BusinessChainEditorPage.tsx` | 新增 |
|
||
| 链条选择面板 | `frontend-web/src/roundtable-planning/components/BusinessChainPicker.tsx` | 新增 |
|
||
| 链条卡片 | `frontend-web/src/roundtable-planning/components/BusinessChainCard.tsx` | 新增 |
|
||
| 推荐弹窗(加模式壳) | `frontend-web/src/roundtable-planning/components/RecommendAgentsDialog.tsx` | 改 |
|
||
| 主页面(mode 透传) | `frontend-web/src/roundtable-planning/pages/RoundtablePlanningPage.tsx` | 改 |
|
||
| Step2 编排(加 chain) | `frontend-web/src/roundtable-planning/hooks/useStep2Orchestration.ts` | 改 |
|
||
| 草稿快照(加 mode) | `hooks/useDraftPersistence.ts` + `api/drafts.ts` | 改 |
|
||
| 路由注册 | `frontend-web/src/pages/WorkspaceRoutes.tsx` | 改 |
|
||
| 后端链条持久化 | `offline-backend-20260512/backend/packages/harness/deerflow/persistence/roundtable_chains/` | 新增 |
|
||
| 后端链条路由 | `offline-backend-20260512/backend/app/gateway/routers/roundtable_chains.py` | 新增 |
|
||
| 后端 store 接线 | `offline-backend-20260512/backend/app/gateway/deps.py`(`app.state.roundtable_chain_store`)+ `app.py` | 改 |
|
||
</content>
|
||
</invoke>
|