From 8ac1ab06bec299a0bce00b39909c39f185d0defb Mon Sep 17 00:00:00 2001 From: jinhhanhan <1683105490@qq.com> Date: Thu, 22 Jan 2026 10:44:53 +0800 Subject: [PATCH] =?UTF-8?q?chore=EF=BC=9AH5=E5=92=8C=E5=BE=AE=E4=BF=A1?= =?UTF-8?q?=E5=B0=8F=E7=A8=8B=E5=BA=8F=E9=83=BD=E5=8F=AF=E4=BB=A5=E6=AD=A3?= =?UTF-8?q?=E5=B8=B8=E8=81=8A=E5=A4=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/js/ai-service.js | 80 +++++------ .../ai-chat-message/ai-chat-message.vue | 124 +++++++++++++----- 2 files changed, 133 insertions(+), 71 deletions(-) diff --git a/common/js/ai-service.js b/common/js/ai-service.js index ae2fa39..f88c7a2 100644 --- a/common/js/ai-service.js +++ b/common/js/ai-service.js @@ -183,64 +183,75 @@ export default { conversation_id: this.getConversationId() || '' }) }); - - if (!response.ok) { - throw new Error(`HTTP ${response.status}`); + + if (!response.ok || !response.body) { + throw new Error('无效响应'); } - if (!response.body) { - throw new Error('响应体不可用'); - } - + const reader = response.body.getReader(); const decoder = new TextDecoder('utf-8'); let buffer = ''; let content = ''; let conversationId = ''; - - function processBuffer(buf, callback) { - const lines = buf.split('\n'); - buf = lines.pop() || ''; + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + buffer += decoder.decode(value, { stream: true }); + + // 按行分割,保留不完整的最后一行 + const lines = buffer.split('\n'); + buffer = lines.pop() || ''; // 未完成的行留到下次 + for (const line of lines) { const trimmed = line.trim(); if (trimmed.startsWith('data:')) { - const jsonStr = trimmed.slice(5).trim(); - if (jsonStr) { - try { + try { + const jsonStr = trimmed.slice(5).trim(); + if (jsonStr && jsonStr !== '[DONE]') { const data = JSON.parse(jsonStr); if (data.event === 'message') { const text = data.answer || data.text || ''; content += text; - callback(text); + if (onChunk) onChunk(text); } if (data.conversation_id) { conversationId = data.conversation_id; } if (data.event === 'message_end') { - // 可选:提前完成 + // 可提前结束 } - } catch (e) { - console.warn('解析流数据失败:', e); } + } catch (e) { + console.warn('解析失败:', e, line); } } } - return buf; } - - while (true) { - const { done, value } = await reader.read(); - if (done) break; - buffer += decoder.decode(value, { stream: true }); - buffer = processBuffer(buffer, (chunk) => { - if (onChunk) onChunk(chunk); - }); + + // 处理最后残留的 buffer(如果有) + if (buffer.trim().startsWith('data:')) { + try { + const jsonStr = buffer.trim().slice(5); + if (jsonStr) { + const data = JSON.parse(jsonStr); + if (data.event === 'message') { + const text = data.answer || ''; + content += text; + if (onChunk) onChunk(text); + } + if (data.conversation_id) { + conversationId = data.conversation_id; + } + } + } catch (e) { + console.warn('最后 buffer 解析失败:', e); + } } - + if (onComplete) { - onComplete({ - content, - conversation_id: conversationId - }); + onComplete({ content, conversation_id: conversationId }); } return { content, conversation_id: conversationId }; } catch (error) { @@ -248,11 +259,6 @@ export default { throw error; } // #endif - - // #ifdef MP-WEIXIN - // 理论上不会执行到这里,但防止 fallback - return this.sendStreamMessage(message, onChunk, onComplete); - // #endif }, /** diff --git a/components/ai-chat-message/ai-chat-message.vue b/components/ai-chat-message/ai-chat-message.vue index c24fab4..3cc9af2 100644 --- a/components/ai-chat-message/ai-chat-message.vue +++ b/components/ai-chat-message/ai-chat-message.vue @@ -810,65 +810,121 @@ export default { }, // 发送流式消息 - async sendStreamMessage(userMessage) { - // 创建流式消息对象 - const streamMessage = { - id: ++this.messageId, - role: 'ai', - type: 'text', - content: '', - timestamp: Date.now(), - isStreaming: true - } - - // 移除加载状态,添加流式消息 - this.messages = this.messages.filter(msg => msg.type !== 'loading') - this.shouldScrollToBottom = true - this.messages.push(streamMessage) - - // 开始流式响应 - await aiService.sendStreamMessage( + // 发送流式消息(自动适配 H5 / 微信小程序) + async sendStreamMessage(userMessage) { + // 创建流式消息对象 + const streamMessage = { + id: ++this.messageId, + role: 'ai', + type: 'text', + content: '', + timestamp: Date.now(), + isStreaming: true + }; + + // 移除加载状态,添加流式消息 + this.messages = this.messages.filter(msg => msg.type !== 'loading'); + this.shouldScrollToBottom = true; + this.messages.push(streamMessage); + + try { + // #ifdef H5 + // ===== H5: 使用 POST + 流式 (fetch readable stream) ===== + await aiService.sendHttpStream( userMessage, - // 流式数据回调 (chunk) => { - streamMessage.content += chunk - this.$forceUpdate() // 或 this.$nextTick() + // 实时更新内容 + streamMessage.content += chunk; + this.$forceUpdate(); // 强制更新视图 }, - // 完成回调:处理对象或字符串 (completeResult) => { + // 流结束回调 let finalContent = ''; let convId = ''; - - // 判断是对象还是字符串 + if (typeof completeResult === 'string') { finalContent = completeResult; } else { finalContent = completeResult.content || ''; - convId = completeResult.conversation_id || ''; // 👈 关键:提取 conversation_id + convId = completeResult.conversation_id || ''; } - - // 更新消息状态 + + // 更新最终内容 streamMessage.isStreaming = false; streamMessage.content = finalContent; - + // 添加操作按钮 streamMessage.actions = [ { id: 1, text: '有帮助', type: 'like' }, { id: 2, text: '没帮助', type: 'dislike' } ]; - - // 保存 conversation_id 到本地 + + // 保存会话 ID if (convId) { + this.currentConversationId = convId; aiService.setConversationId(convId); + this.saveConversationIdToLocal(convId); } - + // 触发事件 this.$emit('ai-response', streamMessage); - this.saveConversationIdToLocal(convId); - this.currentConversationId = convId; } ); - }, + // #endif + + // #ifdef MP-WEIXIN + // ===== 微信小程序: 使用 WebSocket ===== + await aiService.sendStreamMessage( + userMessage, + (chunk) => { + // 实时更新内容 + streamMessage.content += chunk; + this.$forceUpdate(); + }, + (completeResult) => { + // 流结束回调 + let finalContent = completeResult?.content || ''; + let convId = completeResult?.conversation_id || ''; + + // 更新最终内容 + streamMessage.isStreaming = false; + streamMessage.content = finalContent; + + // 添加操作按钮 + streamMessage.actions = [ + { id: 1, text: '有帮助', type: 'like' }, + { id: 2, text: '没帮助', type: 'dislike' } + ]; + + // 保存会话 ID + if (convId) { + this.currentConversationId = convId; + aiService.setConversationId(convId); + this.saveConversationIdToLocal(convId); + } + + // 触发事件 + this.$emit('ai-response', streamMessage); + } + ); + // #endif + } catch (error) { + console.error('流式请求失败:', error); + // 移除流式消息 + this.messages = this.messages.filter(msg => msg.id !== streamMessage.id); + // 显示错误 + const errorMsg = { + id: ++this.messageId, + role: 'ai', + type: 'text', + content: '抱歉,服务暂时不可用,请稍后重试。', + timestamp: Date.now(), + actions: [{ id: 1, text: '重试', type: 'retry' }] + }; + this.messages.push(errorMsg); + this.$emit('ai-response', errorMsg); + } + }, generateAIResponse(userMessage) { const responses = { '你好': '您好!我是AI智能客服,很高兴为您服务!有什么可以帮助您的吗?',