Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refine chat log #327

Merged
merged 1 commit into from
Mar 12, 2025
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
4 changes: 3 additions & 1 deletion src/lib/helpers/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,9 @@ function skipLoader(config) {
new RegExp('http(s*)://(.*?)/user/(.*?)/details', 'g'),
new RegExp('http(s*)://(.*?)/agent/labels', 'g'),
new RegExp('http(s*)://(.*?)/conversation/state/keys', 'g'),
new RegExp('http(s*)://(.*?)/logger/instruction/log/keys', 'g')
new RegExp('http(s*)://(.*?)/logger/instruction/log/keys', 'g'),
new RegExp('http(s*)://(.*?)/logger/conversation/(.*?)/content-log', 'g'),
new RegExp('http(s*)://(.*?)/logger/conversation/(.*?)/state-log', 'g')
];

if (config.method === 'post' && postRegexes.some(regex => regex.test(config.url || ''))) {
Expand Down
8 changes: 8 additions & 0 deletions src/lib/helpers/types/commonTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@
* @property {T[]} items - Items.
*/

/**
* @template T
* @typedef {Object} DateTimePagedItems<T>
* @property {number} count - Row count.
* @property {T[]} items - Items.
* @property {Date?} [nextTime]
*/

/**
* @typedef {Object} LlmConfigOption
* @property {number?} [type]
Expand Down
8 changes: 8 additions & 0 deletions src/lib/helpers/types/conversationTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ IRichContent.prototype.quick_replies;

/**
* @typedef {Object} ConversationContentLogModel
* @property {string?} [uid]
* @property {string} conversation_id - The conversation id.
* @property {string} message_id - The message id.
* @property {string} name - The sender name.
Expand All @@ -164,12 +165,19 @@ IRichContent.prototype.quick_replies;

/**
* @typedef {Object} ConversationStateLogModel
* @property {string?} [uid]
* @property {string} conversation_id - The conversation id.
* @property {string} message_id - The message id.
* @property {Object} states - The states content.
* @property {Date} created_at - The log sent time.
*/

/**
* @typedef {Object} ConversationLogFilter
* @property {number} size
* @property {Date?} [startTime]
*/

/**
* @typedef {Object} MessageStateLogModel
* @property {string} conversation_id - The conversation id.
Expand Down
2 changes: 1 addition & 1 deletion src/lib/scss/custom/pages/_chat.scss
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@

.chat-head-info {
display: flex;
flex: 0 0 fit-content;
// flex: 0 0 fit-content;
flex-direction: column;
height: 100%;
gap: 5px;
Expand Down
9 changes: 7 additions & 2 deletions src/lib/services/conversation-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,16 @@ export async function deleteConversation(conversationId) {
/**
* Get dialog history
* @param {string} conversationId
* @param {number} count
* @returns {Promise<import('$conversationTypes').ChatResponseModel[]>}
*/
export async function getDialogs(conversationId) {
export async function getDialogs(conversationId, count = 100) {
let url = replaceUrl(endpoints.dialogsUrl, {conversationId: conversationId});
const response = await axios.get(url);
const response = await axios.get(url, {
params: {
count: count
}
});
return response.data;
}

Expand Down
26 changes: 18 additions & 8 deletions src/lib/services/logging-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,32 @@ export async function getFullLog() {

/**
* Get conversation content log
* @param {string} conversationId
* @returns {Promise<import('$conversationTypes').ConversationContentLogModel[]>}
* @param {string} conversationId
* @param {import('$conversationTypes').ConversationLogFilter} filter
* @returns {Promise<import('$commonTypes').DateTimePagedItems<import('$conversationTypes').ConversationContentLogModel>>}
*/
export async function GetContentLogs(conversationId) {
export async function getContentLogs(conversationId, filter) {
let url = replaceUrl(endpoints.loggingContentLogUrl, {conversationId: conversationId});
const response = await axios.get(url);
const response = await axios.get(url, {
params: {
...filter
}
});
return response.data;
}

/**
* Get conversation state log
* @param {string} conversationId
* @returns {Promise<import('$conversationTypes').ConversationStateLogModel[]>}
* @param {string} conversationId
* @param {import('$conversationTypes').ConversationLogFilter} filter
* @returns {Promise<import('$commonTypes').DateTimePagedItems<import('$conversationTypes').ConversationStateLogModel>>}
*/
export async function GetStateLogs(conversationId) {
export async function getStateLogs(conversationId, filter) {
let url = replaceUrl(endpoints.loggingStateLogUrl, {conversationId: conversationId});
const response = await axios.get(url);
const response = await axios.get(url, {
params: {
...filter
}
});
return response.data;
}
20 changes: 11 additions & 9 deletions src/routes/chat/[agentId]/[conversationId]/chat-box.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,10 @@
const params = $page.params;
const messageLimit = 100;
const screenWidthThreshold = 1024;
const chatWidthThreshold = 300;
const chatWidthThreshold = 500;
const maxTextLength = 64000;
const duration = 2000;
const dialogCount = 50;
const MESSAGE_STORAGE_KEY = 'message_draft_';

/** @type {import('$agentTypes').AgentModel} */
Expand Down Expand Up @@ -219,7 +220,7 @@
onMount(async () => {
disableSpeech = navigator.userAgent.includes('Firefox');
conversation = await getConversation(params.conversationId);
dialogs = await getDialogs(params.conversationId);
dialogs = await getDialogs(params.conversationId, dialogCount);
conversationUser = await getConversationUser(params.conversationId);
selectedTags = conversation?.tags || [];
initUserSentMessages(dialogs);
Expand Down Expand Up @@ -494,7 +495,8 @@
/** @param {import('$conversationTypes').ConversationContentLogModel} log */
function onConversationContentLogGenerated(log) {
if (!isLoadPersistLog) return;
contentLogs.push({ ...log });

contentLogs.push({ ...log, uid: uuidv4() });
contentLogs = contentLogs.map(x => { return { ...x }; });
}

Expand All @@ -503,7 +505,7 @@
if (!isLoadPersistLog) return;

latestStateLog = log;
convStateLogs.push({ ...log });
convStateLogs.push({ ...log, uid: uuidv4() });
convStateLogs = convStateLogs.map(x => { return { ...x }; });
}

Expand Down Expand Up @@ -1489,7 +1491,7 @@
<div class="card mb-0" style="height: 100vh;">
<div class="border-bottom chat-head">
<div class="row chat-row">
<div class="col-md-4 col-7 chat-head-info">
<div class="col-md-4 col-4 chat-head-info">
<div class="chat-head-agent">
{#if agent?.icon_url}
<div class="line-align-center">
Expand All @@ -1507,9 +1509,9 @@
</div>
</div>
</div>
<div class="col-md-8 col-5">
<div class="user-chat-nav user-chat-nav-flex mb-0">

<div class="col-md-8 col-8">
<div class="user-chat-nav user-chat-nav-flex mb-0" style={`padding-top: ${!isFrame ? '5px' : '0px'};`}>
{#if PUBLIC_DEBUG_MODE === 'true' && isFrame}
<div class="">
<button
Expand Down Expand Up @@ -1913,7 +1915,7 @@
bind:contentLogs={contentLogs}
bind:convStateLogs={convStateLogs}
bind:lastestStateLog={latestStateLog}
autoScroll={autoScrollLog}
bind:autoScroll={autoScrollLog}
closeWindow={() => closePersistLog()}
cleanScreen={() => cleanPersistLogScreen()}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
];

$: {
logDisplayStyle = '';
logTextStyle = '';
if (data.source === ContentLogSource.AgentResponse || data.source === ContentLogSource.Notification) {
logDisplayStyle = 'border border-secondary';
logTextStyle = 'text-info';
Expand Down Expand Up @@ -62,7 +64,7 @@
{/if}
</span>
<span class="ms-2">{`${utcToLocal(data?.created_at, 'hh:mm:ss.SSS A, MMM DD YYYY')} `}</span>
</div>
</div>
</div>
<div
class={`rounded log-content ${logDisplayStyle}`}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
/>
</div>
{#if data.message_id}
<div style="margin-top: 10px;">
{`MessageId: ${data.message_id}`}
</div>
<div style="margin-top: 10px;">
{`MessageId: ${data.message_id}`}
</div>
{/if}
</div>
Original file line number Diff line number Diff line change
@@ -1,16 +1,31 @@
<script>
import { afterUpdate, beforeUpdate, onDestroy, onMount, tick } from 'svelte';
import { page } from '$app/stores';
import moment from 'moment';
import { v4 as uuidv4 } from 'uuid';
import 'overlayscrollbars/overlayscrollbars.css';
import { OverlayScrollbars } from 'overlayscrollbars';
import { afterUpdate, beforeUpdate, onDestroy, onMount } from 'svelte';
import { page } from '$app/stores';
import { GetContentLogs, GetStateLogs } from '$lib/services/logging-service';
import { getContentLogs, getStateLogs } from '$lib/services/logging-service';
import NavBar from '$lib/common/nav-bar/NavBar.svelte';
import NavItem from '$lib/common/nav-bar/NavItem.svelte';
import ContentLogElement from './content-log-element.svelte';
import ConversationStateLogElement from './conversation-state-log-element.svelte';

const contentLogTab = 1;
const conversationStateLogTab = 2;
const conversationId = $page.params.conversationId;
const utcNow = moment.utc().toDate();

const scrollbarElements = [
{
id: '.content-log-scrollbar',
type: contentLogTab,
},
{
id: '.conv-state-log-scrollbar',
type: conversationStateLogTab,
}
];

/** @type {import('$conversationTypes').ConversationContentLogModel[]} */
export let contentLogs = [];
Expand All @@ -36,6 +51,11 @@
let selectedTab = contentLogTab;
let tabChanged = false;

/** @type {import('$conversationTypes').ConversationLogFilter} */
let contentLogFilter = { size: 20, startTime: utcNow };
/** @type {import('$conversationTypes').ConversationLogFilter} */
let stateLogFilter = { size: 20, startTime: utcNow };

const options = {
scrollbars: {
visibility: 'auto',
Expand All @@ -49,18 +69,10 @@
};

onMount(async () => {
const conversationId = $page.params.conversationId;
contentLogs = await GetContentLogs(conversationId);
convStateLogs = await GetStateLogs(conversationId);
lastestStateLog = convStateLogs.slice(-1)[0];

const scrollbarElements = [
document.querySelector('.content-log-scrollbar'),
document.querySelector('.conv-state-log-scrollbar')
].filter(Boolean);
scrollbarElements.forEach(elem => {
scrollbars = [ ...scrollbars, OverlayScrollbars(elem, options) ];
});
await getChatContentLogs();
await getChatStateLogs();

initScrollbars();
scrollToBottom();
});

Expand Down Expand Up @@ -95,6 +107,63 @@
}, 200);
});
}

function initScrollbars() {
scrollbarElements.forEach(item => {
const elem = document.querySelector(item.id);
if (!elem) return;

const scrollbar = OverlayScrollbars(elem, options);
scrollbar.on("scroll", async (e) => {
const curScrollTop = e.elements().scrollOffsetElement.scrollTop;
if (curScrollTop <= 1) {
tabChanged = true;
if (item.type === contentLogTab) {
await getChatContentLogs();
} else if (item.type === conversationStateLogTab) {
await getChatStateLogs();
}
}
});

scrollbars = [ ...scrollbars, scrollbar];
});
}

async function getChatContentLogs() {
if (!contentLogFilter.startTime) return;

const pagedContentLogs = await getContentLogs(conversationId, contentLogFilter);
contentLogFilter = {
...contentLogFilter,
startTime: pagedContentLogs.nextTime || null
};
const newLogs = pagedContentLogs.items?.map(x => {
return { uid: uuidv4(), ...x };
}) || [];

if (newLogs.length > 0) {
contentLogs = [...newLogs, ...contentLogs];
}
}

async function getChatStateLogs() {
if (!stateLogFilter.startTime) return;

const pagedStateLogs = await getStateLogs(conversationId, stateLogFilter);
stateLogFilter = {
...stateLogFilter,
startTime: pagedStateLogs.nextTime || null
};
const newLogs = pagedStateLogs.items?.map(x => {
return { uid: uuidv4(), ...x };
}) || [];

if (newLogs.length > 0) {
convStateLogs = [...newLogs, ...convStateLogs];
lastestStateLog = convStateLogs.slice(-1)[0];
}
}

function cleanLogs() {
contentLogs = [];
Expand Down Expand Up @@ -141,15 +210,15 @@

<div class="content-log-scrollbar log-list padding-side log-body" class:hide={selectedTab !== contentLogTab}>
<ul>
{#each contentLogs as log}
{#each contentLogs as log (log.uid)}
<ContentLogElement data={log} />
{/each}
</ul>
</div>

<div class="conv-state-log-scrollbar log-list padding-side log-body" class:hide={selectedTab !== conversationStateLogTab}>
<ul>
{#each convStateLogs as log}
{#each convStateLogs as log (log.uid)}
<ConversationStateLogElement data={log} />
{/each}
</ul>
Expand Down