# 6 步法推演问答页面 — 开发说明文档 > 所在目录:`src/components/angentchat/`(主页面 `SixStepChatPage.tsx`) > 路由地址:`/qa-center/sixstep-chat` > 菜单入口:问答中心 → **6bf**(侧栏文案见 `src/components/Sidebar.tsx`) > 同模块相关路由:`/qa-center/sixstep-agents`、`/qa-center/sixstep-agent-edit` > **最后更新(摘要)**:2026-05-12 — 流式 **`error_log`** / **`errors[]`** 与 `StreamErrorNotice`(后端 skill 失败摘要可含异常片段);`buildLayout` 根节点 **`rootTitle` / `rootDetail`**(已选专家);**`SixStepFlowFullscreen`** 父节点 **收起/展开子树**(过渡动画、缩略图随可见节点);**`/observability`** 独立全站路由。 --- ## 一、功能概述 「6bf 推演问答」由 `SixStepChatPage` 对接后端 **SixStep MCP Backend**。主链路通过 **`POST /api/orchestrate/stream`** 统一流式返回:后端先做 **意图识别**(`intent` 事件),再分支为 **查找专家**(`expert_*`)、**6 步法编排**(信息准备 → 目的 → 行为体 → 预期行为与驱动因素 → 叙事 → 最终结果)、**追问**(`follow_up`,`delta` 流式 Markdown)或 **澄清**(`clarify`)。支持 **结构化卡片** 与 **Markdown** 输出切换;结构化模式下可打开 **流程图沙箱 / 全屏流程图**(`SixStepFlowSandbox` / `SixStepFlowFullscreen`),展示编排返回的实体关系。 单 MCP 调试仍可直接调用 **`POST /api/agents/{key}/invoke`**(例如 `find_expert`、`create_expert`);6bf 主页面以编排流为主,专家查找也可由流内 `intent` 分支触发。对接细节见同目录 **`INTENT_INTEGRATION.md`**。 页面布局为 **顶栏 + 下方主体**:顶栏含页面标题、`SixStepProgressHorizontal`(横向主/子步骤进度)、以及结构化·Markdown、聊天记录、流程图沙箱等控制;主体为 **可收起的会话列表轨**(`SixStepChatHistory`)、**中间对话区与输入**(`SixStepConversation` + `ChatInputComposer`,含专家匹配与 MCP 选择弹窗)、以及 **可选的右侧沙箱**(宽度可拖拽,可关闭)。全屏流程图仍以 `fixed` 遮罩覆盖视口,独立于上述布局。 ``` ┌──────────────────────────────────────────────────────────────────┐ │ 顶栏:标题 · SixStepProgressHorizontal · 输出格式 · 聊天记录 · 流程图 │ ├───┬────────────────────────────────────────────────────┬─────────┤ │会话│ SixStepConversation(多轮结构化阶段) │ 流程图 │ │列表│ + ChatInputComposer │ 沙箱 │ │轨 │ │(可关闭) │ └───┴────────────────────────────────────────────────────┴─────────┘ ``` --- ## 二、目录结构与各文件职责 ``` src/components/angentchat/ │ ├── types.ts # 数据模型类型(含 phase / intent / rawSteps 等) ├── mockData.ts # 本地模拟流式控制器(与 api 侧事件对齐,便于离线演示) ├── api.ts # runIntentOrchestrateStream、runSixStepDeduction、runExpertSearch/Create ├── flowLayout.ts # SVG 流程图布局 ├── styles.css # 动画与工具类 │ ├── SixStepChatPage.tsx # 推演主页面(状态中枢) ├── SixStepProgressHorizontal.tsx# 顶栏横向主/子步骤进度(主页面使用) ├── SixStepProgress.tsx # 纵向进度面板(组件保留;布局以横向为准) ├── SixStepChatHistory.tsx # 会话列表轨(展开/收起) ├── SixStepConversation.tsx # 对话区(阶段卡片 + 专家匹配) ├── SixStepCards.tsx # 阶段卡片原子组件 ├── ExpertMatchCards.tsx # 专家匹配卡片 ├── McpSelectModal.tsx # MCP 多选确认弹窗 ├── StreamErrorNotice.tsx # 助手消息底部:汇总 SSE error_log / 网络类错误的可折叠提示条 ├── SixStepFlowSandbox.tsx # 右侧流程图沙箱 ├── SixStepFlowFullscreen.tsx # 全屏流程图 ├── AgentCardList.tsx # 路由 sixstep-agents:MCP 卡片列表 ├── AgentEditPage.tsx # 路由 sixstep-agent-edit:单 MCP 调试编辑 ├── AgentChatMessageList.tsx # 智能体对话消息列表(编辑页等) ├── INTENT_INTEGRATION.md # 意图流与前后端字段说明(对接备忘) └── doc.md # 本文档 ``` --- ## 三、数据模型(`types.ts`) 所有类型均从此文件导出,是各组件之间传递数据的唯一契约。 ### 3.1 基础枚举 | 类型 | 取值 | 说明 | |------|------|------| | `MainStepKey` | `'info' \| 'sixstep' \| 'final'` | 三个主步骤的键名 | | `SubStepKey` | `'purpose' \| 'actor' \| 'behavior' \| 'narrative'` | 6步法的四个子阶段键名 | | `StepStatus` | `'pending' \| 'running' \| 'done'` | 步骤状态,驱动进度面板和动画 | | `SixStepPhase` | `'idle'` \| `'intent-detecting'` \| `'expert-match'` \| `'expert-confirm'` \| `'generate'` \| `'paused'` \| `'completed'` \| `'clarifying'` \| `'follow-up'` | 助手消息所处交互阶段(与编排流、停止、追问等配合) | | `SelectableMcpKey` | `'purpose' \| 'actor' \| 'behavior_factor' \| 'narrative'` | MCP 弹窗可选子链,会随请求传给后端 `selected_mcps` | `BackendStepResult`:与后端 `step_end` 原始结构对齐,用于 **`continue_analysis`** 时把 `last_steps` 回传编排接口;`SixStepMessage.rawSteps` 在流式过程中累积,`resumedFrom` 对应 **`resume`** 事件中的继续起点。 ### 3.2 卡片数据结构 每类卡片都有独立接口,并通过外键(`purposeId`、`actorId`、`driverId`)建立父子关联,构成树形层级关系。 ``` PurposeCard (目的) └─ ActorCard.purposeId → PurposeCard.id └─ BehaviorDriverCard.actorId → ActorCard.id └─ NarrativeCard.driverId → BehaviorDriverCard.id ``` | 接口 | 字段 | 说明 | |------|------|------| | `PurposeCard` | `id, index, title, reason` | 目的卡片,包含标题和推演理由 | | `ActorCard` | `id, index, purposeId, title, content` | 行为体卡片,`purposeId` 关联所属目的 | | `BehaviorDriverCard` | `id, index, actorId, expectedBehavior, driver` | 预期行为 + 驱动因素二合一卡片,`actorId` 关联所属行为体 | | `NarrativeCard` | `id, index, driverId, title, reason` | 叙事卡片,`driverId` 关联所属驱动因素(`BehaviorDriverCard.id`) | ### 3.3 阶段数据结构 每个子阶段有独立的 `*StageData` 接口: ```typescript interface PurposeStageData { thinking: string; // 模型思考过程文本(流式填充) cards: PurposeCard[]; // 已生成的卡片列表(逐张追加) complete: boolean; // 该阶段是否完全结束 } // ActorStageData / BehaviorStageData / NarrativeStageData 结构类似 ``` ### 3.4 消息总线:`SixStepMessage` 这是整个页面最核心的数据结构,一条助手消息包含所有推演状态: ```typescript interface SixStepMessage { id: string; role: 'user' | 'assistant'; content?: string; timestamp: Date; outputFormat?: 'structured' | 'markdown'; markdownContent?: string; stages?: SixStepStages; finalAnswer?: string; mainStepStatus?: Record; subStepStatus?: Record; currentSubStep?: SubStepKey; currentMainStep?: MainStepKey; phase?: SixStepPhase; expertMatch?: ExpertMatchData; selectedExpert?: ExpertCard; selectedMcps?: SelectableMcpKey[]; intent?: { name: string; confidence?: number; reason?: string }; clarifyQuestion?: string; rawSteps?: BackendStepResult[]; resumedFrom?: string; effectiveUserInput?: string; errors?: StreamErrorItem[]; } ``` **`errors` / `StreamErrorItem`**:`runIntentOrchestrateStream` 收到 SSE **`error_log`** 时追加;致命失败仍会走 **`error`** 事件并映射为同类结构。`SixStepConversation` 中 **`StreamErrorNotice`** 读取 `message.errors` 展示摘要(可折叠列表)。 **关键设计**:`mainStepStatus` / `subStepStatus` 驱动顶栏 `SixStepProgressHorizontal`;`stages` 驱动中间对话渲染;`currentSubStep` 驱动流程图沙箱蚂蚁线。`phase` / `intent` / `rawSteps` 与编排流、暂停继续、追问分支一致。所有信息集中在一个对象内,组件通过 props 按需取用。 --- ## 四、模拟数据生成器(`mockData.ts`) > **线上主路径**:`SixStepChatPage` 使用 **`runIntentOrchestrateStream`** 消费真实 `/api/orchestrate/stream`。`mockData.ts` 中的 **`runSixStepDeduction`** 仍保留,用于本地演示或与 `api.ts` 中纯编排解析逻辑对齐;新功能(意图、专家 SSE、`resume` 等)以 `runIntentOrchestrateStream` 为准。 ### 4.1 种子数据 文件顶部定义了四组静态种子数组: | 变量 | 长度 | 用途 | |------|------|------| | `PURPOSE_SEED` | 3 | 目的卡片内容模板 | | `ACTOR_SEED` | 7 | 行为体卡片内容模板 | | `BEHAVIOR_SEED` | 8 | 预期行为与驱动因素卡片内容模板 | | `NARRATIVE_SEED` | 8 | 叙事卡片内容模板 | 使用时循环取模(`(counter - 1) % SEED.length`),确保不越界。 ### 4.2 卡片生成规则 | 阶段 | 数量规则 | |------|---------| | 目的 | 固定 3 张(取自 `PURPOSE_SEED` 全量) | | 行为体 | 每个目的对应的数量:第 0 个目的 2 张,其余 1 张,共 4 张 | | 预期行为/驱动 | 每个行为体:偶数索引 2 张,奇数索引 1 张,共 6 张 | | 叙事 | 每个预期行为:偶数索引 2 张,奇数索引 1 张,共 9 张 | 最终流程图节点总数:`1(智能体)+ 3 + 4 + 6×2(预期行为+驱动因素)+ 9 = 29 节点`。 ### 4.3 流式控制器:`runSixStepDeduction` ```typescript export function runSixStepDeduction( message: SixStepMessage, // 直接在此对象上原地修改字段 options: { onUpdate: (msg: SixStepMessage) => void; // 每次有新内容时回调 onComplete?: () => void; // 全部完成时回调 } ): SixStepMockController ``` 返回的 `SixStepMockController` 包含: - `message`: 正在被修改的消息对象引用 - `stop()`: 立即取消所有待执行的 `setTimeout`,中止推演 - `promise`: 推演完成后 resolve 的 Promise(stop 后不再 resolve) **内部时序**(所有延迟均可通过顶部常量调整): ``` 常量 默认值 说明 TYPE_INTERVAL 28ms 每次打字输出 4 个字符后的等待间隔 STAGE_GAP 380ms 阶段切换之间的等待时长 ``` 执行顺序(伪代码): ``` setMain('info', 'running') → emit wait(800ms) ← 模拟信息准备耗时 setMain('info', 'done') setMain('sixstep', 'running') → emit wait(STAGE_GAP) setSub('purpose', 'running') → emit typeInto(PURPOSE_THINKING) ← 逐字打出思考过程 append cards 逐张(wait 280ms/张) setSub('purpose', 'done') → emit wait(STAGE_GAP) setSub('actor', 'running') → emit typeInto(ACTOR_THINKING) append cards 逐张(wait 220ms/张) setSub('actor', 'done') → emit wait(STAGE_GAP) setSub('behavior', 'running') → emit typeInto(BEHAVIOR_THINKING) append cards 逐张(wait 200ms/张) setSub('behavior', 'done') → emit wait(STAGE_GAP) setSub('narrative', 'running') → emit typeInto(NARRATIVE_THINKING) append cards 逐张(wait 200ms/张) setSub('narrative', 'done') setMain('sixstep', 'done') setMain('final', 'running') → emit wait(STAGE_GAP) typeInto(FINAL_ANSWER) ← 逐字打出最终结论 setMain('final', 'done') → emit onComplete() ``` **`emit` 机制**:每次调用 `onUpdate({ ...message, stages: { ...message.stages } })`,将消息对象浅拷贝后传给父组件,触发 React re-render。 --- ## 五、流程图布局工具(`flowLayout.ts`) 此文件被沙箱和全屏组件共同引用,避免重复计算。 ### 5.1 核心函数:`buildLayout` ```typescript function buildLayout( purposes: PurposeCard[], actors: ActorCard[], behaviors: BehaviorDriverCard[], narratives: NarrativeCard[], options?: BuildLayoutOptions // 可覆盖节点尺寸和间距,以及根节点文案 ): FlowLayout ``` **`BuildLayoutOptions` 扩展**:除 `nodeWidth`、`nodeHeight`、`levelGap`、`nodeGap`、`paddingX`、`paddingY` 外,可选 **`rootTitle`**、**`rootDetail`**。用于将层级 0 的 `agent-root` 从默认「智能体 / 协同推演…」改为 **当前已选专家**(`SixStepFlowSandbox` / `SixStepFlowFullscreen` 在存在 `message.selectedExpert` 时传入专家姓名与简介摘要)。 返回 `FlowLayout`: ```typescript interface FlowLayout { nodes: LayoutNode[]; // 已计算好坐标的节点列表 edges: LayoutEdge[]; // 已确定连接关系和颜色对的边列表 width: number; // 整体画布宽度(用于 SVG viewBox) height: number; // 整体画布高度 } ``` **布局算法**:改良的"叶节点优先后序遍历": 1. 从 agent 根节点开始,递归 DFS 处理子树 2. 叶节点(无子节点的节点)按从左到右顺序分配 x 坐标,每次递增 `nodeWidth + nodeGap` 3. 非叶节点的 x = (最左子节点 x + 最右子节点 x) / 2,确保父节点居中于子节点上方 4. 所有节点的 y = `paddingY + level × (nodeHeight + levelGap)`,level 即树的深度 节点层级(`level`)对应关系: | level | variant | 说明 | |-------|---------|------| | 0 | `agent` | 智能体根节点 | | 1 | `purpose` | 目的 | | 2 | `actor` | 行为体 | | 3 | `behavior` | 预期行为 | | 4 | `driver` | 驱动因素 | | 5 | `narrative` | 叙事 | 注意:`BehaviorDriverCard` 一张卡片会生成两个节点(`behavior` + `driver`),id 分别为 `${card.id}-behavior` 和 `${card.id}-driver`。 ### 5.2 共享常量 | 常量 | 说明 | |------|------| | `EDGE_COLOR` | 按边类型分别定义深色/浅色主题颜色(5 组) | | `EDGE_LABEL` | 每种边类型的中文图例说明 | | `CURRENT_PAIR_BY_STAGE` | `SubStepKey → LayoutEdge['pair']` 映射,决定哪组边在哪个阶段展示蚂蚁线 | | `STATUS_TITLE` | `SubStepKey → string` 映射,沙箱/全屏顶部标题文字 | | `VARIANT_ABBREV` | 节点类型 → 简化缩写(沙箱专用,如 `purpose → 'md'`) | | `VARIANT_LABEL` | 节点类型 → 完整中文标签 | ### 5.3 辅助函数:`buildEdgePath` 生成 SVG 三次贝塞尔曲线路径字符串: ``` M x1 y1 C x1 my, x2 my, x2 y2 ``` 控制点均取中间 y 坐标(`(y1+y2)/2`),形成平滑 S 型连线,不与节点重叠。 --- ## 六、CSS 动画(`styles.css`) | 类名 | 动画 | 用途 | |------|------|------| | `.sixstep-ant-line` | `stroke-dasharray: 6 6` + `dashoffset` 循环 | SVG 边上的蚂蚁线流动效果(当阶段正在生成时) | | `.sixstep-pulse` | box-shadow 呼吸光晕 | 进度面板当前步骤图标、沙箱标题图标 | | `.sixstep-shimmer-text` / `...-light` | 渐变横扫文字 | 进度面板"生成中"文字、沙箱标题生成中状态 | | `.sixstep-spin-soft` | 360° 匀速旋转 | loading 图标 | | `.sixstep-fade-up` | 从下方淡入 | 子阶段卡片逐步出现 | | `.sixstep-node-pop` | scale 弹出 + 高光 | 流程图新增节点的弹出动画(持续 600ms) | | `.sixstep-ring-burst` | SVG circle `r` 从 0 扩散到 60,同时 opacity→0 | 节点出现时的扩散光环效果(持续 700ms) | | `.sixstep-canvas-grab` / `.sixstep-canvas-grabbing` | `cursor: grab/grabbing` | 流程图画布拖拽手型 | | `.sixstep-toolbar-btn` | 基础尺寸 + hover 过渡 | 工具栏图标按钮 | | `.sixstep-minimap-rect` | 静态样式 | 缩略图视口矩形指示框 | | `.sixstep-resize-handle` | 竖线拖拽手柄 | 沙箱左边缘的宽度调节拖拽条 | --- ## 七、主页面(`SixStepChatPage.tsx`) ### 7.1 职责 主页面是整个功能的**状态中枢**,负责: - 多会话列表与 **`/api/conversations`** 持久化(`filter_key` / `filter_value` 标识 6bf 页)。 - 创建并管理 **`runIntentOrchestrateStream`**(编排 + 意图)与 **`runExpertSearch` / `runExpertCreate`**(仍可用于「换一批专家」「创建专家」等直连 invoke)的控制器生命周期。 - 将状态与专家相关回调分发给 `SixStepConversation`、`McpSelectModal`、`SixStepFlowSandbox` 等。 - 管理沙箱宽度拖拽。 ### 7.2 状态一览(节选) | 状态 | 类型 | 说明 | |------|------|------| | `conversations` / `activeConvId` | 多会话 | 当前选中会话及其 `messages` | | `input` | `string` | 输入框文本 | | `isGenerating` | `boolean` | 是否正在请求流式接口 | | `outputFormat` | `'structured' \| 'markdown'` | 传给后端的 `output_format` | | `sandboxOpen` / `sandboxWidth` / `fullscreenOpen` | UI | 流程图沙箱与全屏 | | `historyOpen` / `loading` | UI / 加载 | 聊天记录侧栏与首屏拉取 | | `activeAssistantMsgId` | `string \| null` | 当前高亮/流式更新的助手消息 | | `mcpModal` | `{ open, messageId, expert }` | MCP 多选弹窗状态 | ### 7.3 Ref 一览 | Ref | 类型 | 用途 | |-----|------|------| | `controllerRef` | `SixStepMockController \| null` | `runIntentOrchestrateStream` 的 `stop()` | | `expertControllerRef` | `ExpertRunController \| null` | 专家搜索/创建流的 `stop()` | | `conversationsRef` | 会话数组 | 持久化前读取最新消息 | | `containerRef` / `resizeRef` | DOM / 拖拽 | 沙箱宽度拖拽 | ### 7.4 核心逻辑 **`handleSend`** 1. 追加用户消息;创建初始助手消息(`buildInitialAssistantMessage`,`phase` 随流程推进)。 2. 调用 **`runIntentOrchestrateStream`**,请求体为 **`IntentOrchestrateRequest`**:`user_input`、`output_format`、`intent_mode`(默认 `auto`)、`current_phase`(含上一条助手为 **`paused`** 时传 `paused`)、`selected_expert`、`selected_mcps`、`expert_candidates`、**`last_steps`(来自上一条助手 `rawSteps`)**、`conversation_history` 等。 - 暂停继续时会继承上一条助手的 `rawSteps` 与 `selectedMcps`(`pausedSelectedMcps`),并显式回传 `selected_mcps` / `last_steps`,避免恢复时丢失步骤选择与历史上下文。 3. `onUpdate` 内更新当前会话中对应 `id` 的助手消息;结束后 `persistConversation`。 **专家与 MCP** - 用户确认专家后 **`handleMcpModalConfirm`**:写入 `selectedExpert` / `selectedMcps`,并可用 **`intent_mode: 'force_orchestrate'`** 立即启动分析(无需用户再次输入)。 - **`handleRefetchExpert`**:可用 `force_find_expert` 并排除已有候选。 **`handleStop`** - `AbortController` 中止流;将未完成步骤从 `running` 收回;助手 **`phase` 置为 `paused`**,便于下次发送走 **`continue_analysis`** / `rerun_from_step` / `revise_input_continue` / `change_steps_continue` / `edit_stage_continue`。 **流内关键事件补充** - `resume`:恢复继续分析时,会先把已有 `stages` 中的 `purpose/actor/behavior` 卡片回填到转换缓存,确保后续 `step_end` 的父子映射准确。 - `rerun`:前端会按 `affected_steps` 清空受影响阶段(`stages`、`subStepStatus`、`rawSteps` 与局部卡片缓存),再接收重跑后的 `step_*`。 - `change_steps`:按 `removed_steps` 清理被移除阶段与 `rawSteps`,保留未移除阶段并继续执行 `steps_to_run`。 - `edit_apply`:先更新被编辑卡片内容,再按 `affected_steps` 清理后续阶段并触发重跑。 - `intent`:包含 `slots`(如 `step`、`new_input`),用于更细粒度前端状态更新。 **沙箱宽度拖拽**:逻辑同前(`mousemove` / `mouseup`,保证对话区最小宽度)。 **活跃助手消息派生**: ```typescript const activeAssistantId = /* activeAssistantMsgId 或最后一条 assistant */; const activeAssistant = messages.find((m) => m.id === activeAssistantId); const liveMainStatus = activeAssistant?.mainStepStatus ?? DEFAULT_MAIN_STATUS; const liveSubStatus = activeAssistant?.subStepStatus ?? DEFAULT_SUB_STATUS; ``` `liveMainStatus` / `liveSubStatus` 传给 **`SixStepProgressHorizontal`**;`activeAssistant` 传给沙箱与全屏。 ### 7.5 Props 传递图 ``` SixStepChatPage │ ├─► SixStepChatHistory # 会话列表(与顶栏「聊天记录」联动) ├─► SixStepProgressHorizontal # mainStatus / subStatus / isDark ├─► SixStepConversation │ messages, isGenerating, activeMessageId, │ expertHandlers(选专家 / 换一批 / 创建 / 确认) ├─► ChatInputComposer # onSend → handleSend, onStop → handleStop ├─► SixStepFlowSandbox # message={activeAssistant} ├─► SixStepFlowFullscreen └─► McpSelectModal # 专家确认后选 MCP ``` --- ## 八、左侧进度面板(`SixStepProgress.tsx`) > **主页面顶栏**使用的是 **`SixStepProgressHorizontal.tsx`**(横向主/子步骤);本节描述纵向 `SixStepProgress` 的 Props 与交互,组件仍保留供其他布局复用。 ### 8.1 Props | Prop | 类型 | 必填 | 说明 | |------|------|------|------| | `mainStatus` | `Record` | ✓ | 三主步骤状态 | | `subStatus` | `Record` | ✓ | 四子阶段状态 | | `collapsed` | `boolean` | 可选 | 是否折叠,默认 `false` | | `onToggleCollapse` | `() => void` | 可选 | 展开/收起回调 | ### 8.2 展开视图(`collapsed = false`) 从上到下展示三个主步骤卡片,每个卡片包含: - 彩色图标(每个步骤有独立配色,running 时呼吸脉冲) - 标题 + 进度徽标(`已完成 / 生成中 / 待开始`) - 副文本描述(running 时展示 shimmer 动画) - 步骤之间用竖线连接(完成后变绿色) 当 `sixstep` 阶段进入 running 或 done,在其卡片内部展开四个子阶段列表(`SUB_STEPS`),每个子阶段包含: - 彩色图标(与流程图对应色) - 标题 + 状态图标 - 时间线左侧的节点圆点 + 连接竖线 右上角收起按钮(`ChevronLeft`)触发 `onToggleCollapse`。 ### 8.3 折叠视图(`collapsed = true`) 渲染轻量的 `CollapsedView` 子组件: - 宽度由父容器通过 `w-14` 控制(56px),有 `transition-all duration-300` 过渡动画 - 顶部 `ChevronRight` 按钮用于展开 - 竖向排列三个主步骤彩色图标 + 状态圆点 - 若 `sixstep` 已解锁,在其下方额外展示四个小尺寸子阶段图标(`w-7 h-7`) - 所有元素均有 `title` 属性,鼠标悬停显示步骤名称 ### 8.4 配色方案 步骤颜色定义在 `MAIN_STEPS` 和 `SUB_STEPS` 数组的 `color` 字段中: | 步骤 | 深色 | 浅色 | |------|------|------| | 信息准备 | sky-300 / sky-500 | sky-600 / sky-50 | | 6步法推演 | violet-300 / violet-500 | violet-600 / violet-50 | | 最终结果 | emerald-300 / emerald-500 | emerald-600 / emerald-50 | | 目的 | violet-300 | orange | | 行为体 | cyan-300 | red | | 预期行为/驱动 | emerald-300 | emerald | | 叙事 | amber-300 | amber | --- ## 九、对话列表(`SixStepConversation.tsx`) ### 9.1 Props | Prop | 类型 | 说明 | |------|------|------| | `messages` | `SixStepMessage[]` | 全部消息 | | `isGenerating` | `boolean` | 是否正在生成 | | `activeMessageId` | `string \| null` | 当前流式聚焦的助手消息 id | | `expertHandlers` | 可选对象 | `onSelectExpert` / `onRefetchExpert` / `onCreateExpert` / `onConfirmExpert` | ### 9.2 消息渲染策略 每条消息根据 `role` 字段分叉: - `role === 'user'` → 渲染 `UserMessage`:右对齐气泡 + 用户头像 - `role === 'assistant'` → 渲染 `AssistantMessage`:左对齐 + 机器人头像(生成中时呼吸脉冲 + 星光图标) 消息列表容器 `ref` 监听内容变化,每次 `messages` 或 `isGenerating` 变化后自动滚动到底部。 ### 9.3 助手消息 `AssistantMessage` 内部读取 `message.stages` 和 `message.mainStepStatus`、`message.subStepStatus`,根据是否存在各阶段数据来条件渲染对应的阶段块。底部(及部分分支末尾)挂载 **`StreamErrorNotice`**,展示 **`message.errors`**(来自 SSE `error_log` 等)。 助手消息结构示意: ``` AssistantMessage │ ├─ 信息准备中(仅当 info=running 且无任何 stage)→ shimmer 提示 │ ├─ PurposeStage (当 stages.purpose 存在时) │ ├─ InlineCollapsible (以 StageHeader 为标题) │ │ ├─ ThinkingBlock (思考过程) │ │ └─ InlineCollapsible (目的卡片列表) │ │ └─ PurposeCardItem × N │ ├─ ActorStage (当 stages.actor 存在时) │ ├─ InlineCollapsible │ │ ├─ ThinkingBlock │ │ └─ InlineCollapsible (行为体卡片列表) │ │ └─ 按 purpose 分组 │ │ ├─ ParentGroupHeader (所属目的) │ │ └─ ActorCardItem × N │ ├─ BehaviorStage (当 stages.behavior 存在时) │ ├─ InlineCollapsible │ │ ├─ ThinkingBlock │ │ └─ InlineCollapsible (预期行为/驱动因素卡片列表) │ │ └─ 按 actor 分组 │ │ ├─ ParentGroupHeader (所属行为体) │ │ └─ BehaviorCardItem × N(2列网格) │ ├─ NarrativeStage (当 stages.narrative 存在时) │ ├─ InlineCollapsible │ │ ├─ ThinkingBlock │ │ └─ InlineCollapsible (叙事卡片列表) │ │ └─ 按 driver 分组 │ │ ├─ ParentGroupHeader (所属驱动因素) │ │ └─ NarrativeCardItem × N │ └─ FinalAnswer (当 finalAnswer 存在或 final=running 时) │ └─ StreamErrorNotice (当 message.errors 非空) ``` **分组计算**:`ActorStage`、`BehaviorStage`、`NarrativeStage` 都在内部用 `useMemo` 计算分组,将卡片按父 ID 聚合到 Map,再按父实体列表顺序输出。 --- ## 十、卡片原子组件(`SixStepCards.tsx`) 此文件作为"组件库",为对话列表提供所有可复用的卡片和容器组件。 ### 10.1 `InlineCollapsible` 通用折叠面板,思考过程区块、卡片列表区块均复用此组件。 | Prop | 说明 | |------|------| | `title` | 标题区 ReactNode | | `children` | 折叠内容 | | `defaultOpen` | 默认展开状态(默认 `true`) | | `variant` | `'default'`(标准边框背景)或 `'subtle'`(更淡)| 展开/收起使用 `grid-template-rows` 动画(`0fr → 1fr`),配合 `opacity` 过渡,无需 JS 计算高度。 ### 10.2 各卡片组件 | 组件 | 卡片类型 | 布局特点 | |------|---------|---------| | `PurposeCardItem` | 目的 | 紫/橙渐变背景,索引徽标 + Target 图标 + 标题 + 理由 | | `ActorCardItem` | 行为体 | 青/红渐变背景,UserCheck 图标 + 标题 + 内容 | | `BehaviorCardItem` | 预期行为/驱动因素 | 翠绿渐变背景,上下两部分:TrendingUp 图标+预期行为 / Zap 图标+驱动因素 | | `NarrativeCardItem` | 叙事 | 琥珀渐变背景,ScrollText 图标 + 标题 + 理由 | 所有卡片均有 `.sixstep-fade-up` 进入动画,`hover` 时边框加深。 ### 10.3 `ParentGroupHeader` 显示某类卡片所属父实体名称的标签行,用于行为体、预期行为/驱动、叙事列表的分组标题。 | Prop | 说明 | |------|------| | `label` | 关系说明(如"所属目的") | | `parentTitle` | 父实体名称 | | `count` | 该组卡片数量 | | `variant` | `'actor' \| 'behavior' \| 'narrative'`,决定图标和配色 | ### 10.4 `ThinkingBlock` 展示思考过程文本的区块,`loading` 为 `true` 时标题行展示 "生成中" shimmer 文字。 ### 10.5 `StageHeader` 折叠面板标题,包含阶段图标、阶段标题、卡片数量徽标、状态徽标(已完成/生成中)。`loading = true` 时图标呼吸脉冲,副文本展示 shimmer 动画。 --- ## 十一、流程图沙箱(`SixStepFlowSandbox.tsx`) ### 11.1 Props | Prop | 类型 | 说明 | |------|------|------| | `message` | `SixStepMessage \| undefined` | 活跃助手消息,用于提取各阶段卡片数据 | | `width` | `number` | 当前沙箱宽度(px),由主页面拖拽控制 | | `onResizeStart` | `(e: React.MouseEvent) => void` | 拖拽手柄 mousedown 回调 | | `onClose` | `() => void` | 关闭沙箱回调 | | `onOpenFullscreen` | `() => void` | 打开全屏回调 | ### 11.2 布局参数(`COMPACT_OPTIONS`) 沙箱使用缩小版布局,节点小于全屏版: ```typescript const COMPACT_OPTIONS = { nodeWidth: 120, nodeHeight: 60, levelGap: 80, nodeGap: 24, paddingX: 32, paddingY: 28, }; ``` ### 11.3 简化节点(`CompactNode`) 每个节点只显示**彩色图标 + 阶段缩写**(`VARIANT_ABBREV`): | 层级 | 缩写 | |------|------| | 智能体(或根标题改为已选专家名时仍为 variant `agent`) | AI | | 目的 | md | | 行为体 | xwt | | 预期行为 | yqxw | | 驱动因素 | qdys | | 叙事 | xs | 底部固定图例说明对应关系。 ### 11.4 新增节点动画 通过 `seenIdsRef`(`Set`)记录已渲染过的节点 ID。每次 `layout.nodes` 变化时,找出新增的 ID,将其加入 `appearingIds` 状态,700ms 后自动移除。 `CompactNode` 接收 `appearing` prop,为 `true` 时: 1. 为 `` 添加 `.sixstep-node-pop` CSS 类(scale 弹出) 2. 在节点中心绘制一个 SVG `` 并添加 `.sixstep-ring-burst` CSS 类(扩散光环) ### 11.5 交互能力 **平移(Pan)**: - `mousedown` 记录起始坐标和初始 `tx/ty` 到 `panStart.current` - 通过 `useEffect` 监听 `isPanning` 状态,动态注册/注销 `window` 的 `mousemove` 和 `mouseup` - `mousemove` 计算偏移量 `(e.clientX - startX, e.clientY - startY)` 叠加到初始平移值 **缩放(Zoom)**: - 通过 `useEffect` 向 `containerRef` 注册原生 `wheel` 事件(`{ passive: false }`),确保 `e.preventDefault()` 生效,不触发浏览器页面缩放 - 以鼠标位置为缩放中心:`nextTx = mx - (mx - prev.tx) * (nextScale / prev.scale)` - 缩放范围:0.3x ~ 2.5x **工具栏**: | 按钮 | 功能 | |------|------| | `+` | 按 1.2 倍放大 | | `-` | 按 1.2 倍缩小 | | ↺ | 恢复 1:1(translate 0,0 scale 1) | | ⊕ | 定位画布中央(保持当前缩放比,调整平移使图形居中) | | ⤢ | 触发 `onOpenFullscreen` | 首次有内容时自动执行 `fitToView`(等比缩放 + 居中),由 `fitAppliedRef` 控制只执行一次。 ### 11.6 连线说明 SVG 中的 `` 为每种边类型各定义一个箭头 marker(id 加 `sixstep-sandbox-` 前缀,与全屏版 marker 不冲突)。当 `activePair === edge.pair` 时,该边添加 `.sixstep-ant-line` CSS 类展示蚂蚁线动画。 ### 11.7 调整宽度 左侧有一个 `position: absolute` 的 `.sixstep-resize-handle` 竖线拖拽条,`mousedown` 触发 `onResizeStart`(由主页面处理具体逻辑)。 --- ## 十二、全屏流程图视图(`SixStepFlowFullscreen.tsx`) ### 12.1 Props | Prop | 类型 | 说明 | |------|------|------| | `message` | `SixStepMessage \| undefined` | 与沙箱相同的活跃助手消息 | | `onClose` | `() => void` | 关闭全屏回调 | ### 12.2 与沙箱的区别 | 特性 | 沙箱 | 全屏 | |------|------|------| | 布局参数 | 紧凑(节点 120×60) | 标准(节点 200×84) | | 节点内容 | 缩写 + 图标 | 完整标题 + 详情 + 图标 | | 节点出现动画 | 有(pop + ring-burst) | 无 | | 子树收起/展开 | 无 | 有(点击**有子节点**的父节点;右下角「-」收起 / 「+」展开;画布左下角有操作提示) | | 缩略图 | 无 | 有(右下角;仅绘制当前可见节点) | | 下载 | 无 | 有(导出 SVG) | | Esc 关闭 | 无 | 有 | | 层级 | flex 子元素 | `fixed inset-0 z-50`(全视口遮罩) | | 缩放范围 | 0.3x ~ 2.5x | 0.2x ~ 3x | ### 12.3 详细节点(`DetailNode`) 使用 `foreignObject` 嵌入 HTML,展示: - 彩色图标包裹 + 大写类型标签(`VARIANT_LABEL`) - 节点标题(`line-clamp-1`) - 详情内容(`line-clamp-2`) 深色/浅色主题的渐变背景、图标颜色均通过 `VARIANT_TOKEN` 查表获取。 **收起子树**:当 `flowLayout` 中节点带 **`children`** 时,卡片右上角显示 **`+` / `-`**,点击节点(或该角标)切换 **`collapsedNodeIds`**:自该节点向下的后代节点与相关边不再纳入 **`targetVisibleNodeSet`**,并以约 **240ms** 的透明度过渡(进入/离开集合合并渲染,避免闪烁)。重新生成布局时会剔除已不存在的节点 id,避免残留收起状态。根节点若被选为专家展示,仍可按树结构收起其下子链。 ### 12.4 缩略图(`Minimap`) 固定 220×150px,渲染在右下角。 内部逻辑: 1. 计算整体布局与 minimap 尺寸的缩放比 `ratio = Math.min((W-10)/layoutW, (H-10)/layoutH)` 2. 按 `ratio` 缩小绘制每个节点矩形(使用 `MINIMAP_VARIANT_FILL` 颜色,无文字) 3. 根据当前 `transform`(`tx, ty, scale`)和视口尺寸,计算可见区域在世界坐标的矩形,再乘 `ratio` 映射到 minimap 坐标系,绘制蓝色描边矩形(`.sixstep-minimap-rect`) 4. 点击 minimap:将点击的 minimap 坐标逆映射到世界坐标,调用 `onJump` 将视口移动到以该点为中心 ### 12.5 SVG 下载 点击下载按钮时: 1. 克隆 `svgRef.current`(当前全屏 SVG 元素) 2. 设置 `width/height` 为实际布局尺寸,移除 transform 3. 用 `XMLSerializer` 序列化为字符串 4. 创建 `Blob` 和临时 `` 标签,触发下载 5. 文件名格式:`6步法推演-YYYY-MM-DD-HH-mm-ss.svg` > ⚠️ 注意:由于节点内容在 `foreignObject` 中使用 HTML + CSS 类名,下载的 SVG 打开时若外部查看器不支持 CSS,样式会丢失。可在未来改为纯 SVG 文本节点渲染以提升兼容性。 --- ## 十三、整体数据流图 ``` 用户输入(回车) │ ▼ SixStepChatPage.handleSend() │ ├─► 当前会话追加 UserMessage + 初始 AssistantMessage │ └─► runIntentOrchestrateStream(assistant, { onUpdate, onComplete }, IntentOrchestrateRequest) │ │ readSSE('/api/orchestrate/stream', body) │ │ intent / expert_* / clarify / resume / rerun / change_steps / edit_apply │ start / step_* / flow / done / delta(追问或 markdown) │ 每次 onUpdate → 更新会话中该 assistant 的 SixStepMessage │ ▼ React re-render │ ├─► SixStepProgressHorizontal(liveMainStatus / liveSubStatus) ├─► SixStepConversation(messages、expertHandlers、intent/clarify 等) ├─► SixStepFlowSandbox / Fullscreen(stages → buildLayout) └─► persistConversation(SQLite) ``` --- ## 十四、路由注册 **`src/App.tsx`**: ```tsx // 路由定义 } /> } /> // 导航路径映射(handleNavigate 函数) else if ([..., 'sixstep-chat'].includes(page)) { path = `/qa-center/${page}`; } ``` **可观测性页面**(调用耗时 / 错误日志筛选):全站路由 **`/observability`**,侧栏顶级「可观测性」,对接后端 **`GET /api/observability/calls`**、**`/errors`**、**`/options`** 等(详见 `backend/doc.md`)。与 6bf 页面独立,不在 `/qa-center` 前缀下。 **`src/components/Sidebar.tsx`**: ```tsx // 菜单配置(问答中心 children) { id: 'sixstep-chat', label: '6bf', icon: Workflow } // qaPages 集合(控制问答中心父菜单高亮) const qaPages = new Set([..., 'sixstep-chat']); ``` --- ## 十五、流式错误与 `error_log`(`api.ts`) 编排 SSE 除 **`error`** 外,可能下发 **`error_log`**(步骤失败摘要、skill 降级等)。`runIntentOrchestrateStream` 将其归一为 **`StreamErrorItem`** 并 **`push` 到 `message.errors`**;网络异常也会写入类似结构。后端在可执行 **skill 失败已跳过**时,**`message` 文案**可能附带 **异常原因前若干字符**(与 `backend/app/mcp/base.py` 截断策略一致),便于在气泡内区分超时与其它错误。 **`StreamErrorNotice`**:仅在对应助手气泡内展示,支持折叠展开、错误/警告计数摘要;不参与 `showAnalysisContent` 隐藏逻辑(专家阶段亦可显示)。 --- ## 十六、扩展指南 ### 接入编排与意图流 1. 主入口为 **`runIntentOrchestrateStream`**(`api.ts`),与后端 **`/api/orchestrate/stream`** 事件一一对应;扩展新意图时通常同时改 **`backend/app/intent/detector.py`** 与 **`runIntentOrchestrateStream`** 的分支解析。 - 当前除 `continue_analysis` 外,还支持 `rerun_from_step`、`revise_input_continue`、`change_steps_continue`、`edit_stage_continue` 分支。 2. 保持 **`onUpdate(SixStepMessage)`** 契约:`SixStepChatPage` 只需合并助手消息字段。 3. 纯编排、无意图分支的调试可继续用 **`runSixStepDeduction`**(同文件内,仅 `start` / `step_*` / `flow` / `done`)。 详见 **`INTENT_INTEGRATION.md`** 与仓库 **`docs/xvqiu.md`**。 ### 某子步(如行为体、叙事)体感很慢时 前端 **`step_end`** 事件里会带上该步的 **`duration_ms`**(整步总耗时),便于在 UI 或调试时对比各步。 更细拆分(skill 耗时 vs 拼 prompt 扩展 vs LLM)在**后端**完成:每次 `POST /api/agents/{key}/invoke` 及编排内对 `MCPAgent.invoke` 的调用,服务端会打 **`mcp_timing`** / **`mcp_skill_timing`** 日志(见 **`backend/doc.md`**「MCP 调用接口」与 **`backend/DEVELOPMENT.md`** §5.2)。排查时在运行 `python -m app.main` 的终端查看即可;无需改前端请求体。 ### 替换流程图引擎(如 G6 / React Flow) 仅需替换 `SixStepFlowSandbox.tsx` 和 `SixStepFlowFullscreen.tsx` 中的 SVG 渲染部分: - `buildLayout()` 的 `LayoutNode` 数组可直接转换为 G6 节点 - `LayoutEdge` 数组可直接转换为 G6 边 - `CURRENT_PAIR_BY_STAGE` 和 `EDGE_COLOR` 仍适用 ### 新增推演子阶段 1. 在 `types.ts` 的 `SubStepKey` 中新增键名 2. 在 `SixStepStages` 中新增对应 `*StageData` 字段 3. 在 `mockData.ts` 的 `runSixStepDeduction` 中新增生成逻辑(若仍需要离线演示) 4. 在 `SixStepConversation.tsx` 的 `AssistantMessage` 中新增阶段渲染块 5. 在 `SixStepProgress.tsx` / **`SixStepProgressHorizontal.tsx`** 的 `SUB_STEPS` 中新增步骤项 6. 若需要流程图新层级,在 `flowLayout.ts` 的 `buildLayout` 中新增节点构建逻辑