Files
YGChatCS/request/api/AgentChatStream.js

282 lines
8.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { BASE_URL } from "../../constant/base";
/// 请求流式数据的API
const API = '/agent/assistant/chat';
/**
* 获取AI聊天流式信息仅微信小程序支持
* @param {Object} params 请求参数
* @param {Function} onChunk 回调,每收到一段数据触发
* @returns {Object} 包含Promise和requestTask的对象
*/
let requestTask = null;
let isAborted = false; // 添加终止状态标志
let currentPromiseReject = null; // 保存当前Promise的reject函数
let currentMessageId = null; // 当前请求的messageId
/**
* 终止的请求
*/
const stopAbortTask = () => {
console.log("🛑 开始强制终止请求... ");
isAborted = true; // 立即设置终止标志
// 立即拒绝当前Promise最强制的终止
if (currentPromiseReject) {
currentPromiseReject(new Error('请求已被用户终止'));
currentPromiseReject = null;
}
if (requestTask) {
// 先取消所有监听器
try {
if (requestTask.offChunkReceived) {
requestTask.offChunkReceived();
}
} catch (e) {
console.log('🛑 取消 ChunkReceived 监听失败:', e);
}
try {
if (requestTask.offHeadersReceived) {
requestTask.offHeadersReceived();
}
} catch (e) {
console.log('🛑 取消 HeadersReceived 监听失败:', e);
}
// 然后终止网络请求
try {
if (requestTask.abort) {
requestTask.abort();
}
} catch (e) {
console.log('🛑 终止网络请求失败:', e);
}
requestTask = null;
}
currentMessageId = null;
console.log('🛑 请求强制终止完成');
}
const agentChatStream = (params, onChunk) => {
const promise = new Promise((resolve, reject) => {
const token = uni.getStorageSync('token');
let hasError = false;
isAborted = false; // 重置终止状态
// 保存当前Promise的reject函数用于强制终止
currentPromiseReject = reject;
// 设置当前请求的messageId用于区分不同请求
const messageId = params.messageId;
currentMessageId = messageId; // 记录当前请求的messageId
console.log("🚀 发送请求内容: ", params)
// #ifdef MP-WEIXIN
requestTask = uni.request({
url: BASE_URL + API, // 替换为你的接口地址
method: 'POST',
data: params,
enableChunked: true,
header: {
Accept: 'text/event-stream',
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`, // 如需token可加
},
responseType: 'arraybuffer',
success(res) {
if (!isAborted && currentMessageId === messageId) {
console.log("✅ 请求成功");
resolve(res.data);
} else if (currentMessageId !== messageId) {
console.log("❌ 请求已过期messageId不匹配忽略success回调");
} else {
console.log("❌ 请求已终止忽略success回调");
}
},
fail(err) {
if (!isAborted && currentMessageId === messageId) {
console.log("❌ 请求失败,错误:", JSON.stringify(err));
reject(err);
} else if (currentMessageId !== messageId) {
console.log("❌ 请求已过期messageId不匹配忽略fail回调");
} else {
console.log("❌ 请求已终止忽略fail回调");
}
},
complete(res) {
if (!isAborted && currentMessageId === messageId && res.statusCode !== 200) {
console.log("❌ 请求完成但状态错误,状态:", res.statusCode);
if (onChunk) {
onChunk({ error: true, message: '服务器错误', detail: res });
}
reject(res);
} else if (currentMessageId !== messageId) {
console.log("❌ 请求已过期messageId不匹配忽略complete回调");
} else if (isAborted) {
console.log("❌ 请求已终止忽略complete回调");
}
}
});
requestTask.onHeadersReceived(res => {
// 检查请求是否已终止或过期messageId不匹配
if (isAborted || currentMessageId !== messageId) {
if (currentMessageId !== messageId) {
console.log('🚫 Headers已过期messageId不匹配忽略');
} else {
console.log('🚫 Headers已终止忽略');
}
return;
}
console.log('📡 onHeadersReceivedres:', res);
const status = res.statusCode || (res.header && res.header.statusCode);
if (status && status !== 200) {
hasError = true;
if (onChunk && !isAborted && currentMessageId === messageId) {
onChunk({ error: true, message: `服务器错误(${status})`, detail: res });
}
if (requestTask && requestTask.abort) {
requestTask.abort();
}
}
});
requestTask.onChunkReceived(res => {
// 第一道防线立即检查终止状态和messageId防止处理过期请求
if (isAborted || hasError || requestTask === null || currentMessageId !== messageId) {
if (currentMessageId !== messageId) {
console.log('🚫 数据块已过期messageId不匹配忽略 - 第一道检查');
} else {
console.log('🚫 数据块已终止或无效,忽略 - 第一道检查');
}
return;
}
const base64 = uni.arrayBufferToBase64(res.data);
let data = '';
try {
data = decodeURIComponent(escape(weAtob(base64)));
} catch (e) {
// 某些平台可能不支持 atob可以直接用 base64
data = base64;
}
console.log("📦 onChunkReceivedres:", data)
// 第二道防线:解析前再次检查
if (isAborted || hasError || requestTask === null || currentMessageId !== messageId) {
if (currentMessageId !== messageId) {
console.log('🚫 数据块已过期messageId不匹配忽略 - 第二道检查');
} else {
console.log('🚫 数据块已终止或无效,忽略 - 第二道检查');
}
return;
}
const messages = parseSSEChunk(data);
messages.forEach(msg => {
// 第三道防线:每个消息处理前都检查(确保只处理当前请求的消息)
if (onChunk && !isAborted && !hasError && requestTask !== null && currentMessageId === messageId) {
onChunk(msg);
} else if (currentMessageId !== messageId) {
console.log('🚫 消息已过期messageId不匹配忽略处理');
} else {
console.log('🚫 消息已终止或无效,忽略处理');
}
});
});
// #endif
});
return promise
}
// window.atob兼容性处理
const weAtob = (string) => {
const b64re = /^(?:[A-Za-z\d+/]{4})*?(?:[A-Za-z\d+/]{2}(?:==)?|[A-Za-z\d+/]{3}=?)?$/;
const b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
// 去除空白字符
string = String(string).replace(/[\t\n\f\r ]+/g, '');
// 验证 Base64 编码
if (!b64re.test(string)) {
throw new TypeError(
// eslint-disable-next-line quotes
"Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded."
);
}
// 填充字符
string += '=='.slice(2 - (string.length & 3));
let bitmap,
result = '',
r1,
r2,
i = 0;
for (; i < string.length;) {
bitmap =
(b64.indexOf(string.charAt(i++)) << 18) |
(b64.indexOf(string.charAt(i++)) << 12) |
((r1 = b64.indexOf(string.charAt(i++))) << 6) |
(r2 = b64.indexOf(string.charAt(i++)));
if (r1 === 64) {
result += String.fromCharCode((bitmap >> 16) & 255);
} else if (r2 === 64) {
result += String.fromCharCode(
(bitmap >> 16) & 255,
(bitmap >> 8) & 255
);
} else {
result += String.fromCharCode(
(bitmap >> 16) & 255,
(bitmap >> 8) & 255,
bitmap & 255
);
}
}
return result;
};
// 解析SSE分段数据
const parseSSEChunk = (raw) => {
const results = [];
// 按一个或多个连续换行分段,表示每一个事件块
const chunks = raw.split(/\n\n+/);
for (const chunk of chunks) {
const lines = chunk.split(/\r?\n/);
let dataLines = [];
for (const line of lines) {
if (line.startsWith('data:')) {
// 提取data:后面的内容并去除首尾空格
dataLines.push(line.slice(5).trim());
}
}
if (dataLines.length > 0) {
// 拼接多行数据
const fullData = dataLines.join('\n');
try {
const obj = JSON.parse(fullData);
results.push(obj);
} catch (e) {
console.warn('⚠️ SSE数据解析失败:', e, '原始数据:', fullData);
// 解析失败忽略
}
}
}
return results;
}
export { agentChatStream, stopAbortTask }