内嵌聊天界面详解

适用版本:ChengOS v0.1.0+ | 最后核对:2026-08-12 | 来源:chengflow-ui/src/features/chatchengflow-ui/src/features/chat/hooks/useConversationWebSocket.tschengflow-ui/src/features/chat/utils/buildChatContextPayload.tscrates/cheng-api/src/rest/handlers/conversation

编辑器里的对话面板不是硬拼在工作流引擎上的演示控件,而是一等的执行界面:你发一条消息就启动一次工作流运行,而这次运行产生的一切——流式 token、工具调用、智能体迭代、审批请求——都会实时回渲到会话里。

几个界面

同一套对话机件被组合到四个地方:

界面 位置
ChatPanel 完整的对话页,带会话列表
ChatPreviewPanel 画布旁边,用来测试正在编辑的工作流
ChatPreviewPage 独立的预览页
CliChatPanel 镜像到浏览器里的 CLI 会话

它们在如何跟踪工作区、会话与工作流 id 上各不相同——预览面板会在真实会话存在之前先铸一个本地占位会话 id(local-…)——所以共享规则集中放在一个共用工具条里,而不是重复四遍。占位 id 一律当作「没有会话」,绝不会拿一个后端从未见过的 id 去发请求。

一个回合的全过程

你输入 ──► 上下文装配 ──► POST /conversations/:id/messages
                                    │
                              工作流执行启动
                                    │
              WebSocket 事件 ◄──────┘
                    │
     message_created · node_stream_* · tool_execution ·
     agent_iteration_completed · agent_turn_* · approval_requested ·
     message_completed
                    │
                    ▼
             助手气泡就地更新

对话面板通过共享的全局 WebSocket 订阅该会话的事件流。由于用户可能在运行中途切换会话,订阅会跟踪一个 expectedExecutionId,并丢弃属于用户已经离开的那次运行的事件——没有这一层,一次慢执行会把它的 token 漏进当前屏幕上的任何会话里。

如果浏览器在运行中途重连,面板不会干等下一个事件:它会去取执行轨迹快照并合并进来,于是在漫长的智能体回合中途刷新页面,能恢复出已经发生过的工具调用,而不是留下一段空白。

上下文装配

你真正发出去的东西不止输入框里的文字。有一个统一的装配器为所有界面构建载荷,数据来自两处:

  • 显式引用——你主动附加的文档、块和表格记录。
  • 自动上下文——当前打开的文档、其选中或聚焦的块,以及当前表格。

有四条规则支配它,而它们的存在都源于「显然的实现」会引发的 bug:

  1. 自动上下文按需派生,绝不写入 chat store。一旦写进去,切换标签页之后就会留下脏数据。
  2. 显式引用优先于自动选中/聚焦的块。
  3. draft- 开头的文档 id 绝不作为持久化的 documentId 下发给后端。
  4. 装配器只负责构建载荷,不负责发送,也不负责清理引用。发送与清理属于调用方。

产出的载荷携带 finalContent、可选的 documentIdfocusedBlockIds(显式与自动块 id 的并集并去重),以及 tableContext。界面上的上下文指示器会精确显示将要附带什么,因此没有任何东西是悄悄发出去的。

路由控件:快捷指令与预设

输入框上方是两个选择器,它们改变下一个回合的路由方式。

快捷指令把一份结构化路由值附加到下一回合,而不是把裸 JSON 塞进你的消息文本。这个区别很关键:对话历史保持干净,而模型也绝不会把路由控制 JSON 当成自然语言来读。该值必须是 JSON 对象,因为它的主要消费者是 ai/llm_branch,后者按嵌套路径条件路由——要写成 action: { name: search } 而不是扁平的 action.name 键。

两种生命周期:

route_mode 行为
once(默认) 只作用于下一回合,之后自动清除
always 跨回合保持生效,直到收到明确的关闭信号或你取消选择

工具条会监听回合结束,并在模式为 once 时让缓存的活动快捷指令失效——后端已经消费掉它了,所以那个勾必须消失。

预设把一份具名配置补丁应用到当前工作流的某个节点上。人类可以应用任何预设;由大语言模型发起的切换则需要经过审批,且只限于被显式标记为对 LLM 可见的预设。参见预设与快捷指令

此外还有模型选择器和工作流选择器,让你不必离开会话就能改变它的目标。

富消息渲染

助手气泡是若干事件流之上的一个投影,而不是一个字符串:

  • 流式文本node_stream_* 分片到达并实时追加。
  • 工具调用渲染为独立卡片,并按键归并,让一次调用与它稍后的结果合成一条,而不是出现两次。
  • 供应商推理内容与答案正文分开呈现。
  • 智能体的迭代与轮次会被分组,于是十步的智能体运行读起来是一个序列,而不是一堵墙。
  • 审批以内联审批卡出现;智能体评审以评审卡出现。在卡片里做决定即可恢复被挂起的执行——见人工审批与评审
  • 附件与产物会被归一化成媒体附件,因此智能体生成的图片可以内联显示。

回退:修改更早的消息

选中你自己更早的一条消息并编辑它,会把会话回退到那个点。

截断点是所选消息的 sequence_number,且是闭区间——绝不是 sequence_number - 1。持久化会话要求一个真实的数字序号,因此乐观的客户端 id(temp-streaminglocal-msg-)会被拒绝作为回退目标,而不是被近似地回滚。

回退之前,面板会估算影响范围,在会丢弃消息时请求确认,并在该回合含有无法恢复的附件时给出警告。

回退是「回滚 + 重发」,而这条传输路径刻意吞掉自己的失败:「回滚成功但重发失败」是界面必须能报出来的状态,而这只有在发送路径把错误抛上去时才可能。

会话

会话归属于工作区,并在侧边栏列出。后端通过 POST /conversations/resolve 为渠道流量解析或创建会话,编辑器流量则直接创建;消息是持久化的,因此会话能挺过一次刷新,并可从任意界面继续,包括 CLI。

下一步

© 版权声明
THE END
喜欢就支持一下吧
点赞6 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容