import API from '@/api/config.js' import config from '@/utils/config.js' import { getClient, getNonce, getVersion, guid } from '@/utils/util.js' let BASE_URL = config.BASE_URL // #ifdef H5 BASE_URL = '/api' // #endif function buildRequestData(params = {}) { const token = uni.getStorageSync('token') const uid = uni.getStorageSync('open_id') let mac = uni.getStorageSync('mac') if (!mac) { mac = uni.getSystemInfoSync().deviceId || guid() uni.setStorageSync('mac', mac) } const { version, version_code } = getVersion() const data = { token, uid, mac, base_timestamp: parseInt(Date.now() / 1000), client: getClient(), source: uni.getStorageSync('source') || '', client_ios: uni.getSystemInfoSync().platform === 'ios' ? 1 : 0, version, version_code, ...params, stream: 1 } Object.keys(data).forEach((key) => { if (data[key] === '') delete data[key] }) return data } function buildHeader() { const token = uni.getStorageSync('token') const { timeStr, nonce } = getNonce() const header = { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', Accept: 'text/event-stream', timestr: timeStr, nonce } if (token) header.token = token return header } function mergeBytes(pending, bytes) { if (!pending || !pending.length) return bytes const merged = new Uint8Array(pending.length + bytes.length) merged.set(pending, 0) merged.set(bytes, pending.length) return merged } function appendCodePoint(text, codePoint) { if (codePoint <= 0xffff) return text + String.fromCharCode(codePoint) const value = codePoint - 0x10000 return text + String.fromCharCode(0xd800 + (value >> 10), 0xdc00 + (value & 0x3ff)) } function isContinuationByte(byte) { return (byte & 0xc0) === 0x80 } function decodeUtf8Fallback(bytes, state, flush = false) { const input = mergeBytes(state.utf8Pending, bytes) let output = '' let i = 0 while (i < input.length) { const first = input[i] if (first < 0x80) { output += String.fromCharCode(first) i += 1 continue } let needed = 0 let codePoint = 0 if (first >= 0xc2 && first <= 0xdf) { needed = 2 codePoint = first & 0x1f } else if (first >= 0xe0 && first <= 0xef) { needed = 3 codePoint = first & 0x0f } else if (first >= 0xf0 && first <= 0xf4) { needed = 4 codePoint = first & 0x07 } else { output += '\uFFFD' i += 1 continue } if (i + needed > input.length) break let valid = true for (let j = 1; j < needed; j += 1) { const next = input[i + j] if (!isContinuationByte(next)) { valid = false break } codePoint = (codePoint << 6) | (next & 0x3f) } const second = input[i + 1] if ( !valid || (needed === 3 && ((first === 0xe0 && second < 0xa0) || (first === 0xed && second >= 0xa0))) || (needed === 4 && ((first === 0xf0 && second < 0x90) || (first === 0xf4 && second >= 0x90))) ) { output += '\uFFFD' i += 1 continue } output = appendCodePoint(output, codePoint) i += needed } const pending = Array.prototype.slice.call(input, i) state.utf8Pending = flush ? [] : pending if (flush && pending.length) output += '\uFFFD' return output } function arrayBufferToString(buffer, state, flush = false) { const bytes = buffer ? new Uint8Array(buffer) : new Uint8Array(0) if (!state.disableTextDecoder && typeof TextDecoder !== 'undefined') { try { if (!state.decoder) state.decoder = new TextDecoder('utf-8') return state.decoder.decode(bytes, { stream: !flush }) } catch (e) { state.disableTextDecoder = true } } return decodeUtf8Fallback(bytes, state, flush) } function consumeSse(bufferState, chunk, callbacks) { bufferState.text += String(chunk || '').replace(/\r\n/g, '\n') const parts = bufferState.text.split(/\n\n+/) bufferState.text = parts.pop() || '' parts.forEach((part) => { const lines = part.split('\n') let event = 'message' let dataText = '' lines.forEach((line) => { if (line.indexOf('event:') === 0) event = line.replace(/^event:\s*/, '').trim() if (line.indexOf('data:') === 0) dataText += line.replace(/^data:\s*/, '') }) if (!dataText) return let data = {} try { data = JSON.parse(dataText) } catch (e) { data = { content: dataText } } if (event === 'start') callbacks.onStart && callbacks.onStart(data) else if (event === 'message') callbacks.onMessage && callbacks.onMessage(data) else if (event === 'done') callbacks.onDone && callbacks.onDone(data) else if (event === 'error') callbacks.onError && callbacks.onError(data) }) } function flushSse(bufferState, callbacks) { const tail = arrayBufferToString(null, bufferState, true) if (tail) consumeSse(bufferState, tail, callbacks) if (bufferState.text && bufferState.text.trim()) { consumeSse(bufferState, '\n\n', callbacks) } } export function requestNameStream(params, callbacks = {}) { // #ifdef MP-WEIXIN const bufferState = { text: '', utf8Pending: [] } const task = wx.request({ url: `${BASE_URL}${API.INTERFACE_AINAME_POST_SEND}`, method: 'POST', header: buildHeader(), data: buildRequestData(params), enableChunked: true, success: (res) => { flushSse(bufferState, callbacks) if (res.statusCode >= 400) { callbacks.onError && callbacks.onError({ msg: `网络异常 (${res.statusCode})` }) } }, fail: (e) => { callbacks.onError && callbacks.onError({ msg: e.errMsg || '生成失败' }) } }) task.onChunkReceived((res) => { consumeSse(bufferState, arrayBufferToString(res.data, bufferState), callbacks) }) return task // #endif // #ifndef MP-WEIXIN callbacks.onError && callbacks.onError({ msg: '当前端暂不支持流式生成' }) return null // #endif }