diff options
| author | haoyuren <13851610112@163.com> | 2026-02-12 12:45:24 -0600 |
|---|---|---|
| committer | haoyuren <13851610112@163.com> | 2026-02-12 12:45:24 -0600 |
| commit | c8fae0256c91a0ebe495270aa15baa2f27211268 (patch) | |
| tree | efc908a9fb259a18809ab5151a15fc0f1e10fdf1 /frontend/src/api.js | |
| parent | 92e1fccb1bdcf1bab7221aa9ed90f9dc72529131 (diff) | |
Multi-turn conversation, stop generation, SSE fix, and UI improvements
- Multi-turn context: all council stages now receive conversation history
(user messages + Stage 3 chairman responses) for coherent follow-ups
- Stop generation: abort streaming mid-request, recover query to input box
- SSE parsing: buffer-based chunking to prevent JSON split across packets
- Atomic storage: user + assistant messages saved together after completion,
preventing dangling messages on abort
- GFM markdown: tables, strikethrough via remark-gfm plugin + table styles
- Performance: memo user messages and completed assistant messages, only
re-render the active streaming message
- Model config: gpt-5.2, claude-opus-4.6 as chairman
- Always show input box for multi-turn conversations
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'frontend/src/api.js')
| -rw-r--r-- | frontend/src/api.js | 31 |
1 files changed, 20 insertions, 11 deletions
diff --git a/frontend/src/api.js b/frontend/src/api.js index 87ec685..a53ed90 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -73,7 +73,7 @@ export const api = { * @param {function} onEvent - Callback function for each event: (eventType, data) => void * @returns {Promise<void>} */ - async sendMessageStream(conversationId, content, onEvent) { + async sendMessageStream(conversationId, content, onEvent, signal) { const response = await fetch( `${API_BASE}/api/conversations/${conversationId}/message/stream`, { @@ -82,6 +82,7 @@ export const api = { 'Content-Type': 'application/json', }, body: JSON.stringify({ content }), + signal, } ); @@ -91,22 +92,30 @@ export const api = { const reader = response.body.getReader(); const decoder = new TextDecoder(); + let buffer = ''; while (true) { const { done, value } = await reader.read(); if (done) break; - const chunk = decoder.decode(value); - const lines = chunk.split('\n'); + buffer += decoder.decode(value, { stream: true }); - for (const line of lines) { - if (line.startsWith('data: ')) { - const data = line.slice(6); - try { - const event = JSON.parse(data); - onEvent(event.type, event); - } catch (e) { - console.error('Failed to parse SSE event:', e); + // Split on double newline (SSE event boundary) + const parts = buffer.split('\n\n'); + // Last part may be incomplete — keep it in buffer + buffer = parts.pop(); + + for (const part of parts) { + const lines = part.split('\n'); + for (const line of lines) { + if (line.startsWith('data: ')) { + const data = line.slice(6); + try { + const event = JSON.parse(data); + onEvent(event.type, event); + } catch (e) { + console.error('Failed to parse SSE event:', e); + } } } } |
