ans123's picture
Initial upload from Colab
ef1ad9e verified
<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";
// define variables for websocket
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; // Make isCollapsed prop exported
export let searchData;
// get the input value on keypress in child and send req to websocket
$: {
if (searchData) {
messages.update((msgs) => [
...msgs,
{ id: uuidv4(), content: searchData, type: "sent" },
]);
running = true;
sendMessage(
JSON.stringify({
user_input: searchData,
})
);
scrollToBottom();
}
}
// send message on send button
const send = () => {
if (message === "") return;
running = true;
sendMessage(
JSON.stringify({
user_input: message,
})
);
addMsg(message);
// setTimeout(() => addResponseMsg(message), 1000); // Echo the message after 1 second
};
// format the message to be rendered
const addMsg = (msg) => {
messages.update((msgs) => [
...msgs,
{ id: uuidv4(), content: msg, type: "sent" },
]);
message = "";
scrollToBottom();
};
// const addResponseMsg = (msg) => {
// messages.update((msgs) => [...msgs, { text: msg, type: "received" }]);
// running = false;
// 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();
};
//connect to ws and reconnect if disconnected and store the data in messages
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 the last message in this array has an matching id, it means it's part of a stream.
// We update the messages array by replacing the existing message with the same id,
// or by adding it if it doesn't exist. This ensures that the message
// content is updated correctly for streaming data
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();
});
// Ensure we set the initial state correctly
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="line"></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>
<!-- hello -->
<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;
/* border: 1px solid rgba(227, 227, 227, 1); */
}
.main-card.collapsed {
/* transform: translateY(0); */
}
.main-card {
position: fixed;
bottom: 0;
right: 0;
width: 300px;
height: 400px;
/* border: 1px solid #ccc; */
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;
/* border-top: 1px solid #ccc; */
}
.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; /*previous value 48px*/
height: 0px !important; /*previous value 48px*/
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>