// 增量 SSE 解析器:网络分块可能把一个事件切成两半,必须缓冲到空行再解析。 // 浏览器的 EventSource 只支持 GET 且不能带自定义请求头,AI 对话通常用 fetch + POST,再自己解析 SSE。 // 格式参考 HTML 标准:https://html.spec.whatwg.org/multipage/server-sent-events.html export interface SSEEvent { event: string; // 默认 "message" data: string; id?: string; } export function createSSEParser(onEvent: (e: SSEEvent) => void) { let buffer = ""; return (chunk: string): void => { buffer += chunk.replace(/\r\n?/g, "\n"); let sep: number; while ((sep = buffer.indexOf("\n\n")) >= 0) { const block = buffer.slice(0, sep); buffer = buffer.slice(sep + 2); const ev: SSEEvent = { event: "message", data: "" }; const data: string[] = []; for (const line of block.split("\n")) { if (!line || line.startsWith(":")) continue; // 空行、注释(常用作心跳) const i = line.indexOf(":"); const field = i < 0 ? line : line.slice(0, i); const value = i < 0 ? "" : line.slice(i + 1).replace(/^ /, ""); if (field === "event") ev.event = value; else if (field === "data") data.push(value); else if (field === "id") ev.id = value; } if (data.length) { ev.data = data.join("\n"); onEvent(ev); } } }; } // 对话任务的状态:前端据此显示“连接中 / 生成中 / 完成 / 失败(可重试)/ 已停止” export type ChatStatus = "idle" | "connecting" | "streaming" | "done" | "error" | "aborted"; // 服务端约定的事件:token(增量文本)、progress(任务进度)、final(结构化结果)、error export interface FinalPayload { answer: string; citations: { id: number; title: string }[]; } export function parseFinal(data: string): FinalPayload { const v = JSON.parse(data) as Partial; if (typeof v.answer !== "string" || !Array.isArray(v.citations)) throw new Error("final 事件格式错误"); return { answer: v.answer, citations: v.citations }; }