AI 对话与流式界面

AI 对话产品前端在消息流、状态管理、渐进渲染和交互反馈上的核心界面模型。

#tech / dev / frontend #type / synthesis #status / growing

[!info] related notes

AI 对话与流式界面

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

一句话定义

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

前端通常要处理什么

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

如果面试官问“你有没有自己对接过大模型官方 API”

这题不能只回答“调过接口”,更稳的答法应该覆盖四层:

  1. 请求链路怎么建
  2. 流式数据怎么接
  3. 前端状态怎么管
  4. 异常和中止怎么处理

一个更完整的答法可以是:

我对接过大模型 API 的流式输出接口,前端一般会把一次对话拆成“用户消息提交、占位 assistant 消息创建、增量 chunk 追加、完成态落盘、失败/中止兜底”几个阶段。请求层通常用 SSE 或 Fetch Streams 接收增量内容,状态层会区分 generating、success、error、aborted,并支持取消、重试、重新生成。除了把内容显示出来,我还会关注首 token 时间、流中断、重复 chunk、滚动策略和 Markdown/code block 渲染性能。

常见接法:SSE / Fetch Streams / WebSocket

SSE

  • 服务端单向推送文本流
  • 协议简单
  • 很适合 AI 文本生成这类“服务端持续往前端吐增量”的场景

Fetch Streams

  • 通过 fetch() + ReadableStream 读取 chunk
  • 灵活度更高
  • 前后端都更容易和普通 HTTP 体系统一

WebSocket

  • 更适合强双向实时交互
  • 纯文本生成场景通常不是第一选择

面试里一句话怎么选

单向生成流优先考虑 SSE 或 Fetch Streams;只有强双向实时协作时才更偏向 WebSocket。

一个常见状态模型

  • 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. 如果失败或用户取消,切到 erroraborted

为什么要先插占位消息

  • 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 对话页不是普通聊天页,它要同时管理流、状态、性能和不确定性。

面试要点

来自 how-to-build-streaming-ai-chat-ui-interview-question 的面试视角整理。

一句话回答

前端通常会把消息状态拆成 generating、success、error、aborted 等阶段,基于 SSE 或流式响应按 chunk 增量更新内容,同时做好取消请求、异常兜底、滚动策略和渲染频率控制。

最稳的回答主线

  • 先说流式接收方式
  • 再说消息状态机设计
  • 最后说异常恢复和性能控制

可以怎么展开

  • 请求层:SSE 或 Fetch Streams 持续接收 chunk
  • 状态层:区分生成中、完成、失败和中止
  • 交互层:支持停止生成、重试、重新生成和继续追问
  • 渲染层:合并更新频率,避免每个 token 都触发大面积刷新

常见误区

  • 只说用 SSE,不说消息状态和异常恢复
  • 忽略滚动策略和长文本渲染成本
  • 没有停止生成和中止态处理

可能追问

  • 如果流式响应中断了你怎么处理
  • 你怎么避免每个 token 都重渲染全页面
  • AI 对话页和普通聊天页的区别是什么

最短记忆方式

先管流,再管状态,再管性能和异常恢复。

创建于 2026/3/19 更新于 2026/5/27