Prefill messages in main process and ensure loading state is always shown (incl. new chat) (#35)

This commit is contained in:
Will Chen
2025-04-28 16:47:02 -07:00
committed by GitHub
parent 9fb5439ecf
commit 813f170c68
2 changed files with 54 additions and 58 deletions

View File

@@ -62,44 +62,18 @@ export function useStreamChat({
} }
setError(null); setError(null);
setMessages((currentMessages: Message[]) => {
if (redo) {
let remainingMessages = currentMessages.slice();
if (
currentMessages[currentMessages.length - 1].role === "assistant"
) {
remainingMessages = currentMessages.slice(0, -1);
}
return [
...remainingMessages,
{
id: getRandomNumberId(),
role: "assistant",
content: "",
},
];
}
return [
...currentMessages,
{
id: getRandomNumberId(),
role: "user",
content: prompt,
},
{
id: getRandomNumberId(),
role: "assistant",
content: "",
},
];
});
setIsStreaming(true); setIsStreaming(true);
setStreamCount((streamCount) => streamCount + 1); let hasIncrementedStreamCount = false;
try { try {
IpcClient.getInstance().streamMessage(prompt, { IpcClient.getInstance().streamMessage(prompt, {
chatId, chatId,
redo, redo,
onUpdate: (updatedMessages: Message[]) => { onUpdate: (updatedMessages: Message[]) => {
if (!hasIncrementedStreamCount) {
setStreamCount((streamCount) => streamCount + 1);
hasIncrementedStreamCount = true;
}
setMessages(updatedMessages); setMessages(updatedMessages);
}, },
onEnd: (response: ChatResponseEnd) => { onEnd: (response: ChatResponseEnd) => {

View File

@@ -97,6 +97,16 @@ export function registerChatStreamHandlers() {
}) })
.returning(); .returning();
// Add a placeholder assistant message immediately
const [placeholderAssistantMessage] = await db
.insert(messages)
.values({
chatId: req.chatId,
role: "assistant",
content: "", // Start with empty content
})
.returning();
// Fetch updated chat data after possible deletions and additions // Fetch updated chat data after possible deletions and additions
const updatedChat = await db.query.chats.findFirst({ const updatedChat = await db.query.chats.findFirst({
where: eq(chats.id, req.chatId), where: eq(chats.id, req.chatId),
@@ -112,6 +122,12 @@ export function registerChatStreamHandlers() {
throw new Error(`Chat not found: ${req.chatId}`); throw new Error(`Chat not found: ${req.chatId}`);
} }
// Send the messages right away so that the loading state is shown for the message.
event.sender.send("chat:response:chunk", {
chatId: req.chatId,
messages: updatedChat.messages,
});
let fullResponse = ""; let fullResponse = "";
// Check if this is a test prompt // Check if this is a test prompt
@@ -206,7 +222,7 @@ export function registerChatStreamHandlers() {
temperature: 0, temperature: 0,
model: modelClient, model: modelClient,
system: systemPrompt, system: systemPrompt,
messages: chatMessages, messages: chatMessages.filter((m) => m.content),
onError: (error) => { onError: (error) => {
logger.error("Error streaming text:", error); logger.error("Error streaming text:", error);
const message = const message =
@@ -240,16 +256,20 @@ export function registerChatStreamHandlers() {
// Store the current partial response // Store the current partial response
partialResponses.set(req.chatId, fullResponse); partialResponses.set(req.chatId, fullResponse);
// Update the placeholder assistant message content in the messages array
const currentMessages = [...updatedChat.messages];
if (
currentMessages.length > 0 &&
currentMessages[currentMessages.length - 1].role === "assistant"
) {
currentMessages[currentMessages.length - 1].content =
fullResponse;
}
// Update the assistant message in the database // Update the assistant message in the database
event.sender.send("chat:response:chunk", { event.sender.send("chat:response:chunk", {
chatId: req.chatId, chatId: req.chatId,
messages: [ messages: currentMessages,
...updatedChat.messages,
{
role: "assistant",
content: fullResponse,
},
],
}); });
// If the stream was aborted, exit early // If the stream was aborted, exit early
@@ -266,14 +286,20 @@ export function registerChatStreamHandlers() {
// If we have a partial response, save it to the database // If we have a partial response, save it to the database
if (partialResponse) { if (partialResponse) {
try { try {
// Insert a new assistant message with the partial content // Update the placeholder assistant message with the partial content and cancellation note
await db.insert(messages).values({ await db
chatId, .update(messages)
role: "assistant", .set({
content: `${partialResponse}\n\n[Response cancelled by user]`, content: `${partialResponse}
});
logger.log(`Saved partial response for chat ${chatId}`); [Response cancelled by user]`,
partialResponses.delete(chatId); })
.where(eq(messages.id, placeholderAssistantMessage.id));
logger.log(
`Updated cancelled response for placeholder message ${placeholderAssistantMessage.id} in chat ${chatId}`
);
partialResponses.delete(req.chatId);
} catch (error) { } catch (error) {
logger.error( logger.error(
`Error saving partial response for chat ${chatId}:`, `Error saving partial response for chat ${chatId}:`,
@@ -301,21 +327,17 @@ export function registerChatStreamHandlers() {
} }
const chatSummary = chatTitle?.[1]; const chatSummary = chatTitle?.[1];
// Create initial assistant message // Update the placeholder assistant message with the full response
const [assistantMessage] = await db await db
.insert(messages) .update(messages)
.values({ .set({ content: fullResponse })
chatId: req.chatId, .where(eq(messages.id, placeholderAssistantMessage.id));
role: "assistant",
content: fullResponse,
})
.returning();
if (readSettings().autoApproveChanges) { if (readSettings().autoApproveChanges) {
const status = await processFullResponseActions( const status = await processFullResponseActions(
fullResponse, fullResponse,
req.chatId, req.chatId,
{ chatSummary, messageId: assistantMessage.id } { chatSummary, messageId: placeholderAssistantMessage.id } // Use placeholder ID
); );
const chat = await db.query.chats.findFirst({ const chat = await db.query.chats.findFirst({