diff --git a/common/js/ai-service.js b/common/js/ai-service.js index 88c4dff..f88c7a2 100644 --- a/common/js/ai-service.js +++ b/common/js/ai-service.js @@ -60,55 +60,107 @@ export default { /** * 流式消息入口(自动适配平台) */ - async sendStreamMessage(message, onChunk, onComplete) { - // #ifdef MP-WEIXIN - // 微信小程序:降级为普通请求 + 前端打字模拟 - try { - const result = await this.sendMessage(message); - const content = result.content || ''; - const conversationId = result.conversationId || ''; - - // 保存会话ID(确保连续对话) - if (conversationId) { - this.setConversationId(conversationId); - } - - // 模拟打字效果 - let index = 0; - const chunkSize = 2; // 每次显示2个字符 - return new Promise((resolve) => { - const timer = setInterval(() => { - if (index < content.length) { - const chunk = content.substring(index, index + chunkSize); - index += chunkSize; - if (onChunk) onChunk(chunk); - } else { - clearInterval(timer); - if (onComplete) { - onComplete({ - content: content, - conversation_id: conversationId - }); - } - resolve({ content, conversation_id: conversationId }); - } - }, 80); // 打字速度:80ms/次 - }); - } catch (error) { - console.error('小程序流式消息降级失败:', error); - if (onComplete) { - onComplete({ error: error.message || '发送失败' }); - } - throw error; - } + async sendStreamMessage(message, onChunk, onComplete) { + // #ifdef MP-WEIXIN + return new Promise((resolve, reject) => { + const socketTask = wx.connectSocket({ + url: 'wss://dev.aigc-quickapp.com/ws/aikefu', + header: {} + }); + + let content = ''; + let conversationId = ''; + let isAuthenticated = false; + + socketTask.onOpen(() => { + console.log('WebSocket 连接成功,开始认证...'); + socketTask.send({ + data: JSON.stringify({ + action: 'auth', + uniacid: store.state.uniacid || '1', + token: store.state.token || 'test_token', + user_id: store.state.memberInfo?.id || 'anonymous' + }) + }); + }); + + socketTask.onMessage((res) => { + try { + const data = JSON.parse(res.data); + console.log('收到 WebSocket 消息:', data); + + if (data.type === 'auth_success') { + console.log('认证成功,发送聊天消息...'); + isAuthenticated = true; + socketTask.send({ + data: JSON.stringify({ + action: 'chat', + uniacid: store.state.uniacid || '1', + query: message, + user_id: store.state.memberInfo?.id || 'anonymous', + conversation_id: this.getConversationId() || '' + }) + }); + } else if (data.type === 'auth_failed') { + const errorMsg = '认证失败,请重新登录'; + console.error(errorMsg, data); + reject(new Error(errorMsg)); + if (onComplete) onComplete({ error: errorMsg }); + socketTask.close(); + } + + // 处理流式消息块 + else if (data.type === 'message' || data.event === 'message') { + const text = data.answer || data.content || data.text || ''; + content += text; + if (onChunk) onChunk(text); + } + + // 处理流结束 + else if (data.event === 'message_end' || data.type === 'message_end') { + conversationId = data.conversation_id || ''; + if (conversationId) { + this.setConversationId(conversationId); + } + if (onComplete) { + onComplete({ content, conversation_id: conversationId }); + } + resolve({ content, conversation_id: conversationId }); + socketTask.close(); + } + + // 可选:处理 done + else if (data.type === 'done') { + console.log('对话完成:', data); + } + + } catch (e) { + console.error('WebSocket 消息解析失败:', e, '原始数据:', res.data); + } + }); + + socketTask.onError((err) => { + const errorMsg = 'WebSocket 连接失败'; + console.error(errorMsg, err); + reject(new Error(errorMsg)); + if (onComplete) onComplete({ error: errorMsg }); + }); + + socketTask.onClose(() => { + console.log('WebSocket 连接已关闭'); + }); + + const timeout = setTimeout(() => { + if (!isAuthenticated || content === '') { + console.warn('WebSocket 超时,强制关闭'); + socketTask.close(); + reject(new Error('AI服务响应超时')); + if (onComplete) onComplete({ error: 'AI服务响应超时' }); + } + }, 10000); + }); // #endif - - // #ifdef H5 - // H5:使用真实流式(EventSource / Fetch) - return this.sendHttpStream(message, onChunk, onComplete); - // #endif - }, - +}, /** * HTTP 流式请求(仅 H5 使用) */ @@ -131,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) { @@ -196,11 +259,6 @@ export default { throw error; } // #endif - - // #ifdef MP-WEIXIN - // 理论上不会执行到这里,但防止 fallback - return this.sendStreamMessage(message, onChunk, onComplete); - // #endif }, /**