jsonChunkParser.ts 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. /** 服务端流式返回的数据 **/
  2. // 解析的流式数据格式如下:
  3. /**
  4. data: data:{"content":"我理解您可能对当前系统的功能范围有些疑问。","contentType":"text/plain","last":false,"payload":{},"role":"assistant"}
  5. data: data:{"content":"让我明确说明一下:\n\n1. 根据系统设定,我确实无法协助完成代码生成相关的任务\n2.","contentType":"text/plain","last":false,"payload":{},"role":"assistant"}
  6. data: data:{"content":" 我的专长领域是提供信息咨询、创意建议和问题解答等非编程类服务\n3. 如果您有任何其他非技术性问题,","contentType":"text/plain","last":false,"payload":{},"role":"assistant"}
  7. data: data:{"content":"比如学习建议、写作指导或日常咨询,我很乐意为您提供帮助\n\n您是否有一些其他方面的问题需要讨论呢?我可以为您推荐更适合的解决方案。","contentType":"text/plain","last":false,"payload":{},"role":"assistant"}
  8. data: data:{"content":"","contentType":"text/plain","last":true,"payload":{"usage":{"completion_tokens":101,"prompt_tokens":28,"total_tokens":129,"web_searched":false}},"role":"assistant"}
  9. data:{"content":{"answer":{"payload":{},"text":"你好,我是饭饭,很高兴你能使用QA"},"qaId":"qa_9605"},"contentType":"aiseek/qa","last":true,"payload":{},"role":"assistant"}
  10. data: [DONE]
  11. */
  12. export interface ICompleteCallback {
  13. content: string;
  14. reasoningContent?: string;
  15. body: Record<string,any>
  16. }
  17. type TContentType = "text/plain" | "aiseek/qa" | 'application/json' | 'aiseek/audio_chunk' | 'aiseek/thinking' | 'aiseek/function_call' | 'aiseek/multimodal'
  18. type TJsonMessage = {contentType: TContentType, content: string | any, reasoningContent: string}
  19. export default class JsonChunkParser {
  20. private buffer: string; // 用于存储未完成的 JSON 字符串
  21. private complete: (data: ICompleteCallback) => void;
  22. constructor() {
  23. this.buffer = "";
  24. }
  25. public onParseComplete(completeCallback: (data: ICompleteCallback) => void) {
  26. this.complete = completeCallback;
  27. }
  28. // 解析接收到的 chunk
  29. public parseChunk(chunk: string, onParsed: (json: ICompleteCallback) => void): void {
  30. // 将新接收到的 chunk 添加到 buffer
  31. this.buffer += chunk;
  32. // 按换行符分割字符串
  33. const lines = this.buffer.split("\n");
  34. let receivedJsonBody = {}
  35. let combinedContent: string[] = []; // 用于合并 content 字段
  36. let combinedReasoningContent: string[] = []; // 用于合并 reasoner 字段
  37. // 遍历每一行
  38. for (let i = 0; i < lines.length; i++) {
  39. const line = lines[i].trim(); // 去除前后空格
  40. if (line) {
  41. // 如果行不为空
  42. if (line === "data:[DONE]") {
  43. // 如果遇到 DONE,合并并调用 onParsed
  44. if (combinedContent.length > 0) {
  45. onParsed({ content: combinedContent.join("") , body: receivedJsonBody}); // 合并 content
  46. }
  47. this.complete({ content: combinedContent.join(""), body: receivedJsonBody });
  48. this.buffer = ""; // 清空 buffer
  49. return; // 结束解析
  50. }
  51. try {
  52. // 处理 data: data: 前缀
  53. if (line.startsWith("data:")) {
  54. const jsonStr = line.substring(5); // 移除 "data:" 前缀
  55. const json: TJsonMessage = JSON.parse(jsonStr);
  56. receivedJsonBody = json
  57. // 文本回复
  58. if(json.contentType === "text/plain") {
  59. if (json.content) {
  60. combinedContent.push(json.content as string); // 收集 content 字段
  61. }
  62. if (json.reasoningContent) {
  63. combinedReasoningContent.push(json.reasoningContent); // 收集 content 字段
  64. }
  65. }
  66. // QA 回复
  67. else if(json.contentType === "aiseek/qa") {
  68. if (json.content.answer?.text) {
  69. combinedContent.push(json.content.answer.text); // 收集 QA 的 answer 文本
  70. }
  71. }
  72. }
  73. } catch (error) {
  74. // 如果解析失败,说明当前行不是完整的 JSON
  75. // 将当前行保留在 buffer 中,等待下一个 chunk
  76. this.buffer = lines.slice(i).join("\n"); // 更新 buffer
  77. return; // 直接返回,等待下一个 chunk
  78. }
  79. }
  80. }
  81. // 如果当前 chunk 解析完毕且有合并的内容,调用 onParsed
  82. if (combinedContent.length > 0) {
  83. onParsed({ content: combinedContent.join(""), body: receivedJsonBody }); // 合并并输出
  84. }
  85. // if (combinedReasoningContent.length > 0) {
  86. // onParsed({ reasoningContent: combinedReasoningContent.join(""), body: receivedJsonBody , body: receivedJsonBody }); // 合并并输出
  87. // }
  88. // 清空 buffer
  89. this.buffer = "";
  90. }
  91. }