廢話不多說直接上代碼
// 小程序專用流式服務
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()})}