Skip to content

Commit

Permalink
Merge pull request #50 from iceljc/features/add-loading-dots-markdown
Browse files Browse the repository at this point in the history
Features/add loading dots markdown
  • Loading branch information
Oceania2018 authored Feb 17, 2024
2 parents bd28c7f + 1326d4a commit 45f78be
Show file tree
Hide file tree
Showing 10 changed files with 196 additions and 24 deletions.
12 changes: 12 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"drawflow": "^0.0.59",
"fullcalendar": "^6.1.9",
"lodash": "^4.17.21",
"marked": "^12.0.0",
"moment": "^2.30.1",
"overlayscrollbars": "^2.4.4",
"overlayscrollbars-svelte": "^0.5.2",
Expand Down
78 changes: 78 additions & 0 deletions src/lib/common/Loading.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<script>
import { range } from "$lib/helpers/utils";
/** @type {string} */
export let color = '#FF3E00';
/** @type {string} */
export let unit = 'px';
/** @type {string} */
export let duration = '1.5s';
/** @type {string | number} */
export let size = '60';
/** @type {boolean} */
export let pause = false;
/** @type {'dot' | 'cube'} */
export let shape = 'dot';
/** @type {number} */
export let count = 3;
/** @type {string | number} */
export let gap = '10';
const unitRegex = /[a-zA-Z]/;
const durationUnit = duration.match(unitRegex)?.[0] ?? 's';
const durationNum = duration.replace(unitRegex, '');
</script>
<div class="loading-wrapper" style="--size: {size}{unit}; --color: {color}; --duration: {duration}; --count: {count}; --gap: {gap}{unit}">
{#each range(count, 0) as version}
<div
class={`animation ${shape === 'cube' ? 'cube' : 'dot'}`}
class:pause-animation={pause}
style="animation-delay: {version * (+durationNum / 10)}{durationUnit};"
/>
{/each}
</div>
<style>
.loading-wrapper {
position: relative;
display: flex;
justify-content: center;
align-items: center;
width: calc(var(--size) * var(--count) + var(--gap) * (var(--count) - 1));
height: calc(var(--size));
gap: var(--gap);
}
.animation {
background-color: var(--color);
animation: motion var(--duration) cubic-bezier(0.895, 0.03, 0.685, 0.22) infinite;
}
.cube {
width: calc(var(--size) / 2);
height: var(--size);
}
.dot {
width: var(--size);
height: var(--size);
border-radius: 50%;
}
.pause-animation {
animation-play-state: paused;
}
@keyframes motion {
0% {
opacity: 1;
}
50% {
opacity: 0;
}
100% {
opacity: 1;
}
}
</style>
10 changes: 9 additions & 1 deletion src/lib/helpers/enums.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,12 @@ const userRole = {
Assistant: "assistant"
};

export const UserRole = Object.freeze(userRole);
export const UserRole = Object.freeze(userRole);

const senderAction = {
TypingOn: 1,
TypingOff: 2,
MarkSeen: 3
}

export const SenderAction = Object.freeze(senderAction);
24 changes: 20 additions & 4 deletions src/lib/helpers/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,15 @@ IRichContent.prototype.text;
* @property {UserStateDetailModel[]} states - The states added by user.
*/


/**
* Conversation sender action
*
* @typedef {Object} ConversationSenderActionModel
* @property {string} conversation_id - The conversation id.
* @property {number} sender_action - The sender action.
*/

/**
* Invoked when a new conersation is created.
* This callback type is called `requestCallback` and is displayed as a global symbol.
Expand All @@ -315,17 +324,24 @@ IRichContent.prototype.text;
*/

/**
* Content log
* Conversation content log
*
* @callback OnContentLogReceived
* @callback OnConversationContentLogReceived
* @param {ConversationContentLogModel} log
*/

/**
* Conversation state log
*
* @callback OnConversationStateLogGenerated
* @param {ConversationStateLogModel} log
*/

/**
* Conversation states
*
* @callback OnConversationStatesGenerated
* @param {ConversationStateLogModel} data
* @callback OnSenderActionGenerated
* @param {ConversationSenderActionModel} data
*/


Expand Down
3 changes: 3 additions & 0 deletions src/lib/helpers/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function range(size = 3, startAt = 0) {
return [...Array(size).keys()].map((i) => i + startAt);
};
5 changes: 5 additions & 0 deletions src/lib/scss/custom/pages/_chat.scss
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,11 @@
color: $primary;
margin-bottom: 4px;
}

