refac
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { toast } from 'svelte-sonner';
|
import { toast } from 'svelte-sonner';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
import Sortable from 'sortablejs';
|
||||||
|
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import {
|
import {
|
||||||
@@ -41,11 +42,14 @@
|
|||||||
toggleChatPinnedStatusById,
|
toggleChatPinnedStatusById,
|
||||||
getChatById,
|
getChatById,
|
||||||
updateChatFolderIdById,
|
updateChatFolderIdById,
|
||||||
importChats
|
importChats,
|
||||||
|
deleteAllChats,
|
||||||
|
getChatListBySearchText
|
||||||
} from '$lib/apis/chats';
|
} from '$lib/apis/chats';
|
||||||
import { createNewFolder, getFolders, updateFolderParentIdById } from '$lib/apis/folders';
|
import { createNewFolder, getFolders, updateFolderParentIdById } from '$lib/apis/folders';
|
||||||
|
import { createNewNote, getPinnedNoteList, toggleNotePinnedStatusById } from '$lib/apis/notes';
|
||||||
|
import { updateUserSettings } from '$lib/apis/users';
|
||||||
import { checkActiveChats } from '$lib/apis/tasks';
|
import { checkActiveChats } from '$lib/apis/tasks';
|
||||||
import { getPinnedNoteList, toggleNotePinnedStatusById } from '$lib/apis/notes';
|
|
||||||
import { createNoteHandler } from '$lib/components/notes/utils';
|
import { createNoteHandler } from '$lib/components/notes/utils';
|
||||||
import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
|
import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
|
||||||
|
|
||||||
@@ -67,10 +71,12 @@
|
|||||||
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 Code from '../icons/Code.svelte';
|
||||||
import { slide } from 'svelte/transition';
|
import { slide } from 'svelte/transition';
|
||||||
import HotkeyHint from '../common/HotkeyHint.svelte';
|
import HotkeyHint from '../common/HotkeyHint.svelte';
|
||||||
|
|
||||||
const BREAKPOINT = 768;
|
const BREAKPOINT = 768;
|
||||||
|
const DEFAULT_PINNED_ITEMS = ['notes', 'workspace'];
|
||||||
|
|
||||||
let scrollTop = 0;
|
let scrollTop = 0;
|
||||||
|
|
||||||
@@ -98,6 +104,55 @@
|
|||||||
|
|
||||||
let newFolderId = null;
|
let newFolderId = null;
|
||||||
|
|
||||||
|
$: pinnedItems = $settings?.pinnedMenuItems ?? DEFAULT_PINNED_ITEMS;
|
||||||
|
|
||||||
|
const isMenuItemVisible = (id) => {
|
||||||
|
switch (id) {
|
||||||
|
case 'notes':
|
||||||
|
return ($config?.features?.enable_notes ?? false) && ($user?.role === 'admin' || ($user?.permissions?.features?.notes ?? true));
|
||||||
|
case 'workspace':
|
||||||
|
return $user?.role === 'admin' || $user?.permissions?.workspace?.models || $user?.permissions?.workspace?.knowledge || $user?.permissions?.workspace?.prompts || $user?.permissions?.workspace?.tools;
|
||||||
|
case 'automations':
|
||||||
|
return $config?.features?.enable_automations && ($user?.role === 'admin' || $user?.permissions?.features?.automations);
|
||||||
|
case 'calendar':
|
||||||
|
return $config?.features?.enable_calendar && ($user?.role === 'admin' || $user?.permissions?.features?.calendar);
|
||||||
|
case 'playground':
|
||||||
|
return $user?.role === 'admin';
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getMenuItemMeta = (id) => {
|
||||||
|
const items = {
|
||||||
|
notes: { label: 'Notes', href: '/notes', iconType: 'note' },
|
||||||
|
workspace: { label: 'Workspace', href: '/workspace', iconType: 'workspace' },
|
||||||
|
automations: { label: 'Automations', href: '/automations', iconType: 'automations' },
|
||||||
|
calendar: { label: 'Calendar', href: '/calendar', iconType: 'calendar' },
|
||||||
|
playground: { label: 'Playground', href: '/playground', iconType: 'playground' }
|
||||||
|
};
|
||||||
|
return items[id];
|
||||||
|
};
|
||||||
|
|
||||||
|
const initPinnedMenuSortable = () => {
|
||||||
|
const el = document.getElementById('pinned-menu-items-list');
|
||||||
|
if (el && !$mobile) {
|
||||||
|
new Sortable(el, {
|
||||||
|
animation: 150,
|
||||||
|
onUpdate: async (event) => {
|
||||||
|
const itemId = event.item.dataset.id;
|
||||||
|
const newIndex = event.newIndex;
|
||||||
|
const current = [...pinnedItems];
|
||||||
|
const oldIndex = current.indexOf(itemId);
|
||||||
|
current.splice(oldIndex, 1);
|
||||||
|
current.splice(newIndex, 0, itemId);
|
||||||
|
settings.set({ ...$settings, pinnedMenuItems: current });
|
||||||
|
await updateUserSettings(localStorage.token, { ui: $settings });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
$: if ($selectedFolder) {
|
$: if ($selectedFolder) {
|
||||||
initFolders();
|
initFolders();
|
||||||
}
|
}
|
||||||
@@ -433,7 +488,7 @@
|
|||||||
document.documentElement.style.setProperty('--sidebar-width', `${newSidebarWidth}px`);
|
document.documentElement.style.setProperty('--sidebar-width', `${newSidebarWidth}px`);
|
||||||
};
|
};
|
||||||
|
|
||||||
onMount(() => {
|
onMount(async () => {
|
||||||
try {
|
try {
|
||||||
const width = Number(localStorage.getItem('sidebarWidth'));
|
const width = Number(localStorage.getItem('sidebarWidth'));
|
||||||
if (!Number.isNaN(width) && width >= MIN_WIDTH && width <= MAX_WIDTH) {
|
if (!Number.isNaN(width) && width >= MIN_WIDTH && width <= MAX_WIDTH) {
|
||||||
@@ -528,6 +583,9 @@
|
|||||||
const socketInstance = $socket;
|
const socketInstance = $socket;
|
||||||
socketInstance?.on('events', chatActiveEventHandler);
|
socketInstance?.on('events', chatActiveEventHandler);
|
||||||
|
|
||||||
|
await tick();
|
||||||
|
initPinnedMenuSortable();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unsubscribers.forEach((unsubscriber) => unsubscriber());
|
unsubscribers.forEach((unsubscriber) => unsubscriber());
|
||||||
|
|
||||||
@@ -783,7 +841,7 @@
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if ($config?.features?.enable_notes ?? false) && ($user?.role === 'admin' || ($user?.permissions?.features?.notes ?? true))}
|
{#if pinnedItems.includes('notes') && ($config?.features?.enable_notes ?? false) && ($user?.role === 'admin' || ($user?.permissions?.features?.notes ?? true))}
|
||||||
<div class="">
|
<div class="">
|
||||||
<Tooltip content={$i18n.t('Notes')} placement="right">
|
<Tooltip content={$i18n.t('Notes')} placement="right">
|
||||||
<a
|
<a
|
||||||
@@ -807,7 +865,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if $user?.role === 'admin' || $user?.permissions?.workspace?.models || $user?.permissions?.workspace?.knowledge || $user?.permissions?.workspace?.prompts || $user?.permissions?.workspace?.tools}
|
{#if pinnedItems.includes('workspace') && ($user?.role === 'admin' || $user?.permissions?.workspace?.models || $user?.permissions?.workspace?.knowledge || $user?.permissions?.workspace?.prompts || $user?.permissions?.workspace?.tools)}
|
||||||
<div class="">
|
<div class="">
|
||||||
<Tooltip content={$i18n.t('Workspace')} placement="right">
|
<Tooltip content={$i18n.t('Workspace')} placement="right">
|
||||||
<a
|
<a
|
||||||
@@ -843,6 +901,79 @@
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if pinnedItems.includes('automations') && $config?.features?.enable_automations && ($user?.role === 'admin' || $user?.permissions?.features?.automations)}
|
||||||
|
<div class="">
|
||||||
|
<Tooltip content={$i18n.t('Automations')} placement="right">
|
||||||
|
<a
|
||||||
|
class=" cursor-pointer flex rounded-xl hover:bg-gray-100 dark:hover:bg-gray-850 transition group"
|
||||||
|
href="/automations"
|
||||||
|
on:click={async (e) => {
|
||||||
|
e.stopImmediatePropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
goto('/automations');
|
||||||
|
itemClickHandler();
|
||||||
|
}}
|
||||||
|
draggable="false"
|
||||||
|
aria-label={$i18n.t('Automations')}
|
||||||
|
>
|
||||||
|
<div class=" self-center flex items-center justify-center size-9">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4.5">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if pinnedItems.includes('calendar') && $config?.features?.enable_calendar && ($user?.role === 'admin' || $user?.permissions?.features?.calendar)}
|
||||||
|
<div class="">
|
||||||
|
<Tooltip content={$i18n.t('Calendar')} placement="right">
|
||||||
|
<a
|
||||||
|
class=" cursor-pointer flex rounded-xl hover:bg-gray-100 dark:hover:bg-gray-850 transition group"
|
||||||
|
href="/calendar"
|
||||||
|
on:click={async (e) => {
|
||||||
|
e.stopImmediatePropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
goto('/calendar');
|
||||||
|
itemClickHandler();
|
||||||
|
}}
|
||||||
|
draggable="false"
|
||||||
|
aria-label={$i18n.t('Calendar')}
|
||||||
|
>
|
||||||
|
<div class=" self-center flex items-center justify-center size-9">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4.5">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75m-18 0v-7.5A2.25 2.25 0 0 1 5.25 9h13.5A2.25 2.25 0 0 1 21 11.25v7.5" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if pinnedItems.includes('playground') && $user?.role === 'admin'}
|
||||||
|
<div class="">
|
||||||
|
<Tooltip content={$i18n.t('Playground')} placement="right">
|
||||||
|
<a
|
||||||
|
class=" cursor-pointer flex rounded-xl hover:bg-gray-100 dark:hover:bg-gray-850 transition group"
|
||||||
|
href="/playground"
|
||||||
|
on:click={async (e) => {
|
||||||
|
e.stopImmediatePropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
goto('/playground');
|
||||||
|
itemClickHandler();
|
||||||
|
}}
|
||||||
|
draggable="false"
|
||||||
|
aria-label={$i18n.t('Playground')}
|
||||||
|
>
|
||||||
|
<div class=" self-center flex items-center justify-center size-9">
|
||||||
|
<Code className="size-4.5" />
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -1017,61 +1148,48 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if ($config?.features?.enable_notes ?? false) && ($user?.role === 'admin' || ($user?.permissions?.features?.notes ?? true))}
|
<div id="pinned-menu-items-list">
|
||||||
<div class="px-[0.4375rem] flex justify-center text-gray-800 dark:text-gray-200">
|
{#each pinnedItems as itemId (itemId)}
|
||||||
<a
|
{@const meta = getMenuItemMeta(itemId)}
|
||||||
id="sidebar-notes-button"
|
{#if meta && isMenuItemVisible(itemId)}
|
||||||
class="grow flex items-center space-x-3 rounded-2xl px-2.5 py-2 hover:bg-gray-100 dark:hover:bg-gray-900 transition"
|
<div class="px-[0.4375rem] flex justify-center text-gray-800 dark:text-gray-200" data-id={itemId}>
|
||||||
href="/notes"
|
<a
|
||||||
on:click={itemClickHandler}
|
id="sidebar-{itemId}-button"
|
||||||
draggable="false"
|
class="grow flex items-center space-x-3 rounded-2xl px-2.5 py-2 hover:bg-gray-100 dark:hover:bg-gray-900 transition"
|
||||||
aria-label={$i18n.t('Notes')}
|
href={meta.href}
|
||||||
>
|
on:click={itemClickHandler}
|
||||||
<div class="self-center">
|
draggable="false"
|
||||||
<Note className="size-4.5" strokeWidth="2" />
|
aria-label={$i18n.t(meta.label)}
|
||||||
</div>
|
>
|
||||||
|
<div class="self-center">
|
||||||
|
{#if itemId === 'notes'}
|
||||||
|
<Note className="size-4.5" strokeWidth="2" />
|
||||||
|
{:else if itemId === 'workspace'}
|
||||||
|
<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>
|
||||||
|
{:else if itemId === 'automations'}
|
||||||
|
<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="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
||||||
|
</svg>
|
||||||
|
{:else if itemId === 'calendar'}
|
||||||
|
<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="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75m-18 0v-7.5A2.25 2.25 0 0 1 5.25 9h13.5A2.25 2.25 0 0 1 21 11.25v7.5" />
|
||||||
|
</svg>
|
||||||
|
{:else if itemId === 'playground'}
|
||||||
|
<Code className="size-4.5" strokeWidth="2" />
|
||||||
|
{/if}
|
||||||
|
</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(meta.label)}</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
{/each}
|
||||||
{#if $user?.role === 'admin' || $user?.permissions?.workspace?.models || $user?.permissions?.workspace?.knowledge || $user?.permissions?.workspace?.prompts || $user?.permissions?.workspace?.tools}
|
|
||||||
<div class="px-[0.4375rem] flex justify-center text-gray-800 dark:text-gray-200">
|
|
||||||
<a
|
|
||||||
id="sidebar-workspace-button"
|
|
||||||
class="grow flex items-center space-x-3 rounded-2xl px-2.5 py-2 hover:bg-gray-100 dark:hover:bg-gray-900 transition"
|
|
||||||
href="/workspace"
|
|
||||||
on:click={itemClickHandler}
|
|
||||||
draggable="false"
|
|
||||||
aria-label={$i18n.t('Workspace')}
|
|
||||||
>
|
|
||||||
<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>
|
||||||
|
</div>
|
||||||
|
|
||||||
{#if ($models ?? []).length > 0 && (($settings?.pinnedModels ?? []).length > 0 || $config?.default_pinned_models)}
|
{#if ($models ?? []).length > 0 && (($settings?.pinnedModels ?? []).length > 0 || $config?.default_pinned_models)}
|
||||||
<Folder
|
<Folder
|
||||||
|
|||||||
@@ -7,7 +7,15 @@
|
|||||||
import { getUsage } from '$lib/apis';
|
import { getUsage } from '$lib/apis';
|
||||||
import { getSessionUser, userSignOut } from '$lib/apis/auths';
|
import { getSessionUser, userSignOut } from '$lib/apis/auths';
|
||||||
|
|
||||||
import { showSettings, mobile, showSidebar, showShortcuts, user, config } from '$lib/stores';
|
import {
|
||||||
|
showSettings,
|
||||||
|
mobile,
|
||||||
|
showSidebar,
|
||||||
|
showShortcuts,
|
||||||
|
user,
|
||||||
|
config,
|
||||||
|
settings
|
||||||
|
} from '$lib/stores';
|
||||||
|
|
||||||
import { WEBUI_API_BASE_URL } from '$lib/constants';
|
import { WEBUI_API_BASE_URL } from '$lib/constants';
|
||||||
|
|
||||||
@@ -26,7 +34,10 @@
|
|||||||
import UserStatusModal from './UserStatusModal.svelte';
|
import UserStatusModal from './UserStatusModal.svelte';
|
||||||
import Emoji from '$lib/components/common/Emoji.svelte';
|
import Emoji from '$lib/components/common/Emoji.svelte';
|
||||||
import XMark from '$lib/components/icons/XMark.svelte';
|
import XMark from '$lib/components/icons/XMark.svelte';
|
||||||
import { updateUserStatus } from '$lib/apis/users';
|
import Note from '$lib/components/icons/Note.svelte';
|
||||||
|
import Pin from '$lib/components/icons/Pin.svelte';
|
||||||
|
import PinSlash from '$lib/components/icons/PinSlash.svelte';
|
||||||
|
import { updateUserStatus, updateUserSettings } from '$lib/apis/users';
|
||||||
import { toast } from 'svelte-sonner';
|
import { toast } from 'svelte-sonner';
|
||||||
|
|
||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
@@ -43,9 +54,29 @@
|
|||||||
export let showActiveUsers = true;
|
export let showActiveUsers = true;
|
||||||
|
|
||||||
let showUserStatusModal = false;
|
let showUserStatusModal = false;
|
||||||
|
let shiftKey = false;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
const DEFAULT_PINNED_ITEMS = ['notes', 'workspace'];
|
||||||
|
|
||||||
|
$: pinnedItems = $settings?.pinnedMenuItems ?? DEFAULT_PINNED_ITEMS;
|
||||||
|
|
||||||
|
const isPinned = (id: string) => {
|
||||||
|
return pinnedItems.includes(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const togglePin = async (id: string) => {
|
||||||
|
let updated;
|
||||||
|
if (isPinned(id)) {
|
||||||
|
updated = pinnedItems.filter((item) => item !== id);
|
||||||
|
} else {
|
||||||
|
updated = [...pinnedItems, id];
|
||||||
|
}
|
||||||
|
await settings.set({ ...$settings, pinnedMenuItems: updated });
|
||||||
|
await updateUserSettings(localStorage.token, { ui: $settings });
|
||||||
|
};
|
||||||
|
|
||||||
let usage = null;
|
let usage = null;
|
||||||
const getUsageInfo = async () => {
|
const getUsageInfo = async () => {
|
||||||
const res = await getUsage(localStorage.token).catch((error) => {
|
const res = await getUsage(localStorage.token).catch((error) => {
|
||||||
@@ -69,6 +100,15 @@
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<svelte:window
|
||||||
|
on:keydown={(e) => {
|
||||||
|
if (e.key === 'Shift') shiftKey = true;
|
||||||
|
}}
|
||||||
|
on:keyup={(e) => {
|
||||||
|
if (e.key === 'Shift') shiftKey = false;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
<ShortcutsModal bind:show={$showShortcuts} />
|
<ShortcutsModal bind:show={$showShortcuts} />
|
||||||
<UserStatusModal
|
<UserStatusModal
|
||||||
bind:show={showUserStatusModal}
|
bind:show={showUserStatusModal}
|
||||||
@@ -194,119 +234,263 @@
|
|||||||
<hr class=" border-gray-50/30 dark:border-gray-800/30 my-1.5 p-0" />
|
<hr class=" border-gray-50/30 dark:border-gray-800/30 my-1.5 p-0" />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<button
|
{#if ($config?.features?.enable_notes ?? false) && ($user?.role === 'admin' || ($user?.permissions?.features?.notes ?? true))}
|
||||||
class="flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none"
|
<div class="flex items-center w-full">
|
||||||
type="button"
|
<a
|
||||||
on:click={async () => {
|
href="/notes"
|
||||||
show = false;
|
draggable="false"
|
||||||
|
class="flex flex-1 rounded-xl py-1.5 px-3 hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none"
|
||||||
await showSettings.set(true);
|
on:click={async (e) => {
|
||||||
|
if (e.metaKey || e.ctrlKey || e.shiftKey || e.button === 1) return;
|
||||||
if ($mobile) {
|
e.preventDefault();
|
||||||
await tick();
|
show = false;
|
||||||
showSidebar.set(false);
|
goto('/notes');
|
||||||
}
|
if ($mobile) {
|
||||||
}}
|
await tick();
|
||||||
>
|
showSidebar.set(false);
|
||||||
<div class=" self-center mr-3">
|
}
|
||||||
<Settings className="w-5 h-5" strokeWidth="1.5" />
|
}}
|
||||||
|
>
|
||||||
|
<div class="self-center mr-3">
|
||||||
|
<Note className="size-5" strokeWidth="1.5" />
|
||||||
|
</div>
|
||||||
|
<div class="self-center truncate">{$i18n.t('Notes')}</div>
|
||||||
|
</a>
|
||||||
|
{#if shiftKey}
|
||||||
|
<Tooltip
|
||||||
|
content={isPinned('notes')
|
||||||
|
? $i18n.t('Unpin from Sidebar')
|
||||||
|
: $i18n.t('Pin to Sidebar')}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="p-1 mr-1 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition"
|
||||||
|
on:click|preventDefault|stopPropagation={() => togglePin('notes')}
|
||||||
|
>
|
||||||
|
{#if isPinned('notes')}
|
||||||
|
<PinSlash className="size-3.5" strokeWidth="1.5" />
|
||||||
|
{:else}
|
||||||
|
<Pin className="size-3.5" strokeWidth="1.5" />
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class=" self-center truncate">{$i18n.t('Settings')}</div>
|
{/if}
|
||||||
</button>
|
|
||||||
|
{#if $user?.role === 'admin' || $user?.permissions?.workspace?.models || $user?.permissions?.workspace?.knowledge || $user?.permissions?.workspace?.prompts || $user?.permissions?.workspace?.tools}
|
||||||
|
<div class="flex items-center w-full">
|
||||||
|
<a
|
||||||
|
href="/workspace"
|
||||||
|
draggable="false"
|
||||||
|
class="flex flex-1 rounded-xl py-1.5 px-3 hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none"
|
||||||
|
on:click={async (e) => {
|
||||||
|
if (e.metaKey || e.ctrlKey || e.shiftKey || e.button === 1) return;
|
||||||
|
e.preventDefault();
|
||||||
|
show = false;
|
||||||
|
goto('/workspace');
|
||||||
|
if ($mobile) {
|
||||||
|
await tick();
|
||||||
|
showSidebar.set(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class="self-center mr-3">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
class="size-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="self-center truncate">{$i18n.t('Workspace')}</div>
|
||||||
|
</a>
|
||||||
|
{#if shiftKey}
|
||||||
|
<Tooltip
|
||||||
|
content={isPinned('workspace')
|
||||||
|
? $i18n.t('Unpin from Sidebar')
|
||||||
|
: $i18n.t('Pin to Sidebar')}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="p-1 mr-1 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition"
|
||||||
|
on:click|preventDefault|stopPropagation={() => togglePin('workspace')}
|
||||||
|
>
|
||||||
|
{#if isPinned('workspace')}
|
||||||
|
<PinSlash className="size-3.5" strokeWidth="1.5" />
|
||||||
|
{:else}
|
||||||
|
<Pin className="size-3.5" strokeWidth="1.5" />
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if $config?.features?.enable_automations && ($user?.role === 'admin' || $user?.permissions?.features?.automations)}
|
{#if $config?.features?.enable_automations && ($user?.role === 'admin' || $user?.permissions?.features?.automations)}
|
||||||
<a
|
<div class="flex items-center w-full">
|
||||||
href="/automations"
|
<a
|
||||||
draggable="false"
|
href="/automations"
|
||||||
class="flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none"
|
draggable="false"
|
||||||
on:click={async (e) => {
|
class="flex flex-1 rounded-xl py-1.5 px-3 hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none"
|
||||||
if (e.metaKey || e.ctrlKey || e.shiftKey || e.button === 1) return;
|
on:click={async (e) => {
|
||||||
e.preventDefault();
|
if (e.metaKey || e.ctrlKey || e.shiftKey || e.button === 1) return;
|
||||||
show = false;
|
e.preventDefault();
|
||||||
goto('/automations');
|
show = false;
|
||||||
if ($mobile) {
|
goto('/automations');
|
||||||
await tick();
|
if ($mobile) {
|
||||||
showSidebar.set(false);
|
await tick();
|
||||||
}
|
showSidebar.set(false);
|
||||||
}}
|
}
|
||||||
>
|
}}
|
||||||
<div class="self-center mr-3">
|
>
|
||||||
<svg
|
<div class="self-center mr-3">
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<svg
|
||||||
fill="none"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 24 24"
|
fill="none"
|
||||||
stroke-width="1.5"
|
viewBox="0 0 24 24"
|
||||||
stroke="currentColor"
|
stroke-width="1.5"
|
||||||
class="size-5"
|
stroke="currentColor"
|
||||||
|
class="size-5"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="self-center truncate">{$i18n.t('Automations')}</div>
|
||||||
|
</a>
|
||||||
|
{#if shiftKey}
|
||||||
|
<Tooltip
|
||||||
|
content={isPinned('automations')
|
||||||
|
? $i18n.t('Unpin from Sidebar')
|
||||||
|
: $i18n.t('Pin to Sidebar')}
|
||||||
>
|
>
|
||||||
<path
|
<button
|
||||||
stroke-linecap="round"
|
type="button"
|
||||||
stroke-linejoin="round"
|
class="p-1 mr-1 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition"
|
||||||
d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
|
on:click|preventDefault|stopPropagation={() => togglePin('automations')}
|
||||||
/>
|
>
|
||||||
</svg>
|
{#if isPinned('automations')}
|
||||||
</div>
|
<PinSlash className="size-3.5" strokeWidth="1.5" />
|
||||||
<div class="self-center truncate">{$i18n.t('Automations')}</div>
|
{:else}
|
||||||
</a>
|
<Pin className="size-3.5" strokeWidth="1.5" />
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if $config?.features?.enable_calendar && ($user?.role === 'admin' || $user?.permissions?.features?.calendar)}
|
{#if $config?.features?.enable_calendar && ($user?.role === 'admin' || $user?.permissions?.features?.calendar)}
|
||||||
<a
|
<div class="flex items-center w-full">
|
||||||
href="/calendar"
|
<a
|
||||||
draggable="false"
|
href="/calendar"
|
||||||
class="flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none"
|
draggable="false"
|
||||||
on:click={async (e) => {
|
class="flex flex-1 rounded-xl py-1.5 px-3 hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none"
|
||||||
if (e.metaKey || e.ctrlKey || e.shiftKey || e.button === 1) return;
|
on:click={async (e) => {
|
||||||
e.preventDefault();
|
if (e.metaKey || e.ctrlKey || e.shiftKey || e.button === 1) return;
|
||||||
show = false;
|
e.preventDefault();
|
||||||
goto('/calendar');
|
show = false;
|
||||||
}}
|
goto('/calendar');
|
||||||
>
|
}}
|
||||||
<div class="self-center mr-3">
|
>
|
||||||
<svg
|
<div class="self-center mr-3">
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<svg
|
||||||
fill="none"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 24 24"
|
fill="none"
|
||||||
stroke-width="1.5"
|
viewBox="0 0 24 24"
|
||||||
stroke="currentColor"
|
stroke-width="1.5"
|
||||||
class="size-5"
|
stroke="currentColor"
|
||||||
|
class="size-5"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75m-18 0v-7.5A2.25 2.25 0 0 1 5.25 9h13.5A2.25 2.25 0 0 1 21 11.25v7.5"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="self-center truncate">{$i18n.t('Calendar')}</div>
|
||||||
|
</a>
|
||||||
|
{#if shiftKey}
|
||||||
|
<Tooltip
|
||||||
|
content={isPinned('calendar')
|
||||||
|
? $i18n.t('Unpin from Sidebar')
|
||||||
|
: $i18n.t('Pin to Sidebar')}
|
||||||
>
|
>
|
||||||
<path
|
<button
|
||||||
stroke-linecap="round"
|
type="button"
|
||||||
stroke-linejoin="round"
|
class="p-1 mr-1 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition"
|
||||||
d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75m-18 0v-7.5A2.25 2.25 0 0 1 5.25 9h13.5A2.25 2.25 0 0 1 21 11.25v7.5"
|
on:click|preventDefault|stopPropagation={() => togglePin('calendar')}
|
||||||
/>
|
>
|
||||||
</svg>
|
{#if isPinned('calendar')}
|
||||||
</div>
|
<PinSlash className="size-3.5" strokeWidth="1.5" />
|
||||||
<div class="self-center truncate">{$i18n.t('Calendar')}</div>
|
{:else}
|
||||||
</a>
|
<Pin className="size-3.5" strokeWidth="1.5" />
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if role === 'admin'}
|
{#if role === 'admin'}
|
||||||
<a
|
<div class="flex items-center w-full">
|
||||||
href="/playground"
|
<a
|
||||||
draggable="false"
|
href="/playground"
|
||||||
class="flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none"
|
draggable="false"
|
||||||
on:click={async (e) => {
|
class="flex flex-1 rounded-xl py-1.5 px-3 hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none"
|
||||||
if (e.metaKey || e.ctrlKey || e.shiftKey || e.button === 1) {
|
on:click={async (e) => {
|
||||||
return;
|
if (e.metaKey || e.ctrlKey || e.shiftKey || e.button === 1) return;
|
||||||
}
|
e.preventDefault();
|
||||||
e.preventDefault();
|
show = false;
|
||||||
show = false;
|
goto('/playground');
|
||||||
goto('/playground');
|
if ($mobile) {
|
||||||
if ($mobile) {
|
await tick();
|
||||||
await tick();
|
showSidebar.set(false);
|
||||||
showSidebar.set(false);
|
}
|
||||||
}
|
}}
|
||||||
}}
|
>
|
||||||
>
|
<div class="self-center mr-3">
|
||||||
<div class=" self-center mr-3">
|
<Code className="size-5" strokeWidth="1.5" />
|
||||||
<Code className="size-5" strokeWidth="1.5" />
|
</div>
|
||||||
</div>
|
<div class="self-center truncate">{$i18n.t('Playground')}</div>
|
||||||
<div class=" self-center truncate">{$i18n.t('Playground')}</div>
|
</a>
|
||||||
</a>
|
{#if shiftKey}
|
||||||
|
<Tooltip
|
||||||
|
content={isPinned('playground')
|
||||||
|
? $i18n.t('Unpin from Sidebar')
|
||||||
|
: $i18n.t('Pin to Sidebar')}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="p-1 mr-1 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition"
|
||||||
|
on:click|preventDefault|stopPropagation={() => togglePin('playground')}
|
||||||
|
>
|
||||||
|
{#if isPinned('playground')}
|
||||||
|
<PinSlash className="size-3.5" strokeWidth="1.5" />
|
||||||
|
{:else}
|
||||||
|
<Pin className="size-3.5" strokeWidth="1.5" />
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<hr class=" border-gray-50/30 dark:border-gray-800/30 my-1 p-0" />
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none"
|
class="flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none"
|
||||||
type="button"
|
type="button"
|
||||||
@@ -328,6 +512,26 @@
|
|||||||
<div class=" self-center truncate">{$i18n.t('Archived Chats')}</div>
|
<div class=" self-center truncate">{$i18n.t('Archived Chats')}</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none"
|
||||||
|
type="button"
|
||||||
|
on:click={async () => {
|
||||||
|
show = false;
|
||||||
|
|
||||||
|
await showSettings.set(true);
|
||||||
|
|
||||||
|
if ($mobile) {
|
||||||
|
await tick();
|
||||||
|
showSidebar.set(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class=" self-center mr-3">
|
||||||
|
<Settings className="w-5 h-5" strokeWidth="1.5" />
|
||||||
|
</div>
|
||||||
|
<div class=" self-center truncate">{$i18n.t('Settings')}</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
{#if role === 'admin'}
|
{#if role === 'admin'}
|
||||||
<a
|
<a
|
||||||
href="/admin"
|
href="/admin"
|
||||||
|
|||||||
@@ -230,6 +230,7 @@ type Settings = {
|
|||||||
ctrlEnterToSend?: boolean;
|
ctrlEnterToSend?: boolean;
|
||||||
renderMarkdownInPreviews?: boolean;
|
renderMarkdownInPreviews?: boolean;
|
||||||
recentEmojis?: string[];
|
recentEmojis?: string[];
|
||||||
|
pinnedMenuItems?: string[];
|
||||||
|
|
||||||
system?: string;
|
system?: string;
|
||||||
seed?: number;
|
seed?: number;
|
||||||
|
|||||||
Reference in New Issue
Block a user