在当代 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) return
-
- const content = inputMessage.value.trim()
- inputMessage.value = '' // 立即清空输入框
- loading.value = true
- try {
- let sessionId = currentSessionId.value
-
- // 如果没有当前会话,创建新会话
- if (!sessionId) {
- sessionId = await createConversation(content || selectedFile.value?.name)
- if (!sessionId) {
- loading.value = false
- return
- }
- currentSessionId.value = sessionId
- await 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()
- // 创建 FormData
- const formData = new FormData()
- formData.append('input', JSON.stringify({
- message: chatMessage,
- params: {
- enableVectorStore: enableVectorStore.value,
- enableAgent: enableAgent.value
- }
- }))
-
- // 如果有文件,添加到 FormData
- if (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?.finishReason
- if (responseData.result?.output?.content) {
- aiMessage.textContent += responseData.result.output.content
- messages.value = [...messages.value]
- scrollToBottom()
- }
- if (finishReason && finishReason.toLowerCase() === 'stop') {
- // 保存ai的回复消息
- saveMessage({
- sessionId: aiMessage.sessionId,
- textContent: aiMessage.textContent,
- type: aiMessage.type,
- medias: []
- })
- loading.value = false
- clearFile()
- 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 = false
- clearFile()
- }
- 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 = true
- try {
- 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 谈天助手界面。通过集成历史消息管理、文件上传、向量存储等功能,提供了一个全面的对话平台,满意了用户在一样平常工作中的各种需求。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |