diff --git a/src/lib/common/DialogModal.svelte b/src/lib/common/DialogModal.svelte index 36debcfc..45915fd2 100644 --- a/src/lib/common/DialogModal.svelte +++ b/src/lib/common/DialogModal.svelte @@ -4,10 +4,13 @@ /** @type {boolean} */ export let isOpen; + /** @type {boolean} */ + export let closeable = false; + /** @type {string} */ export let size = 'xl'; - /** @type {string} */ + /** @type {string | any} */ export let title; /** @type {string} */ @@ -28,6 +31,9 @@ /** @type {() => void} */ export let cancel = () => {}; + /** @type {() => void} */ + export let close = () => {}; + /** @type {boolean} */ export let disableConfirmBtn = false; @@ -43,13 +49,44 @@ cancel && cancel(); } + /** @param {any} e */ + function handleClose(e) { + e.preventDefault(); + close && close(); + } - toggleModal()}> - {#if !!title} - {title} - {/if} + toggleModal()} + unmountOnClose +> + +
+ {#if !!title} +
+ +
{title}
+
+ {/if} + {#if closeable} +
+ + +
handleClose(e)} + > + +
+
+ {/if} +
+
diff --git a/src/lib/common/LiveChatEntry.svelte b/src/lib/common/LiveChatEntry.svelte index 976648ba..1c4ddf13 100644 --- a/src/lib/common/LiveChatEntry.svelte +++ b/src/lib/common/LiveChatEntry.svelte @@ -23,7 +23,7 @@ showChatBox = false; } else if (e.data.action == ChatAction.Open) { // showChatBox = true; - } else if (e.data.action == ChatAction.ReceiveMsg && !showChatBox) { + } else if (e.data.action == ChatAction.ReceiveNotification && !showChatBox) { receivedMsg = e.data?.data?.rich_content?.message?.text || e.data?.data?.text || ''; showBubbleMsg = true; wave(); diff --git a/src/lib/common/StateModal.svelte b/src/lib/common/StateModal.svelte index ec5addf6..0a6deb2e 100644 --- a/src/lib/common/StateModal.svelte +++ b/src/lib/common/StateModal.svelte @@ -158,7 +158,7 @@ } - toggleModal && toggleModal()}> + toggleModal && toggleModal()}> {title}
diff --git a/src/lib/helpers/enums.js b/src/lib/helpers/enums.js index b7d7fbbc..061cd3bd 100644 --- a/src/lib/helpers/enums.js +++ b/src/lib/helpers/enums.js @@ -38,7 +38,8 @@ const contentLogSource = { Prompt: "prompt", FunctionCall: "function call", AgentResponse: "agent response", - HardRule: "hard rule" + HardRule: "hard rule", + Notification: "notification" }; export const ContentLogSource = Object.freeze(contentLogSource); @@ -106,6 +107,6 @@ const chatAction = { Logout: 'logout', Chat: 'chat', NewChat: 'new-chat', - ReceiveMsg: 'receive-msg' + ReceiveNotification: 'receive-notification' }; export const ChatAction = Object.freeze(chatAction); \ No newline at end of file diff --git a/src/lib/scss/custom/components/_chat.scss b/src/lib/scss/custom/components/_chat.scss index 45c95d04..14fa8eaa 100644 --- a/src/lib/scss/custom/components/_chat.scss +++ b/src/lib/scss/custom/components/_chat.scss @@ -1,5 +1,22 @@ $bubble-chat-theme-color: rgba($primary, 80%); +.dialog-modal-container { + .modal-title { + width: 100%; + } + + .dialog-modal-header { + display: flex; + justify-content: space-between; + + .header-title { + display: flex; + gap: 8px; + } + } +} + + .chat-util-common { display: block; position: absolute; @@ -45,7 +62,12 @@ $bubble-chat-theme-color: rgba($primary, 80%); font-size: 30px; } - +.chat-notification { + min-height: 20px; + max-height: 150px; + overflow-y: auto; + scrollbar-width: none; +} .chat-bubble-container { display: flex; diff --git a/src/lib/services/api-endpoints.js b/src/lib/services/api-endpoints.js index 3f2733e0..6f11024e 100644 --- a/src/lib/services/api-endpoints.js +++ b/src/lib/services/api-endpoints.js @@ -39,6 +39,7 @@ export const endpoints = { // conversation conversationInitUrl: `${host}/conversation/{agentId}`, conversationMessageUrl: `${host}/conversation/{agentId}/{conversationId}`, + notificationUrl: `${host}/conversation/{conversationId}/notification`, conversationsUrl: `${host}/conversations`, conversationCountUrl: `${host}/conversations/count`, conversationDeletionUrl: `${host}/conversation/{conversationId}`, diff --git a/src/lib/services/conversation-service.js b/src/lib/services/conversation-service.js index 718e4abc..ba668f5c 100644 --- a/src/lib/services/conversation-service.js +++ b/src/lib/services/conversation-service.js @@ -106,6 +106,24 @@ export async function sendMessageToHub(agentId, conversationId, text, data = nul } +/** + * send a notification to conversation + * @param {string} conversationId - The conversation id + * @param {string} text - The text message sent to CSR + * @param {import('$conversationTypes').MessageData?} data - Additional data + */ +export async function sendNotification(conversationId, text, data = null) { + let url = replaceUrl(endpoints.notificationUrl, { + conversationId: conversationId + }); + const response = await axios.post(url, { + text: text, + states: [], + postback: data?.postback + }); + return response.data; +} + /** * @param {string} conversationId */ diff --git a/src/lib/services/signalr-service.js b/src/lib/services/signalr-service.js index 1d9a52fa..83bc2ad8 100644 --- a/src/lib/services/signalr-service.js +++ b/src/lib/services/signalr-service.js @@ -21,6 +21,9 @@ export const signalr = { /** @type {import('$conversationTypes').OnMessageReceived} */ onMessageReceivedFromAssistant: () => {}, + /** @type {import('$conversationTypes').OnMessageReceived} */ + onNotificationGenerated: () => {}, + /** @type {import('$conversationTypes').OnConversationContentLogReceived} */ onConversationContentLogGenerated: () => {}, @@ -93,6 +96,13 @@ export const signalr = { } }); + connection.on('OnNotificationGenerated', (json) => { + const message = JSON.parse(json); + if (conversationId === message?.conversation_id) { + this.onNotificationGenerated(message); + } + }); + connection.on('OnConversationContentLogGenerated', (log) => { const jsonLog = JSON.parse(log); if (conversationId === jsonLog?.conversation_id) { diff --git a/src/routes/chat/[agentId]/[conversationId]/chat-box.svelte b/src/routes/chat/[agentId]/[conversationId]/chat-box.svelte index 2d14c30c..6fc77820 100644 --- a/src/routes/chat/[agentId]/[conversationId]/chat-box.svelte +++ b/src/routes/chat/[agentId]/[conversationId]/chat-box.svelte @@ -92,6 +92,7 @@ let truncateMsgId = ""; let indication = ""; let mode = ''; + let notificationText = ''; /** @type {number} */ let messageInputTimeout; @@ -158,6 +159,7 @@ let disableSpeech = false; let isLoading = false; let isCreatingNewConv = false; + let isDisplayNotification = false; $: { const editor = lastBotMsg?.rich_content?.editor || ''; @@ -184,6 +186,7 @@ signalr.onMessageReceivedFromClient = onMessageReceivedFromClient; signalr.onMessageReceivedFromCsr = onMessageReceivedFromCsr; signalr.onMessageReceivedFromAssistant = onMessageReceivedFromAssistant; + signalr.onNotificationGenerated = onNotificationGenerated; signalr.onConversationContentLogGenerated = onConversationContentLogGenerated; signalr.onConversationStateLogGenerated = onConversationStateLogGenerated; signalr.onStateChangeGenerated = onStateChangeGenerated; @@ -255,9 +258,9 @@ } /** @param {import('$conversationTypes').ChatResponseModel} message */ - function sendReceivedMessage(message) { + function sendReceivedNotification(message) { if (isFrame) { - window.parent.postMessage({ action: ChatAction.ReceiveMsg, data: message }, "*"); + window.parent.postMessage({ action: ChatAction.ReceiveNotification, data: message }, "*"); } } @@ -431,10 +434,21 @@ ...message, is_chat_message: true }); - sendReceivedMessage(message); refresh(); } + /** @param {import('$conversationTypes').ChatResponseModel} message */ + function onNotificationGenerated(message) { + notificationText = message?.rich_content?.message?.text || message.text || ''; + isDisplayNotification = true; + setTimeout(() => { + isDisplayNotification = false; + notificationText = ''; + }, notificationText?.length > 200 ? 8000 : 3000); + + sendReceivedNotification(message); + } + /** @param {import('$conversationTypes').ConversationContentLogModel} log */ function onConversationContentLogGenerated(log) { if (!isLoadPersistLog) return; @@ -1094,6 +1108,13 @@ }; sendChatMessage(text, data); } + + function toggleNotificationModal() { + isDisplayNotification = !isDisplayNotification; + if (!isDisplayNotification) { + notificationText = ''; + } + } @@ -1103,6 +1124,25 @@ {/if} + toggleNotificationModal()} + confirmBtnText={''} + cancelBtnText={''} + close={() => toggleNotificationModal()} +> +
+ +
+
+ {notificationText} +
+
+ + import { onMount } from 'svelte'; - import { _ } from 'svelte-i18n' + import { _ } from 'svelte-i18n' + import util from "lodash"; import { Card, CardBody, CardTitle, Col, Row } from '@sveltestrap/sveltestrap'; - import Link from 'svelte-link/src/Link.svelte'; - import { GetDialogs, getConversationFiles } from '$lib/services/conversation-service.js'; + import { GetDialogs, getConversationFiles, sendNotification } from '$lib/services/conversation-service.js'; import { utcToLocal } from '$lib/helpers/datetime'; import { USER_SENDERS } from '$lib/helpers/constants'; import MessageFileGallery from '$lib/common/MessageFileGallery.svelte'; import { FileSourceType } from '$lib/helpers/enums'; - import ConvDialogElement from './conv-dialog-element.svelte'; + import DialogModal from '$lib/common/DialogModal.svelte'; + import LoadingToComplete from '$lib/common/LoadingToComplete.svelte'; + import ConvDialogElement from './conv-dialog-element.svelte'; + + const maxTextLength = 4096; + const duration = 1500; /** @type {import('$conversationTypes').ChatResponseModel[]} */ let dialogs = []; + /** @type {boolean} */ + let isOpenNotificationModal = false; + let isComplete = false; + let isError = false; + + /** @type {string} */ + let text = ''; + /** @type {import('$conversationTypes').ConversationModel} */ export let conversation; @@ -28,21 +41,90 @@ return USER_SENDERS.includes(dialog?.sender?.role || ''); } - function directToChat() { + function goToChat() { window.open(`/chat/${conversation.agent_id}/${conversation.id}`); } + + function handleSendNotification() { + isOpenNotificationModal = true; + } + + function toggleNotificationModal() { + isOpenNotificationModal = !isOpenNotificationModal; + if (!isOpenNotificationModal) { + text = ''; + } + } + + function confirmMsg() { + sendNotification(conversation.id, text).then(() => { + isComplete = true; + setTimeout(() => { + isComplete = false; + }, duration); + }).catch(() => { + isError = true; + setTimeout(() => { + isError = false; + }, duration); + }).finally(() => { + isOpenNotificationModal = false; + text = ''; + }); + } + + + toggleNotificationModal()} + confirm={() => confirmMsg()} + cancel={() => toggleNotificationModal()} + confirmBtnText={'Send'} + disableConfirmBtn={!!!util.trim(text)} +> +