功能背景
用户在小程序里填好宝宝信息(姓氏、性别、出生时间),点击”开始智能起名”,期望快速拿到一组可直接参考、解释清晰的名字建议。
但起名不是简单地”吐出几个字”。为了让建议更可信、更方便用户决策,页面需要展示多个模块:
- 八字分析与取名方向:先告诉用户整体思路
- 多个名字建议:通常 5~10 个
- 每个名字的详细解释:寓意、五行补益、读音搭配、综合评分等
内容越完整,用户越好做决定——但生成时间也越长。
原方案的问题
优化前的交付方式是”一次性整包返回”:
- 用户点击”开始起名”
- 页面进入 loading 状态
- 后端调用大模型生成完整的结构化 JSON
- 等 JSON 全部生成完毕后,一次性返回前端
- 页面拿到完整结果后,一次性渲染所有模块
工程上很直观,但用户体验上把整个过程变成了黑盒。
用户的真实感受
用户不是怕慢,而是怕”什么都看不到的慢”。
在旧流程里,用户只能看到一个”加载中”——没有进展、没有反馈、不知道还要等多久。
这导致了几个典型问题:
- 怀疑卡住:反复点击按钮,产生重复请求
- 提前放弃:看不到希望,直接退出
- 感知放大:即使后端已经在生成,用户也完全不知道
问题本质
不是内容太多,而是交付方式太”整包”。
打个比方:
- 旧方案像交作业:必须写完整篇论文才能交
- 用户体验像等快递:物流不更新,完全不知道进度
我们想要的是:
起名可以像”直播”一样,生成一点就展示一点。
优化目标
聚焦起名场景,我们定了三个核心目标:
- 更早出现有价值的内容:优先展示八字分析,而不是空等
- 名字逐条出现:边看边等,不必等全部生成完
- 明确的进度反馈:避免用户误以为卡住或已结束
目标不是”让模型变快”,而是让等待从”黑盒”变成”有反馈、有进展”。
方案设计
整体思路:分段输出 + 渐进渲染
把起名结果从”单次大返回”改成”多阶段流式输出”:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐│ 八字分析 │ ──▶ │ 名字 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.8 | 4.5 | +18% |
数据来源:优化上线后两周的埋点统计
适用场景延伸
这套”流式输出 + 渐进渲染”的模式,适用于所有内容长、生成慢、可拆分的 AI 场景:
- AI 报告生成:体检报告、财务分析、竞品调研
- 长文本创作:营销文案、文章大纲、多轮对话摘要
- 多步骤任务:表单智能填充、代码生成、翻译校对
- 搜索增强:RAG 场景下的多源结果聚合
核心判断标准:
- 结果是否可以拆成多个独立模块?
- 用户是否可以”边看边等”?
- 先出现的内容是否有独立价值?
如果三个答案都是”是”,就值得考虑流式输出。
小结
起名这类”内容长、结构化强”的 AI 能力,真正影响体验的往往不是”最终花多少秒”,而是:
- 用户在前几秒能不能看到价值
- 系统有没有把进度讲清楚
- 页面在生成过程中是不是仍然可用
把起名改成流式展示,本质上是在做一件事:
把不可见的等待,变成可见的进展。
扫码体验
想亲自感受流式输出的效果?扫描下方二维码,立即体验智能起名功能:
