AI 对话与流式界面
AI 对话产品前端在消息流、状态管理、渐进渲染和交互反馈上的核心界面模型。
[!info] related notes
- 所属 MOC: 前端基础 MOC, 前端工程化 MOC
- 相关概念: frontend-streaming-response-patterns, ai-chat-page, ai-product-frontend-reliability, ai-output-uncertainty-and-ux, ai-chat-performance-optimization
- 面试问法: how-to-build-streaming-ai-chat-ui-interview-question, how-to-handle-ai-output-uncertainty-in-ui-interview-question, 同花顺 AI 前端笔试+面试准备
AI 对话与流式界面
AI 产品前端难点,往往不在“把文本显示出来”,而在于如何把生成中的不确定性、长任务状态和流式反馈做成稳定体验。
一句话定义
AI 对话与流式界面,本质是在前端持续接收增量内容的同时,管理消息状态、交互反馈、长文本渲染和异常恢复。
前端通常要处理什么
- 消息列表
- 逐步追加内容
- 生成中、完成、失败、中止等状态
- 重新生成、继续追问、取消请求
- markdown 和 code block 渲染
如果面试官问“你有没有自己对接过大模型官方 API”
这题不能只回答“调过接口”,更稳的答法应该覆盖四层:
- 请求链路怎么建
- 流式数据怎么接
- 前端状态怎么管
- 异常和中止怎么处理
一个更完整的答法可以是:
我对接过大模型 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
}
这样“流式进行中”和“最终失败”不会混在一起。
流式更新时前端通常怎么做
基本思路
- 用户发送消息
- 先插入一条空的 assistant 占位消息
- 收到 chunk 后,持续把内容 append 到这条消息
- 流结束后,把状态切到
success - 如果失败或用户取消,切到
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 对话页不是普通聊天页,它要同时管理流、状态、性能和不确定性。
面试要点
来自 how-to-build-streaming-ai-chat-ui-interview-question 的面试视角整理。
一句话回答
前端通常会把消息状态拆成 generating、success、error、aborted 等阶段,基于 SSE 或流式响应按 chunk 增量更新内容,同时做好取消请求、异常兜底、滚动策略和渲染频率控制。
最稳的回答主线
- 先说流式接收方式
- 再说消息状态机设计
- 最后说异常恢复和性能控制
可以怎么展开
- 请求层:SSE 或 Fetch Streams 持续接收 chunk
- 状态层:区分生成中、完成、失败和中止
- 交互层:支持停止生成、重试、重新生成和继续追问
- 渲染层:合并更新频率,避免每个 token 都触发大面积刷新
常见误区
- 只说用 SSE,不说消息状态和异常恢复
- 忽略滚动策略和长文本渲染成本
- 没有停止生成和中止态处理
可能追问
- 如果流式响应中断了你怎么处理
- 你怎么避免每个 token 都重渲染全页面
- AI 对话页和普通聊天页的区别是什么
最短记忆方式
先管流,再管状态,再管性能和异常恢复。