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 Object,headers,默认:{} * @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(); } } }); }) }