mirror of
https://github.com/danielmiessler/fabric.git
synced 2026-09-10 07:36:44 -04:00
Merge pull request #2029 from ksylvan/kayvan/web-cleanup
Web UI: Fix streaming chat: tokens now accumulate in order into a single message
This commit is contained in:
commit
abf045ac81
6
cmd/generate_changelog/incoming/2029.txt
Normal file
6
cmd/generate_changelog/incoming/2029.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
### PR [#2029](https://github.com/danielmiessler/Fabric/pull/2029) by [ksylvan](https://github.com/ksylvan): Web UI: Fix streaming chat: tokens now accumulate in order into a single message
|
||||
|
||||
- Feat: improve streaming message handling and SSE buffer parsing
|
||||
- Append content to existing assistant messages instead of replacing, and fix loading message removal to search by index rather than position
|
||||
- Refactor SSE buffer splitting to always retain incomplete segments, and trim segments before parsing to handle whitespace edge cases
|
||||
- Consolidate duplicate message update logic across chat components and process remaining buffer after stream completion more reliably
|
||||
|
|
@ -329,15 +329,22 @@ async function readFileContent(file: File): Promise<string> {
|
|||
messageStore.update(messages => {
|
||||
const newMessages = [...messages];
|
||||
// Replace the processing message with actual content
|
||||
const lastMessage = newMessages[newMessages.length - 1];
|
||||
if (lastMessage?.format === 'loading') {
|
||||
newMessages.pop();
|
||||
const loadingIndex = newMessages.findIndex(m => m.format === 'loading');
|
||||
if (loadingIndex !== -1) {
|
||||
newMessages.splice(loadingIndex, 1);
|
||||
}
|
||||
|
||||
const lastMessage = newMessages[newMessages.length - 1];
|
||||
if (lastMessage?.role === 'assistant') {
|
||||
lastMessage.content += content;
|
||||
lastMessage.format = response?.format;
|
||||
} else {
|
||||
newMessages.push({
|
||||
role: 'assistant',
|
||||
content,
|
||||
format: response?.format
|
||||
});
|
||||
}
|
||||
newMessages.push({
|
||||
role: 'assistant',
|
||||
content,
|
||||
format: response?.format
|
||||
});
|
||||
return newMessages;
|
||||
});
|
||||
},
|
||||
|
|
@ -442,13 +449,18 @@ async function readFileContent(file: File): Promise<string> {
|
|||
if (loadingIndex !== -1) {
|
||||
newMessages.splice(loadingIndex, 1);
|
||||
}
|
||||
|
||||
// Always append a new assistant message
|
||||
newMessages.push({
|
||||
role: 'assistant',
|
||||
content,
|
||||
format: response?.format
|
||||
});
|
||||
|
||||
const lastMessage = newMessages[newMessages.length - 1];
|
||||
if (lastMessage?.role === 'assistant') {
|
||||
lastMessage.content += content;
|
||||
lastMessage.format = response?.format;
|
||||
} else {
|
||||
newMessages.push({
|
||||
role: 'assistant',
|
||||
content,
|
||||
format: response?.format
|
||||
});
|
||||
}
|
||||
return newMessages;
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -142,31 +142,41 @@ export class ChatService {
|
|||
if (done) break;
|
||||
|
||||
buffer += new TextDecoder().decode(value);
|
||||
const messages = buffer
|
||||
.split("\n\n")
|
||||
.filter((msg) => msg.startsWith("data: "));
|
||||
|
||||
if (messages.length > 1) {
|
||||
buffer = messages.pop() || "";
|
||||
for (const msg of messages) {
|
||||
try {
|
||||
let response = JSON.parse(msg.slice(6)) as StreamResponse;
|
||||
response = processResponse(response);
|
||||
controller.enqueue(response);
|
||||
} catch (parseError) {
|
||||
console.error("Error parsing stream message:", parseError);
|
||||
}
|
||||
const segments = buffer.split("\n\n");
|
||||
// Last segment may be incomplete; keep it as buffer
|
||||
buffer = segments.pop() || "";
|
||||
for (const segment of segments) {
|
||||
const trimmed = segment.trim();
|
||||
if (!trimmed.startsWith("data: ")) continue;
|
||||
try {
|
||||
let response = JSON.parse(
|
||||
trimmed.slice(6),
|
||||
) as StreamResponse;
|
||||
response = processResponse(response);
|
||||
controller.enqueue(response);
|
||||
} catch (parseError) {
|
||||
console.error(
|
||||
"Error parsing stream message:",
|
||||
parseError,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (buffer.startsWith("data: ")) {
|
||||
// Process any remaining complete message in the buffer
|
||||
const trimmed = buffer.trim();
|
||||
if (trimmed.startsWith("data: ")) {
|
||||
try {
|
||||
let response = JSON.parse(buffer.slice(6)) as StreamResponse;
|
||||
let response = JSON.parse(
|
||||
trimmed.slice(6),
|
||||
) as StreamResponse;
|
||||
response = processResponse(response);
|
||||
controller.enqueue(response);
|
||||
} catch (parseError) {
|
||||
console.error("Error parsing final message:", parseError);
|
||||
console.error(
|
||||
"Error parsing final message:",
|
||||
parseError,
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ export async function sendMessage(
|
|||
const lastMessage = newMessages[newMessages.length - 1];
|
||||
|
||||
if (lastMessage?.role === "assistant") {
|
||||
lastMessage.content = content;
|
||||
lastMessage.content += content;
|
||||
lastMessage.format = response?.format;
|
||||
console.log("Message updated:", {
|
||||
role: "assistant",
|
||||
|
|
|
|||
Loading…
Reference in a new issue