圆桌 Step1:支持多澄清实时展示与合并提交
流式解析 ask_clarification 逐题渲染澄清卡;草稿恢复 selectedAgents 修复参与角色;Step3 隐藏左侧栏让报告区全宽。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
838cae0d2d
commit
23d6f9efc2
9 files changed
+376
-136
No files matched your search
@@ -184,6 +184,7 @@ hooks/useDraftPersistence → utils/drafts + step1/step2 的 Snapshot 类型
|
||||
- `signal`:必填(页面通过 `intentAborterRef` 控制暂停 / 切步取消)
|
||||
- `onTextDelta(text, messageId)`:每个 AI 文本增量
|
||||
- `onPhaseChange(phase, toolName?)`:`connecting → connected → streaming|tool_calling`
|
||||
- **`onClarification(clarification, index)`(2026-06)**:每解析到一条 `ask_clarification` 工具结果帧就回调一次,让前端**实时**逐个渲染澄清卡。任务理解智能体**一轮可能问多个问题**,这是「实时接收显示协助过程」的关键入口。
|
||||
|
||||
**`StreamIntentResult`**:
|
||||
|
||||
@@ -192,14 +193,16 @@ hooks/useDraftPersistence → utils/drafts + step1/step2 的 Snapshot 类型
|
||||
| `status` | `"asking" \| "clarification" \| "done" \| "error"` | 终态分支 |
|
||||
| `content` | `string` | 完整 AI 文本(含 `[INTENT_READY]` 块) |
|
||||
| `summary` | `IntentSummary \| null` | 仅 `done` 有;前端 fallback 也会从 `content` 里用 `splitIntentReadyBlock` 解 |
|
||||
| `clarification` | `ClarificationPayload \| null` | 仅 `clarification` 有 |
|
||||
| `clarification` | `ClarificationPayload \| null` | **第一个**澄清(向后兼容字段) |
|
||||
| `clarifications` | `ClarificationPayload[]` | **本轮全部澄清问题**(2026-06)。来源是流式 `type:"tool"` 帧而非终态帧——**终态帧只带第 1 个**,多问题必须靠它。每条带 `id`(= `tool_call_id`)供前端按题维护选择。 |
|
||||
|
||||
**SSE 解析要点**:
|
||||
|
||||
- `event: metadata` → phase `connected`
|
||||
- `event: messages` + `extractAiTextDelta` → `onTextDelta`
|
||||
- `event: messages` + `type:"tool"` 且 `name==="ask_clarification"` → `extractClarificationToolMessage` 解出完整澄清(options 已是 `{id,label}`),按 `tool_call_id` 去重后 `onClarification` 实时回调并收进 `result.clarifications`
|
||||
- tool call 帧(`tool_calls` / `tool_call_chunks` 非空)→ phase `tool_calling`(含 `ask_clarification` 工具名)
|
||||
- 终态帧识别:`isFinalIntentFrame`(必须有 `status` + `content`)
|
||||
- 终态帧识别:`isFinalIntentFrame`(必须有 `status` + `content`);`status==="clarification"` 且**未**收到任何流式 tool 澄清帧时,才用终态帧字段兜底合成单条澄清
|
||||
|
||||
**Abort 行为**:
|
||||
|
||||
@@ -436,7 +439,8 @@ isOrchestrating = false
|
||||
> **重大变更**:草稿从浏览器 `localStorage` 迁到**后端 MySQL**,按登录用户隔离、跨设备/会话保留。旧 `utils/drafts.ts` 已删除;`useDraftPersistence` 仍保持对外接口不变(列表项含 `name`,由后端 `title` 映射而来),主页面无需改动。后端表/路由见 backend doc「圆桌草稿持久化」。
|
||||
|
||||
- **API 客户端 `api/drafts.ts`**:`listDrafts()`(轻量元数据)/ `getDraft(id)`(全量 step1+step2)/ `createDraft` / `updateDraft`(partial)/ `deleteDraft` / `deriveDraftName`。snake_case↔camelCase 映射在这一层收口(后端 `furthest_step/created_at/updated_at` ↔ 前端 `furthestStep/...`)。
|
||||
- **快照形状不变**:`DraftStep1Snapshot.{intentThreadId, intentReady, interactiveMessages, timelineEntries}`、`DraftStep2Snapshot.{threadIds, coordinatorName, step2RoundtableDialogues, lastLeaderContent, hasConsensus, consensusPercentage, budgetLimit, seatStubMessages}`;以 JSON 整存进后端 `step1`/`step2`(`PortableLongText`/LONGTEXT,避免大对话超 64KB)。
|
||||
- **快照形状**:`DraftStep1Snapshot.{intentThreadId, intentReady, interactiveMessages, timelineEntries}`、`DraftStep2Snapshot.{selectedAgents, threadIds, coordinatorName, step2RoundtableDialogues, lastLeaderContent, hasConsensus, consensusPercentage, budgetLimit, seatStubMessages}`;以 JSON 整存进后端 `step1`/`step2`(`PortableLongText`/LONGTEXT,避免大对话超 64KB)。
|
||||
- **`selectedAgents`(2026-06 修复)**:本次研讨实际选中的席位。**不存的话加载草稿后 Step 2/3 左侧「参与角色」会回退到 `DEFAULT_SELECTED_AGENTS`(默认 6 个)而非当时真正选的那几个** —— `roleTemplates`/`agentNameToRole` 都由 `selectedAgents` 派生。`useStep2Orchestration.hydrateFromDraft` 在该字段非空时 `setSelectedAgents`,老草稿(无此字段)保持当前值不强行覆盖。
|
||||
- `deriveDraftName({intentReady, interactiveMessages})`:优先 `intentReady.objective`,否则第一个 user 消息,否则 `未命名草稿 <时间>`,截断 40 字。**仅在 create 时写一次**(见下方 bug 修复)。
|
||||
- **列表加载**:挂载时 `useEffect` 调 `listDrafts()`;增删改后 `refreshList()` 重拉。
|
||||
- **自动保存策略**:仅在「前进」步骤切换时保存(1→2、2→3),后退不触发。
|
||||
@@ -460,13 +464,20 @@ isOrchestrating = false
|
||||
|
||||
### 6.1 `IntentClarificationCard`
|
||||
|
||||
- Props:`payload`(`ClarificationPayload`)、`selection`、`onSubmit(overrideSelection?)`、`disabled`、`submitting`、`fallbackContent`
|
||||
- Props:`payload`(`ClarificationPayload`)、`selection`、`onSubmit(overrideSelection?)`、`disabled`、`submitting`、`fallbackContent`、**`deferSubmit?`(2026-06)**
|
||||
- **单选**(默认 `allowMultiple === false`):`resolveAllowMultiple` 返回 false → 点击选项即 `onSubmit({selectedIds:[id], custom: ""})`,UI 不显示「发送回答」按钮(除非 `allowCustom`)
|
||||
- **多选**:勾选多个 + 输入补充,再点「发送回答」;答案格式见 `formatIntentClarificationAnswer`:
|
||||
- 仅选项 → `我选择:A、B`
|
||||
- 仅自定义 → 直接发自定义文本
|
||||
- 同时存在 → `我选择:A、B;补充:…`
|
||||
- 展示条件:`isPendingClarification && !intentReady && !parts.summary && pendingIntentClarificationMsgId === msg.id`(已出现 `[INTENT_READY]` 则隐藏可操作卡片)
|
||||
- **单问题 vs 多问题的判断(2026-06)**:`Step1Message` 按 `clarifications.length` 分流,**默认单选**(后端 `allow_multiple` 缺省 false,`resolveAllowMultiple` 兜底):
|
||||
- **单问题(最常见)**:`deferSubmit={false}`,沿用原交互 —— 单选**点击选项即发送**;多选 / 允许自定义则走卡片自带的「发送回答」按钮。**不**显示合并按钮。
|
||||
- **多问题**:`deferSubmit={true}` —— 连单选点击也只**记录**选择(不立即发送),卡片自身隐藏按钮;父组件在卡片下方渲染**一个**「发送全部回答」按钮,统一提交。
|
||||
- 该判断纯在前端(不动工具/后端):依据 `clarifications.length` + `allow_multiple`,后端 `ask_clarification` 本就默认单选。
|
||||
- **合并提交逻辑**:`submitIntentClarifications(msgId, clarifications, overrideByKey?)` 逐题取选择、格式化、拼成一条消息(多题带 `1. <问题>\n答:<答案>` 编号;单题直接发答案;至少答一题才发)。`overrideByKey` 供「单题单选点击即发送」用——此时选择还没写进 state map(setState 异步),把即时选择按 `clarificationKey` 传进去直接用。
|
||||
- **逐题选择隔离**:选择 Map 改为 `Map<string, IntentClarificationSelection>`,key 由 `clarificationKey(msgId, clar.id, index)` 生成(`clar.id` = `tool_call_id`,缺省退化为 `idx-N`)。
|
||||
- 展示条件:`isAgent && clarifications.length>0 && !intentReady && !parts.summary`;**流式期也展示**(卡片 `disabled`,只读,体现「协助过程」实时出现),仅当终态且 `pendingIntentClarificationMsgId === msg.id && !streaming` 时才可交互。出现 `[INTENT_READY]` / summary 后隐藏。
|
||||
- **步骤链强化**:`onClarification` 同时往 `msg.steps` 追加一条 `追问 N:<问题>`,让 `MessageStepsCard` 把每个 `ask_clarification` 调用显示成独立步骤。
|
||||
|
||||
### 6.2 `RecommendAgentsDialog`
|
||||
|
||||
@@ -502,6 +513,7 @@ isOrchestrating = false
|
||||
- 各席位最终交付(中段折叠区)数据源:父组件传入的 `seatDeliveries`,按发送者去重(后到的覆盖前者)
|
||||
- 「重新起草」→ 父组件 `handleStartNewTask`(清空并 `runStep1Init`)
|
||||
- 「打印 / 另存 PDF」→ `window.print()`,浏览器原生流程
|
||||
- **Step 3 不显示左侧栏(2026-06)**:`LeftSidebar` 在 `currentStep === 3` 时 `return null`(原「参与与结论贡献」列已移除),右侧栏在 Step !== 1 时本就 `return null`,因此中间 `middle-viewport`(`flex-1`)会自适应撑满整行,报告区获得最大宽度。
|
||||
|
||||
### 6.4 `ArtifactPreviewModal`
|
||||
|
||||
|
||||
Reference in new issue
Block a user