Skip to content

AI 消息渲染方案 v2 设计(ReAct 与 Plan-and-Act 统一) ​

description: 重构回复组渲染模型——执行过程区与最终结果区两个互斥区域,结果确认前一切消息只渲染在过程区内,消除串流/错位/区外悬挂,并支持每轮思考过程独立折叠

created: 2026-10-13 19:12:11

updated: 2026-10-13 19:36:13

author:


1. 设计目标与硬约束 ​

范围:frontend/src/components/AiChatPanel.vue 回复组(group)渲染层,即折叠区(执行过程区)、最终结果区、任务耗时行、每轮思考过程折叠的展示逻辑与状态机。 边界:不改动消息持久化结构与 SSE 流协议;不改动 ReAct 循环与 Plan-and-Act 执行编排(ai-react-loop-design.md、ai-plan-and-act-design.md);不改动授权弹窗、计划面板、变更文件面板的交互。 关联代码:frontend/src/components/AiChatPanel.vue、frontend/src/lib/ai.ts、frontend/src/stores/session.ts

目标量化口径
不串流任意时刻同一段文本最多渲染在一个区域;不存在同一正文在折叠区内外同显的帧
不错位折叠区内条目按消息顺序唯一确定槽位,无"末条即结果"的动态归属规则
顺序不乱轮次严格按消息序渲染;结果区永远位于折叠区之后
禁止区外悬挂结果确认前,最终结果区恒为空,组内没有任何消息渲染在折叠区外;"区外有待定结果 + 区内持续追加"状态不可能出现
过程长度可控每轮思考过程独立折叠,自动态下过程区可视高度只保留正文与工具卡片
兼容旧数据旧会话消息(无 stepIndex 标记)重载后渲染完整,仅缺步骤分隔条

2. 必须遵守的既有项目约束 ​

  • AI 消息不使用气泡样式,固定从上到下:项目 Logo + Agent 标识 → 可折叠思考过程 → 最终回答内容(既有用户决策);
  • 推理可视化状态操作统一使用 Map 直接 mutate 更新,手动展开/收起优先于自动逻辑(isTraceOpen 的 manual 优先原则沿用);
  • 工具调用提案卡片与后续自动续跑回复合并为单条消息视觉,不重复显示 Logo;
  • 任务运行期间输入区右侧显示实时耗时计时器(hh:mm:ss),任务耗时行同时承担过程区折叠开关职责;
  • 切换 scope 或清空对话时统一清理手动状态记录;
  • 消息随会话持久化,渲染层必须可由消息列表纯推导,重载后结果一致。

3. 现状盘点与差距 ​

位置现状差距
折叠区收集(groupTrace)扁平条目流:全部 reasoning + 工具提案 + 非末条 content;组末条 content 留在区外作"待定结果"流式期间区外常驻待定正文,若该轮产生工具调用,正文需随 groupEnd 后移"落入"区内,存在动态归属导致的错位风险
自动展开/收起(isTraceOpen)任务活跃(groupActive)期间全程展开,落定时收起最终结果流出时过程区仍展开,收起跳动发生在用户开始读结果时;单轮"content 到达即收起"与多步"全部结束才收起"语义不一致
思考过程折叠仅整体折叠区一层 + 末条 reasoning 单独折叠中间轮 reasoning 全部平铺在过程区内,长任务过程区高度失控
Plan 步骤标识步骤边界仅靠计划面板状态展示,过程区内无步骤分隔用户无法在过程区内定位"这条思考属于哪个步骤"

结论:核心差距是"最终结果"的归属规则——现状为动态归属(当前末条正文即结果),v2 改为确认式归属(确认是最后一轮才晋升到结果区),从结构上消除区外悬挂与串流窗口。

4. 目标与非目标 ​

目标 ​

  1. 回复组只渲染两个固定区域:执行过程区(折叠区)与最终结果区,职责互斥;
  2. 结果确认前结果区恒空,全部组消息(含流式中的当前轮)渲染在折叠区内;
  3. 最终结果晋升规则统一:ReAct/单轮在流结束且无工具调用时原子晋升;Plan 总结轮首轮 token 即确认,直接流入结果区;
  4. 折叠区按轮次分组渲染,每轮思考过程独立折叠(两级状态机);
  5. Plan 模式在过程区内显示步骤分隔条(步骤 i/n · title)。

非目标 ​

  • 不实现虚拟滚动/长列表性能优化(条目数量级为单任务十轮以内);
  • 不改动后端 ai-chat-delta 等 SSE 事件协议;
  • 不实现折叠状态的持久化(手动状态为会话内存态);
  • 不处理用户消息的渲染变化(用户消息不进组,不受影响)。

5. 总体架构 ​

text
回复组(一条 user 消息触发的全部 assistant 消息)
│
├── Logo + Agent 标识 + 任务耗时行(折叠开关)
│
├── 执行过程区(折叠区)                    ← 任务期间内容
│   ├── 轮次 1
│   │   ├── 思考过程 ▸/▾(独立折叠)
│   │   ├── 正文(Markdown)
│   │   └── 工具卡片(提案 + 状态徽标)
│   ├── [Plan] 步骤分隔条:步骤 i/n · title
│   ├── 轮次 2 …
│   └── 当前轮(流式中):思考中… 脉冲 / 正文实时流出
│
└── 最终结果区                              ← 确认后内容(任务期间恒空)

