跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
JavaScriptAI大前端java

前端接入 AI 大模型流式接口实践

基于 Vue.js 和 fetch-event-source 库实现 AI 大模型流式对话接口的技术方案。通过模拟后端 SSE 或 Server-Sent Events 的 POST 请求方式,前端能够实时接收并渲染大模型生成的文本内容。核心逻辑包括使用 AbortController 管理请求生命周期、维护消息列表状态以实现历史对话展示、以及利用 Markdown 解析器增强富文本显示效果。此外,文章还涵盖了错误处理、自动滚动优化及安全性注意事项,为开发者提供了一套完整的流式交互前端实现参考。

花里胡哨发布于 2025/2/6更新于 2026/10/20101 浏览
前端接入 AI 大模型流式接口实践

前端接入 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(item => {
        messages.value.push(
          { type: 'user-message', content: item.question, isHistory: true },
          { type: 'system-message', content: item.answer, isHistory: true }
        );
      });
    }
  } catch (error) {
    console.error('Failed to load history:', 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('text/event-stream')) {
        // 连接建立成功
      } else if (response.status >= 400) {
        throw new Error(`Request failed with status ${response.status}`);
      }
    },
    onmessage(event) {
      try {
        const data = JSON.parse(event.data);
        // 假设后端返回结构为 { output: { choices: [{ message: { content: "...", toolCallId: "..." }] } } }
        const content = data.output?.choices?.[0]?.message?.content || '';
        const callId = data.output?.choices?.[0]?.message?.toolCallId;

        systemReply += content;
        
        // 查找当前正在生成的系统消息并更新
        const lastSystemIndex = messages.value.lastIndexOf(
          m => m.type === 'system-message' && !m.complete && !m.isHistory
        );
        
        if (lastSystemIndex !== -1) {
          messages.value[lastSystemIndex].content = systemReply;
        } else {
          messages.value.push({
            type: 'system-message',
            content: systemReply,
            complete: false,
            timestamp: new Date().toLocaleTimeString()
          });
        }

        if (callId) toolCallId.value = callId;
        scrollToBottom();
      } catch (e) {
        console.warn('Parse error:', e);
      }
    },
    onclose() {
      // 标记消息完成
      const lastSystemIndex = messages.value.lastIndexOf(
        m => m.type === 'system-message' && !m.complete && !m.isHistory
      );
      if (lastSystemIndex !== -1) {
        messages.value[lastSystemIndex].complete = true;
      }
      isSending.value = false;
      question.value = '';
    },
    onerror(err) {
      console.error('Stream error:', err);
      isSending.value = false;
      // 这里可以添加重试逻辑或提示用户
    }
  });
}

3.3 辅助功能

自动滚动

为了保证用户始终看到最新内容,需要在消息更新后滚动到底部。

function scrollToBottom() {
  const container = document.querySelector('.conversation-container');
  if (container) {
    // 使用 requestAnimationFrame 确保 DOM 更新后再滚动
    requestAnimationFrame(() => {
      container.scrollTop = container.scrollHeight;
    });
  }
}
Markdown 渲染安全

直接渲染用户输入或后端返回的 HTML 存在 XSS 风险。务必使用经过白名单过滤的解析器。

const renderMarkdown = (content) => {
  // 确保 md 实例已配置安全选项,如禁用 script 标签
  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 rgba(0, 0, 0, 0.05);
}

.avatar-icon {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0 10px;
}

.input-area {
  display: flex;
  gap: 10px;
  margin-top: 20px;
  padding-top: 10px;
  border-top: 1px solid #eee;
}

/* 代码块样式 */
.hljs {
  background: #2d2d2d;
  padding: 10px;
  border-radius: 4px;
  overflow-x: 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 助手,也可扩展至实时日志监控、在线协作编辑等场景。

后续可进一步优化之处包括:

  1. 增加语音合成(TTS)支持,让机器朗读回复。
  2. 实现多轮对话上下文记忆,提升回答连贯性。
  3. 引入 WebSocket 替代 HTTP Stream,降低延迟。

开发者在实际落地时,应重点关注安全性与性能平衡,确保服务稳定可靠。

目录

  1. 前端接入 AI 大模型流式接口实践
  2. 一、环境准备与依赖安装
  3. 1.1 核心依赖
  4. 1.2 环境变量配置
  5. 二、组件结构设计
  6. 2.1 模板层 (Template)
  7. 三、核心逻辑实现
  8. 3.1 状态管理
  9. 3.2 流式请求处理
  10. 3.3 辅助功能
  11. 自动滚动
  12. Markdown 渲染安全
  13. 四、样式与交互优化
  14. 五、常见问题与最佳实践
  15. 5.1 请求中断处理
  16. 5.2 长文本截断
  17. 5.3 鉴权 Token 刷新
  18. 5.4 防抖输入
  19. 六、总结

更多推荐文章

查看全部
  • C++20 模块用户视角下的最佳实践
  • Python 调用大模型(LLM)的四种方式及对比分析
  • Pi0 机器人大模型在昇腾 A2 上的部署与性能测评
  • 使用 Python 调用高德地图 MCP 服务查询天气
  • 视觉语言模型(VLM)综述:从原理到训练与评估指南
  • Anaconda 安装与环境配置教程
  • 基于 Python 与开源 AI 构建本地智能问答系统
  • 2026 年 AI 论文写作工具盘点:全流程生成与降重指南
  • 大模型应用元年,有哪些场景可以实际落地?
  • C 语言指针与数组的深度关联及实战应用
  • Kimi Code CLI 实战:新一代 AI 编程助手深度解析
  • 提示词工程(Prompt Engineering)全面指南
  • OpenClaw 赋能具身智能,开源机器人生态迎来新突破
  • 利用 AI 与轻量化工具优化开发团队协同流程
  • LLaMA-Factory 微调多模态大模型 Qwen3-VL
  • 程序员式浪漫:代码示爱、专属推送与网页游戏案例
  • OpenClaw QQ 机器人接入实战指南
  • CKS 认证考试核心题型实战解析与避坑指南
  • ComfyUI AI 工作流抠图构建实践
  • GTC2026 前瞻:Rubin 平台与 AI 工厂体系

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • RSA密钥对生成器

    生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online

  • Mermaid 预览与可视化编辑

    基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online