拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Vite+Vue前端项目集成DeepSeek AI助手:从架构设计到实战实现

1. 项目缘起为什么要在前端项目中集成AI助手最近在重构一个内部管理后台技术栈是Vite 8.0 Vue 3.5 Arco Design。项目里有很多表单填报、数据查询和文档生成的场景团队成员经常需要反复查阅API文档、复制粘贴样板代码或者手动整理一些格式化的数据。这种重复性劳动不仅效率低下还容易出错。正好看到DeepSeek网页版AI助手开放了API而且模型能力比如DeepSeek-V4 Flash在代码生成、文本理解和逻辑推理上表现不错我就琢磨着能不能把这个AI能力直接“缝合”到我们的前端项目里这个想法不是凭空来的。现在很多开发工具都集成了AI辅助比如GitHub Copilot、Cursor但它们要么是IDE插件要么是独立的聊天窗口。我想做的是在我们自己的业务系统里让AI助手成为一个“原生”的功能模块。比如用户在填写一个复杂的配置表单时旁边有个悬浮窗可以直接问AI“这个字段的格式要求是什么”或者“根据当前数据帮我生成一段JSON配置”。再比如开发者在看一段晦涩的日志时能选中文本右键一键让AI帮忙分析错误原因。这不仅仅是加一个聊天机器人那么简单。它涉及到如何在前端工程化框架ViteVue下优雅地管理AI对话的状态、处理流式响应、设计UI组件用Arco Design保证风格统一以及处理各种边界情况比如网络超时、token限制、上下文管理。市面上虽然有一些现成的SDK或UI库但要么太重要么定制化程度不够很难完美融入我们现有的技术栈和产品设计语言。所以我决定自己动手从零开始搞一套深度集成的方案。2. 技术选型与架构设计为什么是Vite 8.0 Vue 3.5 Arco在做技术选型时我主要考虑了性能、开发体验和生态兼容性。Vite 8.0作为下一代前端构建工具其基于ESM的按需编译和闪电般的HMR热模块替换体验对于需要频繁调试AI交互界面的项目来说是巨大的效率提升。相比WebpackVite在启动和热更新上的优势是碾压性的这让我们在调整AI组件样式和逻辑时几乎能做到“所见即所得”。Vue 3.5则带来了更稳定、性能更好的组合式APIComposition API体验。对于AI助手这种状态复杂、交互频繁的模块用script setup语法配合ref、computed、watchEffect等响应式API来管理对话历史、加载状态、流式消息片段代码会非常清晰和易于维护。Vue 3.5对TypeScript的支持也更为完善这对于调用DeepSeek API这类强类型交互的场景至关重要能有效减少运行时错误。UI框架选择Arco Design主要是出于两方面的考虑。一是设计语言统一我们整个后台系统都用的Arco引入AI助手组件必须保持视觉和交互的一致性。二是Arco组件库功能丰富且质量高像Modal、Drawer、Popover、Message、Input、Button这些组件我们直接拿过来就能用再配合其强大的Grid和Flex布局系统可以快速搭建出美观且实用的AI聊天界面。自己从零造轮子不仅耗时还很难达到同样的细节水准。整个集成的架构思路是“松耦合高内聚”。AI助手核心逻辑API调用、状态管理被封装成一个独立的Vue组合式函数Composable例如useDeepSeekChat。这个函数不依赖任何UI只负责业务逻辑。然后我们再基于这个Composable和Arco Design的组件封装出几个即插即用的UI组件比如DeepSeekChatDrawer侧边栏聊天抽屉、DeepSeekInlineAssistant行内助手。这样业务页面只需要引入对应的UI组件并通过props传入必要的配置如API Key、初始提示词就能获得完整的AI能力而无需关心底层实现。3. 核心实现从API对接到流式聊天界面3.1 环境准备与依赖安装首先我们需要一个DeepSeek的API Key。去DeepSeek官网注册账号并在控制台创建一个API Key。非常重要的一点前端直接暴露API Key是极度危险的任何用户都可以通过浏览器开发者工具窃取它。因此我们绝不能把API Key硬编码在前端代码里。正确的做法是通过我们自己的后端服务来代理对DeepSeek API的调用。前端只与我们自己的后端接口通信由后端负责添加API Key并转发请求。这样API Key就安全地保存在服务器端了。假设我们的后端已经提供了一个接口POST /api/chat/deepseek它接收前端传来的消息数组然后加上API Key去调用DeepSeek的官方接口并将结果流式或非流式地返回给前端。前端项目需要安装的依赖不多主要是处理HTTP请求和流式响应pnpm add axios # 或者如果你更喜欢 fetch API现代浏览器已原生支持无需额外安装我们选择使用原生的fetch来处理流式响应因为它对ReadableStream的支持非常直接。同时为了管理应用状态我们可以使用Vue 3内置的ref和computed无需引入Pinia或Vuex除非你的项目其他地方已经大规模使用。3.2 封装核心聊天逻辑useDeepSeekChat这是整个项目的“大脑”。我们创建一个composables/useDeepSeekChat.ts文件。// composables/useDeepSeekChat.ts import { ref, computed, watchEffect } from vue; export interface Message { role: user | assistant | system; content: string; } export interface UseDeepSeekChatOptions { apiEndpoint: string; // 你自己的后端接口地址例如 /api/chat/deepseek initialMessages?: Message[]; model?: string; // 例如 deepseek-chat temperature?: number; maxTokens?: number; } export function useDeepSeekChat(options: UseDeepSeekChatOptions) { const { apiEndpoint, initialMessages [], model deepseek-chat, temperature 0.7, maxTokens 2048, } options; // 状态定义 const messages refMessage[]([...initialMessages]); const input ref(); const isLoading ref(false); const error refstring | null(null); const abortController refAbortController | null(null); // 计算属性最后几条消息作为上下文注意token限制 const contextMessages computed(() { // 简单的实现返回所有消息。生产环境需要根据token数进行截断 return messages.value; }); // 核心方法发送消息 const sendMessage async (content?: string) { const messageContent content || input.value.trim(); if (!messageContent || isLoading.value) return; // 添加用户消息 const userMessage: Message { role: user, content: messageContent }; messages.value.push(userMessage); input.value ; // 清空输入框 error.value null; isLoading.value true; // 准备请求体 const requestBody { model, messages: contextMessages.value, temperature, max_tokens: maxTokens, stream: true, // 开启流式响应 }; // 创建AbortController以便可以取消请求 abortController.value new AbortController(); try { const response await fetch(apiEndpoint, { method: POST, headers: { Content-Type: application/json, // 注意这里不传API Key由后端处理 }, body: JSON.stringify(requestBody), signal: abortController.value.signal, }); if (!response.ok) { throw new Error(HTTP error! status: ${response.status}); } // 创建助理消息初始内容为空 const assistantMessage: Message { role: assistant, content: }; messages.value.push(assistantMessage); // 处理流式响应 const reader response.body?.getReader(); if (!reader) throw new Error(Failed to get response reader); const decoder new TextDecoder(utf-8); let done false; while (!done) { const { value, done: readerDone } await reader.read(); done readerDone; if (value) { const chunk decoder.decode(value); // 流式数据通常是多个data: {...}行 const lines chunk.split(\n).filter(line line.trim() ! ); for (const line of lines) { if (line.startsWith(data: )) { const data line.slice(6); // 去掉data: 前缀 if (data [DONE]) { break; } try { const parsed JSON.parse(data); const delta parsed.choices[0]?.delta?.content || ; // 将流式片段追加到最后一条助理消息 assistantMessage.content delta; // 触发响应式更新因为我们在修改对象属性需要通知Vue messages.value [...messages.value]; } catch (e) { console.error(Failed to parse stream data:, e, Data:, data); } } } } } } catch (err: any) { if (err.name AbortError) { console.log(Request aborted); // 如果被取消可以添加一个提示信息 messages.value.push({ role: assistant, content: [对话已中断] }); } else { error.value 请求失败: ${err.message}; console.error(Chat request failed:, err); } } finally { isLoading.value false; abortController.value null; } }; // 方法停止生成 const stopGenerating () { if (abortController.value) { abortController.value.abort(); } }; // 方法重置对话 const resetConversation () { messages.value [...initialMessages]; error.value null; stopGenerating(); }; return { // 状态 messages, input, isLoading, error, // 方法 sendMessage, stopGenerating, resetConversation, }; }这个Composable做了几件关键的事状态管理集中管理消息列表、输入内容、加载状态和错误信息。流式处理使用fetch和ReadableStream处理DeepSeek API返回的流式数据实现了打字机效果。可取消性通过AbortController支持用户中途停止生成提升交互体验。灵活性通过options参数允许外部配置API端点、模型参数等。注意这里的contextMessages计算属性实现得很简单。在实际生产中你需要计算消息的token总数可以使用tiktoken等库在前端估算或由后端处理并确保不超过模型的最大上下文长度例如DeepSeek-V4 Flash是128K。一个常见的策略是保留最新的N条消息或者从后往前累加直到token数接近上限。3.3 构建聊天UI组件基于Arco Design有了核心逻辑接下来我们用Arco Design的组件来搭建界面。我们创建一个可复用的侧边栏聊天抽屉组件components/DeepSeekChatDrawer.vue。!-- components/DeepSeekChatDrawer.vue -- template a-drawer :visiblevisible :width480 titleDeepSeek AI助手 placementright unmount-on-close cancelhandleClose div classchat-container !-- 消息列表区域 -- div refmessagesContainer classmessages-area div v-for(msg, index) in chat.messages :keyindex classmessage-wrapper :classmsg.role div classmessage-bubble div classmessage-role{{ msg.role user ? 你 : AI助手 }}/div div classmessage-content !-- 使用v-html渲染Markdown注意安全确保内容可信 -- div v-ifmsg.role assistant v-htmlrenderMarkdown(msg.content)/div div v-else{{ msg.content }}/div /div /div /div div v-ifchat.isLoading classthinking-indicator a-spin sizesmall / span stylemargin-left: 8pxAI正在思考.../span /div div v-ifchat.error classerror-message a-alert typeerror :messagechat.error show-icon / /div /div !-- 输入区域 -- div classinput-area a-textarea v-modelchat.input placeholder输入您的问题... :auto-size{ minRows: 1, maxRows: 4 } press-enterhandleSend :disabledchat.isLoading / div classinput-actions a-button typeprimary :loadingchat.isLoading clickhandleSend :disabled!canSend 发送 /a-button a-button v-ifchat.isLoading clickchat.stopGenerating停止生成/a-button a-button clickchat.resetConversation清空对话/a-button /div /div /div /a-drawer /template script setup langts import { computed, nextTick, ref, watch } from vue; import { useDeepSeekChat, type Message } from ../composables/useDeepSeekChat; import { marked } from marked; // 用于渲染Markdown interface Props { visible: boolean; apiEndpoint: string; initialPrompt?: string; } const props definePropsProps(); const emit defineEmits{ (e: update:visible, value: boolean): void; }(); // 初始化聊天逻辑 const chat useDeepSeekChat({ apiEndpoint: props.apiEndpoint, initialMessages: props.initialPrompt ? [{ role: system, content: props.initialPrompt } as Message] : [], }); const messagesContainer refHTMLElement(); // 自动滚动到底部 const scrollToBottom () { nextTick(() { if (messagesContainer.value) { messagesContainer.value.scrollTop messagesContainer.value.scrollHeight; } }); }; // 监听消息变化自动滚动 watch(() chat.messages.length, scrollToBottom, { deep: true }); watch(() chat.isLoading, scrollToBottom); const canSend computed(() { return chat.input.trim().length 0 !chat.isLoading; }); const handleSend () { if (!canSend.value) return; chat.sendMessage(); }; const handleClose () { emit(update:visible, false); }; // 简单的Markdown渲染生产环境需考虑XSS防护例如使用DOMPurify const renderMarkdown (text: string) { return marked.parse(text); }; /script style scoped .chat-container { display: flex; flex-direction: column; height: 100%; } .messages-area { flex: 1; overflow-y: auto; padding: 16px; margin-bottom: 16px; border: 1px solid var(--color-border); border-radius: var(--border-radius-medium); } .message-wrapper { margin-bottom: 16px; } .message-wrapper.user { text-align: right; } .message-bubble { display: inline-block; max-width: 85%; text-align: left; padding: 12px 16px; border-radius: 18px; background-color: var(--color-fill-2); } .message-wrapper.user .message-bubble { background-color: var(--color-primary-light-1); color: white; } .message-role { font-size: 12px; color: var(--color-text-3); margin-bottom: 4px; } .message-content { line-height: 1.6; } .message-content :deep(pre) { background-color: var(--color-fill-1); padding: 12px; border-radius: 6px; overflow-x: auto; } .message-content :deep(code) { font-family: Monaco, Menlo, monospace; padding: 2px 4px; border-radius: 3px; background-color: var(--color-fill-1); } .thinking-indicator { display: flex; align-items: center; color: var(--color-text-3); padding: 12px; } .error-message { margin-top: 12px; } .input-area { border-top: 1px solid var(--color-border); padding-top: 16px; } .input-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 12px; } /style这个组件充分利用了Arco Design的Drawer、Textarea、Button、Spin、Alert等组件快速构建了一个风格统一、功能完整的聊天界面。它通过visible属性控制显示/隐藏可以轻松地在任何父组件中调用。3.4 在业务页面中集成使用现在我们可以在任意一个Vue页面中引入并使用这个AI助手了。例如在一个数据报表页面ReportPage.vue中!-- views/ReportPage.vue -- template div classreport-page h1销售数据报表/h1 !-- 页面其他内容... -- a-button typeprimary clickshowChat true template #iconicon-question-circle //template 打开AI助手 /a-button !-- AI助手抽屉 -- DeepSeekChatDrawer v-model:visibleshowChat :api-endpoint/api/chat/deepseek :initial-prompt你是一个数据分析专家擅长解读图表和销售数据。请用简洁、专业的语言回答用户关于当前报表的问题。 / /div /template script setup langts import { ref } from vue; import DeepSeekChatDrawer from /components/DeepSeekChatDrawer.vue; import { IconQuestionCircle } from arco-design/web-vue/es/icon; const showChat ref(false); /script就这样我们在业务页面中添加了一个按钮点击后就会从右侧滑出AI助手抽屉。并且我们通过initial-prompt给AI设定了一个“数据分析专家”的角色让它能更好地理解页面上下文并给出专业回答。4. 进阶优化与实战踩坑记录4.1 上下文管理与Token限制的平衡这是集成大模型时最常遇到的问题。DeepSeek-V4 Flash虽然有128K的超长上下文但并不意味着我们可以无脑地把所有对话历史都传过去。一方面这会消耗大量token增加成本另一方面过长的上下文可能会干扰模型对最近问题的关注。我的解决方案是实现一个智能的上下文窗口系统提示词固定始终保留最初的system提示词它定义了AI的角色和任务。最近对话优先保留最近N轮例如10轮的user和assistant对话。关键信息摘要对于更早的对话不再发送原始文本而是由AI或后端服务生成一个简短的摘要summary然后将这个摘要作为一条system消息插入到上下文开头。这样AI就能知道之前的对话梗概而不必记住所有细节。Token计数在发送请求前粗略估算一下上下文消息的token总数可以用Web端轻量级的库如js-tiktoken。如果超过一个安全阈值比如100K就触发摘要生成或更激进地裁剪最老的消息。在实际代码中我们可以增强useDeepSeekChat中的contextMessages计算逻辑// 伪代码展示思路 const contextMessages computed(() { const allMessages [...messages.value]; let tokenCount estimateTokens(allMessages); // 估算token的函数 const MAX_TOKENS 100000; // 安全阈值 if (tokenCount MAX_TOKENS) { return allMessages; } // 如果超了开始裁剪 const result: Message[] []; // 1. 永远保留第一条系统提示 if (allMessages[0]?.role system) { result.push(allMessages[0]); tokenCount estimateTokens(result); } // 2. 从后往前最新的消息添加直到接近阈值 for (let i allMessages.length - 1; i 0; i--) { const msg allMessages[i]; if (msg.role system i 0) continue; // 跳过已添加的系统提示 const msgTokens estimateTokens([msg]); if (tokenCount msgTokens MAX_TOKENS) { break; } result.unshift(msg); // 加到结果前面保持顺序 tokenCount msgTokens; } // 3. 如果裁剪后还是很长可以考虑在这里插入一个“历史摘要”消息 return result; });4.2 流式响应的用户体验细节优化流式响应虽然带来了“打字机”效果但处理不好会让用户体验打折扣。坑点一中文乱码或断字在fetch流式读取时如果网络数据包不是按完整字符边界分割的直接用TextDecoder解码可能会导致中文字符被截断出现乱码。一个更稳健的方法是使用TextDecoder的stream模式或者将多个chunk缓冲起来再解码。// 改进后的解码逻辑示例 const decoder new TextDecoder(utf-8); let buffer ; // ... 在while循环内 if (value) { buffer decoder.decode(value, { stream: true }); // 使用stream模式 const lines buffer.split(\n); // 最后一行可能是不完整的保留在buffer中 buffer lines.pop() || ; for (const line of lines) { // 处理每一行完整的数据 if (line.startsWith(data: )) { ... } } } // 循环结束后处理buffer中剩余的数据 if (buffer) { // 处理剩余数据 }坑点二代码块等格式内容的渲染AI回复中经常包含代码块。如果等整个流式响应结束再一次性渲染Markdown就会失去“逐字打印”的效果。如果每个字符都重新解析和渲染整个Markdown性能又会很差。我的折中方案是将流式接收到的原始文本直接追加到assistantMessage.content。在模板中使用一个自定义指令或组件来“渐进式”渲染Markdown。这个组件会监听内容变化但不会在每次字符追加时都进行完整的Markdown解析和DOM重绘。可以设置一个防抖例如每200毫秒或者只在检测到换行符、代码块标记等特定边界时才触发一次解析和渲染。这样既能保持一定的流式效果又能保证代码块等高亮格式的正确显示同时性能可控。4.3 错误处理与重试机制网络请求总有可能失败。除了基本的错误状态展示我们还需要更健壮的机制。超时控制在fetch请求中设置timeout通过AbortController实现。const timeoutId setTimeout(() { abortController.value?.abort(); error.value 请求超时请重试; }, 30000); // 30秒超时 // 在finally块中 clearTimeout(timeoutId)自动重试对于网络错误如5xx服务器错误、网络断开可以实现一个带指数退避的重试逻辑。但注意对于4xx错误如API Key无效、参数错误不应该重试。const MAX_RETRIES 3; let retryCount 0; const sendMessageWithRetry async () { while (retryCount MAX_RETRIES) { try { await sendMessageLogic(); // 你的发送逻辑 break; // 成功则跳出循环 } catch (err) { if (isRetryableError(err) retryCount MAX_RETRIES - 1) { retryCount; const delay Math.pow(2, retryCount) * 1000; // 指数退避 await new Promise(resolve setTimeout(resolve, delay)); continue; } else { throw err; // 重试次数用完或不可重试错误抛出 } } } };优雅降级如果AI服务完全不可用可以考虑提供一个友好的降级界面比如显示一些预设的常见问题解答FAQ或者引导用户通过其他渠道获取帮助。4.4 安全与权限考量API Key保护再次强调绝对不要在前端代码、环境变量或网络请求中暴露你的DeepSeek API Key。必须通过你自己的后端服务进行代理。请求频率限制Rate Limiting在你的后端代理接口上要对用户进行频率限制防止恶意刷API导致费用暴涨。可以根据用户ID、IP或Token来实施限流。内容审核可选如果应用面向公众需要考虑对用户输入和AI输出进行内容安全过滤防止生成不当内容。这可以在后端代理层实现。对话隔离确保不同用户的对话上下文是隔离的不能混肴。这通常通过后端会话管理来实现。5. 扩展思路不止于聊天抽屉将AI助手深度集成意味着它可以以多种形态出现在产品的不同角落而不仅仅是一个独立的聊天窗口。行内助手Inline Assistant 创建一个DeepSeekInlineAssistant组件它可以嵌入到表单输入框旁边。当用户聚焦到某个复杂字段时这个助手可以自动给出填写建议或者用户点击一个问号图标弹出一个小浮窗进行问答。这需要组件能够获取当前输入域的上下文比如字段名、相邻字段的值作为提示词的一部分。代码编辑器增强 如果你的项目有代码编辑器如Monaco Editor可以集成AI能力实现“代码解释”、“代码优化建议”、“生成测试用例”等功能。监听编辑器中的选中文本通过右键菜单或快捷键触发AI分析。数据洞察生成 在图表组件旁边添加一个“AI解读”按钮。点击后将当前图表的配置和数据可以简化或摘要发送给AI让它生成一段自然语言的分析报告帮助用户快速理解数据趋势。与项目状态联动 让AI助手“感知”应用状态。例如当用户在“错误日志”页面时AI助手的初始提示词可以自动变为“你是一个运维专家擅长分析系统日志和错误信息”。这可以通过Vue的Provide/Inject或状态管理库将当前路由、页面类型等信息传递给AI助手组件来实现。实现这些扩展的关键在于我们最初设计的useDeepSeekChat这个Composable是UI无关的。我们可以基于它轻松地封装出适应不同场景的UI组件每个组件负责收集自己所在上下文的特定信息并组装成合适的提示词然后调用统一的聊天逻辑。这种架构保证了核心能力的复用和一致性。整个集成过程下来最大的体会是把强大的AI模型变成好用的产品功能技术实现只是一部分更重要的是对用户场景的深入思考和细腻的交互设计。从简单的聊天框到无处不在的智能辅助这条路还很长但ViteVueArco这套技术栈给了我们快速迭代和探索的资本。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门