1805 字
9 分钟
让等待可见:起名场景的流式输出优化实践

功能背景#

用户在小程序里填好宝宝信息(姓氏、性别、出生时间),点击”开始智能起名”,期望快速拿到一组可直接参考、解释清晰的名字建议。

但起名不是简单地”吐出几个字”。为了让建议更可信、更方便用户决策,页面需要展示多个模块:

  • 八字分析与取名方向:先告诉用户整体思路
  • 多个名字建议:通常 5~10 个
  • 每个名字的详细解释:寓意、五行补益、读音搭配、综合评分等

内容越完整,用户越好做决定——但生成时间也越长。

原方案的问题#

优化前的交付方式是”一次性整包返回”:

  1. 用户点击”开始起名”
  2. 页面进入 loading 状态
  3. 后端调用大模型生成完整的结构化 JSON
  4. 等 JSON 全部生成完毕后,一次性返回前端
  5. 页面拿到完整结果后,一次性渲染所有模块

工程上很直观,但用户体验上把整个过程变成了黑盒。

用户的真实感受#

用户不是怕慢,而是怕”什么都看不到的慢”。

在旧流程里,用户只能看到一个”加载中”——没有进展、没有反馈、不知道还要等多久。

这导致了几个典型问题:

  • 怀疑卡住:反复点击按钮,产生重复请求
  • 提前放弃:看不到希望,直接退出
  • 感知放大:即使后端已经在生成,用户也完全不知道

问题本质#

不是内容太多,而是交付方式太”整包”。

打个比方:

  • 旧方案像交作业:必须写完整篇论文才能交
  • 用户体验像等快递:物流不更新,完全不知道进度

我们想要的是:

起名可以像”直播”一样,生成一点就展示一点。

优化目标#

聚焦起名场景,我们定了三个核心目标:

  1. 更早出现有价值的内容:优先展示八字分析,而不是空等
  2. 名字逐条出现:边看边等,不必等全部生成完
  3. 明确的进度反馈:避免用户误以为卡住或已结束

目标不是”让模型变快”,而是让等待从”黑盒”变成”有反馈、有进展”。

方案设计#

整体思路:分段输出 + 渐进渲染#

把起名结果从”单次大返回”改成”多阶段流式输出”:

┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 八字分析 │ ──▶ │ 名字 1 │ ──▶ │ 名字 2 │ ──▶ ...
│ (先输出) │ │ (追加) │ │ (追加) │
└─────────────┘ └─────────────┘ └─────────────┘
↓ ↓ ↓
用户开始阅读 结果在增长 持续有反馈

阶段 1:先输出八字分析#

八字分析是用户最想先看到的”方向性信息”。它一出现,用户就能开始阅读,并对接下来的名字建议建立期待。

阶段 2:名字逐条追加#

名字建议不需要等全部生成完再展示。每生成一条,就在页面上追加一条,让结果持续”增长”。

阶段 3:加载中提示卡片#

当用户看到八字分析和 1~2 个名字后,最容易产生新疑问:

“怎么不动了?是不是卡住了?”

因此我们增加了一个明确的提示模块:

  • 当已出现部分结果、但系统仍在生成时
  • 页面展示”剩余模块正在加载”的卡片
  • 卡片包含进度提示,如”已生成 2/5 个名字,更多内容加载中…”

这张卡片的价值不是”加个转圈”,而是给用户稳定的心理预期:结果还在来,我可以先看已有内容

技术实现要点#

通信协议:SSE(Server-Sent Events)#

我们选择 SSE 而非 WebSocket,原因是:

  • 起名是单向推送场景,不需要双向通信
  • SSE 基于 HTTP,实现简单、兼容性好
  • 自动重连机制,对弱网更友好

基本的数据格式设计:

// 每个 SSE 事件的 payload 结构
interface StreamChunk {
type: 'bazi' | 'name' | 'done' | 'error';
data: BaziAnalysis | NameSuggestion | null;
progress?: {
current: number;
total: number;
};
}

前端状态管理#

使用状态机管理整个流式过程:

type StreamState =
| { status: 'idle' }
| { status: 'loading'; bazi: null; names: [] }
| { status: 'streaming'; bazi: BaziAnalysis; names: NameSuggestion[] }
| { status: 'done'; bazi: BaziAnalysis; names: NameSuggestion[] }
| { status: 'error'; error: string };

核心处理逻辑:

const eventSource = new EventSource('/api/naming/stream');
eventSource.onmessage = (event) => {
const chunk: StreamChunk = JSON.parse(event.data);
switch (chunk.type) {
case 'bazi':
setState(prev => ({ ...prev, bazi: chunk.data }));
break;
case 'name':
setState(prev => ({
...prev,
names: [...prev.names, chunk.data],
progress: chunk.progress
}));
break;
case 'done':
setState(prev => ({ ...prev, status: 'done' }));
eventSource.close();
break;
case 'error':
setState({ status: 'error', error: chunk.data });
eventSource.close();
break;
}
};

渲染优化#

为了让追加动画更流畅,名字卡片使用了简单的入场动画:

.name-card {
animation: fadeInUp 0.3s ease-out;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}

边界情况处理#

网络中断#

  • SSE 自带重连机制,断开后会自动尝试重连
  • 前端记录已接收的 progress.current,重连时带上偏移量
  • 后端支持断点续传,避免重复生成

生成失败#

  • 单个名字生成失败:跳过该条,继续生成下一个,最终告知用户”部分内容生成失败”
  • 整体失败:展示错误提示 + 重试按钮,已展示的内容保留

用户中途离开#

  • 页面隐藏时暂停渲染(使用 visibilitychange 事件)
  • 返回时恢复,避免后台持续消耗资源

优化效果#

指标优化前优化后变化
首屏内容出现时间~8s~2s-75%
用户等待期间跳出率12%4%-67%
重复点击率(误操作)8%1.5%-81%
用户满意度评分3.84.5+18%

数据来源:优化上线后两周的埋点统计

适用场景延伸#

这套”流式输出 + 渐进渲染”的模式,适用于所有内容长、生成慢、可拆分的 AI 场景:

  • AI 报告生成:体检报告、财务分析、竞品调研
  • 长文本创作:营销文案、文章大纲、多轮对话摘要
  • 多步骤任务:表单智能填充、代码生成、翻译校对
  • 搜索增强:RAG 场景下的多源结果聚合

核心判断标准:

  1. 结果是否可以拆成多个独立模块?
  2. 用户是否可以”边看边等”?
  3. 先出现的内容是否有独立价值?

如果三个答案都是”是”,就值得考虑流式输出。

小结#

起名这类”内容长、结构化强”的 AI 能力,真正影响体验的往往不是”最终花多少秒”,而是:

  • 用户在前几秒能不能看到价值
  • 系统有没有把进度讲清楚
  • 页面在生成过程中是不是仍然可用

把起名改成流式展示,本质上是在做一件事:

把不可见的等待,变成可见的进展。


扫码体验#

想亲自感受流式输出的效果?扫描下方二维码,立即体验智能起名功能:

扫码体验智能起名

让等待可见:起名场景的流式输出优化实践
https://www.mihouo.com/posts/front/naming_streaming_article/
作者
发布于
2025-12-21
许可协议
CC BY-NC-SA 4.0