This commit is contained in:
Timothy Jaeryang Baek 2026-10-31 18:23:26 +09:00
parent 64796faceb
commit a3e261d4cb
5 changed files with 143 additions and 134 deletions

View file

@ -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'

View file

@ -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}

View file

@ -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:

View file

@ -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>

View file

@ -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>