欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 健康 > 美食 > 使用 Vue 重构 RAGFlow 实现聊天功能

使用 Vue 重构 RAGFlow 实现聊天功能

2025/4/6 10:14:01 来源:https://blog.csdn.net/weixin_44997264/article/details/146914955  浏览:    关键词:使用 Vue 重构 RAGFlow 实现聊天功能

引言

在现代的应用程序开发中,聊天功能是非常常见且重要的一部分。特别是在智能客服、在线协作等场景下,聊天功能能够极大地提升用户体验。RAG(Retrieval Augmented Generation,检索增强生成)是一种结合了检索和生成技术的方法,它可以让聊天系统更加智能和高效。本文将详细介绍如何使用 Vue 框架重构 RAGFlow 来实现一个聊天功能。

什么是 RAGFlow

RAGFlow 指的是检索增强生成流程。传统的生成式模型在回答问题时,可能会因为缺乏相关的知识而产生不准确或不完整的回答。而 RAG 方法通过在生成之前先从外部知识库中检索相关信息,然后将这些信息融入到生成过程中,从而提高回答的质量和准确性。

RAG 流程概述

  1. 用户输入:用户向聊天系统发送一个问题或消息。
  2. 检索阶段:系统根据用户输入从知识库中检索相关的信息。
  3. 生成阶段:将检索到的信息和用户输入作为输入,传递给生成模型,生成回答。
  4. 返回结果:将生成的回答返回给用户。

Vue 框架简介

Vue 是一个用于构建用户界面的渐进式 JavaScript 框架。它具有简单易学、响应式数据绑定、组件化开发等特点,非常适合用于构建交互式的前端应用程序。在本次重构中,我们将利用 Vue 的这些特性来实现聊天界面和与后端的交互。(我这里用的RAGFlow自身的接口,主要用来学习人家的编程思路)
 

项目搭建

初始化项目

首先,确保你已经安装了 Node.js 和 npm。然后,使用 Vue CLI 来初始化一个新的 Vue 项目:

npm install -g @vue/cli
vue create rag-chat-app
cd rag-chat-app

安装依赖

为了实现与后端的交互,我们需要安装一些依赖,包括makdown的解析,因为聊天的文本流需要拿makdown去渲染这样才能页面上看起来美观,下面是我用到的依赖 package.json

{"name": "chat-app","version": "0.1.0","private": true,"scripts": {"serve": "vue-cli-service serve","build": "vue-cli-service build","lint": "vue-cli-service lint"},"dependencies": {"axios": "^1.8.1","core-js": "^3.8.3","crypto-js": "^4.2.0","dompurify": "^3.2.4","highlight.js": "^11.11.1","js-base64": "^3.7.5","jsencrypt": "^3.3.2","marked": "^15.0.7","uuid": "^9.0.1","vue": "^2.6.14","vue-router": "^3.6.5"},"devDependencies": {"@babel/core": "^7.12.16","@babel/eslint-parser": "^7.12.16","@vue/cli-plugin-babel": "~5.0.0","@vue/cli-plugin-eslint": "~5.0.0","@vue/cli-service": "~5.0.0","eslint": "^7.32.0","eslint-plugin-vue": "^8.0.3","less": "^4.2.2","less-loader": "^10.2.0","vue-template-compiler": "^2.6.14"},"eslintConfig": {"root": true,"env": {"node": true},"extends": ["plugin:vue/essential","eslint:recommended"],"parserOptions": {"parser": "@babel/eslint-parser"},"rules": {}},"browserslist": ["> 1%","last 2 versions","not dead"]
}

实现聊天界面

创建组件结构树

SRC下面的结构树├─assets
├─components
├─fonts
├─router
├─utils
└─views└─HomeChatcomponents下面的结构树├─AppList.vue
├─chatRecord.vue
├─Homemange.vue
├─mainpage.vue

 登录界面:

