/* Generic SSE client wrapper for novel-app streams. * * 后端事件契约: * token: {text:string} * section_start: {section:string} (拆书) * section_end: {section:string, content:string} * context: {summary:string} (写章节) * done: {content:string, word_count:number} * error: {error:string} * * handlers 全部可选: * {onToken, onSectionStart, onSectionEnd, onContext, onDone, onError} * 返回句柄 {close()}, close() 幂等. done / error 帧后自动 close. */ const EVENTS = ["token", "section_start", "section_end", "context", "done", "error"]; export function createSSEStream(url, handlers = {}) { let closed = false; const es = new EventSource(url); function close() { if (closed) return; closed = true; es.close(); } function parse(e) { try { return JSON.parse(e.data); } catch (err) { return null; } } es.addEventListener("token", (e) => { const d = parse(e); if (d && handlers.onToken) handlers.onToken(d.text || ""); }); es.addEventListener("section_start", (e) => { const d = parse(e); if (d && handlers.onSectionStart) handlers.onSectionStart(d.section); }); es.addEventListener("section_end", (e) => { const d = parse(e); if (d && handlers.onSectionEnd) handlers.onSectionEnd(d.section, d.content || ""); close(); }); es.addEventListener("context", (e) => { const d = parse(e); if (d && handlers.onContext) handlers.onContext(d.summary || ""); }); es.addEventListener("done", (e) => { const d = parse(e); if (d && handlers.onDone) handlers.onDone(d.content || "", d.word_count || 0); close(); }); es.addEventListener("error", (e) => { const d = parse(e); if (handlers.onError) handlers.onError(d && d.error ? d.error : "未知错误", "server"); close(); }); es.onerror = () => { if (!closed) { if (handlers.onError) handlers.onError("连接错误, 请检查后端日志", "connection"); close(); } }; return { close }; }