Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Vue3 封装 AI 问答组件实现 AI 流式回答问题的方法

Vue3 封装 AI 问答组件实现 AI 流式回答问题的方法

作者头像
小焱
发布于 2025-05-23 00:39:02
发布于 2025-05-23 00:39:02
486011
代码可运行
举报
文章被收录于专栏:前端开发前端开发
运行总次数:11
代码可运行

Vue3实现AI流式回答问题:组件封装与应用实例

一、AI流式回答技术原理

(一)传统请求与流式响应对比

  • 传统请求:客户端发送请求 → 服务器处理 → 一次性返回完整响应
  • 流式响应:客户端发送请求 → 服务器逐步生成响应 → 实时推送给客户端

(二)流式响应的优势

  • 即时反馈:用户无需等待完整回答
  • 更好的用户体验:减少感知等待时间
  • 资源优化:服务器无需一次性生成完整回答

(三)技术实现方案

  1. 服务器端:支持流式输出的AI模型(如OpenAI GPT系列)
  2. 通信协议:使用SSE(Server-Sent Events)或WebSocket
  3. 前端处理:实时解析和渲染流式数据

二、Vue3组件封装基础

(一)组件设计思路

  • 独立封装AI对话功能
  • 支持流式接收和渲染内容
  • 提供自定义样式和交互接口
  • 处理错误和加载状态

(二)核心技术点

  1. 使用Vue3的Composition API
  2. 处理异步数据流
  3. 实现文本逐字渲染动画
  4. 管理对话状态

三、组件实现代码

(一)基础组件结构

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<!-- AiChat.vue -->
<template>
  <div class="ai-chat">
    <!-- 对话历史 -->
    <div class="chat-history">
      <div v-for="(message, index)" :key="index" class="message">
        <div class="user-message" v-if="message.role === 'user'">
          <div class="avatar">
            <i class="fa fa-user"></i>
          </div>
          <div class="content">
            <div class="text" v-html="message.content"></div>
          </div>
        </div>
        <div class="ai-message" v-else>
          <div class="avatar">
            <i class="fa fa-robot"></i>
          </div>
          <div class="content">
            <div class="text" v-html="message.displayContent || message.content"></div>
            <div v-if="message.loading" class="loading-indicator">
              <span>.</span><span>.</span><span>.</span>
            </div>
          </div>
        </div>
      </div>
    </div>
    
    <!-- 输入区域 -->
    <div class="chat-input">
      <textarea 
        v-model="userInput" 
        placeholder="请输入问题..."
        @keyup.enter="sendMessage"
      ></textarea>
      <button @click="sendMessage" :disabled="loading">
        {{ loading ? '思考中...' : '发送' }}
      </button>
    </div>
  </div>
</template>

<script setup>
import { ref, reactive, onMounted, watch } from 'vue';

const props = defineProps({
  apiUrl: {
    type: String,
    required: true
  },
  apiKey: {
    type: String,
    required: true
  }
});

const emits = defineEmits(['messageSent', 'responseReceived', 'error']);

// 对话状态
const messages = reactive([]);
const userInput = ref('');
const loading = ref(false);

// 发送消息
const sendMessage = async () => {
  if (!userInput.value.trim() || loading.value) return;
  
  // 添加用户消息
  const userMessage = {
    role: 'user',
    content: userInput.value
  };
  
  messages.push(userMessage);
  emits('messageSent', userMessage);
  
  // 清空输入框
  userInput.value = '';
  
  // 添加AI响应占位
  const aiMessage = {
    role: 'assistant',
    content: '',
    displayContent: '',
    loading: true
  };
  
  messages.push(aiMessage);
  
  try {
    loading.value = true;
    await streamResponse(aiMessage);
    loading.value = false;
  } catch (error) {
    loading.value = false;
    aiMessage.loading = false;
    aiMessage.content = '抱歉,出现错误,请重试。';
    emits('error', error);
  }
};