<template><div id="app"><div class="login-container"><div class="header-title"><p class="title">{{ title }}</p></div><div class="login-box"><h1 class="login-title">登录</h1><p class="welcome-message">很高兴再次见到您!</p><form><div class="form-group inline-items"><label for="email">邮箱</label><input type="email" id="email" v-model="parmas.email" placeholder="请输入你的邮箱" /></div><div class="form-group inline-items"><label for="password">密码</label><input type="password" id="password" v-model="parmas.password" placeholder="请输入你的密码" /></div><div class="form-group inline-items remember-me-container"><input type="checkbox" id="remember-me" v-model="rememberMe"><label for="remember-me" class="remember-me-label">记住密码</label></div><button type="button" @click="login">登录</button></form><!-- <a href="#" class="forgot-password">忘记密码?</a> --></div></div></div>
</template><script>
import { rsaPsw } from '@/utils';
import axios from 'axios';
export default {data() {return {parmas: {email: '',password: ''},rememberMe: false,timer: null,title: window.BASE_CONFIG.title};},created() {const savedEmail = localStorage.getItem('savedEmail');const savedPassword = localStorage.getItem('savedPassword');const savedRememberMe = localStorage.getItem('savedRememberMe') === 'true';if (savedEmail && savedPassword && savedRememberMe) {this.parmas.email = savedEmail;this.parmas.password = savedPassword;this.rememberMe = savedRememberMe;}},methods: {async login() {let self = this;const rsaPassWord = rsaPsw(self.parmas.password);try {// 定义请求的 URLconst url = window.BASE_CONFIG.apiUrl + 'v1/user/login';const data = {email: `${self.parmas.email}`.trim(),password: rsaPassWord};const response = await axios.post(url, data);// console.log(response.data);if (response.data) {const authorizationHeader = response.headers['authorization'];let local = localStorage.getItem('Authorization');if (local != null && local != '' && local != undefined) {localStorage.removeItem("Authorization");localStorage.getItem("token");}localStorage.setItem("Authorization", authorizationHeader);localStorage.setItem("token", response.data.data.access_token);//登录成功开启定时器检查token是否过期// self.timer = setInterval(() => {//   self.fetchUserInfo();//  }, 5000);if (this.rememberMe) {localStorage.setItem('savedEmail', this.parmas.email);localStorage.setItem('savedPassword', this.parmas.password);localStorage.setItem('savedRememberMe', 'true');} else {localStorage.removeItem('savedEmail');localStorage.removeItem('savedPassword');localStorage.removeItem('savedRememberMe');}this.$router.push('/ChatHome');// alert("登录成功");} else {alert("登录失败");}} catch (error) {// 请求失败,处理错误信息console.error('登录失败', error);alert("登录失败,请检查您的邮箱和密码");}},//开启一个定时器,定时检查token是否过期// async fetchUserInfo() {//   try {//     let local =localStorage.getItem('Authorization');//     const headers = {//       'authorization': local//             };//     const response = await axios.get('http://192.168.0.187/v1/user/info', { headers });//     let userInfo = response.data;//     if(userInfo.message!="success"){//       clearInterval(this.timer);//       localStorage.removeItem("Authorization");//       this.$router.push('/login');//     }//   } catch (error) {//     console.error('获取用户信息失败:', error);//   }// }}
};
</script><style scoped lang="less">
/* 整体容器样式 */
.login-container {display: flex;justify-content: center;align-items: center;min-height: 100vh;/* background: linear-gradient(45deg, #1a2a6c, #b21f1f, #fdbb2d); */background-image: url('../../assets/bg.png');background-size: 100% 100%;/* animation: gradient 15s ease infinite; */
}.header-title {width: 50%;height: 100px;position: absolute;top: 100px;.title {color: #fff;font-size: 40px;font-weight: 700;}
}/* @keyframes gradient {0% {background-position: 0% 50%;}50% {background-position: 100% 50%;}100% {background-position: 0% 50%;}
} *//* 登录框样式 */
.login-box {position: relative;background-color: rgba(255, 255, 255, 0.1);padding: 40px;border-radius: 20px;box-shadow: 0 0 20px rgba(0, 0, 0, 0.2);width: 350px;left: 290px;backdrop-filter: blur(10px);border: 1px solid rgba(255, 255, 255, 0.2);animation: fadeIn 0.5s ease-out;
}@keyframes fadeIn {from {opacity: 0;transform: translateY(-20px);}to {opacity: 1;transform: translateY(0);}
}/* 登录标题样式 */
.login-title {text-align: center;color: #fff;margin-bottom: 15px;font-size: 32px;font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;text-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
}/* 欢迎信息样式 */
.welcome-message {text-align: center;color: #fff;margin-bottom: 30px;font-size: 18px;font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;text-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
}/* Form Group Style */
.form-group {margin-bottom: 20px;
}/* Styles that make labels and input boxes appear in one line */
.inline-items {display: flex;align-items: center;
}/* Label Style */
.form-group label {margin-right: 10px;color: #fff;font-size: 14px;min-width: 60px;font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;text-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
}/* Input Box Style */
.form-group input {flex: 1;padding: 10px;border: none;border-radius: 5px;font-size: 16px;background-color: rgba(255, 255, 255, 0.2);color: #fff;transition: background-color 0.3s ease;
}.form-group input::placeholder {color: rgba(255, 255, 255, 0.6);
}/* 输入框聚焦样式 */
.form-group input:focus {outline: none;background-color: rgba(255, 255, 255, 0.3);box-shadow: 0 0 5px rgba(255, 255, 255, 0.5);
}/* 登录按钮样式 */
button {width: 100%;padding: 12px;background-color: rgba(255, 255, 255, 0.2);color: #fff;border: none;border-radius: 5px;font-size: 18px;cursor: pointer;transition: background-color 0.3s ease;font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;text-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
}/* 登录按钮悬停样式 */
button:hover {background-color: rgba(255, 255, 255, 0.3);
}/* 忘记密码链接样式 */
.forgot-password {display: block;text-align: center;margin-top: 20px;color: #fff;text-decoration: none;font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;text-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
}/* 忘记密码链接悬停样式 */
.forgot-password:hover {text-decoration: underline;
}/* 记住密码容器样式 */
.remember-me-container {justify-content: flex-start;align-items: center;
}/* 记住密码复选框样式 */
#remember-me {margin-left: 6px;position: absolute;left: 48px;
}.remember-me-label {position: absolute;left: 70px;color: #fff;font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;text-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
}
</style>

运行结果:

聊天主页:

<template><div id="app"><div id="main-container"><!-- 左侧菜单栏 --><aside id="menu-bar" :style="menuBarStyle" class="menu-bar"><div class="menu-toggle" @click="toggleMenu"><i class="iconfont liuduidui-shouqi" style="font-size: 25px;"></i></div><ul v-show="!isMenuCollapsed"><div class="menu-logo"><div class="menu-logo-content"><img style="width: 140px;" src="../../assets/logo3.png" alt=""></div></div><!-- <li v-for="(menuItem, index) in menuItems" :key="index">{{ menuItem }}</li> --><div class="newConv" @click="createNewDialog()"><i class="iconfont liuduidui-jia"></i><span>新建对话</span></div><li></li></ul><chatRecord v-show="!isMenuCollapsed" ref="chatRecord" @historical="historical" /></aside><!-- 右侧主体部分 --><div id="main-content"><header id="main-header"><div class="header-item"><i v-if="!isshowmange" @click="returnButton()" class="iconfont liuduidui-fanhui"></i><!-- <div class="heard-logo"><img width="40px" height="40px" src="../../assets/deep.png" alt=""></div> --><span class="heart-text">怼怼·大语言模型</span><p v-if="this.local == undefined && this.local == ''" class="login" @click="login()">登录</p><div v-else class="login"><p>用户</p><div class="logout-menu" @click="logout()">退出登录</div></div></div></header><div id="chat-window"><MainPage v-if="isshowmange" @emitUserClick="emitUserClick" @emitSelectChange="emitSelectChange" /><Homemange ref="Homemange" v-if="!isshowmange" /><!-- <p v-for="(message, index) in chatMessages" :key="index">{{ message }}</p> --></div></div></div></div>
</template><script>
import MainPage from '../../components/mainpage.vue';
import Homemange from '@/components/Homemange.vue';
import chatRecord from '@/components/chatRecord.vue';
import { getConversationId } from '@/utils';
import axios from 'axios';
export default {components: {MainPage, Homemange, chatRecord},data() {return {menuItems: ['菜单选项1', '菜单选项2', '菜单选项3'],chatMessages: [],isMenuCollapsed: false,isshowmange: true,local: '',selectedFeature: '',};},computed: {menuBarStyle() {return {width: this.isMenuCollapsed ? '50px' : '200px',transition: 'all 0.3s ease'};}},methods: {toggleMenu() {this.isMenuCollapsed = !this.isMenuCollapsed;},emitUserClick(item, value) {let self = thisthis.isshowmange = !this.isshowmange;this.$nextTick(() => {self.$refs.Homemange.newMessage = itemself.$refs.Homemange.sendMessage()self.$refs.Homemange.selectObj = value})},//用户返回操作returnButton() {this.isshowmange = !this.isshowmange;},login() {this.$router.push('/Login')},logout() {this.$router.push('/Login')localStorage.removeItem("Authorization");},createNewDialog() {//用户新建对话,其实就是更新一个idlet self = this//下面清空,其实就是怕之前的历史对话对数据进行污染了this.isshowmange = false;this.$nextTick(() => {self.$refs.Homemange.messages = []self.$refs.Homemange.messages.push({id: 1,sender: 'bot',content: '',displayedText: '你好! 我是你的助理怼怼,有什么可以帮到你的吗?',timestamp: this.getCurrentTime(),isStreaming: true,status: '✓',})self.$refs.Homemange.userMessage = []})this.SetconversationId()self.ConversationHistory(self.selectedFeature)},emitSelectChange(value) {this.selectedFeature = valuethis.ConversationHistory(this.selectedFeature)//请求会话历史记录列表},//获取会话历史记录列表async ConversationHistory(dialog_id) {try {// 定义请求头const headers = {'Content-Type': 'application/json','Accept': 'text/event-stream','authorization': this.local};// 修正字符串拼接let url = `${window.BASE_CONFIG.apiUrl}v1/conversation/list?dialog_id=${dialog_id}`;const response = await axios.get(url, { headers });let arr = response.data.data;this.$refs.chatRecord.chatList = arrconsole.log(arr)} catch (error) {// 处理错误console.error('请求失败:', error);}},SetconversationId() {let a = getConversationId()const payload = {"dialog_id": this.selectedFeature,"name": "你好","is_new": true,"conversation_id": a,"message": [{"role": "assistant","content": "你好"}]};const headers = {'Content-Type': 'application/json','Accept': 'text/event-stream','authorization': this.local};let url = window.BASE_CONFIG.apiUrl + '/v1/conversation/set'axios.post(url, payload, { headers }).then(response => {let ConversationId = response.data.data.id;sessionStorage.setItem('conversation_id', ConversationId);   //将会更新ConversationId的值}).catch(error => {console.error('请求失败', error);});},//获取时间点getCurrentTime() {const now = new Date();return `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`;},historical(arr) {let self = thisthis.isshowmange = false;this.$nextTick(() => {self.$refs.Homemange.messages = []self.$refs.Homemange.messages = arr})}},mounted() {this.local = localStorage.getItem('Authorization');},
};
</script><style scoped lang="less">
#main-container {display: flex;height: 100vh;
}.menu-bar {padding: 10px;border-right: 1px solid #ece8e8;overflow: hidden;position: relative;
}.menu-toggle {position: absolute;top: 12px;right: 10px;font-size: 24px;cursor: pointer;}.menu-logo {position: absolute;left: 10px;top: 15px;left: 15px;font-size: 24px;cursor: pointer;.menu-logo-content {display: flex;justify-content: center;align-items: center;position: absolute;top: 3px;.menu-logo-text {font-size: 18px;margin-left: 10px;}}
}.newConv {width: 160px;height: 40px;display: flex;align-items: center;background-color: #FBFBFB;border: 1px solid;border-radius: 12px;cursor: pointer;color: #000;border-color: rgba(0, 0, 0, 0.1);display: flex;justify-content: center;align-items: center;margin-left: 18px;margin-top: 15px;
}.newConv:hover {background-color: rgba(0, 0, 0, 0.031);/* #00000008 转换为 rgba */
}#menu-bar ul {list-style-type: none;padding: 0;margin-top: 50px;
}#menu-bar ul li {padding: 10px 0;cursor: pointer;opacity: 0.8;transition: opacity 0.3s ease;
}#menu-bar ul li:hover {opacity: 1;
}#main-content {flex: 1;display: flex;flex-direction: column;
}#main-header {padding: 5px 5px;justify-content: center;display: flex;border-bottom: 1px solid #ece8e8;.header-item {width: 100%;height: 40px;position: relative;.heard-logo {z-index: 10000;position: absolute;top: 8px;display: flex;align-items: center;justify-content: center;left: 41px;width: 40px;height: 40px;border-radius: 50%;border-width: 1px;/* border-color: rgba(0, 0, 0, 0.1) */border: 1px solid;border-color: rgba(0, 0, 0, 0.1);}.liuduidui-fanhui {font-size: 20px;position: absolute;left: 10px;top: 10px;}.heart-text {position: absolute;top: 10px;font-size: 17px;color: #000;font-weight: 500;left: 30px;}.login {position: absolute;right: 10px;top: 5px;border: 1px solid;display: flex;flex-direction: row;align-items: center;justify-content: center;width: 74px;height: 32px;font-size: 14px;cursor: pointer;margin-right: 20px;border-radius: 9999px;border-width: 1px;border-color: rgba(0, 0, 0, 0.1);}.login:hover {/* background-color: button-bg-hover (you need to define this variable) */background-color: #e9e7e7;/* border: none */border: none;/* font-weight: bold */font-weight: bold;}}}#chat-window {flex: 1;// border: 1px solid #ccc;margin: 10px;padding: 10px;justify-content: center;align-items: center;
}.logout-menu {display: none;position: absolute;top: 109%;left: 0;background-color: #f9f9f9;color: #333;border: 1px solid #e0e0e0;border-radius: 4px;padding: 3px 0;min-width: 74px;list-style-type: none;box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);transition: all 0.2s ease-in-out;-webkit-user-select: none;-moz-user-select: none;user-select: none;border: 1px solid;/* display: flex/* flex-direction: row; */flex-direction: row;/* align-items: center; */align-items: center;/* justify-content: center; */justify-content: center;/* width: 74px; */width: 74px;/* height: 32px; */height: 32px;/* font-size: 14px; */font-size: 14px;/* cursor: pointer; */cursor: pointer;/* margin-right: 20px; */margin-right: 20px;border-radius: 9999px;/* border-width: 1px; */border-width: 1px;/* border-color: rgba(0, 0, 0, 0.1); */border-color: rgba(0, 0, 0, 0.1);
}.logout-menu li {/* 内边距 */padding: 8px 16px;/* 鼠标悬停时的指针样式 */cursor: pointer;
}.logout-menu li:hover {/* 鼠标悬停时的背景颜色 */background-color: #e9e9e9;
}.login:hover .logout-menu {/* 鼠标悬停时显示菜单 */display: block;display: flex;align-items: center;
}
</style>

