javascript// src/utils/request.jsimport axios from'axios';// 创建Axios实例const request = axios.create({ baseURL: import.meta.env.VITE_AI_API_URL, // 从环境变量获取AI接口地址 timeout: 30000, // 超时时间30秒(AI接口响应可能较慢) headers: {'Content-Type': 'application/json', // 固定请求格式 },});// 请求拦截器:添加鉴权请求头request.interceptors.request.use((config) => {// 从环境变量获取API Key,添加到请求头const apiKey = import.meta.env.VITE_DEEPSEEK_API_KEY;if (apiKey) { config.headers.Authorization = `Bearer ${apiKey}`; } else {thrownewError('请配置DeepSeek API Key(.env文件中)'); }return config; },(error) => {// 请求错误处理returnPromise.reject(error); });// 响应拦截器:统一处理响应异常request.interceptors.response.use((response) => {return response; },(error) => {// 常见异常处理(超时、API Key错误、接口报错)let errorMsg = 'AI请求失败,请稍后再试';if (error.code === 'ECONNABORTED') { errorMsg = '请求超时,请检查网络或重试'; } elseif (error.response?.status === 401) { errorMsg = 'API Key错误或失效,请重新配置'; } elseif (error.response?.status === 429) { errorMsg = '请求过于频繁,请稍后再试'; }returnPromise.reject(newError(errorMsg)); });exportdefault request;
javascript// src/composables/useAIChat.jsimport { ref, shallowRef, triggerRef, onUnmounted } from'vue';import request from'../utils/request';import marked from'marked';exportfunctionuseAIChat() {// 聊天消息列表(用shallowRef优化性能,避免深度监听)const messages = shallowRef([ {role: 'assistant',content: '你好!我是你的智能助手,有什么可以帮你的?',time: newDate().toLocaleTimeString(), }, ]);// 输入框内容const inputValue = ref('');// 加载状态(流式输出时显示)const isLoading = ref(false);// 错误信息const errorMsg = ref('');// 语音输入支持状态const isSpeechSupported = ref('SpeechRecognition'inwindow || 'webkitSpeechRecognition'inwindow);// 发送消息(核心方法,支持流式输出)const sendMessage = async () => {// 输入校验:空消息不发送const content = inputValue.value.trim();if (!content) return;// 添加用户消息到列表 messages.value.push({role: 'user', content,time: newDate().toLocaleTimeString(), }); inputValue.value = ''; // 清空输入框 errorMsg.value = ''; // 清空错误信息 isLoading.value = true;// 添加AI加载中的占位消息const loadingMsgIndex = messages.value.length; messages.value.push({role: 'assistant',content: '',time: newDate().toLocaleTimeString(),isStreaming: true, }); triggerRef(messages); // 手动触发响应式更新try {// 调用AI接口,开启流式输出const response = await request({method: 'POST',data: {model: 'deepseek-chat', // DeepSeek固定模型名 stream: true, // 开启流式输出 messages: [ ...messages.value.slice(0, -1).map((msg) => ({role: msg.role,content: msg.content, })), ], },responseType: 'stream', // 响应类型设为流式 });// 处理流式响应(核心逻辑)const reader = response.data.getReader();const decoder = new TextDecoder('utf-8');let done = false;let buffer = ''; // 缓存不完整的分片数据while (!done) {const { value, done: doneReading } = await reader.read(); done = doneReading;// 解码二进制数据,拼接缓存const chunk = decoder.decode(value || newUint8Array(), { stream: true }); buffer += chunk;// 解析AI返回的分片数据(每行一个JSON对象)const lines = buffer.split('\n').filter((line) => line.trim() !== ''); buffer = lines.pop() || ''; // 保留最后一行不完整的内容,下次拼接// 逐行处理数据,更新AI响应内容 lines.forEach((line) => {try {// 去掉AI接口返回的前缀(data: ),解析JSONconst data = JSON.parse(line.replace(/^data: /, ''));if (data.choices && data.choices[0].delta.content) {// 逐字拼接AI响应内容 messages.value[loadingMsgIndex].content += data.choices[0].delta.content; triggerRef(messages); // 手动触发响应式更新,避免卡顿 } } catch (e) {// 忽略解析错误(部分分片可能不完整)console.log('解析流式数据失败:', e); } }); }// 流式输出完成,移除loading标记 messages.value[loadingMsgIndex].isStreaming = false; triggerRef(messages); } catch (error) {// 错误处理:更新错误信息,替换占位消息 errorMsg.value = error.message; messages.value[loadingMsgIndex].content = `请求失败:${error.message}`; messages.value[loadingMsgIndex].isStreaming = false; triggerRef(messages); } finally {// 结束加载状态 isLoading.value = false; } };// 语音输入(可选功能,基于Web Speech API)const startVoiceInput = () => {if (!isSpeechSupported.value) { errorMsg.value = '当前浏览器不支持语音输入';return; }const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;const recognition = new SpeechRecognition(); recognition.lang = 'zh-CN'; // 中文识别 recognition.interimResults = false; // 不返回中间结果// 语音识别开始 recognition.onstart = () => { errorMsg.value = ''; };// 语音识别完成,获取识别结果 recognition.onresult = (event) => {const result = event.results[0][0].transcript; inputValue.value = result; };// 语音识别错误 recognition.onerror = (event) => { errorMsg.value = `语音识别失败:${event.error}`; };// 语音识别结束 recognition.onend = () => {// 自动发送语音识别结果if (inputValue.value.trim()) { sendMessage(); } };// 开始语音识别 recognition.start(); };// 清空聊天记录const clearChat = () => { messages.value = [ {role: 'assistant',content: '你好!我是你的智能助手,有什么可以帮你的?',time: newDate().toLocaleTimeString(), }, ]; triggerRef(messages); };// 组件卸载时,终止正在进行的语音识别 onUnmounted(() => {if (isSpeechSupported.value) {const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;const recognition = new SpeechRecognition(); recognition.abort(); } });// Markdown渲染(将AI返回的Markdown内容转为HTML)const renderMarkdown = (content) => {return marked.parse(content); };return { messages, inputValue, isLoading, errorMsg, isSpeechSupported, sendMessage, startVoiceInput, clearChat, renderMarkdown, };}