You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

209 lines
5.8 KiB

  1. import API from '@/api/config.js'
  2. import config from '@/utils/config.js'
  3. import { getClient, getNonce, getVersion, guid } from '@/utils/util.js'
  4. let BASE_URL = config.BASE_URL
  5. // #ifdef H5
  6. BASE_URL = '/api'
  7. // #endif
  8. function buildRequestData(params = {}) {
  9. const token = uni.getStorageSync('token')
  10. const uid = uni.getStorageSync('open_id')
  11. let mac = uni.getStorageSync('mac')
  12. if (!mac) {
  13. mac = uni.getSystemInfoSync().deviceId || guid()
  14. uni.setStorageSync('mac', mac)
  15. }
  16. const { version, version_code } = getVersion()
  17. const data = {
  18. token,
  19. uid,
  20. mac,
  21. base_timestamp: parseInt(Date.now() / 1000),
  22. client: getClient(),
  23. source: uni.getStorageSync('source') || '',
  24. client_ios: uni.getSystemInfoSync().platform === 'ios' ? 1 : 0,
  25. version,
  26. version_code,
  27. ...params,
  28. stream: 1
  29. }
  30. Object.keys(data).forEach((key) => {
  31. if (data[key] === '') delete data[key]
  32. })
  33. return data
  34. }
  35. function buildHeader() {
  36. const token = uni.getStorageSync('token')
  37. const { timeStr, nonce } = getNonce()
  38. const header = {
  39. 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
  40. Accept: 'text/event-stream',
  41. timestr: timeStr,
  42. nonce
  43. }
  44. if (token) header.token = token
  45. return header
  46. }
  47. function mergeBytes(pending, bytes) {
  48. if (!pending || !pending.length) return bytes
  49. const merged = new Uint8Array(pending.length + bytes.length)
  50. merged.set(pending, 0)
  51. merged.set(bytes, pending.length)
  52. return merged
  53. }
  54. function appendCodePoint(text, codePoint) {
  55. if (codePoint <= 0xffff) return text + String.fromCharCode(codePoint)
  56. const value = codePoint - 0x10000
  57. return text + String.fromCharCode(0xd800 + (value >> 10), 0xdc00 + (value & 0x3ff))
  58. }
  59. function isContinuationByte(byte) {
  60. return (byte & 0xc0) === 0x80
  61. }
  62. function decodeUtf8Fallback(bytes, state, flush = false) {
  63. const input = mergeBytes(state.utf8Pending, bytes)
  64. let output = ''
  65. let i = 0
  66. while (i < input.length) {
  67. const first = input[i]
  68. if (first < 0x80) {
  69. output += String.fromCharCode(first)
  70. i += 1
  71. continue
  72. }
  73. let needed = 0
  74. let codePoint = 0
  75. if (first >= 0xc2 && first <= 0xdf) {
  76. needed = 2
  77. codePoint = first & 0x1f
  78. } else if (first >= 0xe0 && first <= 0xef) {
  79. needed = 3
  80. codePoint = first & 0x0f
  81. } else if (first >= 0xf0 && first <= 0xf4) {
  82. needed = 4
  83. codePoint = first & 0x07
  84. } else {
  85. output += '\uFFFD'
  86. i += 1
  87. continue
  88. }
  89. if (i + needed > input.length) break
  90. let valid = true
  91. for (let j = 1; j < needed; j += 1) {
  92. const next = input[i + j]
  93. if (!isContinuationByte(next)) {
  94. valid = false
  95. break
  96. }
  97. codePoint = (codePoint << 6) | (next & 0x3f)
  98. }
  99. const second = input[i + 1]
  100. if (
  101. !valid ||
  102. (needed === 3 && ((first === 0xe0 && second < 0xa0) || (first === 0xed && second >= 0xa0))) ||
  103. (needed === 4 && ((first === 0xf0 && second < 0x90) || (first === 0xf4 && second >= 0x90)))
  104. ) {
  105. output += '\uFFFD'
  106. i += 1
  107. continue
  108. }
  109. output = appendCodePoint(output, codePoint)
  110. i += needed
  111. }
  112. const pending = Array.prototype.slice.call(input, i)
  113. state.utf8Pending = flush ? [] : pending
  114. if (flush && pending.length) output += '\uFFFD'
  115. return output
  116. }
  117. function arrayBufferToString(buffer, state, flush = false) {
  118. const bytes = buffer ? new Uint8Array(buffer) : new Uint8Array(0)
  119. if (!state.disableTextDecoder && typeof TextDecoder !== 'undefined') {
  120. try {
  121. if (!state.decoder) state.decoder = new TextDecoder('utf-8')
  122. return state.decoder.decode(bytes, { stream: !flush })
  123. } catch (e) {
  124. state.disableTextDecoder = true
  125. }
  126. }
  127. return decodeUtf8Fallback(bytes, state, flush)
  128. }
  129. function consumeSse(bufferState, chunk, callbacks) {
  130. bufferState.text += String(chunk || '').replace(/\r\n/g, '\n')
  131. const parts = bufferState.text.split(/\n\n+/)
  132. bufferState.text = parts.pop() || ''
  133. parts.forEach((part) => {
  134. const lines = part.split('\n')
  135. let event = 'message'
  136. let dataText = ''
  137. lines.forEach((line) => {
  138. if (line.indexOf('event:') === 0) event = line.replace(/^event:\s*/, '').trim()
  139. if (line.indexOf('data:') === 0) dataText += line.replace(/^data:\s*/, '')
  140. })
  141. if (!dataText) return
  142. let data = {}
  143. try {
  144. data = JSON.parse(dataText)
  145. } catch (e) {
  146. data = { content: dataText }
  147. }
  148. if (event === 'start') callbacks.onStart && callbacks.onStart(data)
  149. else if (event === 'message') callbacks.onMessage && callbacks.onMessage(data)
  150. else if (event === 'done') callbacks.onDone && callbacks.onDone(data)
  151. else if (event === 'error') callbacks.onError && callbacks.onError(data)
  152. })
  153. }
  154. function flushSse(bufferState, callbacks) {
  155. const tail = arrayBufferToString(null, bufferState, true)
  156. if (tail) consumeSse(bufferState, tail, callbacks)
  157. if (bufferState.text && bufferState.text.trim()) {
  158. consumeSse(bufferState, '\n\n', callbacks)
  159. }
  160. }
  161. export function requestNameStream(params, callbacks = {}) {
  162. // #ifdef MP-WEIXIN
  163. const bufferState = { text: '', utf8Pending: [] }
  164. const task = wx.request({
  165. url: `${BASE_URL}${API.INTERFACE_AINAME_POST_SEND}`,
  166. method: 'POST',
  167. header: buildHeader(),
  168. data: buildRequestData(params),
  169. enableChunked: true,
  170. success: (res) => {
  171. flushSse(bufferState, callbacks)
  172. if (res.statusCode >= 400) {
  173. callbacks.onError && callbacks.onError({ msg: `网络异常 (${res.statusCode})` })
  174. }
  175. },
  176. fail: (e) => {
  177. callbacks.onError && callbacks.onError({ msg: e.errMsg || '生成失败' })
  178. }
  179. })
  180. task.onChunkReceived((res) => {
  181. consumeSse(bufferState, arrayBufferToString(res.data, bufferState), callbacks)
  182. })
  183. return task
  184. // #endif
  185. // #ifndef MP-WEIXIN
  186. callbacks.onError && callbacks.onError({ msg: '当前端暂不支持流式生成' })
  187. return null
  188. // #endif
  189. }