// 流式接收响应
const streamResponse = async (message) => {
  const controller = new AbortController();
  const signal = controller.signal;
  
  try {
    const response = await fetch(props.apiUrl, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${props.apiKey}`
      },
      body: JSON.stringify({
        prompt: messages.filter(m => m.role === 'user').map(m => m.content).join('\n'),
        stream: true
      }),
      signal
    });
    
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }
    
    const reader = response.body.getReader();
    const decoder = new TextDecoder();
    
    let fullContent = '';
    
    while (true) {
      const { done, value } = await reader.read();
      
      if (done) {
        message.loading = false;
        break;
      }
      
      // 解码数据
      const chunk = decoder.decode(value, { stream: true });
      
      // 处理SSE格式数据
      const lines = chunk.split('\n').filter(line => line.trim() !== '');
      
      for (const line of lines) {
        if (line.startsWith('data: ')) {
          const data = line.substring(6);
          
          if (data === '[DONE]') {
            message.loading = false;
            continue;
          }
          
          try {
            const parsed = JSON.parse(data);
            const content = parsed.choices[0].delta.content || '';
            fullContent += content;
            
            // 更新显示内容
            message.content = fullContent;
            message.displayContent = formatDisplayContent(fullContent);
            
            emits('responseReceived', {
              content,
              fullContent
            });
          } catch (error) {
            console.error('解析响应失败:', error);
          }
        }
      }
    }
  } catch (error) {
    if (error.name !== 'AbortError') {
      throw error;
    }
  } finally {
    controller.abort();
  }
};

// 格式化显示内容(可添加Markdown解析等)
const formatDisplayContent = (content) => {
  // 简单处理换行
  return content.replace(/\n/g, '<br>');
};
</script>

<style scoped>
.ai-chat {
  display: flex;
  flex-direction: column;
  height: 100%;
  border: 1px solid #eee;
  border-radius: 4px;
  overflow: hidden;
}

.chat-history {
  flex: 1;
  overflow-y: auto;
  padding: 15px;
}

.message {
  margin-bottom: 15px;
  display: flex;
}

.avatar {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background-color: #f0f0f0;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-right: 10px;
}

.user-message .content {
  background-color: #e6f7ff;
  padding: 10px;
  border-radius: 4px;
  max-width: 80%;
}

.ai-message .content {
  background-color: #f5f5f5;
  padding: 10px;
  border-radius: 4px;
  max-width: 80%;
}

.loading-indicator {
  display: flex;
  justify-content: center;
  margin-top: 5px;
  color: #666;
}

.loading-indicator span {
  animation: loading 1.4s infinite ease-in-out both;
  margin: 0 1px;
}

.loading-indicator span:nth-child(1) { animation-delay: -0.32s; }
.loading-indicator span:nth-child(2) { animation-delay: -0.16s; }

@keyframes loading {
  0%, 80%, 100% { transform: scale(0); }
  40% { transform: scale(1); }
}

.chat-input {
  display: flex;
  padding: 10px;
  border-top: 1px solid #eee;
}

textarea {
  flex: 1;
  padding: 8px;
  border: 1px solid #ddd;
  border-radius: 4px;
  resize: none;
  margin-right: 10px;
}

button {
  padding: 8px 15px;
  background-color: #1890ff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

button:disabled {
  background-color: #f0f0f0;
  color: #aaa;
  cursor: not-allowed;
}
</style>

(二)组件核心功能说明

  1. 状态管理
    • 使用reactive存储对话历史
    • 使用ref管理输入框和加载状态
  2. 流式响应处理
    • 使用fetch API发起请求
    • 通过ReadableStream读取流式数据
    • 解析SSE格式数据(data: {...}
  3. 内容渲染
    • 实时更新displayContent
    • 支持基本格式处理(如换行)

四、应用实例

(一)简单使用示例

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<template>
  <div class="app-container">
    <h1>AI聊天助手</h1>
    
    <AiChat
      :api-url="apiUrl"
      :api-key="apiKey"
      @messageSent="handleMessageSent"
      @responseReceived="handleResponseReceived"
      @error="handleError"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue';
import AiChat from './components/AiChat.vue';

const apiUrl = ref('https://api.openai.com/v1/chat/completions');
const apiKey = ref('your-api-key');

const handleMessageSent = (message) => {
  console.log('用户发送消息:', message);
};

const handleResponseReceived = (response) => {
  console.log('收到AI响应:', response);
};

const handleError = (error) => {
  console.error('发生错误:', error);
};
</script>

<style>
.app-container {
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
}
</style>

(二)增强功能:Markdown解析

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 添加Markdown解析支持
import marked from 'marked';
import DOMPurify from 'dompurify';

// ...原有代码...

// 更新formatDisplayContent函数
const formatDisplayContent = (content) => {
  // 使用marked解析Markdown
  const html = marked.parse(content);
  
  // 净化HTML防止XSS攻击
  const cleanHtml = DOMPurify.sanitize(html);
  
  return cleanHtml;
};

(三)增强功能:打字机效果

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 添加打字机效果
const typingSpeed = 15; // 毫秒/字符
let typingTimer = null;

// 更新streamResponse函数
const streamResponse = async (message) => {
  // ...原有代码...
  
  let fullContent = '';
  let displayContent = '';
  let lastUpdateTime = 0;
  
  while (true) {
    // ...原有代码...
    
    for (const line of lines) {
      // ...原有代码...
      
      try {
        const parsed = JSON.parse(data);
        const content = parsed.choices[0].delta.content || '';
        fullContent += content;
        
        // 控制显示速度,实现打字机效果
        const now = Date.now();
        const timeSinceLastUpdate = now - lastUpdateTime;
        
        if (timeSinceLastUpdate > typingSpeed) {
          displayContent += content;
          lastUpdateTime = now;
        } else {
          // 累积内容,稍后显示
          displayContent += content;
          clearTimeout(typingTimer);
          
          typingTimer = setTimeout(() => {
            message.displayContent = formatDisplayContent(displayContent);
          }, typingSpeed);
        }
        
        message.content = fullContent;
        message.displayContent = formatDisplayContent(displayContent);
        
        emits('responseReceived', {
          content,
          fullContent
        });
      } catch (error) {
        console.error('解析响应失败:', error);
      }
    }
  }
};

五、高级优化

(一)错误处理增强

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 增强错误处理
const streamResponse = async (message) => {
  try {
    // ...原有代码...
    
    if (!response.ok) {
      let errorMessage = `HTTP错误! 状态码: ${response.status}`;
      
      try {
        const errorData = await response.json();
        errorMessage += ` - ${errorData.error.message}`;
      } catch (e) {
        // 无法解析错误响应
      }
      
      throw new Error(errorMessage);
    }
    
    // ...原有代码...
  } catch (error) {
    if (error.name !== 'AbortError') {
      // 显示友好的错误消息
      message.content = '抱歉,AI回答过程中出现错误。请重试或稍后再试。';
      message.displayContent = message.content;
      message.loading = false;
      
      // 记录错误日志
      console.error('AI响应错误:', error);
      emits('error', error);
    }
  } finally {
    controller.abort();
  }
};

(二)自定义样式与主题

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<!-- 在使用组件时自定义样式 -->
<template>
  <div class="app-container">
    <h1>AI聊天助手</h1>
    
    <AiChat
      :api-url="apiUrl"
      :api-key="apiKey"
      class="custom-chat"
    />
  </div>
</template>

<style scoped>
/* 自定义AI聊天组件样式 */
.custom-chat .user-message .content {
  background-color: #dcf8c6;
}

.custom-chat .ai-message .content {
  background-color: #ffffff;
  border: 1px solid #eee;
}

.custom-chat .avatar {
  background-color: #075e54;
  color: white;
}
</style>

六、性能优化

(一)虚拟滚动优化长对话

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 使用vue-virtual-scroller优化长对话
import { RecycleScroller } from 'vue-virtual-scroller';
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css';

// 在组件中使用
<RecycleScroller
  class="chat-history"
  :items="messages"
  :item-size="60"
  key-field="id"
>
  <template #item="{ item, index }">
    <!-- 消息内容 -->
  </template>
</RecycleScroller>

(二)防抖处理输入

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 防抖处理用户输入
import { debounce } from 'lodash-es';

const debouncedSendMessage = debounce(sendMessage, 300);

// 在模板中使用
<button @click="debouncedSendMessage" :disabled="loading">发送</button>

七、部署与安全注意事项

(一)API密钥安全

  • 不要在前端直接暴露API密钥
  • 建议通过后端代理API请求
  • 使用环境变量管理敏感信息

(二)后端代理示例

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Node.js后端代理示例
const express = require('express');
const axios = require('axios');
const dotenv = require('dotenv');

dotenv.config();

const app = express();
const PORT = process.env.PORT || 3000;

app.use(express.json());

// CORS设置
app.use((req, res, next) => {
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  next();
});

// 代理AI API请求
app.post('/api/chat', async (req, res) => {
  try {
    const response = await axios.post(
      'https://api.openai.com/v1/chat/completions',
      req.body,
      {
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`
        },
        responseType: 'stream'
      }
    );
    
    // 直接将流式响应转发给客户端
    response.data.pipe(res);
  } catch (error) {
    console.error('代理请求失败:', error);
    res.status(500).json({ error: 'Internal Server Error' });
  }
});

