废话不多说直接上代码
// 小程序专用流式服务
export const streamChatMiniProgram = (messages, options = {secret: ""
}) => {return new Promise((resolve, reject) => {// 构建请求数据 const requestData = {model: 'deepseek-chat',messages,stream: true,max_tokens: 2048,temperature: 0.7};// 平台特定配置const requestConfig = {url: 'https://api.deepseek.com/v1/chat/completions',method: 'POST',header: {'Accept-Charset': 'utf-8','Content-Type': 'application/json','Authorization': `Bearer ${options.secret}`},data: JSON.stringify(requestData),// responseType: 'text',enableChunked: true, // 关键配置:启用分块传输// enableHttp2: true,timeout: 30000};// 跨平台适配 // #ifdef MP-WEIXIN || MP-QQ requestConfig.enableChunked = true;// #endif // #ifdef MP-ALIPAY || MP-BAIDU requestConfig.enableChunked = false;// #endif // 发起请求 const requestTask = wx.request({...requestConfig,// 分块数据接收处理 chunked: requestConfig.enableChunked,success: (res) => {if (res.statusCode !== 200) {reject(new Error(`API错误: ${res.statusCode}`));}},fail: (err) => {reject(new Error(`请求失败: ${err.errMsg}`));}});try {// requestTask.onHeadersReceived((chunk)=>{// console.log("onHeadersReceived")// })// requestTask.onProgressUpdate((chunk)=>{// console.log("onProgressUpdate")// })// const decoder = new TextDecoder('utf-8') // 显式指定UTF-8requestTask.onChunkReceived((chunk) => {try {if (!requestTaskMap.get(requestTask.uniqueId)) {return;}// 缓冲区初始化为空字符串 let buffer = '';buffer += utf8Decode(chunk.data) //decoder.decode(chunk.data, { stream: true });// String.fromCharCode.apply(null, new Uint8Array(chunk.data));// SSE格式解析 const lines = buffer.split('\n');buffer = '';for (const line of lines) {if (line.trim() === '') continue;if (line.startsWith('data:')) {const dataStr = line.replace('data:', '').trim();// 结束标记处理 if (dataStr === '[DONE]') {resolve(fullResponse);return;}// 解析JSON内容 try {const data = JSON.parse(dataStr);if (data.choices?.[0]?.delta?.content) {const content = data.choices[0].delta.content;fullResponse += content;// 实时事件通知 uni.$emit('deepseek_stream_update', {partial: content,full: fullResponse});}} catch (e) {console.error('JSON 解析错误', e);uni.$emit('deepseek_stream_update', {partial: 'JSON 解析错误',e,full: 'JSON 解析错误',e});}}}} catch (err) {uni.$emit('deepseek_stream_update', {partial: JSON.stringify(err),full: JSON.stringify(err)});}});} catch (err) {uni.$emit('deepseek_stream_update', {partial: JSON.stringify(err),full: JSON.stringify(err)});}// 存储任务引用以便中断 console.log("requestTask=", requestTask)requestTaskMap.set(requestTask.uniqueId, requestTask);let fullResponse = '';});
};export function utf8Decode(buffer) {let uint8 = new Uint8Array(buffer);let str = '';let i = 0;while (i < uint8.length) {const byte = uint8[i++];// 单字节字符 (0-127)if (byte < 0x80) {str += String.fromCharCode(byte);}// 双字节字符 else if ((byte & 0xE0) === 0xC0) {const byte2 = uint8[i++];str += String.fromCharCode(((byte & 0x1F) << 6) | (byte2 & 0x3F));}// 三字节字符(支持中文)else if ((byte & 0xF0) === 0xE0) {const byte2 = uint8[i++];const byte3 = uint8[i++];str += String.fromCharCode(((byte & 0x0F) << 12) |((byte2 & 0x3F) << 6) |(byte3 & 0x3F));}// 四字节字符(简单兼容)else if ((byte & 0xF8) === 0xF0) {i += 3; // 跳过后续字节str += ''; // 替换字符占位}}return str;
}
// 请求任务管理器
const requestTaskMap = new Map();
// 中断指定请求
export const abortStreamRequest = (requestId) => {const task = requestTaskMap.get(requestId);if (task) {task.abort();requestTaskMap.delete(requestId);}
};
// 中断所有请求
export const abortAllRequests = () => {requestTaskMap.forEach(task => {task.abort()});requestTaskMap.clear();
};
调用
async startStream() {if (!this.message) {return;}const userMessage = {id: Date.now(),role: 'user',content: this.message};this.messages.push(userMessage);this.message = ""// 构建对话历史 const messages = this.messages.map(m => ({role: m.role,content: m.content}));this.currMessage = {content: "",role: "assistant",thinking: true}this.messages.push(this.currMessage)// 发起流式请求this.loading = trueconst response = await streamChatMiniProgram(messages, {secret: this.deepSeekSecret});this.loading = falsethis.currMessage = undefinedthis.$nextTick(()=>{this.scrollBottom()})}