mirror of
https://github.com/open-webui/open-webui.git
synced 2026-08-25 15:04:49 -06:00
refac
This commit is contained in:
@@ -43,8 +43,7 @@
|
||||
import Minus from '$lib/components/icons/Minus.svelte';
|
||||
import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
|
||||
import { goto } from '$app/navigation';
|
||||
import { DropdownMenu } from 'bits-ui';
|
||||
import { flyAndScale } from '$lib/utils/transitions';
|
||||
|
||||
import Dropdown from '$lib/components/common/Dropdown.svelte';
|
||||
import AdminViewSelector from './Models/AdminViewSelector.svelte';
|
||||
import Pagination from '$lib/components/common/Pagination.svelte';
|
||||
@@ -495,55 +494,55 @@
|
||||
</Tooltip>
|
||||
|
||||
<div slot="content">
|
||||
<DropdownMenu.Content
|
||||
class="w-full max-w-[170px] rounded-xl p-1 border border-gray-100 dark:border-gray-800 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-sm"
|
||||
sideOffset={-2}
|
||||
side="bottom"
|
||||
align="end"
|
||||
transition={flyAndScale}
|
||||
<div
|
||||
class="w-[170px] rounded-xl p-1 border border-gray-100 dark:border-gray-800 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-sm"
|
||||
>
|
||||
<DropdownMenu.Item
|
||||
class="select-none flex 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"
|
||||
<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>
|
||||
</DropdownMenu.Item>
|
||||
</button>
|
||||
|
||||
<DropdownMenu.Item
|
||||
class="select-none flex 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"
|
||||
<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>
|
||||
</DropdownMenu.Item>
|
||||
</button>
|
||||
|
||||
<hr class="border-gray-100 dark:border-gray-800 my-1" />
|
||||
|
||||
<DropdownMenu.Item
|
||||
class="select-none flex 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"
|
||||
<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>
|
||||
</DropdownMenu.Item>
|
||||
</button>
|
||||
|
||||
<DropdownMenu.Item
|
||||
class="select-none flex 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"
|
||||
<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>
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu } from 'bits-ui';
|
||||
import { flyAndScale } from '$lib/utils/transitions';
|
||||
import { getContext, onMount, tick } from 'svelte';
|
||||
|
||||
import { config, user, tools as _tools, mobile } from '$lib/stores';
|
||||
@@ -45,33 +43,31 @@
|
||||
</Tooltip>
|
||||
|
||||
<div slot="content">
|
||||
<DropdownMenu.Content
|
||||
class="w-full max-w-[200px] rounded-2xl px-1 py-1 border border-gray-100 dark:border-gray-800 z-999 bg-white dark:bg-gray-850 dark:text-white shadow-lg transition"
|
||||
sideOffset={4}
|
||||
alignOffset={-6}
|
||||
side="bottom"
|
||||
align="start"
|
||||
transition={flyAndScale}
|
||||
<div
|
||||
class="w-[200px] rounded-2xl px-1 py-1 border border-gray-100 dark:border-gray-800 z-999 bg-white dark:bg-gray-850 dark:text-white shadow-lg transition"
|
||||
>
|
||||
<DropdownMenu.Item
|
||||
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/50 rounded-xl"
|
||||
<button
|
||||
class="select-none 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/50 rounded-xl"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
uploadFilesHandler();
|
||||
}}
|
||||
>
|
||||
<Clip />
|
||||
<div class="line-clamp-1">{$i18n.t('Upload Files')}</div>
|
||||
</DropdownMenu.Item>
|
||||
</button>
|
||||
|
||||
<DropdownMenu.Item
|
||||
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/50 rounded-xl"
|
||||
<button
|
||||
class="select-none 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/50 rounded-xl"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
screenCaptureHandler();
|
||||
}}
|
||||
>
|
||||
<Camera />
|
||||
<div class=" line-clamp-1">{$i18n.t('Capture')}</div>
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Dropdown>
|
||||
|
||||
|
||||
@@ -194,7 +194,7 @@
|
||||
|
||||
{#if $user !== undefined}
|
||||
<UserMenu
|
||||
className="max-w-[240px]"
|
||||
className="w-[240px]"
|
||||
role={$user?.role}
|
||||
help={true}
|
||||
on:show={(e) => {
|
||||
|
||||
@@ -89,8 +89,7 @@
|
||||
import PlusAlt from '../icons/PlusAlt.svelte';
|
||||
import Dropdown from '../common/Dropdown.svelte';
|
||||
|
||||
import { DropdownMenu } from 'bits-ui';
|
||||
import { flyAndScale } from '$lib/utils/transitions';
|
||||
|
||||
|
||||
import CommandSuggestionList from './MessageInput/CommandSuggestionList.svelte';
|
||||
import Knobs from '../icons/Knobs.svelte';
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu } from 'bits-ui';
|
||||
|
||||
import { getContext, onMount, tick } from 'svelte';
|
||||
import { fly } from 'svelte/transition';
|
||||
import { flyAndScale } from '$lib/utils/transitions';
|
||||
|
||||
|
||||
import { config, user, tools as _tools, mobile, knowledge } from '$lib/stores';
|
||||
import { getKnowledgeBases } from '$lib/apis/knowledge';
|
||||
@@ -122,13 +122,8 @@
|
||||
</Tooltip>
|
||||
|
||||
<div slot="content">
|
||||
<DropdownMenu.Content
|
||||
class="w-full max-w-70 rounded-2xl px-1 py-1 border border-gray-100 dark:border-gray-800 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg max-h-72 overflow-y-auto overflow-x-hidden scrollbar-thin transition"
|
||||
sideOffset={4}
|
||||
alignOffset={-6}
|
||||
side="bottom"
|
||||
align="start"
|
||||
transition={flyAndScale}
|
||||
<div
|
||||
class="w-70 rounded-2xl px-1 py-1 border border-gray-100 dark:border-gray-800 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg max-h-72 overflow-y-auto overflow-x-hidden scrollbar-thin transition"
|
||||
>
|
||||
{#if tab === ''}
|
||||
<div in:fly={{ x: -20, duration: 150 }}>
|
||||
@@ -140,10 +135,11 @@
|
||||
: ''}
|
||||
className="w-full"
|
||||
>
|
||||
<DropdownMenu.Item
|
||||
class="flex gap-2 items-center px-3 py-1.5 text-sm select-none cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800/50 rounded-xl {!fileUploadEnabled
|
||||
<button
|
||||
class="flex w-full gap-2 items-center px-3 py-1.5 text-sm select-none cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800/50 rounded-xl {!fileUploadEnabled
|
||||
? 'opacity-50'
|
||||
: ''}"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
if (fileUploadEnabled) {
|
||||
uploadFilesHandler();
|
||||
@@ -153,7 +149,7 @@
|
||||
<Clip />
|
||||
|
||||
<div class="line-clamp-1">{$i18n.t('Upload Files')}</div>
|
||||
</DropdownMenu.Item>
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip
|
||||
@@ -164,10 +160,11 @@
|
||||
: ''}
|
||||
className="w-full"
|
||||
>
|
||||
<DropdownMenu.Item
|
||||
class="flex gap-2 items-center px-3 py-1.5 text-sm select-none cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800/50 rounded-xl {!fileUploadEnabled
|
||||
<button
|
||||
class="flex w-full gap-2 items-center px-3 py-1.5 text-sm select-none cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800/50 rounded-xl {!fileUploadEnabled
|
||||
? 'opacity-50'
|
||||
: ''}"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
if (fileUploadEnabled) {
|
||||
if (!detectMobile()) {
|
||||
@@ -184,7 +181,7 @@
|
||||
>
|
||||
<Camera />
|
||||
<div class=" line-clamp-1">{$i18n.t('Capture')}</div>
|
||||
</DropdownMenu.Item>
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip
|
||||
@@ -193,10 +190,11 @@
|
||||
: ''}
|
||||
className="w-full"
|
||||
>
|
||||
<DropdownMenu.Item
|
||||
class="flex gap-2 items-center px-3 py-1.5 text-sm select-none cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl {!webUploadEnabled
|
||||
<button
|
||||
class="flex w-full gap-2 items-center px-3 py-1.5 text-sm select-none cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl {!webUploadEnabled
|
||||
? 'opacity-50'
|
||||
: ''}"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
if (webUploadEnabled) {
|
||||
showAttachWebpageModal = true;
|
||||
@@ -205,7 +203,7 @@
|
||||
>
|
||||
<GlobeAlt />
|
||||
<div class="line-clamp-1">{$i18n.t('Attach Webpage')}</div>
|
||||
</DropdownMenu.Item>
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
||||
{#if $config?.features?.enable_notes ?? false}
|
||||
@@ -302,8 +300,9 @@
|
||||
|
||||
{#if fileUploadEnabled}
|
||||
{#if $config?.features?.enable_google_drive_integration}
|
||||
<DropdownMenu.Item
|
||||
class="flex gap-2 items-center px-3 py-1.5 text-sm select-none cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800/50 rounded-xl"
|
||||
<button
|
||||
class="flex w-full gap-2 items-center px-3 py-1.5 text-sm select-none cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800/50 rounded-xl"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
uploadGoogleDriveHandler();
|
||||
}}
|
||||
@@ -335,7 +334,7 @@
|
||||
/>
|
||||
</svg>
|
||||
<div class="line-clamp-1">{$i18n.t('Google Drive')}</div>
|
||||
</DropdownMenu.Item>
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
{#if $config?.features?.enable_onedrive_integration && ($config?.features?.enable_onedrive_personal || $config?.features?.enable_onedrive_business)}
|
||||
@@ -521,8 +520,9 @@
|
||||
</button>
|
||||
|
||||
{#if $config?.features?.enable_onedrive_personal}
|
||||
<DropdownMenu.Item
|
||||
class="flex gap-2 items-center px-3 py-1.5 text-sm select-none cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800/50 rounded-xl text-left"
|
||||
<button
|
||||
class="flex w-full gap-2 items-center px-3 py-1.5 text-sm select-none cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800/50 rounded-xl text-left"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
uploadOneDriveHandler('personal');
|
||||
}}
|
||||
@@ -530,12 +530,13 @@
|
||||
<div class="flex flex-col">
|
||||
<div class="line-clamp-1">{$i18n.t('Microsoft OneDrive (personal)')}</div>
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
{#if $config?.features?.enable_onedrive_business}
|
||||
<DropdownMenu.Item
|
||||
class="flex gap-2 items-center px-3 py-1.5 text-sm select-none cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800/50 rounded-xl text-left"
|
||||
<button
|
||||
class="flex w-full gap-2 items-center px-3 py-1.5 text-sm select-none cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800/50 rounded-xl text-left"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
uploadOneDriveHandler('organizations');
|
||||
}}
|
||||
@@ -546,10 +547,10 @@
|
||||
</div>
|
||||
<div class="text-xs text-gray-500">{$i18n.t('Includes SharePoint')}</div>
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
</div>
|
||||
</div>
|
||||
</Dropdown>
|
||||
|
||||
@@ -230,7 +230,7 @@
|
||||
|
||||
{#if $user !== undefined && $user !== null}
|
||||
<UserMenu
|
||||
className="max-w-[240px]"
|
||||
className="w-[240px]"
|
||||
role={$user?.role}
|
||||
help={true}
|
||||
on:show={(e) => {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { flyAndScale } from '$lib/utils/transitions';
|
||||
import { tick } from 'svelte';
|
||||
import { createEventDispatcher, tick } from 'svelte';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
/** Whether the dropdown is open */
|
||||
export let show = false;
|
||||
@@ -104,6 +106,7 @@
|
||||
async function toggleOpen() {
|
||||
show = !show;
|
||||
onOpenChange(show);
|
||||
dispatch('change', show);
|
||||
if (show) {
|
||||
await tick();
|
||||
positionContent();
|
||||
@@ -126,12 +129,14 @@
|
||||
if (contentEl?.contains(event.target)) return;
|
||||
show = false;
|
||||
onOpenChange(false);
|
||||
dispatch('change', false);
|
||||
}
|
||||
|
||||
function handleKeydown(event) {
|
||||
if (event.key === 'Escape' && show) {
|
||||
show = false;
|
||||
onOpenChange(false);
|
||||
dispatch('change', false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,6 +144,7 @@
|
||||
export function close() {
|
||||
show = false;
|
||||
onOpenChange(false);
|
||||
dispatch('change', false);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu } from 'bits-ui';
|
||||
import VirtualList from '@sveltejs/svelte-virtual-list';
|
||||
|
||||
import { getContext } from 'svelte';
|
||||
|
||||
import { flyAndScale } from '$lib/utils/transitions';
|
||||
import { WEBUI_BASE_URL } from '$lib/constants';
|
||||
|
||||
import Dropdown from '$lib/components/common/Dropdown.svelte';
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
|
||||
import emojiGroups from '$lib/emoji-groups.json';
|
||||
@@ -107,79 +106,74 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root
|
||||
bind:open={show}
|
||||
closeFocus={false}
|
||||
onOpenChange={(state) => {
|
||||
if (!state) {
|
||||
<Dropdown
|
||||
bind:show
|
||||
{align}
|
||||
on:change={(e) => {
|
||||
if (e.detail === false) {
|
||||
search = '';
|
||||
onClose();
|
||||
}
|
||||
}}
|
||||
typeahead={false}
|
||||
>
|
||||
<DropdownMenu.Trigger>
|
||||
<slot />
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content
|
||||
class="max-w-full w-80 border border-gray-100 dark:border-gray-800 bg-white dark:bg-gray-850 rounded-3xl z-9999 shadow-lg dark:text-white"
|
||||
sideOffset={8}
|
||||
{side}
|
||||
{align}
|
||||
transition={flyAndScale}
|
||||
>
|
||||
<div class="mb-1 px-4 pt-2.5 pb-2">
|
||||
<input
|
||||
type="text"
|
||||
class="w-full text-sm bg-transparent outline-hidden"
|
||||
placeholder={$i18n.t('Search all emojis')}
|
||||
bind:value={search}
|
||||
/>
|
||||
</div>
|
||||
<slot />
|
||||
|
||||
<div slot="content">
|
||||
<div
|
||||
class="max-w-full w-80 border border-gray-100 dark:border-gray-800 bg-white dark:bg-gray-850 rounded-3xl z-9999 shadow-lg dark:text-white"
|
||||
>
|
||||
<div class="mb-1 px-4 pt-2.5 pb-2">
|
||||
<input
|
||||
type="text"
|
||||
class="w-full text-sm bg-transparent outline-hidden"
|
||||
placeholder={$i18n.t('Search all emojis')}
|
||||
bind:value={search}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Virtualized Emoji List -->
|
||||
<div class="w-full flex justify-start h-96 overflow-y-auto px-3 pb-3 text-sm">
|
||||
{#if emojiRows.length === 0}
|
||||
<div class="text-center text-xs text-gray-500 dark:text-gray-400">
|
||||
{$i18n.t('No results')}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="w-full flex ml-0.5">
|
||||
<VirtualList rowHeight={ROW_HEIGHT} items={emojiRows} height={384} let:item>
|
||||
<div class="w-full">
|
||||
{#if item.length === 1 && item[0].type === 'group'}
|
||||
<!-- Render group header -->
|
||||
<div class="text-xs font-medium mb-2 text-gray-500 dark:text-gray-400">
|
||||
{item[0].label}
|
||||
</div>
|
||||
{:else}
|
||||
<!-- Render emojis in a row -->
|
||||
<div class="flex items-center gap-1.5 w-full">
|
||||
{#each item as emojiItem}
|
||||
<Tooltip
|
||||
content={emojiItem.shortCodes.map((code) => `:${code}:`).join(', ')}
|
||||
placement="top"
|
||||
>
|
||||
<button
|
||||
class="p-1.5 rounded-lg cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-700 transition {selected === emojiItem.shortCodes[0] ? 'bg-gray-200 dark:bg-gray-700' : ''}"
|
||||
on:click={() => selectEmoji(emojiItem)}
|
||||
<!-- Virtualized Emoji List -->
|
||||
<div class="w-full flex justify-start h-96 overflow-y-auto px-3 pb-3 text-sm">
|
||||
{#if emojiRows.length === 0}
|
||||
<div class="text-center text-xs text-gray-500 dark:text-gray-400">
|
||||
{$i18n.t('No results')}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="w-full flex ml-0.5">
|
||||
<VirtualList rowHeight={ROW_HEIGHT} items={emojiRows} height={384} let:item>
|
||||
<div class="w-full">
|
||||
{#if item.length === 1 && item[0].type === 'group'}
|
||||
<!-- Render group header -->
|
||||
<div class="text-xs font-medium mb-2 text-gray-500 dark:text-gray-400">
|
||||
{item[0].label}
|
||||
</div>
|
||||
{:else}
|
||||
<!-- Render emojis in a row -->
|
||||
<div class="flex items-center gap-1.5 w-full">
|
||||
{#each item as emojiItem}
|
||||
<Tooltip
|
||||
content={emojiItem.shortCodes.map((code) => `:${code}:`).join(', ')}
|
||||
placement="top"
|
||||
>
|
||||
<img
|
||||
src="{WEBUI_BASE_URL}/assets/emojis/{emojiItem.name.toLowerCase()}.svg"
|
||||
alt={emojiItem.name}
|
||||
class="size-5"
|
||||
loading="lazy"
|
||||
/>
|
||||
</button>
|
||||
</Tooltip>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</VirtualList>
|
||||
</div>
|
||||
{/if}
|
||||
<button
|
||||
class="p-1.5 rounded-lg cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-700 transition {selected === emojiItem.shortCodes[0] ? 'bg-gray-200 dark:bg-gray-700' : ''}"
|
||||
on:click={() => selectEmoji(emojiItem)}
|
||||
>
|
||||
<img
|
||||
src="{WEBUI_BASE_URL}/assets/emojis/{emojiItem.name.toLowerCase()}.svg"
|
||||
alt={emojiItem.name}
|
||||
class="size-5"
|
||||
loading="lazy"
|
||||
/>
|
||||
</button>
|
||||
</Tooltip>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</VirtualList>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
</div>
|
||||
</Dropdown>
|
||||
|
||||
@@ -1398,7 +1398,7 @@
|
||||
role={$user?.role}
|
||||
profile={$config?.features?.enable_user_status ?? true}
|
||||
showActiveUsers={false}
|
||||
className="max-w-[calc(var(--sidebar-width)-1rem)]"
|
||||
className="w-[calc(var(--sidebar-width)-1rem)]"
|
||||
on:show={(e) => {
|
||||
if (e.detail === 'archived-chat') {
|
||||
showArchivedChats.set(true);
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu } from 'bits-ui';
|
||||
import { createEventDispatcher, getContext, onMount, tick } from 'svelte';
|
||||
|
||||
import { flyAndScale } from '$lib/utils/transitions';
|
||||
import { goto } from '$app/navigation';
|
||||
import { fade, slide } from 'svelte/transition';
|
||||
|
||||
@@ -13,6 +11,7 @@
|
||||
|
||||
import { WEBUI_API_BASE_URL } from '$lib/constants';
|
||||
|
||||
import Dropdown from '$lib/components/common/Dropdown.svelte';
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
import ArchiveBox from '$lib/components/icons/ArchiveBox.svelte';
|
||||
import QuestionMarkCircle from '$lib/components/icons/QuestionMarkCircle.svelte';
|
||||
@@ -38,7 +37,7 @@
|
||||
export let profile = false;
|
||||
export let help = false;
|
||||
|
||||
export let className = 'max-w-[240px]';
|
||||
export let className = 'w-[240px]';
|
||||
export let align = 'end';
|
||||
|
||||
export let showActiveUsers = true;
|
||||
@@ -60,7 +59,8 @@
|
||||
}
|
||||
};
|
||||
|
||||
const handleDropdownChange = (state: boolean) => {
|
||||
const handleDropdownChange = (e) => {
|
||||
const state = e.detail;
|
||||
dispatch('change', state);
|
||||
|
||||
// Fetch usage info when dropdown opens, if user has permission
|
||||
@@ -79,18 +79,12 @@
|
||||
/>
|
||||
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<DropdownMenu.Root bind:open={show} onOpenChange={handleDropdownChange}>
|
||||
<DropdownMenu.Trigger>
|
||||
<slot />
|
||||
</DropdownMenu.Trigger>
|
||||
<Dropdown bind:show on:change={handleDropdownChange} {align}>
|
||||
<slot />
|
||||
|
||||
<slot name="content">
|
||||
<DropdownMenu.Content
|
||||
class="w-full {className} rounded-2xl px-1 py-1 border border-gray-100 dark:border-gray-800 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg text-sm"
|
||||
sideOffset={4}
|
||||
side="top"
|
||||
{align}
|
||||
transition={(e) => fade(e, { duration: 100 })}
|
||||
<div slot="content">
|
||||
<div
|
||||
class="{className} rounded-2xl px-1 py-1 border border-gray-100 dark:border-gray-800 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg text-sm"
|
||||
>
|
||||
{#if profile}
|
||||
<div class=" flex gap-3.5 w-full p-2.5 items-center">
|
||||
@@ -201,8 +195,9 @@
|
||||
<hr class=" border-gray-50/30 dark:border-gray-800/30 my-1.5 p-0" />
|
||||
{/if}
|
||||
|
||||
<DropdownMenu.Item
|
||||
<button
|
||||
class="flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none"
|
||||
type="button"
|
||||
on:click={async () => {
|
||||
show = false;
|
||||
|
||||
@@ -218,10 +213,11 @@
|
||||
<Settings className="w-5 h-5" strokeWidth="1.5" />
|
||||
</div>
|
||||
<div class=" self-center truncate">{$i18n.t('Settings')}</div>
|
||||
</DropdownMenu.Item>
|
||||
</button>
|
||||
|
||||
<DropdownMenu.Item
|
||||
<button
|
||||
class="flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none"
|
||||
type="button"
|
||||
on:click={async () => {
|
||||
show = false;
|
||||
|
||||
@@ -238,11 +234,10 @@
|
||||
<ArchiveBox className="size-5" strokeWidth="1.5" />
|
||||
</div>
|
||||
<div class=" self-center truncate">{$i18n.t('Archived Chats')}</div>
|
||||
</DropdownMenu.Item>
|
||||
</button>
|
||||
|
||||
{#if role === 'admin'}
|
||||
<DropdownMenu.Item
|
||||
as="a"
|
||||
<a
|
||||
href="/playground"
|
||||
draggable="false"
|
||||
class="flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none"
|
||||
@@ -258,9 +253,8 @@
|
||||
<Code className="size-5" strokeWidth="1.5" />
|
||||
</div>
|
||||
<div class=" self-center truncate">{$i18n.t('Playground')}</div>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
as="a"
|
||||
</a>
|
||||
<a
|
||||
href="/admin"
|
||||
draggable="false"
|
||||
class="flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none"
|
||||
@@ -276,7 +270,7 @@
|
||||
<UserGroup className="w-5 h-5" strokeWidth="1.5" />
|
||||
</div>
|
||||
<div class=" self-center truncate">{$i18n.t('Admin Panel')}</div>
|
||||
</DropdownMenu.Item>
|
||||
</a>
|
||||
{/if}
|
||||
|
||||
{#if help}
|
||||
@@ -285,8 +279,7 @@
|
||||
<!-- {$i18n.t('Help')} -->
|
||||
|
||||
{#if $user?.role === 'admin'}
|
||||
<DropdownMenu.Item
|
||||
as="a"
|
||||
<a
|
||||
href="https://docs.openwebui.com"
|
||||
target="_blank"
|
||||
draggable="false"
|
||||
@@ -300,11 +293,10 @@
|
||||
<QuestionMarkCircle className="size-5" />
|
||||
</div>
|
||||
<div class=" self-center truncate">{$i18n.t('Documentation')}</div>
|
||||
</DropdownMenu.Item>
|
||||
</a>
|
||||
|
||||
<!-- Releases -->
|
||||
<DropdownMenu.Item
|
||||
as="a"
|
||||
<a
|
||||
href="https://github.com/open-webui/open-webui/releases"
|
||||
target="_blank"
|
||||
draggable="false"
|
||||
@@ -318,11 +310,12 @@
|
||||
<Map className="size-5" />
|
||||
</div>
|
||||
<div class=" self-center truncate">{$i18n.t('Releases')}</div>
|
||||
</DropdownMenu.Item>
|
||||
</a>
|
||||
{/if}
|
||||
|
||||
<DropdownMenu.Item
|
||||
<button
|
||||
class="flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none"
|
||||
type="button"
|
||||
id="chat-share-button"
|
||||
on:click={async () => {
|
||||
show = false;
|
||||
@@ -338,13 +331,14 @@
|
||||
<Keyboard className="size-5" />
|
||||
</div>
|
||||
<div class=" self-center truncate">{$i18n.t('Keyboard shortcuts')}</div>
|
||||
</DropdownMenu.Item>
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<hr class=" border-gray-50/30 dark:border-gray-800/30 my-1 p-0" />
|
||||
|
||||
<DropdownMenu.Item
|
||||
<button
|
||||
class="flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none"
|
||||
type="button"
|
||||
on:click={async () => {
|
||||
const res = await userSignOut();
|
||||
user.set(null);
|
||||
@@ -358,7 +352,7 @@
|
||||
<SignOut className="w-5 h-5" strokeWidth="1.5" />
|
||||
</div>
|
||||
<div class=" self-center truncate">{$i18n.t('Sign Out')}</div>
|
||||
</DropdownMenu.Item>
|
||||
</button>
|
||||
|
||||
{#if showActiveUsers && ($config?.features?.enable_public_active_users_count || role === 'admin') && usage}
|
||||
{#if usage?.user_count}
|
||||
@@ -395,10 +389,6 @@
|
||||
</Tooltip>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<!-- <DropdownMenu.Item class="flex items-center py-1.5 px-3 text-sm ">
|
||||
<div class="flex items-center">Profile</div>
|
||||
</DropdownMenu.Item> -->
|
||||
</DropdownMenu.Content>
|
||||
</slot>
|
||||
</DropdownMenu.Root>
|
||||
</div>
|
||||
</div>
|
||||
</Dropdown>
|
||||
|
||||
@@ -43,8 +43,7 @@
|
||||
import XMark from '../icons/XMark.svelte';
|
||||
import EyeSlash from '../icons/EyeSlash.svelte';
|
||||
import Eye from '../icons/Eye.svelte';
|
||||
import { DropdownMenu } from 'bits-ui';
|
||||
import { flyAndScale } from '$lib/utils/transitions';
|
||||
|
||||
import Dropdown from '$lib/components/common/Dropdown.svelte';
|
||||
import ViewSelector from './common/ViewSelector.svelte';
|
||||
import TagSelector from './common/TagSelector.svelte';
|
||||
@@ -517,55 +516,55 @@
|
||||
</Tooltip>
|
||||
|
||||
<div slot="content">
|
||||
<DropdownMenu.Content
|
||||
class="w-full max-w-[170px] rounded-xl p-1 border border-gray-100 dark:border-gray-800 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-sm"
|
||||
sideOffset={-2}
|
||||
side="bottom"
|
||||
align="end"
|
||||
transition={flyAndScale}
|
||||
<div
|
||||
class="w-[170px] rounded-xl p-1 border border-gray-100 dark:border-gray-800 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-sm"
|
||||
>
|
||||
<DropdownMenu.Item
|
||||
class="select-none flex 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"
|
||||
<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>
|
||||
</DropdownMenu.Item>
|
||||
</button>
|
||||
|
||||
<DropdownMenu.Item
|
||||
class="select-none flex 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"
|
||||
<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>
|
||||
</DropdownMenu.Item>
|
||||
</button>
|
||||
|
||||
<hr class="border-gray-100 dark:border-gray-800 my-1" />
|
||||
|
||||
<DropdownMenu.Item
|
||||
class="select-none flex 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"
|
||||
<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>
|
||||
</DropdownMenu.Item>
|
||||
</button>
|
||||
|
||||
<DropdownMenu.Item
|
||||
class="select-none flex 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"
|
||||
<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>
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script lang="ts">
|
||||
import dayjs from 'dayjs';
|
||||
import { DropdownMenu } from 'bits-ui';
|
||||
|
||||
import { onMount, onDestroy, getContext, createEventDispatcher } from 'svelte';
|
||||
import { searchNotes } from '$lib/apis/notes';
|
||||
import { searchKnowledgeBases, searchKnowledgeFiles } from '$lib/apis/knowledge';
|
||||
|
||||
import { flyAndScale } from '$lib/utils/transitions';
|
||||
|
||||
import { decodeString } from '$lib/utils';
|
||||
|
||||
import Dropdown from '$lib/components/common/Dropdown.svelte';
|
||||
@@ -118,12 +118,8 @@
|
||||
<slot />
|
||||
|
||||
<div slot="content">
|
||||
<DropdownMenu.Content
|
||||
<div
|
||||
class="z-[10000] text-black dark:text-white rounded-2xl shadow-lg border border-gray-200 dark:border-gray-800 flex flex-col bg-white dark:bg-gray-850 w-70 p-1.5"
|
||||
sideOffset={8}
|
||||
side="bottom"
|
||||
align="start"
|
||||
transition={flyAndScale}
|
||||
>
|
||||
<div class=" flex w-full space-x-2 px-2 pb-0.5">
|
||||
<div class="flex flex-1">
|
||||
@@ -210,6 +206,6 @@
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</DropdownMenu.Content>
|
||||
</div>
|
||||
</div>
|
||||
</Dropdown>
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
<div class=" self-center flex items-center gap-1">
|
||||
{#if $user !== undefined && $user !== null}
|
||||
<UserMenu
|
||||
className="max-w-[240px]"
|
||||
className="w-[240px]"
|
||||
role={$user?.role}
|
||||
help={true}
|
||||
on:show={(e) => {
|
||||
|
||||
Reference in New Issue
Block a user