span p {
margin-top: 0px !important;
margin-bottom: 0px !important;
}
}

.dropdown {
Expand Down
27 changes: 18 additions & 9 deletions src/lib/services/signalr-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,14 @@ export const signalr = {
/** @type {import('$types').OnMessageReceived} */
onMessageReceivedFromAssistant: () => {},

/** @type {import('$types').OnContentLogReceived} */
onContentLogGenerated: () => {},
/** @type {import('$types').OnConversationContentLogReceived} */
onConversationContentLogGenerated: () => {},

/** @type {import('$types').OnConversationStatesGenerated} */
onConversationStatesGenerated: () => {},
/** @type {import('$types').OnConversationStateLogGenerated} */
onConversationStateLogGenerated: () => {},

/** @type {import('$types').OnSenderActionGenerated} */
onSenderActionGenerated: () => {},

// start the connection
/** @param {string} conversationId */
Expand Down Expand Up @@ -81,17 +84,23 @@ export const signalr = {
}
});

connection.on('onContentLogGenerated', (log) => {
connection.on('OnConversationContentLogGenerated', (log) => {
const jsonLog = JSON.parse(log);
if (conversationId === jsonLog?.conversation_id) {
this.onContentLogGenerated(jsonLog);
this.onConversationContentLogGenerated(jsonLog);
}
});

connection.on('onConversateStatesGenerated', (data) => {
const jsonData = JSON.parse(data);
connection.on('OnConversateStateLogGenerated', (log) => {
const jsonData = JSON.parse(log);
if (conversationId === jsonData?.conversation_id) {
this.onConversationStatesGenerated(jsonData);
this.onConversationStateLogGenerated(jsonData);
}
});

connection.on('OnSenderActionGenerated', (data) => {
if (conversationId === data?.conversation_id) {
this.onSenderActionGenerated(data);
}
});
},
Expand Down
48 changes: 38 additions & 10 deletions src/routes/chat/[agentId]/[conversationId]/chat-box.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@
import StateLog from './state-log.svelte';
import StateModal from './state-modal.svelte';
import DialogModal from '$lib/common/DialogModal.svelte';
import { UserRole } from '$lib/helpers/enums';
import { SenderAction, UserRole } from '$lib/helpers/enums';
import moment from 'moment';
import HeadTitle from '$lib/common/HeadTitle.svelte';
import RcMarkdown from './rc-markdown.svelte';
import Loading from '$lib/common/Loading.svelte';
const options = {
scrollbars: {
Expand Down Expand Up @@ -81,6 +83,7 @@
let isOpenEditMsgModal = false;
let isOpenAddStateModal = false;
let isSendingMsg = false;
let isThinking = false;
onMount(async () => {
dialogs = await GetDialogs(params.conversationId);
Expand All @@ -90,8 +93,9 @@
signalr.onMessageReceivedFromClient = onMessageReceivedFromClient;
signalr.onMessageReceivedFromCsr = onMessageReceivedFromCsr;
signalr.onMessageReceivedFromAssistant = onMessageReceivedFromAssistant;
signalr.onContentLogGenerated = onContentLogGenerated;
signalr.onConversationStatesGenerated = onConversationStatesGenerated;
signalr.onConversationContentLogGenerated = onConversationContentLogGenerated;
signalr.onConversationStateLogGenerated = onConversationStateLogGenerated;
signalr.onSenderActionGenerated = onSenderActionGenerated;
await signalr.start(params.conversationId);
const scrollElements = document.querySelectorAll('.scrollbar');
Expand Down Expand Up @@ -171,19 +175,28 @@
}
/** @param {import('$types').ConversationContentLogModel} log */
function onContentLogGenerated(log) {
function onConversationContentLogGenerated(log) {
if (!isLoadContentLog) return;
contentLogs.push({ ...log });
contentLogs = contentLogs.map(x => { return { ...x }; });
}
/** @param {import('$types').ConversationStateLogModel} data */
function onConversationStatesGenerated(data) {
/** @param {import('$types').ConversationStateLogModel} log */
function onConversationStateLogGenerated(log) {
if (!isLoadStateLog) return;
stateLogs.push({ ...data });
stateLogs.push({ ...log });
stateLogs = stateLogs.map(x => { return { ...x }; });
}
/** @param {import('$types').ConversationSenderActionModel} data */
function onSenderActionGenerated(data) {
if (data?.sender_action == SenderAction.TypingOn) {
isThinking = true;
} else if (data?.sender_action == SenderAction.TypingOff) {
isThinking = false;
}
}
async function newConversationHandler() {
const conversation = await newConversation(params.agentId);
conversationStore.set(conversation);
Expand Down Expand Up @@ -250,7 +263,7 @@
return;
}
if ((e.key === 'Enter' && (!!e.shiftKey || !!e.ctrlKey)) || e.key !== 'Enter' || !!!_.trim(text) || isSendingMsg) {
if ((e.key === 'Enter' && (!!e.shiftKey || !!e.ctrlKey)) || e.key !== 'Enter' || !!!_.trim(text) || isSendingMsg || isThinking) {
return;
}
Expand Down Expand Up @@ -579,6 +592,21 @@
</li>
{/each}
{/each}
{#if isThinking}
<li>
<div class="conversation-list">
<div class="cicon-wrap float-start">
<img src={PUBLIC_LIVECHAT_ENTRY_ICON} class="rounded-circle avatar-xs" alt="avatar">
</div>
<div class="ctext-wrap float-start">
<div class="flex-shrink-0 align-self-center">
<Loading duration={'1s'} size={12} color={'var(--bs-primary)'} />
</div>
</div>
</div>
</li>
{/if}
</ul>
</div>
</div>
Expand All @@ -595,7 +623,7 @@
</div>
<div class="col">
<div class="position-relative">
<textarea rows={1} maxlength={500} class="form-control chat-input" bind:value={text} on:keydown={e => onSendMessage(e)} disabled={isSendingMsg} placeholder="Enter Message..." />
<textarea rows={1} maxlength={500} class="form-control chat-input" bind:value={text} on:keydown={e => onSendMessage(e)} disabled={isSendingMsg || isThinking} placeholder="Enter Message..." />
<div class="chat-input-links" id="tooltip-container">
<ul class="list-inline mb-0">
<li class="list-inline-item">
Expand All @@ -609,7 +637,7 @@
<button
type="submit"
class="btn btn-primary btn-rounded chat-send waves-effect waves-light"
disabled={!!!_.trim(text) || isSendingMsg}
disabled={!!!_.trim(text) || isSendingMsg || isThinking}
on:click={sendTextMessage}
><span class="d-none d-sm-inline-block me-2">Send</span>
<i class="mdi mdi-send" />
Expand Down
12 changes: 12 additions & 0 deletions src/routes/chat/[agentId]/[conversationId]/rc-markdown.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<script>
import { marked } from 'marked';
import { replaceNewLine } from '$lib/helpers/http';
// /** @type {import('$types').TextMessage} */
// export let message;
/** @type {string} */
export let message = "The SQL statement to create a new service category named 'DSC Equipment' is: <br /> ```sql INSERT INTO data_ServiceCategory (Name) VALUES ('DSC Equipment');``` <br /> I have executed the query to create the new service category.";
</script>

<span>{@html marked(replaceNewLine(message || ''))}</span>

0 comments on commit 45f78be

Please sign in to comment.