跳到主要内容

AI 产品体验

副标题:流式交互、中断续写、失败兜底与可解释

目标:把“不确定的 AI 输出”包装成“稳定、好用、可控”的用户体验。这也是前端在 AI 产品里最稀缺的价值。

目录

体验为什么决定胜负

很多 AI 产品“能力差”,本质不是模型差,而是体验没兜住:

  • 等半天没反应,用户以为卡死
  • 一直输出,用户停不下来
  • 输出一半报错,用户不知道怎么办
  • 答案看起来对,但无法验证来源

前端 + AI 的核心价值是:把模型能力转化为可感知、可控制、可恢复的交互。

流式输出:打字机效果只是起点

流式输出要解决的不是“显示”,而是“控制”

至少要覆盖:

  • 实时反馈:首 token 时间(TTFT)越低越好
  • 节流渲染:避免每个 token 都触发重渲染
  • 增量渲染策略
    • 文本:直接增量拼接 + 节流刷新
    • Markdown:建议“流式阶段先纯文本”,结束后再一次性 Markdown 渲染(或分段渲染)

体验细节(非常加分)

  • 显示“正在思考/正在检索/正在生成”的阶段提示(尤其 RAG)
  • 显示“已生成字数/预计剩余”(可选)
  • 支持“复制本条回答”“复制引用来源”

示例:前端节流渲染(避免每个 token 触发渲染)

let buffer = "";
let lastFlush = 0;
const FLUSH_INTERVAL = 80; // ms

function onDelta(text: string) {
buffer += text;
const now = Date.now();
if (now - lastFlush > FLUSH_INTERVAL) {
flush();
lastFlush = now;
}
}

function flush() {
if (!buffer) return;
// 这里替换成 setState/emit 逻辑
appendToUI(buffer);
buffer = "";
}

经验值:50-120ms 的刷新间隔体感最好,能兼顾“像打字”与“不卡顿”。

中断 / 继续 / 重试:三个按钮背后的状态机

一个实用的聊天消息状态机(建议你按这个设计 UI/数据结构):

  • idle:未开始
  • streaming:正在流式输出
  • stopped:用户主动停止
  • failed:失败(超时/限流/网络/模型错误)
  • done:完成

对应用户操作:

  • 停止:streaming → stopped
  • 继续生成:stopped → streaming(携带“已生成内容”作为上下文继续)
  • 重试:failed → streaming(可带上同一输入、不同策略)

工程要点:

  • 停止一定要“真的停止”:
    • 前端中止请求
    • 服务端也要尽快中止对模型的流式拉取(避免继续烧 Token)
  • 并发控制:
    • 同一会话一次只允许一个 streaming(或给用户明确提示)

图示:消息状态机(Mermaid)

并发与会话策略(一定要写清楚)

  • 同会话单流式:避免“一问多答”的 UI 混乱
  • 跨会话并发上限:例如每用户最多 2 条流式并发
  • 停止/继续的上下文策略
    • 停止时保留已生成内容
    • 继续时把“已生成内容 + 原问题”一起作为上下文

失败兜底:超时、限流、内容不合规、模型不可用

1)超时

用户视角:不要只显示“错误”,要告诉他能做什么:

  • “已生成内容保留”
  • “你可以:继续生成 / 重新生成 / 缩短问题 / 稍后再试”

UI 文案示例(可直接用)

  • 标题:生成超时
  • 正文:已保留当前内容。你可以点击“继续生成”,或缩短问题再试。

2)限流/配额不足

这是 AI 产品最常见失败之一,建议在 UI 上做出“可理解”的提示:

  • 是“并发太多”还是“今日额度用完”
  • 引导升级/稍后再试/减少输出长度

UI 文案示例

  • 标题:请求过于频繁
  • 正文:当前并发已满,请稍后重试,或先结束其他对话。

3)内容不合规

如果你接入的是有内容安全策略的模型:

  • 明确告诉用户“哪些部分不支持”
  • 给出可替代建议(比如“我可以改成科普/非敏感表达”)

UI 文案示例

  • 标题:内容不符合使用规范
  • 正文:我无法处理该请求,但可以帮你改为科普/非敏感的表达方式。

4)模型不可用

做“多模型降级”:

  • 主模型失败 → 备用模型(能力可能弱,但可用)
  • UI 显示“已自动切换模型”(透明且可控)

UI 文案示例

  • 标题:模型暂不可用
  • 正文:已自动切换到备用模型,结果可能略有差异。

可解释与可追溯:引用来源、证据片段与操作日志

对用户来说,“答案对不对”有时很难判断;但“证据从哪来”更容易判断。

你可以在 UI 上提供:

  • 引用来源:每段回答对应哪些文档片段
  • 证据片段:可展开查看 chunk 内容
  • 定位到原文:如果有页码/段落定位就更好

对工程来说(排障与审计):

  • 每次回答都带 traceId
  • 记录:检索到的 docId/chunkId、模型参数、耗时、错误码(脱敏)

透明化设计:让用户“看到过程”

  • 检索型回答展示“证据来源列表”
  • 工具型回答展示“执行步骤(可折叠)”
  • 失败时显示“错误码 + traceId”,方便反馈

Prompt 可视化与可配置:把“魔法”变成“产品能力”

不要把 Prompt 写死在代码里(尤其是产品化后),建议提供:

  • Prompt 模板选择:不同任务不同模板
  • 关键参数可调:语气、长度、是否要引用、输出格式
  • 版本管理:改 Prompt 就像改配置,能回滚

用户体验层可以这样设计:

  • “高级设置”抽屉:展示关键参数
  • “提示词预览”只读区域:让用户知道系统在做什么(增强信任)

最小可视化组件清单

  • 模型选择器(主/备模型)
  • 稳定性滑杆(温度映射)
  • 输出长度选项(短/中/长)
  • 是否引用来源(RAG 开关)

最小可运行 Demo(体验版)

目标:做出一个“能流式+能停止+有失败兜底”的最小交互体验

最小可运行代码(UI 状态机 + 取消)

type State = "idle" | "streaming" | "stopped" | "failed" | "done";
let state: State = "idle";

function send() {
state = "streaming";
// start stream...
}

function stop() {
// abort stream...
state = "stopped";
}

function retry() {
state = "streaming";
}

完整 Demo 结构(最小体验骨架)

demo-ai-ux/
src/
App.tsx
ChatMessage.tsx
useStream.ts
uiStates.ts

常见坑排查清单

  • 状态错乱:多次点击“发送/停止”无防抖 → 加按钮禁用/节流
  • Markdown 卡顿:流式阶段先纯文本,结束后再渲染
  • 兜底文案缺失:只显示“错误” → 加可操作提示

完整可运行代码(流式体验示例)

源码目录:docs/demos/ai-chat-demo/web

cd docs/demos/ai-chat-demo/web
npm i
npm run dev