diff --git a/backend/app_mixins/chat.py b/backend/app_mixins/chat.py index 23fe801..51054c6 100644 --- a/backend/app_mixins/chat.py +++ b/backend/app_mixins/chat.py @@ -39,6 +39,7 @@ class ChatMixin: with self.sql_session() as session: session.execute(db.Membership.__table__.delete().where(db.Membership.chat_id == chat_id)) session.execute(db.Chat.__table__.delete().where(db.Chat.id == chat_id)) + # TODO: Delete messages from the chat session.commit() print(f"Deleted chat #{chat_id} and its memberships.") @@ -103,9 +104,8 @@ class ChatMixin: session.refresh(message) # Notify chat owner - if sender != 'user': - chat_owner = session.execute(select(db.Chat.user_id).where(db.Chat.id == chat_id)).scalar() - self.notify_message_to_user(chat_owner, message) + chat_owner = session.execute(select(db.Chat.user_id).where(db.Chat.id == chat_id)).scalar() + self.notify_message_to_user(chat_owner, message) # Notify participant agents agent_ids = session.execute(select(db.Membership.agent_id).where(db.Membership.chat_id == chat_id)).scalars().all() diff --git a/backend/database.py b/backend/database.py index a62fe0d..7bfb3a9 100644 --- a/backend/database.py +++ b/backend/database.py @@ -82,7 +82,7 @@ class Message(Base): # Identifier of the sender: user / system / user-$ID / agent-$ID sender: Mapped[str] - # Timestamp of this message + # Timestamp when this message was sent time: Mapped[int] # Timestamp of this message's last edit #last_edit: Mapped[int] diff --git a/frontend-vue/src/App.vue b/frontend-vue/src/App.vue index 753729d..4679cf8 100644 --- a/frontend-vue/src/App.vue +++ b/frontend-vue/src/App.vue @@ -1,9 +1,9 @@ + + + + diff --git a/frontend-vue/src/views/Chat.vue b/frontend-vue/src/views/Chat.vue index 77e05e2..d6fd74b 100644 --- a/frontend-vue/src/views/Chat.vue +++ b/frontend-vue/src/views/Chat.vue @@ -20,9 +20,10 @@ // Chat State const chat = ref({}); const messages = ref([]); + const unsentMessages = ref([]); const currentThreadId = ref(null); const chatEntries = computed(() => { - return computeChatEntries(messages.value); + return computeChatEntries(messages.value.concat(unsentMessages.value)); }); // UI State @@ -30,6 +31,20 @@ const editingMessageId = ref(null); const noteMode = ref(false); + const editInfo = computed(() => { + if (editingMessageId.value != null) { + if (noteMode.value) { + return `Editing Note ${editingMessageId.value}`; + } else { + return `Editing ${editingMessageId.value}`; + } + } else if (noteMode.value) { + return 'Editing Note'; + } else { + return null; + } + }); + let _isMounted = false; let unsubscribeChat = null; @@ -59,7 +74,19 @@ }); unsubscribeChat = backend.onChatMessages(chatId, it => { - if (it.sender == 'user') return; + if (it.sender == 'user') { + let unsent = unsentMessages.value.shift(); + if (unsent) { + if (unsent.content == it.content) { + unsent.id = it.id; + addMessage(unsent); + return; + } else { + // Remove from unsent list and add message as received + ui.alert("Unsent messages received differently"); + } + } + } addMessage({ id: it.id, @@ -133,10 +160,13 @@ const inputFieldElem = ref(null); const chatContainerElem = ref(null); - function addMessage (message) { + function addMessage (message, options={}) { requestRescroll(); - message.id = _messageNonce++; - messages.value.push(message); + if (options.unsent) { + unsentMessages.value.push(message); + } else { + messages.value.push(message); + } } async function sendMessage () { @@ -167,9 +197,9 @@ content, threadId, }; - addMessage(newMessage); + addMessage(newMessage, {unsent: true}); } else { - const messageRef = messages.firstWhere((element) => element.id == messageId); + const messageRef = messages.value.find((it) => it.id == messageId); backend.sendAction("update-chat-message", { messageId, content, @@ -270,9 +300,51 @@ } } + function editMessage (message) { + let elem = inputFieldElem.value; + if (message) { + editingMessageId.value = message.id; + elem.innerText = message.content; + } else { + editingMessageId.value = null; + elem.innerText = ""; + } + } + // === UI Helper Functions === // + async function chatEntryContextMenu (message) { + let selected = await ui.contextMenu([ + {value: 'pin', title: "Pin message"}, + {value: 'edit', title: "Edit message"}, + {value: 'start-thread', title: "Start thread from here"}, + {value: 'delete', icon: 'fa-trash', title: "Delete message"}, + ]); + + switch (selected) { + case 'pin': { + alert("Pin Message (TODO)"); + break; + } + case 'edit': { + editMessage(message); + break; + } + case 'start-thread': { + alert("Start Thread (TODO)"); + break; + } + case 'delete': { + if (confirm("Are you sure you want to delete this message?")) { + alert("Delete Message (TODO)"); + } + break; + } + + } + } + function requestRescroll ({forceBottom=false, bottomRelative=false}={}) { const elem = chatContainerElem.value; @@ -319,7 +391,7 @@

{{ chat.name || 'Chat #' + chat.id }}

-
+