refac
This commit is contained in:
@@ -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),
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user