mirror of
https://github.com/open-webui/open-webui.git
synced 2025-12-12 04:15:25 +00:00
refac: styling
This commit is contained in:
parent
e2ce735d12
commit
b084613004
1 changed files with 382 additions and 376 deletions
|
|
@ -60,6 +60,7 @@
|
||||||
import Sidebar from '../icons/Sidebar.svelte';
|
import Sidebar from '../icons/Sidebar.svelte';
|
||||||
import PinnedModelList from './Sidebar/PinnedModelList.svelte';
|
import PinnedModelList from './Sidebar/PinnedModelList.svelte';
|
||||||
import Note from '../icons/Note.svelte';
|
import Note from '../icons/Note.svelte';
|
||||||
|
import { slide } from 'svelte/transition';
|
||||||
|
|
||||||
const BREAKPOINT = 768;
|
const BREAKPOINT = 768;
|
||||||
|
|
||||||
|
|
@ -495,8 +496,8 @@
|
||||||
{#if !$mobile}
|
{#if !$mobile}
|
||||||
<div
|
<div
|
||||||
class="{$showSidebar
|
class="{$showSidebar
|
||||||
? 'md:hidden'
|
? 'hidden'
|
||||||
: ''} py-2 px-1.5 flex flex-col justify-between text-black dark:text-white h-full border-e border-gray-50 dark:border-gray-850"
|
: ''} py-2 px-1.5 flex flex-col justify-between text-black dark:text-white h-full border-e border-gray-50 dark:border-gray-850 z-10"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="flex flex-col flex-1 cursor-[e-resize]"
|
class="flex flex-col flex-1 cursor-[e-resize]"
|
||||||
|
|
@ -657,305 +658,387 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div
|
{#if $showSidebar}
|
||||||
bind:this={navElement}
|
|
||||||
id="sidebar"
|
|
||||||
class="h-screen max-h-[100dvh] min-h-screen select-none {$showSidebar
|
|
||||||
? 'md:relative w-[260px] max-w-[260px] bg-gray-50 dark:bg-gray-950'
|
|
||||||
: 'w-[0px] md:relative md:w-[40px] bg-transparent'} {$isApp
|
|
||||||
? `ml-[4.5rem] md:ml-0 `
|
|
||||||
: 'transition-width transition-all duration-200 '} shrink-0 text-gray-900 dark:text-gray-200 text-sm fixed z-50 top-0 left-0 overflow-x-hidden
|
|
||||||
"
|
|
||||||
data-state={$showSidebar}
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
class=" my-auto flex flex-col justify-between h-screen max-h-[100dvh] w-[260px] overflow-x-hidden scrollbar-hidden z-50 {$showSidebar
|
bind:this={navElement}
|
||||||
? ''
|
id="sidebar"
|
||||||
: 'invisible'}"
|
class="h-screen max-h-[100dvh] min-h-screen select-none {$showSidebar
|
||||||
|
? 'bg-gray-50 dark:bg-gray-950 z-50'
|
||||||
|
: ' bg-transparent z-0 '} {$isApp
|
||||||
|
? `ml-[4.5rem] md:ml-0 `
|
||||||
|
: 'transition-[width] duration-300 '} shrink-0 text-gray-900 dark:text-gray-200 text-sm fixed top-0 left-0 overflow-x-hidden
|
||||||
|
"
|
||||||
|
transition:slide={{ duration: 200, axis: 'x' }}
|
||||||
|
data-state={$showSidebar}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="px-1.5 pt-2 pb-1.5 flex justify-between space-x-1 text-gray-600 dark:text-gray-400 sticky top-0 z-10 bg-gray-50 dark:bg-gray-950"
|
class=" my-auto flex flex-col justify-between h-screen max-h-[100dvh] w-[260px] overflow-x-hidden scrollbar-hidden z-50 {$showSidebar
|
||||||
|
? ''
|
||||||
|
: 'invisible'}"
|
||||||
>
|
>
|
||||||
<a
|
<div
|
||||||
class="flex items-center rounded-lg p-1.5 h-full justify-center hover:bg-gray-100 dark:hover:bg-gray-850 transition no-drag-region"
|
class="px-1.5 pt-2 pb-1.5 flex justify-between space-x-1 text-gray-600 dark:text-gray-400 sticky top-0 z-10 bg-gray-50 dark:bg-gray-950"
|
||||||
href="/"
|
|
||||||
draggable="false"
|
|
||||||
on:click={newChatHandler}
|
|
||||||
>
|
>
|
||||||
<img
|
|
||||||
crossorigin="anonymous"
|
|
||||||
src="{WEBUI_BASE_URL}/static/favicon.png"
|
|
||||||
class="sidebar-new-chat-icon size-6 rounded-full"
|
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="/" class="flex flex-1 px-1.5" on:click={newChatHandler}>
|
|
||||||
<div class=" self-center font-medium text-gray-850 dark:text-white font-primary">
|
|
||||||
{$WEBUI_NAME}
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
<Tooltip content={$showSidebar ? $i18n.t('Close Sidebar') : $i18n.t('Open Sidebar')}>
|
|
||||||
<button
|
|
||||||
class=" flex rounded-lg hover:bg-gray-100 dark:hover:bg-gray-850 transition cursor-[w-resize]"
|
|
||||||
on:click={() => {
|
|
||||||
showSidebar.set(!$showSidebar);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div class=" self-center p-1.5">
|
|
||||||
<Sidebar />
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pb-1.5">
|
|
||||||
<div class="px-[7px] flex justify-center text-gray-800 dark:text-gray-200">
|
|
||||||
<a
|
<a
|
||||||
id="sidebar-new-chat-button"
|
class="flex items-center rounded-lg p-1.5 h-full justify-center hover:bg-gray-100 dark:hover:bg-gray-850 transition no-drag-region"
|
||||||
class="grow flex items-center space-x-3 rounded-lg px-2 py-2 hover:bg-gray-100 dark:hover:bg-gray-900 transition outline-none"
|
|
||||||
href="/"
|
href="/"
|
||||||
draggable="false"
|
draggable="false"
|
||||||
on:click={newChatHandler}
|
on:click={newChatHandler}
|
||||||
>
|
>
|
||||||
<div class="self-center">
|
<img
|
||||||
<PencilSquare className=" size-4.5" strokeWidth="2" />
|
crossorigin="anonymous"
|
||||||
</div>
|
src="{WEBUI_BASE_URL}/static/favicon.png"
|
||||||
|
class="sidebar-new-chat-icon size-6 rounded-full"
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
|
||||||
<div class="flex self-center translate-y-[0.5px]">
|
<a href="/" class="flex flex-1 px-1.5" on:click={newChatHandler}>
|
||||||
<div class=" self-center text-sm font-primary">{$i18n.t('New Chat')}</div>
|
<div class=" self-center font-medium text-gray-850 dark:text-white font-primary">
|
||||||
|
{$WEBUI_NAME}
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
<Tooltip content={$showSidebar ? $i18n.t('Close Sidebar') : $i18n.t('Open Sidebar')}>
|
||||||
|
<button
|
||||||
|
class=" flex rounded-lg hover:bg-gray-100 dark:hover:bg-gray-850 transition cursor-[w-resize]"
|
||||||
|
on:click={() => {
|
||||||
|
showSidebar.set(!$showSidebar);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class=" self-center p-1.5">
|
||||||
|
<Sidebar />
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="px-[7px] flex justify-center text-gray-800 dark:text-gray-200">
|
<div class="pb-1.5">
|
||||||
<button
|
|
||||||
class="grow flex items-center space-x-3 rounded-lg px-2 py-2 hover:bg-gray-100 dark:hover:bg-gray-900 transition outline-none"
|
|
||||||
on:click={() => {
|
|
||||||
showSearch.set(true);
|
|
||||||
}}
|
|
||||||
draggable="false"
|
|
||||||
>
|
|
||||||
<div class="self-center">
|
|
||||||
<Search strokeWidth="2" className="size-4.5" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex self-center translate-y-[0.5px]">
|
|
||||||
<div class=" self-center text-sm font-primary">{$i18n.t('Search')}</div>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if ($config?.features?.enable_notes ?? false) && ($user?.role === 'admin' || ($user?.permissions?.features?.notes ?? true))}
|
|
||||||
<div class="px-[7px] flex justify-center text-gray-800 dark:text-gray-200">
|
<div class="px-[7px] flex justify-center text-gray-800 dark:text-gray-200">
|
||||||
<a
|
<a
|
||||||
class="grow flex items-center space-x-3 rounded-lg px-2 py-2 hover:bg-gray-100 dark:hover:bg-gray-900 transition"
|
id="sidebar-new-chat-button"
|
||||||
href="/notes"
|
class="grow flex items-center space-x-3 rounded-lg px-2 py-2 hover:bg-gray-100 dark:hover:bg-gray-900 transition outline-none"
|
||||||
on:click={itemClickHandler}
|
href="/"
|
||||||
draggable="false"
|
draggable="false"
|
||||||
|
on:click={newChatHandler}
|
||||||
>
|
>
|
||||||
<div class="self-center">
|
<div class="self-center">
|
||||||
<Note className="size-4.5" strokeWidth="2" />
|
<PencilSquare className=" size-4.5" strokeWidth="2" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex self-center translate-y-[0.5px]">
|
<div class="flex self-center translate-y-[0.5px]">
|
||||||
<div class=" self-center text-sm font-primary">{$i18n.t('Notes')}</div>
|
<div class=" self-center text-sm font-primary">{$i18n.t('New Chat')}</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if $user?.role === 'admin' || $user?.permissions?.workspace?.models || $user?.permissions?.workspace?.knowledge || $user?.permissions?.workspace?.prompts || $user?.permissions?.workspace?.tools}
|
|
||||||
<div class="px-[7px] flex justify-center text-gray-800 dark:text-gray-200">
|
<div class="px-[7px] flex justify-center text-gray-800 dark:text-gray-200">
|
||||||
<a
|
<button
|
||||||
class="grow flex items-center space-x-3 rounded-lg px-2 py-2 hover:bg-gray-100 dark:hover:bg-gray-900 transition"
|
class="grow flex items-center space-x-3 rounded-lg px-2 py-2 hover:bg-gray-100 dark:hover:bg-gray-900 transition outline-none"
|
||||||
href="/workspace"
|
on:click={() => {
|
||||||
on:click={itemClickHandler}
|
showSearch.set(true);
|
||||||
|
}}
|
||||||
draggable="false"
|
draggable="false"
|
||||||
>
|
>
|
||||||
<div class="self-center">
|
<div class="self-center">
|
||||||
<svg
|
<Search strokeWidth="2" className="size-4.5" />
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke="currentColor"
|
|
||||||
class="size-4.5"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
d="M13.5 16.875h3.375m0 0h3.375m-3.375 0V13.5m0 3.375v3.375M6 10.5h2.25a2.25 2.25 0 0 0 2.25-2.25V6a2.25 2.25 0 0 0-2.25-2.25H6A2.25 2.25 0 0 0 3.75 6v2.25A2.25 2.25 0 0 0 6 10.5Zm0 9.75h2.25A2.25 2.25 0 0 0 10.5 18v-2.25a2.25 2.25 0 0 0-2.25-2.25H6a2.25 2.25 0 0 0-2.25 2.25V18A2.25 2.25 0 0 0 6 20.25Zm9.75-9.75H18a2.25 2.25 0 0 0 2.25-2.25V6A2.25 2.25 0 0 0 18 3.75h-2.25A2.25 2.25 0 0 0 13.5 6v2.25a2.25 2.25 0 0 0 2.25 2.25Z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex self-center translate-y-[0.5px]">
|
<div class="flex self-center translate-y-[0.5px]">
|
||||||
<div class=" self-center text-sm font-primary">{$i18n.t('Workspace')}</div>
|
<div class=" self-center text-sm font-primary">{$i18n.t('Search')}</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="relative flex flex-col flex-1">
|
{#if ($config?.features?.enable_notes ?? false) && ($user?.role === 'admin' || ($user?.permissions?.features?.notes ?? true))}
|
||||||
{#if ($models ?? []).length > 0 && ($settings?.pinnedModels ?? []).length > 0}
|
<div class="px-[7px] flex justify-center text-gray-800 dark:text-gray-200">
|
||||||
<PinnedModelList bind:selectedChatId />
|
<a
|
||||||
{/if}
|
class="grow flex items-center space-x-3 rounded-lg px-2 py-2 hover:bg-gray-100 dark:hover:bg-gray-900 transition"
|
||||||
|
href="/notes"
|
||||||
|
on:click={itemClickHandler}
|
||||||
|
draggable="false"
|
||||||
|
>
|
||||||
|
<div class="self-center">
|
||||||
|
<Note className="size-4.5" strokeWidth="2" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex self-center translate-y-[0.5px]">
|
||||||
|
<div class=" self-center text-sm font-primary">{$i18n.t('Notes')}</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $user?.role === 'admin' || $user?.permissions?.workspace?.models || $user?.permissions?.workspace?.knowledge || $user?.permissions?.workspace?.prompts || $user?.permissions?.workspace?.tools}
|
||||||
|
<div class="px-[7px] flex justify-center text-gray-800 dark:text-gray-200">
|
||||||
|
<a
|
||||||
|
class="grow flex items-center space-x-3 rounded-lg px-2 py-2 hover:bg-gray-100 dark:hover:bg-gray-900 transition"
|
||||||
|
href="/workspace"
|
||||||
|
on:click={itemClickHandler}
|
||||||
|
draggable="false"
|
||||||
|
>
|
||||||
|
<div class="self-center">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke="currentColor"
|
||||||
|
class="size-4.5"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
d="M13.5 16.875h3.375m0 0h3.375m-3.375 0V13.5m0 3.375v3.375M6 10.5h2.25a2.25 2.25 0 0 0 2.25-2.25V6a2.25 2.25 0 0 0-2.25-2.25H6A2.25 2.25 0 0 0 3.75 6v2.25A2.25 2.25 0 0 0 6 10.5Zm0 9.75h2.25A2.25 2.25 0 0 0 10.5 18v-2.25a2.25 2.25 0 0 0-2.25-2.25H6a2.25 2.25 0 0 0-2.25 2.25V18A2.25 2.25 0 0 0 6 20.25Zm9.75-9.75H18a2.25 2.25 0 0 0 2.25-2.25V6A2.25 2.25 0 0 0 18 3.75h-2.25A2.25 2.25 0 0 0 13.5 6v2.25a2.25 2.25 0 0 0 2.25 2.25Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex self-center translate-y-[0.5px]">
|
||||||
|
<div class=" self-center text-sm font-primary">{$i18n.t('Workspace')}</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="relative flex flex-col flex-1">
|
||||||
|
{#if ($models ?? []).length > 0 && ($settings?.pinnedModels ?? []).length > 0}
|
||||||
|
<PinnedModelList bind:selectedChatId />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $config?.features?.enable_channels && ($user?.role === 'admin' || $channels.length > 0)}
|
||||||
|
<Folder
|
||||||
|
className="px-2 mt-0.5"
|
||||||
|
name={$i18n.t('Channels')}
|
||||||
|
dragAndDrop={false}
|
||||||
|
onAdd={async () => {
|
||||||
|
if ($user?.role === 'admin') {
|
||||||
|
await tick();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
showCreateChannel = true;
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onAddLabel={$i18n.t('Create Channel')}
|
||||||
|
>
|
||||||
|
{#each $channels as channel}
|
||||||
|
<ChannelItem
|
||||||
|
{channel}
|
||||||
|
onUpdate={async () => {
|
||||||
|
await initChannels();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
</Folder>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if $config?.features?.enable_channels && ($user?.role === 'admin' || $channels.length > 0)}
|
|
||||||
<Folder
|
<Folder
|
||||||
className="px-2 mt-0.5"
|
className="px-2 mt-0.5"
|
||||||
name={$i18n.t('Channels')}
|
name={$i18n.t('Chats')}
|
||||||
dragAndDrop={false}
|
onAdd={() => {
|
||||||
onAdd={async () => {
|
showCreateFolderModal = true;
|
||||||
if ($user?.role === 'admin') {
|
|
||||||
await tick();
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
showCreateChannel = true;
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
onAddLabel={$i18n.t('Create Channel')}
|
onAddLabel={$i18n.t('New Folder')}
|
||||||
>
|
on:change={async (e) => {
|
||||||
{#each $channels as channel}
|
selectedFolder.set(null);
|
||||||
<ChannelItem
|
await goto('/');
|
||||||
{channel}
|
}}
|
||||||
onUpdate={async () => {
|
on:import={(e) => {
|
||||||
await initChannels();
|
importChatHandler(e.detail);
|
||||||
}}
|
}}
|
||||||
/>
|
on:drop={async (e) => {
|
||||||
{/each}
|
const { type, id, item } = e.detail;
|
||||||
</Folder>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<Folder
|
if (type === 'chat') {
|
||||||
className="px-2 mt-0.5"
|
let chat = await getChatById(localStorage.token, id).catch((error) => {
|
||||||
name={$i18n.t('Chats')}
|
return null;
|
||||||
onAdd={() => {
|
});
|
||||||
showCreateFolderModal = true;
|
if (!chat && item) {
|
||||||
}}
|
chat = await importChat(
|
||||||
onAddLabel={$i18n.t('New Folder')}
|
localStorage.token,
|
||||||
on:change={async (e) => {
|
item.chat,
|
||||||
selectedFolder.set(null);
|
item?.meta ?? {},
|
||||||
await goto('/');
|
false,
|
||||||
}}
|
null,
|
||||||
on:import={(e) => {
|
item?.created_at ?? null,
|
||||||
importChatHandler(e.detail);
|
item?.updated_at ?? null
|
||||||
}}
|
|
||||||
on:drop={async (e) => {
|
|
||||||
const { type, id, item } = e.detail;
|
|
||||||
|
|
||||||
if (type === 'chat') {
|
|
||||||
let chat = await getChatById(localStorage.token, id).catch((error) => {
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
if (!chat && item) {
|
|
||||||
chat = await importChat(
|
|
||||||
localStorage.token,
|
|
||||||
item.chat,
|
|
||||||
item?.meta ?? {},
|
|
||||||
false,
|
|
||||||
null,
|
|
||||||
item?.created_at ?? null,
|
|
||||||
item?.updated_at ?? null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (chat) {
|
|
||||||
console.log(chat);
|
|
||||||
if (chat.folder_id) {
|
|
||||||
const res = await updateChatFolderIdById(localStorage.token, chat.id, null).catch(
|
|
||||||
(error) => {
|
|
||||||
toast.error(`${error}`);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chat.pinned) {
|
if (chat) {
|
||||||
const res = await toggleChatPinnedStatusById(localStorage.token, chat.id);
|
console.log(chat);
|
||||||
|
if (chat.folder_id) {
|
||||||
|
const res = await updateChatFolderIdById(localStorage.token, chat.id, null).catch(
|
||||||
|
(error) => {
|
||||||
|
toast.error(`${error}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chat.pinned) {
|
||||||
|
const res = await toggleChatPinnedStatusById(localStorage.token, chat.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
initChatList();
|
||||||
|
}
|
||||||
|
} else if (type === 'folder') {
|
||||||
|
if (folders[id].parent_id === null) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
initChatList();
|
const res = await updateFolderParentIdById(localStorage.token, id, null).catch(
|
||||||
}
|
(error) => {
|
||||||
} else if (type === 'folder') {
|
toast.error(`${error}`);
|
||||||
if (folders[id].parent_id === null) {
|
return null;
|
||||||
return;
|
}
|
||||||
}
|
);
|
||||||
|
|
||||||
const res = await updateFolderParentIdById(localStorage.token, id, null).catch(
|
if (res) {
|
||||||
(error) => {
|
await initFolders();
|
||||||
toast.error(`${error}`);
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
|
||||||
if (res) {
|
|
||||||
await initFolders();
|
|
||||||
}
|
}
|
||||||
}
|
}}
|
||||||
}}
|
>
|
||||||
>
|
{#if $pinnedChats.length > 0}
|
||||||
{#if $pinnedChats.length > 0}
|
<div class="flex flex-col space-y-1 rounded-xl">
|
||||||
<div class="flex flex-col space-y-1 rounded-xl">
|
<Folder
|
||||||
<Folder
|
className=""
|
||||||
className=""
|
bind:open={showPinnedChat}
|
||||||
bind:open={showPinnedChat}
|
on:change={(e) => {
|
||||||
on:change={(e) => {
|
localStorage.setItem('showPinnedChat', e.detail);
|
||||||
localStorage.setItem('showPinnedChat', e.detail);
|
console.log(e.detail);
|
||||||
console.log(e.detail);
|
}}
|
||||||
|
on:import={(e) => {
|
||||||
|
importChatHandler(e.detail, true);
|
||||||
|
}}
|
||||||
|
on:drop={async (e) => {
|
||||||
|
const { type, id, item } = e.detail;
|
||||||
|
|
||||||
|
if (type === 'chat') {
|
||||||
|
let chat = await getChatById(localStorage.token, id).catch((error) => {
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
if (!chat && item) {
|
||||||
|
chat = await importChat(
|
||||||
|
localStorage.token,
|
||||||
|
item.chat,
|
||||||
|
item?.meta ?? {},
|
||||||
|
false,
|
||||||
|
null,
|
||||||
|
item?.created_at ?? null,
|
||||||
|
item?.updated_at ?? null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chat) {
|
||||||
|
console.log(chat);
|
||||||
|
if (chat.folder_id) {
|
||||||
|
const res = await updateChatFolderIdById(
|
||||||
|
localStorage.token,
|
||||||
|
chat.id,
|
||||||
|
null
|
||||||
|
).catch((error) => {
|
||||||
|
toast.error(`${error}`);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!chat.pinned) {
|
||||||
|
const res = await toggleChatPinnedStatusById(localStorage.token, chat.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
initChatList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
name={$i18n.t('Pinned')}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ml-3 pl-1 mt-[1px] flex flex-col overflow-y-auto scrollbar-hidden border-s border-gray-100 dark:border-gray-900"
|
||||||
|
>
|
||||||
|
{#each $pinnedChats as chat, idx (`pinned-chat-${chat?.id ?? idx}`)}
|
||||||
|
<ChatItem
|
||||||
|
className=""
|
||||||
|
id={chat.id}
|
||||||
|
title={chat.title}
|
||||||
|
{shiftKey}
|
||||||
|
selected={selectedChatId === chat.id}
|
||||||
|
on:select={() => {
|
||||||
|
selectedChatId = chat.id;
|
||||||
|
}}
|
||||||
|
on:unselect={() => {
|
||||||
|
selectedChatId = null;
|
||||||
|
}}
|
||||||
|
on:change={async () => {
|
||||||
|
initChatList();
|
||||||
|
}}
|
||||||
|
on:tag={(e) => {
|
||||||
|
const { type, name } = e.detail;
|
||||||
|
tagEventHandler(type, name, chat.id);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</Folder>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if folders}
|
||||||
|
<Folders
|
||||||
|
{folders}
|
||||||
|
{shiftKey}
|
||||||
|
onDelete={(folderId) => {
|
||||||
|
selectedFolder.set(null);
|
||||||
|
initChatList();
|
||||||
|
}}
|
||||||
|
on:update={() => {
|
||||||
|
initChatList();
|
||||||
}}
|
}}
|
||||||
on:import={(e) => {
|
on:import={(e) => {
|
||||||
importChatHandler(e.detail, true);
|
const { folderId, items } = e.detail;
|
||||||
|
importChatHandler(items, false, folderId);
|
||||||
}}
|
}}
|
||||||
on:drop={async (e) => {
|
on:change={async () => {
|
||||||
const { type, id, item } = e.detail;
|
initChatList();
|
||||||
|
|
||||||
if (type === 'chat') {
|
|
||||||
let chat = await getChatById(localStorage.token, id).catch((error) => {
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
if (!chat && item) {
|
|
||||||
chat = await importChat(
|
|
||||||
localStorage.token,
|
|
||||||
item.chat,
|
|
||||||
item?.meta ?? {},
|
|
||||||
false,
|
|
||||||
null,
|
|
||||||
item?.created_at ?? null,
|
|
||||||
item?.updated_at ?? null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (chat) {
|
|
||||||
console.log(chat);
|
|
||||||
if (chat.folder_id) {
|
|
||||||
const res = await updateChatFolderIdById(
|
|
||||||
localStorage.token,
|
|
||||||
chat.id,
|
|
||||||
null
|
|
||||||
).catch((error) => {
|
|
||||||
toast.error(`${error}`);
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!chat.pinned) {
|
|
||||||
const res = await toggleChatPinnedStatusById(localStorage.token, chat.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
initChatList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
name={$i18n.t('Pinned')}
|
/>
|
||||||
>
|
{/if}
|
||||||
<div
|
|
||||||
class="ml-3 pl-1 mt-[1px] flex flex-col overflow-y-auto scrollbar-hidden border-s border-gray-100 dark:border-gray-900"
|
<div class=" flex-1 flex flex-col overflow-y-auto scrollbar-hidden">
|
||||||
>
|
<div class="pt-1.5">
|
||||||
{#each $pinnedChats as chat, idx (`pinned-chat-${chat?.id ?? idx}`)}
|
{#if $chats}
|
||||||
|
{#each $chats as chat, idx (`chat-${chat?.id ?? idx}`)}
|
||||||
|
{#if idx === 0 || (idx > 0 && chat.time_range !== $chats[idx - 1].time_range)}
|
||||||
|
<div
|
||||||
|
class="w-full pl-2.5 text-xs text-gray-500 dark:text-gray-500 font-medium {idx ===
|
||||||
|
0
|
||||||
|
? ''
|
||||||
|
: 'pt-5'} pb-1.5"
|
||||||
|
>
|
||||||
|
{$i18n.t(chat.time_range)}
|
||||||
|
<!-- localisation keys for time_range to be recognized from the i18next parser (so they don't get automatically removed):
|
||||||
|
{$i18n.t('Today')}
|
||||||
|
{$i18n.t('Yesterday')}
|
||||||
|
{$i18n.t('Previous 7 days')}
|
||||||
|
{$i18n.t('Previous 30 days')}
|
||||||
|
{$i18n.t('January')}
|
||||||
|
{$i18n.t('February')}
|
||||||
|
{$i18n.t('March')}
|
||||||
|
{$i18n.t('April')}
|
||||||
|
{$i18n.t('May')}
|
||||||
|
{$i18n.t('June')}
|
||||||
|
{$i18n.t('July')}
|
||||||
|
{$i18n.t('August')}
|
||||||
|
{$i18n.t('September')}
|
||||||
|
{$i18n.t('October')}
|
||||||
|
{$i18n.t('November')}
|
||||||
|
{$i18n.t('December')}
|
||||||
|
-->
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<ChatItem
|
<ChatItem
|
||||||
className=""
|
className=""
|
||||||
id={chat.id}
|
id={chat.id}
|
||||||
|
|
@ -977,144 +1060,67 @@
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
|
||||||
</Folder>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if folders}
|
{#if $scrollPaginationEnabled && !allChatsLoaded}
|
||||||
<Folders
|
<Loader
|
||||||
{folders}
|
on:visible={(e) => {
|
||||||
{shiftKey}
|
if (!chatListLoading) {
|
||||||
onDelete={(folderId) => {
|
loadMoreChats();
|
||||||
selectedFolder.set(null);
|
}
|
||||||
initChatList();
|
}}
|
||||||
}}
|
|
||||||
on:update={() => {
|
|
||||||
initChatList();
|
|
||||||
}}
|
|
||||||
on:import={(e) => {
|
|
||||||
const { folderId, items } = e.detail;
|
|
||||||
importChatHandler(items, false, folderId);
|
|
||||||
}}
|
|
||||||
on:change={async () => {
|
|
||||||
initChatList();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class=" flex-1 flex flex-col overflow-y-auto scrollbar-hidden">
|
|
||||||
<div class="pt-1.5">
|
|
||||||
{#if $chats}
|
|
||||||
{#each $chats as chat, idx (`chat-${chat?.id ?? idx}`)}
|
|
||||||
{#if idx === 0 || (idx > 0 && chat.time_range !== $chats[idx - 1].time_range)}
|
|
||||||
<div
|
|
||||||
class="w-full pl-2.5 text-xs text-gray-500 dark:text-gray-500 font-medium {idx ===
|
|
||||||
0
|
|
||||||
? ''
|
|
||||||
: 'pt-5'} pb-1.5"
|
|
||||||
>
|
>
|
||||||
{$i18n.t(chat.time_range)}
|
<div
|
||||||
<!-- localisation keys for time_range to be recognized from the i18next parser (so they don't get automatically removed):
|
class="w-full flex justify-center py-1 text-xs animate-pulse items-center gap-2"
|
||||||
{$i18n.t('Today')}
|
>
|
||||||
{$i18n.t('Yesterday')}
|
<Spinner className=" size-4" />
|
||||||
{$i18n.t('Previous 7 days')}
|
<div class=" ">Loading...</div>
|
||||||
{$i18n.t('Previous 30 days')}
|
</div>
|
||||||
{$i18n.t('January')}
|
</Loader>
|
||||||
{$i18n.t('February')}
|
|
||||||
{$i18n.t('March')}
|
|
||||||
{$i18n.t('April')}
|
|
||||||
{$i18n.t('May')}
|
|
||||||
{$i18n.t('June')}
|
|
||||||
{$i18n.t('July')}
|
|
||||||
{$i18n.t('August')}
|
|
||||||
{$i18n.t('September')}
|
|
||||||
{$i18n.t('October')}
|
|
||||||
{$i18n.t('November')}
|
|
||||||
{$i18n.t('December')}
|
|
||||||
-->
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
{:else}
|
||||||
<ChatItem
|
<div
|
||||||
className=""
|
class="w-full flex justify-center py-1 text-xs animate-pulse items-center gap-2"
|
||||||
id={chat.id}
|
|
||||||
title={chat.title}
|
|
||||||
{shiftKey}
|
|
||||||
selected={selectedChatId === chat.id}
|
|
||||||
on:select={() => {
|
|
||||||
selectedChatId = chat.id;
|
|
||||||
}}
|
|
||||||
on:unselect={() => {
|
|
||||||
selectedChatId = null;
|
|
||||||
}}
|
|
||||||
on:change={async () => {
|
|
||||||
initChatList();
|
|
||||||
}}
|
|
||||||
on:tag={(e) => {
|
|
||||||
const { type, name } = e.detail;
|
|
||||||
tagEventHandler(type, name, chat.id);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{/each}
|
|
||||||
|
|
||||||
{#if $scrollPaginationEnabled && !allChatsLoaded}
|
|
||||||
<Loader
|
|
||||||
on:visible={(e) => {
|
|
||||||
if (!chatListLoading) {
|
|
||||||
loadMoreChats();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<div
|
<Spinner className=" size-4" />
|
||||||
class="w-full flex justify-center py-1 text-xs animate-pulse items-center gap-2"
|
<div class=" ">Loading...</div>
|
||||||
>
|
</div>
|
||||||
<Spinner className=" size-4" />
|
|
||||||
<div class=" ">Loading...</div>
|
|
||||||
</div>
|
|
||||||
</Loader>
|
|
||||||
{/if}
|
{/if}
|
||||||
{:else}
|
|
||||||
<div class="w-full flex justify-center py-1 text-xs animate-pulse items-center gap-2">
|
|
||||||
<Spinner className=" size-4" />
|
|
||||||
<div class=" ">Loading...</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Folder>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="px-2 pt-1.5 pb-2 sticky bottom-0 z-10 bg-gray-50 dark:bg-gray-950">
|
|
||||||
<div class="flex flex-col font-primary">
|
|
||||||
{#if $user !== undefined && $user !== null}
|
|
||||||
<UserMenu
|
|
||||||
role={$user?.role}
|
|
||||||
on:show={(e) => {
|
|
||||||
if (e.detail === 'archived-chat') {
|
|
||||||
showArchivedChats.set(true);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class=" flex items-center rounded-xl py-2.5 px-2.5 w-full hover:bg-gray-100 dark:hover:bg-gray-900 transition"
|
|
||||||
>
|
|
||||||
<div class=" self-center mr-3">
|
|
||||||
<img
|
|
||||||
src={$user?.profile_image_url}
|
|
||||||
class=" size-6 object-cover rounded-full"
|
|
||||||
alt={$i18n.t('Open User Profile Menu')}
|
|
||||||
aria-label={$i18n.t('Open User Profile Menu')}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class=" self-center font-medium">{$user?.name}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</UserMenu>
|
</div>
|
||||||
{/if}
|
</Folder>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="px-2 pt-1.5 pb-2 sticky bottom-0 z-10 bg-gray-50 dark:bg-gray-950">
|
||||||
|
<div class="flex flex-col font-primary">
|
||||||
|
{#if $user !== undefined && $user !== null}
|
||||||
|
<UserMenu
|
||||||
|
role={$user?.role}
|
||||||
|
on:show={(e) => {
|
||||||
|
if (e.detail === 'archived-chat') {
|
||||||
|
showArchivedChats.set(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class=" flex items-center rounded-xl py-2.5 px-2.5 w-full hover:bg-gray-100 dark:hover:bg-gray-900 transition"
|
||||||
|
>
|
||||||
|
<div class=" self-center mr-3">
|
||||||
|
<img
|
||||||
|
src={$user?.profile_image_url}
|
||||||
|
class=" size-6 object-cover rounded-full"
|
||||||
|
alt={$i18n.t('Open User Profile Menu')}
|
||||||
|
aria-label={$i18n.t('Open User Profile Menu')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class=" self-center font-medium">{$user?.name}</div>
|
||||||
|
</div>
|
||||||
|
</UserMenu>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.scrollbar-hidden:active::-webkit-scrollbar-thumb,
|
.scrollbar-hidden:active::-webkit-scrollbar-thumb,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue