|
<script>
|
|
import { onMount } from "svelte";
|
|
import { writable } from "svelte/store";
|
|
import { chatEndPoint } from "../../constants/constants";
|
|
import { createWebSocket } from "../../utils/websocketFormatter";
|
|
import { v4 as uuidv4 } from "uuid";
|
|
import SvelteMarkdown from "svelte-markdown";
|
|
import { ICONS } from "$constants/imageUrls.js";
|
|
|
|
|
|
let sendMessage, onMessage, formatStream, isStreaming, refresh;
|
|
|
|
let messages = writable([]);
|
|
let message = "";
|
|
let running = false;
|
|
let streaming = false;
|
|
let instructionMessages = [
|
|
`“Can you provide advice on refinancing my current mortgage to save on interest?”`,
|
|
`“What strategies can I employ to pay off my mortgage faster?”`,
|
|
`“Can you guide me through different types of mortgage plans available and which one would suit my needs best?”`,
|
|
];
|
|
export let isCollapsed = true;
|
|
export let searchData;
|
|
|
|
|
|
$: {
|
|
if (searchData) {
|
|
messages.update((msgs) => [
|
|
...msgs,
|
|
{ id: uuidv4(), content: searchData, type: "sent" },
|
|
]);
|
|
running = true;
|
|
sendMessage(
|
|
JSON.stringify({
|
|
user_input: searchData,
|
|
})
|
|
);
|
|
scrollToBottom();
|
|
}
|
|
}
|
|
|
|
|
|
const send = () => {
|
|
if (message === "") return;
|
|
running = true;
|
|
sendMessage(
|
|
JSON.stringify({
|
|
user_input: message,
|
|
})
|
|
);
|
|
addMsg(message);
|
|
|
|
};
|
|
|
|
|
|
const addMsg = (msg) => {
|
|
messages.update((msgs) => [
|
|
...msgs,
|
|
{ id: uuidv4(), content: msg, type: "sent" },
|
|
]);
|
|
message = "";
|
|
scrollToBottom();
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const scrollToBottom = () => {
|
|
const messageBox = document.getElementById("message-box");
|
|
messageBox.scrollTop = messageBox.scrollHeight;
|
|
};
|
|
|
|
const handleEnter = (event) => {
|
|
if (event.key === "Enter") {
|
|
event.preventDefault();
|
|
send();
|
|
}
|
|
};
|
|
|
|
onMount(() => {
|
|
document.getElementById("message").addEventListener("keyup", handleEnter);
|
|
});
|
|
|
|
let chatbotCollapsed = true;
|
|
|
|
const toggleChatbot = (event) => {
|
|
event.preventDefault();
|
|
isCollapsed = !isCollapsed;
|
|
if (!isCollapsed) {
|
|
setTimeout(() => {
|
|
const chatbot = document.getElementById("chatbot");
|
|
chatbot &&
|
|
chatbot.dispatchEvent(
|
|
new CustomEvent("addResponseMsg", { detail: "Hi" })
|
|
);
|
|
}, 1000);
|
|
}
|
|
};
|
|
|
|
const updateStreamingState = () => {
|
|
streaming = isStreaming();
|
|
};
|
|
|
|
|
|
onMount(() => {
|
|
if (typeof window !== "undefined") {
|
|
const ws = createWebSocket(chatEndPoint);
|
|
sendMessage = ws.sendMessage;
|
|
onMessage = ws.onMessage;
|
|
formatStream = ws.formatStream;
|
|
isStreaming = ws.isStreaming;
|
|
refresh = ws.refresh;
|
|
|
|
onMessage((message) => {
|
|
const formattedMessage = formatStream(message);
|
|
|
|
|
|
|
|
|
|
if (formattedMessage.length > 0) {
|
|
running = false;
|
|
scrollToBottom();
|
|
const lastMessage = formattedMessage[formattedMessage.length - 1];
|
|
messages.update((msgs) => {
|
|
const updatedMessages = [...msgs];
|
|
if (lastMessage.id) {
|
|
const existingIndex = updatedMessages.findIndex(
|
|
(msg) => msg.id === lastMessage.id
|
|
);
|
|
if (existingIndex !== -1) {
|
|
updatedMessages[existingIndex] = lastMessage;
|
|
} else {
|
|
updatedMessages.push(lastMessage);
|
|
}
|
|
} else {
|
|
updatedMessages.push(lastMessage);
|
|
}
|
|
return updatedMessages;
|
|
});
|
|
}
|
|
updateStreamingState();
|
|
});
|
|
|
|
|
|
updateStreamingState();
|
|
|
|
return () => {
|
|
refresh();
|
|
};
|
|
}
|
|
});
|
|
|
|
function handleSendMessage(event, message) {
|
|
messages.update((msgs) => [
|
|
...msgs,
|
|
{ id: uuidv4(), content: message, type: "sent" },
|
|
]);
|
|
running = true;
|
|
sendMessage(
|
|
JSON.stringify({
|
|
user_input: message,
|
|
})
|
|
);
|
|
scrollToBottom();
|
|
}
|
|
</script>
|
|
|
|
<div id="chatbot" class="main-card {isCollapsed ? 'collapsed' : ''}">
|
|
<div class="main-title">
|
|
<div>
|
|
<img
|
|
class="!w-[100px] !max-w-[100px]"
|
|
src={ICONS.MIRA_LOGO}
|
|
alt="Miralogo"
|
|
/>
|
|
</div>
|
|
<div class="close-button" style="cursor: pointer;" on:click={toggleChatbot}>
|
|
<img src={ICONS.CLOSE_ICON} alt="close" />
|
|
</div>
|
|
</div>
|
|
<div class="chat-area" id="message-box">
|
|
<div class="flex flex-col justify-center items-center mt-24 mb-16">
|
|
<img src={ICONS.MIRA_LAB} class="!w-[55px]" alt="logo" />
|
|
<h3 class="text-secondary-800 text-xl mt-4 font-bold text-nowrap">
|
|
What can I do for you today?
|
|
</h3>
|
|
</div>
|
|
{#if $messages.length === 0}
|
|
<div class="flex flex-col gap-4 text-left">
|
|
{#each instructionMessages as data, index}
|
|
<div
|
|
on:click={(e) => handleSendMessage(e, data)}
|
|
class="rounded-md cursor-pointer flex flex-row justify-start py-2 pr-[11px] pl-[11px] border border-primary"
|
|
>
|
|
<div
|
|
class="relative rounded-lg hidden max-w-full border-[1px] border-solid border-gainsboro"
|
|
></div>
|
|
<div
|
|
class="relative leading-[24px] text-black max-w-full z-[1] text-sm"
|
|
>
|
|
{data}
|
|
</div>
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
{/if}
|
|
{#each $messages as msg, index (msg.id)}
|
|
<div class="chat-message-div flex justify-end">
|
|
{#if msg?.type === "sent"}
|
|
<div class="chat-message-sent">
|
|
<span class="max-w-[13rem]">{msg.content}</span>
|
|
<div class="ml-2 w-[24px] h-[24px] rounded-full bg-[#AFBEFF]"></div>
|
|
</div>
|
|
{:else}
|
|
<div class="chat-message-received flex items-start">
|
|
<img src={ICONS.MIRA_LAB} class="!w-[25px] mr-2" alt="logo" />
|
|
<span>
|
|
<SvelteMarkdown source={msg.content?.replace("%J", "")} />
|
|
</span>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
{#if running && index === $messages.length - 1}
|
|
<div class="flex w-[10rem] flex-col gap-4 mt-2">
|
|
<div
|
|
class="h-[10px] w-full rounded-full loader-gradient animate-pulse"
|
|
></div>
|
|
<div
|
|
class="h-[10px] w-3/4 rounded-full loader-gradient animate-pulse"
|
|
></div>
|
|
</div>
|
|
{/if}
|
|
{/each}
|
|
</div>
|
|
|
|
<div class="input-div">
|
|
<input
|
|
class="input-message"
|
|
name="message"
|
|
type="text"
|
|
style=" border :1px solid #e3e3e3"
|
|
id="message"
|
|
bind:value={message}
|
|
placeholder="Write a message"
|
|
/>
|
|
<button
|
|
style=" border :1px solid #e3e3e3"
|
|
class="input-send bg-[#1340FF] {streaming ? 'cursor-not-allowed' : ''}"
|
|
disabled={streaming}
|
|
on:click={send}
|
|
>
|
|
<img src={streaming ? ICONS.STREAMING_ICON : ICONS.SEND_BUTTON} alt="" />
|
|
</button>
|
|
</div>
|
|
|
|
<div
|
|
class="text-secondary-600 text-[10px] flex justify-center pb-2 items-center text-center w-[74%] m-auto"
|
|
>
|
|
Mira can sometimes produce incorrect or out of context response. Please read
|
|
the responses carefully.
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<style>
|
|
.loader-gradient {
|
|
background: linear-gradient(90deg, #e9edff 0%, #ffd0ff 121.67%);
|
|
}
|
|
|
|
.close-button {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
.input-message {
|
|
background: #fbfcff;
|
|
color: #555;
|
|
|
|
|
|
}
|
|
.main-card.collapsed {
|
|
|
|
}
|
|
.main-card {
|
|
position: fixed;
|
|
bottom: 0;
|
|
right: 0;
|
|
width: 300px;
|
|
height: 400px;
|
|
|
|
display: flex;
|
|
flex-direction: column;
|
|
background-color: #fff;
|
|
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.chat-message-div {
|
|
display: flex;
|
|
margin: 5px;
|
|
}
|
|
.chat-message-sent {
|
|
background-color: #dcf8c6;
|
|
padding: 5px;
|
|
border-radius: 5px;
|
|
}
|
|
.chat-message-received {
|
|
background-color: #fff;
|
|
padding: 5px;
|
|
border-radius: 5px;
|
|
}
|
|
.chat-area {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: 10px;
|
|
}
|
|
.input-div {
|
|
display: flex;
|
|
padding: 10px;
|
|
|
|
}
|
|
.input-message {
|
|
flex: 1;
|
|
padding: 5px;
|
|
border: 1px solid #ccc;
|
|
border-radius: 5px;
|
|
}
|
|
.input-send {
|
|
margin-left: 10px;
|
|
border: none;
|
|
cursor: pointer;
|
|
padding: 0 10px;
|
|
border-radius: 5px;
|
|
}
|
|
|
|
.title {
|
|
margin: auto;
|
|
font-size: x-large;
|
|
color: rebeccapurple;
|
|
}
|
|
@media (min-width: 450px) {
|
|
.main-card {
|
|
width: 96%;
|
|
max-width: 400px;
|
|
height: calc(100% - 8rem) !important;
|
|
border-radius: 8px !important;
|
|
max-height: 600px;
|
|
margin: 16px !important;
|
|
}
|
|
}
|
|
|
|
.collapsed {
|
|
width: 0px !important;
|
|
height: 0px !important;
|
|
border-radius: 24px !important;
|
|
margin: 16px !important;
|
|
}
|
|
|
|
.main-card {
|
|
display: flex;
|
|
background: white;
|
|
color: white;
|
|
width: 100%;
|
|
height: 80%;
|
|
margin: 0px;
|
|
border-radius: 0px;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
right: 0;
|
|
bottom: 0;
|
|
position: fixed;
|
|
z-index: 10000;
|
|
transition: all 0.5s;
|
|
box-shadow:
|
|
0 10px 16px 0 rgba(0, 0, 0, 0.2),
|
|
0 6px 20px 0 rgba(0, 0, 0, 0.19);
|
|
}
|
|
|
|
#chatbot_toggle {
|
|
position: absolute;
|
|
right: 0;
|
|
border: none;
|
|
height: 48px;
|
|
width: 48px;
|
|
background: rebeccapurple;
|
|
padding: 14px;
|
|
color: white;
|
|
}
|
|
|
|
.line {
|
|
height: 1px;
|
|
background-color: rebeccapurple;
|
|
width: 100%;
|
|
opacity: 0.2;
|
|
}
|
|
.main-title {
|
|
background: linear-gradient(to right, #3f66fb, #cb49f4);
|
|
font-size: large;
|
|
font-weight: bold;
|
|
display: flex;
|
|
height: 48px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
}
|
|
.main-title > div {
|
|
height: 48px;
|
|
width: 48px;
|
|
display: flex;
|
|
margin-left: 8px;
|
|
}
|
|
.main-title svg {
|
|
height: 24px;
|
|
margin: auto;
|
|
}
|
|
.main-title > span {
|
|
margin: auto auto auto 8px;
|
|
}
|
|
.chat-area {
|
|
flex-grow: 1;
|
|
overflow: auto;
|
|
border-radius: 8px;
|
|
padding: 10px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
.input-message {
|
|
padding: 14px 8px 14px 8px;
|
|
flex-grow: 1;
|
|
border: none;
|
|
}
|
|
.input-message:focus {
|
|
outline: #1340ff;
|
|
}
|
|
.input-div {
|
|
height: 58px;
|
|
display: flex;
|
|
}
|
|
|
|
.input-send svg {
|
|
fill: rebeccapurple;
|
|
margin: 11px 8px;
|
|
}
|
|
.chat-message-div {
|
|
display: flex;
|
|
}
|
|
|
|
.chat-message-sent {
|
|
margin: 8px 0px 8px 64px;
|
|
padding: 8px 16px;
|
|
animation-name: fadeIn;
|
|
display: flex;
|
|
justify-content: end !important;
|
|
animation-iteration-count: 1;
|
|
animation-timing-function: ease-in;
|
|
animation-duration: 100ms;
|
|
color: black;
|
|
border-radius: 8px 8px 2px 8px;
|
|
background-color: #e9edff;
|
|
}
|
|
|
|
.chat-message-received {
|
|
margin: 8px 64px 8px 0px;
|
|
padding: 8px 16px;
|
|
animation-name: fadeIn;
|
|
animation-iteration-count: 1;
|
|
animation-timing-function: ease-in;
|
|
animation-duration: 100ms;
|
|
color: black;
|
|
border-radius: 8px 8px 8px 2px;
|
|
background-color: #f4f4f4;
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
from {
|
|
opacity: 0;
|
|
}
|
|
|
|
to {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
::-webkit-scrollbar {
|
|
width: 10px;
|
|
}
|
|
::-webkit-scrollbar-track {
|
|
background: #f1f1f1;
|
|
}
|
|
|
|
::-webkit-scrollbar-thumb {
|
|
background: #888;
|
|
}
|
|
|
|
::-webkit-scrollbar-thumb:hover {
|
|
background: #555;
|
|
}
|
|
|
|
@media screen and (max-width: 448px) {
|
|
.main-card {
|
|
width: 100%;
|
|
height: 100% !important;
|
|
z-index: 999999999999999999999999;
|
|
}
|
|
}
|
|
</style>
|
|
|