网站开发存在的问题网站友链外链
在现代 web 应用中,集成智能对话功能已经成为提升用户体验的重要手段之一。本文将介绍如何通过 Vue 3 和 Element Plus 构建一个高效的 AI 聊天助手界面,并详细讲解其实现原理和功能。
1. 整体架构
该聊天界面分为 左侧边栏 和 右侧内容区域,实现了清晰的布局结构,左侧边栏用于展示历史会话和各种功能开关,右侧内容区域用于展示当前会话的消息和输入框。系统支持以下功能:
- 向量存储:可以开启以便访问已上传的文档内容。
- Agent功能:允许开启自动化代理功能来增强对话能力。
- 数据分析:用户可以上传文件进行分析,并在分析后通过向量存储访问文件内容。
此外,用户还可以查看和管理历史会话、快速开始对话、上传文件等。
2. 左侧边栏:管理与功能开关
左侧边栏包含了三个主要部分:
- Logo与新建会话:展示应用的Logo并支持创建新会话。
- 功能开关:包括启用向量存储、启用Agent功能以及文件上传进行数据分析的功能开关。这些功能开关都使用了 Element Plus 的
el-switch
组件,允许用户根据需求自由开关。 - 历史消息列表:展示用户的历史会话,用户可以点击每个会话查看之前的消息,并删除不需要的会话。
<div class="sidebar"><div class="sidebar-header"><div class="logo"><img src="@/assets/logo.png" alt="Logo" class="logo-img" /><span class="logo-text">AI助手</span></div><el-icon class="new-chat-icon" @click="resetChat"><Plus /></el-icon></div><div class="feature-toggles"><div class="toggle-item"><div class="toggle-label-group"><span class="toggle-label">向量存储</span><el-tooltip content="开启后可以访问已上传的文档内容" placement="right"><el-icon class="info-icon"><InfoFilled /></el-icon></el-tooltip></div><el-switch v-model="enableVectorStore" size="small" active-color="#409EFF" /></div><div class="toggle-item"><span class="toggle-label">Agent</span><el-switch v-model="enableAgent" size="small" active-color="#409EFF" /></div><div class="toggle-item"><div class="toggle-label-group"><span class="toggle-label">数据分析</span><el-tooltip content="上传文件进行分析,分析后可通过向量存储访问文件内容" placement="right"><el-icon class="info-icon"><InfoFilled /></el-icon></el-tooltip></div><el-upload :auto-upload="false" :show-file-list="false" :on-change="handleEmbedding" class="upload-btn-small"><el-button size="small" type="primary" :loading="uploadLoading">上传文件</el-button></el-upload></div></div><div class="history-list"><div class="history-group"><div class="group-title">历史消息</div><div v-for="item in conversations" :key="item.sessionId" class="chat-item" :class="{ active: currentSessionId === item.sessionId }" @click="selectSession(item)"><div class="chat-item-content"><el-icon><ChatLineRound /></el-icon><span class="chat-title">{{ item.name }}</span><span class="chat-time">{{ formatTime(item.createdTime) }}</span></div><div class="chat-item-actions"><el-icon class="delete-icon" @click.stop="handleDelete(item.sessionId)"><Delete /></el-icon></div></div></div></div>
</div>
3. 右侧内容区:展示与互动
右侧内容区展示当前选定会话的详细内容。用户可以看到与 AI 的对话消息,并通过输入框进行新一轮的对话。输入框支持文本输入和文件上传,允许用户在对话中插入文件或直接输入消息内容。
初始页面
当用户没有选择当前会话时,右侧内容区域会显示欢迎信息和热门问答,供用户快速开始与 AI 的对话。
<template v-if="!currentSessionId"><div class="welcome-page"><h1 class="welcome-title">欢迎使用小鹏AI助手</h1><div class="history-qa" v-if="randomHistoryMessages.length > 0"><h2>热门问答</h2><div class="qa-list"><div v-for="(qa, index) in randomHistoryMessages" :key="index" class="qa-item" @click="quickStart(qa.question)"><div class="qa-question"><el-icon><ChatLineRound /></el-icon><span>{{ qa.question }}</span></div><div class="qa-answer">{{ qa.answer }}</div></div></div></div></div>
</template>
对话消息区
当用户选择了某个会话后,右侧内容区展示历史对话消息,包括用户和 AI 的消息内容。每条消息根据消息类型(如用户消息、AI 回复、系统消息)进行不同的样式渲染。
<template v-else><div class="chat-messages" ref="messagesContainer"><div v-for="message in messages" :key="message.id" class="message-item" :class="message.type.toLowerCase()"><div class="message-content">{{ message.textContent }}</div><div class="message-time">{{ formatMessageTime(message.createdTime) }}</div></div></div>
</template>
输入与发送
输入框允许用户在对话框内输入消息,并支持文件上传。点击发送按钮后,消息将被发送到后端,并实时显示在聊天界面。
<div class="input-section"><div class="input-wrapper"><el-input v-model="inputMessage" :placeholder="currentSessionId ? '继续对话...' : '给 AI助手 发送消息'" class="message-input" size="large" @keyup.enter="sendMessage"><template #suffix><div class="input-actions"><el-upload ref="uploadRef" :auto-upload="false" :show-file-list="false" :on-change="handleFileChange" class="upload-btn"><el-icon :size="20"><Paperclip /></el-icon></el-upload><div class="send-btn" :class="{ active: canSend }" @click="sendMessage"><el-icon><Position /></el-icon></div></div></template></el-input></div><div v-if="selectedFile" class="selected-file"><el-tag closable @close="clearFile" class="file-tag">{{ selectedFile.name }}</el-tag></div>
</div>
4. 数据与状态管理
在代码中,使用了 Vue 3 的 Composition API 来管理组件的状态和响应式数据。这里的状态主要包括用户输入、当前会话、消息内容、文件上传等。
const inputMessage = ref('') // 用户输入的消息
const conversations = ref([]) // 历史会话列表
const currentSessionId = ref(null) // 当前选中的会话 ID
const messages = ref([]) // 当前会话的消息内容
const loading = ref(false) // 加载状态,用于发送消息时显示加载效果
const messagesContainer = ref(null) // 对话内容容器,用于滚动
const uploadRef = ref(null) // 文件上传组件的引用
const selectedFile = ref(null) // 已选择的文件
const eventSource = ref(null) // 用于 SSE 连接的对象
const enableVectorStore = ref(false) // 启用向量存储
const enableAgent = ref(false) // 启用 Agent
const uploadLoading = ref(false) // 文件上传时的加载状态
const randomHistoryMessages = ref([]) // 随机历史消息
5. 历史会话与消息获取
const getConversations = async () => {try {const res = await request({url: '/api/ai/getConversationById',method: 'get',params: { userId: userStore.userId }})if (res.success) {conversations.value = res.data// 获取随机历史消息await getRandomHistoryMessages()} else {ElMessage.error('获取历史会话失败')}} catch (error) {console.error('获取历史会话出错:', error)ElMessage.error('获取历史会话失败')}
}
getConversations
方法用于从后端获取历史会话记录,并通过 conversations
状态更新会话列表。如果获取失败,会显示错误信息。
同理,getMessages
用来获取特定会话的消息内容。
6. 消息发送与文件上传
const sendMessage = async () => {if ((!inputMessage.value.trim() && !selectedFile.value) || loading.value) returnconst content = inputMessage.value.trim()inputMessage.value = '' // 立即清空输入框loading.value = truetry {let sessionId = currentSessionId.value// 如果没有当前会话,创建新会话if (!sessionId) {sessionId = await createConversation(content || selectedFile.value?.name)if (!sessionId) {loading.value = falsereturn}currentSessionId.value = sessionIdawait getConversations()}// 准备消息数据const chatMessage = {sessionId: sessionId.toString(),textContent: content,type: 'user',medias: [],userId: userStore.userId}// 添加用户消息到界面messages.value.push({...chatMessage,createdTime: new Date().toISOString()})// 添加AI消息占位const aiMessage = {id: (Date.now() + 1).toString(),type: 'assistant',textContent: '',createdTime: new Date().toISOString(),sessionId: sessionId.toString()}messages.value.push(aiMessage)await scrollToBottom()// 创建 FormDataconst formData = new FormData()formData.append('input', JSON.stringify({message: chatMessage,params: {enableVectorStore: enableVectorStore.value,enableAgent: enableAgent.value}}))// 如果有文件,添加到 FormDataif (selectedFile.value) {formData.append('file', selectedFile.value)}// 关闭之前的连接if (eventSource.value) {eventSource.value.close()}// 创建 SSE 连接eventSource.value = new SSE('/api/ai/chat', {headers: {Accept: 'text/event-stream',},method: 'POST',payload: formData})eventSource.value.onmessage = (event) => {try {const responseData = JSON.parse(event.data)const finishReason = responseData.result?.metadata?.finishReasonif (responseData.result?.output?.content) {aiMessage.textContent += responseData.result.output.contentmessages.value = [...messages.value]scrollToBottom()}if (finishReason && finishReason.toLowerCase() === 'stop') {// 保存ai的回复消息saveMessage({sessionId: aiMessage.sessionId,textContent: aiMessage.textContent,type: aiMessage.type,medias: []})loading.value = falseclearFile()eventSource.value?.close()}} catch (error) {console.error('解析消息出错:', error)}}eventSource.value.onerror = (error) => {console.error('SSE 连接错误:', error)if (aiMessage.textContent === '') {aiMessage.textContent = '消息处理出错,请重试'}eventSource.value?.close()loading.value = falseclearFile()}eventSource.value.onopen = () => {console.log('SSE 连接已建立')}} catch (error) {console.error('发送消息失败:', error)ElMessage.error('发送消息失败,请重试')loading.value = false}
}
sendMessage
是处理用户输入的核心函数。当用户输入消息并点击发送时,函数首先判断是否有有效的输入(文本或文件),然后处理消息的发送,包括创建新的会话、更新界面上的消息以及建立与后端的 SSE(服务器推送事件)连接。
SSE 连接的目的是接收 AI 模型的响应,并实时更新对话内容。AI 回复的内容通过 onmessage
事件监听器获取,并动态地将其添加到消息列表中。
7. 文件上传与处理
const handleEmbedding = async (file) => {uploadLoading.value = truetry {const formData = new FormData()formData.append('file', file.raw)const res = await request({url: '/api/ai/embedding',method: 'post',data: formData,headers: {'Content-Type': 'multipart/form-data'}})if (res.success) {ElMessage.success('文件分析成功,现在可以开启向量存储来访问文件内容')// 自动开启向量存储enableVectorStore.value = true} else {ElMessage.error('文件分析失败')}} catch (error) {console.error('文件分析失败:', error)ElMessage.error('文件分析失败')} finally {uploadLoading.value = false}
}
handleEmbedding
方法处理文件上传。文件被上传到后端进行分析,分析后开启向量存储功能,用户可以通过此功能访问文件中的内容。上传过程使用了 FormData
来处理文件数据,并通过 multipart/form-data
请求头发送。
8. 滚动和视图更新
const scrollToBottom = async () => {await nextTick()if (messagesContainer.value) {const scrollOptions = {top: messagesContainer.value.scrollHeight,behavior: 'smooth'}messagesContainer.value.scrollTo(scrollOptions)}
}
scrollToBottom
方法确保消息列表在更新后自动滚动到最新消息。这是通过访问 messagesContainer
的 DOM 节点,并设置其 scrollTop
属性实现的。
总结
本系统利用 Vue 3 和 Element Plus 实现了一个高效、互动性强的 AI 聊天助手界面。通过集成历史消息管理、文件上传、向量存储等功能,提供了一个全面的对话平台,满足了用户在日常工作中的各种需求。