Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions src/lib/common/BubbleChat.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<script>
/** @type {string} */
export let text;

/** @type {() => void} */
export let close = () => {};

/** @type {string} */
export let containerClasses = "";

/** @type {string} */
export let containerStyles = "";

/** @type {boolean} */
export let disableDefaultStyles = false;


/** @param {any} e */
function handleClose(e) {
e.preventDefault();
close?.();
}
</script>

<div
class="{disableDefaultStyles ? '' : 'chat-bubble-container'} {containerClasses}"
style={`${containerStyles}`}
>
<div class="chat-bubble">
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="bubble-delete clickable"
on:click={e => handleClose(e)}
>
<i class="mdi mdi-close" />
</div>
<div class="bubble-text">
{text}
</div>
</div>
</div>
60 changes: 51 additions & 9 deletions src/lib/common/LiveChatEntry.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
import { onMount } from 'svelte';
import { PUBLIC_LIVECHAT_HOST, PUBLIC_LIVECHAT_ENTRY_ICON } from '$env/static/public';
import { getSettingDetail } from '$lib/services/setting-service';
import { chatBotStore } from '$lib/helpers/store';
import { CHAT_FRAME_ID } from '$lib/helpers/constants';
import { ChatAction } from '$lib/helpers/enums';
import BubbleChat from './BubbleChat.svelte';

let chatUrl = PUBLIC_LIVECHAT_HOST;
let showChatBox = false;
let showBubbleMsg = false;
let receivedMsg = '';

