From bc378ce0dd2338e458d486bc6a29a3d2655cdf80 Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Tue, 12 Dec 2023 10:28:05 -0500 Subject: [PATCH] Edit button, with save and cancel. modified: app.py modified: templates/chat.html --- app.py | 27 ++++++++ templates/chat.html | 155 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 175 insertions(+), 7 deletions(-) diff --git a/app.py b/app.py index 3fa98a8..b1a68fa 100644 --- a/app.py +++ b/app.py @@ -272,6 +272,33 @@ def handle_delete_message(data): emit("message_deleted", {"message_id": msg_id}, room=data["room_name"]) +@socketio.on("update_message") +def handle_update_message(data): + message_id = data["message_id"] + new_content = data["content"] + room_name = data["room_name"] + + # Find the message by ID + message = Message.query.get(message_id) + if message: + # Update the message content + message.content = new_content + message.count_tokens() + db.session.add(message) + db.session.commit() + + # Emit an event to update the message on all clients + emit( + "message_updated", + { + "message_id": message_id, + "content": new_content, + "username": message.username + }, + room=room_name + ) + + def chat_claude(username, room_name, message, model_name="anthropic.claude-v1"): with app.app_context(): room = get_room(room_name) diff --git a/templates/chat.html b/templates/chat.html index d999843..269c58a 100644 --- a/templates/chat.html +++ b/templates/chat.html @@ -62,11 +62,17 @@ } /* Styling for the message input area */ - #message { + #message, .message-edit { width: 100%; border: 1px solid #e1e1e1; border-radius: 5px; padding: 5px; + display: block; + } + + /* Styling for the message div holding html/markdown content */ + .message-content { + width: 100%; } /* Styling for individual message wrappers */ @@ -256,7 +262,9 @@ socket.on("message", (data) => { messageWrapper.className = "message-wrapper"; messageWrapper.id = "message-" + data.id; - const newMessage = document.createElement("p"); + const newMessage = document.createElement("div"); + newMessage.className = "message-content"; + // Check if the username is present and prepend it to the message content const messageContent = data.username ? `**${data.username}:**\n\n${data.content}` : data.content; @@ -276,6 +284,13 @@ socket.on("message", (data) => { deleteButton.innerHTML = "x"; deleteButton.onclick = () => deleteMessage(data.id, room_name); messageWrapper.appendChild(deleteButton); + + // Create the edit button + const editButton = document.createElement("button"); + editButton.textContent = "Edit"; + editButton.className = "edit-button"; + editButton.onclick = () => editMessage(data.id, newMessage, data.content); + messageWrapper.appendChild(editButton); } messageWrapper.appendChild(newMessage); @@ -300,7 +315,8 @@ socket.on("previous_messages", (data) => { messageWrapper.className = "message-wrapper"; messageWrapper.id = "message-" + data.id; - const newMessage = document.createElement("p"); + const newMessage = document.createElement("div"); + newMessage.className = "message-content"; // Check if the message contains a base64 image if (data.message.startsWith(' { const deleteButton = document.createElement("button"); deleteButton.innerHTML = "x"; deleteButton.onclick = () => deleteMessage(data.id, room_name); - messageWrapper.appendChild(deleteButton); + + + // Create the edit button + const editButton = document.createElement("button"); + editButton.textContent = "Edit"; + editButton.className = "edit-button"; + editButton.onclick = () => editMessage(data.id, newMessage, data.message); + messageWrapper.appendChild(editButton); + messageWrapper.appendChild(newMessage); document.getElementById("chat").appendChild(messageWrapper); @@ -356,18 +380,28 @@ socket.on("message_chunk", (data) => { messageWrapper.id = wrapperId; document.getElementById("chat").appendChild(messageWrapper); + // Create the div element to hold the message content + const targetMessageElement = document.createElement("div"); + targetMessageElement.className = "message-content"; + // Create the "x" button for deletion const deleteButton = document.createElement("button"); deleteButton.innerHTML = "x"; deleteButton.onclick = () => deleteMessage(data.id, room_name); messageWrapper.appendChild(deleteButton); - // Create the p element to hold the message content - targetMessageElement = document.createElement("p"); + // Create the edit button + const editButton = document.createElement("button"); + editButton.textContent = "Edit"; + editButton.className = "edit-button"; + editButton.onclick = () => editMessage(data.id, targetMessageElement, messageBuffers[data.id]); + messageWrapper.appendChild(editButton); + messageWrapper.appendChild(targetMessageElement); + } else { // If the wrapper already exists, get the p element inside it - targetMessageElement = messageWrapper.querySelector("p"); + targetMessageElement = messageWrapper.querySelector("div.message-content"); } // If the message buffer for this ID doesn't exist, create it @@ -401,6 +435,113 @@ socket.on("message_deleted", (data) => { }); +// Socket event for when a message is updated +socket.on("message_updated", (data) => { + // Find the existing message wrapper by ID + const messageWrapper = document.getElementById("message-" + data.message_id); + + if (messageWrapper) { + // Find the specific element that contains the message content + const messageContentContainer = messageWrapper.querySelector(".message-content"); + + // Update the message content + if (data.content.startsWith(' { + addCopyButtonToCodeBlock(block); + truncateCodeBlock(block); + hljs.highlightElement(block); + addLineNumbers(block); + }); + } +}); + + +// Function to enter edit mode +function editMessage(messageId, messageContentContainer, rawMarkdown) { + // Store the current HTML in a data attribute + messageContentContainer.dataset.originalHtml = messageContentContainer.innerHTML; + messageContentContainer.dataset.rawMarkdown = rawMarkdown; + + // Create a textarea for editing + const textarea = document.createElement("textarea"); + textarea.value = rawMarkdown; + textarea.rows = 16; + textarea.className = "message-edit"; + + // Replace the message content with the textarea + messageContentContainer.innerHTML = ''; + messageContentContainer.appendChild(textarea); + + // Find the message wrapper to access the edit and save buttons + const messageWrapper = messageContentContainer.closest('.message-wrapper'); + + // Create a save button with the 'save-button' class + const saveButton = document.createElement("button"); + saveButton.textContent = "Save"; + saveButton.className = "save-button"; // Add the class here + saveButton.onclick = () => saveEditedMessage(messageId, textarea, messageContentContainer); + + // Change the edit button to a cancel button + const editButton = messageWrapper.querySelector(".edit-button"); + editButton.textContent = "Cancel"; + editButton.onclick = () => cancelEdit(messageId, messageContentContainer); + + // Append the save button next to the cancel button + editButton.after(saveButton); +} + +// Function to save the edited message +function saveEditedMessage(messageId, textarea, messageContentContainer) { + // Get the updated markdown from the textarea + const updatedMarkdown = textarea.value; + + // Emit the update_message event to the server + socket.emit("update_message", { + "message_id": messageId, + "content": updatedMarkdown, + "room_name": room_name + }); + + // Reset the edit button to its original state + const messageWrapper = messageContentContainer.closest('.message-wrapper'); + const editButton = messageWrapper.querySelector(".edit-button"); + editButton.textContent = 'Edit'; + editButton.onclick = () => editMessage(messageId, messageContentContainer, updatedMarkdown); + + // Remove the save button using the 'save-button' class + const saveButton = messageWrapper.querySelector(".save-button"); + if (saveButton) { + saveButton.remove(); + } +} + +// Function to cancel the edit and revert changes +function cancelEdit(messageId, messageContentContainer) { + // Restore the original HTML of the message content from the data attribute + messageContentContainer.innerHTML = messageContentContainer.dataset.originalHtml; + + // Reset the edit button to its original state + const messageWrapper = messageContentContainer.closest('.message-wrapper'); + const editButton = messageWrapper.querySelector(".edit-button"); + editButton.textContent = 'Edit'; + editButton.onclick = () => editMessage(messageId, messageContentContainer, messageContentContainer.dataset.rawMarkdown); + + // Remove the save button using the 'save-button' class + const saveButton = messageWrapper.querySelector(".save-button"); + if (saveButton) { + saveButton.remove(); + } +} + + function truncateCodeBlock(block, maxLines = 100) { // Split the content by new lines and check if it exceeds the maxLines const lines = block.textContent.split('\n');