- {#if isStreaming && messageIndex === messages.length - 1}
-
+
+ {#if currentStreamingMessageId === message.id}
+
{:else}
- {@html getStaticHTML(message, messageIndex)}
+ {@html getStaticHTML(message)}
{/if}
{/if}
diff --git a/Components/ChatWindow.svelte b/Components/ChatWindow.svelte
index a44596c..de34177 100644
--- a/Components/ChatWindow.svelte
+++ b/Components/ChatWindow.svelte
@@ -36,6 +36,7 @@
let hasNoApiKey = false;
let isSubmitting = false;
let isStreaming = false;
+ let currentStreamingMessageId: string | null = null;
let conversation = new Conversation();
@@ -138,9 +139,12 @@
}
async function streamRequestResponse(): Promise<{ functionCall: AIFunctionCall | null, shouldContinue: boolean }> {
- // Create AI message placeholder
- const aiMessageIndex = conversation.contents.length;
- conversation.contents = [...conversation.contents, new ConversationContent(Role.Assistant, "")];
+ // Create AI message with stable ID
+ const aiMessage = new ConversationContent(Role.Assistant, "");
+ conversation.contents = [...conversation.contents, aiMessage];
+
+ // Track which message is currently streaming
+ currentStreamingMessageId = aiMessage.id;
isStreaming = true;
let accumulatedContent = "";
@@ -150,12 +154,13 @@
for await (const chunk of ai.streamRequest(conversation)) {
if (chunk.error) {
console.error("Streaming error:", chunk.error);
- conversation.contents = conversation.contents.map((msg, messageIndex) =>
- messageIndex === aiMessageIndex
+ conversation.contents = conversation.contents.map((msg) =>
+ msg.id === aiMessage.id
? { ...msg, content: "Error: " + chunk.error }
: msg
);
isStreaming = false;
+ currentStreamingMessageId = null;
await conversationService.saveConversation(conversation);
break;
}
@@ -163,8 +168,8 @@
if (chunk.content) {
currentThought = null;
accumulatedContent += chunk.content;
- conversation.contents = conversation.contents.map((msg, messageIndex) =>
- messageIndex === aiMessageIndex
+ conversation.contents = conversation.contents.map((msg) =>
+ msg.id === aiMessage.id
? { ...msg, content: accumulatedContent }
: msg
);
@@ -179,25 +184,32 @@
}
if (chunk.isComplete) {
+ // Mark streaming as complete for this message
isStreaming = false;
- // If there's a function call, remove the placeholder message
- if (capturedFunctionCall) {
- conversation.contents = conversation.contents.filter((_, messageIndex) => messageIndex !== aiMessageIndex);
- } else if (accumulatedContent.trim() !== "") {
- // Only save the message if it has content and no function call
- conversation.contents = conversation.contents.map((msg, messageIndex) =>
- messageIndex === aiMessageIndex
+ currentStreamingMessageId = null;
+
+ // Handle message based on content and function call presence
+ if (accumulatedContent.trim() !== "") {
+ console.log(`acc: ${accumulatedContent}, func: ${capturedFunctionCall}, cont: ${capturedShouldContinue}`)
+ // We have content - always keep the message
+ conversation.contents = conversation.contents.map((msg) =>
+ msg.id === aiMessage.id
? { ...msg, content: accumulatedContent }
: msg
);
+ } else if (capturedFunctionCall) {
+ // No content but there's a function call - remove the empty placeholder
+ conversation.contents = conversation.contents.filter((msg) => msg.id !== aiMessage.id);
} else {
- // Remove the empty placeholder message
- conversation.contents = conversation.contents.filter((_, messageIndex) => messageIndex !== aiMessageIndex);
+ // No content and no function call - remove empty message
+ conversation.contents = conversation.contents.filter((msg) => msg.id !== aiMessage.id);
}
await conversationService.saveConversation(conversation);
}
}
+ console.log(`shouldContinue: ${capturedShouldContinue}`)
+
return { functionCall: capturedFunctionCall, shouldContinue: capturedShouldContinue };
}
@@ -250,7 +262,7 @@
-
+
diff --git a/Conversations/ConversationContent.ts b/Conversations/ConversationContent.ts
index c7f17c9..b19f9e2 100644
--- a/Conversations/ConversationContent.ts
+++ b/Conversations/ConversationContent.ts
@@ -1,19 +1,22 @@
export class ConversationContent {
+ id: string;
role: string;
content: string
timestamp: Date;
isFunctionCall: boolean;
isFunctionCallResponse: boolean;
- public static isConversationContentData(data: unknown): data is { role: string; content: string; timestamp: string } {
+ public static isConversationContentData(data: unknown): data is { id: string; role: string; content: string; timestamp: string } {
return (
typeof data === 'object' &&
data !== null &&
+ 'id' in data &&
'role' in data &&
'content' in data &&
'timestamp' in data &&
'isFunctionCall' in data &&
'isFunctionCallResponse' in data &&
+ typeof data.id === 'string' &&
typeof data.role === 'string' &&
typeof data.content === 'string' &&
typeof data.timestamp === 'string' &&
@@ -21,8 +24,9 @@ export class ConversationContent {
typeof data.isFunctionCallResponse == 'boolean'
);
}
-
- constructor(role: string, content: string, timestamp: Date = new Date(), isFunctionCall = false, isFunctionCallResponse = false) {
+
+ constructor(role: string, content: string, timestamp: Date = new Date(), isFunctionCall = false, isFunctionCallResponse = false, id?: string) {
+ this.id = id || `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
this.role = role;
this.content = content;
this.timestamp = timestamp;
diff --git a/Services/ConversationFileSystemService.ts b/Services/ConversationFileSystemService.ts
index 0eaa520..49d348b 100644
--- a/Services/ConversationFileSystemService.ts
+++ b/Services/ConversationFileSystemService.ts
@@ -27,6 +27,7 @@ export class ConversationFileSystemService {
title: conversation.title,
created: conversation.created.toISOString(),
contents: conversation.contents.map(content => ({
+ id: content.id,
role: content.role,
content: content.content,
timestamp: content.timestamp.toISOString(),
@@ -77,7 +78,7 @@ export class ConversationFileSystemService {
conversation.created = new Date(data.created);
conversation.contents = data.contents.map(content => {
return new ConversationContent(
- content.role, content.content, new Date(content.timestamp), content.isFunctionCall, content.isFunctionCallResponse);
+ content.role, content.content, new Date(content.timestamp), content.isFunctionCall, content.isFunctionCallResponse, content.id);
});
conversations.push(conversation);
}