diff --git a/backend/src/server/websocket.py b/backend/src/server/websocket.py index 3df1a43..6ace764 100644 --- a/backend/src/server/websocket.py +++ b/backend/src/server/websocket.py @@ -285,7 +285,7 @@ def register(server, app, auth_manager): elif action == "fork-chat-thread": thread_id = await app.fork_chat_thread( thread_id=body['threadId'], - message_id=body['messageId'], + message_id=body.get('messageId'), ) await send({'message': "chat thread created", 'threadId': thread_id, 'nonce': nonce}) diff --git a/frontend/src/chat.js b/frontend/src/chat.js index bc199af..02e1558 100644 --- a/frontend/src/chat.js +++ b/frontend/src/chat.js @@ -251,21 +251,36 @@ export class ChatSession { // Handles a chat message broadcast from the backend receiveMessage (it) { if (it.update) { - // Find message in list. If not found, add it - for (let thread of this.threads) { - for (let entry of thread.messages) { - if (entry.id == it.id) { - entry.type = it.kind; - entry.subkind = it.subkind; - entry.format = it.format; - entry.content = it.content; - entry.threadId = it.threadId; + // TODO: if threadId changed, the old message has to be searched - this.computeChatEntries(); - return; - } + let thread = this.getThread(it.threadId); + + for (let entry of thread.messages) { + if (entry.id == it.id) { + entry.type = it.kind; + entry.subkind = it.subkind; + entry.format = it.format; + entry.content = it.content; + + // If this changes, message has to get moved + entry.threadId = it.threadId; + + this.computeChatEntries(); + return; } } + + // Message not found in target thread + // Find the old message + ui.alert("TODO: find updated messages in different threads"); + //let stack = Array.from(this.threads); + let oldMessage = null; + + // if we didn't find it, add it like a new one + if (oldMessage) { + ui.alert("TODO: move a message with updated thread"); + return; + } } if (it.sender == 'user') { @@ -351,16 +366,34 @@ export class ChatSession { if (kind == null && this.noteMode.value) kind = 'note'; const messageId = this.editingMessageId.value; - const threadId = await this.resolveTargetThread(); - if (messageId == null || this.creatingThread.value) { - // New message in target thread (maybe fork) + if (messageId == null) { + let threadId = this.currentThreadId.value; + + // New message creating threads fork the current thread + if (this.creatingThread.value) { + let res; + if (threadId == null) { + res = await backend.sendAction("create-chat-thread", { + chatId: this.chatId, + }); + } else { + res = await backend.sendAction("fork-chat-thread", { + chatId: this.chatId, + threadId: threadId, + }); + } + + threadId = res.threadId; + } backend.sendAction("send-chat-message", { chatId: this.chatId, content, threadId, kind, subkind, - }).then(response => { - if (response && response.error) { - ui.alert("Error sending chat message: " + response.error); + }).then(res => { + if (res.error) { + ui.alert("Error sending chat message: " + res.error); + } else { + this.activateThread(threadId); } }); @@ -373,24 +406,90 @@ export class ChatSession { }, {unsent: true}); } else { - // Editing existing message + // Editing or forking a message - backend.sendAction("update-chat-message", { - messageId, content, kind, - }).then(response => { - if (response && response.error) { - ui.alert("Error updating message: " + response.error); + // Find the threads of the message and it's predecessor + let messageThreadId; + let previousThreadId; + + let thread = this.getThread(this.currentThreadId.value); + while (thread) { + let index = thread.messages.findIndex(it => it.id == messageId); + if (index >= 0) { + messageThreadId = thread.id; + if (index == 0) { + previousThreadId = thread.parentId; + } else { + previousThreadId = thread.id; + } + break; + } else if (thread.parentId) { + thread = this.getThread(thread.parentId); + } else { + ui.alert("Target message not found in active threads: " + messageId); + return; } - }); + } + + if (this.creatingThread.value) { + + // Send the target message to a fork + let res = await backend.sendAction("fork-chat-thread", { + chatId: this.chatId, + threadId: previousThreadId, + messageId, + }); + + // Then create a new fork again for the new message + res = await backend.sendAction("fork-chat-thread", { + chatId: this.chatId, + threadId: previousThreadId, + }); + + let newThreadId = res.threadId; + this.getThread(newThreadId).update({parentId: previousThreadId}); + + // Send in new thread + backend.sendAction("send-chat-message", { + chatId: this.chatId, + threadId: newThreadId, + content, kind, subkind, + }).then(res => { + if (res.error) { + ui.alert("Error sending chat message: " + res.error); + } else { + this.activateThread(newThreadId); + } + }); + + this.addMessage({ + type: kind || 'message', + subkind, + sender: 'user', + content, + threadId: newThreadId, + }, {unsent: true}); - // No update event received, local manual update - let thread = await this.getThread(threadId); - let existing = thread.messages.find(msg => msg.id == messageId); - if (existing) { - existing.content = content; - existing.type = kind || 'message'; } else { - console.error("Can't edit locally?", {messageId, threadId, t_m: thread.messages}); + + // Editing existing message + backend.sendAction("update-chat-message", { + messageId, content, kind, + }).then(response => { + if (response && response.error) { + ui.alert("Error updating message: " + response.error); + } + }); + + // No update event received, local manual update + let thread = await this.getThread(messageThreadId); + let existing = thread.messages.find(msg => msg.id == messageId); + if (existing) { + existing.content = content; + existing.type = kind || 'message'; + } else { + console.error("Can't edit locally?", {messageId, thread}); + } } this.editingMessageId.value = null; @@ -419,8 +518,9 @@ export class ChatSession { } // Begins or ends message editing; the view keeps its input field in sync - setEditing (messageId) { + setEditing (messageId, fork) { this.editingMessageId.value = messageId ?? null; + this.creatingThread.value = Boolean(fork); } // Adds a bulk of messages to the message list, in their correct