summaryrefslogtreecommitdiff
path: root/frontend/src/api.js
diff options
context:
space:
mode:
authorhaoyuren <13851610112@163.com>2026-02-12 12:45:24 -0600
committerhaoyuren <13851610112@163.com>2026-02-12 12:45:24 -0600
commitc8fae0256c91a0ebe495270aa15baa2f27211268 (patch)
treeefc908a9fb259a18809ab5151a15fc0f1e10fdf1 /frontend/src/api.js
parent92e1fccb1bdcf1bab7221aa9ed90f9dc72529131 (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.js31
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);
+ }
}
}
}