数据流不变:SSE 逐条追加 AiChatMessage,渲染层每次由消息列表实时推导分组与槽位,折叠区不持有独立存储。

6. 详细设计 ​

6.1 轮次与槽位推导 ​

  • 轮次 = 一条 assistant 消息(ReAct 轮 / 计划步骤轮 / 续跑轮 / 总结轮),严格按消息顺序;
  • 每轮槽位固定三段:reasoning → 思考条目,toolProposals → 工具卡片,content → 正文;
  • 槽位由消息在 [groupStart, groupEnd] 区间的下标唯一确定,与是否流式无关。

6.2 最终结果确认与晋升规则(统一一条) ​

某轮正文成为最终结果,当且仅当它被确认是最后一轮;确认前它只渲染在折叠区本轮槽位内。

场景确认时机晋升方式
ReAct / 单轮ai-done 且本轮无工具调用提案正文原子晋升至结果区(文本已完整,无流式中途搬迁),折叠区同时收起
Plan 总结轮由 planSummaryRef 标记、无工具调用,首轮 token 即确认直接流入结果区,不搬迁;折叠区在总结 content 开始时收起
Plan 各步骤末轮永不确认(非组末轮)留在折叠区作为该步正文
中止 / 步骤失败落定时末轮已有正文部分正文晋升;无正文则结果区为空(计划各步状态与结果只在规划面板呈现,不在消息回复区拼装步骤列表)
计划 cancelled落定续跑轮正文晋升;无正文则结果区为空(同上)

6.3 两级折叠状态机 ​

整体折叠区(沿用 traceManual,manual 优先):

状态自动态
任务活跃 && 结果区尚未出现内容展开
结果区一出现内容收起(仅一次)
中止 / 失败 / cancelled落定时收起

每轮思考过程(新增 reasoningManual: Map<msgId, boolean>,manual 优先,scope 切换/清空对话时清理):

状态自动态
reasoning 流式中且该轮正文未到达展开 + 「思考中…」脉冲
该轮正文开始到达自动收起(正文优先)
轮次完成保持收起

即单轮"思考展开、说话收起"语义推广到每一轮;用户手动切换后进入手动模式,不受自动逻辑覆盖。

6.4 Plan 步骤分隔条 ​

  • 给计划流推送的消息新增可选字段 stepIndex(随消息持久化,旧数据无此字段);
  • 渲染时相邻消息 stepIndex 发生变化则在轮次前插入分隔条:步骤 i/n · title;
  • 旧数据无标记 → 不插入分隔条,退化为一层轮次列表,其余行为一致。

6.5 已知取舍与晋升滚动兜底 ​

ReAct/单轮的最终回答在流式期间显示在折叠区内、流结束后晋升——晋升瞬间折叠区收起、结果区出现,文本位置上移一次(折叠区高度)。这是"确认前不外显"约束的直接代价,用晋升后的视口补偿缓解,且必须复用现有「用户上翻则暂停自动滚动」守卫,不劫持用户视口:

视口状态晋升后行为
用户未手动上翻(视口贴近最新消息)平滑滚动至结果区顶部,补偿上移
用户已向上翻看历史过程禁止自动滚动,原地渲染,保持用户当前阅读位置

守卫判定沿用现有流式滚动的同一口径(用户滚动位置距底部超过阈值即视为上翻中),晋升滚动与流式滚动共享该守卫状态,不引入第二套判定。阈值取 80px——覆盖 2~3 行正文增量,滚动条微抖不误判,上翻半屏必命中;守卫仅约束任务驱动滚动(流式 token 跟随、晋升补偿),用户主动动作(发送、切会话、确认计划)仍无条件回底。

7. 安全与降级 ​

  • 纯前端渲染层改动,无新增网络、文件、命令执行路径,无安全面变化;
  • 降级路径:stepIndex 缺失 → 无步骤分隔条;reasoningManual Map 异常缺失 → 退化为始终展开;planSummaryRef 未命中(异常中断)→ 退化为落定时晋升,行为与 v1 一致;
  • 晋升操作只移动渲染归属,不修改消息数据,重载后按同一规则重新推导,保证持久化一致。

8. 测试与验证 ​

项验证方式
不串流/不错位多轮 ReAct 对话(含工具调用 ≥ 2 轮)全程观察:任一正文不出现在两处;轮次顺序与消息顺序一致
结果区时序单轮对话流式期间正文在折叠区内流出,ai-done 后一次性晋升,过程区收起
Plan 全流程多步骤计划执行:步骤分隔条正确;总结轮 content 到达时折叠区收起、结果直接流入结果区
各状态收敛步骤执行中中止 / 步骤失败终止 / cancelled / 总结轮中中止——四类场景结果区均被填充且折叠区收起
手动优先任务期间手动折叠过程区或某轮思考后,自动逻辑不再覆盖
晋升滚动守卫未上翻时晋升后平滑滚动至结果区顶部;先上翻查看历史再等晋升完成,视口保持原位不跳动
旧数据兼容打开 v1 时期的历史会话,渲染完整无报错,仅无步骤分隔条
回归npm run build(frontend/)通过;删除消息、重新生成、编辑重发、多模态附件、授权弹窗交互不回归

9. 未决问题 ​

  • 每轮思考条目是否需要像整体折叠区一样在条目头部展示耗时标签(现有末条 reasoning 已有),待实现时统一。