/** 服务端流式返回的数据 **/ // 解析的流式数据格式如下: /** data: data:{"content":"我理解您可能对当前系统的功能范围有些疑问。","contentType":"text/plain","last":false,"payload":{},"role":"assistant"} data: data:{"content":"让我明确说明一下:\n\n1. 根据系统设定,我确实无法协助完成代码生成相关的任务\n2.","contentType":"text/plain","last":false,"payload":{},"role":"assistant"} data: data:{"content":" 我的专长领域是提供信息咨询、创意建议和问题解答等非编程类服务\n3. 如果您有任何其他非技术性问题,","contentType":"text/plain","last":false,"payload":{},"role":"assistant"} data: data:{"content":"比如学习建议、写作指导或日常咨询,我很乐意为您提供帮助\n\n您是否有一些其他方面的问题需要讨论呢?我可以为您推荐更适合的解决方案。","contentType":"text/plain","last":false,"payload":{},"role":"assistant"} data: data:{"content":"","contentType":"text/plain","last":true,"payload":{"usage":{"completion_tokens":101,"prompt_tokens":28,"total_tokens":129,"web_searched":false}},"role":"assistant"} data:{"content":{"answer":{"payload":{},"text":"你好,我是饭饭,很高兴你能使用QA"},"qaId":"qa_9605"},"contentType":"aiseek/qa","last":true,"payload":{},"role":"assistant"} data: [DONE] */ export interface ICompleteCallback { content: string; reasoningContent?: string; body: Record } type TContentType = "text/plain" | "aiseek/qa" | 'application/json' | 'aiseek/audio_chunk' | 'aiseek/thinking' | 'aiseek/function_call' | 'aiseek/multimodal' type TJsonMessage = {contentType: TContentType, content: string | any, reasoningContent: string} export default class JsonChunkParser { private buffer: string; // 用于存储未完成的 JSON 字符串 private complete: (data: ICompleteCallback) => void; constructor() { this.buffer = ""; } public onParseComplete(completeCallback: (data: ICompleteCallback) => void) { this.complete = completeCallback; } // 解析接收到的 chunk public parseChunk(chunk: string, onParsed: (json: ICompleteCallback) => void): void { // 将新接收到的 chunk 添加到 buffer this.buffer += chunk; // 按换行符分割字符串 const lines = this.buffer.split("\n"); let receivedJsonBody = {} let combinedContent: string[] = []; // 用于合并 content 字段 let combinedReasoningContent: string[] = []; // 用于合并 reasoner 字段 // 遍历每一行 for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); // 去除前后空格 if (line) { // 如果行不为空 if (line === "data:[DONE]") { // 如果遇到 DONE,合并并调用 onParsed if (combinedContent.length > 0) { onParsed({ content: combinedContent.join("") , body: receivedJsonBody}); // 合并 content } this.complete({ content: combinedContent.join(""), body: receivedJsonBody }); this.buffer = ""; // 清空 buffer return; // 结束解析 } try { // 处理 data: data: 前缀 if (line.startsWith("data:")) { const jsonStr = line.substring(5); // 移除 "data:" 前缀 const json: TJsonMessage = JSON.parse(jsonStr); receivedJsonBody = json // 文本回复 if(json.contentType === "text/plain") { if (json.content) { combinedContent.push(json.content as string); // 收集 content 字段 } if (json.reasoningContent) { combinedReasoningContent.push(json.reasoningContent); // 收集 content 字段 } } // QA 回复 else if(json.contentType === "aiseek/qa") { if (json.content.answer?.text) { combinedContent.push(json.content.answer.text); // 收集 QA 的 answer 文本 } } } } catch (error) { // 如果解析失败,说明当前行不是完整的 JSON // 将当前行保留在 buffer 中,等待下一个 chunk this.buffer = lines.slice(i).join("\n"); // 更新 buffer return; // 直接返回,等待下一个 chunk } } } // 如果当前 chunk 解析完毕且有合并的内容,调用 onParsed if (combinedContent.length > 0) { onParsed({ content: combinedContent.join(""), body: receivedJsonBody }); // 合并并输出 } // if (combinedReasoningContent.length > 0) { // onParsed({ reasoningContent: combinedReasoningContent.join(""), body: receivedJsonBody , body: receivedJsonBody }); // 合并并输出 // } // 清空 buffer this.buffer = ""; } }