Skip to content

Commit d984dc0

Browse files
Algorithm5838github-actions[bot]
authored andcommitted
perf: incremental artifact updates during streaming
1 parent bd9d865 commit d984dc0

1 file changed

Lines changed: 83 additions & 34 deletions

File tree

src/lib/components/chat/Chat.svelte

Lines changed: 83 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,10 @@
497497
message.content += data.content;
498498
} else if (type === 'chat:message' || type === 'replace') {
499499
message.content = data.content;
500+
// Non-current content patch; invalidate the stable cache.
501+
if (event.message_id !== history.currentId) {
502+
stableArtifactCurrentId = null;
503+
}
500504
} else if (type === 'chat:message:files' || type === 'files') {
501505
message.files = data.files;
502506
} else if (type === 'chat:message:tasks') {
@@ -532,6 +536,10 @@
532536
originalContent: existing.content,
533537
...msg
534538
};
539+
// Non-current content patch; invalidate the stable cache.
540+
if (msg.id !== history.currentId) {
541+
stableArtifactCurrentId = null;
542+
}
535543
}
536544
}
537545
}
@@ -1056,32 +1064,13 @@
10561064
}
10571065
};
10581066
1059-
const onHistoryChange = (history) => {
1060-
if (history) {
1061-
cancelAnimationFrame(contentsRAF);
1062-
contentsRAF = requestAnimationFrame(() => {
1063-
getContents();
1064-
contentsRAF = null;
1065-
});
1066-
} else {
1067-
artifactContents.set([]);
1068-
}
1069-
};
1070-
1071-
$: onHistoryChange(history);
1072-
1073-
const getContents = () => {
1074-
const messages = history ? createMessagesList(history, history.currentId) : [];
1075-
let contents = [];
1076-
messages.forEach((message) => {
1077-
if (message?.role !== 'user' && message?.content) {
1078-
const { codeBlocks: codeBlocks, htmlGroups: htmlGroups } = getCodeBlockContents(
1079-
message.content
1080-
);
1067+
const extractArtifactItemsFromContent = (content: string): ArtifactItem[] => {
1068+
const { codeBlocks, htmlGroups } = getCodeBlockContents(content);
1069+
const items: ArtifactItem[] = [];
10811070
1082-
if (htmlGroups && htmlGroups.length > 0) {
1083-
htmlGroups.forEach((group) => {
1084-
const renderedContent = `
1071+
if (htmlGroups && htmlGroups.length > 0) {
1072+
htmlGroups.forEach((group) => {
1073+
const renderedContent = `
10851074
<!DOCTYPE html>
10861075
<html lang="en">
10871076
<head>
@@ -1104,22 +1093,78 @@
11041093
</body>
11051094
</html>
11061095
`;
1107-
contents = [...contents, { type: 'iframe', content: renderedContent }];
1108-
});
1109-
} else {
1110-
// Check for SVG content
1111-
for (const block of codeBlocks) {
1112-
if (block.lang === 'svg' || (block.lang === 'xml' && block.code.includes('<svg'))) {
1113-
contents = [...contents, { type: 'svg', content: block.code }];
1114-
}
1115-
}
1096+
items.push({ type: 'iframe', content: renderedContent });
1097+
});
1098+
} else {
1099+
for (const block of codeBlocks) {
1100+
if (block.lang === 'svg' || (block.lang === 'xml' && block.code.includes('<svg'))) {
1101+
items.push({ type: 'svg', content: block.code });
11161102
}
11171103
}
1104+
}
1105+
1106+
return items;
1107+
};
1108+
1109+
const onHistoryChange = (history) => {
1110+
if (!history) {
1111+
stableArtifactContents = [];
1112+
stableArtifactCurrentId = null;
1113+
artifactContents.set([]);
1114+
return;
1115+
}
1116+
1117+
const currentMsg = history.currentId ? history.messages[history.currentId] : null;
1118+
const isStreamingAssistant = currentMsg?.role === 'assistant' && !currentMsg.done;
1119+
1120+
cancelAnimationFrame(contentsRAF);
1121+
contentsRAF = requestAnimationFrame(() => {
1122+
isStreamingAssistant ? getStreamingContents() : getContents();
1123+
contentsRAF = null;
11181124
});
1125+
};
1126+
1127+
$: onHistoryChange(history);
1128+
1129+
// Full reconcile; resets stable cache so the next streaming session starts clean.
1130+
const getContents = () => {
1131+
const messages = history ? createMessagesList(history, history.currentId) : [];
1132+
const contents: ArtifactItem[] = [];
11191133
1134+
for (const message of messages) {
1135+
if (message?.role !== 'user' && message?.content) {
1136+
contents.push(...extractArtifactItemsFromContent(message.content));
1137+
}
1138+
}
1139+
1140+
stableArtifactContents = [];
1141+
stableArtifactCurrentId = null;
11201142
artifactContents.set(contents);
11211143
};
11221144
1145+
// Streaming hot path; only parses the current message per frame.
1146+
// Rebuilds the stable cache once at the start of each streaming session.
1147+
const getStreamingContents = () => {
1148+
const currentMsg = history.currentId ? history.messages[history.currentId] : null;
1149+
1150+
if (stableArtifactCurrentId !== history.currentId) {
1151+
const messages = history ? createMessagesList(history, history.currentId) : [];
1152+
stableArtifactContents = [];
1153+
for (const message of messages) {
1154+
if (message.id !== history.currentId && message?.role !== 'user' && message?.content) {
1155+
stableArtifactContents.push(...extractArtifactItemsFromContent(message.content));
1156+
}
1157+
}
1158+
stableArtifactCurrentId = history.currentId;
1159+
}
1160+
1161+
const streamingItems = currentMsg?.content
1162+
? extractArtifactItemsFromContent(currentMsg.content)
1163+
: [];
1164+
1165+
artifactContents.set([...stableArtifactContents, ...streamingItems]);
1166+
};
1167+
11231168
//////////////////////////
11241169
// Web functions
11251170
//////////////////////////
@@ -1512,8 +1557,12 @@
15121557
await messagesRef?.scrollToTop();
15131558
};
15141559
1560+
type ArtifactItem = { type: string; content: string };
1561+
15151562
let scrollRAF = null;
15161563
let contentsRAF = null;
1564+
let stableArtifactContents: ArtifactItem[] = [];
1565+
let stableArtifactCurrentId: string | null = null;
15171566
const scheduleScrollToBottom = () => {
15181567
if (!scrollRAF) {
15191568
scrollRAF = requestAnimationFrame(async () => {

0 commit comments

Comments
 (0)