前端接入 AI 大模型流式接口实践
在现代 Web 开发中,集成人工智能大模型(LLM)已成为提升应用智能化水平的关键手段。传统的 RESTful API 通常返回完整的 JSON 响应,但在处理大模型生成内容时,这种模式会导致用户等待时间过长。采用流式传输(Streaming)技术,允许服务器将生成的文本分块实时推送给客户端,能显著提升用户体验。
本文将基于 Vue.js 框架,结合 @microsoft/fetch-event-source 库,详细讲解如何在前端实现与 AI 大模型的流式对话交互。我们将深入分析代码结构、状态管理、错误处理以及安全性优化,提供一套完整的生产级参考方案。
一、环境准备与依赖安装
在开始编码之前,需要确保项目已配置好基础环境。本项目基于 Vue 3 + Vite 构建。
1.1 核心依赖
除了基础的 Vue 生态外,我们需要引入以下关键库:
- fetch-event-source: 用于支持 POST 请求的 Server-Sent Events (SSE) 或类似流式协议。原生
EventSource 仅支持 GET 请求且无法自定义 Header,因此该库是处理鉴权 POST 流式请求的首选。
- markdown-it: 用于将后端返回的 Markdown 格式文本渲染为 HTML,支持代码高亮、列表等富文本展示。
- axios (可选): 虽然本例使用 fetch,但项目中可能仍保留 axios 用于非流式请求。
安装命令示例:
npm install @microsoft/fetch-event-source markdown-it
1.2 环境变量配置
在 .env 文件中配置后端 API 的基础地址,避免硬编码:
VITE_APP_BASE_API=https://api.yourdomain.com
二、组件结构设计
一个典型的聊天界面包含欢迎区域、消息滚动容器、输入框和发送按钮。我们采用模块化思维设计 Vue 单文件组件(SFC)。
2.1 模板层 (Template)
界面布局应清晰区分用户消息与系统回复。使用 Flexbox 布局实现左右对齐,并预留头像位置。
<template>
<div class="app-container">
<!-- 欢迎引导区 -->
<div class="welcome-message" v-if="messages.length === 0">
<p>您好!我是您的数字助理</p>
<p>我可以为你解答各类问题、生成图片、总结文档等。</p>
<p>有任何需要,请随时对我说~~😉</p>
</div>
<!-- 消息历史容器 -->
<div class="conversation-container">
<div
v-for="(msg, index) in messages"
:key="index"
:class="[msg.type, { 'is-history': msg.isHistory }]"
>
<!-- 用户消息样式 -->
<div class="message-wrapper user-message" v-if="msg.type === 'user-message'">
<div class="avatar-icon user-avatar">
<svg-icon icon-class="user" />
</div>
<div class="message-content">
<div v-html="renderMarkdown(msg.content)"></div>
</div>
</div>
<!-- 系统消息样式 -->
<div class="message-wrapper system-message" v-else>
<div class="message-content">
<div v-html="renderMarkdown(msg.content)"></div>
</div>
<div class="avatar-icon system-avatar">
<svg-icon icon-class="assistant" />
</div>
</div>
</div>
</div>
<!-- 底部输入区 -->
<div class="input-area">
<el-input
v-model="question"
type="textarea"
:rows="3"
placeholder="请输入您的问题..."
:disabled="isSending"
@keyup.enter.ctrl="sendChatMessage"
/>
<el-button
type="primary"
@click="sendChatMessage"
:loading="isSending"
>
{{ isSending ? '生成中...' : '发送' }}
</el-button>
</div>
</div>
</template>
三、核心逻辑实现
3.1 状态管理
使用 Vue 3 的 Composition API (setup) 来管理状态。我们需要维护消息列表、当前输入、加载状态以及用于取消请求的控制信号。
<script setup>
import { ref, reactive, onUnmounted } from 'vue';
import { historyList } from '@/api/system/chat';
import { fetchEventSource } from '@microsoft/fetch-event-source';
import { getToken } from '@/utils/auth';
import { md } from '@/utils/markdownSetup';
const baseURL = import.meta.env.VITE_APP_BASE_API;
const question = ref('');
const messages = ref([]);
const isSending = ref(false);
const controller = new AbortController();
const toolCallId = ref('');
async function getHistoryList() {
try {
const response = await historyList({ pageNum: 1, pageSize: 10 });
if (response.code === 200) {
response.rows.forEach( {
messages..(
{ : , : item., : },
{ : , : item., : }
);
});
}
} (error) {
.(, error);
}
}
3.2 流式请求处理
这是整个功能的核心。利用 fetchEventSource 监听服务端推送的数据块。每次收到数据时,动态更新最后一条系统消息的内容,实现打字机效果。
function sendChatMessage() {
if (!question.value.trim()) return;
controller.abort();
const signal = controller.signal;
isSending.value = true;
messages.value.push({
type: 'user-message',
content: question.value,
timestamp: new Date().toLocaleTimeString()
});
let systemReply = '';
fetchEventSource(`${baseURL}/system/chat/getChat`, {
method: 'POST',
body: JSON.stringify({
question: question.value,
sessionId: toolCallId.value
}),
headers: {
'Authorization': 'Bearer ' + getToken(),
'Content-Type': 'application/json'
},
signal,
onopen(response) {
if (response.ok && response.headers.get('content-type')?.includes()) {
} (response. >= ) {
();
}
},
() {
{
data = .(event.);
content = data.?.?.[]?.?. || ;
callId = data.?.?.[]?.?.;
systemReply += content;
lastSystemIndex = messages..(
m. === && !m. && !m.
);
(lastSystemIndex !== -) {
messages.[lastSystemIndex]. = systemReply;
} {
messages..({
: ,
: systemReply,
: ,
: ().()
});
}
(callId) toolCallId. = callId;
();
} (e) {
.(, e);
}
},
() {
lastSystemIndex = messages..(
m. === && !m. && !m.
);
(lastSystemIndex !== -) {
messages.[lastSystemIndex]. = ;
}
isSending. = ;
question. = ;
},
() {
.(, err);
isSending. = ;
}
});
}
3.3 辅助功能
自动滚动
为了保证用户始终看到最新内容,需要在消息更新后滚动到底部。
function scrollToBottom() {
const container = document.querySelector('.conversation-container');
if (container) {
requestAnimationFrame(() => {
container.scrollTop = container.scrollHeight;
});
}
}
Markdown 渲染安全
直接渲染用户输入或后端返回的 HTML 存在 XSS 风险。务必使用经过白名单过滤的解析器。
const renderMarkdown = (content) => {
return md.render(content);
};
四、样式与交互优化
良好的视觉体验对于聊天应用至关重要。以下是 CSS 样式的核心要点。
<style scoped>
.app-container {
display: flex;
flex-direction: column;
height: 100vh;
padding: 20px;
background-color: #f5f7fa;
}
.conversation-container {
flex: 1;
overflow-y: auto;
padding-bottom: 20px;
}
.message-wrapper {
display: flex;
margin-bottom: 16px;
align-items: flex-start;
}
.user-message {
justify-content: flex-end;
}
.system-message {
justify-content: flex-start;
}
.message-content {
max-width: 70%;
padding: 12px 16px;
border-radius: 8px;
font-size: 14px;
line-height: 1.6;
word-wrap: break-word;
}
.user-message .message-content {
background-color: #409eff;
color: white;
}
.system-message .message-content {
background-color: white;
box-shadow: 0 2px 12px (, , , );
}
{
: ;
: ;
: ;
: flex;
: center;
: center;
: ;
}
{
: flex;
: ;
: ;
: ;
: solid ;
}
{
: ;
: ;
: ;
: auto;
}
</style>
五、常见问题与最佳实践
5.1 请求中断处理
在网络不稳定或用户主动切换问题时,必须清理旧请求。AbortController 是关键。在组件卸载或发起新请求前调用 controller.abort(),防止内存泄漏和数据错乱。
5.2 长文本截断
如果大模型返回内容极长,前端应避免一次性渲染所有节点导致页面卡顿。可以考虑虚拟滚动(Virtual Scroll)技术,但这会增加复杂度。对于一般场景,分批渲染或按需加载是可行的折衷方案。
5.3 鉴权 Token 刷新
流式连接可能持续较长时间,期间 Access Token 可能过期。建议在 onopen 阶段检查 Token 有效性,若过期则先刷新 Token 再重试连接,或在 onerror 中捕获 401 状态码触发重登录流程。
5.4 防抖输入
在用户快速输入时,建议对发送按钮进行防抖处理,或者仅在用户停止输入一定时间后才允许发送,减少无效请求。
六、总结
通过上述步骤,我们成功实现了基于 Vue 的前端 AI 流式对话功能。关键点在于正确使用 fetchEventSource 处理 POST 流式数据,配合 AbortController 管理生命周期,并利用 Markdown 渲染增强可读性。这套方案不仅适用于 AI 助手,也可扩展至实时日志监控、在线协作编辑等场景。
后续可进一步优化之处包括:
- 增加语音合成(TTS)支持,让机器朗读回复。
- 实现多轮对话上下文记忆,提升回答连贯性。
- 引入 WebSocket 替代 HTTP Stream,降低延迟。
开发者在实际落地时,应重点关注安全性与性能平衡,确保服务稳定可靠。