前言:在 LLM(大语言模型)应用开发中,用户体验至关重要。传统“等待-加载-显示”的交互模式早已不能满足用户对即时反馈的期待。本文将带你从零搭建一个基于 Vue3 的前端项目,通过调用 DeepSeek API 实现真正的流式输出(Streaming) ,让用户看到文字逐字生成的“魔法”过程。
🌟 为什么需要流式输出?
想象一下,你向 AI 提问:“讲一个喜羊羊与灰太狼的故事,20字”。
如果系统要等模型完整生成全部内容后再一次性返回,用户可能要等待 2~5 秒——在这段时间里,界面一片空白,用户会怀疑“是不是卡了?”、“请求失败了吗?”
而流式输出则完全不同:
“青青草原上,喜羊羊智斗灰太……”
文字像打字机一样逐字出现,用户立刻获得反馈,感知到“AI 正在思考”,心理等待时间大幅缩短,体验更自然、更“智能”。
这正是现代 AI 应用(如 ChatGPT、Claude、DeepSeek)的标准交互范式。
🛠️ 技术栈与核心思路
- 前端框架:Vue 3(
<script setup>+ref响应式) - API 服务:DeepSeek 官方 Chat Completions 接口
- 关键特性:支持
stream: true的 SSE(Server-Sent Events)流式响应 - 核心挑战:如何解析
data: {...}\n\n格式的流式 chunk,并实时更新 DOM?
💡 代码实现详解
1. 响应式数据定义
const question = ref('讲一个喜羊羊与灰太狼的故事,20字');
const stream = ref(true); // 是否启用流式
const content = ref(""); // AI 回复内容
使用 ref 创建响应式变量,Vue 会自动追踪依赖并在 content.value 变化时更新视图。
2. 调用 DeepSeek API(流式模式)
const askLLM = async () => {
content.value = '思考中...';
const response = await fetch('https://api.deepseek.com/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${import.meta.env.VITE_DEEPSEEK_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: 'deepseek-chat',
stream: stream.value,
messages: [{ role: 'user', content: question.value }]
})
});
🔐 安全提示:API Key 务必通过
VITE_*环境变量注入,避免硬编码泄露。
3. 流式响应解析(核心逻辑)
当 stream: true 时,API 返回的是 二进制流(ReadableStream) ,需手动读取并解析:
const reader = response.body?.getReader();
const decoder = new TextDecoder(); // 将 Uint8Array 转为字符串
let buffer = ''; // 用于拼接不完整的 chunk
while (!done) {
const { value, done: doneReading } = await reader.read();
done = doneReading;
// 解码当前 chunk 并拼接到 buffer
const chunkValue = buffer + decoder.decode(value, { stream: !done });
buffer = '';
// 按行分割,只处理以 "data: " 开头的行
const lines = chunkValue.split('\n').filter(line => line.startsWith('data: '));
for (const line of lines) {
const jsonStr = line.slice(6); // 去掉 "data: "
if (jsonStr === '[DONE]') {
done = true;
break;
}
try {
const data = JSON.parse(jsonStr);
const delta = data.choices[0].delta.content;
if (delta) {
content.value += delta; // 响应式更新!
}
} catch (err) {
// 若 JSON 解析失败(如 chunk 被截断),暂存回 buffer
buffer += `data: ${jsonStr}`;
}
}
}
✅ 关键细节:
- 使用
TextDecoder({ stream: true })处理跨 chunk 的 UTF-8 字符(如 emoji)- 用
buffer缓存不完整的 JSON 行,避免解析错误- 仅提取
delta.content并累加到content.value,实现“打字机”效果
4. 非流式模式(兜底方案)
// 非流式:直接解析完整 JSON
const data = await response.json();
content.value = data.choices[0].message.content;
适合调试或低延迟场景。
🎨 UI 与交互设计
<template>
<div class="container">
<div>
<label>输入:</label>
<input v-model="question" />
<button @click="askLLM">提交</button>
</div>
<div class="output">
<label>Streaming</label>
<input type="checkbox" v-model="stream" />
<div>{{ content }}</div>
</div>
</div>
</template>
- 支持动态切换流式/非流式模式
- 输入框双向绑定
question - 输出区域实时渲染
content
🧠 深度思考:流式输出背后的技术哲学
- 用户体验 > 技术简洁性
流式虽增加前端复杂度,但换来的是用户感知速度的飞跃——这是值得的工程权衡。 - 响应式编程的力量
Vue 的ref让我们无需手动操作 DOM,只需关注“数据如何变化”,框架自动同步视图。 - Web Streams API 的潜力
HTML5 的ReadableStream是处理大文件上传、实时日志、AI 流式输出的基石,值得深入掌握。
🚀 扩展建议
- 添加 loading 动画、复制按钮、历史记录
- 支持多轮对话(维护
messages数组) - 错误处理(网络失败、API 限流)
- 使用
AbortController实现“停止生成”功能
✅ 结语
通过不到 100 行代码,我们就实现了一个具备生产级交互体验的 AI 前端 Demo。这不仅是技术实现,更是对“用户为中心”理念的践行。
好的产品,让用户感觉不到技术的存在,只感受到流畅与智能。