This commit is contained in:
Timothy Jaeryang Baek 2026-07-14 01:39:40 -04:00
parent 34813573fa
commit 6e14d0d627
22 changed files with 249 additions and 243 deletions

View file

@ -17,7 +17,6 @@
import {
chatId,
chats,
config,
type Model,
models,
@ -31,7 +30,6 @@
audioQueue,
showControls,
showCallOverlay,
currentChatPage,
temporaryChatEnabled,
mobile,
chatTitle,
@ -43,7 +41,6 @@
terminalServers,
functions,
selectedFolder,
pinnedChats,
showEmbeds,
selectedTerminalId,
showFileNavPath,
@ -51,6 +48,7 @@
chatRequestQueues,
desktopEvent
} from '$lib/stores';
import { refreshChatList } from '$lib/stores/chat-list';
import { WEBUI_API_BASE_URL } from '$lib/constants';
@ -76,8 +74,6 @@
deleteChatById,
getAllTags,
getChatById,
getChatList,
getPinnedChatList,
getTagsById,
updateChatById,
updateChatFolderIdById
@ -699,13 +695,12 @@
} else if (type === 'chat:message:favorite') {
// Update message favorite status
message.favorite = data.favorite;
} else if (type === 'chat:title') {
chatTitle.set(data);
currentChatPage.set(1);
await chats.set(await getChatList(localStorage.token, $currentChatPage));
} else if (type === 'chat:tags') {
chat = await getChatById(localStorage.token, $chatId);
allTags.set(await getAllTags(localStorage.token));
} else if (type === 'chat:title') {
chatTitle.set(data);
await refreshChatList(localStorage.token);
} else if (type === 'chat:tags') {
chat = await getChatById(localStorage.token, $chatId);
allTags.set(await getAllTags(localStorage.token));
} else if (type === 'source' || type === 'citation') {
if (data?.type === 'code_execution') {
// Code execution; update existing code execution by ID, or add new one.
@ -1764,12 +1759,11 @@
const chatCompletedHandler = async (_chatId, modelId, responseMessageId, messages) => {
// Backend handles outlet filters and persistence inline.
// Just refresh the sidebar chat list.
if ($chatId == _chatId && !$temporaryChatEnabled) {
currentChatPage.set(1);
await chats.set(await getChatList(localStorage.token, $currentChatPage));
}
};
// Just refresh the sidebar chat list.
if ($chatId == _chatId && !$temporaryChatEnabled) {
await refreshChatList(localStorage.token);
}
};
const chatActionHandler = async (_chatId, actionId, modelId, responseMessageId, event = null) => {
const messages = createMessagesList(history, responseMessageId);
@ -1816,13 +1810,12 @@
history: history,
params: params,
files: chatFiles
});
});
currentChatPage.set(1);
await chats.set(await getChatList(localStorage.token, $currentChatPage));
await refreshChatList(localStorage.token);
}
}
}
};
};
const getChatEventEmitter = async (modelId: string, chatId: string = '') => {
return setInterval(() => {
@ -2660,12 +2653,11 @@
// and causing spurious toast notifications / state duplication).
if (res.chat_id && $chatId !== res.chat_id && $chatId === _chatId) {
await chatId.set(res.chat_id);
if (!$temporaryChatEnabled) {
window.history.replaceState(history.state, '', `/c/${res.chat_id}`);
currentChatPage.set(1);
await chats.set(await getChatList(localStorage.token, $currentChatPage));
if (!$temporaryChatEnabled) {
window.history.replaceState(history.state, '', `/c/${res.chat_id}`);
await refreshChatList(localStorage.token);
// Persist chat-level params (system prompt, advanced
// Persist chat-level params (system prompt, advanced
// params) that the backend doesn't receive in the
// chat completion request. Files are now persisted
// by the backend at chat creation time.
@ -2938,15 +2930,14 @@
_chatId = chat.id;
await chatId.set(_chatId);
window.history.replaceState(history.state, '', `/c/${_chatId}`);
window.history.replaceState(history.state, '', `/c/${_chatId}`);
await tick();
await tick();
await chats.set(await getChatList(localStorage.token, $currentChatPage));
currentChatPage.set(1);
await refreshChatList(localStorage.token);
selectedFolder.set(null);
} else {
selectedFolder.set(null);
} else {
_chatId = `local:${$socket?.id}`; // Use socket id for temporary chat
await chatId.set(_chatId);
}
@ -3021,28 +3012,24 @@
}
);
if (res) {
currentChatPage.set(1);
await chats.set(await getChatList(localStorage.token, $currentChatPage));
await pinnedChats.set(await getPinnedChatList(localStorage.token));
if (res) {
await refreshChatList(localStorage.token, { refreshPinned: true });
toast.success($i18n.t('Chat moved successfully'));
}
toast.success($i18n.t('Chat moved successfully'));
}
} else {
toast.error($i18n.t('Failed to move chat'));
}
};
const archiveChatHandler = async (id: string) => {
try {
await archiveChatById(localStorage.token, id);
currentChatPage.set(1);
initNewChat();
await goto('/');
chats.set(await getChatList(localStorage.token, $currentChatPage));
pinnedChats.set(await getPinnedChatList(localStorage.token));
toast.success($i18n.t('Chat archived.'));
} catch (error) {
const archiveChatHandler = async (id: string) => {
try {
await archiveChatById(localStorage.token, id);
initNewChat();
await goto('/');
await refreshChatList(localStorage.token, { refreshPinned: true });
toast.success($i18n.t('Chat archived.'));
} catch (error) {
console.error('Error archiving chat:', error);
toast.error($i18n.t('Failed to archive chat.'));
}
@ -3071,15 +3058,13 @@
if (!id) return;
try {
const res = await deleteChatById(localStorage.token, id);
if (res) {
currentChatPage.set(1);
initNewChat();
await goto('/');
chats.set(await getChatList(localStorage.token, $currentChatPage));
pinnedChats.set(await getPinnedChatList(localStorage.token));
allTags.set(await getAllTags(localStorage.token));
toast.success($i18n.t('Chat deleted.'));
const res = await deleteChatById(localStorage.token, id);
if (res) {
initNewChat();
await goto('/');
await refreshChatList(localStorage.token, { refreshPinned: true });
allTags.set(await getAllTags(localStorage.token));
toast.success($i18n.t('Chat deleted.'));
}
} catch (error) {
console.error('Error deleting chat:', error);
@ -3229,12 +3214,12 @@
null
);
if (savedChat) {
temporaryChatEnabled.set(false);
chatId.set(savedChat.id);
chats.set(await getChatList(localStorage.token, $currentChatPage));
if (savedChat) {
temporaryChatEnabled.set(false);
chatId.set(savedChat.id);
await refreshChatList(localStorage.token);
await goto(`/c/${savedChat.id}`);
await goto(`/c/${savedChat.id}`);
toast.success($i18n.t('Conversation saved successfully'));
}
} catch (error) {

View file

@ -106,7 +106,7 @@
accept="image/*"
capture="environment"
on:change={handleFileChange}
style="display: none;"
class="hidden"
/>
<Dropdown
@ -136,7 +136,7 @@
className="w-full"
>
<button
class="flex w-full gap-2 items-center px-3 py-1.5 text-sm select-none cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800/50 rounded-xl {!fileUploadEnabled
class="flex w-full gap-2 items-center h-[1.6875rem] px-2 text-[13px] font-normal select-none cursor-pointer hover:bg-gray-50/40 dark:hover:bg-gray-800/40 rounded-xl {!fileUploadEnabled
? 'opacity-50'
: ''}"
type="button"
@ -162,7 +162,7 @@
className="w-full"
>
<button
class="flex w-full gap-2 items-center px-3 py-1.5 text-sm select-none cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800/50 rounded-xl {!fileUploadEnabled
class="flex w-full gap-2 items-center h-[1.6875rem] px-2 text-[13px] font-normal select-none cursor-pointer hover:bg-gray-50/40 dark:hover:bg-gray-800/40 rounded-xl {!fileUploadEnabled
? 'opacity-50'
: ''}"
type="button"
@ -193,7 +193,7 @@
className="w-full"
>
<button
class="flex w-full gap-2 items-center px-3 py-1.5 text-sm select-none cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl {!webUploadEnabled
class="flex w-full gap-2 items-center h-[1.6875rem] px-2 text-[13px] font-normal select-none cursor-pointer hover:bg-gray-50/40 dark:hover:bg-gray-800/40 rounded-xl {!webUploadEnabled
? 'opacity-50'
: ''}"
type="button"
@ -218,7 +218,7 @@
className="w-full"
>
<button
class="flex gap-2 w-full items-center px-3 py-1.5 text-sm select-none cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800/50 rounded-xl {!fileUploadEnabled
class="flex gap-2 w-full items-center h-[1.6875rem] px-2 text-[13px] font-normal select-none cursor-pointer hover:bg-gray-50/40 dark:hover:bg-gray-800/40 rounded-xl {!fileUploadEnabled
? 'opacity-50'
: ''}"
on:click={() => {
@ -251,7 +251,7 @@
className="w-full"
>
<button
class="flex gap-2 w-full items-center px-3 py-1.5 text-sm select-none cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800/50 rounded-xl {!fileUploadEnabled
class="flex gap-2 w-full items-center h-[1.6875rem] px-2 text-[13px] font-normal select-none cursor-pointer hover:bg-gray-50/40 dark:hover:bg-gray-800/40 rounded-xl {!fileUploadEnabled
? 'opacity-50'
: ''}"
on:click={() => {
@ -282,7 +282,7 @@
className="w-full"
>
<button
class="flex gap-2 w-full items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800/50 rounded-xl {!fileUploadEnabled
class="flex gap-2 w-full items-center h-[1.6875rem] px-2 text-[13px] font-normal cursor-pointer hover:bg-gray-50/40 dark:hover:bg-gray-800/40 rounded-xl {!fileUploadEnabled
? 'opacity-50'
: ''}"
on:click={() => {
@ -312,7 +312,7 @@
className="w-full"
>
<button
class="flex gap-2 w-full items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800/50 rounded-xl {!fileUploadEnabled
class="flex gap-2 w-full items-center h-[1.6875rem] px-2 text-[13px] font-normal cursor-pointer hover:bg-gray-50/40 dark:hover:bg-gray-800/40 rounded-xl {!fileUploadEnabled
? 'opacity-50'
: ''}"
on:click={() => {
@ -336,14 +336,14 @@
{#if fileUploadEnabled}
{#if $config?.features?.enable_google_drive_integration}
<button
class="flex w-full gap-2 items-center px-3 py-1.5 text-sm select-none cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800/50 rounded-xl"
class="flex w-full gap-2 items-center h-[1.6875rem] px-2 text-[13px] font-normal select-none cursor-pointer hover:bg-gray-50/40 dark:hover:bg-gray-800/40 rounded-xl"
type="button"
on:click={() => {
uploadGoogleDriveHandler();
show = false;
}}
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 87.3 78" class="w-4">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 87.3 78" class="size-3.5 shrink-0">
<path
d="m6.6 66.85 3.85 6.65c.8 1.4 1.95 2.5 3.3 3.3l13.75-23.8h-27.5c0 1.55.4 3.1 1.2 4.5z"
fill="#0066da"
@ -375,7 +375,7 @@
{#if $config?.features?.enable_onedrive_integration && ($config?.features?.enable_onedrive_personal || $config?.features?.enable_onedrive_business)}
<button
class="flex gap-2 w-full items-center px-3 py-1.5 text-sm select-none cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800/50 rounded-xl {!fileUploadEnabled
class="flex gap-2 w-full items-center h-[1.6875rem] px-2 text-[13px] font-normal select-none cursor-pointer hover:bg-gray-50/40 dark:hover:bg-gray-800/40 rounded-xl {!fileUploadEnabled
? 'opacity-50'
: ''}"
on:click={() => {
@ -385,7 +385,7 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 32 32"
class="size-4"
class="size-3.5"
fill="none"
>
<mask
@ -484,7 +484,7 @@
{:else if tab === 'knowledge'}
<div in:fly={{ x: 20, duration: 150 }}>
<button
class="flex w-full justify-between gap-2 items-center px-3 py-1.5 text-sm select-none cursor-pointer rounded-xl hover:bg-gray-50 dark:hover:bg-gray-800/50"
class="flex w-full justify-between gap-2 items-center h-[1.6875rem] px-2 text-[13px] font-normal select-none cursor-pointer rounded-xl hover:bg-gray-50/40 dark:hover:bg-gray-800/40"
on:click={() => {
tab = '';
}}
@ -503,7 +503,7 @@
{:else if tab === 'notes'}
<div in:fly={{ x: 20, duration: 150 }}>
<button
class="flex w-full justify-between gap-2 items-center px-3 py-1.5 text-sm select-none cursor-pointer rounded-xl hover:bg-gray-50 dark:hover:bg-gray-800/50"
class="flex w-full justify-between gap-2 items-center h-[1.6875rem] px-2 text-[13px] font-normal select-none cursor-pointer rounded-xl hover:bg-gray-50/40 dark:hover:bg-gray-800/40"
on:click={() => {
tab = '';
}}
@ -522,7 +522,7 @@
{:else if tab === 'files'}
<div in:fly={{ x: 20, duration: 150 }}>
<button
class="flex w-full justify-between gap-2 items-center px-3 py-1.5 text-sm select-none cursor-pointer rounded-xl hover:bg-gray-50 dark:hover:bg-gray-800/50"
class="flex w-full justify-between gap-2 items-center h-[1.6875rem] px-2 text-[13px] font-normal select-none cursor-pointer rounded-xl hover:bg-gray-50/40 dark:hover:bg-gray-800/40"
on:click={() => {
tab = '';
}}
@ -541,7 +541,7 @@
{:else if tab === 'chats'}
<div in:fly={{ x: 20, duration: 150 }}>
<button
class="flex w-full justify-between gap-2 items-center px-3 py-1.5 text-sm select-none cursor-pointer rounded-xl hover:bg-gray-50 dark:hover:bg-gray-800/50"
class="flex w-full justify-between gap-2 items-center h-[1.6875rem] px-2 text-[13px] font-normal select-none cursor-pointer rounded-xl hover:bg-gray-50/40 dark:hover:bg-gray-800/40"
on:click={() => {
tab = '';
}}
@ -560,7 +560,7 @@
{:else if tab === 'microsoft_onedrive'}
<div in:fly={{ x: 20, duration: 150 }}>
<button
class="flex w-full justify-between gap-2 items-center px-3 py-1.5 text-sm select-none cursor-pointer rounded-xl hover:bg-gray-50 dark:hover:bg-gray-800/50"
class="flex w-full justify-between gap-2 items-center h-[1.6875rem] px-2 text-[13px] font-normal select-none cursor-pointer rounded-xl hover:bg-gray-50/40 dark:hover:bg-gray-800/40"
on:click={() => {
tab = '';
}}
@ -576,7 +576,7 @@
{#if $config?.features?.enable_onedrive_personal}
<button
class="flex w-full gap-2 items-center px-3 py-1.5 text-sm select-none cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800/50 rounded-xl text-left"
class="flex w-full gap-2 items-center h-[1.6875rem] px-2 text-[13px] font-normal select-none cursor-pointer hover:bg-gray-50/40 dark:hover:bg-gray-800/40 rounded-xl text-left"
type="button"
on:click={() => {
uploadOneDriveHandler('personal');
@ -591,18 +591,15 @@
{#if $config?.features?.enable_onedrive_business}
<button
class="flex w-full gap-2 items-center px-3 py-1.5 text-sm select-none cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800/50 rounded-xl text-left"
class="flex w-full gap-2 items-center h-[1.6875rem] px-2 text-[13px] font-normal select-none cursor-pointer hover:bg-gray-50/40 dark:hover:bg-gray-800/40 rounded-xl text-left"
type="button"
on:click={() => {
uploadOneDriveHandler('organizations');
show = false;
}}
>
<div class="flex flex-col">
<div class="line-clamp-1">
{$i18n.t('Microsoft OneDrive (work/school)')}
</div>
<div class="text-xs text-gray-500">{$i18n.t('Includes SharePoint')}</div>
<div class="line-clamp-1">
{$i18n.t('Microsoft OneDrive (work/school)')}
</div>
</button>
{/if}

View file

@ -74,9 +74,9 @@
<div class="flex flex-col gap-0.5">
{#each items as item, idx}
<button
class=" px-2.5 py-1 rounded-xl w-full text-left flex justify-between items-center text-sm {idx ===
class=" h-[1.6875rem] px-2 rounded-xl w-full text-left flex justify-between items-center text-[13px] font-normal {idx ===
selectedIdx
? ' bg-gray-50 dark:bg-gray-800 dark:text-gray-100 selected-command-option-button'
? ' bg-gray-50/40 dark:bg-gray-800/40 dark:text-gray-100 selected-command-option-button'
: ''}"
type="button"
on:click={() => {

View file

@ -65,9 +65,9 @@
<div class="flex flex-col gap-0.5">
{#each items as item, idx}
<button
class=" px-2.5 py-1 rounded-xl w-full text-left flex justify-between items-center text-sm {idx ===
class=" h-[1.6875rem] px-2 rounded-xl w-full text-left flex justify-between items-center text-[13px] font-normal {idx ===
selectedIdx
? ' bg-gray-50 dark:bg-gray-800 dark:text-gray-100 selected-command-option-button'
? ' bg-gray-50/40 dark:bg-gray-800/40 dark:text-gray-100 selected-command-option-button'
: ''}"
type="button"
on:click={() => {
@ -85,7 +85,7 @@
>
<div class="text-black dark:text-gray-100 flex items-center gap-1.5 overflow-hidden">
<Tooltip content={$i18n.t('File')} placement="top">
<DocumentPage className="size-4 shrink-0" />
<DocumentPage className="size-3.5 shrink-0" />
</Tooltip>
<Tooltip content={item?.name} placement="top-start">

View file

@ -166,9 +166,9 @@
{:else}
{#each items as item, idx (item.id)}
<div
class=" px-2.5 py-1 rounded-xl w-full text-left flex justify-between items-center text-sm {idx ===
class=" h-[1.6875rem] px-2 rounded-xl w-full text-left flex justify-between items-center text-[13px] font-normal hover:bg-gray-50/40 hover:text-gray-900 dark:hover:bg-gray-800/40 dark:hover:text-gray-100 {idx ===
selectedIdx
? ' bg-gray-50 dark:bg-gray-800 dark:text-gray-100 selected-command-option-button'
? ' bg-gray-50/40 dark:bg-gray-800/40 dark:text-gray-100 selected-command-option-button'
: ''}"
>
<button
@ -192,7 +192,7 @@
>
<div class="w-full text-left text-black dark:text-gray-100 flex items-center gap-1">
<Tooltip content={$i18n.t('Collection')} placement="top">
<Database className="size-4" />
<Database className="size-3.5" />
</Tooltip>
<Tooltip
@ -200,7 +200,7 @@
placement="top-start"
className="flex flex-1 min-w-0"
>
<div class="line-clamp-1 flex-1 text-sm">
<div class="line-clamp-1 flex-1 text-[13px]">
{decodeString(item?.name)}
</div>
</Tooltip>
@ -241,7 +241,7 @@
{:else}
{#each selectedFileItems as file, fileIdx (file.id)}
<button
class=" px-2.5 py-1 rounded-xl w-full text-left flex justify-between items-center text-sm hover:bg-gray-50 hover:dark:bg-gray-800 hover:dark:text-gray-100"
class=" h-[1.6875rem] px-2 rounded-xl w-full text-left flex justify-between items-center text-[13px] font-normal hover:bg-gray-50/40 hover:text-gray-900 dark:hover:bg-gray-800/40 dark:hover:text-gray-100"
type="button"
on:click={() => {
console.log(file);
@ -254,11 +254,11 @@
>
<div class=" flex items-center gap-1.5">
<Tooltip content={$i18n.t('Collection')} placement="top">
<DocumentPage className="size-4" />
<DocumentPage className="size-3.5" />
</Tooltip>
<Tooltip content={decodeString(file?.meta?.name)} placement="top-start">
<div class="line-clamp-1 flex-1 text-sm">
<div class="line-clamp-1 flex-1 text-[13px]">
{decodeString(file?.meta?.name)}
</div>
</Tooltip>

View file

@ -73,9 +73,9 @@
<div class="flex flex-col gap-0.5">
{#each items as item, idx}
<button
class=" px-2.5 py-1 rounded-xl w-full text-left flex justify-between items-center text-sm {idx ===
class=" h-[1.6875rem] px-2 rounded-xl w-full text-left flex justify-between items-center text-[13px] font-normal {idx ===
selectedIdx
? ' bg-gray-50 dark:bg-gray-800 dark:text-gray-100 selected-command-option-button'
? ' bg-gray-50/40 dark:bg-gray-800/40 dark:text-gray-100 selected-command-option-button'
: ''}"
type="button"
on:click={() => {
@ -93,7 +93,7 @@
>
<div class="text-black dark:text-gray-100 flex items-center gap-1.5">
<Tooltip content={$i18n.t('Note')} placement="top">
<PageEdit className="size-4" />
<PageEdit className="size-3.5" />
</Tooltip>
<Tooltip content={item.description || decodeString(item?.name)} placement="top-start">

View file

@ -149,7 +149,7 @@
{#if tools}
{#if Object.keys(tools).length > 0}
<button
class="flex w-full justify-between gap-2 items-center px-3 py-1.5 text-sm cursor-pointer rounded-xl hover:bg-gray-50 dark:hover:bg-gray-800/50"
class="flex w-full justify-between gap-2 items-center h-[1.6875rem] px-2 text-[13px] font-normal cursor-pointer rounded-xl hover:bg-gray-50/40 dark:hover:bg-gray-800/40"
on:click={() => {
tab = 'tools';
}}
@ -171,12 +171,12 @@
{#if skills && Object.keys(skills).length > 0}
<button
class="flex w-full justify-between gap-2 items-center px-3 py-1.5 text-sm cursor-pointer rounded-xl hover:bg-gray-50 dark:hover:bg-gray-800/50"
class="flex w-full justify-between gap-2 items-center h-[1.6875rem] px-2 text-[13px] font-normal cursor-pointer rounded-xl hover:bg-gray-50/40 dark:hover:bg-gray-800/40"
on:click={() => {
tab = 'skills';
}}
>
<Keyframes className="size-4" strokeWidth="1.75" />
<Keyframes className="size-3.5" strokeWidth="1.75" />
<div class="flex items-center w-full justify-between">
<div class=" line-clamp-1">
@ -200,7 +200,7 @@
{#each toggleFilters.sort( (a, b) => a.name.localeCompare( b.name, undefined, { sensitivity: 'base' } ) ) as filter, filterIdx (filter.id)}
<Tooltip content={filter?.description} placement="top-start">
<button
class="flex w-full justify-between gap-2 items-center px-3 py-1.5 text-sm cursor-pointer rounded-xl hover:bg-gray-50 dark:hover:bg-gray-800/50"
class="flex w-full justify-between gap-2 items-center h-[1.6875rem] px-2 text-[13px] font-normal cursor-pointer rounded-xl hover:bg-gray-50/40 dark:hover:bg-gray-800/40"
on:click={() => {
if (selectedFilterIds.includes(filter.id)) {
selectedFilterIds = selectedFilterIds.filter((id) => id !== filter.id);
@ -213,7 +213,7 @@
<div class="flex flex-1 gap-2 items-center">
<div class="shrink-0">
{#if filter?.icon}
<div class="size-4 items-center flex justify-center">
<div class="size-3.5 items-center flex justify-center">
<img
src={filter.icon}
class="size-3.5 {filter.icon.includes('data:image/svg')
@ -224,7 +224,7 @@
/>
</div>
{:else}
<Sparkles className="size-4" strokeWidth="1.75" />
<Sparkles className="size-3.5" strokeWidth="1.75" />
{/if}
</div>
@ -270,7 +270,7 @@
{#if showWebSearchButton}
<Tooltip content={$i18n.t('Search the internet')} placement="top-start">
<button
class="flex w-full justify-between gap-2 items-center px-3 py-1.5 text-sm cursor-pointer rounded-xl hover:bg-gray-50 dark:hover:bg-gray-800/50"
class="flex w-full justify-between gap-2 items-center h-[1.6875rem] px-2 text-[13px] font-normal cursor-pointer rounded-xl hover:bg-gray-50/40 dark:hover:bg-gray-800/40"
aria-pressed={webSearchEnabled}
aria-label={webSearchEnabled
? $i18n.t('Disable Web Search')
@ -306,7 +306,7 @@
{#if showImageGenerationButton}
<Tooltip content={$i18n.t('Generate an image')} placement="top-start">
<button
class="flex w-full justify-between gap-2 items-center px-3 py-1.5 text-sm cursor-pointer rounded-xl hover:bg-gray-50 dark:hover:bg-gray-800/50"
class="flex w-full justify-between gap-2 items-center h-[1.6875rem] px-2 text-[13px] font-normal cursor-pointer rounded-xl hover:bg-gray-50/40 dark:hover:bg-gray-800/40"
aria-pressed={imageGenerationEnabled}
aria-label={imageGenerationEnabled
? $i18n.t('Disable Image Generation')
@ -318,7 +318,7 @@
<div class="flex-1 truncate">
<div class="flex flex-1 gap-2 items-center">
<div class="shrink-0">
<Photo className="size-4" strokeWidth="1.5" />
<Photo className="size-3.5" strokeWidth="1.5" />
</div>
<div class=" truncate">{$i18n.t('Image')}</div>
@ -341,7 +341,7 @@
{#if showCodeInterpreterButton}
<Tooltip content={$i18n.t('Execute code for analysis')} placement="top-start">
<button
class="flex w-full justify-between gap-2 items-center px-3 py-1.5 text-sm cursor-pointer rounded-xl hover:bg-gray-50 dark:hover:bg-gray-800/50"
class="flex w-full justify-between gap-2 items-center h-[1.6875rem] px-2 text-[13px] font-normal cursor-pointer rounded-xl hover:bg-gray-50/40 dark:hover:bg-gray-800/40"
aria-pressed={codeInterpreterEnabled}
aria-label={codeInterpreterEnabled
? $i18n.t('Disable Code Interpreter')
@ -376,7 +376,7 @@
{:else if tab === 'tools' && tools}
<div in:fly={{ x: 20, duration: 150 }}>
<button
class="flex w-full justify-between gap-2 items-center px-3 py-1.5 text-sm cursor-pointer rounded-xl hover:bg-gray-50 dark:hover:bg-gray-800/50"
class="flex w-full justify-between gap-2 items-center h-[1.6875rem] px-2 text-[13px] font-normal cursor-pointer rounded-xl hover:bg-gray-50/40 dark:hover:bg-gray-800/40"
on:click={() => {
tab = '';
}}
@ -393,7 +393,7 @@
{#each Object.keys(tools) as toolId}
<button
class="relative flex w-full justify-between gap-2 items-center px-3 py-1.5 text-sm cursor-pointer rounded-xl hover:bg-gray-50 dark:hover:bg-gray-800/50"
class="relative flex w-full justify-between gap-2 items-center h-[1.6875rem] px-2 text-[13px] font-normal cursor-pointer rounded-xl hover:bg-gray-50/40 dark:hover:bg-gray-800/40"
on:click={async (e) => {
if (!(tools[toolId]?.authenticated ?? true)) {
e.preventDefault();
@ -499,7 +499,7 @@
{:else if tab === 'skills' && skills}
<div in:fly={{ x: 20, duration: 150 }}>
<button
class="flex w-full justify-between gap-2 items-center px-3 py-1.5 text-sm cursor-pointer rounded-xl hover:bg-gray-50 dark:hover:bg-gray-800/50"
class="flex w-full justify-between gap-2 items-center h-[1.6875rem] px-2 text-[13px] font-normal cursor-pointer rounded-xl hover:bg-gray-50/40 dark:hover:bg-gray-800/40"
on:click={() => {
tab = '';
}}
@ -516,7 +516,7 @@
{#each Object.keys(skills) as skillId}
<button
class="relative flex w-full justify-between gap-2 items-center px-3 py-1.5 text-sm cursor-pointer rounded-xl hover:bg-gray-50 dark:hover:bg-gray-800/50"
class="relative flex w-full justify-between gap-2 items-center h-[1.6875rem] px-2 text-[13px] font-normal cursor-pointer rounded-xl hover:bg-gray-50/40 dark:hover:bg-gray-800/40"
on:click={async () => {
skills[skillId].enabled = !skills[skillId].enabled;
@ -534,7 +534,7 @@
<div class="flex flex-1 gap-2 items-center">
<Tooltip content={skills[skillId]?.name ?? ''} placement="top">
<div class="shrink-0">
<Keyframes className="size-4" strokeWidth="1.75" />
<Keyframes className="size-3.5" strokeWidth="1.75" />
</div>
</Tooltip>
<Tooltip content={skills[skillId]?.description ?? ''} placement="top-start">

View file

@ -95,7 +95,7 @@
<Tooltip content={$i18n.t('Terminal')} placement="top">
<button
type="button"
class="flex items-center gap-1.5 translate-y-[1px] hover:bg-gray-50 dark:hover:bg-gray-850 text-sm transition rounded-lg cursor-pointer {$selectedTerminalId &&
class="flex items-center gap-1.5 translate-y-[1px] hover:bg-gray-50/40 dark:hover:bg-gray-800/40 text-[13px] transition rounded-lg cursor-pointer {$selectedTerminalId &&
selectedLabel
? ' px-2.5 py-1 '
: ' p-2 opacity-50'}"
@ -116,7 +116,7 @@
{#if directTerminals.length > 0 && ($user?.role === 'admin' || ($user?.permissions?.features?.direct_tool_servers ?? true))}
<div class="flex items-center justify-between px-3 py-1">
<span
class="text-[10px] font-medium text-gray-400 dark:text-gray-500 uppercase tracking-wider"
class="text-[10px] font-normal text-gray-400 dark:text-gray-500 uppercase tracking-wider"
>
{$i18n.t('Direct')}
</span>
@ -146,14 +146,14 @@
{#each directTerminals as terminal}
<button
type="button"
class="flex w-full justify-between gap-2 items-center px-3 py-1.5 text-sm cursor-pointer rounded-xl {$selectedTerminalId ===
class="flex w-full justify-between gap-2 items-center h-[1.6875rem] px-2 text-[13px] font-normal cursor-pointer rounded-xl {$selectedTerminalId ===
terminal.url
? 'bg-gray-50 dark:bg-gray-800/50'
: 'hover:bg-gray-50 dark:hover:bg-gray-800/50'}"
? 'bg-gray-50/40 dark:bg-gray-800/40'
: 'hover:bg-gray-50/40 dark:hover:bg-gray-800/40'}"
on:click={() => selectDirect(terminal)}
>
<div class="flex flex-1 gap-2 items-center truncate">
<Cloud className="size-4 shrink-0" strokeWidth="2" />
<Cloud className="size-3.5 shrink-0" strokeWidth="2" />
<span class="truncate"
>{terminal.name || terminal.url.replace(/^https?:\/\//, '')}</span
>
@ -164,7 +164,7 @@
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="size-4"
class="size-3.5"
>
<path
fill-rule="evenodd"
@ -176,17 +176,13 @@
{/if}
</button>
{/each}
{#if directTerminals.length > 0 && systemTerminals.length > 0}
<hr class="border-gray-100 dark:border-gray-800 my-1" />
{/if}
{/if}
<!-- System terminals -->
{#if systemTerminals.length > 0}
<div class="flex items-center justify-between px-3 py-1">
<span
class="text-[10px] font-medium text-gray-400 dark:text-gray-500 uppercase tracking-wider"
class="text-[10px] font-normal text-gray-400 dark:text-gray-500 uppercase tracking-wider"
>
{$i18n.t('System')}
</span>
@ -218,14 +214,14 @@
{#each systemTerminals as terminal}
<button
type="button"
class="flex w-full justify-between gap-2 items-center px-3 py-1.5 text-sm cursor-pointer rounded-xl {$selectedTerminalId ===
class="flex w-full justify-between gap-2 items-center h-[1.6875rem] px-2 text-[13px] font-normal cursor-pointer rounded-xl {$selectedTerminalId ===
terminal.id
? 'bg-gray-50 dark:bg-gray-800/50'
: 'hover:bg-gray-50 dark:hover:bg-gray-800/50'}"
? 'bg-gray-50/40 dark:bg-gray-800/40'
: 'hover:bg-gray-50/40 dark:hover:bg-gray-800/40'}"
on:click={() => selectSystem(terminal)}
>
<div class="flex flex-1 gap-2 items-center truncate">
<Cloud className="size-4 shrink-0" strokeWidth="2" />
<Cloud className="size-3.5 shrink-0" strokeWidth="2" />
<span class="truncate">{terminal.name || $i18n.t('Terminal')}</span>
</div>
{#if $selectedTerminalId === terminal.id}
@ -234,7 +230,7 @@
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="size-4"
class="size-3.5"
>
<path
fill-rule="evenodd"

View file

@ -1,19 +1,18 @@
<script lang="ts">
import { v4 as uuidv4 } from 'uuid';
import {
chats,
config,
settings,
user as _user,
mobile,
currentChatPage,
temporaryChatEnabled
} from '$lib/stores';
import { refreshChatList } from '$lib/stores/chat-list';
import { tick, getContext, onMount, onDestroy, createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
import { toast } from 'svelte-sonner';
import { deleteChatMessageById, getChatList, updateChatById } from '$lib/apis/chats';
import { deleteChatMessageById, updateChatById } from '$lib/apis/chats';
import { copyToClipboard, extractCurlyBraceWords } from '$lib/utils';
import Message from './Messages/Message.svelte';
@ -183,8 +182,7 @@
history = history;
}
currentChatPage.set(1);
await chats.set(await getChatList(localStorage.token, $currentChatPage));
await refreshChatList(localStorage.token);
}
};
@ -484,8 +482,7 @@
history = res.chat.history;
}
currentChatPage.set(1);
await chats.set(await getChatList(localStorage.token, $currentChatPage));
await refreshChatList(localStorage.token);
}
};

View file

@ -8,7 +8,6 @@
const dispatch = createEventDispatcher();
import { getChatList } from '$lib/apis/chats';
import { updateFolderById } from '$lib/apis/folders';
import {
@ -16,10 +15,9 @@
user,
models as _models,
temporaryChatEnabled,
selectedFolder,
chats,
currentChatPage
selectedFolder
} from '$lib/stores';
import { refreshChatList } from '$lib/stores/chat-list';
import { sanitizeResponseContent, extractCurlyBraceWords } from '$lib/utils';
import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
@ -105,12 +103,10 @@
folder={$selectedFolder}
readOnly={folderReadOnly}
onUpdate={async (folder) => {
await chats.set(await getChatList(localStorage.token, $currentChatPage));
currentChatPage.set(1);
await refreshChatList(localStorage.token);
}}
onDelete={async () => {
await chats.set(await getChatList(localStorage.token, $currentChatPage));
currentChatPage.set(1);
await refreshChatList(localStorage.token);
selectedFolder.set(null);
}}

View file

@ -4,20 +4,15 @@
import {
chatId,
chats,
user,
settings,
scrollPaginationEnabled,
currentChatPage,
pinnedChats
settings
} from '$lib/stores';
import { refreshChatList } from '$lib/stores/chat-list';
import {
archiveAllChats,
deleteAllChats,
getAllChats,
getChatList,
getPinnedChatList,
importChats
} from '$lib/apis/chats';
import { getImportOrigin, convertOpenAIChats } from '$lib/utils';
@ -96,10 +91,7 @@
toast.success(`Successfully imported ${res.length} chats.`);
}
currentChatPage.set(1);
await chats.set(await getChatList(localStorage.token, $currentChatPage));
pinnedChats.set(await getPinnedChatList(localStorage.token));
scrollPaginationEnabled.set(true);
await refreshChatList(localStorage.token, { refreshPinned: true });
};
const exportChats = async () => {
@ -115,10 +107,7 @@
toast.error(`${error}`);
});
currentChatPage.set(1);
await chats.set(await getChatList(localStorage.token, $currentChatPage));
pinnedChats.set([]);
scrollPaginationEnabled.set(true);
await refreshChatList(localStorage.token, { clearPinned: true });
};
const deleteAllChatsHandler = async () => {
@ -127,16 +116,11 @@
toast.error(`${error}`);
});
currentChatPage.set(1);
await chats.set(await getChatList(localStorage.token, $currentChatPage));
scrollPaginationEnabled.set(true);
await refreshChatList(localStorage.token);
};
const handleArchivedChatsChange = async () => {
currentChatPage.set(1);
await chats.set(await getChatList(localStorage.token, $currentChatPage));
scrollPaginationEnabled.set(true);
await refreshChatList(localStorage.token);
};
</script>

View file

@ -3,18 +3,10 @@
addTagById,
deleteTagById,
getAllTags,
getChatList,
getChatListByTagName,
getTagsById,
updateChatById
} from '$lib/apis/chats';
import {
tags as _tags,
chats,
pinnedChats,
currentChatPage,
scrollPaginationEnabled
} from '$lib/stores';
import { tags as _tags } from '$lib/stores';
import { createEventDispatcher, onMount } from 'svelte';
const dispatch = createEventDispatcher();

View file

@ -4,7 +4,7 @@
</script>
<div
class={`rounded-xl! p-0.5! border border-gray-100 dark:border-gray-800 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg ${className} [&_button:hover]:bg-transparent! [&_a:hover]:bg-transparent! [&_button:hover]:text-gray-900 dark:[&_button:hover]:text-gray-100 [&_a:hover]:text-gray-900 dark:[&_a:hover]:text-gray-100 [&>button]:flex [&>button]:w-full [&>button]:h-[1.6875rem]! [&>button]:items-center [&>button]:gap-2! [&>button]:rounded-xl! [&>button]:bg-transparent [&>button]:px-2! [&>button]:text-[13px]! [&>button]:font-normal! [&>a]:flex [&>a]:w-full [&>a]:h-[1.6875rem]! [&>a]:items-center [&>a]:gap-2! [&>a]:rounded-xl! [&>a]:bg-transparent [&>a]:px-2! [&>a]:text-[13px]! [&>a]:font-normal! [&_button_svg]:size-3.5! [&_a_svg]:size-3.5! [&>hr]:my-0.5 [&>hr]:mx-1`}
class={`rounded-xl! p-0.5! border border-gray-100 dark:border-gray-800 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg ${className} [&_button:hover]:bg-gray-50/40! dark:[&_button:hover]:bg-gray-800/40! [&_a:hover]:bg-gray-50/40! dark:[&_a:hover]:bg-gray-800/40! [&_button:hover]:text-gray-900 dark:[&_button:hover]:text-gray-100 [&_a:hover]:text-gray-900 dark:[&_a:hover]:text-gray-100 [&>button]:flex [&>button]:w-full [&>button]:h-[1.6875rem]! [&>button]:items-center [&>button]:gap-2! [&>button]:rounded-xl! [&>button]:bg-transparent [&>button]:px-2! [&>button]:text-[13px]! [&>button]:font-normal! [&>a]:flex [&>a]:w-full [&>a]:h-[1.6875rem]! [&>a]:items-center [&>a]:gap-2! [&>a]:rounded-xl! [&>a]:bg-transparent [&>a]:px-2! [&>a]:text-[13px]! [&>a]:font-normal! [&_button_svg]:size-3.5! [&_a_svg]:size-3.5! [&>hr]:my-0.5 [&>hr]:mx-1`}
{style}
>
<slot />

View file

@ -31,7 +31,7 @@
triggerClass={className
? className
: 'flex h-8 shrink-0 items-center gap-1.5 rounded-xl bg-transparent px-1.5 text-[13px] font-normal text-gray-700 transition placeholder-gray-400 outline-hidden hover:text-gray-900 focus:outline-hidden dark:text-gray-200 dark:hover:text-gray-100'}
itemClass="flex h-[1.6875rem] w-full cursor-pointer items-center gap-2 rounded-xl bg-transparent px-2 text-[13px] hover:text-gray-900 dark:hover:text-gray-100"
itemClass="flex h-[1.6875rem] w-full cursor-pointer items-center gap-2 rounded-xl bg-transparent px-2 text-[13px] hover:bg-gray-50/40 hover:text-gray-900 dark:hover:bg-gray-800/40 dark:hover:text-gray-100"
onChange={(v) => {
onChange(v);
}}

View file

@ -29,7 +29,7 @@
/** CSS classes for each item button */
export let itemClass =
'flex h-[1.6875rem] w-full cursor-pointer items-center gap-2 rounded-xl bg-transparent px-2 text-[13px] hover:text-gray-900 dark:hover:text-gray-100';
'flex h-[1.6875rem] w-full cursor-pointer items-center gap-2 rounded-xl bg-transparent px-2 text-[13px] hover:bg-gray-50/40 hover:text-gray-900 dark:hover:bg-gray-800/40 dark:hover:text-gray-100';
/** Alignment of the dropdown: 'start' | 'end' */
export let align = 'start';

View file

@ -14,7 +14,6 @@
archiveChatById,
updateChatById,
updateChatFolderIdById,
getPinnedChatList,
getAllTags
} from '$lib/apis/chats';
import Spinner from '../common/Spinner.svelte';
@ -28,12 +27,10 @@
import {
config,
user,
chats,
chatId as currentChatId,
pinnedChats,
currentChatPage,
tags
} from '$lib/stores';
import { refreshChatList } from '$lib/stores/chat-list';
import Messages from '../chat/Messages.svelte';
import { goto } from '$app/navigation';
import PencilSquare from '../icons/PencilSquare.svelte';
@ -73,9 +70,7 @@
let generating = false;
const refreshSidebar = async () => {
currentChatPage.set(1);
await chats.set(await getChatList(localStorage.token, $currentChatPage));
await pinnedChats.set(await getPinnedChatList(localStorage.token));
await refreshChatList(localStorage.token, { refreshPinned: true });
};
const cloneChatHandler = async (id) => {

View file

@ -18,8 +18,6 @@
showArchivedChats,
pinnedChats,
pinnedNotes,
scrollPaginationEnabled,
currentChatPage,
temporaryChatEnabled,
channels,
socket,
@ -31,6 +29,7 @@
sidebarWidth,
activeChatIds
} from '$lib/stores';
import { loadNextChatListPage, refreshChatList } from '$lib/stores/chat-list';
import { onMount, getContext, tick, onDestroy } from 'svelte';
const i18n = getContext('i18n');
@ -38,9 +37,7 @@
$: canImportChats = $user?.role === 'admin' || ($user?.permissions?.chat?.import ?? true);
import {
getChatList,
getAllTags,
getPinnedChatList,
toggleChatPinnedStatusById,
getChatById,
updateChatFolderIdById,
@ -97,6 +94,7 @@
// Pagination variables
let chatListLoading = false;
let chatListReady = false;
let allChatsLoaded = false;
let showCreateFolderModal = false;
@ -323,9 +321,8 @@
const initChatList = async () => {
// Reset pagination variables
console.log('initChatList');
currentChatPage.set(1);
allChatsLoaded = false;
scrollPaginationEnabled.set(false);
chatListReady = false;
initFolders();
initSharedFolders();
@ -335,11 +332,6 @@
const _tags = await getAllTags(localStorage.token);
tags.set(_tags);
})(),
(async () => {
console.log('Init pinned chats');
const _pinnedChats = await getPinnedChatList(localStorage.token);
pinnedChats.set(_pinnedChats);
})(),
(async () => {
if (
$config?.features?.enable_notes &&
@ -352,29 +344,20 @@
})(),
(async () => {
console.log('Init chat list');
const _chats = await getChatList(localStorage.token, $currentChatPage);
await chats.set(_chats);
const result = await refreshChatList(localStorage.token, { refreshPinned: true });
if (result.accepted) {
allChatsLoaded = result.allLoaded;
chatListReady = true;
}
})()
]);
// Enable pagination
scrollPaginationEnabled.set(true);
};
const loadMoreChats = async () => {
chatListLoading = true;
currentChatPage.set($currentChatPage + 1);
let newChatList = [];
newChatList = await getChatList(localStorage.token, $currentChatPage);
// once the bottom of the list has been reached (no results) there is no need to continue querying
allChatsLoaded = newChatList.length === 0;
const existingIds = new Set(($chats ?? []).map((c) => c.id));
const uniqueNewChats = newChatList.filter((c) => !existingIds.has(c.id));
await chats.set([...($chats ? $chats : []), ...uniqueNewChats]);
const result = await loadNextChatListPage(localStorage.token);
allChatsLoaded = result.allLoaded;
chatListLoading = false;
};
@ -1573,7 +1556,7 @@
/>
{/each}
{#if $scrollPaginationEnabled && !allChatsLoaded}
{#if chatListReady && !allChatsLoaded}
<Loader
on:visible={(e) => {
if (!chatListLoading) {

View file

@ -15,26 +15,22 @@
deleteChatById,
getAllTags,
getChatById,
getChatList,
getChatListByTagName,
getPinnedChatList,
updateChatById,
updateChatFolderIdById
} from '$lib/apis/chats';
import {
chatId,
chatTitle as _chatTitle,
chats,
mobile,
pinnedChats,
showSidebar,
currentChatPage,
tags,
selectedFolder,
activeChatIds,
settings,
user
} from '$lib/stores';
import { refreshChatList } from '$lib/stores/chat-list';
import ChatMenu from './ChatMenu.svelte';
import DeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
@ -137,9 +133,7 @@
_chatTitle.set(title);
}
currentChatPage.set(1);
await chats.set(await getChatList(localStorage.token, $currentChatPage));
await pinnedChats.set(await getPinnedChatList(localStorage.token));
await refreshChatList(localStorage.token, { refreshPinned: true });
dispatch('change');
}
@ -165,9 +159,7 @@
if (res) {
goto(`/c/${res.id}`);
currentChatPage.set(1);
await chats.set(await getChatList(localStorage.token, $currentChatPage));
await pinnedChats.set(await getPinnedChatList(localStorage.token));
await refreshChatList(localStorage.token, { refreshPinned: true });
}
};
@ -231,9 +223,7 @@
);
if (res) {
currentChatPage.set(1);
await chats.set(await getChatList(localStorage.token, $currentChatPage));
await pinnedChats.set(await getPinnedChatList(localStorage.token));
await refreshChatList(localStorage.token, { refreshPinned: true });
dispatch('change');

View file

@ -22,7 +22,7 @@
{placeholder}
{align}
triggerClass="relative h-8 w-full flex items-center gap-0.5 px-1.5 py-1.5 bg-transparent rounded-xl text-[13px] font-normal text-gray-700 transition hover:text-gray-900 dark:text-gray-200 dark:hover:text-gray-100"
itemClass="flex h-[1.6875rem] w-full cursor-pointer items-center gap-2 rounded-xl bg-transparent px-2 text-[13px] capitalize hover:text-gray-900 dark:hover:text-gray-100"
itemClass="flex h-[1.6875rem] w-full cursor-pointer items-center gap-2 rounded-xl bg-transparent px-2 text-[13px] capitalize hover:bg-gray-50/40 hover:text-gray-900 dark:hover:bg-gray-800/40 dark:hover:text-gray-100"
onChange={() => onChange(value)}
>
<svelte:fragment slot="trigger" let:selectedLabel>

View file

@ -0,0 +1,96 @@
import { get, readonly, writable } from 'svelte/store';
import { getChatList, getPinnedChatList } from '$lib/apis/chats';
const chatsStore = writable(null);
const pinnedChatsStore = writable([]);
export const chats = readonly(chatsStore);
export const pinnedChats = readonly(pinnedChatsStore);
let currentPage = 1;
let paginationReady = false;
let requestGeneration = 0;
let allLoaded = false;
let loadingNextPage = false;
type RefreshChatListOptions = {
refreshPinned?: boolean;
clearPinned?: boolean;
};
type ChatListResult = {
accepted: boolean;
allLoaded: boolean;
};
export const refreshChatList = async (
token: string = '',
options: RefreshChatListOptions = {}
): Promise<ChatListResult> => {
const generation = ++requestGeneration;
paginationReady = false;
loadingNextPage = false;
const [nextChats, nextPinnedChats] = await Promise.all([
getChatList(token, 1),
options.refreshPinned && !options.clearPinned
? getPinnedChatList(token)
: Promise.resolve(undefined)
]);
if (generation !== requestGeneration) {
return { accepted: false, allLoaded };
}
chatsStore.set(nextChats);
currentPage = 1;
allLoaded = nextChats.length === 0;
if (options.clearPinned) {
pinnedChatsStore.set([]);
} else if (options.refreshPinned) {
pinnedChatsStore.set(nextPinnedChats ?? []);
}
paginationReady = true;
return { accepted: true, allLoaded };
};
export const loadNextChatListPage = async (token: string = ''): Promise<ChatListResult> => {
if (!paginationReady || allLoaded || loadingNextPage) {
return { accepted: false, allLoaded };
}
const generation = requestGeneration;
const nextPage = currentPage + 1;
loadingNextPage = true;
try {
const nextChats = await getChatList(token, nextPage);
if (generation !== requestGeneration) {
return { accepted: false, allLoaded };
}
allLoaded = nextChats.length === 0;
currentPage = nextPage;
const existingIds = new Set((get(chatsStore) ?? []).map((chat) => chat.id));
const uniqueChats = nextChats.filter((chat) => !existingIds.has(chat.id));
chatsStore.set([...(get(chatsStore) ?? []), ...uniqueChats]);
return { accepted: true, allLoaded };
} finally {
loadingNextPage = false;
}
};
export const resetChatListState = () => {
requestGeneration += 1;
currentPage = 1;
paginationReady = false;
allLoaded = false;
loadingNextPage = false;
chatsStore.set(null);
pinnedChatsStore.set([]);
};

View file

@ -58,8 +58,7 @@ export const chatTitle = writable('');
export const channels = writable([]);
export const channelId = writable(null);
export const chats = writable(null);
export const pinnedChats = writable([]);
export { chats, pinnedChats } from './chat-list';
export const pinnedNotes = writable([]);
export const tags = writable([]);
export const folders = writable([]);
@ -122,8 +121,6 @@ export type DesktopEvent = {
data?: any;
};
export const desktopEvent: Writable<DesktopEvent | null> = writable(null);
export const scrollPaginationEnabled = writable(false);
export const currentChatPage = writable(1);
export const isLastActiveTab = writable(true);
export const playingNotificationSound = writable(false);

View file

@ -21,8 +21,6 @@
socket,
socketConnected,
chatId,
chats,
currentChatPage,
tags,
temporaryChatEnabled,
isLastActiveTab,
@ -39,6 +37,7 @@
pyodideWorker,
desktopEvent
} from '$lib/stores';
import { refreshChatList } from '$lib/stores/chat-list';
import { getFileContentById } from '$lib/apis/files';
import { goto } from '$app/navigation';
import { page } from '$app/stores';
@ -53,7 +52,7 @@
import { executeToolServer, getBackendConfig, getModels, getVersion } from '$lib/apis';
import { getSessionUser, updateUserTimezone, userSignOut } from '$lib/apis/auths';
import { getAllTags, getChatList } from '$lib/apis/chats';
import { getAllTags } from '$lib/apis/chats';
import { chatCompletion } from '$lib/apis/openai';
import {
addOpenAIConnection,
@ -661,8 +660,7 @@
});
}
} else if (type === 'chat:title') {
currentChatPage.set(1);
await chats.set(await getChatList(localStorage.token, $currentChatPage));
await refreshChatList(localStorage.token);
} else if (type === 'chat:tags') {
tags.set(await getAllTags(localStorage.token));
}