onMount(async () => {
const agentSettings = await getSettingDetail("Agent");
Expand All @@ -17,33 +20,61 @@
// Handle event from iframe
window.onmessage = async function(e) {
if (e.data.action == ChatAction.Close) {
chatBotStore.set({ showChatBox: false });
showChatBox = false;
} else if (e.data.action == ChatAction.Open) {
chatBotStore.set({ showChatBox: true });
// showChatBox = true;
} else if (e.data.action == ChatAction.ReceiveMsg && !showChatBox) {
receivedMsg = e.data?.data?.rich_content?.message?.text || e.data?.data?.text || '';
showBubbleMsg = true;
wave();
}
};

function openChatBox() {
chatBotStore.set({ showChatBox: true });
showChatBox = true;
receivedMsg = '';
showBubbleMsg = false;
}

function closeBubbleMsg() {
receivedMsg = '';
showBubbleMsg = false;
}

function wave() {
const elem = document.getElementById('chatbot-icon');
if (elem) {
elem.classList.add('waving');
setTimeout(() => {
elem.classList.remove('waving');
}, 800);
}
}
</script>

<div class="chatbot-container fixed-bottom float-bottom-right">
{#if showBubbleMsg}
<div transition:fade={{ delay: 50, duration: 200 }}>
<BubbleChat text={receivedMsg} close={() => closeBubbleMsg()} />
</div>
{/if}

<iframe
src={chatUrl}
width={`${$chatBotStore.showChatBox ? '380px' : '0px'}`}
height={`${$chatBotStore.showChatBox ? '650px' : '0px'}`}
class={`border border-2 rounded-3 m-3 float-end ${$chatBotStore.showChatBox ? 'chat-iframe' : ''}`}
width={'380px'}
height={'650px'}
class={`border border-2 rounded-3 m-3 float-end ${showChatBox ? 'chat-iframe' : 'hide'}`}
title="live chat"
id={CHAT_FRAME_ID}
/>

{#if !$chatBotStore.showChatBox}
{#if !showChatBox}
<div
id="chatbot-icon"
class="chatbot-icon mb-3 float-end wave-effect"
transition:fade={{ delay: 50, duration: 200 }}
>
<button class="btn btn-transparent" on:click={() => openChatBox()}>
<button class="btn btn-transparent chat-icon-btn" on:click={() => openChatBox()}>
<img alt="live chat" class="avatar-md rounded-circle" src={PUBLIC_LIVECHAT_ENTRY_ICON} />
</button>
</div>
Expand All @@ -58,6 +89,13 @@
perspective: 1000px;
}

.waving {
animation: wave 0.82s cubic-bezier(.36,.07,.19,.97) both;
transform: translate3d(0, 0, 0);
backface-visibility: hidden;
perspective: 1000px;
}

@keyframes wave {
10%, 90% {
transform: translate3d(-1px, 0, 0);
Expand Down Expand Up @@ -91,4 +129,8 @@
display: flex;
justify-content: flex-end;
}

.chat-icon-btn {
padding-top: 0px;
}
</style>
3 changes: 2 additions & 1 deletion src/lib/helpers/enums.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ const chatAction = {
Close: 'close',
Logout: 'logout',
Chat: 'chat',
NewChat: 'new-chat'
NewChat: 'new-chat',
ReceiveMsg: 'receive-msg'
};
export const ChatAction = Object.freeze(chatAction);
65 changes: 27 additions & 38 deletions src/lib/helpers/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,33 +35,35 @@ userStore.subscribe(value => {
});


/** @type {Writable<import('$conversationTypes').ConversationModel>}*/
export const conversationStore = writable({});

/**
* @returns {Writable<import('$conversationTypes').ConversationModel>}
*/
export function getConversationStore() {
if (browser) {
// Access localStorage only if in the browser context
const json = localStorage.getItem(conversationKey);
if (json)
return JSON.parse(json);
else
return conversationStore;
} else {
// Return a default value for SSR
return conversationStore;
}
const createConversationStore = () => {
const { subscribe } = writable({});
return {
clear: (/** @type {string | null} */ convId = null) => {
if (!convId) {
localStorage.removeItem(conversationKey);
return;
}

const json = localStorage.getItem(conversationKey);
if (json) {
const conv = JSON.parse(json);
if (conv.id === convId) {
localStorage.removeItem(conversationKey);
}
}
},
get: () => {
const json = localStorage.getItem(conversationKey);
return json ? JSON.parse(json) : {};
},
put: (value) => {
localStorage.setItem(conversationKey, JSON.stringify(value));
},
subscribe
};
};

// @ts-ignore
conversationStore.subscribe(value => {
if (browser && value.id) {
localStorage.setItem(conversationKey, JSON.stringify(value));
}
});

export const conversationStore = createConversationStore();


const createLoaderStore = () => {
Expand Down Expand Up @@ -182,19 +184,6 @@ const createKnowledgeBaseDocumentStore = () => {
export const knowledgeBaseDocumentStore = createKnowledgeBaseDocumentStore();


const createChatBotStore = () => {
const { subscribe, set, update } = writable({ showChatBox: false });

return {
set,
update,
subscribe
}
};

export const chatBotStore = createChatBotStore();


export function resetLocalStorage(resetUser = false) {
conversationUserStateStore.resetAll();
conversationSearchOptionStore.reset();
Expand Down
48 changes: 47 additions & 1 deletion src/lib/scss/custom/components/_chat.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
$bubble-chat-theme-color: rgba($primary, 80%);

.chat-util-common {
display: block;
position: absolute;
Expand Down Expand Up @@ -41,4 +43,48 @@
.chat-util-item {
text-align: center;
font-size: 30px;
}
}



.chat-bubble-container {
display: flex;
flex-direction: column;
gap: 0px;

.chat-bubble {
margin: 5px 30px 0px 5px;
display: inline-block;
position: relative;
min-width: 200px;
max-width: 300px;
background-color: $bubble-chat-theme-color;
border-radius: .4em;
color: white;

&:after {
content: ' ';
position: absolute;
top: 100%;
right: 50px;
width: 25px;
height: 20px;
clip-path: polygon(0 0, 100% 0, 100% 100%);
background-color: $bubble-chat-theme-color;
}

.bubble-text {
margin: 15px;
height: fit-content;
max-height: 100px;
overflow-y: auto;
scrollbar-width: none;
}

.bubble-delete {
position: absolute;
right: 5px;
top: 3px;
}
}
}
6 changes: 3 additions & 3 deletions src/routes/chat/[agentId]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import { newConversation } from '$lib/services/conversation-service.js';
import { getToken, setToken } from '$lib/services/auth-service.js'
import { getUserStore } from '$lib/helpers/store.js';
import { conversationStore, getConversationStore } from '$lib/helpers/store.js';
import { conversationStore } from '$lib/helpers/store.js';
import { LERNER_ID, TRAINING_MODE } from '$lib/helpers/constants';

const params = $page.params;
Expand All @@ -32,11 +32,11 @@
});
}

conversation = getConversationStore();
conversation = conversationStore.get();
if (!conversation.id || agentId != conversation.agent_id) {
// new conversation
conversation = await newConversation(agentId);
conversationStore.set(conversation);
conversationStore.put(conversation);
}

conversationId = conversation.id;
Expand Down
Loading
Loading