代码修改

This commit is contained in:
fengxiangdebaozi committed 2026-05-27 19:43:15 +08:00
1 parent 8f5af46ec2
commit 8f75e12871
168 files changed
+76575

No files matched your search

+826
View File
@@ -0,0 +1,826 @@
# 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<MainStepKey, StepStatus>;
subStepStatus?: Record<SubStepKey, StepStatus>;
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<MainStepKey, StepStatus>` | ✓ | 三主步骤状态 |
| `subStatus` | `Record<SubStepKey, StepStatus>` | ✓ | 四子阶段状态 |
| `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<string>`)记录已渲染过的节点 ID。每次 `layout.nodes` 变化时,找出新增的 ID,将其加入 `appearingIds` 状态,700ms 后自动移除。
`CompactNode` 接收 `appearing` prop,为 `true` 时:
1. 为 `<g>` 添加 `.sixstep-node-pop` CSS 类(scale 弹出)
2. 在节点中心绘制一个 SVG `<circle>` 并添加 `.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 中的 `<defs>` 为每种边类型各定义一个箭头 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` 和临时 `<a>` 标签,触发下载
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
// 路由定义
<Route path="sixstep-chat" element={<SixStepChatPage />} />
<Route path="/observability" element={<ObservabilityPage />} />
// 导航路径映射(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` 中新增节点构建逻辑