mirror of
https://github.com/open-webui/open-webui.git
synced 2026-11-15 09:31:21 +00:00
refac
This commit is contained in:
parent
64796faceb
commit
a3e261d4cb
5 changed files with 143 additions and 134 deletions
|
|
@ -45,6 +45,10 @@
|
|||
|
||||
let runs: AutomationRunModel[] = [];
|
||||
let linkedRun: AutomationRunModel | null = null;
|
||||
$: visibleRuns =
|
||||
linkedRun && !runs.some((run) => run.id === linkedRun?.id)
|
||||
? [...runs, linkedRun].sort((a, b) => b.created_at - a.created_at)
|
||||
: runs;
|
||||
let linkedRunId = '';
|
||||
let alive = true;
|
||||
onDestroy(() => {
|
||||
|
|
@ -337,29 +341,23 @@
|
|||
<hr class="my-1.5 border-gray-50/60 dark:border-gray-850/25" />
|
||||
|
||||
<div class="px-4 py-2">
|
||||
{#if linkedRun}<div
|
||||
id="inbox-automation-run"
|
||||
class="mb-3 rounded-xl border border-gray-200 dark:border-gray-700 p-3 text-xs"
|
||||
>
|
||||
<div class="mb-1 font-medium">
|
||||
{$i18n.t('Automation failed')} · {formatTime(linkedRun.created_at)}
|
||||
</div>
|
||||
<p class="whitespace-pre-wrap break-words text-red-500">{linkedRun.error}</p>
|
||||
</div>{/if}
|
||||
<div class="mb-1 text-[0.6875rem] text-gray-400 dark:text-gray-500">{$i18n.t('Runs')}</div>
|
||||
<div class="overflow-y-auto scrollbar-hidden" on:scroll={onScroll}>
|
||||
{#if runsLoading && runs.length === 0}
|
||||
{#if runsLoading && visibleRuns.length === 0}
|
||||
<div class="flex justify-center py-8">
|
||||
<Spinner className="size-4" />
|
||||
</div>
|
||||
{:else if runs.length === 0}
|
||||
{:else if visibleRuns.length === 0}
|
||||
<div class="py-2 text-[0.6875rem] text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('No runs yet')}
|
||||
</div>
|
||||
{:else}
|
||||
<div>
|
||||
{#each runs as run}
|
||||
<div class="flex h-7 items-center gap-2 text-xs">
|
||||
{#each visibleRuns as run}
|
||||
<div
|
||||
id={run.id === linkedRun?.id ? 'inbox-automation-run' : undefined}
|
||||
class="flex h-7 items-center gap-2 text-xs"
|
||||
>
|
||||
<span
|
||||
class="h-1.5 w-1.5 shrink-0 rounded-full {run.status === 'success'
|
||||
? 'bg-emerald-500'
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
import { mentionAttributes } from '$lib/utils/channelMentions';
|
||||
import { channels, models, config } from '$lib/stores';
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
import DropdownMenu from '$lib/components/common/DropdownMenu.svelte';
|
||||
import Grid from '$lib/components/icons/Grid.svelte';
|
||||
import Hashtag from '$lib/components/icons/Hashtag.svelte';
|
||||
import Lock from '$lib/components/icons/Lock.svelte';
|
||||
|
|
@ -203,99 +204,98 @@
|
|||
</script>
|
||||
|
||||
{#if filteredItems.length}
|
||||
<div
|
||||
class="mention-list 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-72 p-1"
|
||||
id="suggestions-container"
|
||||
>
|
||||
<div class="overflow-y-auto scrollbar-thin max-h-60">
|
||||
{#each filteredItems as item, i}
|
||||
{#if i === 0 || item?.type !== filteredItems[i - 1]?.type}
|
||||
<div class="px-2 text-xs text-gray-500 py-1">
|
||||
{#if item?.type === 'user'}
|
||||
{$i18n.t('Users')}
|
||||
{:else if item?.type === 'model'}
|
||||
{$i18n.t('Models')}
|
||||
{:else if item?.type === 'app'}
|
||||
{$i18n.t('Apps')}
|
||||
{:else if item?.type === 'channel'}
|
||||
{$i18n.t('Channels')}
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<Tooltip content={item?.id} placement="top-start">
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => select(i)}
|
||||
on:mousemove={() => {
|
||||
selectedIndex = i;
|
||||
}}
|
||||
class="flex items-center justify-between px-2.5 py-1.5 rounded-xl w-full text-left {i ===
|
||||
selectedIndex
|
||||
? 'bg-gray-50 dark:bg-gray-800 selected-command-option-button'
|
||||
: ''}"
|
||||
data-selected={i === selectedIndex}
|
||||
>
|
||||
{#if item.type === 'channel'}
|
||||
<div class=" size-4 justify-center flex items-center mr-0.5">
|
||||
{#if isPublicChannel(item?.data)}
|
||||
<Hashtag className="size-3" strokeWidth="2.5" />
|
||||
{:else}
|
||||
<Lock className="size-[0.9375rem]" strokeWidth="2" />
|
||||
{/if}
|
||||
</div>
|
||||
{:else if item.type === 'app'}
|
||||
{#if item.profile_image_url}
|
||||
<ProfileImage
|
||||
src={item.profile_image_url}
|
||||
allowExternal
|
||||
className="size-5 mr-2 rounded-lg!"
|
||||
/>
|
||||
{:else}<Grid className="size-4 mr-2" />{/if}
|
||||
{:else if item.type === 'model'}
|
||||
<img
|
||||
src={`${WEBUI_API_BASE_URL}/models/model/profile/image?id=${item.id}&lang=${$i18n.language}`}
|
||||
alt={item?.data?.name ?? item.id}
|
||||
class="rounded-full size-5 items-center mr-2"
|
||||
on:error={(e) => {
|
||||
// LICENSE covers this Open WebUI fallback logo.
|
||||
// Do not alter, remove, obscure, or replace it except as LICENSE permits:
|
||||
// https://docs.openwebui.com/license.
|
||||
e.currentTarget.src = '/favicon.png';
|
||||
}}
|
||||
/>
|
||||
{:else if item.type === 'user'}
|
||||
<img
|
||||
src={`${WEBUI_API_BASE_URL}/users/${item.id}/profile/image`}
|
||||
alt={item?.label ?? item.id}
|
||||
class="rounded-full size-5 items-center mr-2"
|
||||
on:error={(e) => {
|
||||
// LICENSE covers this Open WebUI fallback logo.
|
||||
// Do not alter, remove, obscure, or replace it except as LICENSE permits:
|
||||
// https://docs.openwebui.com/license.
|
||||
e.currentTarget.src = '/favicon.png';
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<div class="truncate flex-1 pr-2">
|
||||
{item.label}
|
||||
</div>
|
||||
|
||||
<div class="shrink-0 text-xs text-gray-500">
|
||||
{#if item.type === 'user'}
|
||||
{$i18n.t('User')}
|
||||
{:else if item.type === 'model'}
|
||||
{$i18n.t('Model')}
|
||||
{:else if item.type === 'app'}
|
||||
{$i18n.t('App')}
|
||||
{:else if item.type === 'channel'}
|
||||
{$i18n.t('Channel')}
|
||||
<div class="mention-list" id="suggestions-container">
|
||||
<DropdownMenu className="w-72 max-w-[calc(100vw-1rem)] overflow-x-hidden font-sans text-xs">
|
||||
<div class="overflow-y-auto overflow-x-hidden scrollbar-thin max-h-60">
|
||||
{#each filteredItems as item, i}
|
||||
{#if i === 0 || item?.type !== filteredItems[i - 1]?.type}
|
||||
<div class="app-muted mb-0.5 px-2 pt-1 pb-0.5 text-[0.625rem] leading-none">
|
||||
{#if item?.type === 'user'}
|
||||
{$i18n.t('Users')}
|
||||
{:else if item?.type === 'model'}
|
||||
{$i18n.t('Models')}
|
||||
{:else if item?.type === 'app'}
|
||||
{$i18n.t('Apps')}
|
||||
{:else if item?.type === 'channel'}
|
||||
{$i18n.t('Channels')}
|
||||
{/if}
|
||||
</div>
|
||||
</button>
|
||||
</Tooltip>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<Tooltip content={item?.id} placement="top-start">
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => select(i)}
|
||||
on:mousemove={() => {
|
||||
selectedIndex = i;
|
||||
}}
|
||||
class="flex h-6 w-full items-center gap-2 rounded-xl px-2 text-left text-xs transition-colors duration-75 hover:bg-gray-50/40 dark:hover:bg-gray-800/40 {i ===
|
||||
selectedIndex
|
||||
? 'app-interactive-active selected-command-option-button'
|
||||
: ''}"
|
||||
data-selected={i === selectedIndex}
|
||||
>
|
||||
{#if item.type === 'channel'}
|
||||
<div class="app-icon-muted flex w-4 shrink-0 items-center justify-center">
|
||||
{#if isPublicChannel(item?.data)}
|
||||
<Hashtag className="size-3.5" strokeWidth="1.6" />
|
||||
{:else}
|
||||
<Lock className="size-3.5" strokeWidth="1.6" />
|
||||
{/if}
|
||||
</div>
|
||||
{:else if item.type === 'app'}
|
||||
{#if item.profile_image_url}
|
||||
<ProfileImage
|
||||
src={item.profile_image_url}
|
||||
allowExternal
|
||||
className="size-4 shrink-0 rounded-sm!"
|
||||
/>
|
||||
{:else}<Grid className="app-icon-muted size-4 shrink-0" />{/if}
|
||||
{:else if item.type === 'model'}
|
||||
<img
|
||||
src={`${WEBUI_API_BASE_URL}/models/model/profile/image?id=${item.id}&lang=${$i18n.language}`}
|
||||
alt={item?.data?.name ?? item.id}
|
||||
class="size-4 shrink-0 rounded-full object-cover"
|
||||
on:error={(e) => {
|
||||
// LICENSE covers this Open WebUI fallback logo.
|
||||
// Do not alter, remove, obscure, or replace it except as LICENSE permits:
|
||||
// https://docs.openwebui.com/license.
|
||||
e.currentTarget.src = '/favicon.png';
|
||||
}}
|
||||
/>
|
||||
{:else if item.type === 'user'}
|
||||
<img
|
||||
src={`${WEBUI_API_BASE_URL}/users/${item.id}/profile/image`}
|
||||
alt={item?.label ?? item.id}
|
||||
class="size-4 shrink-0 rounded-full object-cover"
|
||||
on:error={(e) => {
|
||||
// LICENSE covers this Open WebUI fallback logo.
|
||||
// Do not alter, remove, obscure, or replace it except as LICENSE permits:
|
||||
// https://docs.openwebui.com/license.
|
||||
e.currentTarget.src = '/favicon.png';
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<div class="min-w-0 flex-1 truncate">
|
||||
{item.label}
|
||||
</div>
|
||||
|
||||
<div class="app-muted max-w-24 shrink-0 truncate text-[0.625rem]">
|
||||
{#if item.type === 'user'}
|
||||
{$i18n.t('User')}
|
||||
{:else if item.type === 'model'}
|
||||
{$i18n.t('Model')}
|
||||
{:else if item.type === 'app'}
|
||||
{$i18n.t('App')}
|
||||
{:else if item.type === 'channel'}
|
||||
{$i18n.t('Channel')}
|
||||
{/if}
|
||||
</div>
|
||||
</button>
|
||||
</Tooltip>
|
||||
{/each}
|
||||
</div>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -245,7 +245,7 @@
|
|||
{#each knowledgeResults as item, idx}
|
||||
{@const itemIdx = idx}
|
||||
{#if idx === 0 || item?.type !== knowledgeResults[idx - 1]?.type}
|
||||
<div class="px-2 py-1 text-[0.6875rem] text-gray-500 dark:text-gray-400">
|
||||
<div class="app-muted mb-0.5 px-2 pt-1 pb-0.5 text-[0.625rem] leading-none">
|
||||
{#if item?.type === 'folder'}
|
||||
{$i18n.t('Folders')}
|
||||
{:else if item?.type === 'collection'}
|
||||
|
|
@ -259,9 +259,9 @@
|
|||
{/if}
|
||||
|
||||
<button
|
||||
class="flex h-[1.6875rem] w-full max-w-full items-center justify-between overflow-hidden rounded-xl px-2 text-left text-[0.8125rem] hover:bg-gray-50/40 dark:hover:bg-gray-800/40 {itemIdx ===
|
||||
class="flex h-6 w-full max-w-full items-center justify-between overflow-hidden rounded-xl px-2 text-left text-xs transition-colors duration-75 hover:bg-gray-50/40 dark:hover:bg-gray-800/40 {itemIdx ===
|
||||
selectedIdx
|
||||
? 'bg-gray-50/40 dark:bg-gray-800/40 dark:text-gray-100 selected-command-option-button'
|
||||
? 'app-interactive-active selected-command-option-button'
|
||||
: ''}"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
|
|
@ -272,11 +272,9 @@
|
|||
}}
|
||||
data-selected={itemIdx === selectedIdx}
|
||||
>
|
||||
<div
|
||||
class="flex w-full min-w-0 items-center gap-1.5 overflow-hidden text-black dark:text-gray-100"
|
||||
>
|
||||
<div class="flex w-full min-w-0 items-center gap-2 overflow-hidden">
|
||||
<Tooltip
|
||||
className="shrink-0 flex"
|
||||
className="app-icon-muted flex w-4 shrink-0 items-center justify-center"
|
||||
content={item?.legacy
|
||||
? $i18n.t('Legacy')
|
||||
: item?.type === 'filesystem'
|
||||
|
|
@ -320,7 +318,7 @@
|
|||
{/if}
|
||||
|
||||
{#if filteredModels.length > 0}
|
||||
<div class="px-2 py-1 text-[0.6875rem] text-gray-500 dark:text-gray-400">
|
||||
<div class="app-muted mb-0.5 px-2 pt-1 pb-0.5 text-[0.625rem] leading-none">
|
||||
{$i18n.t('Models')}
|
||||
</div>
|
||||
|
||||
|
|
@ -328,9 +326,9 @@
|
|||
{@const itemIdx = knowledgeResults.length + modelIdx}
|
||||
<Tooltip content={model.id} placement="top-start">
|
||||
<button
|
||||
class="flex h-[1.6875rem] w-full items-center rounded-xl px-2 text-left text-[0.8125rem] hover:bg-gray-50/40 dark:hover:bg-gray-800/40 {itemIdx ===
|
||||
class="flex h-6 w-full items-center rounded-xl px-2 text-left text-xs transition-colors duration-75 hover:bg-gray-50/40 dark:hover:bg-gray-800/40 {itemIdx ===
|
||||
selectedIdx
|
||||
? 'bg-gray-50/40 dark:bg-gray-800/40 selected-command-option-button'
|
||||
? 'app-interactive-active selected-command-option-button'
|
||||
: ''}"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
|
|
@ -342,11 +340,11 @@
|
|||
on:focus={() => {}}
|
||||
data-selected={itemIdx === selectedIdx}
|
||||
>
|
||||
<div class="flex min-w-0 items-center text-black dark:text-gray-100">
|
||||
<div class="flex min-w-0 items-center gap-2">
|
||||
<img
|
||||
src={`${WEBUI_API_BASE_URL}/models/model/profile/image?id=${model.id}&lang=${$i18n.language}`}
|
||||
alt={resolveLocalizedModelName(model, $i18n.language) ?? model.id}
|
||||
class="mr-2 size-4.5 rounded-full object-cover"
|
||||
class="size-4 shrink-0 rounded-full object-cover"
|
||||
on:error={(e) => {
|
||||
// LICENSE covers this Open WebUI fallback logo.
|
||||
// Do not alter, remove, obscure, or replace it except as LICENSE permits:
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@
|
|||
{#if filteredItems.length > 0 || query.startsWith('http')}
|
||||
{#each filteredItems as item, idx}
|
||||
{#if idx === 0 || item?.type !== items[idx - 1]?.type}
|
||||
<div class="px-2 py-1 text-[0.6875rem] text-gray-500 dark:text-gray-400">
|
||||
<div class="app-muted mb-0.5 px-2 pt-1 pb-0.5 text-[0.625rem] leading-none">
|
||||
{#if item?.type === 'folder'}
|
||||
{$i18n.t('Folders')}
|
||||
{:else if item?.type === 'collection'}
|
||||
|
|
@ -156,9 +156,9 @@
|
|||
|
||||
{#if !['youtube', 'web'].includes(item.type)}
|
||||
<button
|
||||
class="flex h-[1.6875rem] w-full items-center justify-between rounded-xl px-2 text-left text-[0.8125rem] hover:bg-gray-50/40 dark:hover:bg-gray-800/40 {idx ===
|
||||
class="flex h-6 w-full items-center justify-between rounded-xl px-2 text-left text-xs transition-colors duration-75 hover:bg-gray-50/40 dark:hover:bg-gray-800/40 {idx ===
|
||||
selectedIdx
|
||||
? 'bg-gray-50/40 dark:bg-gray-800/40 dark:text-gray-100 selected-command-option-button'
|
||||
? 'app-interactive-active selected-command-option-button'
|
||||
: ''}"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
|
|
@ -173,8 +173,9 @@
|
|||
}}
|
||||
data-selected={idx === selectedIdx}
|
||||
>
|
||||
<div class="flex min-w-0 items-center gap-1.5 text-black dark:text-gray-100">
|
||||
<div class="flex w-full min-w-0 items-center gap-2">
|
||||
<Tooltip
|
||||
className="app-icon-muted flex w-4 shrink-0 items-center justify-center"
|
||||
content={item?.legacy
|
||||
? $i18n.t('Legacy')
|
||||
: item?.type === 'file'
|
||||
|
|
@ -193,7 +194,11 @@
|
|||
{/if}
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip content={`${decodeString(item?.name)}`} placement="top-start">
|
||||
<Tooltip
|
||||
className="min-w-0 flex-1"
|
||||
content={`${decodeString(item?.name)}`}
|
||||
placement="top-start"
|
||||
>
|
||||
<div class="min-w-0 flex-1 truncate">
|
||||
{decodeString(item?.name)}
|
||||
</div>
|
||||
|
|
@ -205,7 +210,7 @@
|
|||
|
||||
{#if webUploadEnabled && isYoutubeUrl(query)}
|
||||
<button
|
||||
class="flex h-[1.6875rem] w-full items-center rounded-xl bg-gray-50/40 px-2 text-left text-[0.8125rem] dark:bg-gray-800/40 dark:text-gray-100 selected-command-option-button"
|
||||
class="flex h-6 w-full items-center rounded-xl app-interactive-active px-2 text-left text-xs transition-colors duration-75 selected-command-option-button"
|
||||
type="button"
|
||||
data-selected={selectedIdx === filteredItems.findIndex((i) => i.type === 'youtube')}
|
||||
on:click={() => {
|
||||
|
|
@ -221,8 +226,12 @@
|
|||
}
|
||||
}}
|
||||
>
|
||||
<div class="flex min-w-0 items-center gap-1.5 text-black dark:text-gray-100">
|
||||
<Tooltip content={$i18n.t('YouTube')} placement="top">
|
||||
<div class="flex w-full min-w-0 items-center gap-2">
|
||||
<Tooltip
|
||||
className="app-icon-muted flex w-4 shrink-0 items-center justify-center"
|
||||
content={$i18n.t('YouTube')}
|
||||
placement="top"
|
||||
>
|
||||
<Youtube className="size-3.5" />
|
||||
</Tooltip>
|
||||
|
||||
|
|
@ -233,7 +242,7 @@
|
|||
</button>
|
||||
{:else if webUploadEnabled && query.startsWith('http')}
|
||||
<button
|
||||
class="flex h-[1.6875rem] w-full items-center rounded-xl bg-gray-50/40 px-2 text-left text-[0.8125rem] dark:bg-gray-800/40 dark:text-gray-100 selected-command-option-button"
|
||||
class="flex h-6 w-full items-center rounded-xl app-interactive-active px-2 text-left text-xs transition-colors duration-75 selected-command-option-button"
|
||||
type="button"
|
||||
data-selected={selectedIdx === filteredItems.findIndex((i) => i.type === 'web')}
|
||||
on:click={() => {
|
||||
|
|
@ -249,8 +258,12 @@
|
|||
}
|
||||
}}
|
||||
>
|
||||
<div class="flex min-w-0 items-center gap-1.5 text-black dark:text-gray-100">
|
||||
<Tooltip content={$i18n.t('Web')} placement="top">
|
||||
<div class="flex w-full min-w-0 items-center gap-2">
|
||||
<Tooltip
|
||||
className="app-icon-muted flex w-4 shrink-0 items-center justify-center"
|
||||
content={$i18n.t('Web')}
|
||||
placement="top"
|
||||
>
|
||||
<GlobeAlt className="size-3.5" />
|
||||
</Tooltip>
|
||||
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@
|
|||
};
|
||||
</script>
|
||||
|
||||
<div class="px-2 py-1 text-[0.6875rem] text-gray-500 dark:text-gray-400">
|
||||
<div class="app-muted mb-0.5 px-2 pt-1 pb-0.5 text-[0.625rem] leading-none">
|
||||
{$i18n.t('Skills')}
|
||||
</div>
|
||||
|
||||
|
|
@ -113,9 +113,9 @@
|
|||
tippyOptions={{ maxWidth: '20rem' }}
|
||||
>
|
||||
<button
|
||||
class="flex h-[1.6875rem] w-full items-center rounded-xl px-2 text-left text-[0.8125rem] hover:bg-gray-50/40 dark:hover:bg-gray-800/40 {skillIdx ===
|
||||
class="flex h-6 w-full items-center rounded-xl px-2 text-left text-xs transition-colors duration-75 hover:bg-gray-50/40 dark:hover:bg-gray-800/40 {skillIdx ===
|
||||
selectedIdx
|
||||
? 'bg-gray-50/40 dark:bg-gray-800/40 selected-command-option-button'
|
||||
? 'app-interactive-active selected-command-option-button'
|
||||
: ''}"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
|
|
@ -127,14 +127,14 @@
|
|||
on:focus={() => {}}
|
||||
data-selected={skillIdx === selectedIdx}
|
||||
>
|
||||
<div class="flex w-full min-w-0 items-center text-black dark:text-gray-100">
|
||||
<div class="mr-2 flex size-4.5 shrink-0 items-center justify-center">
|
||||
<div class="flex w-full min-w-0 items-center gap-2">
|
||||
<div class="app-icon-muted flex w-4 shrink-0 items-center justify-center">
|
||||
<Cube className="size-3.5" />
|
||||
</div>
|
||||
<div class="truncate min-w-0 flex-1">
|
||||
{resolveLocalizedResource(skill, $i18n.language)}
|
||||
</div>
|
||||
<div class="ml-2 max-w-24 shrink-0 truncate text-xs text-gray-500 dark:text-gray-400">
|
||||
<div class="app-muted max-w-24 shrink-0 truncate text-[0.625rem]">
|
||||
{skill.source === 'terminal' ? $i18n.t('Terminal') : skill.id}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue