This commit is contained in:
Timothy Jaeryang Baek committed 2026-07-14 01:34:41 -04:00
1 parent a9617ca218
commit 34813573fa
34 files changed
+554 -652

No files matched your search

+40 -59
View File
@@ -33,12 +33,12 @@
import GarbageBin from '../icons/GarbageBin.svelte'; import GarbageBin from '../icons/GarbageBin.svelte';
import Search from '../icons/Search.svelte'; import Search from '../icons/Search.svelte';
import Plus from '../icons/Plus.svelte'; import Plus from '../icons/Plus.svelte';
import ChevronRight from '../icons/ChevronRight.svelte';
import XMark from '../icons/XMark.svelte'; import XMark from '../icons/XMark.svelte';
import AddFunctionMenu from './Functions/AddFunctionMenu.svelte'; import AddFunctionMenu from './Functions/AddFunctionMenu.svelte';
import ImportModal from '../ImportModal.svelte'; import ImportModal from '../ImportModal.svelte';
import ViewSelector from '../workspace/common/ViewSelector.svelte'; import ViewSelector from '../workspace/common/ViewSelector.svelte';
import TagSelector from '../workspace/common/TagSelector.svelte'; import TagSelector from '../workspace/common/TagSelector.svelte';
import CommunityDiscover from '../workspace/common/CommunityDiscover.svelte';
import { capitalizeFirstLetter } from '$lib/utils'; import { capitalizeFirstLetter } from '$lib/utils';
import Spinner from '../common/Spinner.svelte'; import Spinner from '../common/Spinner.svelte';
@@ -352,11 +352,9 @@
</div> </div>
</div> </div>
<div <div class="space-y-1">
class="py-2 bg-white dark:bg-gray-900 rounded-3xl border border-gray-100/30 dark:border-gray-850/30" <div class="flex h-8 flex-1 items-center w-full gap-2">
> <div class="flex min-w-0 flex-1">
<div class="px-3.5 flex flex-1 items-center w-full space-x-2 py-0.5 pb-2">
<div class="flex flex-1">
<div class=" self-center ml-1 mr-3"> <div class=" self-center ml-1 mr-3">
<Search className="size-3.5" /> <Search className="size-3.5" />
</div> </div>
@@ -381,47 +379,49 @@
</div> </div>
{/if} {/if}
</div> </div>
</div>
<div
class="px-3 flex w-full bg-transparent overflow-x-auto scrollbar-none"
on:wheel={(e) => {
if (e.deltaY !== 0) {
e.preventDefault();
e.currentTarget.scrollLeft += e.deltaY;
}
}}
>
<div <div
class="flex gap-0.5 w-fit text-center text-sm rounded-full bg-transparent px-0.5 whitespace-nowrap" class="flex max-w-[55%] shrink-0 overflow-x-auto scrollbar-none"
bind:this={tagsContainerElement} bind:this={tagsContainerElement}
on:wheel={(e) => {
if (e.deltaY !== 0) {
e.preventDefault();
e.currentTarget.scrollLeft += e.deltaY;
}
}}
> >
<ViewSelector <div
bind:value={viewOption} class="flex w-fit gap-0.5 text-center text-sm rounded-full bg-transparent whitespace-nowrap"
onChange={async (value) => { >
localStorage.workspaceViewOption = value; <ViewSelector
bind:value={viewOption}
align="end"
onChange={async (value) => {
localStorage.workspaceViewOption = value;
await tick(); await tick();
}} }}
/> />
<TagSelector <TagSelector
bind:value={selectedType} bind:value={selectedType}
items={[ align="end"
{ value: 'pipe', label: $i18n.t('Pipe') }, items={[
{ value: 'filter', label: $i18n.t('Filter') }, { value: 'pipe', label: $i18n.t('Pipe') },
{ value: 'action', label: $i18n.t('Action') }, { value: 'filter', label: $i18n.t('Filter') },
{ value: 'event', label: $i18n.t('Event') } { value: 'action', label: $i18n.t('Action') },
]} { value: 'event', label: $i18n.t('Event') }
/> ]}
/>
</div>
</div> </div>
</div> </div>
{#if (filteredItems ?? []).length !== 0} {#if (filteredItems ?? []).length !== 0}
<div class="px-3 my-2 gap-1 lg:gap-2 grid lg:grid-cols-2"> <div class="my-1 gap-x-2 gap-y-0.5 grid lg:grid-cols-2">
{#each filteredItems as func (func.id)} {#each filteredItems as func (func.id)}
<div <div
class=" flex space-x-4 cursor-pointer w-full px-2 py-2 dark:hover:bg-white/5 hover:bg-black/5 rounded-xl" class="flex space-x-4 cursor-pointer w-full px-2.5 py-1.5 rounded-2xl hover:bg-gray-50/70 dark:hover:bg-gray-850/50"
> >
<a <a
class=" flex flex-1 space-x-3.5 cursor-pointer w-full" class=" flex flex-1 space-x-3.5 cursor-pointer w-full"
@@ -606,30 +606,11 @@
</div> --> </div> -->
{#if $config?.features.enable_community_sharing} {#if $config?.features.enable_community_sharing}
<div class=" my-16"> <CommunityDiscover
<div class=" text-xl font-medium mb-1 line-clamp-1"> href="https://openwebui.com/functions"
{$i18n.t('Made by Open WebUI Community')} title={$i18n.t('Discover a function')}
</div> description={$i18n.t('Discover, download, and explore custom functions')}
/>
<a
class=" flex cursor-pointer items-center justify-between hover:bg-gray-50 dark:hover:bg-gray-850 w-full mb-2 px-3.5 py-1.5 rounded-xl transition"
href="https://openwebui.com/functions"
target="_blank"
>
<div class=" self-center">
<div class=" font-semibold line-clamp-1">{$i18n.t('Discover a function')}</div>
<div class=" text-sm line-clamp-1">
{$i18n.t('Discover, download, and explore custom functions')}
</div>
</div>
<div>
<div>
<ChevronRight />
</div>
</div>
</a>
</div>
{/if} {/if}
</div> </div>
@@ -33,7 +33,7 @@
<div slot="content"> <div slot="content">
<DropdownMenu className="min-w-[170px]"> <DropdownMenu className="min-w-[170px]">
<button <button
class="select-none flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[13px] cursor-pointer hover:bg-gray-50/60 dark:hover:bg-gray-800/60" class="select-none 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"
draggable="false" draggable="false"
on:click={() => { on:click={() => {
editHandler(); editHandler();
@@ -59,7 +59,7 @@
</button> </button>
<button <button
class="select-none flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[13px] cursor-pointer hover:bg-gray-50/60 dark:hover:bg-gray-800/60" class="select-none 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"
draggable="false" draggable="false"
on:click={() => { on:click={() => {
cloneHandler(); cloneHandler();
@@ -71,7 +71,7 @@
</button> </button>
<button <button
class="select-none flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[13px] cursor-pointer hover:bg-gray-50/60 dark:hover:bg-gray-800/60" class="select-none 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"
draggable="false" draggable="false"
on:click={() => { on:click={() => {
runHandler(); runHandler();
@@ -99,7 +99,7 @@
<hr class="border-gray-50 dark:border-gray-850/30 mx-1 my-0.5" /> <hr class="border-gray-50 dark:border-gray-850/30 mx-1 my-0.5" />
<button <button
class="select-none flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[13px] cursor-pointer hover:bg-gray-50/60 dark:hover:bg-gray-800/60" class="select-none 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"
draggable="false" draggable="false"
on:click={() => { on:click={() => {
deleteHandler(); deleteHandler();
@@ -39,7 +39,7 @@
<button <button
type="button" type="button"
class="flex items-center gap-1.5 px-2.5 py-1.5 rounded-2xl text-xs transition class="flex items-center gap-1.5 px-2.5 py-1.5 rounded-2xl text-xs transition
text-gray-600 dark:text-gray-400 hover:bg-black/5 dark:hover:bg-white/5" text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-100"
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@@ -91,8 +91,8 @@
{#each filteredModels as model (model.id)} {#each filteredModels as model (model.id)}
<button <button
class="h-[1.6875rem] px-2 rounded-xl w-full text-left text-[13px] {model_id === model.id class="h-[1.6875rem] px-2 rounded-xl w-full text-left text-[13px] {model_id === model.id
? 'bg-gray-50 dark:bg-gray-800' ? 'text-gray-900 dark:text-gray-100'
: ''}" : 'text-gray-700 hover:text-gray-900 dark:text-gray-300 dark:hover:text-gray-100'}"
type="button" type="button"
on:click={() => { on:click={() => {
model_id = model.id; model_id = model.id;
@@ -161,7 +161,7 @@
<button <button
type="button" type="button"
class="flex items-center gap-1.5 px-2.5 py-1.5 rounded-2xl text-xs transition class="flex items-center gap-1.5 px-2.5 py-1.5 rounded-2xl text-xs transition
text-gray-600 dark:text-gray-400 hover:bg-black/5 dark:hover:bg-white/5" text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-100"
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@@ -284,7 +284,7 @@
<button <button
type="button" type="button"
class="flex-1 py-1 text-xs rounded-xl transition {selectedDays.includes(d.key) class="flex-1 py-1 text-xs rounded-xl transition {selectedDays.includes(d.key)
? 'bg-gray-50 dark:bg-gray-800 text-black dark:text-gray-100' ? 'text-black dark:text-gray-100'
: 'text-gray-400 dark:text-gray-500 hover:text-gray-700 dark:hover:text-gray-200'}" : 'text-gray-400 dark:text-gray-500 hover:text-gray-700 dark:hover:text-gray-200'}"
on:click={() => { on:click={() => {
if (selectedDays.includes(d.key)) { if (selectedDays.includes(d.key)) {
@@ -31,7 +31,7 @@
type="button" type="button"
class="flex items-center gap-1.5 px-2.5 py-1.5 rounded-2xl text-xs transition class="flex items-center gap-1.5 px-2.5 py-1.5 rounded-2xl text-xs transition
{terminalServerId ? 'text-black dark:text-gray-100' : 'text-gray-600 dark:text-gray-400'} {terminalServerId ? 'text-black dark:text-gray-100' : 'text-gray-600 dark:text-gray-400'}
hover:bg-black/5 dark:hover:bg-white/5" hover:text-gray-900 dark:hover:text-gray-100"
> >
<Cloud className="size-3.5 shrink-0" strokeWidth="2" /> <Cloud className="size-3.5 shrink-0" strokeWidth="2" />
<span class="whitespace-nowrap max-w-32 truncate">{terminalLabel}</span> <span class="whitespace-nowrap max-w-32 truncate">{terminalLabel}</span>
@@ -59,8 +59,8 @@
<button <button
class="flex w-full h-[1.6875rem] justify-between gap-2 items-center px-2 text-[13px] cursor-pointer rounded-xl {terminalServerId === class="flex w-full h-[1.6875rem] justify-between gap-2 items-center px-2 text-[13px] cursor-pointer rounded-xl {terminalServerId ===
server.id server.id
? 'bg-gray-50 dark:bg-gray-800/50' ? 'text-gray-900 dark:text-gray-100'
: 'hover:bg-gray-50 dark:hover:bg-gray-800/50'}" : 'text-gray-700 hover:text-gray-900 dark:text-gray-300 dark:hover:text-gray-100'}"
type="button" type="button"
on:click={() => { on:click={() => {
if (terminalServerId === server.id) { if (terminalServerId === server.id) {
@@ -4,7 +4,7 @@
</script> </script>
<div <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]:flex [&>button]:w-full [&>button]:h-[1.6875rem] [&>button]:items-center [&>button]:gap-2 [&>button]:rounded-xl! [&>button]:px-2! [&>button]:text-[13px] [&>button:hover]:bg-gray-50/60 dark:[&>button:hover]:bg-gray-800/60 [&>a]:flex [&>a]:w-full [&>a]:h-[1.6875rem] [&>a]:items-center [&>a]:gap-2 [&>a]:rounded-xl! [&>a]:px-2! [&>a]:text-[13px] [&>a:hover]:bg-gray-50/60 dark:[&>a:hover]:bg-gray-800/60 [&>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-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`}
{style} {style}
> >
<slot /> <slot />
@@ -30,8 +30,8 @@
{align} {align}
triggerClass={className triggerClass={className
? className ? className
: 'flex shrink-0 items-center gap-2 bg-transparent px-0.5 text-sm placeholder-gray-400 outline-hidden focus:outline-hidden'} : '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 w-full h-[1.6875rem] gap-2 items-center rounded-xl px-2 text-[13px] cursor-pointer hover:bg-gray-50/60 dark:hover:bg-gray-800/60" 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"
onChange={(v) => { onChange={(v) => {
onChange(v); onChange(v);
}} }}
+1 -1
View File
@@ -29,7 +29,7 @@
/** CSS classes for each item button */ /** CSS classes for each item button */
export let itemClass = export let itemClass =
'flex w-full gap-2 items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl'; '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';
/** Alignment of the dropdown: 'start' | 'end' */ /** Alignment of the dropdown: 'start' | 'end' */
export let align = 'start'; export let align = 'start';
+2 -2
View File
@@ -30,7 +30,7 @@
<div slot="content"> <div slot="content">
<DropdownMenu className="min-w-[170px] font-primary"> <DropdownMenu className="min-w-[170px] font-primary">
<button <button
class="flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[13px] hover:bg-gray-50/60 dark:hover:bg-gray-800/60 transition" class="flex h-[1.6875rem] w-full items-center gap-2 rounded-xl bg-transparent px-2 text-[13px] transition hover:text-gray-900 dark:hover:text-gray-100"
on:click={async () => { on:click={async () => {
onEdit(); onEdit();
show = false; show = false;
@@ -43,7 +43,7 @@
</button> </button>
<button <button
class="flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[13px] hover:bg-gray-50/60 dark:hover:bg-gray-800/60 transition" class="flex h-[1.6875rem] w-full items-center gap-2 rounded-xl bg-transparent px-2 text-[13px] transition hover:text-gray-900 dark:hover:text-gray-100"
on:click={() => { on:click={() => {
onChat(); onChat();
show = false; show = false;
+54 -49
View File
@@ -30,7 +30,7 @@
$: loadLocale($i18n.languages); $: loadLocale($i18n.languages);
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { WEBUI_NAME, config, user, pinnedNotes } from '$lib/stores'; import { WEBUI_NAME, config, user, pinnedNotes, mobile, showSidebar } from '$lib/stores';
import { import {
createNewNote, createNewNote,
deleteNoteById, deleteNoteById,
@@ -47,7 +47,6 @@
import DeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte'; import DeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
import Search from '../icons/Search.svelte'; import Search from '../icons/Search.svelte';
import Plus from '../icons/Plus.svelte'; import Plus from '../icons/Plus.svelte';
import ChevronRight from '../icons/ChevronRight.svelte';
import Spinner from '../common/Spinner.svelte'; import Spinner from '../common/Spinner.svelte';
import Tooltip from '../common/Tooltip.svelte'; import Tooltip from '../common/Tooltip.svelte';
import NoteMenu from './Notes/NoteMenu.svelte'; import NoteMenu from './Notes/NoteMenu.svelte';
@@ -55,6 +54,7 @@
import XMark from '../icons/XMark.svelte'; import XMark from '../icons/XMark.svelte';
import DropdownOptions from '../common/DropdownOptions.svelte'; import DropdownOptions from '../common/DropdownOptions.svelte';
import Loader from '../common/Loader.svelte'; import Loader from '../common/Loader.svelte';
import SidebarIcon from '../icons/Sidebar.svelte';
let loaded = false; let loaded = false;
@@ -324,7 +324,7 @@
<FilesOverlay show={dragged} /> <FilesOverlay show={dragged} />
<div id="notes-container" class="w-full min-h-full h-full px-3 md:px-[18px]"> <div id="notes-container" class="w-full min-h-full h-full">
{#if loaded} {#if loaded}
<DeleteConfirmDialog <DeleteConfirmDialog
bind:show={showDeleteConfirm} bind:show={showDeleteConfirm}
@@ -339,15 +339,31 @@
</div> </div>
</DeleteConfirmDialog> </DeleteConfirmDialog>
<div class="flex flex-col gap-1 px-1 mt-1.5 mb-3"> <div class="flex flex-col gap-1 px-1 mt-1.5 mb-2">
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<div class="flex items-center md:self-center text-xl font-medium px-0.5 gap-2 shrink-0"> <div class="flex items-center md:self-center text-xl font-medium px-0.5 gap-2 shrink-0">
{#if $mobile}
<Tooltip content={$showSidebar ? $i18n.t('Close Sidebar') : $i18n.t('Open Sidebar')}>
<button
id="sidebar-toggle-button"
class="cursor-pointer flex rounded-lg hover:bg-gray-100 dark:hover:bg-gray-850 transition"
on:click={() => {
showSidebar.set(!$showSidebar);
}}
>
<div class="self-center p-1.5">
<SidebarIcon />
</div>
</button>
</Tooltip>
{/if}
<div> <div>
{$i18n.t('Notes')} {$i18n.t('Notes')}
</div> </div>
<div class="text-lg font-medium text-gray-500 dark:text-gray-500"> <div class="text-lg font-medium text-gray-500 dark:text-gray-500">
{total} {total ?? ''}
</div> </div>
</div> </div>
@@ -364,17 +380,15 @@
> >
<Plus className="size-3" strokeWidth="2.5" /> <Plus className="size-3" strokeWidth="2.5" />
<div class=" ml-1 text-xs">{$i18n.t('New Note')}</div> <div class="hidden md:block md:ml-1 text-xs">{$i18n.t('New Note')}</div>
</button> </button>
</div> </div>
</div> </div>
</div> </div>
<div <div class="space-y-1">
class="py-2 bg-white dark:bg-gray-900 rounded-3xl border border-gray-100/30 dark:border-gray-850/30" <div class="flex h-8 flex-1 items-center w-full gap-2">
> <div class="flex min-w-0 flex-1 items-center">
<div class="px-3.5 flex flex-1 items-center w-full space-x-2 py-0.5 pb-2">
<div class="flex flex-1 items-center">
<div class=" self-center ml-1 mr-3"> <div class=" self-center ml-1 mr-3">
<Search className="size-3.5" /> <Search className="size-3.5" />
</div> </div>
@@ -399,11 +413,9 @@
</div> </div>
{/if} {/if}
</div> </div>
</div>
<div class="px-3 flex justify-between">
<div <div
class="flex w-full bg-transparent overflow-x-auto scrollbar-none" class="flex max-w-[55%] shrink-0 overflow-x-auto scrollbar-none"
on:wheel={(e) => { on:wheel={(e) => {
if (e.deltaY !== 0) { if (e.deltaY !== 0) {
e.preventDefault(); e.preventDefault();
@@ -412,11 +424,10 @@
}} }}
> >
<div <div
class="flex gap-3 w-fit text-center text-sm rounded-full bg-transparent px-0.5 whitespace-nowrap" class="flex w-fit gap-0.5 text-center text-sm rounded-full bg-transparent whitespace-nowrap"
> >
<DropdownOptions <DropdownOptions
align="start" align="end"
className="flex shrink-0 items-center gap-2 px-3 py-1.5 text-sm bg-gray-50 dark:bg-gray-850 rounded-xl placeholder-gray-400 outline-hidden focus:outline-hidden"
bind:value={viewOption} bind:value={viewOption}
items={[ items={[
{ value: null, label: $i18n.t('All') }, { value: null, label: $i18n.t('All') },
@@ -434,7 +445,7 @@
{#if [null, 'shared'].includes(viewOption)} {#if [null, 'shared'].includes(viewOption)}
<DropdownOptions <DropdownOptions
align="start" align="end"
bind:value={permission} bind:value={permission}
items={[ items={[
{ value: null, label: $i18n.t('Write') }, { value: null, label: $i18n.t('Write') },
@@ -442,25 +453,23 @@
]} ]}
/> />
{/if} {/if}
</div>
</div>
<div class="shrink-0"> <DropdownOptions
<DropdownOptions align="end"
align="start" bind:value={displayOption}
bind:value={displayOption} items={[
items={[ { value: null, label: $i18n.t('List') },
{ value: null, label: $i18n.t('List') }, { value: 'grid', label: $i18n.t('Grid') }
{ value: 'grid', label: $i18n.t('Grid') } ]}
]} onChange={() => {
onChange={() => { if (displayOption) {
if (displayOption) { localStorage.noteDisplayOption = displayOption;
localStorage.noteDisplayOption = displayOption; } else {
} else { delete localStorage.noteDisplayOption;
delete localStorage.noteDisplayOption; }
} }}
}} />
/> </div>
</div> </div>
</div> </div>
@@ -468,22 +477,20 @@
{#if (items ?? []).length > 0} {#if (items ?? []).length > 0}
{@const groupedNotes = groupNotes(items)} {@const groupedNotes = groupNotes(items)}
<div class="@container h-full py-2.5 px-2.5"> <div class="@container h-full my-1">
<div class=""> <div class="">
{#each groupedNotes as [timeRange, notesList], idx} {#each groupedNotes as [timeRange, notesList], idx}
<div <div class="w-full text-xs text-gray-500 dark:text-gray-500 px-2.5 pb-2.5">
class="w-full text-xs text-gray-500 dark:text-gray-500 font-medium px-2.5 pb-2.5"
>
{$i18n.t(timeRange)} {$i18n.t(timeRange)}
</div> </div>
{#if displayOption === null} {#if displayOption === null}
<div <div
class="{groupedNotes.length - 1 !== idx ? 'mb-3' : ''} gap-1.5 flex flex-col" class="{groupedNotes.length - 1 !== idx ? 'mb-3' : ''} gap-y-0.5 flex flex-col"
> >
{#each notesList as note, idx (note.id)} {#each notesList as note, idx (note.id)}
<div <div
class=" flex cursor-pointer w-full px-3.5 py-1.5 border border-gray-50 dark:border-gray-850/30 bg-transparent dark:hover:bg-gray-850 hover:bg-white rounded-2xl transition" class="flex cursor-pointer w-full px-3 py-2 bg-transparent hover:bg-gray-50/70 dark:hover:bg-gray-850/50 rounded-2xl transition"
> >
<a href={`/notes/${note.id}`} class="w-full flex flex-col justify-between"> <a href={`/notes/${note.id}`} class="w-full flex flex-col justify-between">
<div class="flex-1"> <div class="flex-1">
@@ -493,9 +500,7 @@
className="flex-1" className="flex-1"
placement="top-start" placement="top-start"
> >
<div <div class="text-sm capitalize flex-1 w-full line-clamp-1">
class=" text-sm font-medium capitalize flex-1 w-full line-clamp-1"
>
{note.title} {note.title}
</div> </div>
</Tooltip> </Tooltip>
@@ -555,7 +560,7 @@
}} }}
> >
<button <button
class="self-center w-fit text-sm p-1 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl" class="self-center w-fit text-sm p-1 text-gray-500 hover:text-gray-900 dark:text-gray-300 dark:hover:text-gray-100 rounded-xl"
type="button" type="button"
> >
<EllipsisHorizontal className="size-5" /> <EllipsisHorizontal className="size-5" />
@@ -577,7 +582,7 @@
> >
{#each notesList as note, idx (note.id)} {#each notesList as note, idx (note.id)}
<div <div
class=" flex space-x-4 cursor-pointer w-full px-4.5 py-4 border border-gray-50 dark:border-gray-850/30 bg-transparent dark:hover:bg-gray-850 hover:bg-white rounded-2xl transition" class="flex space-x-4 cursor-pointer w-full px-3 py-2.5 bg-transparent hover:bg-gray-50/70 dark:hover:bg-gray-850/50 rounded-2xl transition"
> >
<div class=" flex flex-1 space-x-4 cursor-pointer w-full"> <div class=" flex flex-1 space-x-4 cursor-pointer w-full">
<a <a
@@ -588,7 +593,7 @@
<div <div
class=" flex items-center gap-2 self-center mb-1 justify-between" class=" flex items-center gap-2 self-center mb-1 justify-between"
> >
<div class=" font-semibold line-clamp-1 capitalize"> <div class="line-clamp-1 capitalize">
{note.title} {note.title}
</div> </div>
@@ -625,7 +630,7 @@
}} }}
> >
<button <button
class="self-center w-fit text-sm p-1 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl" class="self-center w-fit text-sm p-1 text-gray-500 hover:text-gray-900 dark:text-gray-300 dark:hover:text-gray-100 rounded-xl"
type="button" type="button"
> >
<EllipsisHorizontal className="size-5" /> <EllipsisHorizontal className="size-5" />
@@ -43,14 +43,14 @@
<DropdownSub contentClass="select-none z-50"> <DropdownSub contentClass="select-none z-50">
<button <button
slot="trigger" slot="trigger"
class="flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[13px] cursor-pointer hover:bg-gray-50/60 dark:hover:bg-gray-800/60" class="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"
> >
<Download className="size-3.5" strokeWidth="2" /> <Download className="size-3.5" strokeWidth="2" />
<div class="flex items-center">{$i18n.t('Download')}</div> <div class="flex items-center">{$i18n.t('Download')}</div>
</button> </button>
<button <button
class="select-none flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[13px] cursor-pointer hover:bg-gray-50/60 dark:hover:bg-gray-800/60" class="select-none 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"
on:click={() => { on:click={() => {
onDownload('txt'); onDownload('txt');
}} }}
@@ -59,7 +59,7 @@
</button> </button>
<button <button
class="select-none flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[13px] cursor-pointer hover:bg-gray-50/60 dark:hover:bg-gray-800/60" class="select-none 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"
on:click={() => { on:click={() => {
onDownload('md'); onDownload('md');
}} }}
@@ -68,7 +68,7 @@
</button> </button>
<button <button
class="select-none flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[13px] cursor-pointer hover:bg-gray-50/60 dark:hover:bg-gray-800/60" class="select-none 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"
on:click={() => { on:click={() => {
onDownload('pdf'); onDownload('pdf');
}} }}
@@ -81,7 +81,7 @@
<DropdownSub contentClass="select-none z-50"> <DropdownSub contentClass="select-none z-50">
<button <button
slot="trigger" slot="trigger"
class="flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[13px] cursor-pointer hover:bg-gray-50/60 dark:hover:bg-gray-800/60" class="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"
> >
<Share className="size-3.5" strokeWidth="2" /> <Share className="size-3.5" strokeWidth="2" />
<div class="flex items-center">{$i18n.t('Share')}</div> <div class="flex items-center">{$i18n.t('Share')}</div>
@@ -89,7 +89,7 @@
{#if onCopyLink} {#if onCopyLink}
<button <button
class="select-none flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[13px] cursor-pointer hover:bg-gray-50/60 dark:hover:bg-gray-800/60" class="select-none 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"
on:click={() => { on:click={() => {
onCopyLink(); onCopyLink();
}} }}
@@ -101,7 +101,7 @@
{#if onCopyToClipboard} {#if onCopyToClipboard}
<button <button
class="select-none flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[13px] cursor-pointer hover:bg-gray-50/60 dark:hover:bg-gray-800/60" class="select-none 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"
on:click={() => { on:click={() => {
onCopyToClipboard(); onCopyToClipboard();
}} }}
@@ -115,7 +115,7 @@
{#if onPin} {#if onPin}
<button <button
class="select-none flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[13px] cursor-pointer hover:bg-gray-50/60 dark:hover:bg-gray-800/60" class="select-none 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"
on:click={() => { on:click={() => {
onPin(); onPin();
show = false; show = false;
@@ -132,7 +132,7 @@
{/if} {/if}
<button <button
class="select-none flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[13px] cursor-pointer hover:bg-gray-50/60 dark:hover:bg-gray-800/60" class="select-none 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"
on:click={() => { on:click={() => {
onDelete(); onDelete();
}} }}
+3 -3
View File
@@ -31,7 +31,7 @@
<div slot="content"> <div slot="content">
<DropdownMenu className="min-w-[170px] font-primary"> <DropdownMenu className="min-w-[170px] font-primary">
<button <button
class="flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[13px] hover:bg-gray-50/60 dark:hover:bg-gray-800/60 transition" class="flex h-[1.6875rem] w-full items-center gap-2 rounded-xl bg-transparent px-2 text-[13px] transition hover:text-gray-900 dark:hover:text-gray-100"
on:click={async () => { on:click={async () => {
onRecord(); onRecord();
show = false; show = false;
@@ -44,7 +44,7 @@
</button> </button>
<button <button
class="flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[13px] hover:bg-gray-50/60 dark:hover:bg-gray-800/60 transition" class="flex h-[1.6875rem] w-full items-center gap-2 rounded-xl bg-transparent px-2 text-[13px] transition hover:text-gray-900 dark:hover:text-gray-100"
on:click={() => { on:click={() => {
onCaptureAudio(); onCaptureAudio();
show = false; show = false;
@@ -57,7 +57,7 @@
</button> </button>
<button <button
class="flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[13px] hover:bg-gray-50/60 dark:hover:bg-gray-800/60 transition" class="flex h-[1.6875rem] w-full items-center gap-2 rounded-xl bg-transparent px-2 text-[13px] transition hover:text-gray-900 dark:hover:text-gray-100"
on:click={() => { on:click={() => {
onUpload(); onUpload();
show = false; show = false;
+40 -43
View File
@@ -29,6 +29,7 @@
import Tooltip from '../common/Tooltip.svelte'; import Tooltip from '../common/Tooltip.svelte';
import XMark from '../icons/XMark.svelte'; import XMark from '../icons/XMark.svelte';
import ViewSelector from './common/ViewSelector.svelte'; import ViewSelector from './common/ViewSelector.svelte';
import TagSelector from './common/TagSelector.svelte';
import Loader from '../common/Loader.svelte'; import Loader from '../common/Loader.svelte';
type KnowledgeListItem = { type KnowledgeListItem = {
@@ -187,7 +188,7 @@
}} }}
/> />
<div class="flex flex-col gap-1 px-1 mt-1.5 mb-3"> <div class="flex flex-col gap-1 px-1 mt-1.5 mb-2">
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<div class="flex items-center md:self-center text-xl font-medium px-0.5 gap-2 shrink-0"> <div class="flex items-center md:self-center text-xl font-medium px-0.5 gap-2 shrink-0">
<div> <div>
@@ -212,11 +213,9 @@
</div> </div>
</div> </div>
<div <div class="space-y-1">
class="py-2 bg-white dark:bg-gray-900 rounded-3xl border border-gray-100/30 dark:border-gray-850/30" <div class="flex h-8 w-full items-center gap-2">
> <div class="flex min-w-0 flex-1">
<div class=" flex w-full space-x-2 py-0.5 px-3.5 pb-2">
<div class="flex flex-1">
<div class=" self-center ml-1 mr-3"> <div class=" self-center ml-1 mr-3">
<Search className="size-3.5" /> <Search className="size-3.5" />
</div> </div>
@@ -242,52 +241,54 @@
</div> </div>
{/if} {/if}
</div> </div>
</div>
<div
class="px-3 flex w-full bg-transparent overflow-x-auto scrollbar-none -mx-1"
on:wheel={(e) => {
if (e.deltaY !== 0) {
e.preventDefault();
e.currentTarget.scrollLeft += e.deltaY;
}
}}
>
<div <div
class="flex gap-0.5 w-fit text-center text-sm rounded-full bg-transparent px-1.5 whitespace-nowrap" class="flex max-w-[55%] shrink-0 overflow-x-auto scrollbar-none"
bind:this={tagsContainerElement} bind:this={tagsContainerElement}
on:wheel={(e) => {
if (e.deltaY !== 0) {
e.preventDefault();
e.currentTarget.scrollLeft += e.deltaY;
}
}}
> >
<ViewSelector <div
bind:value={viewOption} class="flex w-fit gap-0.5 text-center text-sm rounded-full bg-transparent whitespace-nowrap"
onChange={async (value) => {
localStorage.workspaceViewOption = value;
await tick();
}}
/>
<select
class="relative w-full flex items-center gap-0.5 px-2.5 py-1.5 bg-gray-50 dark:bg-gray-850 rounded-xl outline-hidden"
bind:value={sourceOption}
on:change={async () => {
localStorage.workspaceKnowledgeSourceOption = sourceOption;
await tick();
}}
> >
<option value="">{$i18n.t('All Sources')}</option> <ViewSelector
<option value="local">{$i18n.t('Local')}</option> bind:value={viewOption}
<option value="external">{$i18n.t('Connected')}</option> align="end"
</select> onChange={async (value) => {
localStorage.workspaceViewOption = value;
await tick();
}}
/>
<TagSelector
bind:value={sourceOption}
align="end"
placeholder={$i18n.t('All Sources')}
items={[
{ value: 'local', label: $i18n.t('Local') },
{ value: 'external', label: $i18n.t('Connected') }
]}
onChange={async () => {
localStorage.workspaceKnowledgeSourceOption = sourceOption;
await tick();
}}
/>
</div>
</div> </div>
</div> </div>
{#if items !== null && total !== null} {#if items !== null && total !== null}
{#if (items ?? []).length !== 0} {#if (items ?? []).length !== 0}
<!-- The Aleph dreams itself into being, and the void learns its own name --> <!-- The Aleph dreams itself into being, and the void learns its own name -->
<div class=" my-2 px-3 grid grid-cols-1 lg:grid-cols-2 gap-2"> <div class="my-1 grid grid-cols-1 lg:grid-cols-2 gap-x-2 gap-y-0.5">
{#each items as item} {#each items as item}
<button <button
class=" flex space-x-4 cursor-pointer text-left w-full px-3 py-2.5 dark:hover:bg-gray-850/50 hover:bg-gray-50 transition rounded-2xl" class="flex space-x-4 cursor-pointer text-left w-full px-2.5 py-1.5 transition rounded-2xl hover:bg-gray-50/70 dark:hover:bg-gray-850/50"
on:click={() => { on:click={() => {
if (item?.meta?.document) { if (item?.meta?.document) {
toast.error( toast.error(
@@ -413,10 +414,6 @@
</div> </div>
{/if} {/if}
</div> </div>
<div class=" text-gray-500 text-xs m-2">
ⓘ {$i18n.t("Use '#' in the prompt input to load and include your knowledge.")}
</div>
{:else} {:else}
<div class="w-full h-full flex justify-center items-center"> <div class="w-full h-full flex justify-center items-center">
<Spinner className="size-5" /> <Spinner className="size-5" />
@@ -46,7 +46,7 @@
<DropdownMenu className="min-w-[170px]"> <DropdownMenu className="min-w-[170px]">
{#if onExport} {#if onExport}
<button <button
class="select-none flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[13px] cursor-pointer hover:bg-gray-50/60 dark:hover:bg-gray-800/60" class="select-none 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"
on:click={() => { on:click={() => {
onExport(); onExport();
}} }}
@@ -57,7 +57,7 @@
{/if} {/if}
<button <button
class="select-none flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[13px] cursor-pointer hover:bg-gray-50/60 dark:hover:bg-gray-800/60" class="select-none 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"
on:click={() => { on:click={() => {
dispatch('delete'); dispatch('delete');
}} }}
@@ -1410,7 +1410,7 @@
<Dropdown align="end"> <Dropdown align="end">
<button <button
class="p-1.5 mr-1 rounded-xl text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-800 transition" class="p-1.5 mr-1 rounded-xl text-gray-500 bg-transparent hover:text-gray-900 dark:hover:text-gray-100 transition"
type="button" type="button"
> >
<AdjustmentsHorizontal className="size-3.5" strokeWidth="2" /> <AdjustmentsHorizontal className="size-3.5" strokeWidth="2" />
@@ -1419,7 +1419,7 @@
<div slot="content"> <div slot="content">
<DropdownMenu className="min-w-[180px]"> <DropdownMenu className="min-w-[180px]">
<button <button
class="select-none flex gap-2 items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl w-full" class="select-none 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"
type="button" type="button"
on:click={() => { on:click={() => {
includeContent = !includeContent; includeContent = !includeContent;
@@ -1482,8 +1482,8 @@
class="flex gap-3 w-fit text-center text-sm rounded-full bg-transparent px-0.5 whitespace-nowrap" class="flex gap-3 w-fit text-center text-sm rounded-full bg-transparent px-0.5 whitespace-nowrap"
> >
<DropdownOptions <DropdownOptions
align="start" align="end"
className="flex shrink-0 items-center gap-2 px-3 py-1.5 text-sm bg-gray-50 dark:bg-gray-850 rounded-xl placeholder-gray-400 outline-hidden focus:outline-hidden" 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"
bind:value={viewOption} bind:value={viewOption}
items={[ items={[
{ value: null, label: $i18n.t('All') }, { value: null, label: $i18n.t('All') },
@@ -1500,19 +1500,19 @@
/> />
<DropdownOptions <DropdownOptions
align="start" align="end"
bind:value={sortKey} bind:value={sortKey}
placeholder={$i18n.t('Sort')} placeholder={$i18n.t('Sort')}
items={[ items={[
{ value: 'name', label: $i18n.t('Name') }, { value: 'name', label: $i18n.t('Name') },
{ value: 'created_at', label: $i18n.t('Created At') }, { value: 'created_at', label: $i18n.t('Created') },
{ value: 'updated_at', label: $i18n.t('Updated At') } { value: 'updated_at', label: $i18n.t('Updated') }
]} ]}
/> />
{#if sortKey} {#if sortKey}
<DropdownOptions <DropdownOptions
align="start" align="end"
bind:value={direction} bind:value={direction}
items={[ items={[
{ value: 'asc', label: $i18n.t('Asc') }, { value: 'asc', label: $i18n.t('Asc') },
@@ -36,7 +36,7 @@
> >
<Tooltip content={$i18n.t('Add Content')}> <Tooltip content={$i18n.t('Add Content')}>
<button <button
class=" p-1.5 rounded-xl hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 transition font-medium text-sm flex items-center space-x-1" class="p-1.5 rounded-xl bg-transparent transition text-[13px] font-normal flex items-center space-x-1 hover:text-gray-900 dark:hover:text-gray-100"
on:click={(e) => { on:click={(e) => {
e.stopPropagation(); e.stopPropagation();
show = true; show = true;
@@ -58,7 +58,7 @@
<div slot="content"> <div slot="content">
<DropdownMenu className="min-w-[200px] transition"> <DropdownMenu className="min-w-[200px] transition">
<button <button
class="select-none flex gap-2 items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl w-full" class="select-none 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"
on:click={() => { on:click={() => {
onUpload({ type: 'new_directory' }); onUpload({ type: 'new_directory' });
show = false; show = false;
@@ -71,7 +71,7 @@
<hr class="my-1 border-gray-100 dark:border-gray-800" /> <hr class="my-1 border-gray-100 dark:border-gray-800" />
<button <button
class="select-none flex gap-2 items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl w-full" class="select-none 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"
on:click={() => { on:click={() => {
onUpload({ type: 'files' }); onUpload({ type: 'files' });
}} }}
@@ -81,7 +81,7 @@
</button> </button>
<button <button
class="select-none flex gap-2 items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl w-full" class="select-none 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"
on:click={() => { on:click={() => {
onUpload({ type: 'directory' }); onUpload({ type: 'directory' });
}} }}
@@ -97,7 +97,7 @@
className="w-full" className="w-full"
> >
<button <button
class="select-none flex gap-2 items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl w-full" class="select-none 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"
on:click={() => { on:click={() => {
onSync(); onSync();
}} }}
@@ -108,7 +108,7 @@
</Tooltip> </Tooltip>
<button <button
class="select-none flex gap-2 items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl w-full" class="select-none 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"
on:click={() => { on:click={() => {
onUpload({ type: 'web' }); onUpload({ type: 'web' });
}} }}
@@ -118,7 +118,7 @@
</button> </button>
<button <button
class="select-none flex gap-2 items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl w-full" class="select-none 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"
on:click={() => { on:click={() => {
onUpload({ type: 'text' }); onUpload({ type: 'text' });
}} }}
@@ -130,7 +130,7 @@
<hr class="my-1 border-gray-100 dark:border-gray-800" /> <hr class="my-1 border-gray-100 dark:border-gray-800" />
<button <button
class="select-none flex gap-2 items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl w-full" class="select-none 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"
on:click={() => { on:click={() => {
onReset(); onReset();
show = false; show = false;
@@ -175,7 +175,7 @@
<DropdownMenu className="min-w-[140px] z-[9999999]"> <DropdownMenu className="min-w-[140px] z-[9999999]">
<button <button
type="button" type="button"
class="select-none flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition items-center gap-2 text-sm" class="select-none flex h-[1.6875rem] w-full cursor-pointer items-center gap-2 rounded-xl bg-transparent px-2 text-[13px] transition hover:text-gray-900 dark:hover:text-gray-100"
on:click={() => startRename()} on:click={() => startRename()}
> >
<Pencil className="size-3.5" /> <Pencil className="size-3.5" />
@@ -183,7 +183,7 @@
</button> </button>
<button <button
type="button" type="button"
class="select-none flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition items-center gap-2 text-sm" class="select-none flex h-[1.6875rem] w-full cursor-pointer items-center gap-2 rounded-xl bg-transparent px-2 text-[13px] transition hover:text-gray-900 dark:hover:text-gray-100"
on:click={() => onDelete(directory.id)} on:click={() => onDelete(directory.id)}
> >
<GarbageBin className="size-3.5" /> <GarbageBin className="size-3.5" />
@@ -188,7 +188,7 @@
<DropdownMenu className="min-w-[140px] z-[9999999]"> <DropdownMenu className="min-w-[140px] z-[9999999]">
<button <button
type="button" type="button"
class="select-none flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition items-center gap-2 text-sm" class="select-none flex h-[1.6875rem] w-full cursor-pointer items-center gap-2 rounded-xl bg-transparent px-2 text-[13px] transition hover:text-gray-900 dark:hover:text-gray-100"
on:click={() => { on:click={() => {
startRename(file); startRename(file);
}} }}
@@ -198,7 +198,7 @@
</button> </button>
<button <button
type="button" type="button"
class="select-none flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition items-center gap-2 text-sm" class="select-none flex h-[1.6875rem] w-full cursor-pointer items-center gap-2 rounded-xl bg-transparent px-2 text-[13px] transition hover:text-gray-900 dark:hover:text-gray-100"
on:click={() => { on:click={() => {
let fileId = file?.id ?? file?.tempId; let fileId = file?.id ?? file?.tempId;
window.open(`${WEBUI_BASE_URL}/api/v1/files/${fileId}/content`, '_blank'); window.open(`${WEBUI_BASE_URL}/api/v1/files/${fileId}/content`, '_blank');
@@ -209,7 +209,7 @@
</button> </button>
<button <button
type="button" type="button"
class="select-none flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition items-center gap-2 text-sm" class="select-none flex h-[1.6875rem] w-full cursor-pointer items-center gap-2 rounded-xl bg-transparent px-2 text-[13px] transition hover:text-gray-900 dark:hover:text-gray-100"
on:click={() => { on:click={() => {
onDelete(file?.id ?? file?.tempId); onDelete(file?.id ?? file?.tempId);
}} }}
+104 -123
View File
@@ -37,17 +37,18 @@
import GarbageBin from '../icons/GarbageBin.svelte'; import GarbageBin from '../icons/GarbageBin.svelte';
import Search from '../icons/Search.svelte'; import Search from '../icons/Search.svelte';
import Plus from '../icons/Plus.svelte'; import Plus from '../icons/Plus.svelte';
import ChevronRight from '../icons/ChevronRight.svelte';
import Switch from '../common/Switch.svelte'; import Switch from '../common/Switch.svelte';
import Spinner from '../common/Spinner.svelte'; import Spinner from '../common/Spinner.svelte';
import XMark from '../icons/XMark.svelte'; import XMark from '../icons/XMark.svelte';
import EyeSlash from '../icons/EyeSlash.svelte'; import EyeSlash from '../icons/EyeSlash.svelte';
import Eye from '../icons/Eye.svelte'; import Eye from '../icons/Eye.svelte';
import ChevronDown from '../icons/ChevronDown.svelte';
import Dropdown from '$lib/components/common/Dropdown.svelte'; import Dropdown from '$lib/components/common/Dropdown.svelte';
import DropdownMenu from '$lib/components/common/DropdownMenu.svelte'; import DropdownMenu from '$lib/components/common/DropdownMenu.svelte';
import ViewSelector from './common/ViewSelector.svelte'; import ViewSelector from './common/ViewSelector.svelte';
import TagSelector from './common/TagSelector.svelte'; import TagSelector from './common/TagSelector.svelte';
import CommunityDiscover from './common/CommunityDiscover.svelte';
import Pagination from '../common/Pagination.svelte'; import Pagination from '../common/Pagination.svelte';
import Badge from '$lib/components/common/Badge.svelte'; import Badge from '$lib/components/common/Badge.svelte';
@@ -373,7 +374,7 @@
}} }}
/> />
<div class="flex flex-col gap-1 px-1 mt-1.5 mb-3"> <div class="flex flex-col gap-1 px-1 mt-1.5 mb-2">
<input <input
id="models-import-input" id="models-import-input"
bind:this={modelsImportInputElement} bind:this={modelsImportInputElement}
@@ -481,11 +482,9 @@
</div> </div>
</div> </div>
<div <div class="space-y-1">
class="py-2 bg-white dark:bg-gray-900 rounded-3xl border border-gray-100/30 dark:border-gray-850/30" <div class="flex h-8 flex-1 items-center w-full gap-2">
> <div class="flex min-w-0 flex-1 items-center">
<div class="px-3.5 flex flex-1 items-center w-full space-x-2 py-0.5 pb-2">
<div class="flex flex-1 items-center">
<div class=" self-center ml-1 mr-3"> <div class=" self-center ml-1 mr-3">
<Search className="size-3.5" /> <Search className="size-3.5" />
</div> </div>
@@ -520,113 +519,114 @@
</div> </div>
{/if} {/if}
</div> </div>
</div>
<div
class="px-3 flex w-full bg-transparent overflow-x-auto scrollbar-none"
on:wheel={(e) => {
if (e.deltaY !== 0) {
e.preventDefault();
e.currentTarget.scrollLeft += e.deltaY;
}
}}
>
<div <div
class="flex gap-0.5 w-fit text-center text-sm rounded-full bg-transparent px-0.5 whitespace-nowrap" class="flex max-w-[60%] shrink-0 items-center gap-1 overflow-x-auto scrollbar-none"
bind:this={tagsContainerElement} bind:this={tagsContainerElement}
on:wheel={(e) => {
if (e.deltaY !== 0) {
e.preventDefault();
e.currentTarget.scrollLeft += e.deltaY;
}
}}
> >
<ViewSelector <div
bind:value={viewOption} class="flex w-fit gap-0.5 text-center text-sm rounded-full bg-transparent whitespace-nowrap"
onChange={async (value) => { >
localStorage.workspaceViewOption = value; <ViewSelector
await tick(); bind:value={viewOption}
}} align="end"
/> onChange={async (value) => {
localStorage.workspaceViewOption = value;
{#if (tags ?? []).length > 0} await tick();
<TagSelector }}
bind:value={selectedTag}
items={tags.map((tag) => {
return { value: tag, label: tag };
})}
/> />
{/if}
</div>
<div class="flex-1"></div> {#if (tags ?? []).length > 0}
<TagSelector
<Dropdown> bind:value={selectedTag}
<Tooltip content={$i18n.t('Actions')}> align="end"
<button items={tags.map((tag) => {
class="p-1 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition" return { value: tag, label: tag };
type="button" })}
> />
<EllipsisHorizontal className="size-4" /> {/if}
</button>
</Tooltip>
<div slot="content">
<DropdownMenu className="w-[170px] shadow-sm">
<button
class="select-none flex w-full gap-2 items-center px-3 py-1.5 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
type="button"
on:click={() => {
enableAllHandler();
}}
>
<CheckCircle className="size-4" />
<div class="flex items-center">{$i18n.t('Enable All')}</div>
</button>
<button
class="select-none flex w-full gap-2 items-center px-3 py-1.5 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
type="button"
on:click={() => {
disableAllHandler();
}}
>
<Minus className="size-4" />
<div class="flex items-center">{$i18n.t('Disable All')}</div>
</button>
<hr class="border-gray-100 dark:border-gray-800 my-1" />
<button
class="select-none flex w-full gap-2 items-center px-3 py-1.5 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
type="button"
on:click={() => {
showAllHandler();
}}
>
<Eye className="size-4" />
<div class="flex items-center">{$i18n.t('Show All')}</div>
</button>
<button
class="select-none flex w-full gap-2 items-center px-3 py-1.5 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
type="button"
on:click={() => {
hideAllHandler();
}}
>
<EyeSlash className="size-4" />
<div class="flex items-center">{$i18n.t('Hide All')}</div>
</button>
</DropdownMenu>
</div> </div>
</Dropdown>
<Dropdown align="end">
<Tooltip content={$i18n.t('Actions')}>
<button
class="flex h-8 items-center gap-1.5 rounded-xl bg-transparent px-1.5 text-[13px] font-normal text-gray-700 transition hover:text-gray-900 dark:text-gray-200 dark:hover:text-gray-100"
type="button"
>
<span>{$i18n.t('Actions')}</span>
<ChevronDown className="size-3" strokeWidth="2.5" />
</button>
</Tooltip>
<div slot="content">
<DropdownMenu className="w-[170px] shadow-sm">
<button
class="flex h-[1.6875rem] w-full cursor-pointer select-none items-center gap-2 rounded-xl bg-transparent px-2 text-[13px] hover:text-gray-900 dark:hover:text-gray-100"
type="button"
on:click={() => {
enableAllHandler();
}}
>
<CheckCircle className="size-3.5" />
<div class="flex items-center">{$i18n.t('Enable All')}</div>
</button>
<button
class="flex h-[1.6875rem] w-full cursor-pointer select-none items-center gap-2 rounded-xl bg-transparent px-2 text-[13px] hover:text-gray-900 dark:hover:text-gray-100"
type="button"
on:click={() => {
disableAllHandler();
}}
>
<Minus className="size-3.5" />
<div class="flex items-center">{$i18n.t('Disable All')}</div>
</button>
<hr class="mx-1 my-0.5 border-gray-100 dark:border-gray-800" />
<button
class="flex h-[1.6875rem] w-full cursor-pointer select-none items-center gap-2 rounded-xl bg-transparent px-2 text-[13px] hover:text-gray-900 dark:hover:text-gray-100"
type="button"
on:click={() => {
showAllHandler();
}}
>
<Eye className="size-3.5" />
<div class="flex items-center">{$i18n.t('Show All')}</div>
</button>
<button
class="flex h-[1.6875rem] w-full cursor-pointer select-none items-center gap-2 rounded-xl bg-transparent px-2 text-[13px] hover:text-gray-900 dark:hover:text-gray-100"
type="button"
on:click={() => {
hideAllHandler();
}}
>
<EyeSlash className="size-3.5" />
<div class="flex items-center">{$i18n.t('Hide All')}</div>
</button>
</DropdownMenu>
</div>
</Dropdown>
</div>
</div> </div>
{#if models !== null} {#if models !== null}
{#if (models ?? []).length !== 0} {#if (models ?? []).length !== 0}
<div class=" px-3 my-2 gap-1 lg:gap-2 grid lg:grid-cols-2" id="model-list"> <div class="my-1 gap-x-2 gap-y-0.5 grid lg:grid-cols-2" id="model-list">
{#each models as model (model.id)} {#each models as model (model.id)}
<!-- svelte-ignore a11y_no_static_element_interactions --> <!-- svelte-ignore a11y_no_static_element_interactions -->
<!-- svelte-ignore a11y_click_events_have_key_events --> <!-- svelte-ignore a11y_click_events_have_key_events -->
<div <div
class="flex transition rounded-2xl w-full p-2.5 {model.write_access class="flex transition w-full px-2.5 py-1.5 rounded-2xl {model.write_access
? 'cursor-pointer dark:hover:bg-gray-850/50 hover:bg-gray-50' ? 'cursor-pointer hover:bg-gray-50/70 dark:hover:bg-gray-850/50'
: 'dark:hover:bg-gray-850/50 hover:bg-gray-50'}" : ''}"
id="model-item-{model.id}" id="model-item-{model.id}"
on:click={() => { on:click={() => {
if (model.write_access) { if (model.write_access) {
@@ -849,30 +849,11 @@
</div> </div>
{#if $config?.features.enable_community_sharing} {#if $config?.features.enable_community_sharing}
<div class=" my-16"> <CommunityDiscover
<div class=" text-xl font-medium mb-1 line-clamp-1"> href="https://openwebui.com/models"
{$i18n.t('Made by Open WebUI Community')} title={$i18n.t('Discover a model')}
</div> description={$i18n.t('Discover, download, and explore model presets')}
/>
<a
class=" flex cursor-pointer items-center justify-between hover:bg-gray-50 dark:hover:bg-gray-850 w-full mb-2 px-3.5 py-1.5 rounded-xl transition"
href="https://openwebui.com/models"
target="_blank"
>
<div class=" self-center">
<div class=" font-medium line-clamp-1">{$i18n.t('Discover a model')}</div>
<div class=" text-sm line-clamp-1">
{$i18n.t('Discover, download, and explore model presets')}
</div>
</div>
<div>
<div>
<ChevronRight />
</div>
</div>
</a>
</div>
{/if} {/if}
{:else} {:else}
<div class="w-full h-full flex justify-center items-center"> <div class="w-full h-full flex justify-center items-center">
@@ -155,7 +155,7 @@
{/if} {/if}
<div <div
class="h-[1.6875rem] px-2 rounded-xl w-full text-left flex justify-between items-center text-[13px] hover:bg-gray-50/60 hover:dark:bg-gray-800/60 hover:dark:text-gray-100 selected-command-option-button" class="h-[1.6875rem] px-2 rounded-xl w-full text-left flex justify-between items-center text-[13px] bg-transparent hover:text-gray-900 dark:hover:text-gray-100 selected-command-option-button"
> >
<button <button
class="w-full flex-1" class="w-full flex-1"
@@ -62,7 +62,7 @@
<DropdownMenu className="min-w-[170px]"> <DropdownMenu className="min-w-[170px]">
{#if writeAccess} {#if writeAccess}
<button <button
class="select-none flex gap-2 items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl w-full" class="select-none 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"
on:click={() => { on:click={() => {
editHandler(); editHandler();
}} }}
@@ -74,7 +74,7 @@
{#if writeAccess} {#if writeAccess}
<button <button
class="select-none flex gap-2 items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl w-full" class="select-none 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"
on:click={() => { on:click={() => {
hideHandler(); hideHandler();
}} }}
@@ -127,7 +127,7 @@
{/if} {/if}
<button <button
class="select-none flex gap-2 items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl w-full" class="select-none 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"
on:click={() => { on:click={() => {
pinModelHandler(model?.id); pinModelHandler(model?.id);
}} }}
@@ -149,7 +149,7 @@
{#if writeAccess} {#if writeAccess}
<button <button
class="select-none flex gap-2 items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl w-full" class="select-none 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"
on:click={() => { on:click={() => {
cloneHandler(); cloneHandler();
}} }}
@@ -165,7 +165,7 @@
{/if} {/if}
<button <button
class="select-none flex gap-2 items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl w-full" class="select-none 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"
on:click={() => { on:click={() => {
copyLinkHandler(); copyLinkHandler();
}} }}
@@ -177,7 +177,7 @@
{#if writeAccess && ($currentUser?.role === 'admin' || $currentUser?.permissions?.workspace?.models_export)} {#if writeAccess && ($currentUser?.role === 'admin' || $currentUser?.permissions?.workspace?.models_export)}
<button <button
class="select-none flex gap-2 items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl w-full" class="select-none 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"
on:click={() => { on:click={() => {
exportHandler(); exportHandler();
}} }}
@@ -190,7 +190,7 @@
{#if writeAccess && $config?.features.enable_community_sharing} {#if writeAccess && $config?.features.enable_community_sharing}
<button <button
class="select-none flex gap-2 items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl w-full" class="select-none 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"
on:click={() => { on:click={() => {
shareHandler(); shareHandler();
}} }}
@@ -204,7 +204,7 @@
<hr class="border-gray-50/30 dark:border-gray-800/30 my-1" /> <hr class="border-gray-50/30 dark:border-gray-800/30 my-1" />
<button <button
class="select-none flex gap-2 items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl w-full" class="select-none 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"
on:click={() => { on:click={() => {
deleteHandler(); deleteHandler();
}} }}
+39 -58
View File
@@ -23,13 +23,13 @@
import DeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte'; import DeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
import Search from '../icons/Search.svelte'; import Search from '../icons/Search.svelte';
import Plus from '../icons/Plus.svelte'; import Plus from '../icons/Plus.svelte';
import ChevronRight from '../icons/ChevronRight.svelte';
import Spinner from '../common/Spinner.svelte'; import Spinner from '../common/Spinner.svelte';
import Tooltip from '../common/Tooltip.svelte'; import Tooltip from '../common/Tooltip.svelte';
import XMark from '../icons/XMark.svelte'; import XMark from '../icons/XMark.svelte';
import GarbageBin from '../icons/GarbageBin.svelte'; import GarbageBin from '../icons/GarbageBin.svelte';
import ViewSelector from './common/ViewSelector.svelte'; import ViewSelector from './common/ViewSelector.svelte';
import TagSelector from './common/TagSelector.svelte'; import TagSelector from './common/TagSelector.svelte';
import CommunityDiscover from './common/CommunityDiscover.svelte';
import Badge from '$lib/components/common/Badge.svelte'; import Badge from '$lib/components/common/Badge.svelte';
import Switch from '../common/Switch.svelte'; import Switch from '../common/Switch.svelte';
import Pagination from '../common/Pagination.svelte'; import Pagination from '../common/Pagination.svelte';
@@ -231,7 +231,7 @@
</div> </div>
</DeleteConfirmDialog> </DeleteConfirmDialog>
<div class="flex flex-col gap-1 px-1 mt-1.5 mb-3"> <div class="flex flex-col gap-1 px-1 mt-1.5 mb-2">
<input <input
id="prompts-import-input" id="prompts-import-input"
bind:this={promptsImportInputElement} bind:this={promptsImportInputElement}
@@ -323,11 +323,9 @@
</div> </div>
</div> </div>
<div <div class="space-y-1">
class="py-2 bg-white dark:bg-gray-900 rounded-3xl border border-gray-100/30 dark:border-gray-850/30" <div class="flex h-8 w-full items-center gap-2">
> <div class="flex min-w-0 flex-1">
<div class=" flex w-full space-x-2 py-0.5 px-3.5 pb-2">
<div class="flex flex-1">
<div class=" self-center ml-1 mr-3"> <div class=" self-center ml-1 mr-3">
<Search className="size-3.5" /> <Search className="size-3.5" />
</div> </div>
@@ -354,36 +352,38 @@
</div> </div>
{/if} {/if}
</div> </div>
</div>
<div
class="px-3 flex w-full bg-transparent overflow-x-auto scrollbar-none -mx-1"
on:wheel={(e) => {
if (e.deltaY !== 0) {
e.preventDefault();
e.currentTarget.scrollLeft += e.deltaY;
}
}}
>
<div <div
class="flex gap-0.5 w-fit text-center text-sm rounded-full bg-transparent px-1.5 whitespace-nowrap" class="flex max-w-[55%] shrink-0 overflow-x-auto scrollbar-none"
bind:this={tagsContainerElement} bind:this={tagsContainerElement}
on:wheel={(e) => {
if (e.deltaY !== 0) {
e.preventDefault();
e.currentTarget.scrollLeft += e.deltaY;
}
}}
> >
<ViewSelector <div
bind:value={viewOption} class="flex w-fit gap-0.5 text-center text-sm rounded-full bg-transparent whitespace-nowrap"
onChange={async (value) => { >
localStorage.workspaceViewOption = value; <ViewSelector
page = 1; bind:value={viewOption}
await tick(); align="end"
}} onChange={async (value) => {
/> localStorage.workspaceViewOption = value;
page = 1;
{#if (tags ?? []).length > 0} await tick();
<TagSelector }}
bind:value={selectedTag}
items={tags.map((tag) => ({ value: tag, label: tag }))}
/> />
{/if}
{#if (tags ?? []).length > 0}
<TagSelector
bind:value={selectedTag}
align="end"
items={tags.map((tag) => ({ value: tag, label: tag }))}
/>
{/if}
</div>
</div> </div>
</div> </div>
@@ -393,10 +393,10 @@
</div> </div>
{:else if (prompts ?? []).length !== 0} {:else if (prompts ?? []).length !== 0}
<!-- Before they call, I will answer; while they are yet speaking, I will hear. --> <!-- Before they call, I will answer; while they are yet speaking, I will hear. -->
<div class="gap-2 grid my-2 px-3 lg:grid-cols-2"> <div class="gap-x-2 gap-y-0.5 grid my-1 lg:grid-cols-2">
{#each prompts as prompt (prompt.id)} {#each prompts as prompt (prompt.id)}
<a <a
class=" flex space-x-4 cursor-pointer text-left w-full px-3 py-2.5 dark:hover:bg-gray-850/50 hover:bg-gray-50 transition rounded-2xl" class="flex space-x-4 cursor-pointer text-left w-full px-2.5 py-1.5 transition rounded-2xl hover:bg-gray-50/70 dark:hover:bg-gray-850/50"
href={`/workspace/prompts/${prompt.id}`} href={`/workspace/prompts/${prompt.id}`}
> >
<div class=" flex flex-col flex-1 space-x-4 cursor-pointer w-full pl-1"> <div class=" flex flex-col flex-1 space-x-4 cursor-pointer w-full pl-1">
@@ -535,30 +535,11 @@
</div> </div>
{#if $config?.features.enable_community_sharing} {#if $config?.features.enable_community_sharing}
<div class=" my-16"> <CommunityDiscover
<div class=" text-xl font-medium mb-1 line-clamp-1"> href="https://openwebui.com/prompts"
{$i18n.t('Made by Open WebUI Community')} title={$i18n.t('Discover a prompt')}
</div> description={$i18n.t('Discover, download, and explore custom prompts')}
/>
<a
class=" flex cursor-pointer items-center justify-between hover:bg-gray-50 dark:hover:bg-gray-850 w-full mb-2 px-3.5 py-1.5 rounded-xl transition"
href="https://openwebui.com/prompts"
target="_blank"
>
<div class=" self-center">
<div class=" font-medium line-clamp-1">{$i18n.t('Discover a prompt')}</div>
<div class=" text-sm line-clamp-1">
{$i18n.t('Discover, download, and explore custom prompts')}
</div>
</div>
<div>
<div>
<ChevronRight />
</div>
</div>
</a>
</div>
{/if} {/if}
{:else} {:else}
<div class="w-full h-full flex justify-center items-center"> <div class="w-full h-full flex justify-center items-center">
@@ -62,7 +62,7 @@
</Tooltip> </Tooltip>
{:else} {:else}
<button <button
class="select-none flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[13px] cursor-pointer hover:bg-gray-50/60 dark:hover:bg-gray-800/60" class="select-none 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"
on:click={() => { on:click={() => {
show = false; show = false;
showDeleteConfirmDialog = true; showDeleteConfirmDialog = true;
@@ -37,7 +37,7 @@
<div slot="content"> <div slot="content">
<DropdownMenu className="min-w-[170px]"> <DropdownMenu className="min-w-[170px]">
<button <button
class="select-none flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[13px] cursor-pointer hover:bg-gray-50/60 dark:hover:bg-gray-800/60" class="select-none 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"
draggable="false" draggable="false"
on:click={() => { on:click={() => {
editHandler(); editHandler();
@@ -63,7 +63,7 @@
{#if $config.features.enable_community_sharing} {#if $config.features.enable_community_sharing}
<button <button
class="select-none flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[13px] cursor-pointer hover:bg-gray-50/60 dark:hover:bg-gray-800/60" class="select-none 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"
draggable="false" draggable="false"
on:click={() => { on:click={() => {
shareHandler(); shareHandler();
@@ -75,7 +75,7 @@
{/if} {/if}
<button <button
class="select-none flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[13px] cursor-pointer hover:bg-gray-50/60 dark:hover:bg-gray-800/60" class="select-none 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"
draggable="false" draggable="false"
on:click={() => { on:click={() => {
cloneHandler(); cloneHandler();
@@ -87,7 +87,7 @@
{#if $user?.role === 'admin' || $user?.permissions?.workspace?.prompts_export} {#if $user?.role === 'admin' || $user?.permissions?.workspace?.prompts_export}
<button <button
class="select-none flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[13px] cursor-pointer hover:bg-gray-50/60 dark:hover:bg-gray-800/60" class="select-none 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"
draggable="false" draggable="false"
on:click={() => { on:click={() => {
exportHandler(); exportHandler();
@@ -101,7 +101,7 @@
<hr class="border-gray-50 dark:border-gray-850/30 mx-1 my-0.5" /> <hr class="border-gray-50 dark:border-gray-850/30 mx-1 my-0.5" />
<button <button
class="select-none flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[13px] cursor-pointer hover:bg-gray-50/60 dark:hover:bg-gray-800/60" class="select-none 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"
draggable="false" draggable="false"
on:click={() => { on:click={() => {
deleteHandler(); deleteHandler();
+27 -28
View File
@@ -184,7 +184,7 @@
</svelte:head> </svelte:head>
{#if loaded} {#if loaded}
<div class="flex flex-col gap-1 px-1 mt-1.5 mb-3"> <div class="flex flex-col gap-1 px-1 mt-1.5 mb-2">
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<div class="flex items-center md:self-center text-xl font-medium px-0.5 gap-2 shrink-0"> <div class="flex items-center md:self-center text-xl font-medium px-0.5 gap-2 shrink-0">
<div> <div>
@@ -312,11 +312,9 @@
</div> </div>
</div> </div>
<div <div class="space-y-1">
class="py-2 bg-white dark:bg-gray-900 rounded-3xl border border-gray-100/30 dark:border-gray-850/30" <div class="flex h-8 w-full items-center gap-2">
> <div class="flex min-w-0 flex-1">
<div class=" flex w-full space-x-2 py-0.5 px-3.5 pb-2">
<div class="flex flex-1">
<div class=" self-center ml-1 mr-3"> <div class=" self-center ml-1 mr-3">
<Search className="size-3.5" /> <Search className="size-3.5" />
</div> </div>
@@ -342,29 +340,30 @@
</div> </div>
{/if} {/if}
</div> </div>
</div>
<div
class="px-3 flex w-full bg-transparent overflow-x-auto scrollbar-none -mx-1"
on:wheel={(e) => {
if (e.deltaY !== 0) {
e.preventDefault();
e.currentTarget.scrollLeft += e.deltaY;
}
}}
>
<div <div
class="flex gap-0.5 w-fit text-center text-sm rounded-full bg-transparent px-1.5 whitespace-nowrap" class="flex max-w-[55%] shrink-0 overflow-x-auto scrollbar-none"
bind:this={tagsContainerElement} bind:this={tagsContainerElement}
on:wheel={(e) => {
if (e.deltaY !== 0) {
e.preventDefault();
e.currentTarget.scrollLeft += e.deltaY;
}
}}
> >
<ViewSelector <div
bind:value={viewOption} class="flex w-fit gap-0.5 text-center text-sm rounded-full bg-transparent whitespace-nowrap"
onChange={async (value) => { >
localStorage.workspaceViewOption = value; <ViewSelector
page = 1; bind:value={viewOption}
await tick(); align="end"
}} onChange={async (value) => {
/> localStorage.workspaceViewOption = value;
page = 1;
await tick();
}}
/>
</div>
</div> </div>
</div> </div>
@@ -373,12 +372,12 @@
<Spinner className="size-5" /> <Spinner className="size-5" />
</div> </div>
{:else if (filteredItems ?? []).length !== 0} {:else if (filteredItems ?? []).length !== 0}
<div class=" my-2 gap-2 grid px-3 lg:grid-cols-2"> <div class="my-1 gap-x-2 gap-y-0.5 grid lg:grid-cols-2">
{#each filteredItems as skill} {#each filteredItems as skill}
<Tooltip content={skill?.description ?? skill?.id}> <Tooltip content={skill?.description ?? skill?.id}>
<div <div
class=" flex space-x-4 text-left w-full px-3 py-2.5 transition rounded-2xl {skill.write_access class="flex space-x-4 text-left w-full px-2.5 py-1.5 transition rounded-2xl {skill.write_access
? 'cursor-pointer dark:hover:bg-gray-850/50 hover:bg-gray-50' ? 'cursor-pointer hover:bg-gray-50/70 dark:hover:bg-gray-850/50'
: 'cursor-not-allowed opacity-60'}" : 'cursor-not-allowed opacity-60'}"
> >
{#if skill.write_access} {#if skill.write_access}
@@ -35,7 +35,7 @@
<div slot="content"> <div slot="content">
<DropdownMenu className="min-w-[170px]"> <DropdownMenu className="min-w-[170px]">
<button <button
class="select-none flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[13px] cursor-pointer hover:bg-gray-50/60 dark:hover:bg-gray-800/60" class="select-none 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"
on:click={() => { on:click={() => {
editHandler(); editHandler();
}} }}
@@ -59,7 +59,7 @@
</button> </button>
<button <button
class="select-none flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[13px] cursor-pointer hover:bg-gray-50/60 dark:hover:bg-gray-800/60" class="select-none 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"
on:click={() => { on:click={() => {
cloneHandler(); cloneHandler();
}} }}
@@ -70,7 +70,7 @@
{#if $user?.role === 'admin' || $user?.permissions?.workspace?.skills_export} {#if $user?.role === 'admin' || $user?.permissions?.workspace?.skills_export}
<button <button
class="select-none flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[13px] cursor-pointer hover:bg-gray-50/60 dark:hover:bg-gray-800/60" class="select-none 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"
on:click={() => { on:click={() => {
exportHandler(); exportHandler();
}} }}
@@ -83,7 +83,7 @@
<hr class="border-gray-50 dark:border-gray-850/30 mx-1 my-0.5" /> <hr class="border-gray-50 dark:border-gray-850/30 mx-1 my-0.5" />
<button <button
class="select-none flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[13px] cursor-pointer hover:bg-gray-50/60 dark:hover:bg-gray-800/60" class="select-none 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"
on:click={() => { on:click={() => {
deleteHandler(); deleteHandler();
}} }}
+32 -52
View File
@@ -31,12 +31,12 @@
import GarbageBin from '../icons/GarbageBin.svelte'; import GarbageBin from '../icons/GarbageBin.svelte';
import Search from '../icons/Search.svelte'; import Search from '../icons/Search.svelte';
import Plus from '../icons/Plus.svelte'; import Plus from '../icons/Plus.svelte';
import ChevronRight from '../icons/ChevronRight.svelte';
import Spinner from '../common/Spinner.svelte'; import Spinner from '../common/Spinner.svelte';
import XMark from '../icons/XMark.svelte'; import XMark from '../icons/XMark.svelte';
import AddToolMenu from './Tools/AddToolMenu.svelte'; import AddToolMenu from './Tools/AddToolMenu.svelte';
import ImportModal from '../ImportModal.svelte'; import ImportModal from '../ImportModal.svelte';
import ViewSelector from './common/ViewSelector.svelte'; import ViewSelector from './common/ViewSelector.svelte';
import CommunityDiscover from './common/CommunityDiscover.svelte';
import Badge from '$lib/components/common/Badge.svelte'; import Badge from '$lib/components/common/Badge.svelte';
let shiftKey = false; let shiftKey = false;
@@ -220,7 +220,7 @@
/> />
{#if loaded} {#if loaded}
<div class="flex flex-col gap-1 px-1 mt-1.5 mb-3"> <div class="flex flex-col gap-1 px-1 mt-1.5 mb-2">
<input <input
id="documents-import-input" id="documents-import-input"
bind:this={toolsImportInputElement} bind:this={toolsImportInputElement}
@@ -313,12 +313,10 @@
</div> </div>
</div> </div>
<div <div class="space-y-1">
class="py-2 bg-white dark:bg-gray-900 rounded-3xl border border-gray-100/30 dark:border-gray-850/30"
>
<!-- The iron remembers its forge. --> <!-- The iron remembers its forge. -->
<div class=" flex w-full space-x-2 py-0.5 px-3.5 pb-2"> <div class="flex h-8 w-full items-center gap-2">
<div class="flex flex-1"> <div class="flex min-w-0 flex-1">
<div class=" self-center ml-1 mr-3"> <div class=" self-center ml-1 mr-3">
<Search className="size-3.5" /> <Search className="size-3.5" />
</div> </div>
@@ -344,39 +342,40 @@
</div> </div>
{/if} {/if}
</div> </div>
</div>
<div
class="px-3 flex w-full bg-transparent overflow-x-auto scrollbar-none -mx-1"
on:wheel={(e) => {
if (e.deltaY !== 0) {
e.preventDefault();
e.currentTarget.scrollLeft += e.deltaY;
}
}}
>
<div <div
class="flex gap-0.5 w-fit text-center text-sm rounded-full bg-transparent px-1.5 whitespace-nowrap" class="flex max-w-[55%] shrink-0 overflow-x-auto scrollbar-none"
bind:this={tagsContainerElement} bind:this={tagsContainerElement}
on:wheel={(e) => {
if (e.deltaY !== 0) {
e.preventDefault();
e.currentTarget.scrollLeft += e.deltaY;
}
}}
> >
<ViewSelector <div
bind:value={viewOption} class="flex w-fit gap-0.5 text-center text-sm rounded-full bg-transparent whitespace-nowrap"
onChange={async (value) => { >
localStorage.workspaceViewOption = value; <ViewSelector
bind:value={viewOption}
align="end"
onChange={async (value) => {
localStorage.workspaceViewOption = value;
await tick(); await tick();
}} }}
/> />
</div>
</div> </div>
</div> </div>
{#if (filteredItems ?? []).length !== 0} {#if (filteredItems ?? []).length !== 0}
<div class=" my-2 gap-2 grid px-3 lg:grid-cols-2"> <div class="my-1 gap-x-2 gap-y-0.5 grid lg:grid-cols-2">
{#each filteredItems as tool} {#each filteredItems as tool}
<Tooltip content={tool?.meta?.description ?? tool?.id}> <Tooltip content={tool?.meta?.description ?? tool?.id}>
<div <div
class=" flex space-x-4 text-left w-full px-3 py-2.5 transition rounded-2xl {tool.write_access class="flex space-x-4 text-left w-full px-2.5 py-1.5 transition rounded-2xl {tool.write_access
? 'cursor-pointer dark:hover:bg-gray-850/50 hover:bg-gray-50' ? 'cursor-pointer hover:bg-gray-50/70 dark:hover:bg-gray-850/50'
: 'cursor-not-allowed opacity-60'}" : 'cursor-not-allowed opacity-60'}"
> >
{#if tool.write_access} {#if tool.write_access}
@@ -565,30 +564,11 @@
</div> </div>
{#if $config?.features.enable_community_sharing} {#if $config?.features.enable_community_sharing}
<div class=" my-16"> <CommunityDiscover
<div class=" text-xl font-medium mb-1 line-clamp-1"> href="https://openwebui.com/tools"
{$i18n.t('Made by Open WebUI Community')} title={$i18n.t('Discover a tool')}
</div> description={$i18n.t('Discover, download, and explore custom tools')}
/>
<a
class=" flex cursor-pointer items-center justify-between hover:bg-gray-50 dark:hover:bg-gray-850 w-full mb-2 px-3.5 py-1.5 rounded-xl transition"
href="https://openwebui.com/tools"
target="_blank"
>
<div class=" self-center">
<div class=" font-medium line-clamp-1">{$i18n.t('Discover a tool')}</div>
<div class=" text-sm line-clamp-1">
{$i18n.t('Discover, download, and explore custom tools')}
</div>
</div>
<div>
<div>
<ChevronRight />
</div>
</div>
</a>
</div>
{/if} {/if}
<DeleteConfirmDialog <DeleteConfirmDialog
@@ -33,7 +33,7 @@
<div slot="content"> <div slot="content">
<DropdownMenu className="min-w-[190px]"> <DropdownMenu className="min-w-[190px]">
<button <button
class="flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[13px] cursor-pointer hover:bg-gray-50/60 dark:hover:bg-gray-800/60" class="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"
on:click={async () => { on:click={async () => {
createHandler(); createHandler();
show = false; show = false;
@@ -46,7 +46,7 @@
</button> </button>
<button <button
class="flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[13px] cursor-pointer hover:bg-gray-50/60 dark:hover:bg-gray-800/60" class="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"
on:click={async () => { on:click={async () => {
importFromLinkHandler(); importFromLinkHandler();
show = false; show = false;
@@ -38,7 +38,7 @@
<div slot="content"> <div slot="content">
<DropdownMenu className="min-w-[170px]"> <DropdownMenu className="min-w-[170px]">
<button <button
class="select-none flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[13px] cursor-pointer hover:bg-gray-50/60 dark:hover:bg-gray-800/60" class="select-none 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"
draggable="false" draggable="false"
on:click={() => { on:click={() => {
editHandler(); editHandler();
@@ -64,7 +64,7 @@
{#if $config.features.enable_community_sharing} {#if $config.features.enable_community_sharing}
<button <button
class="select-none flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[13px] cursor-pointer hover:bg-gray-50/60 dark:hover:bg-gray-800/60" class="select-none 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"
draggable="false" draggable="false"
on:click={() => { on:click={() => {
shareHandler(); shareHandler();
@@ -76,7 +76,7 @@
{/if} {/if}
<button <button
class="select-none flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[13px] cursor-pointer hover:bg-gray-50/60 dark:hover:bg-gray-800/60" class="select-none 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"
draggable="false" draggable="false"
on:click={() => { on:click={() => {
cloneHandler(); cloneHandler();
@@ -89,7 +89,7 @@
{#if $user?.role === 'admin' || $user?.permissions?.workspace?.tools_export} {#if $user?.role === 'admin' || $user?.permissions?.workspace?.tools_export}
<button <button
class="select-none flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[13px] cursor-pointer hover:bg-gray-50/60 dark:hover:bg-gray-800/60" class="select-none 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"
draggable="false" draggable="false"
on:click={() => { on:click={() => {
exportHandler(); exportHandler();
@@ -104,7 +104,7 @@
<hr class="border-gray-50 dark:border-gray-850/30 mx-1 my-0.5" /> <hr class="border-gray-50 dark:border-gray-850/30 mx-1 my-0.5" />
<button <button
class="select-none flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[13px] cursor-pointer hover:bg-gray-50/60 dark:hover:bg-gray-800/60" class="select-none 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"
draggable="false" draggable="false"
on:click={() => { on:click={() => {
deleteHandler(); deleteHandler();
@@ -0,0 +1,34 @@
<script lang="ts">
import { getContext } from 'svelte';
import ChevronRight from '$lib/components/icons/ChevronRight.svelte';
const i18n = getContext('i18n');
export let href = '';
export let title = '';
export let description = '';
</script>
<div class="mt-6 px-2">
<div class="mb-0.5 text-[11px] font-normal text-gray-400 dark:text-gray-600">
{$i18n.t('Made by Open WebUI Community')}
</div>
<a
class="flex w-full cursor-pointer items-center justify-between gap-3 px-0 py-1 text-left transition"
{href}
target="_blank"
>
<div class="min-w-0">
<div class="line-clamp-1 text-[13px] font-normal text-gray-700 dark:text-gray-200">
{title}
</div>
<div class="line-clamp-1 text-xs text-gray-500 dark:text-gray-500">
{description}
</div>
</div>
<ChevronRight className="size-3.5 shrink-0 text-gray-400 dark:text-gray-600" />
</a>
</div>
@@ -10,6 +10,7 @@
export let value = ''; export let value = '';
export let placeholder = $i18n.t('Tag'); export let placeholder = $i18n.t('Tag');
export let align = 'start';
export let onChange: (value: string) => void = () => {}; export let onChange: (value: string) => void = () => {};
export let items = []; export let items = [];
@@ -19,13 +20,14 @@
bind:value bind:value
{items} {items}
{placeholder} {placeholder}
triggerClass="relative w-full flex items-center gap-0.5 px-2.5 py-1.5 rounded-xl " {align}
itemClass="flex w-full gap-2 items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl capitalize" 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"
onChange={() => onChange(value)} onChange={() => onChange(value)}
> >
<svelte:fragment slot="trigger" let:selectedLabel> <svelte:fragment slot="trigger" let:selectedLabel>
<div <div
class="inline-flex h-input px-0.5 w-full outline-hidden bg-transparent truncate placeholder-gray-400 focus:outline-hidden capitalize" class="inline-flex h-input w-full outline-hidden bg-transparent truncate placeholder-gray-400 focus:outline-hidden capitalize"
> >
{#if value} {#if value}
{value} {value}
@@ -9,6 +9,7 @@
export let value = ''; export let value = '';
export let placeholder = $i18n.t('Select view'); export let placeholder = $i18n.t('Select view');
export let align = 'start';
export let onChange: (value: string) => void = () => {}; export let onChange: (value: string) => void = () => {};
const items = [ const items = [
@@ -22,13 +23,14 @@
bind:value bind:value
{items} {items}
{placeholder} {placeholder}
triggerClass="relative w-full flex items-center gap-0.5 px-2.5 py-1.5 bg-gray-50 dark:bg-gray-850 rounded-xl " {align}
labelClass="inline-flex h-input px-0.5 w-full outline-hidden bg-transparent truncate placeholder-gray-400 focus:outline-hidden" 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"
labelClass="inline-flex h-input w-full outline-hidden bg-transparent truncate placeholder-gray-400 focus:outline-hidden"
onChange={() => onChange(value)} onChange={() => onChange(value)}
> >
<svelte:fragment slot="trigger" let:selectedLabel> <svelte:fragment slot="trigger" let:selectedLabel>
<span <span
class="inline-flex h-input px-0.5 w-full outline-hidden bg-transparent truncate placeholder-gray-400 focus:outline-hidden" class="inline-flex h-input w-full outline-hidden bg-transparent truncate placeholder-gray-400 focus:outline-hidden"
> >
{selectedLabel} {selectedLabel}
</span> </span>
+77 -72
View File
@@ -260,7 +260,7 @@
<div class="flex-1 max-h-full overflow-y-auto"> <div class="flex-1 max-h-full overflow-y-auto">
{#if loaded} {#if loaded}
<div class="pb-1 px-3 md:px-[18px] pt-2"> <div class="pb-1 px-3 md:px-[18px] pt-2">
<div class="flex flex-col gap-1 px-1 mt-1.5 mb-3"> <div class="flex flex-col gap-1 px-1 mt-1.5 mb-2">
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<div class="flex items-center md:self-center text-xl font-medium px-0.5 gap-2 shrink-0"> <div class="flex items-center md:self-center text-xl font-medium px-0.5 gap-2 shrink-0">
{#if $mobile} {#if $mobile}
@@ -303,11 +303,9 @@
</div> </div>
</div> </div>
<div <div class="space-y-1">
class="py-2 bg-white dark:bg-gray-900 rounded-3xl border border-gray-100/30 dark:border-gray-850/30" <div class="flex h-8 flex-1 items-center w-full gap-2">
> <div class="flex min-w-0 flex-1 items-center">
<div class="px-3.5 flex flex-1 items-center w-full space-x-2 py-0.5 pb-2">
<div class="flex flex-1 items-center">
<div class="self-center ml-1 mr-3"> <div class="self-center ml-1 mr-3">
<Search className="size-3.5" /> <Search className="size-3.5" />
</div> </div>
@@ -335,76 +333,83 @@
</div> </div>
{/if} {/if}
</div> </div>
</div>
<div class="px-3 flex w-full bg-transparent overflow-x-auto scrollbar-none -mx-1">
<div <div
class="flex gap-0.5 w-fit text-center text-sm rounded-full bg-transparent px-1.5 whitespace-nowrap" class="flex max-w-[55%] shrink-0 overflow-x-auto scrollbar-none"
on:wheel={(e) => {
if (e.deltaY !== 0) {
e.preventDefault();
e.currentTarget.scrollLeft += e.deltaY;
}
}}
> >
<Select <div
bind:value={statusFilter} class="flex w-fit gap-0.5 text-center text-sm rounded-full bg-transparent whitespace-nowrap"
items={[
{ value: 'all', label: $i18n.t('All') },
{ value: 'active', label: $i18n.t('Active') },
{ value: 'paused', label: $i18n.t('Paused') }
]}
onChange={() => {
page = 1;
}}
triggerClass="relative w-full flex items-center gap-0.5 px-2.5 py-1.5 bg-gray-50 dark:bg-gray-850 rounded-xl"
> >
<svelte:fragment slot="trigger" let:selectedLabel> <Select
<span bind:value={statusFilter}
class="inline-flex h-input px-0.5 w-full outline-hidden bg-transparent truncate placeholder-gray-400 focus:outline-hidden" align="end"
> items={[
{selectedLabel} { value: 'all', label: $i18n.t('All') },
</span> { value: 'active', label: $i18n.t('Active') },
<ChevronDown className="size-3.5" strokeWidth="2.5" /> { value: 'paused', label: $i18n.t('Paused') }
</svelte:fragment> ]}
onChange={() => {
<svelte:fragment slot="item" let:item let:selected> page = 1;
{item.label} }}
<div class="ml-auto {selected ? '' : 'invisible'}"> 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"
<Check />
</div>
</svelte:fragment>
</Select>
</div>
<div class="flex-1"></div>
<Dropdown align="end">
<Tooltip content={$i18n.t('Actions')}>
<button
class="p-1 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition"
type="button"
aria-label={$i18n.t('Actions')}
> >
<EllipsisHorizontal className="size-4" /> <svelte:fragment slot="trigger" let:selectedLabel>
</button> <span
</Tooltip> class="inline-flex h-input w-full outline-hidden bg-transparent truncate placeholder-gray-400 focus:outline-hidden"
>
{selectedLabel}
</span>
<ChevronDown className="size-3.5" strokeWidth="2.5" />
</svelte:fragment>
<div slot="content"> <svelte:fragment slot="item" let:item let:selected>
<DropdownMenu className="w-[170px] shadow-sm"> {item.label}
<button <div class="ml-auto {selected ? '' : 'invisible'}">
class="select-none flex w-full gap-2 items-center px-3 py-1.5 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md" <Check />
type="button" </div>
on:click={() => bulkToggleHandler(true)} </svelte:fragment>
> </Select>
<CheckCircle className="size-4" />
{$i18n.t('Enable All')} <Dropdown align="end">
</button> <Tooltip content={$i18n.t('Actions')}>
<button <button
class="select-none flex w-full gap-2 items-center px-3 py-1.5 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md" class="flex h-8 items-center gap-1.5 rounded-xl bg-transparent px-1.5 text-[13px] font-normal text-gray-700 transition hover:text-gray-900 dark:text-gray-200 dark:hover:text-gray-100"
type="button" type="button"
on:click={() => bulkToggleHandler(false)} >
> <span>{$i18n.t('Actions')}</span>
<Minus className="size-4" /> <ChevronDown className="size-3" strokeWidth="2.5" />
{$i18n.t('Disable All')} </button>
</button> </Tooltip>
</DropdownMenu>
<div slot="content">
<DropdownMenu className="w-[170px] shadow-sm">
<button
class="select-none 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"
type="button"
on:click={() => bulkToggleHandler(true)}
>
<CheckCircle className="size-3.5" />
{$i18n.t('Enable All')}
</button>
<button
class="select-none 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"
type="button"
on:click={() => bulkToggleHandler(false)}
>
<Minus className="size-3.5" />
{$i18n.t('Disable All')}
</button>
</DropdownMenu>
</div>
</Dropdown>
</div> </div>
</Dropdown> </div>
</div> </div>
{#if automations === null || loading} {#if automations === null || loading}
@@ -430,10 +435,10 @@
</div> </div>
</div> </div>
{:else} {:else}
<div class="gap-2 grid my-2 px-3"> <div class="gap-y-0.5 grid my-1">
{#each automations as automation (automation.id)} {#each automations as automation (automation.id)}
<a <a
class="flex space-x-4 text-left w-full px-3 py-2.5 dark:hover:bg-gray-850/50 hover:bg-gray-50 transition rounded-2xl" class="flex space-x-4 text-left w-full px-3 py-2 bg-transparent hover:bg-gray-50/70 dark:hover:bg-gray-850/50 transition rounded-2xl"
href={`/automations/${automation.id}`} href={`/automations/${automation.id}`}
> >
<div class="flex-1"> <div class="flex-1">
@@ -460,7 +465,7 @@
}} }}
> >
<button <button
class="self-center w-fit text-sm p-1.5 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl" class="self-center w-fit text-sm p-1.5 text-gray-500 hover:text-gray-900 dark:text-gray-300 dark:hover:text-gray-100 rounded-xl"
type="button" type="button"
> >
<EllipsisHorizontal className="size-5" /> <EllipsisHorizontal className="size-5" />
+18 -83
View File
@@ -1,20 +1,15 @@
<script> <script>
import { getContext, onMount } from 'svelte'; import { onMount } from 'svelte';
const i18n = getContext('i18n');
import dayjs from '$lib/dayjs'; import dayjs from '$lib/dayjs';
import { mobile, showArchivedChats, showSidebar, user } from '$lib/stores'; import { showSidebar } from '$lib/stores';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { page } from '$app/stores'; import { page } from '$app/stores';
import { createNoteHandler } from '$lib/components/notes/utils'; import { createNoteHandler } from '$lib/components/notes/utils';
import UserMenu from '$lib/components/layout/Sidebar/UserMenu.svelte';
import Notes from '$lib/components/notes/Notes.svelte'; import Notes from '$lib/components/notes/Notes.svelte';
import Tooltip from '$lib/components/common/Tooltip.svelte'; import Spinner from '$lib/components/common/Spinner.svelte';
import Sidebar from '$lib/components/icons/Sidebar.svelte';
import { WEBUI_API_BASE_URL } from '$lib/constants';
let loaded = false; let loaded = false;
@@ -38,80 +33,20 @@
}); });
</script> </script>
{#if loaded} <div
<div class="flex flex-col w-full h-screen max-h-[100dvh] transition-width duration-200 ease-in-out {$showSidebar
class=" flex flex-col w-full h-screen max-h-[100dvh] transition-width duration-200 ease-in-out {$showSidebar ? 'md:max-w-[calc(100%-var(--sidebar-width))]'
? 'md:max-w-[calc(100%-var(--sidebar-width))]' : ''} max-w-full"
: ''} max-w-full" >
> <div class="flex-1 max-h-full overflow-y-auto">
<nav class=" px-2 pt-1.5 backdrop-blur-xl w-full drag-region"> {#if loaded}
<div class=" flex items-center"> <div class="pb-1 px-3 md:px-[18px] pt-2">
{#if $mobile} <Notes />
<div class="{$showSidebar ? 'md:hidden' : ''} flex flex-none items-center">
<Tooltip
content={$showSidebar ? $i18n.t('Close Sidebar') : $i18n.t('Open Sidebar')}
interactive={true}
>
<button
id="sidebar-toggle-button"
class=" cursor-pointer flex rounded-lg hover:bg-gray-100 dark:hover:bg-gray-850 transition cursor-"
on:click={() => {
showSidebar.set(!$showSidebar);
}}
>
<div class=" self-center p-1.5">
<Sidebar />
</div>
</button>
</Tooltip>
</div>
{/if}
<div class="ml-2 py-0.5 self-center flex items-center justify-between w-full">
<div class="">
<div
class="flex gap-1 scrollbar-none overflow-x-auto w-fit text-center text-sm font-medium bg-transparent py-1 touch-auto pointer-events-auto"
>
<a class="min-w-fit transition" href="/notes">
{$i18n.t('Notes')}
</a>
</div>
</div>
<div class=" self-center flex items-center gap-1">
{#if $user !== undefined && $user !== null}
<UserMenu
className="w-[240px]"
role={$user?.role}
help={true}
on:show={(e) => {
if (e.detail === 'archived-chat') {
showArchivedChats.set(true);
}
}}
>
<button
class="select-none flex rounded-xl p-1.5 w-full hover:bg-gray-50 dark:hover:bg-gray-850 transition"
aria-label="User Menu"
>
<div class=" self-center">
<img
src={`${WEBUI_API_BASE_URL}/users/${$user?.id}/profile/image`}
class="size-6 object-cover rounded-full"
alt="User profile"
draggable="false"
/>
</div>
</button>
</UserMenu>
{/if}
</div>
</div>
</div> </div>
</nav> {:else}
<div class="w-full h-full flex justify-center items-center">
<div class=" flex-1 max-h-full overflow-y-auto @container"> <Spinner className="size-5" />
<Notes /> </div>
</div> {/if}
</div> </div>
{/if} </div>