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
5 changes: 3 additions & 2 deletions src/lib/helpers/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ function skipLoader(config) {
new RegExp('http(s*)://(.*?)/knowledge/vector/(.*?)/page', 'g'),
new RegExp('http(s*)://(.*?)/knowledge/(.*?)/search', 'g'),
new RegExp('http(s*)://(.*?)/knowledge/vector/(.*?)/create', 'g'),
new RegExp('http(s*)://(.*?)/knowledge/document/(.*?)/page', 'g')
new RegExp('http(s*)://(.*?)/knowledge/document/(.*?)/page', 'g'),
];

const putRegexes = [
Expand All @@ -80,7 +80,8 @@ function skipLoader(config) {
new RegExp('http(s*)://(.*?)/address/options(.*?)', 'g'),
new RegExp('http(s*)://(.*?)/conversation/(.*?)/files/(.*?)', 'g'),
new RegExp('http(s*)://(.*?)/llm-provider/(.*?)/models', 'g'),
new RegExp('http(s*)://(.*?)/knowledge/vector/collections', 'g')
new RegExp('http(s*)://(.*?)/knowledge/vector/collections', 'g'),
new RegExp('http(s*)://(.*?)/knowledge/vector/(.*?)/exist', 'g')
];

if (config.method === 'post' && postRegexes.some(regex => regex.test(config.url || ''))) {
Expand Down
8 changes: 6 additions & 2 deletions src/lib/scss/custom/pages/_chat.scss
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,10 @@
min-width: 50px;
border-radius: 10px;
}

.link-option:hover {
text-decoration: underline;
}
}

.complex-option-container {
Expand Down Expand Up @@ -750,8 +754,8 @@
border-radius: 10px;
}

.btn-link {
background-color: unset !important;
.link-option:hover {
text-decoration: underline;
}
}
}
Expand Down
23 changes: 22 additions & 1 deletion src/lib/scss/custom/pages/_knowledgebase.scss
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
}

.confidence-box {
width: 40%;
width: 100%;
}

.input-text {
Expand Down Expand Up @@ -375,3 +375,24 @@
}
}
}


