Files
deerflow-code/web-code/components/angentchat/doc.md
T
2026-05-27 19:43:15 +08:00

39 KiB
Raw Blame History

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 接口:

interface PurposeStageData {
  thinking: string;       // 模型思考过程文本(流式填充)
  cards: PurposeCard[];   // 已生成的卡片列表(逐张追加)
  complete: boolean;      // 该阶段是否完全结束
}
// ActorStageData / BehaviorStageData / NarrativeStageData 结构类似

3.4 消息总线:SixStepMessage

这是整个页面最核心的数据结构,一条助手消息包含所有推演状态:

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

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

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:

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,保证对话区最小宽度)。

活跃助手消息派生:

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)

沙箱使用缩小版布局,节点小于全屏版:

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:

// 路由定义
<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:

// 菜单配置(问答中心 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 中新增节点构建逻辑