Files
ks-app-employment-service/utils/streamRequest.js
2026-01-22 12:57:01 +08:00

537 lines
27 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 config from "@/config.js"
import useUserStore from '@/stores/useUserStore';
/**
* @param url String请求的地址默认none
* @param data Object请求的参数默认{}
* @returns promise
**/
export default function StreamRequest(url, data = {}, onDataReceived, onError, onComplete) {
// #ifdef MP-WEIXIN
return StreamRequestMiniProgram(url, data, onDataReceived, onError, onComplete);
// #endif
// #ifdef H5
return StreamRequestH5(url, data, onDataReceived, onError, onComplete);
// #endif
// #ifndef H5 || MP-WEIXIN
return StreamRequestH5(url, data, onDataReceived, onError, onComplete);
// #endif
}
// 微信小程序流式请求实现
function StreamRequestMiniProgram(url, data = {}, onDataReceived, onError, onComplete) {
const userStore = useUserStore();
const Authorization = userStore.token ? encodeURIComponent(userStore.token) : '';
return new Promise((resolve, reject) => {
let buffer = '';
let hasReceivedContent = false;
const requestTask = uni.request({
url: config.StreamBaseURl + url,
method: 'POST',
data: data,
header: {
"Authorization": Authorization,
"Accept": "text/event-stream",
"Content-Type": "application/json;charset=UTF-8"
},
enableChunked: true, // 启用分块传输
success: (res) => {
console.log('📡 Stream request completed');
onComplete && onComplete();
resolve();
},
fail: (err) => {
console.error('Stream 请求失败:', err);
onError && onError(err);
reject(err);
}
});
// 监听分块数据
requestTask.onChunkReceived((res) => {
try {
const decoder = new TextDecoder('utf-8');
const chunk = decoder.decode(new Uint8Array(res.data));
console.log('📦 收到分块数据:', chunk);
buffer += chunk;
let lines = buffer.split("\n");
buffer = lines.pop() || ''; // 保留不完整的行
console.log('📝 解析到行:', lines.length, '行,缓冲区剩余:', buffer.length, '字符');
for (let line of lines) {
console.log('🔍 处理行:', line);
// 处理重复的 data: 前缀
let processedLine = line;
// 移除所有开头的 data: 前缀(无论是否有空格),直到只剩下一个或没有
while (processedLine.startsWith("data:")) {
// 检查是否还有另一个 data: 前缀
const nextPart = processedLine.slice(5).trimStart();
if (nextPart.startsWith("data:")) {
processedLine = nextPart;
} else {
break;
}
}
if (processedLine.startsWith("data: ")) {
const jsonData = processedLine.slice(6).trim();
console.log('📄 提取的JSON数据:', jsonData);
if (jsonData === "[DONE]") {
console.log('✅ 收到结束标记 [DONE]');
onComplete && onComplete();
resolve();
return;
}
if (jsonData && jsonData.trim()) {
try {
const parsedData = JSON.parse(jsonData);
console.log('🔧 解析后的JSON:', parsedData);
// 检查是否有错误信息
const finishReason = parsedData?.choices?.[0]?.finish_reason;
if (finishReason === "error") {
let errorContent = parsedData?.choices?.[0]?.delta?.content || "流式请求失败";
console.error('❌ 收到错误信息:', errorContent);
// 优化token超限错误提示
if (errorContent.includes("maximum input ids length")) {
errorContent = "对话历史过长,请尝试清除部分历史记录或简化问题";
}
// 只有当未收到正常内容时才显示错误信息
if (!hasReceivedContent) {
// 显示错误信息给用户
uni.showToast({
title: errorContent,
icon: 'none',
duration: 3000
});
}
}
// 处理标准的choices格式
else if (parsedData?.choices?.[0]?.delta?.content) {
const content = parsedData.choices[0].delta.content;
if (content) {
hasReceivedContent = true;
console.log('📤 调用onDataReceived(content):', content);
onDataReceived && onDataReceived(content);
}
}
// 处理reasoning_content
else if (parsedData?.choices?.[0]?.delta?.reasoning_content) {
const content = parsedData.choices[0].delta.reasoning_content;
if (content) {
hasReceivedContent = true;
console.log('📤 调用onDataReceived(reasoning_content):', content);
onDataReceived && onDataReceived(content);
}
}
// 处理tool响应
else if (parsedData?.tool?.response) {
const content = parsedData.tool.response;
if (content) {
hasReceivedContent = true;
console.log('📤 调用onDataReceived(tool.response):', content);
onDataReceived && onDataReceived(content);
}
}
// 处理其他可能的内容格式
else if (parsedData?.content) {
// 直接返回content字段的情况
const content = parsedData.content;
if (content) {
hasReceivedContent = true;
console.log('📤 调用onDataReceived(direct content):', content);
onDataReceived && onDataReceived(content);
}
}
// 处理完整的text字段非流式
else if (parsedData?.choices?.[0]?.text) {
const content = parsedData.choices[0].text;
if (content) {
hasReceivedContent = true;
console.log('📤 调用onDataReceived(full text):', content);
onDataReceived && onDataReceived(content);
}
}
else {
console.warn('⚠️ 未匹配到任何内容格式:', parsedData);
}
} catch (e) {
console.error("JSON 解析失败:", e.message, "原始数据:", jsonData);
}
}
}
else if (processedLine.trim()) {
// 处理非data:开头的行
console.warn('⚠️ 收到非data:开头的行:', processedLine);
}
}
} catch (error) {
console.error('处理分块数据失败:', error);
}
});
});
}
// H5 流式请求实现(原有的 fetch 实现)
function StreamRequestH5(url, data = {}, onDataReceived, onError, onComplete) {
const userStore = useUserStore();
const Authorization = userStore.token ? encodeURIComponent(userStore.token) : '';
const headers = {
"Authorization": Authorization,
"Accept": "text/event-stream",
"Content-Type": "application/json;charset=UTF-8"
};
return new Promise(async (resolve, reject) => {
try {
const response = await fetch(config.StreamBaseURl + url, {
method: "POST",
headers,
body: JSON.stringify(data)
});
if (!response.ok) {
throw new Error(`HTTP 错误: ${response.status}`);
}
const reader = response.body.getReader();
const decoder = new TextDecoder("utf-8");
let buffer = "";
let retryCount = 0;
const maxRetries = 3;
let hasReceivedContent = false;
while (true) {
const {
done,
value
} = await reader.read();
if (done) {
console.log("📡 Stream reading completed");
break;
}
buffer += decoder.decode(value, {
stream: true
});
let lines = buffer.split("\n");
buffer = lines.pop(); // 可能是不完整的 JSON 片段,留待下次解析
console.log(`📦 Processing ${lines.length} lines, buffer length: ${buffer.length}`);
for (let line of lines) {
console.log('🔍 处理行:', line);
// 处理重复的 data: 前缀
let processedLine = line;
// 移除所有开头的 data: 前缀(无论是否有空格),直到只剩下一个或没有
while (processedLine.startsWith("data:")) {
// 检查是否还有另一个 data: 前缀
const nextPart = processedLine.slice(5).trimStart();
if (nextPart.startsWith("data:")) {
processedLine = nextPart;
} else {
break;
}
}
if (processedLine.startsWith("data: ")) {
const jsonData = processedLine.slice(6).trim();
console.log('📄 提取的JSON数据:', jsonData);
if (jsonData === "[DONE]") {
console.log('✅ 收到结束标记 [DONE]');
onComplete && onComplete();
resolve();
return;
}
try {
// 检查JSON数据是否完整
if (jsonData && jsonData.trim() && jsonData !== "[DONE]") {
const parsedData = JSON.parse(jsonData);
console.log('🔧 解析后的JSON:', parsedData);
// 检查是否有错误信息
const finishReason = parsedData?.choices?.[0]?.finish_reason;
if (finishReason === "error") {
let errorContent = parsedData?.choices?.[0]?.delta?.content || "流式请求失败";
console.error('❌ 收到错误信息:', errorContent);
// 优化token超限错误提示
if (errorContent.includes("maximum input ids length")) {
errorContent = "对话历史过长,请尝试清除部分历史记录或简化问题";
}
// 只有当未收到正常内容时才显示错误信息
if (!hasReceivedContent) {
// 显示错误信息给用户
uni.showToast({
title: errorContent,
icon: 'none',
duration: 3000
});
}
}
// 处理标准的choices格式
else if (parsedData?.choices?.[0]?.delta?.content) {
const content = parsedData.choices[0].delta.content;
if (content) {
hasReceivedContent = true;
console.log('📤 调用onDataReceived(content):', content);
onDataReceived && onDataReceived(content);
}
}
// 处理reasoning_content
else if (parsedData?.choices?.[0]?.delta?.reasoning_content) {
const content = parsedData.choices[0].delta.reasoning_content;
if (content) {
hasReceivedContent = true;
console.log('📤 调用onDataReceived(reasoning_content):', content);
onDataReceived && onDataReceived(content);
}
}
// 处理tool响应
else if (parsedData?.tool?.response) {
const content = parsedData.tool.response;
if (content) {
hasReceivedContent = true;
console.log('📤 调用onDataReceived(tool.response):', content);
onDataReceived && onDataReceived(content);
}
}
// 处理其他可能的内容格式
else if (parsedData?.content) {
// 直接返回content字段的情况
const content = parsedData.content;
if (content) {
hasReceivedContent = true;
console.log('📤 调用onDataReceived(direct content):', content);
onDataReceived && onDataReceived(content);
}
}
// 处理完整的text字段非流式
else if (parsedData?.choices?.[0]?.text) {
const content = parsedData.choices[0].text;
if (content) {
hasReceivedContent = true;
console.log('📤 调用onDataReceived(full text):', content);
onDataReceived && onDataReceived(content);
}
}
// 处理其他格式的数据如jobs_array_number, durationSeconds等
else {
console.log("📦 收到非内容数据:", Object.keys(parsedData));
}
}
} catch (e) {
console.error("JSON 解析失败:", e.message, "原始数据长度:", jsonData.length, "数据预览:", jsonData.substring(0, 100) + "...");
// 不抛出错误,继续处理下一个数据块
}
}
else if (processedLine.trim()) {
// 处理非data:开头的行
console.warn('⚠️ 收到非data:开头的行:', processedLine);
}
}
}
// 处理剩余的缓冲区数据
if (buffer.trim()) {
console.log("📦 Processing remaining buffer:", buffer.substring(0, 100) + "...");
const lines = buffer.split("\n");
for (let line of lines) {
console.log('🔍 处理剩余缓冲区行:', line);
// 处理重复的 data: 前缀
let processedLine = line;
// 移除所有开头的 data: 前缀(无论是否有空格),直到只剩下一个或没有
while (processedLine.startsWith("data:")) {
// 检查是否还有另一个 data: 前缀
const nextPart = processedLine.slice(5).trimStart();
if (nextPart.startsWith("data:")) {
processedLine = nextPart;
} else {
break;
}
}
if (processedLine.startsWith("data: ")) {
const jsonData = processedLine.slice(6).trim();
console.log('📄 提取的剩余JSON数据:', jsonData);
if (jsonData && jsonData !== "[DONE]") {
try {
const parsedData = JSON.parse(jsonData);
console.log('🔧 解析后的剩余JSON:', parsedData);
// 检查是否有错误信息
const finishReason = parsedData?.choices?.[0]?.finish_reason;
if (finishReason === "error") {
let errorContent = parsedData?.choices?.[0]?.delta?.content || "流式请求失败";
console.error('❌ 收到错误信息:', errorContent);
// 优化token超限错误提示
if (errorContent.includes("maximum input ids length")) {
errorContent = "对话历史过长,请尝试清除部分历史记录或简化问题";
}
// 只有当未收到正常内容时才显示错误信息
if (!hasReceivedContent) {
// 显示错误信息给用户
uni.showToast({
title: errorContent,
icon: 'none',
duration: 3000
});
}
}
// 处理标准的choices格式
else if (parsedData?.choices?.[0]?.delta?.content) {
const content = parsedData.choices[0].delta.content;
if (content) {
hasReceivedContent = true;
console.log('📤 调用onDataReceived(content):', content);
onDataReceived && onDataReceived(content);
}
}
// 处理reasoning_content
else if (parsedData?.choices?.[0]?.delta?.reasoning_content) {
const content = parsedData.choices[0].delta.reasoning_content;
if (content) {
hasReceivedContent = true;
console.log('📤 调用onDataReceived(reasoning_content):', content);
onDataReceived && onDataReceived(content);
}
}
// 处理tool响应
else if (parsedData?.tool?.response) {
const content = parsedData.tool.response;
if (content) {
hasReceivedContent = true;
console.log('📤 调用onDataReceived(tool.response):', content);
onDataReceived && onDataReceived(content);
}
}
// 处理其他可能的内容格式
else if (parsedData?.content) {
// 直接返回content字段的情况
const content = parsedData.content;
if (content) {
hasReceivedContent = true;
console.log('📤 调用onDataReceived(direct content):', content);
onDataReceived && onDataReceived(content);
}
}
// 处理完整的text字段非流式
else if (parsedData?.choices?.[0]?.text) {
const content = parsedData.choices[0].text;
if (content) {
hasReceivedContent = true;
console.log('📤 调用onDataReceived(full text):', content);
onDataReceived && onDataReceived(content);
}
}
} catch (e) {
console.warn("处理剩余数据时JSON解析失败:", e.message);
}
}
}
else if (processedLine.trim()) {
// 处理非data:开头的行
console.warn('⚠️ 收到非data:开头的剩余行:', processedLine);
}
}
}
onComplete && onComplete();
resolve();
} catch (error) {
console.error("Stream 请求失败:", error);
onError && onError(error);
reject(error);
}
});
}
/**
* @param url String请求的地址默认none
* @param data Object请求的参数默认{}
* @param method String请求的方式默认GET
* @param loading Boolean是否需要loading 默认false
* @param header Objectheaders默认{}
* @returns promise
**/
export function chatRequest(url, data = {}, method = 'GET', loading = false, headers = {}) {
if (loading) {
uni.showLoading({
title: '请稍后',
mask: true
})
}
let Authorization = ''
if (useUserStore().token) {
Authorization = `${useUserStore().token}`
}
const header = headers || {};
header["Authorization"] = encodeURIComponent(Authorization);
return new Promise((resolve, reject) => {
uni.request({
url: config.StreamBaseURl + url,
method: method,
data: data,
header,
success: resData => {
// 响应拦截
if (resData.statusCode === 200) {
const {
code,
msg
} = resData.data
if (code === 200) {
resolve(resData.data)
return
}
uni.showToast({
title: msg || '请求失败',
icon: 'none'
})
// 拒绝Promise并提供详细错误信息
const err = new Error(msg || '请求失败,服务器返回错误码: ' + code)
err.error = resData
reject(err)
} else {
// 处理非200状态码
const errorMsg = `请求失败HTTP状态码: ${resData.statusCode}`
uni.showToast({
title: errorMsg,
icon: 'none'
})
const err = new Error(errorMsg)
err.error = resData
reject(err)
}
if (resData.data?.code === 401 || resData.data?.code === 402) {
useUserStore().logOut()
}
},
fail: (err) => {
reject(err)
},
complete: () => {
if (loading) {
uni.hideLoading();
}
}
});
})
}