From 1af6fd2e5fc7a91485038a4b1593aa9a04e23f82 Mon Sep 17 00:00:00 2001 From: zoujing Date: Fri, 8 Aug 2025 09:08:07 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=AF=B7=E6=B1=82=E7=BB=88=E6=AD=A2?= =?UTF-8?q?=E7=9A=84=E9=80=BB=E8=BE=91=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/chat/ChatMainList.vue | 58 ++++++-------- request/api/AgentChatStream.js | 141 +++++++++++++++++++++++++++------ 2 files changed, 141 insertions(+), 58 deletions(-) diff --git a/pages/chat/ChatMainList.vue b/pages/chat/ChatMainList.vue index 771487b..ae98f0a 100644 --- a/pages/chat/ChatMainList.vue +++ b/pages/chat/ChatMainList.vue @@ -103,7 +103,7 @@ import CreateServiceOrder from '@/components/CreateServiceOrder/index.vue' - import { agentChatStream } from '@/request/api/AgentChatStream'; + import { agentChatStream, stopAbortTask } from '@/request/api/AgentChatStream'; import { mainPageData } from '@/request/api/MainPageDataApi'; import { conversationMsgList, recentConversation } from '@/request/api/ConversationApi'; @@ -145,8 +145,6 @@ // 会话进行中标志 const isSessionActive = ref(false); - // 请求任务引用 - const requestTaskRef = ref(null); /// 指令 let commonType = '' @@ -340,7 +338,7 @@ } } chatMsgList.value.push(newMsg) - inputMessage.value = ''; + inputMessage.value = ''; sendChat(message, isInstruct) console.log("发送的新消息:",JSON.stringify(newMsg)) } @@ -357,7 +355,8 @@ conversationId: conversationId.value, agentId: agentId.value, messageType: isInstruct ? 1 : 0, - messageContent: isInstruct ? commonType : message + messageContent: isInstruct ? commonType : message, + messageId: 'mid' + new Date().getTime() } // 插入AI消息 @@ -390,8 +389,8 @@ } // 流式接收内容 - const { promise, requestTask } = agentChatStream(args, (chunk) => { - console.log('分段内容:', chunk) + const promise = agentChatStream(args, (chunk) => { + // console.log('分段内容:', chunk) if (chunk && chunk.error) { chatMsgList.value[aiMsgIndex].msg = '请求错误,请重试'; clearInterval(loadingTimer); @@ -456,8 +455,6 @@ } }) - // 存储请求任务 - requestTaskRef.value = requestTask; // 可选:处理Promise完成/失败, 已经在回调中处理数据,此处无需再处理 promise.then(data => { @@ -486,31 +483,24 @@ // 停止请求函数 - const stopRequest = () => { - if (requestTaskRef.value && requestTaskRef.value.abort) { - // 标记请求已中止,用于过滤后续可能到达的数据 - requestTaskRef.value.isAborted = true; - // 中止请求 - requestTaskRef.value.abort(); - // 重置状态 - isSessionActive.value = false; - const msg = chatMsgList.value[currentAIMsgIndex].msg; - if (!msg || msg === '加载中.' || msg.startsWith('加载中')) { - chatMsgList.value[currentAIMsgIndex].msg = '已终止请求,请重试'; - } - // 清除计时器 - if (loadingTimer) { - clearInterval(loadingTimer); - loadingTimer = null; - } - if (typeWriterTimer) { - clearTimeout(typeWriterTimer); - typeWriterTimer = null; - } - setTimeoutScrollToBottom() - // 清空请求引用 - requestTaskRef.value = null; - } + const stopRequest = () => { + stopAbortTask() + // 重置状态 + isSessionActive.value = false; + const msg = chatMsgList.value[currentAIMsgIndex].msg; + if (!msg || msg === '加载中.' || msg.startsWith('加载中')) { + chatMsgList.value[currentAIMsgIndex].msg = '已终止请求,请重试'; + } + // 清除计时器 + if (loadingTimer) { + clearInterval(loadingTimer); + loadingTimer = null; + } + if (typeWriterTimer) { + clearTimeout(typeWriterTimer); + typeWriterTimer = null; + } + setTimeoutScrollToBottom() } diff --git a/request/api/AgentChatStream.js b/request/api/AgentChatStream.js index d972d8b..5f5abbf 100644 --- a/request/api/AgentChatStream.js +++ b/request/api/AgentChatStream.js @@ -9,13 +9,72 @@ const API = '/agent/assistant/chat'; * @param {Function} onChunk 回调,每收到一段数据触发 * @returns {Object} 包含Promise和requestTask的对象 */ -function agentChatStream(params, onChunk) { - let requestTask; +let requestTask = null; +let isAborted = false; // 添加终止状态标志 +let currentPromiseReject = null; // 保存当前Promise的reject函数 +let requestId = 0; // 请求ID,用于区分不同的请求 + +const stopAbortTask = () => { + console.log('🛑 开始强制终止请求...'); + isAborted = true; // 立即设置终止标志 + + // 立即拒绝当前Promise(最强制的终止) + if (currentPromiseReject) { + console.log('🛑 立即拒绝Promise'); + currentPromiseReject(new Error('请求已被用户终止')); + currentPromiseReject = null; + } + + if (requestTask) { + // 先取消所有监听器(关键:必须在abort之前) + try { + if (requestTask.offChunkReceived) { + requestTask.offChunkReceived(); + console.log('🛑 已取消 ChunkReceived 监听'); + } + } catch (e) { + console.log('🛑 取消 ChunkReceived 监听失败:', e); + } + + try { + if (requestTask.offHeadersReceived) { + requestTask.offHeadersReceived(); + console.log('🛑 已取消 HeadersReceived 监听'); + } + } catch (e) { + console.log('🛑 取消 HeadersReceived 监听失败:', e); + } + + // 然后终止网络请求 + try { + if (requestTask.abort) { + requestTask.abort(); + console.log('🛑 已终止网络请求'); + } + } catch (e) { + console.log('🛑 终止网络请求失败:', e); + } + + requestTask = null; + } + + // 递增请求ID,使旧请求的数据无效 + requestId++; + console.log('🛑 请求强制终止完成,新请求ID:', requestId); +} + +const agentChatStream = (params, onChunk) => { const promise = new Promise((resolve, reject) => { const token = uni.getStorageSync('token'); let hasError = false; - - console.log("发送请求内容: ", params) + isAborted = false; // 重置终止状态 + + // 保存当前Promise的reject函数,用于强制终止 + currentPromiseReject = reject; + + // 为当前请求分配ID + const currentRequestId = ++requestId; + console.log("🚀 发送请求内容: ", params, "请求ID:", currentRequestId) // #ifdef MP-WEIXIN requestTask = uni.request({ url: BASE_URL + API, // 替换为你的接口地址 @@ -29,38 +88,62 @@ function agentChatStream(params, onChunk) { }, responseType: 'arraybuffer', success(res) { - resolve(res.data); + if (!isAborted && requestId === currentRequestId) { + console.log("✅ 请求成功,ID:", currentRequestId); + resolve(res.data); + } else { + console.log("❌ 请求已过期或终止,忽略success回调,当前ID:", requestId, "请求ID:", currentRequestId); + } }, fail(err) { - console.log("====> ", JSON.stringify(err)) - reject(err); + if (!isAborted && requestId === currentRequestId) { + console.log("❌ 请求失败,ID:", currentRequestId, "错误:", JSON.stringify(err)); + reject(err); + } else { + console.log("❌ 请求已过期或终止,忽略fail回调,当前ID:", requestId, "请求ID:", currentRequestId); + } }, complete(res) { - if(res.statusCode !== 200) { - console.log("====> ", JSON.stringify(res)) + if (!isAborted && requestId === currentRequestId && res.statusCode !== 200) { + console.log("❌ 请求完成但状态错误,ID:", currentRequestId, "状态:", res.statusCode); if (onChunk) { - onChunk({ error: true, message: '服务器错误', detail: res }); - } - reject(res); - } + onChunk({ error: true, message: '服务器错误', detail: res }); + } + reject(res); + } else if (requestId !== currentRequestId) { + console.log("❌ 请求已过期或终止,忽略complete回调,当前ID:", requestId, "请求ID:", currentRequestId); + } } }); requestTask.onHeadersReceived(res => { - console.log('onHeadersReceived', res); + // 检查请求是否已终止或过期 + if (isAborted || requestId !== currentRequestId) { + console.log('🚫 Headers已终止或过期,忽略,当前ID:', requestId, '请求ID:', currentRequestId); + return; + } + + console.log('📡 onHeadersReceived,ID:', currentRequestId, res); const status = res.statusCode || (res.header && res.header.statusCode); if (status && status !== 200) { hasError = true; - if (onChunk) { + if (onChunk && !isAborted && requestId === currentRequestId) { onChunk({ error: true, message: `服务器错误(${status})`, detail: res }); } - requestTask.abort && requestTask.abort(); + if (requestTask && requestTask.abort) { + requestTask.abort(); + } } }); requestTask.onChunkReceived(res => { - // 检查请求是否已被中止 - if (hasError || requestTask.isAborted) return; + // 第一道防线:立即检查请求ID和终止状态 + if (isAborted || hasError || requestTask === null || requestId !== currentRequestId) { + console.log('🚫 数据块已终止或过期,忽略 - 第一道检查,当前ID:', requestId, '请求ID:', currentRequestId); + return; + } + + console.log("📦 onChunkReceived,ID:", currentRequestId, res) const base64 = uni.arrayBufferToBase64(res.data); let data = ''; try { @@ -69,18 +152,28 @@ function agentChatStream(params, onChunk) { // 某些平台可能不支持 atob,可以直接用 base64 data = base64; } + + // 第二道防线:解析前再次检查 + if (isAborted || hasError || requestTask === null || requestId !== currentRequestId) { + console.log('🚫 数据块已终止或过期,忽略 - 第二道检查,当前ID:', requestId, '请求ID:', currentRequestId); + return; + } + const messages = parseSSEChunk(data); messages.forEach(msg => { - if (onChunk) onChunk(msg); + // 第三道防线:每个消息处理前都检查 + if (onChunk && !isAborted && !hasError && requestTask !== null && requestId === currentRequestId) { + console.log(`parseSSEChunk ${currentRequestId}:`, msg) + onChunk(msg); + } else { + console.log('🚫 消息已终止或过期,忽略处理,当前ID:', requestId, '请求ID:', currentRequestId); + } }); }); // #endif }); - return { - promise, - requestTask - }; + return promise } // window.atob兼容性处理 @@ -152,4 +245,4 @@ function parseSSEChunk(raw) { return results; } -export { agentChatStream } \ No newline at end of file +export { agentChatStream, stopAbortTask }