app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

八、总结

通过Vue3的Composition API,我们可以方便地封装一个高效、可复用的AI流式问答组件。关键技术点包括:

  1. 流式响应处理:使用Fetch API和ReadableStream解析SSE格式数据
  2. 状态管理:合理使用ref和reactive管理对话状态
  3. 用户体验优化:实现打字机效果、加载指示器
  4. 安全考虑:通过后端代理保护API密钥

这个组件可以轻松集成到各种应用中,如智能客服、聊天机器人、知识问答系统等。根据实际需求,你可以进一步扩展其功能,如添加语音交互、多轮对话上下文管理、知识库集成等。


Vue3,AI 问答组件,AI 流式回答,前端开发,组件封装,人工智能,实时交互,Web 开发,Vue 组件,自然语言处理,前端组件,AI 对话,流式响应,Vue.js, 智能问答


本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
深入理解SSE:构建实时数据推送的前后端解决方案
在现代Web应用中,实时数据推送已成为提升用户体验的关键技术之一。本文将深入探讨Server-Sent Events (SSE) 技术,从原理到实践,带你全面掌握如何使用SSE实现前后端实时通信。
Front_Yue
2025/06/30
3200
深入理解SSE:构建实时数据推送的前后端解决方案
Vue 弹幕功能实现步骤与组件封装详细指南
通过以上使用方法和组件封装,您可以在Vue项目中实现一个功能完整、性能优良的弹幕系统,支持视频、直播等多种场景,并且可以根据需求进行灵活扩展和定制。
小焱
2025/06/01
900
Vue 弹幕功能实现步骤与组件封装详细指南
Vue3 分页组件关键知识点与详细封装使用方法解析
通过以上封装方法,你可以创建一个高度可复用、灵活配置的分页组件,满足各种前端分页需求。
小焱
2025/06/21
1050
Vue3 分页组件关键知识点与详细封装使用方法解析
Vue 模拟键盘组件封装方法与使用方法详解
将封装好的键盘组件(如VirtualKeyboard.vue)放入项目的components目录,然后在需要使用的Vue文件中引入:
小焱
2025/06/11
1220
Vue 模拟键盘组件封装方法与使用方法详解
deepseek+vue3.5+arco+markdown网页版流式AI聊天问答
半个月之前有发布一篇vite6+deepseek+vant4构建mobile版智能ai对话助手。
andy2018
2025/03/28
1.3K7
deepseek+vue3.5+arco+markdown网页版流式AI聊天问答
DeepSeek-Vue3基于vite6+vant4仿deepseek/Kimi流式AI聊天小助手
2025年智能AI实战-Vue3+DeepSeek API打造一款mobile版ai聊天界面小助手。
andy2018
2025/03/17
1.5K3
DeepSeek-Vue3基于vite6+vant4仿deepseek/Kimi流式AI聊天小助手
20分钟上手DeepSeek开发:SpringBoot + Vue2快速构建AI对话系统
在生成式AI技术蓬勃发展的今天,大语言模型已成为企业智能化转型和个人效率提升的核心驱动力。作为国产大模型的优秀代表,DeepSeek凭借其卓越的中文语义理解能力和开发者友好的API生态,正在成为构建本土化AI应用的首选平台。本文将以Spring Boot3+Vue2全栈技术为基础,手把手带你打造一个具备以下特性的AI对话系统:
全干程序员demo
2025/03/18
5730
20分钟上手DeepSeek开发:SpringBoot + Vue2快速构建AI对话系统
springAI初体验 让人人都能跑大模型
他们的设计理念是:为开发人员提供一个抽象接口,为将生成式AI作为独立组件纳入应用奠定基础
用户10143704
2024/04/05
1.1K0
五分钟搭建一个 Suno AI 音乐站点
在这个数字化时代,人工智能技术正以惊人的速度改变着我们的生活方式和创造方式。音乐作为一种最直接、最感性的艺术形式,自然也成为了人工智能技术的应用场景之一。今天,我们将以 Vue 和 Node.js 为基础,利用现有的 API 来快速搭建一个 Suno AI 音乐站点。让我们一起探索这个令人兴奋的过程吧!
崔庆才
2024/05/27
6450
五分钟搭建一个 Suno AI 音乐站点
Vue3 实现小米商城官网组件使用与封装详细指南
通过以上组件封装和使用方法,您可以完整构建一个功能丰富、交互友好的小米商城官网。这些组件设计遵循了Vue3的最佳实践,具有良好的可扩展性和可维护性,可以根据实际需求进行进一步定制和扩展。
小焱
2025/05/25
920
Vue3 实现小米商城官网组件使用与封装详细指南
使用Kimi开发自己的问答应用
Kimi是大家常用的一个人工智能助手,本文使用Kimi开发文档,以node作为后端,开发与一个问答系统
牛老师讲GIS
2024/12/30
2500
使用Kimi开发自己的问答应用
Vue 项目中 TinyMCE 富文本编辑器的具体使用方法
TinyMCE是一款功能强大、高度可定制的富文本编辑器,被广泛应用于各种Web应用中。在Vue项目中集成TinyMCE可以为用户提供专业的文本编辑体验。本文将详细介绍如何在Vue项目中使用TinyMCE,并提供完整的应用实例。
小焱
2025/05/22
7830
Vue 项目中 TinyMCE 富文本编辑器的具体使用方法
用Blazor和DeepSeek API创建聊天应用
本文将指导您如何使用Blazor框架和DeepSeek API构建一个简单的聊天应用。Blazor是一个用于构建交互式Web UI的框架,它允许开发者使用C#编写前端代码。DeepSeek API则提供强大的自然语言处理能力,使得应用程序能够理解和生成人类语言。
郑子铭
2025/03/21
1800
用Blazor和DeepSeek API创建聊天应用
一百行代码实现简易版 ChatGPT 聊天机器人
最近,OpenAI的一款聊天机器人模型ChatGPT爆火,本篇文章用一百行代码给大家制作一款简易的聊天机器人,话不多说,上图上代码。
海拥
2023/02/27
1K0
一百行代码实现简易版 ChatGPT 聊天机器人
调用DeepSeek API增强版纯前端实现方案,支持文件上传和内容解析功能
在现代Web开发中,文件上传和内容解析是常见的需求。随着人工智能技术的发展,DeepSeek API增强版提供了一个强大的解决方案,支持多种文件格式的上传和内容解析。本文将详细介绍如何通过纯前端技术调用DeepSeek API增强版,实现文件上传和内容解析功能,帮助开发者快速构建高效、安全的Web应用。
全干程序员demo
2025/03/27
9320
调用DeepSeek API增强版纯前端实现方案,支持文件上传和内容解析功能
基于GPT搭建私有知识库聊天机器人(六)仿chatGPT打字机效果
在前几篇文章中,我们已经了解了如何使用 GPT 模型来搭建一个简单的聊天机器人,并在后端使用私有知识库来提供答案。
夕阳也是醉了
2023/10/16
8230
基于GPT搭建私有知识库聊天机器人(六)仿chatGPT打字机效果
我用 Vue3+Ts+Vite2 写了一个美女小黄站
首先使用以下命令创建项目 yarn create @vitejs/app vue3-ts-vite2 --template vue-ts vite.config.js import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import path from 'path' // https://vitejs.dev/config/ export default defineConfig({ plugins: [
公众号---人生代码
2021/04/22
4.5K0
基于uniapp+deepseek+vue3跨平台ai流式对话
基于uniapp+vue3集成deepseek-v3实战跨端流式输出AI对话系统。支持暗黑+亮色模式、代码高亮、本地会话存储等功能。支持编译到小程序+h5+app端。
andy2018
2025/05/06
9560
基于uniapp+deepseek+vue3跨平台ai流式对话
Vue3 结合 TypeScript 项目开发使用指南及组件封装实操方法
这些方法与Vue3+TypeScript项目结合,可以帮助你高效构建高质量的前端应用。
小焱
2025/05/29
2350
Vue3 结合 TypeScript 项目开发使用指南及组件封装实操方法
vue3 手动封装message消息组件
vue3与vue2不同,原先的vue.prototypeAPI已经被弃用,取而代之的是app.config.globalPropertiesAPI在全局目录main.js中进行全局定义,然后在组件中引入即可(其实写到这里,我突然想起来尤大更新的v3版本旨在简化全局定义实际却没有调用到的组件,精简项目大小,对所需组件进行按需引入,所以在vue3中进行全局定义组件,显得有点吃饱撑着了。。。。不过写了就写完了吧)
爆炒鱿鱼
2023/10/30
1.3K0
推荐阅读
相关推荐
深入理解SSE:构建实时数据推送的前后端解决方案
更多 >
交个朋友
加入腾讯云官网粉丝站
蹲全网底价单品 享第一手活动信息
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验