From e16402c9c116b7d482433b477d0cbe1f1ae6c0f6 Mon Sep 17 00:00:00 2001 From: ZF sun <34314687@qq.com> Date: Mon, 11 May 2026 17:14:26 +0800 Subject: [PATCH] =?UTF-8?q?fix(ai):=20=E4=BF=AE=E5=A4=8DWebSocket=E9=87=8D?= =?UTF-8?q?=E5=A4=8D=E5=9B=9E=E8=B0=83=E5=8F=8A=E6=84=8F=E5=A4=96=E5=85=B3?= =?UTF-8?q?=E9=97=AD=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/js/ai-service.js | 60 +++++++++++++++++++++----- pages_tool/ai-chat/ai-chat-message.vue | 8 +++- 2 files changed, 55 insertions(+), 13 deletions(-) diff --git a/common/js/ai-service.js b/common/js/ai-service.js index 9bda135..1b3a278 100644 --- a/common/js/ai-service.js +++ b/common/js/ai-service.js @@ -88,6 +88,16 @@ export default { return new Promise((resolve, reject) => { const match = Config.baseUrl.match(/^(?:https?:\/\/|wss?:\/\/)([^\/\s:]+)/); const domain = match ? match[1] : ''; + + // 修复 5:Domain 提取失败时提前拒绝 + if (!domain) { + const errorMsg = 'WebSocket 域名配置无效'; + console.error(errorMsg); + reject(new Error(errorMsg)); + if (onComplete) onComplete({ error: errorMsg }); + return; + } + const wssUrl = `wss://${domain}/ws/aikefu`; const socketTask = wx.connectSocket({ url: wssUrl, @@ -97,6 +107,29 @@ export default { let content = ''; let conversationId = ''; let isAuthenticated = false; + let closedNormally = false; + let settled = false; + + // 安全地拒绝(防止重复调用) + function safeReject(error) { + if (settled) return; + settled = true; + clearTimeout(timeout); + reject(error); + } + + // 安全地完成 + function safeResolve(result) { + if (settled) return; + settled = true; + clearTimeout(timeout); + resolve(result); + } + + // 安全地触发 onComplete + function safeComplete(result) { + if (onComplete) onComplete(result); + } socketTask.onOpen(() => { socketTask.send({ @@ -128,8 +161,8 @@ export default { } else if (data.type === 'auth_failed') { const errorMsg = '认证失败,请重新登录'; console.error(errorMsg, data); - reject(new Error(errorMsg)); - if (onComplete) onComplete({ error: errorMsg }); + safeComplete({ error: errorMsg }); + safeReject(new Error(errorMsg)); socketTask.close(); } @@ -146,10 +179,9 @@ export default { if (conversationId) { this.setConversationId(conversationId); } - if (onComplete) { - onComplete({ content, conversation_id: conversationId }); - } - resolve({ content, conversation_id: conversationId }); + closedNormally = true; + safeComplete({ content, conversation_id: conversationId }); + safeResolve({ content, conversation_id: conversationId }); socketTask.close(); } @@ -165,21 +197,27 @@ export default { socketTask.onError((err) => { const errorMsg = 'WebSocket 连接失败'; console.error(errorMsg, err); - reject(new Error(errorMsg)); - if (onComplete) onComplete({ error: errorMsg }); + safeComplete({ error: errorMsg }); + safeReject(new Error(errorMsg)); }); socketTask.onClose(() => { + if (!closedNormally && !settled) { + const errorMsg = 'WebSocket 连接意外关闭'; + console.error(errorMsg); + safeComplete({ error: errorMsg }); + safeReject(new Error(errorMsg)); + } }); const timeout = setTimeout(() => { if (!isAuthenticated || content === '') { console.warn('WebSocket 超时,强制关闭'); socketTask.close(); - reject(new Error('AI服务响应超时')); - if (onComplete) onComplete({ error: 'AI服务响应超时' }); + safeComplete({ error: 'AI服务响应超时' }); + safeReject(new Error('AI服务响应超时')); } - }, 10000); + }, 20000); }); // #endif }, diff --git a/pages_tool/ai-chat/ai-chat-message.vue b/pages_tool/ai-chat/ai-chat-message.vue index ac1e8df..2eddd4d 100644 --- a/pages_tool/ai-chat/ai-chat-message.vue +++ b/pages_tool/ai-chat/ai-chat-message.vue @@ -1010,8 +1010,10 @@ async loadChatHistoryIfExist() { await aiService.sendHttpStream( userMessage, (chunk) => { - // 实时更新内容(不频繁滚动) + // 实时更新内容 streamMessage.content += chunk; + // 流式更新时滚动到底部显示最新内容(防抖) + this.scrollToBottom(); }, (completeResult) => { // 流结束回调 @@ -1054,8 +1056,10 @@ async loadChatHistoryIfExist() { await aiService.sendStreamMessage( userMessage, (chunk) => { - // 实时更新内容(不频繁滚动) + // 实时更新内容 streamMessage.content += chunk; + // 流式更新时滚动到底部显示最新内容(防抖) + this.scrollToBottom(); }, (completeResult) => { // 流结束回调