运行结果:

聊天界面:

-->
<template><div class="chat-mange-container"><ul class="chat-message-list" ref="messagesContainer"><div class="main-content"><!-- 聊天窗口 --><div class="messages"><div v-for="msg in messages" :key="msg.id" class="message" :class="msg.sender"><!-- 机器人头像 --><div v-if="msg.sender === 'bot'" class="avatar bot-avatar"><img src="../assets/chat.png" alt="Bot"></div><!-- 消息内容 --><div class="message-content"><div class="message-bubble"><div class="markdown-content" v-if="msg.displayedText === ''" style="color: #999;">正在思考中...</div><div class="markdown-content" v-html="parseMarkdown(msg.displayedText)"></div></div><div class="meta-info"><span class="timestamp">{{ msg.timestamp }}</span><span v-if="msg.status" class="status">{{ msg.status }}</span></div></div><!-- 用户头像 --><div v-if="msg.sender === 'user'" class="avatar user-avatar"><img src="../assets/user1.png" alt="User"><!-- <img src="../../assets/user1.png" alt="User"> --></div></div><!-- 消息输入区域 --></div></div></ul><div class="chat-input-main-container"><section class="chat-input-section"><div class="chat-input-container"><div class="chat-input-content"><div class="chat-input-wrapper"><div class="chat-input-textarea"><textarea v-model="newMessage" @keyup.enter="sendMessage" :disabled="isSending"placeholder="输入任何问题,Enter发送,Shift + Enter 换行"></textarea></div><div class="chat-input-bottom"><div class="chat-input-feature-buttons"><div class="chat-input-feature-button"><i class="iconfont liuduidui-AIzhuli1"></i><span class="custom-select">{{ selectObj.label }}</span><!-- <select class="custom-select"><option value="deepThoughtR1">深度思考(R1)</option><option value="networkSearch">联网搜索</option><option value="otherFeature">其他功能</option></select> --></div><!-- <div class="chat-input-feature-button"><i class="iconfont liuduidui-hulianwang" style="margin-right: 2px;"></i><span>联网搜索</span></div> --></div><div class="chat-input-tools"><div class="chat-input-tool-button" v-if="!isSending" @click="sendMessage":disabled="!newMessage.trim() || isSending"><i class="iconfont liuduidui-shangchuan" style="font-size: 30px;"></i></div><div v-else class="loading-dots"><div class="dot"></div><div class="dot"></div><div class="dot"></div></div></div></div></div></div></div><div class="chat-input-footer">以上内容均由AI大模型生成,仅供参考</div></section></div></div>
</template>

 css样式:

