Skip to content

Commit 933d97b

Browse files
committed
fix(plan-notebook): buffer SSE stream lines to preserve order
1 parent 95b775b commit 933d97b

File tree

1 file changed

+43
-18
lines changed
  • agentscope-examples/plan-notebook/src/main/resources/static

1 file changed

+43
-18
lines changed

agentscope-examples/plan-notebook/src/main/resources/static/index.html

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1103,34 +1103,59 @@ <h3>Subtasks</h3>
11031103
const reader = response.body.getReader();
11041104
const decoder = new TextDecoder();
11051105
let fullText = '';
1106+
// SSE can be split across chunks; keep an incremental buffer so we only
1107+
// process complete lines like `data: xxx`.
1108+
let buffer = '';
1109+
1110+
const handleData = (data) => {
1111+
if (data === '[PAUSED]') {
1112+
isPaused = true;
1113+
stopRequested = false;
1114+
controlState = 'continue';
1115+
updateControlUI();
1116+
if (!fullText) {
1117+
fullText = '(Plan updated - Review and edit the plan, then Continue)';
1118+
}
1119+
} else {
1120+
fullText += data;
1121+
}
1122+
contentDiv.textContent = fullText;
1123+
};
11061124

11071125
while (true) {
11081126
const { done, value } = await reader.read();
11091127
if (done) break;
11101128

1111-
const chunk = decoder.decode(value);
1112-
const lines = chunk.split('\n');
1129+
const chunk = decoder.decode(value, { stream: true });
1130+
buffer += chunk;
1131+
1132+
const lines = buffer.split('\n');
1133+
// Keep the last (possibly incomplete) line for the next chunk.
1134+
buffer = lines.pop();
1135+
11131136
for (const line of lines) {
1114-
if (line.startsWith('data:')) {
1115-
const data = line.substring(5).trim();
1116-
if (data) {
1117-
if (data === '[PAUSED]') {
1118-
isPaused = true;
1119-
stopRequested = false;
1120-
controlState = 'continue';
1121-
updateControlUI();
1122-
if (!fullText) {
1123-
fullText = '(Plan updated - Review and edit the plan, then Continue)';
1124-
}
1125-
} else {
1126-
fullText += data;
1127-
}
1128-
contentDiv.textContent = fullText;
1129-
}
1137+
const lineClean = line.trimEnd();
1138+
if (!lineClean.startsWith('data:')) continue;
1139+
1140+
const data = lineClean.substring(5).trim();
1141+
if (data) {
1142+
handleData(data);
11301143
}
11311144
}
11321145
}
11331146

1147+
// Flush any remaining decoded text and process the final buffered line
1148+
// (in case the stream ends without a trailing newline).
1149+
const remaining = decoder.decode();
1150+
if (remaining) buffer += remaining;
1151+
if (buffer) {
1152+
const lineClean = buffer.trimEnd();
1153+
if (lineClean.startsWith('data:')) {
1154+
const data = lineClean.substring(5).trim();
1155+
if (data) handleData(data);
1156+
}
1157+
}
1158+
11341159
if (!fullText) {
11351160
contentDiv.textContent = defaultMessage;
11361161
}

0 commit comments

Comments
 (0)