From f89c6ab03d03c06aa55a2bf2d200008446c6bc0c Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Fri, 11 Oct 2024 15:06:45 -0500 Subject: [PATCH] add update message --- .../common/audio-player/AudioSpeaker.svelte | 7 +- src/lib/helpers/http.js | 1 + src/lib/helpers/types/conversationTypes.js | 12 ++ src/lib/scss/custom/components/_alert.scss | 2 + src/lib/services/api-endpoints.js | 1 + src/lib/services/conversation-service.js | 25 +++ .../[conversationId]/chat-box.svelte | 160 ++++++++++++++++-- 7 files changed, 191 insertions(+), 17 deletions(-) diff --git a/src/lib/common/audio-player/AudioSpeaker.svelte b/src/lib/common/audio-player/AudioSpeaker.svelte index 217b436e..4ef689af 100644 --- a/src/lib/common/audio-player/AudioSpeaker.svelte +++ b/src/lib/common/audio-player/AudioSpeaker.svelte @@ -89,7 +89,12 @@ >
speak()}> {#if !speaking} - + {:else} {/if} diff --git a/src/lib/helpers/http.js b/src/lib/helpers/http.js index 3055fe62..0caaa1bb 100644 --- a/src/lib/helpers/http.js +++ b/src/lib/helpers/http.js @@ -65,6 +65,7 @@ function skipLoader(config) { const putRegexes = [ new RegExp('http(s*)://(.*?)/knowledge/vector/(.*?)/update', 'g'), + new RegExp('http(s*)://(.*?)/conversation/(.*?)/update-message', 'g'), ]; const deleteRegexes = [ diff --git a/src/lib/helpers/types/conversationTypes.js b/src/lib/helpers/types/conversationTypes.js index 9a34320a..2713221f 100644 --- a/src/lib/helpers/types/conversationTypes.js +++ b/src/lib/helpers/types/conversationTypes.js @@ -128,6 +128,7 @@ IRichContent.prototype.quick_replies; /** * @typedef {Object} ChatResponseModel + * @property {string} uuid - The uuid. * @property {string} conversation_id - The conversation id. * @property {import('$userTypes').UserModel} sender - The message sender. * @property {string} message_id - The message id. @@ -246,6 +247,17 @@ IRichContent.prototype.quick_replies; * @property {string?} [payload] - The payload message. */ +/** + * @typedef {Object} EditBotMessageModel + * @property {ChatResponseModel} message + * @property {string} source + */ + +/** + * @typedef {Object} UpdateBotMessageRequest + * @property {ChatResponseModel} message + * @property {number} innerIndex + */ diff --git a/src/lib/scss/custom/components/_alert.scss b/src/lib/scss/custom/components/_alert.scss index 0c817771..6012c19a 100644 --- a/src/lib/scss/custom/components/_alert.scss +++ b/src/lib/scss/custom/components/_alert.scss @@ -4,9 +4,11 @@ left: 35%; right: 35%; z-index: 8888; + font-size: 0.9em; .alert { text-align: center; + padding: 0.8em; } .success { diff --git a/src/lib/services/api-endpoints.js b/src/lib/services/api-endpoints.js index 6f11024e..1e92c01d 100644 --- a/src/lib/services/api-endpoints.js +++ b/src/lib/services/api-endpoints.js @@ -48,6 +48,7 @@ export const endpoints = { conversationUserUrl: `${host}/conversation/{conversationId}/user`, dialogsUrl: `${host}/conversation/{conversationId}/dialogs`, conversationMessageDeletionUrl: `${host}/conversation/{conversationId}/message/{messageId}`, + conversationMessageUpdateUrl: `${host}/conversation/{conversationId}/update-message`, fileUploadUrl: `${host}/agent/{agentId}/conversation/{conversationId}/upload`, // LLM provider diff --git a/src/lib/services/conversation-service.js b/src/lib/services/conversation-service.js index ba668f5c..be79e3a4 100644 --- a/src/lib/services/conversation-service.js +++ b/src/lib/services/conversation-service.js @@ -170,6 +170,31 @@ export async function deleteConversationMessage(conversationId, messageId, isNew }); } +/** + * delete a message in conversation + * @param {string} conversationId The conversation id + * @param {import('$conversationTypes').UpdateBotMessageRequest} request + * @returns {Promise} + */ +export async function updateConversationMessage(conversationId, request) { + let url = replaceUrl(endpoints.conversationMessageUpdateUrl, { + conversationId: conversationId + }); + + const data = { + message: request.message, + inner_index: request.innerIndex + }; + + return new Promise((resolve, reject) => { + axios.put(url, {...data}).then(response => { + resolve(response.data); + }).catch(err => { + reject(err) + }); + }); +} + /** * upload conversation files diff --git a/src/routes/chat/[agentId]/[conversationId]/chat-box.svelte b/src/routes/chat/[agentId]/[conversationId]/chat-box.svelte index 1749d4bf..a9c928c3 100644 --- a/src/routes/chat/[agentId]/[conversationId]/chat-box.svelte +++ b/src/routes/chat/[agentId]/[conversationId]/chat-box.svelte @@ -8,6 +8,7 @@ import { OverlayScrollbars } from 'overlayscrollbars'; import _ from "lodash"; import moment from 'moment'; + import { v4 as uuidv4 } from 'uuid'; import { Dropdown, DropdownToggle, @@ -25,10 +26,11 @@ sendMessageToHub, GetDialogs, deleteConversationMessage, + updateConversationMessage, getConversationFiles, getConversationUser, uploadConversationFiles, - getAddressOptions + getAddressOptions, } from '$lib/services/conversation-service.js'; import { PUBLIC_LIVECHAT_ENTRY_ICON, @@ -43,6 +45,7 @@ import HeadTitle from '$lib/common/HeadTitle.svelte'; import LoadingDots from '$lib/common/LoadingDots.svelte'; import StateModal from '$lib/common/StateModal.svelte'; + import LoadingToComplete from '$lib/common/LoadingToComplete.svelte'; import ChatTextArea from './chat-util/chat-text-area.svelte'; import AudioSpeaker from '$lib/common/audio-player/AudioSpeaker.svelte'; import { utcToLocal } from '$lib/helpers/datetime'; @@ -59,7 +62,7 @@ import ChatBigMessage from './chat-util/chat-big-message.svelte'; import PersistLog from './persist-log/persist-log.svelte'; import InstantLog from './instant-log/instant-log.svelte'; - import Loader from '$lib/common/Loader.svelte'; + const options = { @@ -78,6 +81,7 @@ const messageLimit = 100; const screenWidthThreshold = 1024; const maxTextLength = 64000; + const duration = 2000; /** @type {import('$agentTypes').AgentModel} */ export let agent; @@ -86,13 +90,16 @@ export let currentUser; /** @type {string} */ - let text = ""; - let editText = ""; - let bigText = ""; - let truncateMsgId = ""; - let indication = ""; + let text = ''; + let editText = ''; + let bigText = ''; + let botText = ''; + let truncateMsgId = ''; + let indication = ''; let mode = ''; let notificationText = ''; + let successText = "Done"; + let errorText = "Error"; /** @type {number} */ let messageInputTimeout; @@ -107,6 +114,9 @@ let scrollbars = []; let microphoneIcon = "microphone-off"; + /** @type {import('$conversationTypes').EditBotMessageModel?} */ + let editBotMsg; + /** @type {import('$conversationTypes').ChatResponseModel?} */ let lastBotMsg; @@ -149,6 +159,7 @@ let isInstantLogClosed = false; // initial condition let isOpenEditMsgModal = false; let isOpenBigMsgModal = false; + let isOpenEditBotMsgModal = false; let isOpenUserAddStateModal = false; let isSendingMsg = false; let isThinking = false; @@ -163,6 +174,8 @@ let isLoading = false; let isCreatingNewConv = false; let isDisplayNotification = false; + let isComplete = false; + let isError = false; $: { const editor = lastBotMsg?.rich_content?.editor || ''; @@ -358,7 +371,7 @@ async function refresh() { // trigger UI render - dialogs = dialogs?.map(item => { return { ...item }; }) || []; + dialogs = dialogs?.map(item => { return { ...item, uuid: uuidv4() }; }) || []; lastBotMsg = null; await tick(); lastBotMsg = findLastBotMessage(dialogs); @@ -1141,14 +1154,100 @@ notificationText = ''; } } + + + /** @param {import('$conversationTypes').ChatResponseModel} message */ + function openEditBotMsgModal(message) { + isOpenEditBotMsgModal = true; + let source = "text"; + if (message.rich_content?.message?.text === message.text) { + source = "both"; + } else if (message.rich_content?.message?.text) { + source = "rich-content-text"; + } + editBotMsg = { + message: message, + source: source + }; + botText = message?.rich_content?.message?.text || message?.text; + } + + function toggleEditBotMsgModal() { + isOpenEditBotMsgModal = !isOpenEditBotMsgModal; + if (!isOpenEditBotMsgModal) { + editBotMsg = null; + botText = ''; + } + } + + function saveBotMsg() { + if (!editBotMsg) return; + + const found = dialogs.find(x => x.uuid === editBotMsg?.message.uuid); + if (!found) return; + + const candidates = dialogs.filter(x => x.message_id === editBotMsg?.message.message_id && x.sender?.role === editBotMsg?.message.sender?.role); + const innerIdx = candidates.findIndex(x => x.uuid === editBotMsg?.message.uuid); + + /** @type {import('$conversationTypes').UpdateBotMessageRequest} */ + const request = { + message: editBotMsg.message, + innerIndex: innerIdx + }; + + if (editBotMsg.source === "both") { + found.text = botText; + found.rich_content.message.text = botText; + editBotMsg.message.text = botText; + editBotMsg.message.rich_content.message.text = botText; + } else if (editBotMsg?.source === "rich-content-text") { + found.rich_content.message.text = botText; + editBotMsg.message.rich_content.message.text = botText; + } else { + found.text = botText; + editBotMsg.message.text = botText; + } + + isLoading = true; + updateConversationMessage(params.conversationId, request).then(res => { + if (res) { + isComplete = true; + successText = "Message has been updated!"; + setTimeout(() => { + isComplete = false; + successText = ""; + }, duration); + + toggleEditBotMsgModal(); + refresh(); + } else { + throw "failed to update message"; + } + }).catch(err => { + isError = true; + errorText = "Failed to update message!"; + setTimeout(() => { + isError = false; + errorText = ""; + }, duration); + toggleEditBotMsgModal(); + }).finally(() => { + isLoading = false; + }); + } resizeChatWindow()}/> -{#if isLoading} - -{/if} + toggleEditMsgModal()} @@ -1199,6 +1298,21 @@
+ toggleEditBotMsgModal()} + confirm={() => saveBotMsg()} + cancel={() => toggleEditBotMsgModal()} + disableConfirmBtn={!!!_.trim(botText)} +> +