From 42780eff0a362db506eb3ba610dec03a4ed6f059 Mon Sep 17 00:00:00 2001 From: zoujing Date: Thu, 7 Aug 2025 14:47:18 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=20=E8=AF=AD=E9=9F=B3=E7=9A=84?= =?UTF-8?q?=E5=BC=B9=E7=AA=97=E5=B0=81=E8=A3=85=E5=88=B0=E4=BA=86=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E5=86=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/Speech/RecordingPopup.vue | 106 ++++++ components/Speech/VoiceResultPopup.vue | 123 +++++++ pages/chat/ChatInputArea.vue | 431 +++++++++++-------------- 3 files changed, 414 insertions(+), 246 deletions(-) create mode 100644 components/Speech/RecordingPopup.vue create mode 100644 components/Speech/VoiceResultPopup.vue diff --git a/components/Speech/RecordingPopup.vue b/components/Speech/RecordingPopup.vue new file mode 100644 index 0000000..e935aeb --- /dev/null +++ b/components/Speech/RecordingPopup.vue @@ -0,0 +1,106 @@ + + + + + \ No newline at end of file diff --git a/components/Speech/VoiceResultPopup.vue b/components/Speech/VoiceResultPopup.vue new file mode 100644 index 0000000..2a87e68 --- /dev/null +++ b/components/Speech/VoiceResultPopup.vue @@ -0,0 +1,123 @@ + + + + + \ No newline at end of file diff --git a/pages/chat/ChatInputArea.vue b/pages/chat/ChatInputArea.vue index fb3a386..608ead4 100644 --- a/pages/chat/ChatInputArea.vue +++ b/pages/chat/ChatInputArea.vue @@ -8,40 +8,27 @@ - 取消 - 发送原语音 发送 @@ -14,15 +13,23 @@ 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 }