<style lang="less">
.chat-mange-container {width: 100%;overflow-y: scroll;padding-left: 16px;padding-right: 10px;padding-bottom: 200px;display: flex;flex-direction: column;align-items: center;outline: none;
}.chat-mange-container::-webkit-scrollbar {display: none;
}.chat-message-list {width: 100%;max-width: 866px;height: 685px;overflow: auto;padding-left: 16px;display: flex;flex-direction: column;
}.chat-message-list::-webkit-scrollbar {width: 0;height: 0;
}.chat-message-list {scrollbar-width: none;
}.chat-message-list {-ms-overflow-style: none;
}::v-deep .markdown-content {line-height: 1.6;font-size: 15px;color: #2d333a;text-align: left;h1,h2,h3,h4,h5,h6 {margin: 1.2em 0 0.8em;font-weight: 600;color: #1a1a1a;padding-bottom: 0.3em;border-bottom: 1px solid #eaecef;}h1 {font-size: 1.8em;}h2 {font-size: 1.6em;}h3 {font-size: 1.4em;}h4 {font-size: 1.2em;}h5 {font-size: 1.1em;}h6 {font-size: 1em;}/* 段落和文字 */p {margin: 0.8em 0;line-height: 1.7;}strong {color: #24292e;font-weight: 650;}em {color: #5a5a5a;font-style: italic;}/* 列表样式 */ul,ol {margin: 0.8em 0;padding-left: 2em;li {margin: 0.4em 0;padding-left: 0.4em;&::marker {color: #6a737d;}}}/* 代码块增强 */pre {position: relative;background: #1e1e1e !important;border-radius: 8px;margin: 1.2em 0;box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);code {display: block;padding: 1.2em !important;font-family: 'Fira Code', Consolas, monospace;font-size: 0.9em;line-height: 1.5;color: #d4d4d4;}/* 语言标签 */&::after {content: attr(data-language);position: absolute;top: 0;right: 0;padding: 0.2em 0.8em;background: rgba(255, 255, 255, 0.1);color: #999;font-size: 0.8em;border-bottom-left-radius: 4px;}}/* 行内代码 */code:not(pre code) {background: rgba(175, 184, 193, 0.2);padding: 0.2em 0.4em;border-radius: 4px;font-size: 0.9em;color: #eb5757;}/* 引用块 */blockquote {margin: 1em 0;padding: 0.8em 1.2em;background: #f8f9fa;border-left: 4px solid #3498db;border-radius: 4px;color: #6a737d;p {margin: 0;}}table {width: 100%;margin: 1.5em 0;border-collapse: collapse;box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);th,td {padding: 0.8em;border: 1px solid #dfe2e5;}th {background: #f6f8fa;font-weight: 600;}tr:nth-child(even) {background: #fafbfc;}}a {color: #3498db;text-decoration: none;border-bottom: 1px solid rgba(52, 152, 219, 0.3);transition: all 0.2s;&:hover {color: #2980b9;border-bottom-color: currentColor;}}
}::v-deep code {font-family: 'Fira Code', 'Consolas', monospace;background-color: rgba(175, 184, 193, 0.2);padding: 0.2em 0.4em;border-radius: 4px;font-size: 0.9em;
}::v-deep pre {position: relative;background-color: #000102 !important;border: 1px solid #e1e4e8;border-radius: 6px;padding: 16px;margin: 1em 0;overflow-x: auto;
}::v-deep pre code {background-color: transparent !important;padding: 0;font-size: 0.9em;line-height: 1.5;
}::v-deep .hljs {background: transparent !important;
}/* 添加代码块复制按钮 */
::v-deep pre {position: relative;
}::v-deep .copy-button {position: absolute;right: 8px;top: 8px;padding: 4px 8px;background: rgba(255, 255, 255, 0.8);border: 1px solid #e1e4e8;border-radius: 4px;cursor: pointer;font-size: 0.8em;
}/* 基础布局 */
.chat-mange-container {display: flex;height: 100vh;font-family: 'Segoe UI', system-ui, sans-serif;
}.sidebar {width: 150px;color: #fff;padding: 20px;display: flex;flex-direction: column;
}.logo {font-size: 23px;/* font-family: YouSheBiaoTiHei; */font-weight: 550;color: #000000;text-indent: 12px;
}.menu {list-style: none;padding: 0;color: #4C4C4C;cursor: pointer;font-weight: 500;font-size: 20px;
}.menu li {border-radius: 8px;cursor: pointer !important;transition: all 0.2s;width: 103px;line-height: 45px;height: 45px;margin-top: 5px;
}.menu li:hover {background: #E8EFFF;border: 1px solid #BED3FB;
}.menu li.active {background: #E8EFFF;border: 1px solid #BED3FB;font-weight: 500;color: #0057FF;
}.history {width: 226px;background: #fff;box-shadow: 2px 0 8px rgba(0, 0, 0, 0.05);transition: transform 0.3s ease;position: relative;}.history.collapsed {transform: translateX(-100%);
}.toggle-btn {position: absolute;right: -40px;top: 20px;width: 40px;height: 40px;border: none;background: #fff;box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1);border-radius: 0 8px 8px 0;cursor: pointer;font-size: 16px;
}.history-header {padding: 20px;font-size: 18px;font-weight: 500;border-bottom: 1px solid #eee;
}.history-list {list-style: none;padding: 12px;margin: 0;.newSession {width: 100%;line-height: 49px;background: #E8EFFF;margin-top: 70px;display: flex;align-items: center;justify-content: center;border-radius: 5px;.newSession_text {font-weight: 600;font-size: 16px;color: #0057FF;height: 49px;line-height: 49px;}}}.history-list li {padding: 12px;border-radius: 8px;cursor: pointer;transition: background 0.2s;font-size: 14px;color: #666;cursor: pointer;
}.history-list li:hover {background: #f8f9fa;
}/* 主聊天窗口 */
.main-content {flex: 1;display: flex;position: relative;
}.chat-window {flex: 1;display: flex;flex-direction: column;background: #fff;transition: all 0.3s;
}.chat-heard {width: 100%;height: 60px;//   background-image: url(../../assets/head.png);background-repeat: no-repeat;background-size: 100% 60px;position: relative;.headerInfo {position: absolute;width: 80%;display: flex;left: 80px;top: 0px;height: 40px;.headerInfo-text1 {height: 20px;font-family: YouSheBiaoTiHei;font-weight: 400;line-height: 20px;font-size: 22px;color: #FFFFFF;background: linear-gradient(0deg, #297DEF 0%, #0A5CCC 100%);-webkit-background-clip: text;-webkit-text-fill-color: transparent;}.app_name {// background-color: #0057FF;display: flex;font-size: 14px;margin-left: 30px;.appname1 {width: 118px;height: 31px;line-height: 31px;background: #F7F9FE;opacity: 0.82;font-family: PingFang SC;font-weight: 500;font-size: 18px;color: #45A658;border-radius: 20px;margin-top: 11px;}.appname2 {width: 200px;text-indent: 10px;height: 31px;line-height: 31px;background: #F7F9FE;opacity: 0.82;opacity: 0.82;font-family: PingFang SC;font-weight: 500;font-size: 18px;color: #46B0BA;margin-left: 20px;border-radius: 20px;margin-top: 11px;}}// .headerInfo-text2 {//   height: 20px;//   line-height: 20px;//   font-family: YouSheBiaoTiHei;//   font-weight: 400;//   font-size: 22px;//   color: #FFFFFF;//   background: linear-gradient(0deg, #297DEF 0%, #0A5CCC 100%);//   -webkit-background-clip: text;//   -webkit-text-fill-color: transparent;// }}
}.chat-window.half-width {width: 60%;
}.messages {flex: 1;overflow-y: auto;padding: 24px;
}.messages::-webkit-scrollbar {width: 6px;}.messages::-webkit-scrollbar-thumb {background-color: #dbdada;;border-radius: 3px;}.messages::-webkit-scrollbar-thumb:hover {background-color: #d3d2d2;}.messages::-webkit-scrollbar-track {background-color: #f1f1f1;border-radius: 3px;}.message {display: flex;gap: 16px;margin-bottom: 24px;
}.message.user {flex-direction: row-reverse;
}/* 头像样式 */
.avatar {width: 40px;height: 40px;border-radius: 50%;flex-shrink: 0;overflow: hidden;
}.avatar img {width: 100%;height: 100%;object-fit: cover;
}.bot-avatar {background: #e8f4ff;
}.user-avatar {background: #f0f2f5;
}/* 消息气泡 */
.message-content {max-width: 70%;min-width: 120px;
}.message-bubble {border-radius: 20px;position: relative;animation: fadeIn 0.3s ease;
}.message.user .message-bubble {background: #eff6ff;color: rgb(0, 0, 0);height: 39px;line-height: 39px;white-space: pre-wrap;word-break: break-word;border-radius: 20px 20px 4px 20px;
}.message.bot .message-bubble {background: #fff;text-align: left;color: rgba(0, 0, 0, .85) !important;padding: 20px;border: 1px solid #e0e0e0;border-radius: 20px 20px 20px 4px;box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}.meta-info {display: flex;justify-content: space-between;padding: 4px 8px;font-size: 12px;color: #999;
}.status {color: #27ae60;
}.input-area {display: flex;gap: 12px;padding: 20px;border-top: 1px solid #eee;background: #fff;
}.message-input {flex: 1;padding: 14px 20px;border: 2px solid #eee;border-radius: 10px;font-size: 16px;transition: all 0.3s;
}.message-input:focus {border-color: #3498db;box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1);outline: none;
}.send-btn {padding: 0 28px;border: none;background: #3498db;color: white;border-radius: 10px;cursor: pointer;transition: all 0.3s;display: flex;align-items: center;
}.send-btn:disabled {background: #a0cfff;cursor: not-allowed;
}.send-btn:hover:not(:disabled) {background: #2980b9;transform: translateY(-1px);
}.loading-dots {display: flex;gap: 4px;padding: 0 8px;
}.dot {width: 6px;height: 6px;background: #fff;border-radius: 50%;animation: bounce 1.4s infinite;
}.dot:nth-child(2) {animation-delay: 0.2s;
}.dot:nth-child(3) {animation-delay: 0.4s;
}.map-panel {width: 40%;background: #fff;border-left: 1px solid #eee;display: flex;flex-direction: column;
}.chat-Knowledge {width: 40%;background: linear-gradient(177deg, #F8F7FD, #DFEAFE);border-left: 1px solid #eee;display: flex;flex-direction: column;}.chatnowledgeButton {width: 114px;height: 32px;background-color: #ebebeb;color: #000000;font-family: PingFang SC;font-weight: 500;font-size: 16px;line-height: 32px;color: #1A1A1A;border-radius: 5px;cursor: pointer;
}.map-header {display: flex;justify-content: space-between;align-items: center;padding: 0px 10px;border-bottom: 1px solid #eee;
}.close-btn {border: none;background: none;font-size: 24px;color: #666;cursor: pointer;padding: 0 8px;
}.map-container {flex: 1;padding: 20px;
}.map-placeholder {height: 100%;background: #f8f9fa;border-radius: 12px;display: flex;align-items: center;justify-content: center;color: #666;
}.fab-container {position: fixed;bottom: 30px;right: 30px;display: flex;gap: 12px;
}.map-fab {width: 56px;height: 56px;border: none;border-radius: 50%;background: #3498db;color: white;font-size: 24px;cursor: pointer;box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);transition: all 0.3s;
}.map-fab:hover {background: #2980b9;transform: translateY(-2px);
}/* 动画 */
@keyframes fadeIn {from {opacity: 0;transform: translateY(10px);}to {opacity: 1;transform: translateY(0);}
}@keyframes bounce {0%,80%,100% {transform: translateY(0);}40% {transform: translateY(-4px);}
}@keyframes cursorBlink {0% {opacity: 1;}50% {opacity: 0;}100% {opacity: 1;}
}@keyframes stream-appear {from {opacity: 0;transform: translateY(5px);}to {opacity: 1;transform: translateY(0);}
}.stream-chunk {animation: stream-appear 0.3s ease-out;
}.cursor {animation: cursorBlink 1s infinite;font-weight: bold;margin-left: 2px;
}.chat-input-main-container {width: 80%;display: flex;bottom: 0px;position: fixed;justify-content: center;background-color: #fff;
}.back-to-top-button {width: 32px;height: 32px;border-radius: 50%;cursor: pointer;display: flex;justify-content: center;align-items: center;border: 1px solid rgba(0, 0, 0, 0.1);background-color: #fff;position: absolute;top: -12px;transform: translateY(-100%);
}.back-to-top-button:hover {background-color: #F6F7F9;
}.back-to-top-button svg {width: 16px;height: 16px;color: #000;
}.chat-input-section {z-index: 9;position: relative;width: 100%;max-width: 866px;padding-left: 16px;padding-bottom: 12px;display: flex;flex-direction: column;align-items: center;gap: 8px;
}.chat-input-container {width: 100%;position: relative;border-radius: 24px;border: 1px solid rgba(0, 0, 0, 0.1);background-color: white;box-shadow: rgba(0, 0, 0, 0.1) 0px 2px 6px 0px;box-sizing: border-box;
}.chat-input-content {width: 97%;padding: 12px;background-color: #fff;overflow: hidden;border-radius: 24px;
}.chat-input-wrapper {width: 100%;display: flex;flex-direction: column;gap: 16px;
}.chat-input-textarea {padding-left: 4px;width: 100%;
}.chat-input-textarea textarea {width: 100%;font-size: 16px;line-height: 1.375;resize: none;word-break: break-all;overflow-x: hidden;border: none;outline: none;min-height: 22px;max-height: 88px;padding: 0;background-color: transparent;color: #333;}.chat-input-bottom {display: flex;align-items: flex-end;justify-content: space-between;width: 100%;
}.chat-input-feature-buttons {display: flex;align-items: flex-end;justify-content: flex-start;gap: 8px;}.chat-input-feature-button {padding-left: 10px;padding-right: 10px;height: 32px;display: flex;align-items: center;border-radius: 50px;cursor: pointer;background-color: #f0f0f0;}.chat-input-feature-button:hover {background-color: #f0f0f0;}.chat-input-feature-button svg {width: 20px;height: 20px;margin-right: 6px;
}.chat-input-feature-button span {color: #333;font-size: 14px;font-weight: bold;white-space: nowrap;
}.chat-input-tools {display: flex;align-items: center;position: relative;top: 4px;
}.chat-input-tool-button {display: flex;align-items: center;border-radius: 50px;cursor: pointer;color: #333;background-color: transparent;border: none;padding: 8px;
}.chat-input-tool-button:hover {background-color: rgba(0, 0, 0, 0.1);}.chat-input-tool-button svg {width: 24px;height: 24px;
}.chat-input-tool-microphone {border-radius: 50px;transition: background-color 0.3s;padding: 4px;
}.chat-input-tool-microphone:hover {background-color: rgba(0, 0, 0, 0.1);}.chat-input-tool-microphone svg {width: 32px;height: 32px;color: #333;
}.chat-input-divider {margin-right: 16px;
}.chat-input-send-button {border-radius: 50px;cursor: pointer;background-color: #000;flex-shrink: 0;width: 32px;height: 32px;display: flex;justify-content: center;align-items: center;pointer-events: none;
}.chat-input-send-button:hover {background-color: #222;
}.chat-input-send-button svg {color: #fff;width: 14px;height: 17px;
}.chat-input-footer {display: flex;font-size: 12px;color: rgba(51, 51, 51, 0.3);}.chat-input-footer a {color: #006BFF;
}.chat-input-footer a:hover {text-decoration: underline;
}.custom-select {/* 外观设置 */appearance: none;-webkit-appearance: none;-moz-appearance: none;text-align: center;padding: 5px 5px;background: transparent;font-size: 16px;font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;border: none;border-radius: 8px;color: #333333;color: #333;font-size: 14px;font-weight: bold;white-space: nowrap;cursor: pointer;transition: all 0.3s ease;
}.custom-select:hover {//   border-color: #999999; 
}.custom-select:focus {outline: none;
}.custom-select::-ms-expand {display: none;
}.custom-select::after {content: '\25BC';position: absolute;top: 50%;right: 16px;transform: translateY(-50%);color: #999999;pointer-events: none;
}
</style><style lang="less">.markdown-content {p:has(+ p) {margin-bottom: 0.4em;}/* 防止代码块换行 */pre {white-space: pre-wrap;word-break: break-word;}/* 紧凑模式 */&.compact-mode {line-height: 1.5;p,li {margin: 0.4em 0;}pre {margin: 0.8em 0;padding: 0.8em !important;}}/* 动画过渡 */.typing-effect {animation: pulse 1.5s infinite;}@keyframes pulse {0%,100% {opacity: 1}50% {opacity: 0.5}}
}/* 光标闪烁动画 */
.cursor {animation: cursorBlink 1s infinite;margin-left: 2px;color: #333;/* 光标颜色 */
}@keyframes cursorBlink {0%,100% {opacity: 1;}50% {opacity: 0;}
}
</style>

运行结果:

总结

通过使用 Vue 框架重构 RAGFlow,我们成功实现了一个简单的聊天功能。在这个过程中,我们学习了如何使用 Vue 的响应式数据绑定和组件化开发来构建界面,以及如何与后端 API 进行交互。同时,我们也了解了 RAG 流程的基本原理和实现方法。希望本文能够帮助你在实际项目中应用这些技术,实现更加智能和高效的聊天系统。

写这份代码的时候本着研究的心理去写的,写的过程中发现,源码的架构很强,感叹不愧是大厂工程级别的项目,他的思路每一步都感觉有迹可循,在写代码的时候就像是做手术,一层一层去理解它的思路去刨析,很快乐,这个聊天涉及到的历史记录, 多轮对话,助理选择,主要聊天层面的实现方法这里不对外展示了,有需要的可以后台T我,下面附上我的思路表。(其实我主要是学习思路)

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词