mirror of
https://github.com/open-webui/open-webui.git
synced 2026-08-28 05:27:35 +00:00
refac
This commit is contained in:
parent
34813573fa
commit
6e14d0d627
22 changed files with 249 additions and 243 deletions
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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={() => {
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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 />
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
96
src/lib/stores/chat-list.ts
Normal file
96
src/lib/stores/chat-list.ts
Normal 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([]);
|
||||
};
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue