This commit is contained in:
Timothy Jaeryang Baek
2026-07-14 00:10:28 -04:00
parent 0c23466a3e
commit 7088d245bb
18 changed files with 1329 additions and 211 deletions
+14
View File
@@ -22,10 +22,12 @@
import Evaluations from './Settings/Evaluations.svelte';
import CodeExecution from './Settings/CodeExecution.svelte';
import Integrations from './Settings/Integrations.svelte';
import Subagents from './Settings/Subagents.svelte';
import ChartBar from '../icons/ChartBar.svelte';
import DocumentChartBar from '../icons/DocumentChartBar.svelte';
import Search from '../icons/Search.svelte';
import User from '../icons/User.svelte';
import XMark from '../icons/XMark.svelte';
const i18n = getContext('i18n');
@@ -41,6 +43,7 @@
'authentication',
'connections',
'models',
'subagents',
'evaluations',
'integrations',
'documents',
@@ -145,6 +148,12 @@
'export'
]
},
{
id: 'subagents',
title: 'Sub-agents',
route: '/admin/settings/subagents',
keywords: ['sub-agents', 'subagents', 'delegation', 'background', 'agents']
},
{
id: 'evaluations',
title: 'Evaluations',
@@ -345,6 +354,7 @@
<!-- {$i18n.t('Authentication')} -->
<!-- {$i18n.t('Connections')} -->
<!-- {$i18n.t('Models')} -->
<!-- {$i18n.t('Sub-agents')} -->
<!-- {$i18n.t('Evaluations')} -->
<!-- {$i18n.t('Integrations')} -->
<!-- {$i18n.t('Documents')} -->
@@ -431,6 +441,8 @@
clip-rule="evenodd"
/>
</svg>
{:else if tab.id === 'subagents'}
<User className="size-4" />
{:else if tab.id === 'documents'}
<svg
xmlns="http://www.w3.org/2000/svg"
@@ -573,6 +585,8 @@
/>
{:else if selectedTab === 'models'}
<Models />
{:else if selectedTab === 'subagents'}
<Subagents />
{:else if selectedTab === 'evaluations'}
<Evaluations />
{:else if selectedTab === 'integrations'}
@@ -0,0 +1,200 @@
<script lang="ts">
import { getContext, onMount } from 'svelte';
import { toast } from 'svelte-sonner';
import { getSubagentsConfig, setSubagentsConfig } from '$lib/apis/configs';
import Spinner from '$lib/components/common/Spinner.svelte';
import Switch from '$lib/components/common/Switch.svelte';
const i18n = getContext('i18n');
let loading = true;
let saving = false;
let enabled = false;
let backgroundEnabled = false;
let maxConcurrent = 20;
let maxAsync = 20;
let maxIterations = 30;
let maxOutput = 30000;
let systemPrompt = '';
onMount(async () => {
try {
const config = await getSubagentsConfig(localStorage.token);
enabled = config?.ENABLE_SUBAGENTS ?? false;
backgroundEnabled = config?.SUBAGENTS_BACKGROUND_ENABLED ?? false;
maxConcurrent = Number(config?.SUBAGENTS_MAX_CONCURRENT) || 20;
maxAsync = Number(config?.SUBAGENTS_MAX_ASYNC) || 20;
maxIterations = Number(config?.SUBAGENTS_MAX_ITERATIONS) || 30;
maxOutput = Number(config?.SUBAGENTS_MAX_OUTPUT) || 30000;
systemPrompt = config?.SUBAGENTS_SYSTEM_PROMPT ?? '';
} catch (error) {
toast.error(`${error}`);
} finally {
loading = false;
}
});
const save = async () => {
saving = true;
try {
await setSubagentsConfig(localStorage.token, {
ENABLE_SUBAGENTS: enabled,
SUBAGENTS_BACKGROUND_ENABLED: backgroundEnabled,
SUBAGENTS_MAX_CONCURRENT: maxConcurrent,
SUBAGENTS_MAX_ASYNC: maxAsync,
SUBAGENTS_MAX_ITERATIONS: maxIterations,
SUBAGENTS_MAX_OUTPUT: maxOutput,
SUBAGENTS_SYSTEM_PROMPT: systemPrompt
});
toast.success($i18n.t('Settings saved successfully!'));
} catch (error) {
toast.error(`${error}`);
} finally {
saving = false;
}
};
</script>
<form class="flex h-full flex-col justify-between text-sm" on:submit|preventDefault={save}>
<div class="h-full overflow-y-auto scrollbar-hidden">
<div class="mt-0.5 mb-4 text-base font-medium">{$i18n.t('Sub-agents')}</div>
{#if loading}
<div class="flex justify-center py-8"><Spinner className="size-6" /></div>
{:else}
<div class="flex flex-col gap-2.5">
<label class="flex cursor-pointer items-center justify-between">
<span class="text-xs text-gray-600 dark:text-gray-400">
{$i18n.t('Enable sub-agents')}
</span>
<Switch bind:state={enabled} />
</label>
<p class="-mt-1 text-[0.6875rem] text-gray-400 dark:text-gray-600">
{$i18n.t(
'Allow the AI to delegate tasks to sub-agents. Each sub-agent creates a real chat with full tool access. Uses additional LLM calls.'
)}
</p>
{#if enabled}
<div>
<label class="text-xs text-gray-600 dark:text-gray-400" for="sa-concurrent">
{$i18n.t('Max concurrent')}
</label>
<div class="mt-1 flex items-center gap-1.5">
<input
id="sa-concurrent"
type="number"
bind:value={maxConcurrent}
min="-1"
class="h-7 w-16 rounded-lg border border-gray-200 bg-gray-100 px-2 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/10 dark:bg-white/5 dark:text-gray-300 dark:focus:border-blue-500"
/>
<span class="text-[0.6875rem] text-gray-400 dark:text-gray-600">
{$i18n.t('simultaneous sub-agents')}
</span>
</div>
</div>
<div>
<label class="flex cursor-pointer items-center justify-between">
<span class="text-xs text-gray-600 dark:text-gray-400">
{$i18n.t('Enable background sub-agents')}
</span>
<Switch bind:state={backgroundEnabled} />
</label>
<p class="mt-1 text-[0.6875rem] text-gray-400 dark:text-gray-600">
{$i18n.t(
'Allow delegated sub-agents to keep running while the parent chat continues.'
)}
</p>
</div>
{#if backgroundEnabled}
<div>
<label class="text-xs text-gray-600 dark:text-gray-400" for="sa-async">
{$i18n.t('Max background')}
</label>
<div class="mt-1 flex items-center gap-1.5">
<input
id="sa-async"
type="number"
bind:value={maxAsync}
min="-1"
class="h-7 w-16 rounded-lg border border-gray-200 bg-gray-100 px-2 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/10 dark:bg-white/5 dark:text-gray-300 dark:focus:border-blue-500"
/>
<span class="text-[0.6875rem] text-gray-400 dark:text-gray-600">
{$i18n.t('background sub-agents')}
</span>
</div>
</div>
{/if}
<div>
<label class="text-xs text-gray-600 dark:text-gray-400" for="sa-iterations">
{$i18n.t('Max iterations')}
</label>
<div class="mt-1 flex items-center gap-1.5">
<input
id="sa-iterations"
type="number"
bind:value={maxIterations}
min="1"
max="100"
class="h-7 w-16 rounded-lg border border-gray-200 bg-gray-100 px-2 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/10 dark:bg-white/5 dark:text-gray-300 dark:focus:border-blue-500"
/>
<span class="text-[0.6875rem] text-gray-400 dark:text-gray-600">
{$i18n.t('tool loops per sub-agent')}
</span>
</div>
</div>
<div>
<label class="text-xs text-gray-600 dark:text-gray-400" for="sa-output">
{$i18n.t('Max output')}
</label>
<div class="mt-1 flex items-center gap-1.5">
<input
id="sa-output"
type="number"
bind:value={maxOutput}
min="1000"
max="100000"
step="1000"
class="h-7 w-20 rounded-lg border border-gray-200 bg-gray-100 px-2 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/10 dark:bg-white/5 dark:text-gray-300 dark:focus:border-blue-500"
/>
<span class="text-[0.6875rem] text-gray-400 dark:text-gray-600">chars</span>
</div>
</div>
<div>
<label class="text-xs text-gray-600 dark:text-gray-400" for="sa-prompt">
{$i18n.t('System prompt')}
</label>
<textarea
id="sa-prompt"
bind:value={systemPrompt}
rows="4"
placeholder={$i18n.t('You are a sub-agent...')}
class="mt-1 w-full resize-y rounded-lg border border-gray-200 bg-gray-100 px-2 py-1.5 font-mono text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/10 dark:bg-white/5 dark:text-gray-300 dark:focus:border-blue-500"
></textarea>
<p class="mt-0.5 text-[0.6875rem] text-gray-400 dark:text-gray-600">
{$i18n.t('Leave empty for the built-in default.')}
</p>
</div>
{/if}
</div>
{/if}
</div>
{#if !loading}
<div class="flex justify-end pt-6 text-sm font-medium">
<button
class="rounded-full bg-black px-3.5 py-1.5 text-sm font-medium text-white transition hover:bg-gray-900 disabled:opacity-50 dark:bg-white dark:text-black dark:hover:bg-gray-100"
type="submit"
disabled={saving}
>
{$i18n.t('Save')}
</button>
</div>
{/if}
</form>
@@ -0,0 +1,67 @@
<script lang="ts">
import { getContext } from 'svelte';
import type { Readable } from 'svelte/store';
import ChevronDown from '$lib/components/icons/ChevronDown.svelte';
type SubagentResult = {
delegation_id?: string;
delegation_ids?: string[];
subagent_chat_id?: string;
subagent_chat_ids?: string[];
};
const i18n = getContext<Readable<{ t: (value: string) => string }>>('i18n');
export let content: string;
export let result: SubagentResult;
let expanded = false;
$: delegationIds = Array.isArray(result.delegation_ids) ? result.delegation_ids : [];
$: delegationLabel =
result.delegation_id ?? (delegationIds.length > 1 ? `${delegationIds.length} tasks` : '');
$: summary = (() => {
const line = content
.split('\n')
.map((value) => value.trim())
.find((value) => value && !value.startsWith('['));
if (!line) return delegationLabel;
return line.length > 96 ? `${line.slice(0, 96)}...` : line;
})();
</script>
<div class="w-full min-w-0 pb-1">
<button
type="button"
class="w-full min-w-0 flex items-center gap-2 text-left text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 transition-colors"
aria-expanded={expanded}
on:click={() => (expanded = !expanded)}
>
<span class="w-1.5 h-1.5 rounded-full bg-gray-300 dark:bg-gray-600 shrink-0"></span>
<span class="text-[0.75rem] font-medium shrink-0">
{$i18n.t('Background sub-agent finished')}
</span>
{#if summary}
<span class="text-[0.75rem] truncate min-w-0 flex-1">{summary}</span>
{/if}
{#if delegationLabel}
<span
class="hidden sm:inline text-[0.6875rem] font-mono text-gray-400 dark:text-gray-600 shrink-0"
>
{delegationLabel}
</span>
{/if}
<ChevronDown
className="size-3 text-gray-400 dark:text-gray-600 shrink-0 transition-transform duration-150 {expanded
? 'rotate-180'
: ''}"
/>
</button>
{#if expanded}
<div
class="mt-2 ml-3 border-l border-gray-100 dark:border-white/8 pl-3 text-[0.78125rem] leading-relaxed text-gray-600 dark:text-gray-400 whitespace-pre-wrap break-words"
>
{content}
</div>
{/if}
</div>
@@ -16,11 +16,19 @@
import Markdown from './Markdown.svelte';
import Image from '$lib/components/common/Image.svelte';
import DeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
import SubagentResultRow from './SubagentResultRow.svelte';
import localizedFormat from 'dayjs/plugin/localizedFormat';
const i18n = getContext('i18n');
dayjs.extend(localizedFormat);
type SubagentResult = {
async_subagent_result: true;
delegation_id?: string;
delegation_ids?: string[];
subagent_chat_id?: string;
subagent_chat_ids?: string[];
};
export let user;
@@ -54,6 +62,7 @@
let editScrollContainer: HTMLDivElement;
let message = structuredClone(history.messages[messageId]);
let subagentResult: SubagentResult | undefined;
$: if (history.messages) {
const source = history.messages[messageId];
if (source) {
@@ -64,6 +73,7 @@
}
}
}
$: subagentResult = message?.meta?.async_subagent_result ? message.meta : undefined;
const copyToClipboard = async (text) => {
const res = await _copyToClipboard(text);
@@ -133,7 +143,7 @@
id="message-{message.id}"
style="scroll-margin-top: 3rem;"
>
{#if !($settings?.chatBubble ?? true)}
{#if !($settings?.chatBubble ?? true) && !subagentResult}
<div class={`shrink-0 ltr:mr-3 rtl:ml-3 mt-1`}>
<ProfileImage
src={user?.id
@@ -143,8 +153,8 @@
/>
</div>
{/if}
<div class="flex-auto w-0 max-w-full pl-1">
{#if !($settings?.chatBubble ?? true)}
<div class="flex-auto w-0 max-w-full {subagentResult ? '' : 'pl-1'}">
{#if !($settings?.chatBubble ?? true) && !subagentResult}
<div>
<Name>
{#if message.user}
@@ -179,7 +189,7 @@
{/if}
</Name>
</div>
{:else if message.timestamp}
{:else if message.timestamp && !subagentResult}
<div class="flex justify-end pr-2 text-xs">
<div
class="text-[0.65rem] font-medium first-letter:capitalize mb-0.5 {($settings?.highContrastMode ??
@@ -365,6 +375,8 @@
</div>
</div>
</div>
{:else if subagentResult}
<SubagentResultRow content={message.content} result={subagentResult} />
{:else if message.content !== ''}
<div class="w-full">
<div class="flex {($settings?.chatBubble ?? true) ? 'justify-end pb-1' : 'w-full'}">
@@ -394,7 +406,7 @@
</div>
{/if}
{#if edit !== true}
{#if edit !== true && !subagentResult}
<div
class=" flex {($settings?.chatBubble ?? true)
? 'justify-end'
@@ -127,6 +127,20 @@ function buildToolCallToken(item: OutputItem, toolOutputByCallId: Record<string,
const callId = item.call_id ?? '';
const resultItem = toolOutputByCallId[callId];
const isDone = isDoneStatus(item.status) || !!resultItem;
let name = item.name ?? '';
if (name === 'delegate_task') {
try {
const args =
typeof item.arguments === 'string'
? JSON.parse(item.arguments || '{}')
: (item.arguments ?? {});
const task = typeof args.task === 'string' && args.task ? args.task : '?';
const label = args.background ? 'Background sub-agent' : 'Sub-agent';
name = `${label}: "${task.length > 60 ? `${task.slice(0, 60)}...` : task}"`;
} catch {
name = 'Sub-agent';
}
}
return {
summary: isDone ? 'Tool Executed' : 'Executing...',
@@ -134,7 +148,7 @@ function buildToolCallToken(item: OutputItem, toolOutputByCallId: Record<string,
attributes: {
type: 'tool_calls',
id: callId,
name: item.name ?? '',
name,
done: isDone ? 'true' : 'false',
arguments: stringifyAttribute(item.arguments ?? ''),
files: stringifyAttribute(resultItem?.files),
+2 -2
View File
@@ -999,7 +999,7 @@
<div class="self-center relative">
<img
src={`${WEBUI_API_BASE_URL}/users/${$user?.id}/profile/image`}
class=" size-7 object-cover rounded-full"
class="size-5.5 object-cover rounded-full"
alt={$i18n.t('Open User Profile Menu')}
aria-label={$i18n.t('Open User Profile Menu')}
/>
@@ -1627,7 +1627,7 @@
<div class=" self-center mr-3 relative flex-shrink-0">
<img
src={`${WEBUI_API_BASE_URL}/users/${$user?.id}/profile/image`}
class=" size-7 object-cover rounded-full"
class="size-5.5 object-cover rounded-full"
alt={$i18n.t('Open User Profile Menu')}
aria-label={$i18n.t('Open User Profile Menu')}
/>
@@ -122,49 +122,13 @@
<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"
class="{className} user-menu 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 text-xs"
>
{#if profile}
<div class=" flex gap-3.5 w-full p-2.5 items-center">
<div class=" items-center flex shrink-0">
<img
src={`${WEBUI_API_BASE_URL}/users/${$user?.id}/profile/image`}
class=" size-10 object-cover rounded-full"
alt="profile"
/>
</div>
<div class=" flex flex-col w-full flex-1">
<div class="font-medium line-clamp-1 pr-2">
{$user.name}
</div>
<div class=" flex items-center gap-2">
{#if $user?.is_active ?? true}
<div>
<span class="relative flex size-2">
<span class="relative inline-flex rounded-full size-2 bg-green-500" />
</span>
</div>
<span class="text-xs"> {$i18n.t('Active')} </span>
{:else}
<div>
<span class="relative flex size-2">
<span class="relative inline-flex rounded-full size-2 bg-gray-500" />
</span>
</div>
<span class="text-xs"> {$i18n.t('Away')} </span>
{/if}
</div>
</div>
</div>
{#if $user?.status_emoji || $user?.status_message}
<div class="mx-1">
<div class="user-menu-status">
<button
class="mb-1 w-full gap-2 px-2.5 py-1.5 rounded-xl bg-gray-50 dark:text-white dark:bg-gray-900/50 text-black transition text-xs flex items-center"
class="w-full gap-2 hover:bg-gray-50/60 dark:hover:bg-gray-800/60 transition cursor-pointer select-none text-xs flex items-center"
type="button"
on:click={() => {
show = false;
@@ -184,9 +148,10 @@
{$user?.status_message}
</Tooltip>
<div class="self-start">
<div class="self-center">
<Tooltip content={$i18n.t('Clear status')}>
<button
class="flex size-5 items-center justify-center"
type="button"
on:click={async (e) => {
e.preventDefault();
@@ -213,9 +178,9 @@
</button>
</div>
{:else}
<div class="mx-1">
<div class="user-menu-status">
<button
class="mb-1 w-full px-3 py-1.5 gap-1 rounded-xl bg-gray-50 dark:text-white dark:bg-gray-900/50 text-black transition text-xs flex items-center justify-center"
class="w-full gap-1 hover:bg-gray-50/60 dark:hover:bg-gray-800/60 transition cursor-pointer select-none text-xs flex items-center justify-center"
type="button"
on:click={() => {
show = false;
@@ -234,7 +199,7 @@
{/if}
<button
class="flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none"
class="flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50/60 dark:hover:bg-gray-800/60 transition cursor-pointer select-none"
type="button"
on:click={async () => {
show = false;
@@ -257,7 +222,7 @@
<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"
class="flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50/60 dark:hover:bg-gray-800/60 transition cursor-pointer select-none"
on:click={async (e) => {
if (e.metaKey || e.ctrlKey || e.shiftKey || e.button === 1) {
return;
@@ -279,7 +244,7 @@
{/if}
<button
class="flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none"
class="flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50/60 dark:hover:bg-gray-800/60 transition cursor-pointer select-none"
type="button"
on:click={async () => {
show = false;
@@ -306,7 +271,7 @@
<a
href="/workspace"
draggable="false"
class="flex flex-1 rounded-xl py-1.5 px-3 hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none"
class="flex flex-1 rounded-xl py-1.5 px-3 hover:bg-gray-50/60 dark:hover:bg-gray-800/60 transition cursor-pointer select-none"
on:click={async (e) => {
if (e.metaKey || e.ctrlKey || e.shiftKey || e.button === 1) return;
e.preventDefault();
@@ -344,7 +309,7 @@
>
<button
type="button"
class="p-1 mr-1 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition"
class="p-1 mr-1 rounded-lg hover:bg-gray-100/60 dark:hover:bg-gray-700/60 transition"
on:click|preventDefault|stopPropagation={() => togglePin('workspace')}
>
{#if isPinned('workspace')}
@@ -363,7 +328,7 @@
<a
href="/notes"
draggable="false"
class="flex flex-1 rounded-xl py-1.5 px-3 hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none"
class="flex flex-1 rounded-xl py-1.5 px-3 hover:bg-gray-50/60 dark:hover:bg-gray-800/60 transition cursor-pointer select-none"
on:click={async (e) => {
if (e.metaKey || e.ctrlKey || e.shiftKey || e.button === 1) return;
e.preventDefault();
@@ -388,7 +353,7 @@
>
<button
type="button"
class="p-1 mr-1 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition"
class="p-1 mr-1 rounded-lg hover:bg-gray-100/60 dark:hover:bg-gray-700/60 transition"
on:click|preventDefault|stopPropagation={() => togglePin('notes')}
>
{#if isPinned('notes')}
@@ -407,7 +372,7 @@
<a
href="/calendar"
draggable="false"
class="flex flex-1 rounded-xl py-1.5 px-3 hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none"
class="flex flex-1 rounded-xl py-1.5 px-3 hover:bg-gray-50/60 dark:hover:bg-gray-800/60 transition cursor-pointer select-none"
on:click={async (e) => {
if (e.metaKey || e.ctrlKey || e.shiftKey || e.button === 1) return;
e.preventDefault();
@@ -441,7 +406,7 @@
>
<button
type="button"
class="p-1 mr-1 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition"
class="p-1 mr-1 rounded-lg hover:bg-gray-100/60 dark:hover:bg-gray-700/60 transition"
on:click|preventDefault|stopPropagation={() => togglePin('calendar')}
>
{#if isPinned('calendar')}
@@ -460,7 +425,7 @@
<a
href="/automations"
draggable="false"
class="flex flex-1 rounded-xl py-1.5 px-3 hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none"
class="flex flex-1 rounded-xl py-1.5 px-3 hover:bg-gray-50/60 dark:hover:bg-gray-800/60 transition cursor-pointer select-none"
on:click={async (e) => {
if (e.metaKey || e.ctrlKey || e.shiftKey || e.button === 1) return;
e.preventDefault();
@@ -498,7 +463,7 @@
>
<button
type="button"
class="p-1 mr-1 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition"
class="p-1 mr-1 rounded-lg hover:bg-gray-100/60 dark:hover:bg-gray-700/60 transition"
on:click|preventDefault|stopPropagation={() => togglePin('automations')}
>
{#if isPinned('automations')}
@@ -517,7 +482,7 @@
<a
href="/playground"
draggable="false"
class="flex flex-1 rounded-xl py-1.5 px-3 hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none"
class="flex flex-1 rounded-xl py-1.5 px-3 hover:bg-gray-50/60 dark:hover:bg-gray-800/60 transition cursor-pointer select-none"
on:click={async (e) => {
if (e.metaKey || e.ctrlKey || e.shiftKey || e.button === 1) return;
e.preventDefault();
@@ -542,7 +507,7 @@
>
<button
type="button"
class="p-1 mr-1 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition"
class="p-1 mr-1 rounded-lg hover:bg-gray-100/60 dark:hover:bg-gray-700/60 transition"
on:click|preventDefault|stopPropagation={() => togglePin('playground')}
>
{#if isPinned('playground')}
@@ -566,7 +531,7 @@
href="https://docs.openwebui.com"
target="_blank"
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"
class="flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50/60 dark:hover:bg-gray-800/60 transition cursor-pointer select-none"
id="chat-share-button"
on:click={() => {
show = false;
@@ -583,7 +548,7 @@
href="https://github.com/open-webui/open-webui/releases"
target="_blank"
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"
class="flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50/60 dark:hover:bg-gray-800/60 transition cursor-pointer select-none"
id="chat-share-button"
on:click={() => {
show = false;
@@ -597,7 +562,7 @@
{/if}
<button
class="flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none"
class="flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50/60 dark:hover:bg-gray-800/60 transition cursor-pointer select-none"
type="button"
id="chat-share-button"
on:click={async () => {
@@ -620,7 +585,7 @@
<hr class=" border-gray-50/30 dark:border-gray-800/30 my-1 p-0" />
<button
class="flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition cursor-pointer select-none"
class="flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50/60 dark:hover:bg-gray-800/60 transition cursor-pointer select-none"
type="button"
on:click={async () => {
const res = await userSignOut();
@@ -647,7 +612,7 @@
: ''}
>
<div
class="flex rounded-xl py-1 px-3 text-xs gap-2.5 items-center"
class="flex rounded-xl px-2 py-0.5 text-[10px] gap-1.5 items-center"
on:mouseenter={() => {
if ($config?.features?.enable_public_active_users_count || role === 'admin') {
getUsageInfo();
@@ -655,8 +620,8 @@
}}
>
<div class=" flex items-center">
<span class="relative flex size-2">
<span class="relative inline-flex rounded-full size-2 bg-green-500" />
<span class="relative flex size-1.5">
<span class="relative inline-flex rounded-full size-1.5 bg-green-500" />
</span>
</div>
@@ -664,7 +629,7 @@
<span class="">
{$i18n.t('Active Users')}:
</span>
<span class=" font-semibold">
<span class="font-medium">
{usage?.user_count}
</span>
</div>
@@ -675,3 +640,34 @@
</div>
</div>
</Dropdown>
<style>
.user-menu > button,
.user-menu > a,
.user-menu > div > a {
height: 1.6875rem;
padding: 0 0.5rem;
border-radius: 0.75rem;
}
.user-menu > button > div:first-child,
.user-menu > a > div:first-child,
.user-menu > div > a > div:first-child {
margin-right: 0.5rem;
}
.user-menu > .user-menu-status > button {
height: 1.6875rem;
padding: 0 0.5rem;
border-radius: 0.75rem;
}
.user-menu > hr {
margin: 0.125rem 0.25rem;
}
.user-menu :global(svg) {
width: 0.875rem;
height: 0.875rem;
}
</style>
+4 -1
View File
@@ -616,7 +616,10 @@
}
}
if ((event.chat_id !== $chatId && !$temporaryChatEnabled) || isInBackground) {
if (
!event?.internal &&
((event.chat_id !== $chatId && !$temporaryChatEnabled) || isInBackground)
) {
if (type === 'chat:completion') {
const { done, content, output, title } = data;
const displayTitle = title || $i18n.t('New Chat');