← [ BACK_TO_NOTES ]

AI 对话与流式界面

面向 AI 对话产品的流式 UI 工程综合笔记,聚焦 transport、消息状态机、渐进渲染、取消恢复与性能边界;不承担通用 Chat UI 概念定义。

#frontend #synthesis #growing

[!info] related notes

AI 对话与流式界面

AI 产品前端难点,往往不在“把文本显示出来”,而在于如何把生成中的不确定性、长任务状态和流式反馈做成稳定体验。

一句话定义

AI 对话与流式界面,本质是在前端持续接收增量内容的同时,管理消息状态、交互反馈、长文本渲染和异常恢复。

与 Chat UI 的边界

  • Chat UI 是上位概念:回答“Agent 对话界面需要具备哪些交互能力”。
  • 本页是工程综合层:回答“增量事件进入前端后,transport、message state、rendering、cancel/recovery 如何协作”。
  • AI 聊天 UI 设计与组件选型 负责把这些需求映射到组件与框架方案。
  • assistant-ui 是其中一个具体资源/实现,不应反向成为本页的概念别名。

因此判断一段内容应该放哪页时,可以问:它是在定义界面能力,还是在解释流式运行时语义? 后者才属于本页。

前端通常要处理什么

  • 消息列表
  • 逐步追加内容
  • 生成中、完成、失败、中止等状态
  • 重新生成、继续追问、取消请求
  • markdown 和 code block 渲染

端到端链路

一次流式 AI 回复通常可以拆成:

提交用户消息
→ 创建 assistant 占位状态
→ 建立流式 transport
→ 持续接收并解析增量事件
→ 合并到当前消息 projection
→ 收到 durable terminal / 完成信号
→ 落盘或刷新最终状态

这里真正需要前端维护的是 transport、消息状态、渲染和恢复之间的协作,而不是“能不能把 chunk 拼起来”。

Transport 只是其中一层

SSE、Fetch Streams 与 WebSocket 的选型统一看 前端流式响应模式。

本页只保留一个原则:

Transport 负责把增量事件送到前端;消息状态机、恢复、取消语义和渲染节奏属于更上层 UI Runtime。

一个常见状态模型

  • idle
  • generating
  • success
  • error
  • aborted

一个更完整的消息状态设计

对 AI 聊天前端来说,光有“请求成功 / 失败”是不够的,通常还要区分:

  • 消息级状态:生成中、完成、失败、中止
  • 请求级状态:发送中、接收中、结束、超时
  • 展示级状态:是否在流式追加、是否需要滚动到底部、是否渲染完成

一个常见做法是:

type MessageStatus = 'generating' | 'success' | 'error' | 'aborted'

type ChatMessage = {
  id: string
  role: 'user' | 'assistant'
  content: string
  status: MessageStatus
  errorMessage?: string
}

这样“流式进行中”和“最终失败”不会混在一起。

流式更新时前端通常怎么做

基本思路

  1. 用户发送消息
  2. 先插入一条空的 assistant 占位消息
  3. 收到 chunk 后,持续把内容 append 到这条消息
  4. 流结束后,把状态切到 success
  5. 如果失败或用户取消,切到 error 或 aborted

为什么要先插占位消息

  • UI 不会等到首个 chunk 才突然出现 assistant 消息
  • 更方便统一处理中止、失败、重试
  • 更适合做“生成中”骨架和按钮状态

一个最小伪代码

async function sendMessage(input: string) {
  insertUserMessage(input)

  const assistantId = insertAssistantPlaceholder()

  try {
    updateMessageStatus(assistantId, 'generating')

    for await (const chunk of streamResponse(input)) {
      appendMessageContent(assistantId, chunk)
    }

    updateMessageStatus(assistantId, 'success')
  } catch (error) {
    updateMessageStatus(assistantId, 'error')
  }
}

面试里很容易被追问的工程点

1. 如何取消生成

  • 前端用 AbortController 取消请求
  • 后端感知 req.on('close') 停止模型流
  • 把消息状态切到 aborted,保留已生成内容
  • 流式回调中检查消息状态,丢弃过期 chunk

详见 SSE 流式输出的暂停与中断。

2. 如何处理重试

  • 保留原始用户输入
  • 新开一次 assistant 生成
  • 或者基于上一轮失败消息做“重新生成”

3. 如何避免高频重渲染

  • 不要每个 token 都触发整页大范围更新
  • 可以按 chunk、按时间片,或者按可见区域合并更新
  • 长列表场景要关注滚动和虚拟化

4. 如何处理异常

  • 网络断开
  • 服务端中途结束
  • chunk 解析失败
  • 重复内容或乱序内容

这题真正想考什么

  • 你是否真的做过流式输出
  • 你是否理解前端不只是“把字显示出来”
  • 你有没有考虑状态、性能、异常、中止、重试和体验一致性

为什么它比普通消息列表更难

  • 内容是增量到达的
  • 渲染频率更高
  • 用户会关注首 token 时间和完整回答时长
  • 失败和中断体验比普通接口更敏感

常见误区

  • 每个 token 到来都直接触发大面积渲染
  • 没有取消、重试和异常兜底
  • 只关注展示,不关注可信性和可解释性

最短记忆方式

AI 对话页不是普通聊天页,它要同时管理流、状态、性能和不确定性。

学习检查

不看正文回答:

  1. AI 对话页为什么比普通“提交表单 → 等 JSON”多出一个持续运行状态?
  2. transport、event parser、message reducer、rendering 分别应该承担什么职责?
  3. 为什么开始请求前先创建 assistant 占位消息有利于状态一致性?
  4. generating、done、error、aborted/cancelled 为什么不应只靠一个 loading boolean 表达?
  5. 高频 token/chunk 更新为什么可能造成渲染抖动?有哪些合并更新策略?
  6. 网络连接中断与服务端 Run 终止为什么不是同一个事实?
  7. Retry / Regenerate / Continue 分别应该基于什么持久状态,而不是盲目重复原请求?
  8. 给一个“流式输出到一半断网”的案例,画出 UI 状态、transport 和 durable recovery 的恢复路径。
创建于 2026/3/19 更新于 2026/10/1
Digital Biome
Access

键盘导航

j / k / ↑ / ↓
选择对象
Enter / o
打开选择
/ 或 Ctrl+K
全站搜索
i / f / 1–4
筛选当前列表
g → h / l / p / t / a
首页 / 笔记 / 项目 / 工具 / About
r → 1–9
打开所选对象的关联
u
返回上一页
Esc
关闭浮层 / 取消命令;输入框内清空文本