message.ts 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. import { formatDate, formatPattern } from '@/utils/formatDate';
  2. import { API_BASE_URL, getSignature } from '../utils/http';
  3. import { TextDecoder } from 'text-encoding';
  4. let requestTask = null;
  5. let messageQueue = [];
  6. let printFlag = false; // 打印标识
  7. let intervalFlag = null;
  8. let requestTaskFlag = false;
  9. let chunkedValid = false;
  10. const parseEvent = (eventData: any) => {
  11. let data = '';
  12. const lines = eventData.split('\n');
  13. if (!lines) {
  14. return null;
  15. }
  16. lines.forEach((line: any) => {
  17. if (line.startsWith('data:')) {
  18. data += eventData.slice(5).trim();
  19. }
  20. });
  21. return data || null;
  22. };
  23. const stopSSE = () => {
  24. if (requestTask) {
  25. requestTask.abort();
  26. }
  27. if (intervalFlag) {
  28. clearInterval(intervalFlag);
  29. }
  30. messageQueue = [];
  31. printFlag = false;
  32. intervalFlag = null;
  33. };
  34. /**
  35. * 判断字符串是否为 JSON
  36. * @param str
  37. */
  38. const isJSON = (str: string): boolean => {
  39. try {
  40. if (!isNaN(parseFloat(str)) && isFinite(Number(str))) {
  41. return false;
  42. }
  43. JSON.parse(str);
  44. return true;
  45. } catch {
  46. return false;
  47. }
  48. };
  49. const printText = (callback: Function = () => {}) => {
  50. if (printFlag) return;
  51. printFlag = true;
  52. intervalFlag = setInterval(() => {
  53. try {
  54. if (messageQueue.length) {
  55. let resultData = messageQueue.shift();
  56. switch (resultData.event.value) {
  57. case 'conversation.chat.created':
  58. callback({
  59. type: 'id',
  60. id: resultData.chat.id,
  61. value: resultData.chat.conversationID
  62. });
  63. break;
  64. case 'conversation.message.delta':
  65. let content = resultData.message.content;
  66. if (!isJSON(content)) {
  67. callback({
  68. type: 'text',
  69. value: content
  70. });
  71. }
  72. break;
  73. case 'conversation.message.completed': // 完整回答
  74. if (resultData.message.contentType.value == 'card') {
  75. let MessageContent = JSON.parse(resultData.message.content);
  76. let messageData = JSON.parse(MessageContent.data ?? '{}');
  77. let cardList: any[] = [];
  78. if (typeof messageData.variables == 'object' && messageData.variables) {
  79. Object.keys(messageData.variables).forEach(key => {
  80. let itemContent = messageData.variables[key];
  81. if (itemContent.defaultValue && itemContent.defaultValue.source) {
  82. cardList = cardList.concat(itemContent.defaultValue.source);
  83. }
  84. });
  85. }
  86. if (cardList.length) {
  87. callback({
  88. type: 'source',
  89. value: cardList
  90. });
  91. }
  92. } else if (resultData.message.contentType.value == 'text') {
  93. // 处理 Coze 返回的 resources 数据(网络搜索结果)
  94. if (isJSON(resultData.message.content)) {
  95. let MessageContent = JSON.parse(resultData.message.content);
  96. let cardList: any[] = [];
  97. if (Array.isArray(MessageContent) && MessageContent.length) {
  98. MessageContent.forEach((itemContent: any) => {
  99. if (itemContent.resource == 'net') {
  100. cardList.push({
  101. logo_url: itemContent.icon ?? '',
  102. title: itemContent.title ?? '',
  103. summary: itemContent.summary ?? '',
  104. url: itemContent.url ?? ''
  105. });
  106. }
  107. });
  108. }
  109. if (cardList.length) {
  110. callback({
  111. type: 'source',
  112. value: cardList
  113. });
  114. }
  115. }
  116. }
  117. break;
  118. case 'conversation.chat.completed':
  119. callback({
  120. type: 'completed',
  121. value: ''
  122. });
  123. stopSSE();
  124. break;
  125. case 'conversation.chat.done':
  126. callback({
  127. type: 'done',
  128. value: ''
  129. });
  130. stopSSE();
  131. break;
  132. }
  133. }
  134. } catch (error) {
  135. stopSSE();
  136. }
  137. }, 50);
  138. };
  139. const connetSSE = async (message: string, conversationId: string, callback: Function = () => {}) => {
  140. try {
  141. let hTimestamp = formatDate(formatPattern.h_timestamp);
  142. let params = {
  143. message: encodeURIComponent(message),
  144. conversationId: conversationId
  145. };
  146. let buffer = '';
  147. requestTask = uni.request({
  148. url: API_BASE_URL + '/v1/chat/sendMsg',
  149. enableChunked: true, // 启用分块传输
  150. data: params,
  151. timeout: 600000,
  152. header: {
  153. 'Content-Type': 'text/event-stream;charset=utf-8', // 根据服务器要求设置请求头
  154. 'h-timestamp': hTimestamp,
  155. 'h-sign': getSignature(params, hTimestamp)
  156. },
  157. success: (res) => {
  158. console.log('SSE请求完成', res);
  159. if (!chunkedValid) {
  160. let eventEndIndex: number;
  161. let content = res.data;
  162. while ((eventEndIndex = content.indexOf('\n\n')) !== -1) {
  163. if (!requestTaskFlag) {
  164. return;
  165. }
  166. const eventData = content.slice(0, eventEndIndex);
  167. content = content.slice(eventEndIndex + 2); // 移除已处理的数据
  168. // 解析事件内容
  169. const message = parseEvent(eventData);
  170. if (requestTaskFlag && message) {
  171. messageQueue.push(JSON.parse(message)); // 向列表中添加元素
  172. }
  173. requestTaskFlag && printText(callback); // 开始打印
  174. }
  175. }
  176. },
  177. fail: (err) => {
  178. console.error('SSE请求失败', err);
  179. // let message = '';
  180. // if (err.errMsg == 'request:fail timeout') {
  181. // message = '网络请求超时,请稍后再试...';
  182. // } else {
  183. // message = '网络请求异常,请稍后再试:' + err.errMsg;
  184. // }
  185. // uni.showToast({
  186. // title: message,
  187. // icon: 'none'
  188. // });
  189. stopSSE();
  190. callback({
  191. type: 'done',
  192. value: ''
  193. });
  194. }
  195. });
  196. const Utf8Decoder = new TextDecoder('utf-8');
  197. requestTask.onChunkReceived((res: any) => {
  198. chunkedValid = true;
  199. let chunk = Utf8Decoder.decode(res.data);
  200. buffer += chunk;
  201. // 解析缓冲区中的事件(按 \n\n 分割)
  202. let eventEndIndex: number;
  203. while ((eventEndIndex = buffer.indexOf('\n\n')) !== -1) {
  204. if (!requestTaskFlag) {
  205. return;
  206. }
  207. const eventData = buffer.slice(0, eventEndIndex);
  208. buffer = buffer.slice(eventEndIndex + 2); // 移除已处理的数据
  209. // 解析事件内容
  210. const message = parseEvent(eventData);
  211. if (requestTaskFlag && message) {
  212. messageQueue.push(JSON.parse(message)); // 向列表中添加元素
  213. }
  214. requestTaskFlag && printText(callback); // 开始打印
  215. }
  216. });
  217. } catch (error) {
  218. console.error('请求流数据时出错2:', error);
  219. }
  220. };
  221. export const MessageApi = {
  222. sendMessage: (message: string, conversationId: string, callback: Function = () => {}) => {
  223. try {
  224. requestTaskFlag = true;
  225. connetSSE(message, conversationId, callback);
  226. } catch (error) {
  227. console.log(error);
  228. }
  229. },
  230. stop() {
  231. requestTaskFlag = false;
  232. stopSSE();
  233. }
  234. };