.vector-collection-create-container {
.collection-input {
.invalid-input {
border-color: var(--bs-danger);
}

.collection-note {
display: flex;
}

.valid {
justify-content: flex-end;
}

.invalid {
justify-content: space-between;
}
}
}
3 changes: 2 additions & 1 deletion src/lib/services/api-endpoints.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ export const endpoints = {
loggingStateLogUrl: `${host}/logger/conversation/{conversationId}/state-log`,

// knowledge base
vectorKnowledgeCollectionsUrl: `${host}/knowledge/vector/collections`,
vectorCollectionExistUrl: `${host}/knowledge/vector/{collection}/exist`,
vectorCollectionsUrl: `${host}/knowledge/vector/collections`,
vectorKnowledgePageListUrl: `${host}/knowledge/vector/{collection}/page`,
vectorKnowledgeSearchUrl: `${host}/knowledge/vector/{collection}/search`,
vectorKnowledgeCreateUrl: `${host}/knowledge/vector/{collection}/create`,
Expand Down
16 changes: 15 additions & 1 deletion src/lib/services/knowledge-base-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,26 @@ import { replaceUrl } from '$lib/helpers/http.js';
import { endpoints } from './api-endpoints.js';
import axios from 'axios';


/**
* @param {string} collection
* @returns {Promise<boolean>}
*/
export async function existVectorCollection(collection) {
const url = replaceUrl(endpoints.vectorCollectionExistUrl, {
collection: collection
});

const response = await axios.get(url);
return response.data;
}

/**
* @param {string} type
* @returns {Promise<string[]>}
*/
export async function getVectorKnowledgeCollections(type) {
const url = endpoints.vectorKnowledgeCollectionsUrl;
const url = endpoints.vectorCollectionsUrl;
const response = await axios.get(url, {
params: {
type: type
Expand Down
159 changes: 81 additions & 78 deletions src/routes/chat/[agentId]/[conversationId]/chat-box.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,8 @@
async function refresh() {
// trigger UI render
dialogs = dialogs?.map(item => { return { ...item }; }) || [];
lastBotMsg = null;
await tick();
lastBotMsg = findLastBotMessage(dialogs);
lastMsg = dialogs.slice(-1)[0];
assignMessageDisclaimer(dialogs)
Expand Down Expand Up @@ -415,6 +417,7 @@
/** @param {import('$conversationTypes').ConversationMessageDeleteModel} data */
function onConversationMessageDeleted(data) {
if (!!!data?.message_id) return;

truncateDialogs(data.message_id);
}

Expand Down Expand Up @@ -863,7 +866,7 @@
}

/** @param {string} messageId */
function truncateDialogs(messageId) {
async function truncateDialogs(messageId) {
const foundIdx = dialogs.findIndex(x => x.message_id === messageId);
if (foundIdx < 0) return false;
dialogs = dialogs.filter((x, idx) => idx < foundIdx);
Expand Down Expand Up @@ -1137,85 +1140,85 @@
<div class="chat-conversation p-3">
<ul class="list-unstyled mb-0">
{#each Object.entries(groupedDialogs) as [createDate, dialogGroup]}
<li>
<div class="chat-day-title">
<span class="title">{createDate}</span>
</div>
</li>
{#each dialogGroup as message}
<li id={'test_k' + message.message_id} class:right={USER_SENDERS.includes(message.sender?.role)}>
<div class="conv-msg-container">
{#if USER_SENDERS.includes(message.sender?.role)}
<div class="msg-container">
<div
tabindex="0"
aria-label="user-msg-to-log"
role="link"
on:keydown={() => {}}
on:click={() => directToLog(message.message_id)}
>
<div class="ctext-wrap user-msg"
class:clickable={!isLite && (isLoadPersistLog || isLoadInstantLog)}
id={`user-msg-${message.message_id}`}
>
<div class="text-start fw-bold">{@html replaceNewLine(message.text)}</div>
<li>
<div class="chat-day-title">
<span class="title">{createDate}</span>
</div>
</li>
{#each dialogGroup as message}
<li id={'test_k' + message.message_id} class:right={USER_SENDERS.includes(message.sender?.role)}>
<div class="conv-msg-container">
{#if USER_SENDERS.includes(message.sender?.role)}
<div class="msg-container">
<div
tabindex="0"
aria-label="user-msg-to-log"
role="link"
on:keydown={() => {}}
on:click={() => directToLog(message.message_id)}
>
<div class="ctext-wrap user-msg"
class:clickable={!isLite && (isLoadPersistLog || isLoadInstantLog)}
id={`user-msg-${message.message_id}`}
>
<div class="text-start fw-bold">{@html replaceNewLine(message.text)}</div>
</div>
<p class="chat-time mb-0 float-end">
<i class="bx bx-time-five align-middle me-1" />
{utcToLocal(message.created_at, 'hh:mm A')}
</p>
</div>
{#if !!message.post_action_disclaimer}
<RcDisclaimer content={message.post_action_disclaimer} />
{/if}
{#if !!message.is_chat_message || !!message.has_message_files}
<MessageFileGallery
messageId={message?.message_id}
galleryStyles={'justify-content: flex-end;'}
fetchFiles={() => getConversationFiles(params.conversationId, message.message_id, FileSourceType.User)}
/>
{/if}
</div>
<p class="chat-time mb-0 float-end">
<i class="bx bx-time-five align-middle me-1" />
{utcToLocal(message.created_at, 'hh:mm A')}
</p>
{#if !isLite}
<Dropdown>
<DropdownToggle class="dropdown-toggle" tag="span" disabled={isSendingMsg || isThinking || disableAction}>
<i class="bx bx-dots-vertical-rounded" />
</DropdownToggle>
<DropdownMenu class="dropdown-menu-end">
<DropdownItem on:click={(e) => editMessage(e, message)}>Edit</DropdownItem>
<DropdownItem on:click={(e) => resendMessage(e, message)}>Resend</DropdownItem>
<DropdownItem on:click={(e) => deleteMessage(e, message.message_id)}>Delete</DropdownItem>
</DropdownMenu>
</Dropdown>
{/if}
{:else}
<div class="cicon-wrap align-content-end">
{#if message.sender.role == UserRole.Client}
<img src="images/users/user-dummy.jpg" class="rounded-circle avatar-sm" style="margin-bottom: -15px;" alt="avatar">
{:else}
<img src={PUBLIC_LIVECHAT_ENTRY_ICON} class="rounded-circle avatar-sm" style="margin-bottom: -15px;" alt="avatar">
{/if}
</div>
<div class="msg-container">
<RcMessage message={message} />
{#if message?.message_id === lastBotMsg?.message_id}
<AudioSpeaker
id={message?.message_id}
text={message?.rich_content?.message?.text || message?.text}
/>
{/if}
{#if !!message.is_chat_message || !!message.has_message_files}
<MessageFileGallery
messageId={message?.message_id}
galleryStyles={'justify-content: flex-start;'}
fetchFiles={() => getConversationFiles(params.conversationId, message.message_id, FileSourceType.Bot)}
/>
{/if}
</div>
{/if}
</div>
{#if !!message.post_action_disclaimer}
<RcDisclaimer content={message.post_action_disclaimer} />
{/if}
{#if !!message.is_chat_message || !!message.has_message_files}
<MessageFileGallery
messageId={message?.message_id}
galleryStyles={'justify-content: flex-end;'}
fetchFiles={() => getConversationFiles(params.conversationId, message.message_id, FileSourceType.User)}
/>
{/if}
</div>
{#if !isLite}
<Dropdown>
<DropdownToggle class="dropdown-toggle" tag="span" disabled={isSendingMsg || isThinking || disableAction}>
<i class="bx bx-dots-vertical-rounded" />
</DropdownToggle>
<DropdownMenu class="dropdown-menu-end">
<DropdownItem on:click={(e) => editMessage(e, message)}>Edit</DropdownItem>
<DropdownItem on:click={(e) => resendMessage(e, message)}>Resend</DropdownItem>
<DropdownItem on:click={(e) => deleteMessage(e, message.message_id)}>Delete</DropdownItem>
</DropdownMenu>
</Dropdown>
{/if}
{:else}
<div class="cicon-wrap align-content-end">
{#if message.sender.role == UserRole.Client}
<img src="images/users/user-dummy.jpg" class="rounded-circle avatar-sm" style="margin-bottom: -15px;" alt="avatar">
{:else}
<img src={PUBLIC_LIVECHAT_ENTRY_ICON} class="rounded-circle avatar-sm" style="margin-bottom: -15px;" alt="avatar">
{/if}
</div>
<div class="msg-container">
<RcMessage message={message} />
{#if message?.message_id === lastBotMsg?.message_id}
<AudioSpeaker
id={message?.message_id}
text={message?.rich_content?.message?.text || message?.text}
/>
{/if}
{#if !!message.is_chat_message || !!message.has_message_files}
<MessageFileGallery
messageId={message?.message_id}
galleryStyles={'justify-content: flex-start;'}
fetchFiles={() => getConversationFiles(params.conversationId, message.message_id, FileSourceType.Bot)}
/>
{/if}
</div>
{/if}
</div>
</li>
{/each}
</li>
{/each}
{/each}

{#if isThinking}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@
*/
function handleClickOption(e, option) {
e.preventDefault();

if (option.type === ElementType.Web && option.url) {
window.open(option.url);
return;
}

innerConfirm(option?.title, option?.payload);
}

Expand Down Expand Up @@ -111,23 +117,13 @@
{#if card.options?.length > 0}
<div class="card-option-group">
{#each card.options as option, i (i)}
{#if option.type === ElementType.Web && option.url}
<button
class={`btn btn-sm btn-link m-1 ${option.is_secondary ? 'btn-outline-secondary': 'btn-outline-primary'}`}
disabled={disabled}
on:click={() => window.open(option.url)}
>
{option.title}
</button>
{:else}
<button
class={`btn btn-sm m-1 ${option.is_secondary ? 'btn-outline-secondary': 'btn-outline-primary'}`}
disabled={disabled}
on:click={(e) => handleClickOption(e, option)}
>
{option.title}
</button>
{/if}
<button
class={`btn btn-sm m-1 ${option.is_secondary ? 'btn-outline-secondary': 'btn-outline-primary'}`}
disabled={disabled}
on:click={(e) => handleClickOption(e, option)}
>
<span class={`${option.type === ElementType.Web && option.url ? 'link-option' : ''}`}>{option.title}</span>
</button>
{/each}
</div>
{/if}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@
function handleClickOption(e, option, index) {
e.preventDefault();

if (option.type === ElementType.Web && option.url) {
window.open(option.url);
return;
}

if (!isMultiSelect) {
innerConfirm(option?.title, option?.payload);
} else {
Expand Down Expand Up @@ -146,26 +151,17 @@
{#if plainOptions || fileOption}
<div class="plain-option-container center-option">
{#each plainOptions as option, index}
{#if option.type === ElementType.Web && option.url}
<button
class={`btn btn-sm btn-link m-1 ${option.is_secondary ? 'btn-outline-secondary': 'btn-outline-primary'}`}
disabled={disabled}
in:fade={{ duration: duration }}
on:click={() => window.open(option.url)}
>
{option.title}
</button>
{:else}
<button
class={`btn btn-sm m-1 ${option.is_secondary ? 'btn-outline-secondary': 'btn-outline-primary'}`}
class:active={!!option.isClicked}
disabled={disabled}
in:fade={{ duration: duration }}
on:click={(e) => handleClickOption(e, option, index)}
>
<button
class={`btn btn-sm m-1 ${option.is_secondary ? 'btn-outline-secondary': 'btn-outline-primary'}`}
class:active={!!option.isClicked}
disabled={disabled}
in:fade={{ duration: duration }}
on:click={(e) => handleClickOption(e, option, index)}
>
<span class={`${option.type === ElementType.Web && option.url ? 'link-option' : ''}`}>
{option.title}
</button>
{/if}
</span>
</button>
{/each}
{#if plainOptions && isMultiSelect}
<button
Expand Down
Loading