Files
Genarrative/docs/technical/CREATION_AGENT_STREAMING_MESSAGE_STABILITY_FIX_2026-04-23.md
kdletters cbc27bad4a
Some checks failed
CI / verify (push) Has been cancelled
init with react+axum+spacetimedb
2026-04-26 18:06:23 +08:00

3.8 KiB
Raw Blame History

创作 Agent 流式消息与草稿切换稳定性修复

日期:2026-04-23

1. 背景

统一创作工作区已经承载 RPG 世界共创、大鱼吃小鱼和拼图等 Agent 对话。当前 RPG 世界共创在本地联调中暴露出以下前端状态抖动:

  1. AI 流式回复过程中,中文内容会先出现乱码,随后又被正常文本覆盖。
  2. 玩家刚发送的消息会在聊天列表中短暂出现,随后消失又重新出现。
  3. AI 回复会短暂插在玩家消息中间,之后又跳回底部。
  4. 曾经打开过某个草稿后,再打开另一个草稿或创建新对话时,结果页可能在旧草稿和当前内容之间来回闪烁。

这些现象的共同原因不是单个滚动动作,而是同一 UI 区域同时被多套不同来源的状态驱动本地乐观消息、SSE 临时回复、服务端最终 session 快照、旧草稿结果页缓存和异步恢复结果。

2. 目标

本轮修复只收敛前端展示稳定性,不改变后端业务语义:

  1. 聊天列表只展示一条稳定的玩家消息,不因最终 session 回写而闪消。
  2. AI 流式回复始终作为当前尾部 assistant 消息呈现,不和正式消息互相插队。
  3. SSE 中文文本按 UTF-8 流式边界安全解码,流结束时刷新解码器尾部缓存。
  4. 草稿切换、打开已有草稿、新建对话时先清理旧结果页缓存,旧异步恢复结果不得覆盖当前视图。
  5. 继续保留“用户主动上滑后不强制滚到底部”的聊天区滚动策略。

3. 设计

3.1 SSE 事件读取

src/services/creation-agent/creationAgentSse.ts 继续作为统一 SSE 读取器,但需要补齐以下边界:

  1. 使用 UTF-8 TextDecoder 的 streaming 模式接收 chunk。
  2. reader.read() 结束后调用 decoder.decode() 刷新尾部缓冲,避免多字节中文字符残留在解码器内部。
  3. 事件分隔同时兼容 \n\n\r\n\r\n
  4. reply_deltatext 字段按“当前可展示文本”传给 UI不在读取器内追加避免累计文本和增量文本语义混用。

3.2 玩家消息展示

RPG Agent 发送消息时,本地乐观玩家消息仍保留,但最终 session 回写时必须做稳定合并:

  1. 若服务端快照已包含同一个 clientMessageId,以服务端消息为准。
  2. 若服务端快照暂未包含该消息,临时保留本地消息,直到后续快照补齐。
  3. 合并只按消息 id 去重,不整包丢弃本地尾部消息。

3.3 AI 流式回复展示

统一聊天工作区不再把流式回复作为独立于列表之外的气泡随意附加,而是在展示消息数组中合成一个稳定的尾部临时 assistant 消息:

  1. session 正式消息仍是基础列表。
  2. 有流式文本时,追加或替换尾部临时 assistant 消息。
  3. 最终 session 到来后,临时消息消失,由正式 assistant 消息接管同一视觉位置。
  4. 推荐回复只挂在正式最后一条 assistant 消息上,流式临时消息不展示推荐回复。

3.4 草稿切换

打开已有草稿、打开 Agent 草稿、新建 RPG Agent 对话前,必须先清理旧结果页相关缓存:

  1. generatedCustomWorldProfile
  2. customWorldGenerationViewSource
  3. customWorldResultViewSource
  4. 自动保存状态

异步读取 session 时要以本次打开的 sessionId 作为准入条件,防止上一个草稿的慢响应覆盖当前草稿。

4. 验收标准

  1. 玩家输入发送后在聊天列表中只稳定出现一次,不再闪消。
  2. AI 流式回复只在底部连续更新,不插入玩家消息中间。
  3. 中文流式回复不再出现先乱码后正常的过渡。
  4. 从一个草稿切换到另一个草稿或新建对话时,不再短暂显示旧草稿结果页。
  5. 用户手动上滑聊天区后,流式更新仍不强制抢回底部。