refac
This commit is contained in:
@@ -393,94 +393,96 @@
|
||||
</nav>
|
||||
|
||||
<div class="flex-1 min-h-0 p-4 lg:px-5 flex flex-col">
|
||||
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">
|
||||
{$i18n.t(selectedTabTitle)}
|
||||
</h2>
|
||||
<div class="w-full h-full min-h-0 flex flex-col">
|
||||
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">
|
||||
{$i18n.t(selectedTabTitle)}
|
||||
</h2>
|
||||
|
||||
<div class="flex-1 min-h-0 overflow-hidden">
|
||||
{#if selectedTab === 'general'}
|
||||
<General
|
||||
saveHandler={async () => {
|
||||
toast.success($i18n.t('Settings saved successfully!'));
|
||||
<div class="flex-1 min-h-0 overflow-hidden">
|
||||
{#if selectedTab === 'general'}
|
||||
<General
|
||||
saveHandler={async () => {
|
||||
toast.success($i18n.t('Settings saved successfully!'));
|
||||
|
||||
await tick();
|
||||
await config.set(await getBackendConfig());
|
||||
}}
|
||||
/>
|
||||
{:else if selectedTab === 'authentication'}
|
||||
<Authentication />
|
||||
{:else if selectedTab === 'connections'}
|
||||
<Connections
|
||||
on:save={() => {
|
||||
toast.success($i18n.t('Settings saved successfully!'));
|
||||
}}
|
||||
/>
|
||||
{:else if selectedTab === 'models'}
|
||||
<Models />
|
||||
{:else if selectedTab === 'subagents'}
|
||||
<Subagents />
|
||||
{:else if selectedTab === 'evaluations'}
|
||||
<Evaluations />
|
||||
{:else if selectedTab === 'integrations'}
|
||||
<Integrations />
|
||||
{:else if selectedTab === 'documents'}
|
||||
<Documents
|
||||
on:save={async () => {
|
||||
toast.success($i18n.t('Settings saved successfully!'));
|
||||
await tick();
|
||||
await config.set(await getBackendConfig());
|
||||
}}
|
||||
/>
|
||||
{:else if selectedTab === 'authentication'}
|
||||
<Authentication />
|
||||
{:else if selectedTab === 'connections'}
|
||||
<Connections
|
||||
on:save={() => {
|
||||
toast.success($i18n.t('Settings saved successfully!'));
|
||||
}}
|
||||
/>
|
||||
{:else if selectedTab === 'models'}
|
||||
<Models />
|
||||
{:else if selectedTab === 'subagents'}
|
||||
<Subagents />
|
||||
{:else if selectedTab === 'evaluations'}
|
||||
<Evaluations />
|
||||
{:else if selectedTab === 'integrations'}
|
||||
<Integrations />
|
||||
{:else if selectedTab === 'documents'}
|
||||
<Documents
|
||||
on:save={async () => {
|
||||
toast.success($i18n.t('Settings saved successfully!'));
|
||||
|
||||
await tick();
|
||||
await config.set(await getBackendConfig());
|
||||
}}
|
||||
/>
|
||||
{:else if selectedTab === 'web'}
|
||||
<WebSearch
|
||||
saveHandler={async () => {
|
||||
toast.success($i18n.t('Settings saved successfully!'));
|
||||
await tick();
|
||||
await config.set(await getBackendConfig());
|
||||
}}
|
||||
/>
|
||||
{:else if selectedTab === 'web'}
|
||||
<WebSearch
|
||||
saveHandler={async () => {
|
||||
toast.success($i18n.t('Settings saved successfully!'));
|
||||
|
||||
await tick();
|
||||
await config.set(await getBackendConfig());
|
||||
}}
|
||||
/>
|
||||
{:else if selectedTab === 'code-execution'}
|
||||
<CodeExecution
|
||||
saveHandler={async () => {
|
||||
toast.success($i18n.t('Settings saved successfully!'));
|
||||
await tick();
|
||||
await config.set(await getBackendConfig());
|
||||
}}
|
||||
/>
|
||||
{:else if selectedTab === 'code-execution'}
|
||||
<CodeExecution
|
||||
saveHandler={async () => {
|
||||
toast.success($i18n.t('Settings saved successfully!'));
|
||||
|
||||
await tick();
|
||||
await config.set(await getBackendConfig());
|
||||
}}
|
||||
/>
|
||||
{:else if selectedTab === 'interface'}
|
||||
<Interface
|
||||
on:save={() => {
|
||||
toast.success($i18n.t('Settings saved successfully!'));
|
||||
}}
|
||||
/>
|
||||
{:else if selectedTab === 'audio'}
|
||||
<Audio
|
||||
saveHandler={() => {
|
||||
toast.success($i18n.t('Settings saved successfully!'));
|
||||
}}
|
||||
/>
|
||||
{:else if selectedTab === 'images'}
|
||||
<Images
|
||||
on:save={() => {
|
||||
toast.success($i18n.t('Settings saved successfully!'));
|
||||
}}
|
||||
/>
|
||||
{:else if selectedTab === 'db'}
|
||||
<Database
|
||||
saveHandler={() => {
|
||||
toast.success($i18n.t('Settings saved successfully!'));
|
||||
}}
|
||||
/>
|
||||
{:else if selectedTab === 'pipelines'}
|
||||
<Pipelines
|
||||
saveHandler={() => {
|
||||
toast.success($i18n.t('Settings saved successfully!'));
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
await tick();
|
||||
await config.set(await getBackendConfig());
|
||||
}}
|
||||
/>
|
||||
{:else if selectedTab === 'interface'}
|
||||
<Interface
|
||||
on:save={() => {
|
||||
toast.success($i18n.t('Settings saved successfully!'));
|
||||
}}
|
||||
/>
|
||||
{:else if selectedTab === 'audio'}
|
||||
<Audio
|
||||
saveHandler={() => {
|
||||
toast.success($i18n.t('Settings saved successfully!'));
|
||||
}}
|
||||
/>
|
||||
{:else if selectedTab === 'images'}
|
||||
<Images
|
||||
on:save={() => {
|
||||
toast.success($i18n.t('Settings saved successfully!'));
|
||||
}}
|
||||
/>
|
||||
{:else if selectedTab === 'db'}
|
||||
<Database
|
||||
saveHandler={() => {
|
||||
toast.success($i18n.t('Settings saved successfully!'));
|
||||
}}
|
||||
/>
|
||||
{:else if selectedTab === 'pipelines'}
|
||||
<Pipelines
|
||||
saveHandler={() => {
|
||||
toast.success($i18n.t('Settings saved successfully!'));
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<script lang="ts">
|
||||
export let label = '';
|
||||
export let description = '';
|
||||
export let forId = '';
|
||||
export let className = '';
|
||||
</script>
|
||||
|
||||
<div class={className}>
|
||||
{#if label}
|
||||
<label class="text-xs text-gray-600 dark:text-gray-400" for={forId || undefined}>
|
||||
{label}
|
||||
</label>
|
||||
{/if}
|
||||
|
||||
<div class={label ? 'mt-1' : ''}>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
{#if description}
|
||||
<p class="mt-0.5 text-[0.6875rem] text-gray-400 dark:text-gray-600">
|
||||
{description}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -0,0 +1,22 @@
|
||||
<script lang="ts">
|
||||
export let label = '';
|
||||
export let description = '';
|
||||
export let className = '';
|
||||
export let labelClassName = '';
|
||||
</script>
|
||||
|
||||
<div class="flex items-center justify-between gap-4 {className}">
|
||||
<div class="min-w-0 text-xs text-gray-600 dark:text-gray-400 {labelClassName}">
|
||||
<slot name="label">{label}</slot>
|
||||
</div>
|
||||
|
||||
<div class="shrink-0">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if description}
|
||||
<p class="-mt-1 text-[0.6875rem] text-gray-400 dark:text-gray-600">
|
||||
{description}
|
||||
</p>
|
||||
{/if}
|
||||
@@ -0,0 +1,17 @@
|
||||
<script lang="ts">
|
||||
export let title = '';
|
||||
export let first = false;
|
||||
export let className = '';
|
||||
</script>
|
||||
|
||||
<section class="{first ? '' : 'mt-5'} {className}">
|
||||
{#if title}
|
||||
<h3 class="mb-2 text-xs text-gray-400 dark:text-gray-600">
|
||||
{title}
|
||||
</h3>
|
||||
{/if}
|
||||
|
||||
<div class="flex flex-col gap-2.5">
|
||||
<slot />
|
||||
</div>
|
||||
</section>
|
||||
@@ -227,28 +227,28 @@
|
||||
</script>
|
||||
|
||||
<form
|
||||
class="flex flex-col h-full justify-between space-y-3 text-sm"
|
||||
class="flex h-full flex-col justify-between text-sm"
|
||||
on:submit|preventDefault={async () => {
|
||||
await updateConfigHandler();
|
||||
dispatch('save');
|
||||
}}
|
||||
>
|
||||
<div class=" space-y-3 flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5 -mr-1.5">
|
||||
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5">
|
||||
<div class="flex flex-col gap-3">
|
||||
<div>
|
||||
<div class=" mt-0.5 mb-2.5 text-xs text-gray-400 dark:text-gray-600">
|
||||
<div class=" mb-2 text-xs text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('Speech-to-Text')}
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-100/20 dark:border-white/[0.025] my-2" />
|
||||
|
||||
{#if STT_ENGINE !== 'web'}
|
||||
<div class="mb-2">
|
||||
<div class=" mb-1.5 text-xs font-normal">{$i18n.t('Supported MIME Types')}</div>
|
||||
<div class=" mb-1.5 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Supported MIME Types')}
|
||||
</div>
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
bind:value={STT_SUPPORTED_CONTENT_TYPES}
|
||||
placeholder={$i18n.t(
|
||||
'e.g., audio/wav,audio/mpeg,video/* (leave blank for defaults)'
|
||||
@@ -260,10 +260,12 @@
|
||||
{/if}
|
||||
|
||||
<div class="mb-2 py-0.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-normal">{$i18n.t('Speech-to-Text Engine')}</div>
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Speech-to-Text Engine')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
<select
|
||||
class="cursor-pointer w-fit pr-8 rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
|
||||
class="cursor-pointer w-fit pr-8 rounded-lg px-2 p-1 text-xs bg-transparent outline-hidden text-right"
|
||||
bind:value={STT_ENGINE}
|
||||
placeholder={$i18n.t('Select an engine')}
|
||||
>
|
||||
@@ -291,13 +293,13 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="border-gray-100/20 dark:border-white/[0.025] my-2" />
|
||||
|
||||
<div class="mb-2 py-0.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-normal">{$i18n.t('Request Format')}</div>
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Request Format')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
<select
|
||||
class="cursor-pointer w-fit pr-8 rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
|
||||
class="cursor-pointer w-fit pr-8 rounded-lg px-2 p-1 text-xs bg-transparent outline-hidden text-right"
|
||||
bind:value={STT_OPENAI_API_REQUEST_FORMAT}
|
||||
>
|
||||
<option value="multipart">{$i18n.t('Multipart Upload')}</option>
|
||||
@@ -306,15 +308,15 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="border-gray-100/20 dark:border-white/[0.025] my-2" />
|
||||
|
||||
<div>
|
||||
<div class=" mb-1.5 text-xs font-normal">{$i18n.t('STT Model')}</div>
|
||||
<div class=" mb-1.5 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('STT Model')}
|
||||
</div>
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
list="model-list"
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
bind:value={STT_MODEL}
|
||||
placeholder={$i18n.t('Select a model')}
|
||||
/>
|
||||
@@ -332,14 +334,14 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="border-gray-100/20 dark:border-white/[0.025] my-2" />
|
||||
|
||||
<div>
|
||||
<div class=" mb-1.5 text-xs font-normal">{$i18n.t('STT Model')}</div>
|
||||
<div class=" mb-1.5 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('STT Model')}
|
||||
</div>
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
bind:value={STT_MODEL}
|
||||
placeholder={$i18n.t('Select a model (optional)')}
|
||||
/>
|
||||
@@ -366,14 +368,14 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<hr class="border-gray-100/20 dark:border-white/[0.025] my-2" />
|
||||
|
||||
<div>
|
||||
<div class=" mb-1.5 text-xs font-normal">{$i18n.t('Azure Region')}</div>
|
||||
<div class=" mb-1.5 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Azure Region')}
|
||||
</div>
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
bind:value={STT_AZURE_REGION}
|
||||
placeholder={$i18n.t('e.g., westus (leave blank for eastus)')}
|
||||
/>
|
||||
@@ -382,11 +384,13 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class=" mb-1.5 text-xs font-normal">{$i18n.t('Language Locales')}</div>
|
||||
<div class=" mb-1.5 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Language Locales')}
|
||||
</div>
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
bind:value={STT_AZURE_LOCALES}
|
||||
placeholder={$i18n.t('e.g., en-US,ja-JP (leave blank for auto-detect)')}
|
||||
/>
|
||||
@@ -395,11 +399,13 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class=" mb-1.5 text-xs font-normal">{$i18n.t('Endpoint URL')}</div>
|
||||
<div class=" mb-1.5 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Endpoint URL')}
|
||||
</div>
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
bind:value={STT_AZURE_BASE_URL}
|
||||
placeholder={$i18n.t('(leave blank for to use commercial endpoint)')}
|
||||
/>
|
||||
@@ -408,11 +414,13 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class=" mb-1.5 text-xs font-normal">{$i18n.t('Max Speakers')}</div>
|
||||
<div class=" mb-1.5 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Max Speakers')}
|
||||
</div>
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
bind:value={STT_AZURE_MAX_SPEAKERS}
|
||||
placeholder={$i18n.t('e.g., 3, 4, 5 (leave blank for default)')}
|
||||
/>
|
||||
@@ -434,14 +442,14 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="border-gray-100/20 dark:border-white/[0.025] my-2" />
|
||||
|
||||
<div>
|
||||
<div class=" mb-1.5 text-xs font-normal">{$i18n.t('STT Model')}</div>
|
||||
<div class=" mb-1.5 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('STT Model')}
|
||||
</div>
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
bind:value={STT_MODEL}
|
||||
placeholder="voxtral-mini-latest"
|
||||
/>
|
||||
@@ -459,11 +467,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="border-gray-100/20 dark:border-white/[0.025] my-2" />
|
||||
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<div class="text-xs font-normal">{$i18n.t('Use Chat Completions API')}</div>
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Use Chat Completions API')}
|
||||
</div>
|
||||
<label class="relative inline-flex items-center cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -483,19 +491,21 @@
|
||||
</div>
|
||||
{:else if STT_ENGINE === ''}
|
||||
<div>
|
||||
<div class=" mb-1.5 text-xs font-normal">{$i18n.t('STT Model')}</div>
|
||||
<div class=" mb-1.5 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('STT Model')}
|
||||
</div>
|
||||
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1 mr-2">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
placeholder={$i18n.t('Set whisper model')}
|
||||
bind:value={STT_WHISPER_MODEL}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="px-2.5 bg-gray-50 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
|
||||
class="px-2.5 hover:bg-black/5 text-gray-800 dark:hover:bg-white/5 dark:text-gray-100 rounded-lg transition"
|
||||
on:click={() => {
|
||||
sttModelUpdateHandler();
|
||||
}}
|
||||
@@ -541,17 +551,17 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class=" mt-0.5 mb-2.5 text-xs text-gray-400 dark:text-gray-600">
|
||||
<div class=" mb-2 text-xs text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('Text-to-Speech')}
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-100/20 dark:border-white/[0.025] my-2" />
|
||||
|
||||
<div class="mb-2 py-0.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-normal">{$i18n.t('Text-to-Speech Engine')}</div>
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Text-to-Speech Engine')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
<select
|
||||
class="w-fit pr-8 cursor-pointer rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
|
||||
class="w-fit pr-8 cursor-pointer rounded-lg px-2 p-1 text-xs bg-transparent outline-hidden text-right"
|
||||
bind:value={TTS_ENGINE}
|
||||
placeholder={$i18n.t('Select a mode')}
|
||||
on:change={async (e) => {
|
||||
@@ -606,14 +616,14 @@
|
||||
<SensitiveInput placeholder={$i18n.t('API Key')} bind:value={TTS_API_KEY} required />
|
||||
</div>
|
||||
|
||||
<hr class="border-gray-100/20 dark:border-white/[0.025] my-2" />
|
||||
|
||||
<div>
|
||||
<div class=" mb-1.5 text-xs font-normal">{$i18n.t('Azure Region')}</div>
|
||||
<div class=" mb-1.5 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Azure Region')}
|
||||
</div>
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
bind:value={TTS_AZURE_SPEECH_REGION}
|
||||
placeholder={$i18n.t('e.g., westus (leave blank for eastus)')}
|
||||
/>
|
||||
@@ -622,11 +632,13 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class=" mb-1.5 text-xs font-normal">{$i18n.t('Endpoint URL')}</div>
|
||||
<div class=" mb-1.5 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Endpoint URL')}
|
||||
</div>
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
bind:value={TTS_AZURE_SPEECH_BASE_URL}
|
||||
placeholder={$i18n.t('(leave blank for to use commercial endpoint)')}
|
||||
/>
|
||||
@@ -652,11 +664,13 @@
|
||||
<div class="mb-2">
|
||||
{#if TTS_ENGINE === ''}
|
||||
<div>
|
||||
<div class=" mb-1.5 text-xs font-normal">{$i18n.t('TTS Voice')}</div>
|
||||
<div class=" mb-1.5 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('TTS Voice')}
|
||||
</div>
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<select
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
bind:value={TTS_VOICE}
|
||||
>
|
||||
<option value="" selected={TTS_VOICE !== ''}>{$i18n.t('Default')}</option>
|
||||
@@ -673,12 +687,14 @@
|
||||
</div>
|
||||
{:else if TTS_ENGINE === 'transformers'}
|
||||
<div>
|
||||
<div class=" mb-1.5 text-xs font-normal">{$i18n.t('TTS Model')}</div>
|
||||
<div class=" mb-1.5 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('TTS Model')}
|
||||
</div>
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
list="model-list"
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
bind:value={TTS_MODEL}
|
||||
placeholder={$i18n.t('CMU ARCTIC speaker embedding name')}
|
||||
/>
|
||||
@@ -715,25 +731,29 @@
|
||||
{:else if TTS_ENGINE === 'openai'}
|
||||
<div class=" flex gap-2">
|
||||
<div class="w-full">
|
||||
<div class=" mb-1.5 text-xs font-normal">{$i18n.t('TTS Voice')}</div>
|
||||
<div class=" mb-1.5 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('TTS Voice')}
|
||||
</div>
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<TTSVoiceInput
|
||||
bind:value={TTS_VOICE}
|
||||
voices={providerVoices}
|
||||
placeholder={$i18n.t('Select a voice')}
|
||||
className="w-full rounded-lg py-2 px-4 bg-gray-50 dark:text-gray-300 dark:bg-gray-850"
|
||||
className="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<div class=" mb-1.5 text-xs font-normal">{$i18n.t('TTS Model')}</div>
|
||||
<div class=" mb-1.5 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('TTS Model')}
|
||||
</div>
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
list="tts-model-list"
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
bind:value={TTS_MODEL}
|
||||
placeholder={$i18n.t('Select a model')}
|
||||
/>
|
||||
@@ -750,11 +770,13 @@
|
||||
|
||||
<div class="mt-2 mb-1 text-xs text-gray-400 dark:text-gray-500">
|
||||
<div class="w-full">
|
||||
<div class=" mb-1.5 text-xs font-normal">{$i18n.t('Additional Parameters')}</div>
|
||||
<div class=" mb-1.5 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Additional Parameters')}
|
||||
</div>
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<Textarea
|
||||
className="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
className="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
bind:value={TTS_OPENAI_PARAMS}
|
||||
placeholder={$i18n.t('Enter additional parameters in JSON format')}
|
||||
minSize={100}
|
||||
@@ -766,25 +788,29 @@
|
||||
{:else if TTS_ENGINE === 'elevenlabs'}
|
||||
<div class=" flex gap-2">
|
||||
<div class="w-full">
|
||||
<div class=" mb-1.5 text-xs font-normal">{$i18n.t('TTS Voice')}</div>
|
||||
<div class=" mb-1.5 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('TTS Voice')}
|
||||
</div>
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<TTSVoiceInput
|
||||
bind:value={TTS_VOICE}
|
||||
voices={providerVoices}
|
||||
placeholder={$i18n.t('Select a voice')}
|
||||
className="w-full rounded-lg py-2 px-4 bg-gray-50 dark:text-gray-300 dark:bg-gray-850"
|
||||
className="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<div class=" mb-1.5 text-xs font-normal">{$i18n.t('TTS Model')}</div>
|
||||
<div class=" mb-1.5 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('TTS Model')}
|
||||
</div>
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
list="tts-model-list"
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
bind:value={TTS_MODEL}
|
||||
placeholder={$i18n.t('Select a model')}
|
||||
/>
|
||||
@@ -801,20 +827,22 @@
|
||||
{:else if TTS_ENGINE === 'azure'}
|
||||
<div class=" flex gap-2">
|
||||
<div class="w-full">
|
||||
<div class=" mb-1.5 text-xs font-normal">{$i18n.t('TTS Voice')}</div>
|
||||
<div class=" mb-1.5 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('TTS Voice')}
|
||||
</div>
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<TTSVoiceInput
|
||||
bind:value={TTS_VOICE}
|
||||
voices={providerVoices}
|
||||
placeholder={$i18n.t('Select a voice')}
|
||||
className="w-full rounded-lg py-2 px-4 bg-gray-50 dark:text-gray-300 dark:bg-gray-850"
|
||||
className="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<div class=" mb-1.5 text-xs font-normal">
|
||||
<div class=" mb-1.5 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Output format')}
|
||||
<a
|
||||
href="https://learn.microsoft.com/en-us/azure/ai-services/speech-service/rest-text-to-speech?tabs=streaming#audio-outputs"
|
||||
@@ -827,7 +855,7 @@
|
||||
<div class="flex-1">
|
||||
<input
|
||||
list="tts-model-list"
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
bind:value={TTS_AZURE_SPEECH_OUTPUT_FORMAT}
|
||||
placeholder={$i18n.t('Select an output format')}
|
||||
/>
|
||||
@@ -838,25 +866,29 @@
|
||||
{:else if TTS_ENGINE === 'mistral'}
|
||||
<div class=" flex gap-2">
|
||||
<div class="w-full">
|
||||
<div class=" mb-1.5 text-xs font-normal">{$i18n.t('TTS Voice')}</div>
|
||||
<div class=" mb-1.5 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('TTS Voice')}
|
||||
</div>
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<TTSVoiceInput
|
||||
bind:value={TTS_VOICE}
|
||||
voices={providerVoices}
|
||||
placeholder={$i18n.t('Select a voice')}
|
||||
className="w-full rounded-lg py-2 px-4 bg-gray-50 dark:text-gray-300 dark:bg-gray-850"
|
||||
className="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<div class=" mb-1.5 text-xs font-normal">{$i18n.t('TTS Model')}</div>
|
||||
<div class=" mb-1.5 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('TTS Model')}
|
||||
</div>
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
list="tts-model-list"
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
bind:value={TTS_MODEL}
|
||||
placeholder={$i18n.t('Select a model')}
|
||||
/>
|
||||
@@ -874,10 +906,12 @@
|
||||
</div>
|
||||
|
||||
<div class="pt-0.5 flex w-full justify-between">
|
||||
<div class="self-center text-xs font-normal">{$i18n.t('Response splitting')}</div>
|
||||
<div class="self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Response splitting')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
<select
|
||||
class="w-fit pr-8 cursor-pointer rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
|
||||
class="w-fit pr-8 cursor-pointer rounded-lg px-2 p-1 text-xs bg-transparent outline-hidden text-right"
|
||||
aria-label={$i18n.t('Select how to split message text for TTS requests')}
|
||||
bind:value={TTS_SPLIT_ON}
|
||||
>
|
||||
@@ -897,7 +931,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end text-sm font-normal">
|
||||
<div class="flex justify-end pt-6 text-sm font-normal">
|
||||
<button
|
||||
class="px-3.5 py-1.5 text-sm font-normal bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
|
||||
type="submit"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,4 @@
|
||||
<script lang="ts">
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { onMount, getContext } from 'svelte';
|
||||
import { getCodeExecutionConfig, setCodeExecutionConfig } from '$lib/apis/configs';
|
||||
|
||||
@@ -8,14 +7,25 @@
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
import Textarea from '$lib/components/common/Textarea.svelte';
|
||||
import Switch from '$lib/components/common/Switch.svelte';
|
||||
import AdminSettingField from './AdminSettingField.svelte';
|
||||
import AdminSettingRow from './AdminSettingRow.svelte';
|
||||
import AdminSettingSection from './AdminSettingSection.svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
const i18n: any = getContext('i18n');
|
||||
|
||||
export let saveHandler: Function;
|
||||
|
||||
let config = null;
|
||||
let config: any = null;
|
||||
|
||||
let engines = ['pyodide', 'jupyter'];
|
||||
const inputClass =
|
||||
'w-full h-7 rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 text-xs text-gray-700 outline-hidden transition-colors placeholder:text-gray-300 focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:placeholder:text-gray-700 dark:focus:border-blue-500';
|
||||
const textareaClass =
|
||||
'w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors placeholder:text-gray-300 focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:placeholder:text-gray-700 dark:focus:border-blue-500';
|
||||
const inlineInputClass =
|
||||
'w-fit h-7 rounded-lg border border-transparent bg-transparent px-2 text-xs text-gray-600 outline-hidden transition-colors hover:bg-black/5 focus:border-blue-400 dark:text-gray-400 dark:hover:bg-white/5 dark:focus:border-blue-500';
|
||||
const selectClass =
|
||||
'cursor-pointer bg-transparent text-xs text-gray-600 outline-hidden dark:text-gray-400';
|
||||
|
||||
const submitHandler = async () => {
|
||||
const res = await setCodeExecutionConfig(localStorage.token, config);
|
||||
@@ -31,304 +41,217 @@
|
||||
</script>
|
||||
|
||||
<form
|
||||
class="flex flex-col h-full justify-between space-y-3 text-sm"
|
||||
class="flex h-full flex-col justify-between text-sm"
|
||||
on:submit|preventDefault={async () => {
|
||||
await submitHandler();
|
||||
saveHandler();
|
||||
}}
|
||||
>
|
||||
<div class=" space-y-3 flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5 -mr-1.5">
|
||||
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5">
|
||||
{#if config}
|
||||
<div>
|
||||
<div class="mb-3.5">
|
||||
<div class=" mt-0.5 mb-2.5 text-xs text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('Code Execution')}
|
||||
</div>
|
||||
<AdminSettingSection first>
|
||||
<AdminSettingRow label={$i18n.t('Enable Code Execution')}>
|
||||
<Switch bind:state={config.ENABLE_CODE_EXECUTION} />
|
||||
</AdminSettingRow>
|
||||
|
||||
<hr class=" border-gray-100/20 dark:border-white/[0.025] my-2" />
|
||||
{#if config.ENABLE_CODE_EXECUTION}
|
||||
<AdminSettingRow
|
||||
label={$i18n.t('Code Execution Engine')}
|
||||
description={config.CODE_EXECUTION_ENGINE === 'jupyter'
|
||||
? $i18n.t(
|
||||
'Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.'
|
||||
)
|
||||
: ''}
|
||||
>
|
||||
<select
|
||||
class={selectClass}
|
||||
bind:value={config.CODE_EXECUTION_ENGINE}
|
||||
placeholder={$i18n.t('Select a engine')}
|
||||
required
|
||||
>
|
||||
<option disabled selected value="">{$i18n.t('Select a engine')}</option>
|
||||
{#each engines as engine}
|
||||
<option value={engine}>{engine}{engine === 'jupyter' ? ' (Legacy)' : ''}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</AdminSettingRow>
|
||||
|
||||
<div class="mb-2.5">
|
||||
<div class=" flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
{$i18n.t('Enable Code Execution')}
|
||||
</div>
|
||||
{#if config.CODE_EXECUTION_ENGINE === 'jupyter'}
|
||||
<AdminSettingField label={$i18n.t('Jupyter URL')}>
|
||||
<input
|
||||
class={inputClass}
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter Jupyter URL')}
|
||||
bind:value={config.CODE_EXECUTION_JUPYTER_URL}
|
||||
autocomplete="off"
|
||||
/>
|
||||
</AdminSettingField>
|
||||
|
||||
<Switch bind:state={config.ENABLE_CODE_EXECUTION} />
|
||||
</div>
|
||||
</div>
|
||||
<AdminSettingRow label={$i18n.t('Jupyter Auth')}>
|
||||
<select
|
||||
class={selectClass}
|
||||
bind:value={config.CODE_EXECUTION_JUPYTER_AUTH}
|
||||
placeholder={$i18n.t('Select an auth method')}
|
||||
>
|
||||
<option selected value="">{$i18n.t('None')}</option>
|
||||
<option value="token">{$i18n.t('Token')}</option>
|
||||
<option value="password">{$i18n.t('Password')}</option>
|
||||
</select>
|
||||
</AdminSettingRow>
|
||||
|
||||
{#if config.ENABLE_CODE_EXECUTION}
|
||||
<div class="mb-2.5">
|
||||
<div class="flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
{$i18n.t('Code Execution Engine')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
<select
|
||||
class="w-fit pr-8 rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
|
||||
bind:value={config.CODE_EXECUTION_ENGINE}
|
||||
placeholder={$i18n.t('Select a engine')}
|
||||
required
|
||||
>
|
||||
<option disabled selected value="">{$i18n.t('Select a engine')}</option>
|
||||
{#each engines as engine}
|
||||
<option value={engine}
|
||||
>{engine}{engine === 'jupyter' ? ' (Legacy)' : ''}</option
|
||||
>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if config.CODE_EXECUTION_ENGINE === 'jupyter'}
|
||||
<div class="text-gray-500 text-xs">
|
||||
{$i18n.t(
|
||||
'Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.'
|
||||
)}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if config.CODE_EXECUTION_ENGINE === 'jupyter'}
|
||||
<div class="mb-2.5 flex flex-col gap-1.5 w-full">
|
||||
<div class="text-xs font-normal">
|
||||
{$i18n.t('Jupyter URL')}
|
||||
</div>
|
||||
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full text-sm py-0.5 placeholder:text-gray-300 dark:placeholder:text-gray-700 bg-transparent outline-hidden"
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter Jupyter URL')}
|
||||
bind:value={config.CODE_EXECUTION_JUPYTER_URL}
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-2.5 flex flex-col gap-1.5 w-full">
|
||||
<div class=" flex gap-2 w-full items-center justify-between">
|
||||
<div class="text-xs font-normal">
|
||||
{$i18n.t('Jupyter Auth')}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<select
|
||||
class="w-fit pr-8 rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-left"
|
||||
bind:value={config.CODE_EXECUTION_JUPYTER_AUTH}
|
||||
placeholder={$i18n.t('Select an auth method')}
|
||||
>
|
||||
<option selected value="">{$i18n.t('None')}</option>
|
||||
<option value="token">{$i18n.t('Token')}</option>
|
||||
<option value="password">{$i18n.t('Password')}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if config.CODE_EXECUTION_JUPYTER_AUTH}
|
||||
<div class="flex w-full gap-2">
|
||||
<div class="flex-1">
|
||||
{#if config.CODE_EXECUTION_JUPYTER_AUTH === 'password'}
|
||||
<SensitiveInput
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter Jupyter Password')}
|
||||
bind:value={config.CODE_EXECUTION_JUPYTER_AUTH_PASSWORD}
|
||||
autocomplete="off"
|
||||
/>
|
||||
{:else}
|
||||
<SensitiveInput
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter Jupyter Token')}
|
||||
bind:value={config.CODE_EXECUTION_JUPYTER_AUTH_TOKEN}
|
||||
autocomplete="off"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 w-full items-center justify-between">
|
||||
<div class="text-xs font-normal">
|
||||
{$i18n.t('Code Execution Timeout')}
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<Tooltip content={$i18n.t('Enter timeout in seconds')}>
|
||||
<input
|
||||
class="w-fit rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
|
||||
type="number"
|
||||
bind:value={config.CODE_EXECUTION_JUPYTER_TIMEOUT}
|
||||
placeholder={$i18n.t('e.g. 60')}
|
||||
autocomplete="off"
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="mb-3.5">
|
||||
<div class=" mt-0.5 mb-2.5 text-xs text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('Code Interpreter')}
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-100/20 dark:border-white/[0.025] my-2" />
|
||||
|
||||
<div class="mb-2.5">
|
||||
<div class=" flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
{$i18n.t('Enable Code Interpreter')}
|
||||
</div>
|
||||
|
||||
<Switch bind:state={config.ENABLE_CODE_INTERPRETER} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if config.ENABLE_CODE_INTERPRETER}
|
||||
<div class="mb-2.5">
|
||||
<div class=" flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
{$i18n.t('Code Interpreter Engine')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
<select
|
||||
class="w-fit pr-8 rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
|
||||
bind:value={config.CODE_INTERPRETER_ENGINE}
|
||||
placeholder={$i18n.t('Select a engine')}
|
||||
required
|
||||
>
|
||||
<option disabled selected value="">{$i18n.t('Select a engine')}</option>
|
||||
{#each engines as engine}
|
||||
<option value={engine}
|
||||
>{engine}{engine === 'jupyter' ? ' (Legacy)' : ''}</option
|
||||
>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if config.CODE_INTERPRETER_ENGINE === 'jupyter'}
|
||||
<div class="text-gray-500 text-xs">
|
||||
{$i18n.t(
|
||||
'Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.'
|
||||
)}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if config.CODE_INTERPRETER_ENGINE === 'jupyter'}
|
||||
<div class="mb-2.5 flex flex-col gap-1.5 w-full">
|
||||
<div class="text-xs font-normal">
|
||||
{$i18n.t('Jupyter URL')}
|
||||
</div>
|
||||
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full text-sm py-0.5 placeholder:text-gray-300 dark:placeholder:text-gray-700 bg-transparent outline-hidden"
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter Jupyter URL')}
|
||||
bind:value={config.CODE_INTERPRETER_JUPYTER_URL}
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-2.5 flex flex-col gap-1.5 w-full">
|
||||
<div class="flex gap-2 w-full items-center justify-between">
|
||||
<div class="text-xs font-normal">
|
||||
{$i18n.t('Jupyter Auth')}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<select
|
||||
class="w-fit pr-8 rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-left"
|
||||
bind:value={config.CODE_INTERPRETER_JUPYTER_AUTH}
|
||||
placeholder={$i18n.t('Select an auth method')}
|
||||
>
|
||||
<option selected value="">{$i18n.t('None')}</option>
|
||||
<option value="token">{$i18n.t('Token')}</option>
|
||||
<option value="password">{$i18n.t('Password')}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if config.CODE_INTERPRETER_JUPYTER_AUTH}
|
||||
<div class="flex w-full gap-2">
|
||||
<div class="flex-1">
|
||||
{#if config.CODE_INTERPRETER_JUPYTER_AUTH === 'password'}
|
||||
<SensitiveInput
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter Jupyter Password')}
|
||||
bind:value={config.CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD}
|
||||
autocomplete="off"
|
||||
/>
|
||||
{:else}
|
||||
<SensitiveInput
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter Jupyter Token')}
|
||||
bind:value={config.CODE_INTERPRETER_JUPYTER_AUTH_TOKEN}
|
||||
autocomplete="off"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 w-full items-center justify-between">
|
||||
<div class="text-xs font-normal">
|
||||
{$i18n.t('Code Execution Timeout')}
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<Tooltip content={$i18n.t('Enter timeout in seconds')}>
|
||||
<input
|
||||
class="w-fit rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
|
||||
type="number"
|
||||
bind:value={config.CODE_INTERPRETER_JUPYTER_TIMEOUT}
|
||||
placeholder={$i18n.t('e.g. 60')}
|
||||
autocomplete="off"
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<hr class="border-gray-100/20 dark:border-white/[0.025] my-2" />
|
||||
|
||||
<div>
|
||||
<div class="py-0.5 w-full">
|
||||
<div class=" mb-2.5 text-xs font-normal">
|
||||
{$i18n.t('Code Interpreter Prompt Template')}
|
||||
</div>
|
||||
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
'Leave empty to use the default prompt, or enter a custom prompt'
|
||||
)}
|
||||
placement="top-start"
|
||||
>
|
||||
<Textarea
|
||||
bind:value={config.CODE_INTERPRETER_PROMPT_TEMPLATE}
|
||||
placeholder={$i18n.t(
|
||||
'Leave empty to use the default prompt, or enter a custom prompt'
|
||||
)}
|
||||
{#if config.CODE_EXECUTION_JUPYTER_AUTH}
|
||||
<AdminSettingField
|
||||
label={config.CODE_EXECUTION_JUPYTER_AUTH === 'password'
|
||||
? $i18n.t('Jupyter Password')
|
||||
: $i18n.t('Jupyter Token')}
|
||||
>
|
||||
{#if config.CODE_EXECUTION_JUPYTER_AUTH === 'password'}
|
||||
<SensitiveInput
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter Jupyter Password')}
|
||||
bind:value={config.CODE_EXECUTION_JUPYTER_AUTH_PASSWORD}
|
||||
autocomplete="off"
|
||||
outerClassName="flex flex-1"
|
||||
inputClassName={inputClass}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<SensitiveInput
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter Jupyter Token')}
|
||||
bind:value={config.CODE_EXECUTION_JUPYTER_AUTH_TOKEN}
|
||||
autocomplete="off"
|
||||
outerClassName="flex flex-1"
|
||||
inputClassName={inputClass}
|
||||
/>
|
||||
{/if}
|
||||
</AdminSettingField>
|
||||
{/if}
|
||||
|
||||
<AdminSettingRow label={$i18n.t('Code Execution Timeout')}>
|
||||
<Tooltip content={$i18n.t('Enter timeout in seconds')}>
|
||||
<input
|
||||
class={inlineInputClass}
|
||||
type="number"
|
||||
bind:value={config.CODE_EXECUTION_JUPYTER_TIMEOUT}
|
||||
placeholder={$i18n.t('e.g. 60')}
|
||||
autocomplete="off"
|
||||
/>
|
||||
</Tooltip>
|
||||
</AdminSettingRow>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</AdminSettingSection>
|
||||
|
||||
<AdminSettingSection title={$i18n.t('Code Interpreter')}>
|
||||
<AdminSettingRow label={$i18n.t('Enable Code Interpreter')}>
|
||||
<Switch bind:state={config.ENABLE_CODE_INTERPRETER} />
|
||||
</AdminSettingRow>
|
||||
|
||||
{#if config.ENABLE_CODE_INTERPRETER}
|
||||
<AdminSettingRow
|
||||
label={$i18n.t('Code Interpreter Engine')}
|
||||
description={config.CODE_INTERPRETER_ENGINE === 'jupyter'
|
||||
? $i18n.t(
|
||||
'Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.'
|
||||
)
|
||||
: ''}
|
||||
>
|
||||
<select
|
||||
class={selectClass}
|
||||
bind:value={config.CODE_INTERPRETER_ENGINE}
|
||||
placeholder={$i18n.t('Select a engine')}
|
||||
required
|
||||
>
|
||||
<option disabled selected value="">{$i18n.t('Select a engine')}</option>
|
||||
{#each engines as engine}
|
||||
<option value={engine}>{engine}{engine === 'jupyter' ? ' (Legacy)' : ''}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</AdminSettingRow>
|
||||
|
||||
{#if config.CODE_INTERPRETER_ENGINE === 'jupyter'}
|
||||
<AdminSettingField label={$i18n.t('Jupyter URL')}>
|
||||
<input
|
||||
class={inputClass}
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter Jupyter URL')}
|
||||
bind:value={config.CODE_INTERPRETER_JUPYTER_URL}
|
||||
autocomplete="off"
|
||||
/>
|
||||
</AdminSettingField>
|
||||
|
||||
<AdminSettingRow label={$i18n.t('Jupyter Auth')}>
|
||||
<select
|
||||
class={selectClass}
|
||||
bind:value={config.CODE_INTERPRETER_JUPYTER_AUTH}
|
||||
placeholder={$i18n.t('Select an auth method')}
|
||||
>
|
||||
<option selected value="">{$i18n.t('None')}</option>
|
||||
<option value="token">{$i18n.t('Token')}</option>
|
||||
<option value="password">{$i18n.t('Password')}</option>
|
||||
</select>
|
||||
</AdminSettingRow>
|
||||
|
||||
{#if config.CODE_INTERPRETER_JUPYTER_AUTH}
|
||||
<AdminSettingField
|
||||
label={config.CODE_INTERPRETER_JUPYTER_AUTH === 'password'
|
||||
? $i18n.t('Jupyter Password')
|
||||
: $i18n.t('Jupyter Token')}
|
||||
>
|
||||
{#if config.CODE_INTERPRETER_JUPYTER_AUTH === 'password'}
|
||||
<SensitiveInput
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter Jupyter Password')}
|
||||
bind:value={config.CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD}
|
||||
autocomplete="off"
|
||||
outerClassName="flex flex-1"
|
||||
inputClassName={inputClass}
|
||||
/>
|
||||
{:else}
|
||||
<SensitiveInput
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter Jupyter Token')}
|
||||
bind:value={config.CODE_INTERPRETER_JUPYTER_AUTH_TOKEN}
|
||||
autocomplete="off"
|
||||
outerClassName="flex flex-1"
|
||||
inputClassName={inputClass}
|
||||
/>
|
||||
{/if}
|
||||
</AdminSettingField>
|
||||
{/if}
|
||||
|
||||
<AdminSettingRow label={$i18n.t('Code Execution Timeout')}>
|
||||
<Tooltip content={$i18n.t('Enter timeout in seconds')}>
|
||||
<input
|
||||
class={inlineInputClass}
|
||||
type="number"
|
||||
bind:value={config.CODE_INTERPRETER_JUPYTER_TIMEOUT}
|
||||
placeholder={$i18n.t('e.g. 60')}
|
||||
autocomplete="off"
|
||||
/>
|
||||
</Tooltip>
|
||||
</AdminSettingRow>
|
||||
{/if}
|
||||
|
||||
<AdminSettingField label={$i18n.t('Code Interpreter Prompt Template')}>
|
||||
<Tooltip
|
||||
content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
|
||||
placement="top-start"
|
||||
>
|
||||
<Textarea
|
||||
className={textareaClass}
|
||||
bind:value={config.CODE_INTERPRETER_PROMPT_TEMPLATE}
|
||||
placeholder={$i18n.t(
|
||||
'Leave empty to use the default prompt, or enter a custom prompt'
|
||||
)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</AdminSettingField>
|
||||
{/if}
|
||||
</AdminSettingSection>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex justify-end pt-3 text-sm font-normal">
|
||||
<div class="flex justify-end pt-6 text-sm font-normal">
|
||||
<button
|
||||
class="px-3.5 py-1.5 text-sm font-normal bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
|
||||
type="submit"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { createEventDispatcher, onMount, getContext, tick } from 'svelte';
|
||||
import { createEventDispatcher, onMount, getContext } from 'svelte';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
@@ -19,13 +19,15 @@
|
||||
import OpenAIConnection from './Connections/OpenAIConnection.svelte';
|
||||
import AddConnectionModal from '$lib/components/AddConnectionModal.svelte';
|
||||
import OllamaConnection from './Connections/OllamaConnection.svelte';
|
||||
import AdminSettingRow from './AdminSettingRow.svelte';
|
||||
import AdminSettingSection from './AdminSettingSection.svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
const i18n: any = getContext('i18n');
|
||||
|
||||
const getModels = async () => {
|
||||
const models = await _getModels(
|
||||
localStorage.token,
|
||||
$config?.features?.enable_direct_connections && ($settings?.directConnections ?? null),
|
||||
$config?.features?.enable_direct_connections ? ($settings?.directConnections ?? null) : null,
|
||||
false,
|
||||
true
|
||||
);
|
||||
@@ -33,19 +35,19 @@
|
||||
};
|
||||
|
||||
// External
|
||||
let OLLAMA_BASE_URLS = [''];
|
||||
let OLLAMA_API_CONFIGS = {};
|
||||
let OLLAMA_BASE_URLS: string[] = [''];
|
||||
let OLLAMA_API_CONFIGS: any = {};
|
||||
|
||||
let OPENAI_API_KEYS = [''];
|
||||
let OPENAI_API_BASE_URLS = [''];
|
||||
let OPENAI_API_CONFIGS = {};
|
||||
let OPENAI_API_KEYS: string[] = [''];
|
||||
let OPENAI_API_BASE_URLS: string[] = [''];
|
||||
let OPENAI_API_CONFIGS: any = {};
|
||||
|
||||
let ENABLE_OPENAI_API: null | boolean = null;
|
||||
let ENABLE_OLLAMA_API: null | boolean = null;
|
||||
|
||||
let connectionsConfig = null;
|
||||
let connectionsConfig: any = null;
|
||||
|
||||
let pipelineUrls = {};
|
||||
let pipelineUrls: Record<string, boolean> = {};
|
||||
let showAddOpenAIConnectionModal = false;
|
||||
let showAddOllamaConnectionModal = false;
|
||||
|
||||
@@ -118,7 +120,7 @@
|
||||
}
|
||||
};
|
||||
|
||||
const addOpenAIConnectionHandler = async (connection) => {
|
||||
const addOpenAIConnectionHandler = async (connection: any) => {
|
||||
OPENAI_API_BASE_URLS = [...OPENAI_API_BASE_URLS, connection.url];
|
||||
OPENAI_API_KEYS = [...OPENAI_API_KEYS, connection.key];
|
||||
OPENAI_API_CONFIGS[OPENAI_API_BASE_URLS.length - 1] = connection.config;
|
||||
@@ -126,7 +128,7 @@
|
||||
await updateOpenAIHandler();
|
||||
};
|
||||
|
||||
const addOllamaConnectionHandler = async (connection) => {
|
||||
const addOllamaConnectionHandler = async (connection: any) => {
|
||||
OLLAMA_BASE_URLS = [...OLLAMA_BASE_URLS, connection.url];
|
||||
OLLAMA_API_CONFIGS[OLLAMA_BASE_URLS.length - 1] = {
|
||||
...connection.config,
|
||||
@@ -138,8 +140,8 @@
|
||||
|
||||
onMount(async () => {
|
||||
if ($user?.role === 'admin') {
|
||||
let ollamaConfig = {};
|
||||
let openaiConfig = {};
|
||||
let ollamaConfig: any = {};
|
||||
let openaiConfig: any = {};
|
||||
|
||||
await Promise.all([
|
||||
(async () => {
|
||||
@@ -215,202 +217,162 @@
|
||||
onSubmit={addOllamaConnectionHandler}
|
||||
/>
|
||||
|
||||
<form class="flex flex-col h-full justify-between text-sm" on:submit|preventDefault={submitHandler}>
|
||||
<div class=" flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5 -mr-1.5">
|
||||
<form class="flex h-full flex-col justify-between text-sm" on:submit|preventDefault={submitHandler}>
|
||||
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5">
|
||||
{#if ENABLE_OPENAI_API !== null && ENABLE_OLLAMA_API !== null && connectionsConfig !== null}
|
||||
<div class="mb-3.5">
|
||||
<div class=" mt-0.5 mb-2.5 text-xs text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('General')}
|
||||
</div>
|
||||
<AdminSettingSection first>
|
||||
<AdminSettingRow label={$i18n.t('OpenAI API')}>
|
||||
<Switch
|
||||
bind:state={ENABLE_OPENAI_API}
|
||||
on:change={async () => {
|
||||
updateOpenAIHandler();
|
||||
}}
|
||||
/>
|
||||
</AdminSettingRow>
|
||||
|
||||
<hr class=" border-gray-100/20 dark:border-white/[0.025] my-2" />
|
||||
|
||||
<div class="my-2">
|
||||
<div class="mt-2 space-y-2">
|
||||
<div class="flex justify-between items-center text-sm">
|
||||
<div class=" font-normal">{$i18n.t('OpenAI API')}</div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<div class="">
|
||||
<Switch
|
||||
bind:state={ENABLE_OPENAI_API}
|
||||
on:change={async () => {
|
||||
updateOpenAIHandler();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if ENABLE_OPENAI_API}
|
||||
<div class="">
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="font-normal text-xs">{$i18n.t('Manage OpenAI API Connections')}</div>
|
||||
|
||||
<Tooltip content={$i18n.t(`Add Connection`)}>
|
||||
<button
|
||||
class="px-1"
|
||||
on:click={() => {
|
||||
showAddOpenAIConnectionModal = true;
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<Plus />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1.5 mt-1.5">
|
||||
{#each OPENAI_API_BASE_URLS as url, idx}
|
||||
<OpenAIConnection
|
||||
bind:url={OPENAI_API_BASE_URLS[idx]}
|
||||
bind:key={OPENAI_API_KEYS[idx]}
|
||||
bind:config={OPENAI_API_CONFIGS[idx]}
|
||||
pipeline={pipelineUrls[url] ? true : false}
|
||||
onSubmit={() => {
|
||||
updateOpenAIHandler();
|
||||
}}
|
||||
onDelete={() => {
|
||||
OPENAI_API_BASE_URLS = OPENAI_API_BASE_URLS.filter(
|
||||
(url, urlIdx) => idx !== urlIdx
|
||||
);
|
||||
OPENAI_API_KEYS = OPENAI_API_KEYS.filter((key, keyIdx) => idx !== keyIdx);
|
||||
|
||||
let newConfig = {};
|
||||
OPENAI_API_BASE_URLS.forEach((url, newIdx) => {
|
||||
newConfig[newIdx] =
|
||||
OPENAI_API_CONFIGS[newIdx < idx ? newIdx : newIdx + 1];
|
||||
});
|
||||
OPENAI_API_CONFIGS = newConfig;
|
||||
updateOpenAIHandler();
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class=" my-2">
|
||||
<div class="flex justify-between items-center text-sm mb-2">
|
||||
<div class=" font-normal">{$i18n.t('Ollama API')}</div>
|
||||
|
||||
<div class="mt-1">
|
||||
<Switch
|
||||
bind:state={ENABLE_OLLAMA_API}
|
||||
on:change={async () => {
|
||||
updateOllamaHandler();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if ENABLE_OLLAMA_API}
|
||||
<div class="">
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="font-normal text-xs">{$i18n.t('Manage Ollama API Connections')}</div>
|
||||
|
||||
<Tooltip content={$i18n.t(`Add Connection`)}>
|
||||
<button
|
||||
class="px-1"
|
||||
on:click={() => {
|
||||
showAddOllamaConnectionModal = true;
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<Plus />
|
||||
</button>
|
||||
</Tooltip>
|
||||
{#if ENABLE_OPENAI_API}
|
||||
<div>
|
||||
<div class="mb-2 flex items-center justify-between gap-4">
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Manage OpenAI API Connections')}
|
||||
</div>
|
||||
|
||||
<div class="flex w-full gap-1.5">
|
||||
<div class="flex-1 flex flex-col gap-1.5 mt-1.5">
|
||||
{#each OLLAMA_BASE_URLS as url, idx}
|
||||
<OllamaConnection
|
||||
bind:url={OLLAMA_BASE_URLS[idx]}
|
||||
bind:config={OLLAMA_API_CONFIGS[idx]}
|
||||
{idx}
|
||||
onSubmit={() => {
|
||||
updateOllamaHandler();
|
||||
}}
|
||||
onDelete={() => {
|
||||
OLLAMA_BASE_URLS = OLLAMA_BASE_URLS.filter((url, urlIdx) => idx !== urlIdx);
|
||||
|
||||
let newConfig = {};
|
||||
OLLAMA_BASE_URLS.forEach((url, newIdx) => {
|
||||
newConfig[newIdx] =
|
||||
OLLAMA_API_CONFIGS[newIdx < idx ? newIdx : newIdx + 1];
|
||||
});
|
||||
OLLAMA_API_CONFIGS = newConfig;
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-1 text-xs text-gray-400 dark:text-gray-500">
|
||||
{$i18n.t('Trouble accessing Ollama?')}
|
||||
<a
|
||||
class=" text-gray-300 font-normal underline"
|
||||
href="https://github.com/open-webui/open-webui#troubleshooting"
|
||||
target="_blank"
|
||||
<Tooltip content={$i18n.t(`Add Connection`)}>
|
||||
<button
|
||||
class="flex size-6 items-center justify-center rounded-lg text-gray-400 transition-colors hover:bg-black/5 hover:text-gray-700 dark:text-gray-600 dark:hover:bg-white/5 dark:hover:text-gray-300"
|
||||
on:click={() => {
|
||||
showAddOpenAIConnectionModal = true;
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
{$i18n.t('Click here for help.')}
|
||||
</a>
|
||||
</div>
|
||||
<Plus />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="my-2">
|
||||
<div class="flex justify-between items-center text-sm">
|
||||
<div class=" font-normal">{$i18n.t('Direct Connections')}</div>
|
||||
<div class="flex flex-col gap-1.5">
|
||||
{#each OPENAI_API_BASE_URLS as url, idx}
|
||||
<OpenAIConnection
|
||||
bind:url={OPENAI_API_BASE_URLS[idx]}
|
||||
bind:key={OPENAI_API_KEYS[idx]}
|
||||
bind:config={OPENAI_API_CONFIGS[idx]}
|
||||
pipeline={pipelineUrls[url] ? true : false}
|
||||
onSubmit={() => {
|
||||
updateOpenAIHandler();
|
||||
}}
|
||||
onDelete={() => {
|
||||
OPENAI_API_BASE_URLS = OPENAI_API_BASE_URLS.filter(
|
||||
(url, urlIdx) => idx !== urlIdx
|
||||
);
|
||||
OPENAI_API_KEYS = OPENAI_API_KEYS.filter((key, keyIdx) => idx !== keyIdx);
|
||||
|
||||
<div class="flex items-center">
|
||||
<div class="">
|
||||
<Switch
|
||||
bind:state={connectionsConfig.ENABLE_DIRECT_CONNECTIONS}
|
||||
on:change={async () => {
|
||||
updateConnectionsHandler();
|
||||
let newConfig: any = {};
|
||||
OPENAI_API_BASE_URLS.forEach((url, newIdx) => {
|
||||
newConfig[newIdx] = OPENAI_API_CONFIGS[newIdx < idx ? newIdx : newIdx + 1];
|
||||
});
|
||||
OPENAI_API_CONFIGS = newConfig;
|
||||
updateOpenAIHandler();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="mt-1 text-xs text-gray-400 dark:text-gray-500">
|
||||
{$i18n.t(
|
||||
'Direct Connections allow users to connect to their own OpenAI compatible API endpoints.'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<AdminSettingRow label={$i18n.t('Ollama API')}>
|
||||
<Switch
|
||||
bind:state={ENABLE_OLLAMA_API}
|
||||
on:change={async () => {
|
||||
updateOllamaHandler();
|
||||
}}
|
||||
/>
|
||||
</AdminSettingRow>
|
||||
|
||||
<hr class=" border-gray-100/20 dark:border-white/[0.025] my-2" />
|
||||
{#if ENABLE_OLLAMA_API}
|
||||
<div>
|
||||
<div class="mb-2 flex items-center justify-between gap-4">
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Manage Ollama API Connections')}
|
||||
</div>
|
||||
|
||||
<div class="my-2">
|
||||
<div class="flex justify-between items-center text-sm">
|
||||
<div class=" text-xs font-normal">{$i18n.t('Cache Base Model List')}</div>
|
||||
<Tooltip content={$i18n.t(`Add Connection`)}>
|
||||
<button
|
||||
class="flex size-6 items-center justify-center rounded-lg text-gray-400 transition-colors hover:bg-black/5 hover:text-gray-700 dark:text-gray-600 dark:hover:bg-white/5 dark:hover:text-gray-300"
|
||||
on:click={() => {
|
||||
showAddOllamaConnectionModal = true;
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<Plus />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<div class="">
|
||||
<Switch
|
||||
bind:state={connectionsConfig.ENABLE_BASE_MODELS_CACHE}
|
||||
on:change={async () => {
|
||||
updateConnectionsHandler();
|
||||
<div class="flex flex-col gap-1.5">
|
||||
{#each OLLAMA_BASE_URLS as url, idx}
|
||||
<OllamaConnection
|
||||
bind:url={OLLAMA_BASE_URLS[idx]}
|
||||
bind:config={OLLAMA_API_CONFIGS[idx]}
|
||||
{idx}
|
||||
onSubmit={() => {
|
||||
updateOllamaHandler();
|
||||
}}
|
||||
onDelete={() => {
|
||||
OLLAMA_BASE_URLS = OLLAMA_BASE_URLS.filter((url, urlIdx) => idx !== urlIdx);
|
||||
|
||||
let newConfig: any = {};
|
||||
OLLAMA_BASE_URLS.forEach((url, newIdx) => {
|
||||
newConfig[newIdx] = OLLAMA_API_CONFIGS[newIdx < idx ? newIdx : newIdx + 1];
|
||||
});
|
||||
OLLAMA_API_CONFIGS = newConfig;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="mt-1 text-[0.6875rem] text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('Trouble accessing Ollama?')}
|
||||
<a
|
||||
class="font-normal underline hover:text-gray-700 dark:hover:text-gray-300"
|
||||
href="https://github.com/open-webui/open-webui#troubleshooting"
|
||||
target="_blank"
|
||||
>
|
||||
{$i18n.t('Click here for help.')}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</AdminSettingSection>
|
||||
|
||||
<div class="mt-1 text-xs text-gray-400 dark:text-gray-500">
|
||||
{$i18n.t(
|
||||
'Base Model List Cache speeds up access by fetching base models only at startup or on settings save—faster, but may not show recent base model changes.'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<AdminSettingSection title={$i18n.t('User Connections')}>
|
||||
<AdminSettingRow
|
||||
label={$i18n.t('Direct Connections')}
|
||||
description={$i18n.t(
|
||||
'Direct Connections allow users to connect to their own OpenAI compatible API endpoints.'
|
||||
)}
|
||||
>
|
||||
<Switch
|
||||
bind:state={connectionsConfig.ENABLE_DIRECT_CONNECTIONS}
|
||||
on:change={async () => {
|
||||
updateConnectionsHandler();
|
||||
}}
|
||||
/>
|
||||
</AdminSettingRow>
|
||||
|
||||
<AdminSettingRow
|
||||
label={$i18n.t('Cache Base Model List')}
|
||||
description={$i18n.t(
|
||||
'Base Model List Cache speeds up access by fetching base models only at startup or on settings save—faster, but may not show recent base model changes.'
|
||||
)}
|
||||
>
|
||||
<Switch
|
||||
bind:state={connectionsConfig.ENABLE_BASE_MODELS_CACHE}
|
||||
on:change={async () => {
|
||||
updateConnectionsHandler();
|
||||
}}
|
||||
/>
|
||||
</AdminSettingRow>
|
||||
</AdminSettingSection>
|
||||
{:else}
|
||||
<div class="flex h-full justify-center">
|
||||
<div class="my-auto">
|
||||
@@ -420,7 +382,7 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end pt-3 text-sm font-normal">
|
||||
<div class="flex justify-end pt-6 text-sm font-normal">
|
||||
<button
|
||||
class="px-3.5 py-1.5 text-sm font-normal bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
|
||||
type="submit"
|
||||
|
||||
@@ -3,17 +3,20 @@
|
||||
const { saveAs } = fileSaver;
|
||||
|
||||
import { downloadDatabase } from '$lib/apis/utils';
|
||||
import { onMount, getContext } from 'svelte';
|
||||
import { config, user } from '$lib/stores';
|
||||
import { getContext } from 'svelte';
|
||||
import { config } from '$lib/stores';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { getAllUserChats } from '$lib/apis/chats';
|
||||
import { getAllUsers } from '$lib/apis/users';
|
||||
import { exportConfig, importConfig } from '$lib/apis/configs';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
const i18n: any = getContext('i18n');
|
||||
|
||||
export let saveHandler: Function;
|
||||
|
||||
const actionButtonClass =
|
||||
'rounded-md px-1.5 py-0.5 text-xs text-gray-500 transition-colors hover:bg-black/5 hover:text-gray-900 dark:text-gray-500 dark:hover:bg-white/5 dark:hover:text-white';
|
||||
|
||||
const exportAllUserChats = async () => {
|
||||
let blob = new Blob([JSON.stringify(await getAllUserChats(localStorage.token))], {
|
||||
type: 'application/json'
|
||||
@@ -43,14 +46,10 @@
|
||||
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
||||
saveAs(blob, 'users.csv');
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
// permissions = await getUserPermissions(localStorage.token);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col h-full justify-between text-sm">
|
||||
<div class="space-y-3 flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5 -mr-1.5">
|
||||
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5">
|
||||
<input
|
||||
id="config-json-input"
|
||||
hidden
|
||||
@@ -77,29 +76,24 @@
|
||||
}}
|
||||
/>
|
||||
|
||||
<div>
|
||||
<div class="mb-1 text-sm font-normal">{$i18n.t('Config')}</div>
|
||||
|
||||
<div>
|
||||
<div class="py-0.5 flex w-full justify-between">
|
||||
<div class="self-center text-xs">{$i18n.t('Import Config')}</div>
|
||||
<div class="flex w-full flex-col gap-2.5">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-24 shrink-0 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Config')}
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-1">
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded-sm transition"
|
||||
class={actionButtonClass}
|
||||
on:click={() => {
|
||||
document.getElementById('config-json-input').click();
|
||||
document.getElementById('config-json-input')?.click();
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<span class="self-center">{$i18n.t('Import')}</span>
|
||||
{$i18n.t('Import')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="py-0.5 flex w-full justify-between">
|
||||
<div class="self-center text-xs">{$i18n.t('Export Config')}</div>
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded-sm transition"
|
||||
class={actionButtonClass}
|
||||
on:click={async () => {
|
||||
const config = await exportConfig(localStorage.token);
|
||||
const blob = new Blob([JSON.stringify(config)], {
|
||||
@@ -109,63 +103,47 @@
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<span class="self-center">{$i18n.t('Export')}</span>
|
||||
{$i18n.t('Export')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if $config?.features.enable_admin_export ?? true}
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-24 shrink-0 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Database')}
|
||||
</div>
|
||||
<button
|
||||
class={actionButtonClass}
|
||||
on:click={() => {
|
||||
downloadDatabase(localStorage.token).catch((error) => {
|
||||
toast.error(`${error}`);
|
||||
});
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
{$i18n.t('Download')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-24 shrink-0 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('All chats')}
|
||||
</div>
|
||||
<button class={actionButtonClass} on:click={exportAllUserChats} type="button">
|
||||
{$i18n.t('Export')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-24 shrink-0 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Users')}
|
||||
</div>
|
||||
<button class={actionButtonClass} on:click={exportUsers} type="button">
|
||||
{$i18n.t('Export')}
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if $config?.features.enable_admin_export ?? true}
|
||||
<div>
|
||||
<div class="mb-1 text-sm font-normal">{$i18n.t('Database')}</div>
|
||||
|
||||
<div>
|
||||
<div class="py-0.5 flex w-full justify-between">
|
||||
<div class="self-center text-xs">{$i18n.t('Download Database')}</div>
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded-sm transition"
|
||||
on:click={() => {
|
||||
downloadDatabase(localStorage.token).catch((error) => {
|
||||
toast.error(`${error}`);
|
||||
});
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<span class="self-center">{$i18n.t('Download')}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="py-0.5 flex w-full justify-between">
|
||||
<div class="self-center text-xs">{$i18n.t('Export All Chats (All Users)')}</div>
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded-sm transition"
|
||||
on:click={() => {
|
||||
exportAllUserChats();
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<span class="self-center">{$i18n.t('Export')}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="py-0.5 flex w-full justify-between">
|
||||
<div class="self-center text-xs">{$i18n.t('Export Users')}</div>
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded-sm transition"
|
||||
on:click={() => {
|
||||
exportUsers();
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<span class="self-center">{$i18n.t('Export')}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -66,6 +66,8 @@
|
||||
};
|
||||
|
||||
let RAGConfig = null;
|
||||
const textareaClass =
|
||||
'w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors placeholder:text-gray-300 focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:placeholder:text-gray-700 dark:focus:border-blue-500';
|
||||
|
||||
const embeddingModelUpdateHandler = async () => {
|
||||
if (RAG_EMBEDDING_ENGINE === '' && RAG_EMBEDDING_MODEL.split('/').length - 1 > 1) {
|
||||
@@ -367,29 +369,23 @@
|
||||
/>
|
||||
|
||||
<form
|
||||
class="flex flex-col h-full justify-between space-y-3 text-sm"
|
||||
class="flex h-full flex-col justify-between text-sm"
|
||||
on:submit|preventDefault={() => {
|
||||
submitHandler();
|
||||
}}
|
||||
>
|
||||
{#if RAGConfig}
|
||||
<div class=" space-y-2.5 flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5 -mr-1.5">
|
||||
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5">
|
||||
<div class="">
|
||||
<div class="mb-3">
|
||||
<div class=" mt-0.5 mb-2.5 text-xs text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('General')}
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-100/20 dark:border-white/[0.025] my-2" />
|
||||
|
||||
<div class="mb-5">
|
||||
<div class="mb-2.5 flex flex-col w-full justify-between">
|
||||
<div class="flex w-full justify-between mb-1">
|
||||
<div class="self-center text-xs font-normal">
|
||||
<div class="self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Content Extraction Engine')}
|
||||
</div>
|
||||
<div class="">
|
||||
<select
|
||||
class="w-fit pr-8 rounded-sm px-2 text-xs bg-transparent outline-hidden text-right"
|
||||
class="w-fit pr-8 rounded-lg px-2 text-xs bg-transparent outline-hidden text-right"
|
||||
bind:value={RAGConfig.CONTENT_EXTRACTION_ENGINE}
|
||||
>
|
||||
<option value="">{$i18n.t('Default')}</option>
|
||||
@@ -408,7 +404,7 @@
|
||||
{#if RAGConfig.CONTENT_EXTRACTION_ENGINE === ''}
|
||||
<div class="flex w-full mt-1">
|
||||
<div class="flex-1 flex justify-between">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('PDF Extract Images (OCR)')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
@@ -419,7 +415,7 @@
|
||||
|
||||
<div class="flex w-full mt-2">
|
||||
<div class="flex-1 flex justify-between">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
'Page mode creates one document per page. Single mode combines all pages into one document for better chunking across page boundaries.'
|
||||
@@ -431,7 +427,7 @@
|
||||
</div>
|
||||
<div class="">
|
||||
<select
|
||||
class="w-fit pr-8 rounded-sm px-2 text-xs bg-transparent outline-hidden text-right"
|
||||
class="w-fit pr-8 rounded-lg px-2 text-xs bg-transparent outline-hidden text-right"
|
||||
bind:value={RAGConfig.PDF_LOADER_MODE}
|
||||
>
|
||||
<option value="page">{$i18n.t('Page')}</option>
|
||||
@@ -450,7 +446,7 @@
|
||||
className="w-full"
|
||||
>
|
||||
<input
|
||||
class="flex-1 w-full text-sm bg-transparent outline-hidden"
|
||||
class="flex-1 w-full text-xs bg-transparent outline-hidden"
|
||||
placeholder={$i18n.t('Enter Datalab Marker API Base URL')}
|
||||
bind:value={RAGConfig.DATALAB_MARKER_API_BASE_URL}
|
||||
/>
|
||||
@@ -466,7 +462,7 @@
|
||||
|
||||
<div class="flex flex-col gap-2 mt-2">
|
||||
<div class=" flex flex-col w-full justify-between">
|
||||
<div class=" mb-1 text-xs font-normal">
|
||||
<div class=" mb-1 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Additional Config')}
|
||||
</div>
|
||||
<div class="flex w-full items-center relative">
|
||||
@@ -478,6 +474,7 @@
|
||||
className="w-full"
|
||||
>
|
||||
<Textarea
|
||||
className={textareaClass}
|
||||
bind:value={RAGConfig.DATALAB_MARKER_ADDITIONAL_CONFIG}
|
||||
placeholder={$i18n.t('Enter JSON config (e.g., {"disable_links": true})')}
|
||||
/>
|
||||
@@ -487,7 +484,7 @@
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between w-full mt-2">
|
||||
<div class="self-center text-xs font-normal">
|
||||
<div class="self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
'Significantly improves accuracy by using an LLM to enhance tables, forms, inline math, and layout detection. Will increase latency. Defaults to False.'
|
||||
@@ -502,7 +499,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between w-full mt-2">
|
||||
<div class="self-center text-xs font-normal">
|
||||
<div class="self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
<Tooltip
|
||||
content={$i18n.t('Skip the cache and re-run the inference. Defaults to False.')}
|
||||
placement="top-start"
|
||||
@@ -515,7 +512,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between w-full mt-2">
|
||||
<div class="self-center text-xs font-normal">
|
||||
<div class="self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
'Force OCR on all pages of the PDF. This can lead to worse results if you have good text in your PDFs. Defaults to False.'
|
||||
@@ -530,7 +527,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between w-full mt-2">
|
||||
<div class="self-center text-xs font-normal">
|
||||
<div class="self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
'Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.'
|
||||
@@ -545,7 +542,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between w-full mt-2">
|
||||
<div class="self-center text-xs font-normal">
|
||||
<div class="self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
'Strip existing OCR text from the PDF and re-run OCR. Ignored if Force OCR is enabled. Defaults to False.'
|
||||
@@ -560,7 +557,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between w-full mt-2">
|
||||
<div class="self-center text-xs font-normal">
|
||||
<div class="self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
'Disable image extraction from the PDF. If Use LLM is enabled, images will be automatically captioned. Defaults to False.'
|
||||
@@ -575,7 +572,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between w-full mt-2">
|
||||
<div class="self-center text-xs font-normal">
|
||||
<div class="self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
'Format the lines in the output. Defaults to False. If set to True, the lines will be formatted to detect inline math and styles.'
|
||||
@@ -590,7 +587,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between w-full mt-2">
|
||||
<div class="self-center text-xs font-normal">
|
||||
<div class="self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
"The output format for the text. Can be 'json', 'markdown', or 'html'. Defaults to 'markdown'."
|
||||
@@ -602,7 +599,7 @@
|
||||
</div>
|
||||
<div class="">
|
||||
<select
|
||||
class="w-fit pr-8 rounded-sm px-2 text-xs bg-transparent outline-hidden text-right"
|
||||
class="w-fit pr-8 rounded-lg px-2 text-xs bg-transparent outline-hidden text-right"
|
||||
bind:value={RAGConfig.DATALAB_MARKER_OUTPUT_FORMAT}
|
||||
>
|
||||
<option value="markdown">{$i18n.t('Markdown')}</option>
|
||||
@@ -615,7 +612,7 @@
|
||||
<div class="my-0.5 flex flex-col gap-2 pr-2">
|
||||
<div class="flex gap-2">
|
||||
<input
|
||||
class="flex-1 w-full text-sm bg-transparent outline-hidden"
|
||||
class="flex-1 w-full text-xs bg-transparent outline-hidden"
|
||||
placeholder={$i18n.t('Enter External Document Loader URL')}
|
||||
bind:value={RAGConfig.EXTERNAL_DOCUMENT_LOADER_URL}
|
||||
/>
|
||||
@@ -633,7 +630,7 @@
|
||||
)}
|
||||
>
|
||||
<Textarea
|
||||
className="w-full text-sm outline-hidden"
|
||||
className={textareaClass}
|
||||
bind:value={RAGConfig.EXTERNAL_DOCUMENT_LOADER_HEADERS}
|
||||
placeholder={$i18n.t('Enter additional headers in JSON format')}
|
||||
required={false}
|
||||
@@ -689,7 +686,7 @@
|
||||
<div class="flex w-full mt-1">
|
||||
<div class="flex-1 mr-2">
|
||||
<input
|
||||
class="flex-1 w-full text-sm bg-transparent outline-hidden"
|
||||
class="flex-1 w-full text-xs bg-transparent outline-hidden"
|
||||
placeholder={$i18n.t('Enter Tika Server URL')}
|
||||
bind:value={RAGConfig.TIKA_SERVER_URL}
|
||||
/>
|
||||
@@ -698,7 +695,7 @@
|
||||
{:else if RAGConfig.CONTENT_EXTRACTION_ENGINE === 'docling'}
|
||||
<div class="my-0.5 flex gap-2 pr-2">
|
||||
<input
|
||||
class="flex-1 w-full text-sm bg-transparent outline-hidden"
|
||||
class="flex-1 w-full text-xs bg-transparent outline-hidden"
|
||||
placeholder={$i18n.t('Enter Docling Server URL')}
|
||||
bind:value={RAGConfig.DOCLING_SERVER_URL}
|
||||
/>
|
||||
@@ -711,11 +708,12 @@
|
||||
|
||||
<div class="flex flex-col gap-2 mt-2">
|
||||
<div class=" flex flex-col w-full justify-between">
|
||||
<div class=" mb-1 text-xs font-normal">
|
||||
<div class=" mb-1 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Parameters')}
|
||||
</div>
|
||||
<div class="flex w-full items-center relative">
|
||||
<Textarea
|
||||
className={textareaClass}
|
||||
bind:value={RAGConfig.DOCLING_PARAMS}
|
||||
placeholder={$i18n.t('Enter additional parameters in JSON format')}
|
||||
minSize={100}
|
||||
@@ -726,7 +724,7 @@
|
||||
{:else if RAGConfig.CONTENT_EXTRACTION_ENGINE === 'document_intelligence'}
|
||||
<div class="my-0.5 flex gap-2 pr-2">
|
||||
<input
|
||||
class="flex-1 w-full text-sm bg-transparent outline-hidden"
|
||||
class="flex-1 w-full text-xs bg-transparent outline-hidden"
|
||||
placeholder={$i18n.t('Enter Document Intelligence Endpoint')}
|
||||
bind:value={RAGConfig.DOCUMENT_INTELLIGENCE_ENDPOINT}
|
||||
/>
|
||||
@@ -737,13 +735,13 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="my-0.5 flex flex-col w-full">
|
||||
<div class=" mb-1 text-xs font-normal">
|
||||
<div class=" mb-1 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Document Intelligence Model')}
|
||||
</div>
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1 mr-2">
|
||||
<input
|
||||
class="flex-1 w-full text-sm bg-transparent outline-hidden"
|
||||
class="flex-1 w-full text-xs bg-transparent outline-hidden"
|
||||
placeholder={$i18n.t('Enter Document Intelligence Model')}
|
||||
bind:value={RAGConfig.DOCUMENT_INTELLIGENCE_MODEL}
|
||||
/>
|
||||
@@ -753,7 +751,7 @@
|
||||
{:else if RAGConfig.CONTENT_EXTRACTION_ENGINE === 'mistral_ocr'}
|
||||
<div class="my-0.5 flex gap-2 pr-2">
|
||||
<input
|
||||
class="flex-1 w-full text-sm bg-transparent outline-hidden"
|
||||
class="flex-1 w-full text-xs bg-transparent outline-hidden"
|
||||
placeholder={$i18n.t('Enter Mistral API Base URL')}
|
||||
bind:value={RAGConfig.MISTRAL_OCR_API_BASE_URL}
|
||||
/>
|
||||
@@ -763,7 +761,7 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="flex justify-between w-full mt-2 pr-2">
|
||||
<div class="self-center text-xs font-normal">
|
||||
<div class="self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
'Send the PDF as a base64 data URL instead of uploading it first.'
|
||||
@@ -780,7 +778,7 @@
|
||||
{:else if RAGConfig.CONTENT_EXTRACTION_ENGINE === 'paddleocr_vl'}
|
||||
<div class="my-0.5 flex gap-2 pr-2">
|
||||
<input
|
||||
class="flex-1 w-full text-sm bg-transparent outline-hidden"
|
||||
class="flex-1 w-full text-xs bg-transparent outline-hidden"
|
||||
placeholder={$i18n.t('Enter PaddleOCR-vl API Base URL')}
|
||||
bind:value={RAGConfig.PADDLEOCR_VL_BASE_URL}
|
||||
/>
|
||||
@@ -794,11 +792,11 @@
|
||||
<!-- API Mode Selection -->
|
||||
<div class="flex w-full mt-2">
|
||||
<div class="flex-1 flex justify-between">
|
||||
<div class="self-center text-xs font-normal">
|
||||
<div class="self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('API Mode')}
|
||||
</div>
|
||||
<select
|
||||
class="w-fit pr-8 rounded-sm px-2 text-xs bg-transparent outline-hidden"
|
||||
class="w-fit pr-8 rounded-lg px-2 text-xs bg-transparent outline-hidden"
|
||||
bind:value={RAGConfig.MINERU_API_MODE}
|
||||
on:change={() => {
|
||||
// Auto-update URL when switching modes if it's empty or matches the opposite mode's default
|
||||
@@ -825,7 +823,7 @@
|
||||
<!-- API URL -->
|
||||
<div class="flex w-full mt-2">
|
||||
<input
|
||||
class="flex-1 w-full text-sm bg-transparent outline-hidden"
|
||||
class="flex-1 w-full text-xs bg-transparent outline-hidden"
|
||||
placeholder={RAGConfig.MINERU_API_MODE === 'cloud'
|
||||
? $i18n.t('https://mineru.net/api/v4')
|
||||
: $i18n.t('http://localhost:8000')}
|
||||
@@ -842,11 +840,11 @@
|
||||
|
||||
<div class="flex w-full mt-2">
|
||||
<div class="flex-1 flex justify-between">
|
||||
<div class="self-center text-xs font-normal">
|
||||
<div class="self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('API Timeout')}
|
||||
</div>
|
||||
<input
|
||||
class="w-16 text-sm bg-transparent outline-hidden text-right"
|
||||
class="w-16 text-xs bg-transparent outline-hidden text-right"
|
||||
type="number"
|
||||
min="1"
|
||||
bind:value={RAGConfig.MINERU_API_TIMEOUT}
|
||||
@@ -857,7 +855,7 @@
|
||||
|
||||
<!-- Parameters -->
|
||||
<div class="flex flex-col justify-between w-full mt-2">
|
||||
<div class="text-xs font-normal">
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400">
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
'Advanced parameters for MinerU parsing (enable_ocr, enable_formula, enable_table, language, model_version, page_ranges)'
|
||||
@@ -869,6 +867,7 @@
|
||||
</div>
|
||||
<div class="mt-1.5">
|
||||
<Textarea
|
||||
className={textareaClass}
|
||||
bind:value={RAGConfig.MINERU_PARAMS}
|
||||
placeholder={`{\n "enable_ocr": false,\n "enable_formula": true,\n "enable_table": true,\n "language": "en",\n "model_version": "pipeline",\n "page_ranges": ""\n}`}
|
||||
minSize={100}
|
||||
@@ -878,7 +877,7 @@
|
||||
|
||||
<!-- File Extensions -->
|
||||
<div class="flex flex-col justify-between w-full mt-2">
|
||||
<div class="text-xs font-normal mb-1">
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
'Comma-separated list of file extensions MinerU will handle (e.g. pdf, docx, pptx, xlsx)'
|
||||
@@ -889,7 +888,7 @@
|
||||
</Tooltip>
|
||||
</div>
|
||||
<input
|
||||
class="flex-1 w-full text-sm bg-transparent outline-hidden"
|
||||
class="flex-1 w-full text-xs bg-transparent outline-hidden"
|
||||
placeholder={$i18n.t('pdf, docx, pptx, xlsx')}
|
||||
bind:value={RAGConfig.MINERU_FILE_EXTENSIONS}
|
||||
/>
|
||||
@@ -898,7 +897,7 @@
|
||||
</div>
|
||||
|
||||
<div class=" mb-2.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
<Tooltip content={$i18n.t('Full Context Mode')} placement="top-start">
|
||||
{$i18n.t('Bypass Embedding and Retrieval')}
|
||||
</Tooltip>
|
||||
@@ -920,10 +919,12 @@
|
||||
|
||||
{#if !RAGConfig.BYPASS_EMBEDDING_AND_RETRIEVAL}
|
||||
<div class=" mb-2.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-normal">{$i18n.t('Text Splitter')}</div>
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Text Splitter')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
<select
|
||||
class="w-fit pr-8 rounded-sm px-2 text-xs bg-transparent outline-hidden text-right"
|
||||
class="w-fit pr-8 rounded-lg px-2 text-xs bg-transparent outline-hidden text-right"
|
||||
bind:value={RAGConfig.TEXT_SPLITTER}
|
||||
>
|
||||
<option value="">{$i18n.t('Default')} ({$i18n.t('Character')})</option>
|
||||
@@ -937,12 +938,14 @@
|
||||
|
||||
{#if RAGConfig.TEXT_SPLITTER === 'token_transformers'}
|
||||
<div class="mb-2.5 flex flex-col w-full justify-between">
|
||||
<div class="self-center text-xs font-normal min-w-fit mb-1 w-full">
|
||||
<div
|
||||
class="self-center text-xs text-gray-600 dark:text-gray-400 min-w-fit mb-1 w-full"
|
||||
>
|
||||
{$i18n.t('Tokenizer Model')}
|
||||
</div>
|
||||
<div class="self-center w-full">
|
||||
<input
|
||||
class="w-full rounded-lg py-1.5 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
placeholder={$i18n.t('Enter Tokenizer Model')}
|
||||
bind:value={RAGConfig.RAG_TOKENIZER_MODEL}
|
||||
autocomplete="off"
|
||||
@@ -953,7 +956,7 @@
|
||||
{/if}
|
||||
|
||||
<div class=" mb-2.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
<Tooltip
|
||||
placement="top-start"
|
||||
content={$i18n.t(
|
||||
@@ -971,12 +974,12 @@
|
||||
<div class=" mb-2.5 flex w-full justify-between">
|
||||
<div class=" flex gap-1.5 w-full">
|
||||
<div class=" w-full justify-between">
|
||||
<div class="self-center text-xs font-normal min-w-fit mb-1">
|
||||
<div class="self-center text-xs text-gray-600 dark:text-gray-400 min-w-fit mb-1">
|
||||
{$i18n.t('Chunk Size')}
|
||||
</div>
|
||||
<div class="self-center">
|
||||
<input
|
||||
class=" w-full rounded-lg py-1.5 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class=" w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
type="number"
|
||||
placeholder={$i18n.t('Enter Chunk Size')}
|
||||
bind:value={RAGConfig.CHUNK_SIZE}
|
||||
@@ -987,13 +990,13 @@
|
||||
</div>
|
||||
|
||||
<div class="w-full">
|
||||
<div class=" self-center text-xs font-normal min-w-fit mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 min-w-fit mb-1">
|
||||
{$i18n.t('Chunk Overlap')}
|
||||
</div>
|
||||
|
||||
<div class="self-center">
|
||||
<input
|
||||
class="w-full rounded-lg py-1.5 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
type="number"
|
||||
placeholder={$i18n.t('Enter Chunk Overlap')}
|
||||
bind:value={RAGConfig.CHUNK_OVERLAP}
|
||||
@@ -1009,7 +1012,9 @@
|
||||
<div class=" mb-2.5 flex w-full justify-between">
|
||||
<div class=" flex gap-1.5 w-full">
|
||||
<div class="w-full">
|
||||
<div class="self-center text-xs font-normal min-w-fit mb-1">
|
||||
<div
|
||||
class="self-center text-xs text-gray-600 dark:text-gray-400 min-w-fit mb-1"
|
||||
>
|
||||
<Tooltip
|
||||
placement="top-start"
|
||||
content={$i18n.t(
|
||||
@@ -1021,7 +1026,7 @@
|
||||
</div>
|
||||
<div class="self-center">
|
||||
<input
|
||||
class="w-full rounded-lg py-1.5 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
type="number"
|
||||
placeholder={$i18n.t('Enter Chunk Min Size Target')}
|
||||
bind:value={RAGConfig.CHUNK_MIN_SIZE_TARGET}
|
||||
@@ -1037,21 +1042,19 @@
|
||||
</div>
|
||||
|
||||
{#if !RAGConfig.BYPASS_EMBEDDING_AND_RETRIEVAL}
|
||||
<div class="mb-3">
|
||||
<div class=" mt-0.5 mb-2.5 text-xs text-gray-400 dark:text-gray-600">
|
||||
<div class="mb-5">
|
||||
<div class=" mb-2 text-xs text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('Embedding')}
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-100/20 dark:border-white/[0.025] my-2" />
|
||||
|
||||
<div class=" mb-2.5 flex flex-col w-full justify-between">
|
||||
<div class="flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Embedding Model Engine')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
<select
|
||||
class="w-fit pr-8 rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
|
||||
class="w-fit pr-8 rounded-lg px-2 p-1 text-xs bg-transparent outline-hidden text-right"
|
||||
bind:value={RAG_EMBEDDING_ENGINE}
|
||||
placeholder={$i18n.t('Select an embedding model engine')}
|
||||
on:change={(e) => {
|
||||
@@ -1077,7 +1080,7 @@
|
||||
{#if RAG_EMBEDDING_ENGINE === 'openai'}
|
||||
<div class="my-0.5 flex gap-2 pr-2">
|
||||
<input
|
||||
class="flex-1 w-full text-sm bg-transparent outline-hidden"
|
||||
class="flex-1 w-full text-xs bg-transparent outline-hidden"
|
||||
placeholder={$i18n.t('API Base URL')}
|
||||
bind:value={OpenAIUrl}
|
||||
required
|
||||
@@ -1092,7 +1095,7 @@
|
||||
{:else if RAG_EMBEDDING_ENGINE === 'ollama'}
|
||||
<div class="my-0.5 flex gap-2 pr-2">
|
||||
<input
|
||||
class="flex-1 w-full text-sm bg-transparent outline-hidden"
|
||||
class="flex-1 w-full text-xs bg-transparent outline-hidden"
|
||||
placeholder={$i18n.t('API Base URL')}
|
||||
bind:value={OllamaUrl}
|
||||
required
|
||||
@@ -1108,7 +1111,7 @@
|
||||
<div class="my-0.5 flex flex-col gap-2 pr-2 w-full">
|
||||
<div class="flex gap-2">
|
||||
<input
|
||||
class="flex-1 w-full text-sm bg-transparent outline-hidden"
|
||||
class="flex-1 w-full text-xs bg-transparent outline-hidden"
|
||||
placeholder={$i18n.t('API Base URL')}
|
||||
bind:value={AzureOpenAIUrl}
|
||||
required
|
||||
@@ -1117,7 +1120,7 @@
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<input
|
||||
class="flex-1 w-full text-sm bg-transparent outline-hidden"
|
||||
class="flex-1 w-full text-xs bg-transparent outline-hidden"
|
||||
placeholder={$i18n.t('Version')}
|
||||
bind:value={AzureOpenAIVersion}
|
||||
required
|
||||
@@ -1128,14 +1131,16 @@
|
||||
</div>
|
||||
|
||||
<div class=" mb-2.5 flex flex-col w-full">
|
||||
<div class=" mb-1 text-xs font-normal">{$i18n.t('Embedding Model')}</div>
|
||||
<div class=" mb-1 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Embedding Model')}
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
{#if RAG_EMBEDDING_ENGINE === 'ollama'}
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1 mr-2">
|
||||
<input
|
||||
class="flex-1 w-full text-sm bg-transparent outline-hidden"
|
||||
class="flex-1 w-full text-xs bg-transparent outline-hidden"
|
||||
bind:value={RAG_EMBEDDING_MODEL}
|
||||
placeholder={$i18n.t('Set embedding model')}
|
||||
required
|
||||
@@ -1146,7 +1151,7 @@
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1 mr-2">
|
||||
<input
|
||||
class="flex-1 w-full text-sm bg-transparent outline-hidden"
|
||||
class="flex-1 w-full text-xs bg-transparent outline-hidden"
|
||||
placeholder={$i18n.t('Set embedding model (e.g. {{model}})', {
|
||||
model: RAG_EMBEDDING_MODEL.slice(-40)
|
||||
})}
|
||||
@@ -1195,7 +1200,7 @@
|
||||
</div>
|
||||
|
||||
<div class=" mb-2.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Embedding Batch Size')}
|
||||
</div>
|
||||
|
||||
@@ -1213,7 +1218,7 @@
|
||||
|
||||
{#if RAG_EMBEDDING_ENGINE === 'ollama' || RAG_EMBEDDING_ENGINE === 'openai' || RAG_EMBEDDING_ENGINE === 'azure_openai'}
|
||||
<div class=" mb-2.5 flex w-full justify-between">
|
||||
<div class="self-center text-xs font-normal">
|
||||
<div class="self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
'Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.'
|
||||
@@ -1229,7 +1234,7 @@
|
||||
</div>
|
||||
|
||||
<div class=" mb-2.5 flex w-full justify-between">
|
||||
<div class="self-center text-xs font-normal">
|
||||
<div class="self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
'Limits the number of concurrent embedding requests. Set to 0 for unlimited.'
|
||||
@@ -1253,16 +1258,16 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="mb-3">
|
||||
<div class=" mt-0.5 mb-2.5 text-xs text-gray-400 dark:text-gray-600">
|
||||
<div class="mb-5">
|
||||
<div class=" mb-2 text-xs text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('Retrieval')}
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-100/20 dark:border-white/[0.025] my-2" />
|
||||
|
||||
{#if !RAGConfig.BYPASS_EMBEDDING_AND_RETRIEVAL}
|
||||
<div class=" mb-2.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-normal">{$i18n.t('Full Context Mode')}</div>
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Full Context Mode')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
<Tooltip
|
||||
content={RAGConfig.RAG_FULL_CONTEXT
|
||||
@@ -1280,7 +1285,9 @@
|
||||
|
||||
{#if !RAGConfig.RAG_FULL_CONTEXT}
|
||||
<div class=" mb-2.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-normal">{$i18n.t('Hybrid Search')}</div>
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Hybrid Search')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
<Switch bind:state={RAGConfig.ENABLE_RAG_HYBRID_SEARCH} />
|
||||
</div>
|
||||
@@ -1288,7 +1295,7 @@
|
||||
|
||||
{#if RAGConfig.ENABLE_RAG_HYBRID_SEARCH === true}
|
||||
<div class="mb-2.5 flex w-full justify-between">
|
||||
<div class="self-center text-xs font-normal">
|
||||
<div class="self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Enrich Hybrid Search Text')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
@@ -1304,12 +1311,12 @@
|
||||
|
||||
<div class=" mb-2.5 flex flex-col w-full justify-between">
|
||||
<div class="flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Reranking Engine')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
<select
|
||||
class="w-fit pr-8 rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
|
||||
class="w-fit pr-8 rounded-lg px-2 p-1 text-xs bg-transparent outline-hidden text-right"
|
||||
bind:value={RAGConfig.RAG_RERANKING_ENGINE}
|
||||
placeholder={$i18n.t('Select a reranking model engine')}
|
||||
on:change={(e) => {
|
||||
@@ -1329,7 +1336,7 @@
|
||||
{#if RAGConfig.RAG_RERANKING_ENGINE === 'external'}
|
||||
<div class="my-0.5 flex gap-2 pr-2">
|
||||
<input
|
||||
class="flex-1 w-full text-sm bg-transparent outline-hidden"
|
||||
class="flex-1 w-full text-xs bg-transparent outline-hidden"
|
||||
placeholder={$i18n.t('API Base URL')}
|
||||
bind:value={RAGConfig.RAG_EXTERNAL_RERANKER_URL}
|
||||
required
|
||||
@@ -1345,13 +1352,15 @@
|
||||
</div>
|
||||
|
||||
<div class=" mb-2.5 flex flex-col w-full">
|
||||
<div class=" mb-1 text-xs font-normal">{$i18n.t('Reranking Model')}</div>
|
||||
<div class=" mb-1 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Reranking Model')}
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1 mr-2">
|
||||
<input
|
||||
class="flex-1 w-full text-sm bg-transparent outline-hidden"
|
||||
class="flex-1 w-full text-xs bg-transparent outline-hidden"
|
||||
placeholder={$i18n.t('Set reranking model (e.g. {{model}})', {
|
||||
model: 'BAAI/bge-reranker-v2-m3'
|
||||
})}
|
||||
@@ -1364,7 +1373,7 @@
|
||||
{/if}
|
||||
|
||||
<div class=" mb-2.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Reranking Batch Size')}
|
||||
</div>
|
||||
|
||||
@@ -1381,10 +1390,12 @@
|
||||
</div>
|
||||
|
||||
<div class=" mb-2.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-normal">{$i18n.t('Top K')}</div>
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Top K')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
<input
|
||||
class="flex-1 w-full text-sm bg-transparent outline-hidden"
|
||||
class="flex-1 w-full text-xs bg-transparent outline-hidden"
|
||||
type="number"
|
||||
placeholder={$i18n.t('Enter Top K')}
|
||||
bind:value={RAGConfig.TOP_K}
|
||||
@@ -1396,10 +1407,12 @@
|
||||
|
||||
{#if RAGConfig.ENABLE_RAG_HYBRID_SEARCH === true}
|
||||
<div class="mb-2.5 flex w-full justify-between">
|
||||
<div class="self-center text-xs font-normal">{$i18n.t('Top K Reranker')}</div>
|
||||
<div class="self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Top K Reranker')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
<input
|
||||
class="flex-1 w-full text-sm bg-transparent outline-hidden"
|
||||
class="flex-1 w-full text-xs bg-transparent outline-hidden"
|
||||
type="number"
|
||||
placeholder={$i18n.t('Enter Top K Reranker')}
|
||||
bind:value={RAGConfig.TOP_K_RERANKER}
|
||||
@@ -1413,12 +1426,12 @@
|
||||
{#if RAGConfig.ENABLE_RAG_HYBRID_SEARCH === true}
|
||||
<div class=" mb-2.5 flex flex-col w-full justify-between">
|
||||
<div class=" flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Relevance Threshold')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
<input
|
||||
class="flex-1 w-full text-sm bg-transparent outline-hidden"
|
||||
class="flex-1 w-full text-xs bg-transparent outline-hidden"
|
||||
type="number"
|
||||
step="0.01"
|
||||
placeholder={$i18n.t('Enter Score')}
|
||||
@@ -1449,11 +1462,11 @@
|
||||
className="inline-tooltip"
|
||||
>
|
||||
<div class="flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('BM25 Weight')}
|
||||
</div>
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded-sm transition shrink-0 outline-hidden"
|
||||
class="p-1 px-3 text-xs flex rounded-lg transition shrink-0 outline-hidden"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
RAGConfig.HYBRID_BM25_WEIGHT =
|
||||
@@ -1511,7 +1524,9 @@
|
||||
{/if}
|
||||
|
||||
<div class=" mb-2.5 flex flex-col w-full justify-between">
|
||||
<div class=" mb-1 text-xs font-normal">{$i18n.t('RAG Template')}</div>
|
||||
<div class=" mb-1 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('RAG Template')}
|
||||
</div>
|
||||
<div class="flex w-full items-center relative">
|
||||
<Tooltip
|
||||
content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
|
||||
@@ -1519,6 +1534,7 @@
|
||||
className="w-full"
|
||||
>
|
||||
<Textarea
|
||||
className={textareaClass}
|
||||
bind:value={RAGConfig.RAG_TEMPLATE}
|
||||
placeholder={$i18n.t(
|
||||
'Leave empty to use the default prompt, or enter a custom prompt'
|
||||
@@ -1537,15 +1553,15 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class=" mt-0.5 mb-2.5 text-xs text-gray-400 dark:text-gray-600">
|
||||
<div class="mb-5">
|
||||
<div class=" mb-2 text-xs text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('Files')}
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-100/20 dark:border-white/[0.025] my-2" />
|
||||
|
||||
<div class=" mb-2.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-normal">{$i18n.t('Allowed File Extensions')}</div>
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Allowed File Extensions')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
@@ -1554,7 +1570,7 @@
|
||||
placement="top-start"
|
||||
>
|
||||
<input
|
||||
class="flex-1 w-full text-sm bg-transparent outline-hidden"
|
||||
class="flex-1 w-full text-xs bg-transparent outline-hidden"
|
||||
type="text"
|
||||
placeholder={$i18n.t('e.g. pdf, docx, txt')}
|
||||
bind:value={RAGConfig.ALLOWED_FILE_EXTENSIONS}
|
||||
@@ -1565,7 +1581,9 @@
|
||||
</div>
|
||||
|
||||
<div class=" mb-2.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-normal">{$i18n.t('Max Upload Size')}</div>
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Max Upload Size')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
@@ -1574,7 +1592,7 @@
|
||||
placement="top-start"
|
||||
>
|
||||
<input
|
||||
class="flex-1 w-full text-sm bg-transparent outline-hidden"
|
||||
class="flex-1 w-full text-xs bg-transparent outline-hidden"
|
||||
type="number"
|
||||
placeholder={$i18n.t('Leave empty for unlimited')}
|
||||
bind:value={RAGConfig.FILE_MAX_SIZE}
|
||||
@@ -1586,7 +1604,9 @@
|
||||
</div>
|
||||
|
||||
<div class=" mb-2.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-normal">{$i18n.t('Max Upload Count')}</div>
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Max Upload Count')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
@@ -1595,7 +1615,7 @@
|
||||
placement="top-start"
|
||||
>
|
||||
<input
|
||||
class="flex-1 w-full text-sm bg-transparent outline-hidden"
|
||||
class="flex-1 w-full text-xs bg-transparent outline-hidden"
|
||||
type="number"
|
||||
placeholder={$i18n.t('Leave empty for unlimited')}
|
||||
bind:value={RAGConfig.FILE_MAX_COUNT}
|
||||
@@ -1607,7 +1627,9 @@
|
||||
</div>
|
||||
|
||||
<div class=" mb-2.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-normal">{$i18n.t('Image Compression Width')}</div>
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Image Compression Width')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
@@ -1616,7 +1638,7 @@
|
||||
placement="top-start"
|
||||
>
|
||||
<input
|
||||
class="flex-1 w-full text-sm bg-transparent outline-hidden"
|
||||
class="flex-1 w-full text-xs bg-transparent outline-hidden"
|
||||
type="number"
|
||||
placeholder={$i18n.t('Leave empty for no compression')}
|
||||
bind:value={RAGConfig.FILE_IMAGE_COMPRESSION_WIDTH}
|
||||
@@ -1628,7 +1650,7 @@
|
||||
</div>
|
||||
|
||||
<div class=" mb-2.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Image Compression Height')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
@@ -1639,7 +1661,7 @@
|
||||
placement="top-start"
|
||||
>
|
||||
<input
|
||||
class="flex-1 w-full text-sm bg-transparent outline-hidden"
|
||||
class="flex-1 w-full text-xs bg-transparent outline-hidden"
|
||||
type="number"
|
||||
placeholder={$i18n.t('Leave empty for no compression')}
|
||||
bind:value={RAGConfig.FILE_IMAGE_COMPRESSION_HEIGHT}
|
||||
@@ -1651,37 +1673,39 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class=" mt-0.5 mb-2.5 text-xs text-gray-400 dark:text-gray-600">
|
||||
<div class="mb-5">
|
||||
<div class=" mb-2 text-xs text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('Integration')}
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-100/20 dark:border-white/[0.025] my-2" />
|
||||
|
||||
<div class=" mb-2.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-normal">{$i18n.t('Google Drive')}</div>
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Google Drive')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
<Switch bind:state={RAGConfig.ENABLE_GOOGLE_DRIVE_INTEGRATION} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class=" mb-2.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-normal">{$i18n.t('OneDrive')}</div>
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('OneDrive')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
<Switch bind:state={RAGConfig.ENABLE_ONEDRIVE_INTEGRATION} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class=" mt-0.5 mb-2.5 text-xs text-gray-400 dark:text-gray-600">
|
||||
<div class="mb-5">
|
||||
<div class=" mb-2 text-xs text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('Danger Zone')}
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-100/20 dark:border-white/[0.025] my-2" />
|
||||
|
||||
<div class=" mb-2.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-normal">{$i18n.t('Reset Upload Directory')}</div>
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Reset Upload Directory')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
<button
|
||||
class="text-xs"
|
||||
@@ -1696,7 +1720,7 @@
|
||||
</div>
|
||||
|
||||
<div class=" mb-2.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Reset Vector Storage/Knowledge')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
@@ -1712,7 +1736,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class=" mb-2.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Reindex Knowledge Base Vectors')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
@@ -1730,7 +1754,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end pt-3 text-sm font-normal">
|
||||
<div class="flex justify-end pt-6 text-sm font-normal">
|
||||
<button
|
||||
class="px-3.5 py-1.5 text-sm font-normal bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
|
||||
type="submit"
|
||||
|
||||
@@ -13,8 +13,10 @@
|
||||
import Plus from '$lib/components/icons/Plus.svelte';
|
||||
import Model from './Evaluations/Model.svelte';
|
||||
import ArenaModelModal from './Evaluations/ArenaModelModal.svelte';
|
||||
import AdminSettingRow from './AdminSettingRow.svelte';
|
||||
import AdminSettingSection from './AdminSettingSection.svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
const i18n: any = getContext('i18n');
|
||||
|
||||
let evaluationConfig = null;
|
||||
let showAddModel = false;
|
||||
@@ -30,7 +32,9 @@
|
||||
models.set(
|
||||
await getModels(
|
||||
localStorage.token,
|
||||
$config?.features?.enable_direct_connections && ($settings?.directConnections ?? null)
|
||||
$config?.features?.enable_direct_connections
|
||||
? ($settings?.directConnections ?? null)
|
||||
: null
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -44,7 +48,7 @@
|
||||
models.set(
|
||||
await getModels(
|
||||
localStorage.token,
|
||||
$config?.features?.enable_direct_connections && ($settings?.directConnections ?? null)
|
||||
$config?.features?.enable_direct_connections ? ($settings?.directConnections ?? null) : null
|
||||
)
|
||||
);
|
||||
};
|
||||
@@ -57,7 +61,7 @@
|
||||
models.set(
|
||||
await getModels(
|
||||
localStorage.token,
|
||||
$config?.features?.enable_direct_connections && ($settings?.directConnections ?? null)
|
||||
$config?.features?.enable_direct_connections ? ($settings?.directConnections ?? null) : null
|
||||
)
|
||||
);
|
||||
};
|
||||
@@ -100,75 +104,60 @@
|
||||
dispatch('save');
|
||||
}}
|
||||
>
|
||||
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5 -mr-1.5">
|
||||
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5">
|
||||
{#if evaluationConfig !== null}
|
||||
<div class="">
|
||||
<div class="mb-3">
|
||||
<div class=" mt-0.5 mb-2.5 text-xs text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('General')}
|
||||
</div>
|
||||
<AdminSettingSection first>
|
||||
<AdminSettingRow
|
||||
label={$i18n.t('Arena Models')}
|
||||
description={$i18n.t('Message rating should be enabled to use this feature')}
|
||||
>
|
||||
<Tooltip content={$i18n.t(`Message rating should be enabled to use this feature`)}>
|
||||
<Switch bind:state={evaluationConfig.ENABLE_EVALUATION_ARENA_MODELS} />
|
||||
</Tooltip>
|
||||
</AdminSettingRow>
|
||||
</AdminSettingSection>
|
||||
|
||||
<hr class=" border-gray-100/20 dark:border-white/[0.025] my-2" />
|
||||
{#if evaluationConfig.ENABLE_EVALUATION_ARENA_MODELS}
|
||||
<AdminSettingSection title={$i18n.t('Models')}>
|
||||
<div class="mb-2 flex items-center justify-between">
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400">{$i18n.t('Arena Models')}</div>
|
||||
|
||||
<div class="mb-2.5 flex w-full justify-between">
|
||||
<div class=" text-xs font-normal">{$i18n.t('Arena Models')}</div>
|
||||
|
||||
<Tooltip content={$i18n.t(`Message rating should be enabled to use this feature`)}>
|
||||
<Switch bind:state={evaluationConfig.ENABLE_EVALUATION_ARENA_MODELS} />
|
||||
<Tooltip content={$i18n.t('Add Arena Model')}>
|
||||
<button
|
||||
class="flex size-6 items-center justify-center rounded-lg text-gray-400 transition-colors hover:bg-black/5 hover:text-gray-900 dark:text-gray-600 dark:hover:bg-white/5 dark:hover:text-white"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
showAddModel = true;
|
||||
}}
|
||||
>
|
||||
<Plus />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if evaluationConfig.ENABLE_EVALUATION_ARENA_MODELS}
|
||||
<div class="mb-3">
|
||||
<div
|
||||
class=" mt-0.5 mb-2.5 text-xs text-gray-400 dark:text-gray-600 flex justify-between items-center"
|
||||
>
|
||||
<div>
|
||||
{$i18n.t('Manage')}
|
||||
<div class="flex flex-col gap-2">
|
||||
{#if (evaluationConfig?.EVALUATION_ARENA_MODELS ?? []).length > 0}
|
||||
{#each evaluationConfig.EVALUATION_ARENA_MODELS as model, index}
|
||||
<Model
|
||||
{model}
|
||||
on:edit={(e) => {
|
||||
editModelHandler(e.detail, index);
|
||||
}}
|
||||
on:delete={(e) => {
|
||||
deleteModelHandler(index);
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
{:else}
|
||||
<div class="text-center text-[0.6875rem] text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t(
|
||||
`Using the default arena model with all models. Click the plus button to add custom models.`
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Tooltip content={$i18n.t('Add Arena Model')}>
|
||||
<button
|
||||
class="p-1"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
showAddModel = true;
|
||||
}}
|
||||
>
|
||||
<Plus />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-100/20 dark:border-white/[0.025] my-2" />
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
{#if (evaluationConfig?.EVALUATION_ARENA_MODELS ?? []).length > 0}
|
||||
{#each evaluationConfig.EVALUATION_ARENA_MODELS as model, index}
|
||||
<Model
|
||||
{model}
|
||||
on:edit={(e) => {
|
||||
editModelHandler(e.detail, index);
|
||||
}}
|
||||
on:delete={(e) => {
|
||||
deleteModelHandler(index);
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
{:else}
|
||||
<div class=" text-center text-xs text-gray-500">
|
||||
{$i18n.t(
|
||||
`Using the default arena model with all models. Click the plus button to add custom models.`
|
||||
)}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</AdminSettingSection>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="flex h-full justify-center">
|
||||
<div class="my-auto">
|
||||
@@ -178,7 +167,7 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end pt-3 text-sm font-normal">
|
||||
<div class="flex justify-end pt-6 text-sm font-normal">
|
||||
<button
|
||||
class="px-3.5 py-1.5 text-sm font-normal bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
|
||||
type="submit"
|
||||
|
||||
@@ -412,7 +412,7 @@
|
||||
>
|
||||
<input
|
||||
id="event-webhook-name"
|
||||
class={`w-full text-sm bg-transparent ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
|
||||
class={`w-full text-xs bg-transparent ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
|
||||
type="text"
|
||||
placeholder={$i18n.t('Identity audit')}
|
||||
autocomplete="off"
|
||||
@@ -431,7 +431,7 @@
|
||||
<div class="flex items-center gap-2">
|
||||
<input
|
||||
id="event-webhook-url"
|
||||
class={`w-full text-sm bg-transparent ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
|
||||
class={`w-full text-xs bg-transparent ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
|
||||
type="url"
|
||||
placeholder="https://example.com/events"
|
||||
autocomplete="off"
|
||||
@@ -482,7 +482,7 @@
|
||||
<div class="flex flex-wrap gap-1 mb-2">
|
||||
{#each targetGroupIds as id}
|
||||
<div
|
||||
class="flex items-center gap-1 rounded-full bg-gray-100 dark:bg-gray-850 px-2 py-1 text-xs"
|
||||
class="flex items-center gap-1 rounded-full bg-gray-100 dark:bg-white/[0.03] px-2 py-1 text-xs"
|
||||
>
|
||||
<span class="truncate max-w-36">{selectedGroups[id]?.name ?? id}</span>
|
||||
<span class="text-gray-500">{$i18n.t('group')}</span>
|
||||
@@ -498,7 +498,7 @@
|
||||
|
||||
{#each targetUserIds as id}
|
||||
<div
|
||||
class="flex items-center gap-1 rounded-full bg-gray-100 dark:bg-gray-850 px-2 py-1 text-xs"
|
||||
class="flex items-center gap-1 rounded-full bg-gray-100 dark:bg-white/[0.03] px-2 py-1 text-xs"
|
||||
>
|
||||
<span class="truncate max-w-36">{selectedUsers[id]?.name ?? id}</span>
|
||||
<span class="text-gray-500">{$i18n.t('user')}</span>
|
||||
@@ -516,7 +516,7 @@
|
||||
|
||||
<div class="relative">
|
||||
<input
|
||||
class={`w-full text-sm bg-transparent ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
|
||||
class={`w-full text-xs bg-transparent ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
|
||||
type="text"
|
||||
placeholder={$i18n.t('Search users or groups')}
|
||||
autocomplete="off"
|
||||
@@ -585,7 +585,7 @@
|
||||
<div class="flex flex-wrap gap-1">
|
||||
{#each form.events as event}
|
||||
<div
|
||||
class="flex items-center gap-1 rounded-full bg-gray-100 dark:bg-gray-850 px-2 py-1 text-xs"
|
||||
class="flex items-center gap-1 rounded-full bg-gray-100 dark:bg-white/[0.03] px-2 py-1 text-xs"
|
||||
>
|
||||
<span class="font-mono break-all">{event}</span>
|
||||
<button
|
||||
@@ -602,7 +602,7 @@
|
||||
|
||||
<div class="flex gap-2">
|
||||
<input
|
||||
class={`w-full flex-1 text-sm bg-transparent font-mono ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
|
||||
class={`w-full flex-1 text-xs bg-transparent font-mono ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
|
||||
type="text"
|
||||
placeholder={$i18n.t('Search or add pattern')}
|
||||
autocomplete="off"
|
||||
@@ -689,7 +689,7 @@
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="mt-0.5 mb-2.5 text-xs text-gray-400 dark:text-gray-600">{$i18n.t('Events')}</div>
|
||||
<hr class="border-gray-100/20 dark:border-white/[0.025] my-2" />
|
||||
<hr class="border-gray-100/10 dark:border-white/[0.015] my-2" />
|
||||
|
||||
<div class="mb-2.5 flex flex-col w-full justify-between">
|
||||
<div class="flex justify-between items-center mb-1">
|
||||
|
||||
@@ -788,18 +788,16 @@
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<div class="mb-2.5 flex flex-col w-full justify-between text-sm">
|
||||
<div class="flex justify-between items-center mb-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="font-normal">{$i18n.t('External Knowledge Sources')}</div>
|
||||
<span
|
||||
class="text-[0.65rem] font-normal uppercase px-1.5 py-0.5 rounded-full bg-gray-100 dark:bg-gray-800 text-gray-500 dark:text-gray-400"
|
||||
>{$i18n.t('Experimental')}</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex w-full flex-col justify-between text-xs">
|
||||
<div class="mb-2 flex items-center justify-between">
|
||||
<div class="text-gray-600 dark:text-gray-400">{$i18n.t('Sources')}</div>
|
||||
|
||||
<Tooltip content={$i18n.t('Add Connection')}>
|
||||
<button class="px-1" on:click={openCreateSource} type="button">
|
||||
<button
|
||||
class="flex size-6 items-center justify-center rounded-lg text-gray-400 transition-colors hover:bg-black/5 hover:text-gray-900 dark:text-gray-600 dark:hover:bg-white/5 dark:hover:text-white"
|
||||
on:click={openCreateSource}
|
||||
type="button"
|
||||
>
|
||||
<Plus />
|
||||
</button>
|
||||
</Tooltip>
|
||||
@@ -821,7 +819,7 @@
|
||||
<DatabaseSettings className="size-4" strokeWidth="1.5" />
|
||||
</Tooltip>
|
||||
|
||||
<div class="outline-hidden w-full bg-transparent text-sm min-w-0 line-clamp-1">
|
||||
<div class="outline-hidden w-full bg-transparent text-xs min-w-0 line-clamp-1">
|
||||
<span>{item.name}</span>
|
||||
{' '}
|
||||
<span class="text-gray-500">
|
||||
@@ -868,16 +866,14 @@
|
||||
<Spinner />
|
||||
</div>
|
||||
{:else if items.length === 0}
|
||||
<div class="text-xs text-gray-400 dark:text-gray-500">
|
||||
<div class="text-[0.6875rem] text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('No external knowledge sources configured.')}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="my-1.5">
|
||||
<div class="text-xs text-gray-500">
|
||||
{$i18n.t(
|
||||
'Create one read-only Knowledge source per external collection. Test must pass before the source is created.'
|
||||
)}
|
||||
{#if items.length === 0}
|
||||
<div class="text-[0.6875rem] text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('Test must pass before a source is created.')}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -16,20 +16,27 @@
|
||||
import Textarea from '$lib/components/common/Textarea.svelte';
|
||||
import Banners from './Interface/Banners.svelte';
|
||||
import Events from './Events.svelte';
|
||||
import AdminSettingField from './AdminSettingField.svelte';
|
||||
import AdminSettingRow from './AdminSettingRow.svelte';
|
||||
import AdminSettingSection from './AdminSettingSection.svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
const i18n: any = getContext('i18n');
|
||||
|
||||
export let saveHandler: Function;
|
||||
|
||||
let updateAvailable = false;
|
||||
let updateAvailable: boolean | null = false;
|
||||
let version = {
|
||||
current: WEBUI_VERSION,
|
||||
latest: WEBUI_VERSION
|
||||
};
|
||||
|
||||
let adminConfig = null;
|
||||
let adminConfig: any = null;
|
||||
|
||||
let banners: Banner[] = [];
|
||||
const inputClass =
|
||||
'w-full h-7 rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 text-xs text-gray-700 outline-hidden transition-colors placeholder:text-gray-300 focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:placeholder:text-gray-700 dark:focus:border-blue-500';
|
||||
const textareaClass =
|
||||
'w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors placeholder:text-gray-300 focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:placeholder:text-gray-700 dark:focus:border-blue-500';
|
||||
|
||||
const checkForVersionUpdates = async () => {
|
||||
updateAvailable = null;
|
||||
@@ -72,378 +79,262 @@
|
||||
</script>
|
||||
|
||||
<form
|
||||
class="flex flex-col h-full justify-between space-y-3 text-sm"
|
||||
class="flex h-full flex-col justify-between text-sm"
|
||||
on:submit|preventDefault={async () => {
|
||||
updateHandler();
|
||||
}}
|
||||
>
|
||||
<div class="space-y-3 flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5 -mr-1.5">
|
||||
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5">
|
||||
{#if adminConfig !== null}
|
||||
<div class="">
|
||||
<div class="mb-3.5">
|
||||
<div class=" mt-0.5 mb-2.5 text-xs text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('General')}
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-100/20 dark:border-white/[0.025] my-2" />
|
||||
|
||||
<div class="mb-2.5">
|
||||
<div class=" mb-1 text-xs font-normal flex space-x-2 items-center">
|
||||
<div>
|
||||
{$i18n.t('Version')}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex w-full justify-between items-center">
|
||||
<div class="flex flex-col text-xs text-gray-700 dark:text-gray-200">
|
||||
<div class="flex gap-1">
|
||||
<Tooltip content={WEBUI_BUILD_HASH}>
|
||||
v{WEBUI_VERSION}
|
||||
</Tooltip>
|
||||
|
||||
{#if $config?.features?.enable_version_update_check}
|
||||
<a
|
||||
href="https://github.com/open-webui/open-webui/releases/tag/v{version.latest}"
|
||||
target="_blank"
|
||||
>
|
||||
{updateAvailable === null
|
||||
? $i18n.t('Checking for updates...')
|
||||
: updateAvailable
|
||||
? `(v${version.latest} ${$i18n.t('available!')})`
|
||||
: $i18n.t('(latest)')}
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<button
|
||||
class=" underline flex items-center space-x-1 text-xs text-gray-500 dark:text-gray-500"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
showChangelog.set(true);
|
||||
}}
|
||||
>
|
||||
<div>{$i18n.t("See what's new")}</div>
|
||||
</button>
|
||||
</div>
|
||||
<AdminSettingSection first>
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div class="min-w-0 text-xs">
|
||||
<div class="text-gray-600 dark:text-gray-400">{$i18n.t('Version')}</div>
|
||||
<div class="mt-1 flex flex-wrap gap-x-1 text-gray-700 dark:text-gray-200">
|
||||
<Tooltip content={WEBUI_BUILD_HASH}>v{WEBUI_VERSION}</Tooltip>
|
||||
|
||||
{#if $config?.features?.enable_version_update_check}
|
||||
<button
|
||||
class=" text-xs px-3 py-1.5 bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 transition rounded-lg font-normal"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
checkForVersionUpdates();
|
||||
}}
|
||||
<a
|
||||
href="https://github.com/open-webui/open-webui/releases/tag/v{version.latest}"
|
||||
target="_blank"
|
||||
class="text-gray-500 hover:text-gray-700 dark:text-gray-500 dark:hover:text-gray-300"
|
||||
>
|
||||
{$i18n.t('Check for updates')}
|
||||
</button>
|
||||
{updateAvailable === null
|
||||
? $i18n.t('Checking for updates...')
|
||||
: updateAvailable
|
||||
? `(v${version.latest} ${$i18n.t('available!')})`
|
||||
: $i18n.t('(latest)')}
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="mt-0.5 text-xs text-gray-400 transition-colors hover:text-gray-700 dark:text-gray-600 dark:hover:text-gray-300"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
showChangelog.set(true);
|
||||
}}
|
||||
>
|
||||
{$i18n.t("See what's new")}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mb-2.5">
|
||||
<div class="flex w-full justify-between items-center">
|
||||
<div class="text-xs pr-2">
|
||||
<div class="">
|
||||
{$i18n.t('Help')}
|
||||
</div>
|
||||
<div class=" text-xs text-gray-500">
|
||||
{$i18n.t('Discover how to use Open WebUI and seek support from the community.')}
|
||||
</div>
|
||||
</div>
|
||||
{#if $config?.features?.enable_version_update_check}
|
||||
<button
|
||||
class="shrink-0 text-xs text-gray-500 transition-colors hover:text-gray-900 dark:text-gray-500 dark:hover:text-white"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
checkForVersionUpdates();
|
||||
}}
|
||||
>
|
||||
{$i18n.t('Check for updates')}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<a
|
||||
class="flex-shrink-0 text-xs font-normal underline"
|
||||
href="https://docs.openwebui.com/"
|
||||
target="_blank"
|
||||
>
|
||||
{$i18n.t('Documentation')}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="mt-1">
|
||||
<div class="flex space-x-1">
|
||||
<a href="https://discord.gg/5rJgQTnV4s" target="_blank">
|
||||
<img
|
||||
alt="Discord"
|
||||
src="https://img.shields.io/badge/Discord-Open_WebUI-blue?logo=discord&logoColor=white"
|
||||
/>
|
||||
</a>
|
||||
|
||||
<a href="https://twitter.com/OpenWebUI" target="_blank">
|
||||
<img
|
||||
alt="X (formerly Twitter) Follow"
|
||||
src="https://img.shields.io/twitter/follow/OpenWebUI"
|
||||
/>
|
||||
</a>
|
||||
|
||||
<a href="https://github.com/open-webui/open-webui" target="_blank">
|
||||
<img
|
||||
alt="Github Repo"
|
||||
src="https://img.shields.io/github/stars/open-webui/open-webui?style=social&label=Star us on Github"
|
||||
/>
|
||||
</a>
|
||||
<div class="text-xs">
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div class="min-w-0">
|
||||
<div class="text-gray-600 dark:text-gray-400">{$i18n.t('Help')}</div>
|
||||
<div class="mt-0.5 text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('Discover how to use Open WebUI and seek support from the community.')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a
|
||||
class="shrink-0 text-gray-500 transition-colors hover:text-gray-900 dark:text-gray-500 dark:hover:text-white"
|
||||
href="https://docs.openwebui.com/"
|
||||
target="_blank"
|
||||
>
|
||||
{$i18n.t('Documentation')}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="mb-2.5">
|
||||
<div class="flex w-full justify-between items-center">
|
||||
<div class="text-xs pr-2">
|
||||
<div class="">
|
||||
{$i18n.t('License')}
|
||||
</div>
|
||||
|
||||
{#if $config?.license_metadata}
|
||||
<a
|
||||
href="https://docs.openwebui.com/enterprise"
|
||||
target="_blank"
|
||||
class="text-gray-500 mt-0.5"
|
||||
>
|
||||
<span class=" capitalize text-black dark:text-white"
|
||||
>{$config?.license_metadata?.type}
|
||||
license</span
|
||||
>
|
||||
registered to
|
||||
<span class=" capitalize text-black dark:text-white"
|
||||
>{$config?.license_metadata?.organization_name}</span
|
||||
>
|
||||
for
|
||||
<span class=" font-normal text-black dark:text-white"
|
||||
>{$config?.license_metadata?.seats ?? 'Unlimited'} users.</span
|
||||
>
|
||||
</a>
|
||||
{#if $config?.license_metadata?.html}
|
||||
<div class="mt-0.5">
|
||||
{@html DOMPurify.sanitize($config?.license_metadata?.html)}
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<a
|
||||
class=" text-xs hover:underline"
|
||||
href="https://docs.openwebui.com/enterprise"
|
||||
target="_blank"
|
||||
>
|
||||
<span class="text-gray-500">
|
||||
{$i18n.t(
|
||||
'Upgrade to a licensed plan for enhanced capabilities, including custom theming and branding, and dedicated support.'
|
||||
)}
|
||||
</span>
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- <button
|
||||
class="flex-shrink-0 text-xs px-3 py-1.5 bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 transition rounded-lg font-normal"
|
||||
>
|
||||
{$i18n.t('Activate')}
|
||||
</button> -->
|
||||
</div>
|
||||
<div class="mt-1 flex flex-wrap gap-x-3 gap-y-1 text-gray-400 dark:text-gray-600">
|
||||
<a
|
||||
class="hover:text-gray-700 dark:hover:text-gray-300"
|
||||
href="https://discord.gg/5rJgQTnV4s"
|
||||
target="_blank">Discord</a
|
||||
>
|
||||
<a
|
||||
class="hover:text-gray-700 dark:hover:text-gray-300"
|
||||
href="https://twitter.com/OpenWebUI"
|
||||
target="_blank">X</a
|
||||
>
|
||||
<a
|
||||
class="hover:text-gray-700 dark:hover:text-gray-300"
|
||||
href="https://github.com/open-webui/open-webui"
|
||||
target="_blank">GitHub</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class=" mt-0.5 mb-2.5 text-xs text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('Features')}
|
||||
</div>
|
||||
<div class="text-xs">
|
||||
<div class="text-gray-600 dark:text-gray-400">{$i18n.t('License')}</div>
|
||||
|
||||
<hr class=" border-gray-100/20 dark:border-white/[0.025] my-2" />
|
||||
|
||||
<div class="mb-2.5 flex w-full items-center justify-between pr-2">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
{$i18n.t('Enable Community Sharing')}
|
||||
</div>
|
||||
|
||||
<Switch bind:state={adminConfig.ENABLE_COMMUNITY_SHARING} />
|
||||
</div>
|
||||
|
||||
<div class="mb-2.5 flex w-full items-center justify-between pr-2">
|
||||
<div class=" self-center text-xs font-normal">{$i18n.t('Enable Message Rating')}</div>
|
||||
|
||||
<Switch bind:state={adminConfig.ENABLE_MESSAGE_RATING} />
|
||||
</div>
|
||||
|
||||
<div class="mb-2.5 flex w-full items-center justify-between pr-2">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
{$i18n.t('Folders')}
|
||||
</div>
|
||||
|
||||
<Switch bind:state={adminConfig.ENABLE_FOLDERS} />
|
||||
</div>
|
||||
|
||||
{#if adminConfig.ENABLE_FOLDERS}
|
||||
<div class="mb-2.5 w-full justify-between">
|
||||
<div class="flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
{$i18n.t('Folder Max File Count')}
|
||||
</div>
|
||||
{#if $config?.license_metadata}
|
||||
<a
|
||||
href="https://docs.openwebui.com/enterprise"
|
||||
target="_blank"
|
||||
class="mt-0.5 block text-gray-500"
|
||||
>
|
||||
<span class="capitalize text-black dark:text-white"
|
||||
>{$config?.license_metadata?.type} license</span
|
||||
>
|
||||
registered to
|
||||
<span class="capitalize text-black dark:text-white"
|
||||
>{$config?.license_metadata?.organization_name}</span
|
||||
>
|
||||
for
|
||||
<span class="text-black dark:text-white"
|
||||
>{$config?.license_metadata?.seats ?? 'Unlimited'} users.</span
|
||||
>
|
||||
</a>
|
||||
{#if $config?.license_metadata?.html}
|
||||
<div class="mt-0.5 text-gray-500">
|
||||
{@html DOMPurify.sanitize($config?.license_metadata?.html)}
|
||||
</div>
|
||||
|
||||
<div class="flex mt-2 space-x-2">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
type="number"
|
||||
min="0"
|
||||
placeholder={$i18n.t('Leave empty for unlimited')}
|
||||
bind:value={adminConfig.FOLDER_MAX_FILE_COUNT}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
|
||||
{$i18n.t('Maximum number of files allowed per folder.')}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="mb-2.5 flex w-full items-center justify-between pr-2">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
{$i18n.t('Memories')}
|
||||
</div>
|
||||
|
||||
<Switch bind:state={adminConfig.ENABLE_MEMORIES} />
|
||||
</div>
|
||||
|
||||
{#if adminConfig.ENABLE_MEMORIES}
|
||||
<div class="mb-2.5 flex w-full items-center justify-between pr-2 pl-4">
|
||||
<div class=" self-center text-xs font-normal text-gray-500 dark:text-gray-400">
|
||||
{$i18n.t('Memory System Context')}
|
||||
</div>
|
||||
|
||||
<Switch bind:state={adminConfig.ENABLE_MEMORY_SYSTEM_CONTEXT} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="mb-2.5 flex w-full items-center justify-between pr-2">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
{$i18n.t('Notes')}
|
||||
</div>
|
||||
|
||||
<Switch bind:state={adminConfig.ENABLE_NOTES} />
|
||||
</div>
|
||||
|
||||
<div class="mb-2.5 flex w-full items-center justify-between pr-2">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
{$i18n.t('Channels')}
|
||||
</div>
|
||||
|
||||
<Switch bind:state={adminConfig.ENABLE_CHANNELS} />
|
||||
</div>
|
||||
|
||||
<div class="mb-2.5 flex w-full items-center justify-between pr-2">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
{$i18n.t('Calendar')}
|
||||
</div>
|
||||
|
||||
<Switch bind:state={adminConfig.ENABLE_CALENDAR} />
|
||||
</div>
|
||||
|
||||
<div class="mb-2.5 flex w-full items-center justify-between pr-2">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
{$i18n.t('Automations')}
|
||||
</div>
|
||||
|
||||
<Switch bind:state={adminConfig.ENABLE_AUTOMATIONS} />
|
||||
</div>
|
||||
|
||||
<div class="mb-2.5 flex w-full items-center justify-between pr-2">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
{$i18n.t('User Webhooks')}
|
||||
</div>
|
||||
|
||||
<Switch bind:state={adminConfig.ENABLE_USER_WEBHOOKS} />
|
||||
</div>
|
||||
|
||||
<div class="mb-2.5 flex w-full items-center justify-between pr-2">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
{$i18n.t('User Status')}
|
||||
</div>
|
||||
|
||||
<Switch bind:state={adminConfig.ENABLE_USER_STATUS} />
|
||||
</div>
|
||||
|
||||
<div class="mb-2.5">
|
||||
<div class=" self-center text-xs font-normal mb-2">
|
||||
{$i18n.t('Response Watermark')}
|
||||
</div>
|
||||
<Textarea
|
||||
placeholder={$i18n.t('Enter a watermark for the response. Leave empty for none.')}
|
||||
bind:value={adminConfig.RESPONSE_WATERMARK}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-2.5 w-full justify-between">
|
||||
<div class="flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-normal">{$i18n.t('WebUI URL')}</div>
|
||||
</div>
|
||||
|
||||
<div class="flex mt-2 space-x-2">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
type="text"
|
||||
placeholder={`e.g.) "http://localhost:3000"`}
|
||||
bind:value={adminConfig.WEBUI_URL}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
|
||||
{/if}
|
||||
{:else}
|
||||
<a
|
||||
class="mt-0.5 block text-gray-400 transition-colors hover:text-gray-700 dark:text-gray-600 dark:hover:text-gray-300"
|
||||
href="https://docs.openwebui.com/enterprise"
|
||||
target="_blank"
|
||||
>
|
||||
{$i18n.t(
|
||||
'Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.'
|
||||
'Upgrade to a licensed plan for enhanced capabilities, including custom theming and branding, and dedicated support.'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
</AdminSettingSection>
|
||||
|
||||
<Events />
|
||||
<AdminSettingSection title={$i18n.t('Features')}>
|
||||
<AdminSettingRow label={$i18n.t('Enable Community Sharing')}>
|
||||
<Switch bind:state={adminConfig.ENABLE_COMMUNITY_SHARING} />
|
||||
</AdminSettingRow>
|
||||
<AdminSettingRow label={$i18n.t('Enable Message Rating')}>
|
||||
<Switch bind:state={adminConfig.ENABLE_MESSAGE_RATING} />
|
||||
</AdminSettingRow>
|
||||
<AdminSettingRow label={$i18n.t('Folders')}>
|
||||
<Switch bind:state={adminConfig.ENABLE_FOLDERS} />
|
||||
</AdminSettingRow>
|
||||
|
||||
<div class="mb-3.5">
|
||||
<div class=" mt-0.5 mb-2.5 text-xs text-gray-400 dark:text-gray-600">{$i18n.t('UI')}</div>
|
||||
{#if adminConfig.ENABLE_FOLDERS}
|
||||
<AdminSettingField
|
||||
label={$i18n.t('Folder Max File Count')}
|
||||
description={$i18n.t('Maximum number of files allowed per folder.')}
|
||||
>
|
||||
<input
|
||||
class={inputClass}
|
||||
type="number"
|
||||
min="0"
|
||||
placeholder={$i18n.t('Leave empty for unlimited')}
|
||||
bind:value={adminConfig.FOLDER_MAX_FILE_COUNT}
|
||||
/>
|
||||
</AdminSettingField>
|
||||
{/if}
|
||||
|
||||
<hr class=" border-gray-100/20 dark:border-white/[0.025] my-2" />
|
||||
<AdminSettingRow label={$i18n.t('Memories')}>
|
||||
<Switch bind:state={adminConfig.ENABLE_MEMORIES} />
|
||||
</AdminSettingRow>
|
||||
{#if adminConfig.ENABLE_MEMORIES}
|
||||
<AdminSettingRow
|
||||
label={$i18n.t('Memory System Context')}
|
||||
labelClassName="pl-4 text-gray-500 dark:text-gray-500"
|
||||
>
|
||||
<Switch bind:state={adminConfig.ENABLE_MEMORY_SYSTEM_CONTEXT} />
|
||||
</AdminSettingRow>
|
||||
{/if}
|
||||
<AdminSettingRow label={$i18n.t('Notes')}>
|
||||
<Switch bind:state={adminConfig.ENABLE_NOTES} />
|
||||
</AdminSettingRow>
|
||||
<AdminSettingRow label={$i18n.t('Channels')}>
|
||||
<Switch bind:state={adminConfig.ENABLE_CHANNELS} />
|
||||
</AdminSettingRow>
|
||||
<AdminSettingRow label={$i18n.t('Calendar')}>
|
||||
<Switch bind:state={adminConfig.ENABLE_CALENDAR} />
|
||||
</AdminSettingRow>
|
||||
<AdminSettingRow label={$i18n.t('Automations')}>
|
||||
<Switch bind:state={adminConfig.ENABLE_AUTOMATIONS} />
|
||||
</AdminSettingRow>
|
||||
<AdminSettingRow label={$i18n.t('User Webhooks')}>
|
||||
<Switch bind:state={adminConfig.ENABLE_USER_WEBHOOKS} />
|
||||
</AdminSettingRow>
|
||||
<AdminSettingRow label={$i18n.t('User Status')}>
|
||||
<Switch bind:state={adminConfig.ENABLE_USER_STATUS} />
|
||||
</AdminSettingRow>
|
||||
|
||||
<div class="mb-2.5">
|
||||
<div class="flex w-full justify-between">
|
||||
<div class=" self-center text-xs">
|
||||
{$i18n.t('Banners')}
|
||||
</div>
|
||||
<AdminSettingField label={$i18n.t('Response Watermark')}>
|
||||
<Textarea
|
||||
className={textareaClass}
|
||||
placeholder={$i18n.t('Enter a watermark for the response. Leave empty for none.')}
|
||||
bind:value={adminConfig.RESPONSE_WATERMARK}
|
||||
/>
|
||||
</AdminSettingField>
|
||||
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded-sm transition"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
if (banners.length === 0 || banners.at(-1).content !== '') {
|
||||
banners = [
|
||||
...banners,
|
||||
{
|
||||
id: uuidv4(),
|
||||
type: '',
|
||||
title: '',
|
||||
content: '',
|
||||
dismissible: true,
|
||||
timestamp: Math.floor(Date.now() / 1000)
|
||||
}
|
||||
];
|
||||
}
|
||||
}}
|
||||
<AdminSettingField
|
||||
label={$i18n.t('WebUI URL')}
|
||||
description={$i18n.t(
|
||||
'Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.'
|
||||
)}
|
||||
>
|
||||
<input
|
||||
class={inputClass}
|
||||
type="text"
|
||||
placeholder={`e.g.) "http://localhost:3000"`}
|
||||
bind:value={adminConfig.WEBUI_URL}
|
||||
/>
|
||||
</AdminSettingField>
|
||||
</AdminSettingSection>
|
||||
|
||||
<Events />
|
||||
|
||||
<AdminSettingSection title={$i18n.t('UI')}>
|
||||
<div>
|
||||
<div class="mb-2 flex w-full items-center justify-between">
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400">{$i18n.t('Banners')}</div>
|
||||
|
||||
<button
|
||||
class="flex size-6 items-center justify-center rounded-lg text-gray-400 transition-colors hover:text-gray-700 dark:text-gray-600 dark:hover:text-gray-300"
|
||||
type="button"
|
||||
aria-label={$i18n.t('Add banner')}
|
||||
on:click={() => {
|
||||
if (banners.length === 0 || banners[banners.length - 1]?.content !== '') {
|
||||
banners = [
|
||||
...banners,
|
||||
{
|
||||
id: uuidv4(),
|
||||
type: '',
|
||||
title: '',
|
||||
content: '',
|
||||
dismissible: true,
|
||||
timestamp: Math.floor(Date.now() / 1000)
|
||||
}
|
||||
];
|
||||
}
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="size-4"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<Banners bind:banners />
|
||||
<path
|
||||
d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<Banners bind:banners />
|
||||
</div>
|
||||
</div>
|
||||
</AdminSettingSection>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end pt-3 text-sm font-normal">
|
||||
<div class="flex justify-end pt-6 text-sm font-normal">
|
||||
<button
|
||||
class="px-3.5 py-1.5 text-sm font-normal bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
|
||||
type="submit"
|
||||
|
||||
@@ -284,21 +284,15 @@
|
||||
</script>
|
||||
|
||||
<form
|
||||
class="flex flex-col h-full justify-between space-y-3 text-sm"
|
||||
class="flex h-full flex-col justify-between text-sm"
|
||||
on:submit|preventDefault={async () => {
|
||||
saveHandler();
|
||||
}}
|
||||
>
|
||||
<div class=" space-y-3 overflow-y-scroll scrollbar-hidden pr-2">
|
||||
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5">
|
||||
{#if config}
|
||||
<div>
|
||||
<div class="mb-3">
|
||||
<div class=" mt-0.5 mb-2.5 text-xs text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('General')}
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-100/20 dark:border-white/[0.025] my-2" />
|
||||
|
||||
<div class="mb-5">
|
||||
<div class="mb-2.5">
|
||||
<div class="flex w-full justify-between items-center">
|
||||
<div class="text-xs pr-2">
|
||||
@@ -312,13 +306,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class=" mt-0.5 mb-2.5 text-xs text-gray-400 dark:text-gray-600">
|
||||
<div class="mb-5">
|
||||
<div class=" mb-2 text-xs text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('Create Image')}
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-100/20 dark:border-white/[0.025] my-2" />
|
||||
|
||||
{#if config.ENABLE_IMAGE_GENERATION}
|
||||
<div class="mb-2.5">
|
||||
<div class="flex w-full justify-between items-center">
|
||||
@@ -331,7 +323,7 @@
|
||||
<Tooltip content={$i18n.t('Enter Model ID')} placement="top-start">
|
||||
<input
|
||||
list="model-list"
|
||||
class=" text-right text-sm bg-transparent outline-hidden max-w-full w-52"
|
||||
class=" text-right text-xs bg-transparent outline-hidden max-w-full w-52"
|
||||
bind:value={config.IMAGE_GENERATION_MODEL}
|
||||
placeholder={$i18n.t('Select a model')}
|
||||
required
|
||||
@@ -356,7 +348,7 @@
|
||||
|
||||
<Tooltip content={$i18n.t('Enter Image Size (e.g. 512x512)')} placement="top-start">
|
||||
<input
|
||||
class=" text-right text-sm bg-transparent outline-hidden max-w-full w-52"
|
||||
class=" text-right text-xs bg-transparent outline-hidden max-w-full w-52"
|
||||
placeholder={$i18n.t('Enter Image Size (e.g. 512x512)')}
|
||||
bind:value={config.IMAGE_SIZE}
|
||||
/>
|
||||
@@ -378,7 +370,7 @@
|
||||
placement="top-start"
|
||||
>
|
||||
<input
|
||||
class=" text-right text-sm bg-transparent outline-hidden"
|
||||
class=" text-right text-xs bg-transparent outline-hidden"
|
||||
placeholder={$i18n.t('Enter Number of Steps (e.g. 50)')}
|
||||
bind:value={config.IMAGE_STEPS}
|
||||
required
|
||||
@@ -410,7 +402,7 @@
|
||||
</div>
|
||||
|
||||
<select
|
||||
class="w-fit pr-8 cursor-pointer rounded-sm px-2 text-xs bg-transparent outline-hidden text-right"
|
||||
class="w-fit pr-8 cursor-pointer rounded-lg px-2 text-xs bg-transparent outline-hidden text-right"
|
||||
bind:value={config.IMAGE_GENERATION_ENGINE}
|
||||
placeholder={$i18n.t('Select Engine')}
|
||||
>
|
||||
@@ -434,7 +426,7 @@
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full text-sm bg-transparent outline-hidden text-right"
|
||||
class="w-full text-xs bg-transparent outline-hidden text-right"
|
||||
placeholder={$i18n.t('API Base URL')}
|
||||
bind:value={config.IMAGES_OPENAI_API_BASE_URL}
|
||||
/>
|
||||
@@ -475,7 +467,7 @@
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full text-sm bg-transparent outline-hidden text-right"
|
||||
class="w-full text-xs bg-transparent outline-hidden text-right"
|
||||
placeholder={$i18n.t('API Version')}
|
||||
bind:value={config.IMAGES_OPENAI_API_VERSION}
|
||||
/>
|
||||
@@ -495,7 +487,7 @@
|
||||
<div class="mt-1.5 flex w-full">
|
||||
<div class="flex-1 mr-2">
|
||||
<Textarea
|
||||
className="rounded-lg w-full py-2 px-3 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
className="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors placeholder:text-gray-300 focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:placeholder:text-gray-700 dark:focus:border-blue-500"
|
||||
bind:value={config.IMAGES_OPENAI_API_PARAMS}
|
||||
placeholder={$i18n.t('Enter additional parameters in JSON format')}
|
||||
minSize={100}
|
||||
@@ -515,7 +507,7 @@
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1 mr-2">
|
||||
<input
|
||||
class="w-full text-sm bg-transparent outline-hidden text-right"
|
||||
class="w-full text-xs bg-transparent outline-hidden text-right"
|
||||
placeholder={$i18n.t('Enter URL (e.g. http://127.0.0.1:7860/)')}
|
||||
bind:value={config.AUTOMATIC1111_BASE_URL}
|
||||
/>
|
||||
@@ -609,7 +601,7 @@
|
||||
<div class="mt-1.5 flex w-full">
|
||||
<div class="flex-1 mr-2">
|
||||
<Textarea
|
||||
className="rounded-lg w-full py-2 px-3 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
className="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors placeholder:text-gray-300 focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:placeholder:text-gray-700 dark:focus:border-blue-500"
|
||||
bind:value={config.AUTOMATIC1111_PARAMS}
|
||||
placeholder={$i18n.t('Enter additional parameters in JSON format')}
|
||||
minSize={100}
|
||||
@@ -629,7 +621,7 @@
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1 mr-2">
|
||||
<input
|
||||
class="w-full text-sm bg-transparent outline-hidden text-right"
|
||||
class="w-full text-xs bg-transparent outline-hidden text-right"
|
||||
placeholder={$i18n.t('Enter URL (e.g. http://127.0.0.1:7860/)')}
|
||||
bind:value={config.COMFYUI_BASE_URL}
|
||||
/>
|
||||
@@ -759,7 +751,7 @@
|
||||
/>
|
||||
<!-- {#if config.COMFYUI_WORKFLOW}
|
||||
<Textarea
|
||||
class="w-full rounded-lg my-1 py-2 px-3 text-xs bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden disabled:text-gray-600 resize-none"
|
||||
className="my-1 w-full resize-none rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors placeholder:text-gray-300 focus:border-blue-400 disabled:text-gray-600 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:placeholder:text-gray-700 dark:focus:border-blue-500"
|
||||
rows="10"
|
||||
bind:value={config.COMFYUI_WORKFLOW}
|
||||
required
|
||||
@@ -836,7 +828,7 @@
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full text-sm bg-transparent outline-hidden text-right"
|
||||
class="w-full text-xs bg-transparent outline-hidden text-right"
|
||||
placeholder={$i18n.t('API Base URL')}
|
||||
bind:value={config.IMAGES_GEMINI_API_BASE_URL}
|
||||
/>
|
||||
@@ -875,7 +867,7 @@
|
||||
</div>
|
||||
|
||||
<select
|
||||
class="w-fit pr-8 cursor-pointer rounded-sm px-2 text-xs bg-transparent outline-hidden text-right"
|
||||
class="w-fit pr-8 cursor-pointer rounded-lg px-2 text-xs bg-transparent outline-hidden text-right"
|
||||
bind:value={config.IMAGES_GEMINI_ENDPOINT_METHOD}
|
||||
placeholder={$i18n.t('Select Method')}
|
||||
>
|
||||
@@ -887,13 +879,11 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class=" mt-0.5 mb-2.5 text-xs text-gray-400 dark:text-gray-600">
|
||||
<div class="mb-5">
|
||||
<div class=" mb-2 text-xs text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('Edit Image')}
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-100/20 dark:border-white/[0.025] my-2" />
|
||||
|
||||
<div class="mb-2.5">
|
||||
<div class="flex w-full justify-between items-center">
|
||||
<div class="text-xs pr-2">
|
||||
@@ -918,7 +908,7 @@
|
||||
<Tooltip content={$i18n.t('Enter Model ID')} placement="top-start">
|
||||
<input
|
||||
list="model-list"
|
||||
class="text-right text-sm bg-transparent outline-hidden max-w-full w-52"
|
||||
class="text-right text-xs bg-transparent outline-hidden max-w-full w-52"
|
||||
bind:value={config.IMAGE_EDIT_MODEL}
|
||||
placeholder={$i18n.t('Select a model')}
|
||||
/>
|
||||
@@ -942,7 +932,7 @@
|
||||
|
||||
<Tooltip content={$i18n.t('Enter Image Size (e.g. 512x512)')} placement="top-start">
|
||||
<input
|
||||
class="text-right text-sm bg-transparent outline-hidden max-w-full w-52"
|
||||
class="text-right text-xs bg-transparent outline-hidden max-w-full w-52"
|
||||
placeholder={$i18n.t('Enter Image Size (e.g. 512x512)')}
|
||||
bind:value={config.IMAGE_EDIT_SIZE}
|
||||
/>
|
||||
@@ -960,7 +950,7 @@
|
||||
</div>
|
||||
|
||||
<select
|
||||
class="w-fit pr-8 cursor-pointer rounded-sm px-2 text-xs bg-transparent outline-hidden text-right"
|
||||
class="w-fit pr-8 cursor-pointer rounded-lg px-2 text-xs bg-transparent outline-hidden text-right"
|
||||
bind:value={config.IMAGE_EDIT_ENGINE}
|
||||
placeholder={$i18n.t('Select Engine')}
|
||||
>
|
||||
@@ -983,7 +973,7 @@
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full text-sm bg-transparent outline-hidden text-right"
|
||||
class="w-full text-xs bg-transparent outline-hidden text-right"
|
||||
placeholder={$i18n.t('API Base URL')}
|
||||
bind:value={config.IMAGES_EDIT_OPENAI_API_BASE_URL}
|
||||
/>
|
||||
@@ -1024,7 +1014,7 @@
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full text-sm bg-transparent outline-hidden text-right"
|
||||
class="w-full text-xs bg-transparent outline-hidden text-right"
|
||||
placeholder={$i18n.t('API Version')}
|
||||
bind:value={config.IMAGES_EDIT_OPENAI_API_VERSION}
|
||||
/>
|
||||
@@ -1044,7 +1034,7 @@
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1 mr-2">
|
||||
<input
|
||||
class="w-full text-sm bg-transparent outline-hidden text-right"
|
||||
class="w-full text-xs bg-transparent outline-hidden text-right"
|
||||
placeholder={$i18n.t('Enter URL (e.g. http://127.0.0.1:7860/)')}
|
||||
bind:value={config.IMAGES_EDIT_COMFYUI_BASE_URL}
|
||||
/>
|
||||
@@ -1243,7 +1233,7 @@
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full text-sm bg-transparent outline-hidden text-right"
|
||||
class="w-full text-xs bg-transparent outline-hidden text-right"
|
||||
placeholder={$i18n.t('API Base URL')}
|
||||
bind:value={config.IMAGES_EDIT_GEMINI_API_BASE_URL}
|
||||
/>
|
||||
@@ -1278,7 +1268,7 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end pt-3 text-sm font-normal">
|
||||
<div class="flex justify-end pt-6 text-sm font-normal">
|
||||
<button
|
||||
class="px-3.5 py-1.5 text-sm font-normal bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full flex items-center gap-2 whitespace-nowrap {loading
|
||||
? ' cursor-not-allowed'
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
import AddToolServerModal from '$lib/components/AddToolServerModal.svelte';
|
||||
import AddTerminalServerModal from '$lib/components/AddTerminalServerModal.svelte';
|
||||
import ExternalKnowledge from './ExternalKnowledge.svelte';
|
||||
import AdminSettingSection from './AdminSettingSection.svelte';
|
||||
|
||||
import {
|
||||
getToolServerConnections,
|
||||
@@ -155,171 +156,159 @@
|
||||
/>
|
||||
|
||||
<form
|
||||
class="flex flex-col h-full justify-between text-sm"
|
||||
class="flex h-full flex-col justify-between text-sm"
|
||||
on:submit|preventDefault={() => {
|
||||
updateHandler();
|
||||
}}
|
||||
>
|
||||
<div class=" flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5 -mr-1.5">
|
||||
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5">
|
||||
{#if servers !== null}
|
||||
<div class="">
|
||||
<div class="mb-3">
|
||||
<div class=" mt-0.5 mb-2.5 text-xs text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('Tools')}
|
||||
<AdminSettingSection title={$i18n.t('Tools')} first>
|
||||
<div>
|
||||
<div class="mb-2 flex items-center justify-between">
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('External Tool Servers')}
|
||||
</div>
|
||||
|
||||
<Tooltip content={$i18n.t(`Add Connection`)}>
|
||||
<button
|
||||
class="flex size-6 items-center justify-center rounded-lg text-gray-400 transition-colors hover:bg-black/5 hover:text-gray-900 dark:text-gray-600 dark:hover:bg-white/5 dark:hover:text-white"
|
||||
on:click={() => {
|
||||
showConnectionModal = true;
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<Plus />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-100/20 dark:border-white/[0.025] my-2" />
|
||||
|
||||
<div class="mb-2.5 flex flex-col w-full justify-between">
|
||||
<div class="flex justify-between items-center mb-0.5">
|
||||
<div class="font-normal">{$i18n.t('External Tool Servers')}</div>
|
||||
|
||||
<Tooltip content={$i18n.t(`Add Connection`)}>
|
||||
<button
|
||||
class="px-1"
|
||||
on:click={() => {
|
||||
showConnectionModal = true;
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<Plus />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
{#each servers ?? [] as server, idx}
|
||||
<Connection
|
||||
bind:connection={server}
|
||||
onSubmit={() => {
|
||||
updateHandler();
|
||||
}}
|
||||
onDelete={() => {
|
||||
servers = (servers ?? []).filter((_, i) => i !== idx);
|
||||
updateHandler();
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
{#if (servers ?? []).length === 0}
|
||||
<div class="text-xs text-gray-400 dark:text-gray-500">
|
||||
{$i18n.t('No tool server connections configured.')}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="my-1.5">
|
||||
<div class="text-xs text-gray-500">
|
||||
{$i18n.t('Connect to your own OpenAPI compatible external tool servers.')}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
{#each servers ?? [] as server, idx}
|
||||
<Connection
|
||||
bind:connection={server}
|
||||
onSubmit={() => {
|
||||
updateHandler();
|
||||
}}
|
||||
onDelete={() => {
|
||||
servers = (servers ?? []).filter((_, i) => i !== idx);
|
||||
updateHandler();
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="mt-4 mb-2.5 flex flex-col w-full">
|
||||
<div class="flex justify-between items-center mb-1">
|
||||
<div class="font-normal">{$i18n.t('Open Terminal')}</div>
|
||||
|
||||
<Tooltip content={$i18n.t('Add Connection')}>
|
||||
<button
|
||||
class="px-1"
|
||||
on:click={() => {
|
||||
editTerminalIdx = null;
|
||||
showAddTerminalModal = true;
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<Plus />
|
||||
</button>
|
||||
</Tooltip>
|
||||
{#if (servers ?? []).length === 0}
|
||||
<div class="text-[0.6875rem] text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('No tool server connections configured.')}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="mt-1 text-[0.6875rem] text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('Connect to your own OpenAPI compatible external tool servers.')}
|
||||
</div>
|
||||
</div>
|
||||
</AdminSettingSection>
|
||||
|
||||
<AdminSettingSection title={$i18n.t('Terminal')}>
|
||||
<div>
|
||||
<div class="mb-2 flex items-center justify-between">
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400">{$i18n.t('Open Terminal')}</div>
|
||||
|
||||
<Tooltip content={$i18n.t('Add Connection')}>
|
||||
<button
|
||||
class="flex size-6 items-center justify-center rounded-lg text-gray-400 transition-colors hover:bg-black/5 hover:text-gray-900 dark:text-gray-600 dark:hover:bg-white/5 dark:hover:text-white"
|
||||
on:click={() => {
|
||||
editTerminalIdx = null;
|
||||
showAddTerminalModal = true;
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<Plus />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1.5">
|
||||
{#each terminalConnections as connection, idx}
|
||||
<div class="flex w-full gap-2 items-center">
|
||||
<Tooltip className="w-full relative" content={''} placement="top-start">
|
||||
<div class="flex w-full">
|
||||
<div
|
||||
class="flex-1 relative flex gap-1.5 items-center {connection?.enabled ===
|
||||
false
|
||||
? 'opacity-50'
|
||||
: ''}"
|
||||
>
|
||||
<Tooltip content={$i18n.t('Terminal')}>
|
||||
<Cloud className="size-4" strokeWidth="1.5" />
|
||||
</Tooltip>
|
||||
|
||||
<div class="flex flex-col gap-1.5">
|
||||
{#each terminalConnections as connection, idx}
|
||||
<div class="flex w-full gap-2 items-center">
|
||||
<Tooltip className="w-full relative" content={''} placement="top-start">
|
||||
<div class="flex w-full">
|
||||
<div
|
||||
class="flex-1 relative flex gap-1.5 items-center {connection?.enabled ===
|
||||
false
|
||||
? 'opacity-50'
|
||||
: ''}"
|
||||
class="outline-hidden w-full bg-transparent text-xs text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
<Tooltip content={$i18n.t('Terminal')}>
|
||||
<Cloud className="size-4" strokeWidth="1.5" />
|
||||
</Tooltip>
|
||||
|
||||
<div class="outline-hidden w-full bg-transparent text-sm">
|
||||
{connection.name || connection.url || $i18n.t('New Terminal')}
|
||||
</div>
|
||||
{connection.name || connection.url || $i18n.t('New Terminal')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
<div class="flex gap-1 items-center">
|
||||
<Tooltip content={$i18n.t('Configure')}>
|
||||
<button
|
||||
class="self-center p-1 bg-transparent hover:bg-black/5 dark:hover:bg-white/5 rounded-lg transition"
|
||||
on:click={() => {
|
||||
editTerminalIdx = idx;
|
||||
showAddTerminalModal = true;
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<Cog6 />
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
||||
<div class="flex gap-1 items-center">
|
||||
<Tooltip content={$i18n.t('Configure')}>
|
||||
<button
|
||||
class="self-center p-1 bg-transparent hover:bg-gray-100 dark:hover:bg-gray-850 rounded-lg transition"
|
||||
on:click={() => {
|
||||
editTerminalIdx = idx;
|
||||
showAddTerminalModal = true;
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<Cog6 />
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip
|
||||
content={connection?.enabled !== false
|
||||
? $i18n.t('Enabled')
|
||||
: $i18n.t('Disabled')}
|
||||
>
|
||||
<Switch
|
||||
state={connection?.enabled !== false}
|
||||
on:change={() => {
|
||||
terminalConnections = terminalConnections.map((c, i) =>
|
||||
i === idx ? { ...c, enabled: !(c?.enabled !== false) } : c
|
||||
);
|
||||
saveTerminalServers();
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Tooltip
|
||||
content={connection?.enabled !== false
|
||||
? $i18n.t('Enabled')
|
||||
: $i18n.t('Disabled')}
|
||||
>
|
||||
<Switch
|
||||
state={connection?.enabled !== false}
|
||||
on:change={() => {
|
||||
terminalConnections = terminalConnections.map((c, i) =>
|
||||
i === idx ? { ...c, enabled: !(c?.enabled !== false) } : c
|
||||
);
|
||||
saveTerminalServers();
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
{#if terminalConnections.length === 0}
|
||||
<div class="text-xs text-gray-400 dark:text-gray-500">
|
||||
{$i18n.t('No terminal connections configured.')}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="mt-1.5">
|
||||
<div class="text-xs text-gray-500">
|
||||
{$i18n.t(
|
||||
'Connect to Open Terminal instances. All users will have access to file browsing and terminal tools through these servers.'
|
||||
)}
|
||||
</div>
|
||||
<div class="text-xs text-gray-600 dark:text-gray-300 mt-1">
|
||||
<a
|
||||
class="underline"
|
||||
href="https://github.com/open-webui/open-terminal"
|
||||
target="_blank">{$i18n.t('Learn more about Open Terminal')} ↗</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="mt-8 mb-2.5 text-xs text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('Knowledge')}
|
||||
{#if terminalConnections.length === 0}
|
||||
<div class="text-[0.6875rem] text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('No terminal connections configured.')}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="mt-1 text-[0.6875rem] text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t(
|
||||
'Connect to Open Terminal instances. All users will have access to file browsing and terminal tools through these servers.'
|
||||
)}
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-100/20 dark:border-white/[0.025] my-2" />
|
||||
|
||||
<ExternalKnowledge />
|
||||
<a
|
||||
class="mt-0.5 block text-[0.6875rem] text-gray-500 underline hover:text-gray-700 dark:text-gray-500 dark:hover:text-gray-300"
|
||||
href="https://github.com/open-webui/open-terminal"
|
||||
target="_blank">{$i18n.t('Learn more about Open Terminal')} ↗</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</AdminSettingSection>
|
||||
|
||||
<AdminSettingSection title={$i18n.t('Knowledge')}>
|
||||
<ExternalKnowledge />
|
||||
</AdminSettingSection>
|
||||
{:else}
|
||||
<div class="flex h-full justify-center">
|
||||
<div class="my-auto">
|
||||
@@ -329,7 +318,7 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end pt-3 text-sm font-normal">
|
||||
<div class="flex justify-end pt-6 text-sm font-normal">
|
||||
<button
|
||||
class="px-3.5 py-1.5 text-sm font-normal bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
|
||||
type="submit"
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
const i18n: any = getContext('i18n');
|
||||
|
||||
let taskConfig = {
|
||||
TASK_MODEL: '',
|
||||
@@ -55,6 +55,8 @@
|
||||
let baseModels = null;
|
||||
|
||||
let models = null;
|
||||
const textareaClass =
|
||||
'w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors placeholder:text-gray-300 focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:placeholder:text-gray-700 dark:focus:border-blue-500';
|
||||
|
||||
const init = async () => {
|
||||
try {
|
||||
@@ -100,20 +102,18 @@
|
||||
|
||||
{#if models !== null && taskConfig && chatConfig}
|
||||
<form
|
||||
class="flex flex-col h-full justify-between space-y-3 text-sm"
|
||||
class="flex h-full flex-col justify-between text-sm"
|
||||
on:submit|preventDefault={() => {
|
||||
updateInterfaceHandler();
|
||||
dispatch('save');
|
||||
}}
|
||||
>
|
||||
<div class=" flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5 -mr-1.5">
|
||||
<div class="mb-3.5">
|
||||
<div class=" mt-0.5 mb-2.5 text-xs text-gray-400 dark:text-gray-600">
|
||||
<div class=" flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5">
|
||||
<div class="mb-5">
|
||||
<div class=" mb-2 text-xs text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('Tasks')}
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-100/20 dark:border-white/[0.025] my-2" />
|
||||
|
||||
<div class=" mb-2 font-normal flex items-center">
|
||||
<div class=" text-xs mr-1">{$i18n.t('Task Model')}</div>
|
||||
<Tooltip
|
||||
@@ -142,7 +142,7 @@
|
||||
<div class="flex-1">
|
||||
<div class=" text-xs mb-1">{$i18n.t('Local Task Model')}</div>
|
||||
<select
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
bind:value={taskConfig.TASK_MODEL}
|
||||
placeholder={$i18n.t('Select a model')}
|
||||
on:change={() => {
|
||||
@@ -185,7 +185,7 @@
|
||||
<div class="flex-1">
|
||||
<div class=" text-xs mb-1">{$i18n.t('External Task Model')}</div>
|
||||
<select
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
bind:value={taskConfig.TASK_MODEL_EXTERNAL}
|
||||
placeholder={$i18n.t('Select a model')}
|
||||
on:change={() => {
|
||||
@@ -226,10 +226,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-100/20 dark:border-white/[0.025] my-3" />
|
||||
|
||||
<div class="mb-2.5 flex w-full items-center justify-between">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Context Compaction')}
|
||||
</div>
|
||||
|
||||
@@ -238,7 +236,9 @@
|
||||
|
||||
{#if chatConfig.ENABLE_CONTEXT_COMPACTION}
|
||||
<div class="mb-2.5">
|
||||
<div class=" mb-1 text-xs font-normal">{$i18n.t('Token Threshold')}</div>
|
||||
<div class=" mb-1 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Token Threshold')}
|
||||
</div>
|
||||
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
@@ -250,14 +250,14 @@
|
||||
type="number"
|
||||
min="1"
|
||||
step="1"
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
bind:value={chatConfig.CONTEXT_COMPACTION_TOKEN_THRESHOLD}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div class="mb-2.5">
|
||||
<div class=" mb-1 text-xs font-normal">{$i18n.t('Token Cap')}</div>
|
||||
<div class=" mb-1 text-xs text-gray-600 dark:text-gray-400">{$i18n.t('Token Cap')}</div>
|
||||
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
@@ -269,20 +269,23 @@
|
||||
type="number"
|
||||
min="1"
|
||||
step="1"
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
bind:value={chatConfig.CONTEXT_COMPACTION_TOKEN_CAP}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div class="mb-2.5">
|
||||
<div class=" mb-1 text-xs font-normal">{$i18n.t('Context Compaction Prompt')}</div>
|
||||
<div class=" mb-1 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Context Compaction Prompt')}
|
||||
</div>
|
||||
|
||||
<Tooltip
|
||||
content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
|
||||
placement="top-start"
|
||||
>
|
||||
<Textarea
|
||||
className={textareaClass}
|
||||
bind:value={chatConfig.CONTEXT_COMPACTION_PROMPT_TEMPLATE}
|
||||
placeholder={$i18n.t(
|
||||
'Leave empty to use the default prompt, or enter a custom prompt'
|
||||
@@ -300,10 +303,8 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<hr class=" border-gray-100/20 dark:border-white/[0.025] my-3" />
|
||||
|
||||
<div class="mb-2.5 flex w-full items-center justify-between">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Title Generation')}
|
||||
</div>
|
||||
|
||||
@@ -312,13 +313,16 @@
|
||||
|
||||
{#if taskConfig.ENABLE_TITLE_GENERATION}
|
||||
<div class="mb-2.5">
|
||||
<div class=" mb-1 text-xs font-normal">{$i18n.t('Title Generation Prompt')}</div>
|
||||
<div class=" mb-1 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Title Generation Prompt')}
|
||||
</div>
|
||||
|
||||
<Tooltip
|
||||
content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
|
||||
placement="top-start"
|
||||
>
|
||||
<Textarea
|
||||
className={textareaClass}
|
||||
bind:value={taskConfig.TITLE_GENERATION_PROMPT_TEMPLATE}
|
||||
placeholder={$i18n.t(
|
||||
'Leave empty to use the default prompt, or enter a custom prompt'
|
||||
@@ -329,7 +333,7 @@
|
||||
{/if}
|
||||
|
||||
<div class="mb-2.5 flex w-full items-center justify-between">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Voice Mode Prompt')}
|
||||
</div>
|
||||
|
||||
@@ -338,13 +342,16 @@
|
||||
|
||||
{#if taskConfig.ENABLE_VOICE_MODE_PROMPT}
|
||||
<div class="mb-2.5">
|
||||
<div class=" mb-1 text-xs font-normal">{$i18n.t('Prompt Template')}</div>
|
||||
<div class=" mb-1 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Prompt Template')}
|
||||
</div>
|
||||
|
||||
<Tooltip
|
||||
content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
|
||||
placement="top-start"
|
||||
>
|
||||
<Textarea
|
||||
className={textareaClass}
|
||||
bind:value={taskConfig.VOICE_MODE_PROMPT_TEMPLATE}
|
||||
placeholder={$i18n.t(
|
||||
'Leave empty to use the default prompt, or enter a custom prompt'
|
||||
@@ -355,7 +362,7 @@
|
||||
{/if}
|
||||
|
||||
<div class="mb-2.5 flex w-full items-center justify-between">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Follow Up Generation')}
|
||||
</div>
|
||||
|
||||
@@ -364,13 +371,16 @@
|
||||
|
||||
{#if taskConfig.ENABLE_FOLLOW_UP_GENERATION}
|
||||
<div class="mb-2.5">
|
||||
<div class=" mb-1 text-xs font-normal">{$i18n.t('Follow Up Generation Prompt')}</div>
|
||||
<div class=" mb-1 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Follow Up Generation Prompt')}
|
||||
</div>
|
||||
|
||||
<Tooltip
|
||||
content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
|
||||
placement="top-start"
|
||||
>
|
||||
<Textarea
|
||||
className={textareaClass}
|
||||
bind:value={taskConfig.FOLLOW_UP_GENERATION_PROMPT_TEMPLATE}
|
||||
placeholder={$i18n.t(
|
||||
'Leave empty to use the default prompt, or enter a custom prompt'
|
||||
@@ -381,7 +391,7 @@
|
||||
{/if}
|
||||
|
||||
<div class="mb-2.5 flex w-full items-center justify-between">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Tags Generation')}
|
||||
</div>
|
||||
|
||||
@@ -390,13 +400,16 @@
|
||||
|
||||
{#if taskConfig.ENABLE_TAGS_GENERATION}
|
||||
<div class="mb-2.5">
|
||||
<div class=" mb-1 text-xs font-normal">{$i18n.t('Tags Generation Prompt')}</div>
|
||||
<div class=" mb-1 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Tags Generation Prompt')}
|
||||
</div>
|
||||
|
||||
<Tooltip
|
||||
content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
|
||||
placement="top-start"
|
||||
>
|
||||
<Textarea
|
||||
className={textareaClass}
|
||||
bind:value={taskConfig.TAGS_GENERATION_PROMPT_TEMPLATE}
|
||||
placeholder={$i18n.t(
|
||||
'Leave empty to use the default prompt, or enter a custom prompt'
|
||||
@@ -407,7 +420,7 @@
|
||||
{/if}
|
||||
|
||||
<div class="mb-2.5 flex w-full items-center justify-between">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Retrieval Query Generation')}
|
||||
</div>
|
||||
|
||||
@@ -415,7 +428,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mb-2.5 flex w-full items-center justify-between">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Web Search Query Generation')}
|
||||
</div>
|
||||
|
||||
@@ -423,13 +436,16 @@
|
||||
</div>
|
||||
|
||||
<div class="mb-2.5">
|
||||
<div class=" mb-1 text-xs font-normal">{$i18n.t('Query Generation Prompt')}</div>
|
||||
<div class=" mb-1 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Query Generation Prompt')}
|
||||
</div>
|
||||
|
||||
<Tooltip
|
||||
content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
|
||||
placement="top-start"
|
||||
>
|
||||
<Textarea
|
||||
className={textareaClass}
|
||||
bind:value={taskConfig.QUERY_GENERATION_PROMPT_TEMPLATE}
|
||||
placeholder={$i18n.t(
|
||||
'Leave empty to use the default prompt, or enter a custom prompt'
|
||||
@@ -439,7 +455,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mb-2.5 flex w-full items-center justify-between">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Autocomplete Generation')}
|
||||
</div>
|
||||
|
||||
@@ -450,7 +466,7 @@
|
||||
|
||||
{#if taskConfig.ENABLE_AUTOCOMPLETE_GENERATION}
|
||||
<div class="mb-2.5">
|
||||
<div class=" mb-1 text-xs font-normal">
|
||||
<div class=" mb-1 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Autocomplete Generation Input Max Length')}
|
||||
</div>
|
||||
|
||||
@@ -466,13 +482,16 @@
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div class="mb-2.5">
|
||||
<div class=" mb-1 text-xs font-normal">{$i18n.t('Autocomplete Generation Prompt')}</div>
|
||||
<div class=" mb-1 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Autocomplete Generation Prompt')}
|
||||
</div>
|
||||
|
||||
<Tooltip
|
||||
content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
|
||||
placement="top-start"
|
||||
>
|
||||
<Textarea
|
||||
className={textareaClass}
|
||||
bind:value={taskConfig.AUTOCOMPLETE_GENERATION_PROMPT_TEMPLATE}
|
||||
placeholder={$i18n.t(
|
||||
'Leave empty to use the default prompt, or enter a custom prompt'
|
||||
@@ -483,13 +502,16 @@
|
||||
{/if}
|
||||
|
||||
<div class="mb-2.5">
|
||||
<div class=" mb-1 text-xs font-normal">{$i18n.t('Image Prompt Generation Prompt')}</div>
|
||||
<div class=" mb-1 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Image Prompt Generation Prompt')}
|
||||
</div>
|
||||
|
||||
<Tooltip
|
||||
content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
|
||||
placement="top-start"
|
||||
>
|
||||
<Textarea
|
||||
className={textareaClass}
|
||||
bind:value={taskConfig.IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE}
|
||||
placeholder={$i18n.t(
|
||||
'Leave empty to use the default prompt, or enter a custom prompt'
|
||||
@@ -499,13 +521,16 @@
|
||||
</div>
|
||||
|
||||
<div class="mb-2.5">
|
||||
<div class=" mb-1 text-xs font-normal">{$i18n.t('Tools Function Calling Prompt')}</div>
|
||||
<div class=" mb-1 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Tools Function Calling Prompt')}
|
||||
</div>
|
||||
|
||||
<Tooltip
|
||||
content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
|
||||
placement="top-start"
|
||||
>
|
||||
<Textarea
|
||||
className={textareaClass}
|
||||
bind:value={taskConfig.TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE}
|
||||
placeholder={$i18n.t(
|
||||
'Leave empty to use the default prompt, or enter a custom prompt'
|
||||
@@ -516,7 +541,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end text-sm font-normal">
|
||||
<div class="flex justify-end pt-6 text-sm font-normal">
|
||||
<button
|
||||
class="px-3.5 py-1.5 text-sm font-normal bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
|
||||
type="submit"
|
||||
|
||||
@@ -382,14 +382,10 @@
|
||||
{#if selectedModelId === null}
|
||||
<div class="flex flex-col gap-1 mt-1.5 mb-2">
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="flex items-center md:self-center text-xl font-normal px-0.5 gap-2 shrink-0">
|
||||
<div>
|
||||
{$i18n.t('Models')}
|
||||
</div>
|
||||
|
||||
<div class="text-lg font-normal text-gray-500 dark:text-gray-500">
|
||||
{filteredModels.length}
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center md:self-center text-xs text-gray-400 dark:text-gray-600 px-0.5 gap-1.5 shrink-0"
|
||||
>
|
||||
<div>{filteredModels.length}</div>
|
||||
</div>
|
||||
|
||||
<div class="flex w-full justify-end gap-1.5">
|
||||
@@ -430,7 +426,7 @@
|
||||
/>
|
||||
|
||||
<button
|
||||
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-200 transition"
|
||||
class="flex text-xs items-center space-x-1 px-2.5 py-1.5 rounded-lg hover:bg-black/5 dark:hover:bg-white/5 dark:text-gray-200 transition"
|
||||
disabled={modelsImportInProgress}
|
||||
on:click={() => {
|
||||
modelsImportInputElement.click();
|
||||
@@ -445,7 +441,7 @@
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-200 transition"
|
||||
class="flex text-xs items-center space-x-1 px-2.5 py-1.5 rounded-lg hover:bg-black/5 dark:hover:bg-white/5 dark:text-gray-200 transition"
|
||||
on:click={async () => {
|
||||
downloadModels(models);
|
||||
}}
|
||||
@@ -457,7 +453,7 @@
|
||||
{/if}
|
||||
|
||||
<button
|
||||
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-200 transition"
|
||||
class="flex text-xs items-center space-x-1 px-2.5 py-1.5 rounded-lg hover:bg-black/5 dark:hover:bg-white/5 dark:text-gray-200 transition"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
showManageModal = true;
|
||||
@@ -483,10 +479,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="py-2 bg-white dark:bg-gray-900 rounded-3xl border border-gray-100/20 dark:border-white/[0.025]"
|
||||
>
|
||||
<div class="px-3.5 flex flex-1 items-center w-full space-x-2 py-0.5 pb-2">
|
||||
<div class="py-1">
|
||||
<div class="flex flex-1 items-center w-full space-x-2 py-0.5 pb-2">
|
||||
<div class="flex flex-1 items-center">
|
||||
<div class=" self-center ml-1 mr-3">
|
||||
<Search className="size-3.5" />
|
||||
@@ -499,7 +493,7 @@
|
||||
{#if searchValue}
|
||||
<div class="self-center pl-1.5 translate-y-[0.5px] rounded-l-xl bg-transparent">
|
||||
<button
|
||||
class="p-0.5 rounded-full hover:bg-gray-100 dark:hover:bg-gray-900 transition"
|
||||
class="p-0.5 rounded-sm hover:bg-black/5 dark:hover:bg-white/5 transition"
|
||||
on:click={() => {
|
||||
searchValue = '';
|
||||
}}
|
||||
@@ -511,7 +505,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-3 flex w-full items-center bg-transparent overflow-x-auto scrollbar-none">
|
||||
<div class="flex w-full items-center bg-transparent overflow-x-auto scrollbar-none">
|
||||
<div
|
||||
class="flex gap-0.5 w-fit text-center text-sm rounded-full bg-transparent whitespace-nowrap"
|
||||
>
|
||||
@@ -533,7 +527,7 @@
|
||||
<Dropdown>
|
||||
<Tooltip content={$i18n.t('Actions')}>
|
||||
<button
|
||||
class="p-1 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition"
|
||||
class="p-1 rounded-lg hover:bg-black/5 dark:hover:bg-white/5 transition"
|
||||
type="button"
|
||||
>
|
||||
<EllipsisHorizontal className="size-3.5" />
|
||||
@@ -564,7 +558,7 @@
|
||||
<div class="flex items-center">{$i18n.t('Disable All')}</div>
|
||||
</button>
|
||||
|
||||
<hr class="border-gray-100/40 dark:border-white/[0.03] my-1" />
|
||||
<hr class="border-gray-100/20 dark:border-white/[0.02] my-1" />
|
||||
|
||||
<button
|
||||
class="select-none flex w-full gap-2 items-center h-[1.6875rem] px-2 text-[13px] font-normal cursor-pointer hover:bg-gray-50/40 dark:hover:bg-gray-800/40 rounded-xl"
|
||||
@@ -592,7 +586,7 @@
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
||||
<div class="px-3 my-2" id="model-list">
|
||||
<div class="my-2" id="model-list">
|
||||
{#if filteredModels.length > 0}
|
||||
{#each filteredModels.slice((currentPage - 1) * perPage, currentPage * perPage) as model, modelIdx (`${model.id}-${modelIdx}`)}
|
||||
<div
|
||||
@@ -766,8 +760,10 @@
|
||||
{:else}
|
||||
<div class=" w-full h-full flex flex-col justify-center items-center my-16 mb-24">
|
||||
<div class="max-w-md text-center">
|
||||
<div class=" text-3xl mb-3">😕</div>
|
||||
<div class=" text-lg font-normal mb-1">{$i18n.t('No models found')}</div>
|
||||
<div class="mb-2 text-xl">😕</div>
|
||||
<div class="mb-1 text-sm text-gray-700 dark:text-gray-300">
|
||||
{$i18n.t('No models found')}
|
||||
</div>
|
||||
<div class=" text-gray-500 text-center text-xs">
|
||||
{$i18n.t('Try adjusting your search or filter to find what you are looking for.')}
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
<script lang="ts">
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { config, models, settings } from '$lib/stores';
|
||||
import { getContext, onMount, tick } from 'svelte';
|
||||
@@ -20,6 +18,8 @@
|
||||
|
||||
import Spinner from '$lib/components/common/Spinner.svelte';
|
||||
import Switch from '$lib/components/common/Switch.svelte';
|
||||
import AdminSettingField from './AdminSettingField.svelte';
|
||||
import AdminSettingRow from './AdminSettingRow.svelte';
|
||||
|
||||
const i18n: Writable<i18nType> = getContext('i18n');
|
||||
|
||||
@@ -28,18 +28,27 @@
|
||||
let downloading = false;
|
||||
let uploading = false;
|
||||
|
||||
let pipelineFiles;
|
||||
let pipelineFiles: any = null;
|
||||
|
||||
let PIPELINES_LIST = null;
|
||||
let PIPELINES_LIST: any[] | null = null;
|
||||
let selectedPipelinesUrlIdx = '';
|
||||
|
||||
let pipelines = null;
|
||||
let pipelines: any[] | null = null;
|
||||
|
||||
let valves = null;
|
||||
let valves_spec = null;
|
||||
let selectedPipelineIdx = null;
|
||||
let valves: any = null;
|
||||
let valves_spec: any = null;
|
||||
let selectedPipelineIdx: number | null = null;
|
||||
|
||||
let pipelineDownloadUrl = '';
|
||||
const inputClass =
|
||||
'w-full h-7 rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 text-xs text-gray-700 outline-hidden transition-colors placeholder:text-gray-300 focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:placeholder:text-gray-700 dark:focus:border-blue-500';
|
||||
const selectClass =
|
||||
'w-full h-7 rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500';
|
||||
const actionButtonClass =
|
||||
'rounded-lg px-2 py-1 text-xs text-gray-500 transition-colors hover:bg-black/5 hover:text-gray-900 disabled:opacity-50 dark:text-gray-500 dark:hover:bg-white/5 dark:hover:text-white';
|
||||
const iconButtonClass =
|
||||
'flex size-7 items-center justify-center rounded-lg text-gray-400 transition-colors hover:bg-black/5 hover:text-gray-900 disabled:opacity-50 dark:text-gray-600 dark:hover:bg-white/5 dark:hover:text-white';
|
||||
const mutedMessageClass = 'text-xs text-gray-400 dark:text-gray-600';
|
||||
|
||||
const updateHandler = async () => {
|
||||
const pipeline = pipelines[selectedPipelineIdx];
|
||||
@@ -66,7 +75,9 @@
|
||||
models.set(
|
||||
await getModels(
|
||||
localStorage.token,
|
||||
$config?.features?.enable_direct_connections && ($settings?.directConnections ?? null)
|
||||
$config?.features?.enable_direct_connections
|
||||
? ($settings?.directConnections ?? null)
|
||||
: null
|
||||
)
|
||||
);
|
||||
saveHandler();
|
||||
@@ -133,7 +144,9 @@
|
||||
models.set(
|
||||
await getModels(
|
||||
localStorage.token,
|
||||
$config?.features?.enable_direct_connections && ($settings?.directConnections ?? null)
|
||||
$config?.features?.enable_direct_connections
|
||||
? ($settings?.directConnections ?? null)
|
||||
: null
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -163,7 +176,9 @@
|
||||
models.set(
|
||||
await getModels(
|
||||
localStorage.token,
|
||||
$config?.features?.enable_direct_connections && ($settings?.directConnections ?? null)
|
||||
$config?.features?.enable_direct_connections
|
||||
? ($settings?.directConnections ?? null)
|
||||
: null
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -197,7 +212,9 @@
|
||||
models.set(
|
||||
await getModels(
|
||||
localStorage.token,
|
||||
$config?.features?.enable_direct_connections && ($settings?.directConnections ?? null)
|
||||
$config?.features?.enable_direct_connections
|
||||
? ($settings?.directConnections ?? null)
|
||||
: null
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -216,346 +233,195 @@
|
||||
</script>
|
||||
|
||||
<form
|
||||
class="flex flex-col h-full justify-between space-y-3 text-sm"
|
||||
class="flex h-full flex-col justify-between text-sm"
|
||||
on:submit|preventDefault={async () => {
|
||||
updateHandler();
|
||||
}}
|
||||
>
|
||||
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5 -mr-1.5">
|
||||
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5">
|
||||
{#if PIPELINES_LIST !== null}
|
||||
<div class="flex w-full justify-between mb-2">
|
||||
<div class=" self-center text-sm font-normal">
|
||||
{$i18n.t('Manage Pipelines')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if PIPELINES_LIST.length > 0}
|
||||
<div class="space-y-1">
|
||||
<div class="flex gap-2">
|
||||
<div class="flex-1">
|
||||
<select
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
bind:value={selectedPipelinesUrlIdx}
|
||||
placeholder={$i18n.t('Select a pipeline url')}
|
||||
on:change={async () => {
|
||||
await tick();
|
||||
await setPipelines();
|
||||
}}
|
||||
<input
|
||||
id="pipelines-upload-input"
|
||||
bind:files={pipelineFiles}
|
||||
type="file"
|
||||
accept=".py"
|
||||
hidden
|
||||
/>
|
||||
|
||||
<div class="flex flex-col gap-2.5">
|
||||
<AdminSettingField label={$i18n.t('Pipeline URL')}>
|
||||
<select
|
||||
class={selectClass}
|
||||
bind:value={selectedPipelinesUrlIdx}
|
||||
placeholder={$i18n.t('Select a pipeline url')}
|
||||
on:change={async () => {
|
||||
await tick();
|
||||
await setPipelines();
|
||||
}}
|
||||
>
|
||||
<option value="" selected disabled class="bg-gray-100 dark:bg-gray-700"
|
||||
>{$i18n.t('Select a pipeline url')}</option
|
||||
>
|
||||
<option value="" selected disabled class="bg-gray-100 dark:bg-gray-700"
|
||||
>{$i18n.t('Select a pipeline url')}</option
|
||||
|
||||
{#each PIPELINES_LIST as pipelines}
|
||||
<option value={pipelines.idx.toString()} class="bg-gray-100 dark:bg-gray-700"
|
||||
>{pipelines.url}</option
|
||||
>
|
||||
{/each}
|
||||
</select>
|
||||
</AdminSettingField>
|
||||
|
||||
{#each PIPELINES_LIST as pipelines, idx}
|
||||
<option value={pipelines.idx.toString()} class="bg-gray-100 dark:bg-gray-700"
|
||||
>{pipelines.url}</option
|
||||
>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class=" my-2">
|
||||
<div class=" mb-2 text-sm font-normal">
|
||||
{$i18n.t('Upload Pipeline')}
|
||||
</div>
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1 mr-2">
|
||||
<input
|
||||
id="pipelines-upload-input"
|
||||
bind:files={pipelineFiles}
|
||||
type="file"
|
||||
accept=".py"
|
||||
hidden
|
||||
/>
|
||||
|
||||
<AdminSettingField label={$i18n.t('Upload Pipeline')}>
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
class="w-full text-sm font-normal py-2 bg-transparent hover:bg-gray-100 border border-dashed dark:border-gray-850 dark:hover:bg-gray-850 text-center rounded-xl"
|
||||
class="h-7 flex-1 rounded-lg border border-dashed border-gray-100/50 bg-transparent px-2 text-left text-xs text-gray-500 transition-colors hover:bg-black/5 hover:text-gray-900 dark:border-white/[0.04] dark:text-gray-500 dark:hover:bg-white/5 dark:hover:text-white"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
document.getElementById('pipelines-upload-input')?.click();
|
||||
}}
|
||||
>
|
||||
{#if pipelineFiles}
|
||||
{pipelineFiles.length > 0 ? `${pipelineFiles.length}` : ''} pipeline(s) selected.
|
||||
{pipelineFiles.length > 0 ? `${pipelineFiles.length}` : ''} pipeline(s) selected
|
||||
{:else}
|
||||
{$i18n.t('Click here to select a py file.')}
|
||||
{$i18n.t('Select a .py file')}
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
<button
|
||||
class={actionButtonClass}
|
||||
on:click={() => {
|
||||
uploadPipelineHandler();
|
||||
}}
|
||||
disabled={uploading}
|
||||
type="button"
|
||||
>
|
||||
{uploading ? $i18n.t('Uploading') : $i18n.t('Upload')}
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
|
||||
on:click={() => {
|
||||
uploadPipelineHandler();
|
||||
}}
|
||||
disabled={uploading}
|
||||
type="button"
|
||||
>
|
||||
{#if uploading}
|
||||
<div class="self-center">
|
||||
<svg
|
||||
class=" w-4 h-4"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<style>
|
||||
.spinner_ajPY {
|
||||
transform-origin: center;
|
||||
animation: spinner_AtaB 0.75s infinite linear;
|
||||
}
|
||||
</AdminSettingField>
|
||||
|
||||
@keyframes spinner_AtaB {
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<path
|
||||
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
|
||||
opacity=".25"
|
||||
/>
|
||||
<path
|
||||
d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
|
||||
class="spinner_ajPY"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
{:else}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="size-4"
|
||||
>
|
||||
<path
|
||||
d="M7.25 10.25a.75.75 0 0 0 1.5 0V4.56l2.22 2.22a.75.75 0 1 0 1.06-1.06l-3.5-3.5a.75.75 0 0 0-1.06 0l-3.5 3.5a.75.75 0 0 0 1.06 1.06l2.22-2.22v5.69Z"
|
||||
/>
|
||||
<path
|
||||
d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class=" my-2">
|
||||
<div class=" mb-2 text-sm font-normal">
|
||||
{$i18n.t('Install from Github URL')}
|
||||
</div>
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1 mr-2">
|
||||
<AdminSettingField
|
||||
label={$i18n.t('GitHub URL')}
|
||||
description={`${$i18n.t('Pipelines are a plugin system with arbitrary code execution.')} ${$i18n.t("Don't fetch random pipelines from sources you don't trust.")}`}
|
||||
>
|
||||
<div class="flex gap-2">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
placeholder={$i18n.t('Enter Github Raw URL')}
|
||||
class={inputClass}
|
||||
placeholder={$i18n.t('Enter GitHub Raw URL')}
|
||||
bind:value={pipelineDownloadUrl}
|
||||
/>
|
||||
<button
|
||||
class={actionButtonClass}
|
||||
on:click={() => {
|
||||
addPipelineHandler();
|
||||
}}
|
||||
disabled={downloading}
|
||||
type="button"
|
||||
>
|
||||
{downloading ? $i18n.t('Installing') : $i18n.t('Install')}
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
|
||||
on:click={() => {
|
||||
addPipelineHandler();
|
||||
}}
|
||||
disabled={downloading}
|
||||
type="button"
|
||||
>
|
||||
{#if downloading}
|
||||
<div class="self-center">
|
||||
<svg
|
||||
class=" w-4 h-4"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<style>
|
||||
.spinner_ajPY {
|
||||
transform-origin: center;
|
||||
animation: spinner_AtaB 0.75s infinite linear;
|
||||
}
|
||||
</AdminSettingField>
|
||||
|
||||
@keyframes spinner_AtaB {
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<path
|
||||
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
|
||||
opacity=".25"
|
||||
/>
|
||||
<path
|
||||
d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
|
||||
class="spinner_ajPY"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
{:else}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
d="M8.75 2.75a.75.75 0 0 0-1.5 0v5.69L5.03 6.22a.75.75 0 0 0-1.06 1.06l3.5 3.5a.75.75 0 0 0 1.06 0l3.5-3.5a.75.75 0 0 0-1.06-1.06L8.75 8.44V2.75Z"
|
||||
/>
|
||||
<path
|
||||
d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mt-2 text-xs text-gray-500">
|
||||
<span class=" font-normal dark:text-gray-200">{$i18n.t('Warning:')}</span>
|
||||
{$i18n.t('Pipelines are a plugin system with arbitrary code execution —')}
|
||||
<span class=" font-normal dark:text-gray-400"
|
||||
>{$i18n.t("don't fetch random pipelines from sources you don't trust.")}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="border-gray-100/20 dark:border-white/[0.025] my-3 w-full" />
|
||||
|
||||
{#if pipelines !== null}
|
||||
{#if pipelines.length > 0}
|
||||
<div class="flex w-full justify-between mb-2">
|
||||
<div class=" self-center text-sm font-normal">
|
||||
{$i18n.t('Pipelines Valves')}
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
{#if pipelines.length > 0}
|
||||
{#if pipelines !== null}
|
||||
{#if pipelines.length > 0}
|
||||
<AdminSettingField label={$i18n.t('Pipeline')}>
|
||||
<div class="flex gap-2">
|
||||
<div class="flex-1">
|
||||
<select
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
bind:value={selectedPipelineIdx}
|
||||
placeholder={$i18n.t('Select a pipeline')}
|
||||
on:change={async () => {
|
||||
await tick();
|
||||
await getValves(selectedPipelineIdx);
|
||||
}}
|
||||
>
|
||||
{#each pipelines as pipeline, idx}
|
||||
<option value={idx} class="bg-gray-100 dark:bg-gray-700"
|
||||
>{pipeline.name} ({pipeline.type ?? 'pipe'})</option
|
||||
>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
<select
|
||||
class={selectClass}
|
||||
bind:value={selectedPipelineIdx}
|
||||
placeholder={$i18n.t('Select a pipeline')}
|
||||
on:change={async () => {
|
||||
await tick();
|
||||
await getValves(selectedPipelineIdx ?? 0);
|
||||
}}
|
||||
>
|
||||
{#each pipelines as pipeline, idx}
|
||||
<option value={idx} class="bg-gray-100 dark:bg-gray-700">
|
||||
{pipeline.name} ({pipeline.type ?? 'pipe'})
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
|
||||
<button
|
||||
class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
|
||||
class={actionButtonClass}
|
||||
on:click={() => {
|
||||
deletePipelineHandler();
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M5 3.25V4H2.75a.75.75 0 0 0 0 1.5h.3l.815 8.15A1.5 1.5 0 0 0 5.357 15h5.285a1.5 1.5 0 0 0 1.493-1.35l.815-8.15h.3a.75.75 0 0 0 0-1.5H11v-.75A2.25 2.25 0 0 0 8.75 1h-1.5A2.25 2.25 0 0 0 5 3.25Zm2.25-.75a.75.75 0 0 0-.75.75V4h3v-.75a.75.75 0 0 0-.75-.75h-1.5ZM6.05 6a.75.75 0 0 1 .787.713l.275 5.5a.75.75 0 0 1-1.498.075l-.275-5.5A.75.75 0 0 1 6.05 6Zm3.9 0a.75.75 0 0 1 .712.787l-.275 5.5a.75.75 0 0 1-1.498-.075l.275-5.5a.75.75 0 0 1 .786-.711Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
{$i18n.t('Delete')}
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</AdminSettingField>
|
||||
|
||||
<div class="space-y-1">
|
||||
{#if pipelines[selectedPipelineIdx].valves}
|
||||
{#if valves}
|
||||
{#each Object.keys(valves_spec.properties) as property, idx}
|
||||
<div class=" py-0.5 w-full justify-between">
|
||||
<div class="flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
{valves_spec.properties[property].title}
|
||||
</div>
|
||||
{#if pipelines[selectedPipelineIdx ?? 0]?.valves}
|
||||
{#if valves}
|
||||
{#each Object.keys(valves_spec.properties) as property}
|
||||
<div>
|
||||
<AdminSettingRow label={valves_spec.properties[property].title}>
|
||||
<button
|
||||
class={actionButtonClass}
|
||||
type="button"
|
||||
on:click={() => {
|
||||
valves[property] = (valves[property] ?? null) === null ? '' : null;
|
||||
}}
|
||||
>
|
||||
{(valves[property] ?? null) === null
|
||||
? $i18n.t('None')
|
||||
: $i18n.t('Custom')}
|
||||
</button>
|
||||
</AdminSettingRow>
|
||||
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded-sm transition"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
valves[property] = (valves[property] ?? null) === null ? '' : null;
|
||||
}}
|
||||
>
|
||||
{#if (valves[property] ?? null) === null}
|
||||
<span class="ml-2 self-center"> {$i18n.t('None')} </span>
|
||||
{:else}
|
||||
<span class="ml-2 self-center"> {$i18n.t('Custom')} </span>
|
||||
{/if}
|
||||
</button>
|
||||
{#if (valves[property] ?? null) !== null}
|
||||
<div class="mt-1">
|
||||
{#if valves_spec.properties[property]?.enum ?? null}
|
||||
<select class={selectClass} bind:value={valves[property]}>
|
||||
{#each valves_spec.properties[property].enum as option}
|
||||
<option value={option} selected={option === valves[property]}>
|
||||
{option}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
{:else if (valves_spec.properties[property]?.type ?? null) === 'boolean'}
|
||||
<AdminSettingRow
|
||||
label={valves[property] ? $i18n.t('Enabled') : $i18n.t('Disabled')}
|
||||
labelClassName="text-gray-400 dark:text-gray-600"
|
||||
>
|
||||
<Switch bind:state={valves[property]} />
|
||||
</AdminSettingRow>
|
||||
{:else}
|
||||
<input
|
||||
class={inputClass}
|
||||
type="text"
|
||||
placeholder={valves_spec.properties[property].title}
|
||||
bind:value={valves[property]}
|
||||
autocomplete="off"
|
||||
required
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if (valves[property] ?? null) !== null}
|
||||
<!-- {valves[property]} -->
|
||||
<div class="flex mt-0.5 mb-1.5 space-x-2">
|
||||
<div class=" flex-1">
|
||||
{#if valves_spec.properties[property]?.enum ?? null}
|
||||
<select
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
bind:value={valves[property]}
|
||||
>
|
||||
{#each valves_spec.properties[property].enum as option}
|
||||
<option value={option} selected={option === valves[property]}>
|
||||
{option}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
{:else if (valves_spec.properties[property]?.type ?? null) === 'boolean'}
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="text-xs text-gray-500">
|
||||
{valves[property] ? $i18n.t('Enabled') : $i18n.t('Disabled')}
|
||||
</div>
|
||||
|
||||
<div class=" pr-2">
|
||||
<Switch bind:state={valves[property]} />
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
type="text"
|
||||
placeholder={valves_spec.properties[property].title}
|
||||
bind:value={valves[property]}
|
||||
autocomplete="off"
|
||||
required
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
{:else}
|
||||
<Spinner className="size-5" />
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
{:else}
|
||||
<div>{$i18n.t('No valves')}</div>
|
||||
<Spinner className="size-5" />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{:else if pipelines.length === 0}
|
||||
<div>{$i18n.t('Pipelines Not Detected')}</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="flex justify-center">
|
||||
<div class="my-auto">
|
||||
{:else}
|
||||
<div class={mutedMessageClass}>{$i18n.t('No valves')}</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class={mutedMessageClass}>{$i18n.t('Pipelines Not Detected')}</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="flex justify-center py-4">
|
||||
<Spinner className="size-4" />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<div>{$i18n.t('Pipelines Not Detected')}</div>
|
||||
<div class={mutedMessageClass}>{$i18n.t('Pipelines Not Detected')}</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="flex justify-center h-full">
|
||||
@@ -567,7 +433,7 @@
|
||||
</div>
|
||||
|
||||
{#if PIPELINES_LIST !== null && PIPELINES_LIST.length > 0}
|
||||
<div class="flex justify-end pt-3 text-sm font-normal">
|
||||
<div class="flex justify-end pt-6 text-sm font-normal">
|
||||
<button
|
||||
class="px-3.5 py-1.5 text-sm font-normal bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
|
||||
type="submit"
|
||||
|
||||
@@ -57,9 +57,7 @@
|
||||
</script>
|
||||
|
||||
<form class="flex h-full flex-col justify-between text-sm" on:submit|preventDefault={save}>
|
||||
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5 -mr-1.5">
|
||||
<div class="mt-0.5 mb-4 text-xs text-gray-400 dark:text-gray-600">{$i18n.t('Sub-agents')}</div>
|
||||
|
||||
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5">
|
||||
{#if loading}
|
||||
<div class="flex justify-center py-8"><Spinner className="size-6" /></div>
|
||||
{:else}
|
||||
@@ -87,7 +85,7 @@
|
||||
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"
|
||||
class="h-7 w-16 rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] 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')}
|
||||
@@ -120,7 +118,7 @@
|
||||
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"
|
||||
class="h-7 w-16 rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] 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')}
|
||||
@@ -140,7 +138,7 @@
|
||||
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"
|
||||
class="h-7 w-16 rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] 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')}
|
||||
@@ -160,7 +158,7 @@
|
||||
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"
|
||||
class="h-7 w-20 rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
/>
|
||||
<span class="text-[0.6875rem] text-gray-400 dark:text-gray-600">chars</span>
|
||||
</div>
|
||||
@@ -175,7 +173,7 @@
|
||||
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"
|
||||
class="mt-1 w-full resize-y rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 font-mono text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] 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.')}
|
||||
|
||||
@@ -47,6 +47,8 @@
|
||||
let webLoaderEngines = ['playwright', 'firecrawl', 'tavily', 'microsoft_web_iq', 'external'];
|
||||
|
||||
let webConfig = null;
|
||||
const textareaClass =
|
||||
'w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors placeholder:text-gray-300 focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:placeholder:text-gray-700 dark:focus:border-blue-500';
|
||||
|
||||
const submitHandler = async () => {
|
||||
// Convert domain filter string to array before sending
|
||||
@@ -144,24 +146,18 @@
|
||||
</script>
|
||||
|
||||
<form
|
||||
class="flex flex-col h-full justify-between space-y-3 text-sm"
|
||||
class="flex h-full flex-col justify-between text-sm"
|
||||
on:submit|preventDefault={async () => {
|
||||
await submitHandler();
|
||||
saveHandler();
|
||||
}}
|
||||
>
|
||||
<div class=" space-y-3 flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5 -mr-1.5">
|
||||
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5">
|
||||
{#if webConfig}
|
||||
<div class="">
|
||||
<div class="mb-3">
|
||||
<div class=" mt-0.5 mb-2.5 text-xs text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('General')}
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-100/20 dark:border-white/[0.025] my-2" />
|
||||
|
||||
<div class="mb-5">
|
||||
<div class=" mb-2.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Web Search')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
@@ -170,7 +166,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mb-2.5 flex w-full justify-between">
|
||||
<div class="self-center text-xs font-normal">
|
||||
<div class="self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Web Search Confirmation')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
@@ -182,10 +178,11 @@
|
||||
|
||||
{#if webConfig.ENABLE_WEB_SEARCH_CONFIRMATION}
|
||||
<div class="mb-2.5">
|
||||
<div class="self-center text-xs font-normal mb-2">
|
||||
<div class="self-center text-xs text-gray-600 dark:text-gray-400 mb-2">
|
||||
{$i18n.t('Web Search Confirmation Content')}
|
||||
</div>
|
||||
<Textarea
|
||||
className={textareaClass}
|
||||
placeholder={$i18n.t(
|
||||
'Your query will be sent to the configured web search provider.'
|
||||
)}
|
||||
@@ -195,12 +192,12 @@
|
||||
{/if}
|
||||
|
||||
<div class=" mb-2.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Web Search Engine')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
<select
|
||||
class="w-fit pr-8 rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
|
||||
class="w-fit pr-8 rounded-lg px-2 p-1 text-xs bg-transparent outline-hidden text-right"
|
||||
bind:value={webConfig.WEB_SEARCH_ENGINE}
|
||||
placeholder={$i18n.t('Select a engine')}
|
||||
required
|
||||
@@ -223,7 +220,7 @@
|
||||
{#if webConfig.WEB_SEARCH_ENGINE === 'ollama_cloud'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Ollama Cloud API Key')}
|
||||
</div>
|
||||
|
||||
@@ -240,14 +237,14 @@
|
||||
{:else if webConfig.WEB_SEARCH_ENGINE === 'perplexity_search'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Perplexity Search API URL')}
|
||||
</div>
|
||||
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter Perplexity Search API URL')}
|
||||
bind:value={webConfig.PERPLEXITY_SEARCH_API_URL}
|
||||
@@ -260,7 +257,7 @@
|
||||
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Perplexity API Key')}
|
||||
</div>
|
||||
|
||||
@@ -277,14 +274,14 @@
|
||||
{:else if webConfig.WEB_SEARCH_ENGINE === 'searxng'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-left text-xs font-normal mb-1">
|
||||
<div class=" self-left text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Searxng Query URL')}
|
||||
</div>
|
||||
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter Searxng Query URL')}
|
||||
bind:value={webConfig.SEARXNG_QUERY_URL}
|
||||
@@ -295,14 +292,14 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div class=" self-left text-xs font-normal mb-1">
|
||||
<div class=" self-left text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Searxng search language (all, en, es, de, fr, etc.)')}
|
||||
</div>
|
||||
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter Searxng search language')}
|
||||
bind:value={webConfig.SEARXNG_LANGUAGE}
|
||||
@@ -316,14 +313,14 @@
|
||||
{:else if webConfig.WEB_SEARCH_ENGINE === 'yacy'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Yacy Instance URL')}
|
||||
</div>
|
||||
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter Yacy URL (e.g. http://yacy.example.com:8090)')}
|
||||
bind:value={webConfig.YACY_QUERY_URL}
|
||||
@@ -336,12 +333,12 @@
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div class="flex gap-2">
|
||||
<div class="w-full">
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Yacy Username')}
|
||||
</div>
|
||||
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
placeholder={$i18n.t('Enter Yacy Username')}
|
||||
bind:value={webConfig.YACY_USERNAME}
|
||||
required
|
||||
@@ -349,7 +346,7 @@
|
||||
</div>
|
||||
|
||||
<div class="w-full">
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Yacy Password')}
|
||||
</div>
|
||||
|
||||
@@ -363,7 +360,7 @@
|
||||
{:else if webConfig.WEB_SEARCH_ENGINE === 'google_pse'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Google PSE API Key')}
|
||||
</div>
|
||||
|
||||
@@ -373,14 +370,14 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-1.5">
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Google PSE Engine Id')}
|
||||
</div>
|
||||
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter Google PSE Engine Id')}
|
||||
bind:value={webConfig.GOOGLE_PSE_ENGINE_ID}
|
||||
@@ -393,7 +390,7 @@
|
||||
{:else if webConfig.WEB_SEARCH_ENGINE === 'brave'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Brave Search API Key')}
|
||||
</div>
|
||||
|
||||
@@ -406,7 +403,7 @@
|
||||
{:else if webConfig.WEB_SEARCH_ENGINE === 'brave_llm_context'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Brave Search API Key')}
|
||||
</div>
|
||||
|
||||
@@ -416,14 +413,14 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-1.5">
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Context Tokens')}
|
||||
</div>
|
||||
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
type="number"
|
||||
min="1024"
|
||||
max="32768"
|
||||
@@ -439,7 +436,7 @@
|
||||
{:else if webConfig.WEB_SEARCH_ENGINE === 'kagi'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Kagi Search API Key')}
|
||||
</div>
|
||||
|
||||
@@ -452,7 +449,7 @@
|
||||
{:else if webConfig.WEB_SEARCH_ENGINE === 'mojeek'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Mojeek Search API Key')}
|
||||
</div>
|
||||
|
||||
@@ -465,7 +462,7 @@
|
||||
{:else if webConfig.WEB_SEARCH_ENGINE === 'bocha'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Bocha Search API Key')}
|
||||
</div>
|
||||
|
||||
@@ -478,7 +475,7 @@
|
||||
{:else if webConfig.WEB_SEARCH_ENGINE === 'serpstack'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Serpstack API Key')}
|
||||
</div>
|
||||
|
||||
@@ -491,7 +488,7 @@
|
||||
{:else if webConfig.WEB_SEARCH_ENGINE === 'serper'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Serper API Key')}
|
||||
</div>
|
||||
|
||||
@@ -504,7 +501,7 @@
|
||||
{:else if webConfig.WEB_SEARCH_ENGINE === 'serphouse'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('SERPHouse API Key')}
|
||||
</div>
|
||||
|
||||
@@ -514,14 +511,14 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-1.5">
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('SERPHouse Domain')}
|
||||
</div>
|
||||
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
type="text"
|
||||
placeholder="google.com"
|
||||
bind:value={webConfig.SERPHOUSE_DOMAIN}
|
||||
@@ -534,7 +531,7 @@
|
||||
{:else if webConfig.WEB_SEARCH_ENGINE === 'serply'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Serply API Key')}
|
||||
</div>
|
||||
|
||||
@@ -547,7 +544,7 @@
|
||||
{:else if webConfig.WEB_SEARCH_ENGINE === 'tavily'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Tavily API Key')}
|
||||
</div>
|
||||
|
||||
@@ -560,7 +557,7 @@
|
||||
{:else if webConfig.WEB_SEARCH_ENGINE === 'searchapi'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('SearchApi API Key')}
|
||||
</div>
|
||||
|
||||
@@ -570,14 +567,14 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-1.5">
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('SearchApi Engine')}
|
||||
</div>
|
||||
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter SearchApi Engine')}
|
||||
bind:value={webConfig.SEARCHAPI_ENGINE}
|
||||
@@ -590,7 +587,7 @@
|
||||
{:else if webConfig.WEB_SEARCH_ENGINE === 'serpapi'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('SerpApi API Key')}
|
||||
</div>
|
||||
|
||||
@@ -600,14 +597,14 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-1.5">
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('SerpApi Engine')}
|
||||
</div>
|
||||
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter SerpApi Engine')}
|
||||
bind:value={webConfig.SERPAPI_ENGINE}
|
||||
@@ -620,14 +617,14 @@
|
||||
{:else if webConfig.WEB_SEARCH_ENGINE === 'jina'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Jina API Base URL')}
|
||||
</div>
|
||||
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter Jina API Base URL')}
|
||||
bind:value={webConfig.JINA_API_BASE_URL}
|
||||
@@ -638,7 +635,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Jina API Key')}
|
||||
</div>
|
||||
|
||||
@@ -651,14 +648,14 @@
|
||||
{:else if webConfig.WEB_SEARCH_ENGINE === 'bing'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Bing Search V7 Endpoint')}
|
||||
</div>
|
||||
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter Bing Search V7 Endpoint')}
|
||||
bind:value={webConfig.BING_SEARCH_V7_ENDPOINT}
|
||||
@@ -669,7 +666,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Bing Search V7 Subscription Key')}
|
||||
</div>
|
||||
|
||||
@@ -682,7 +679,7 @@
|
||||
{:else if webConfig.WEB_SEARCH_ENGINE === 'exa'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Exa API Key')}
|
||||
</div>
|
||||
|
||||
@@ -695,7 +692,7 @@
|
||||
{:else if webConfig.WEB_SEARCH_ENGINE === 'perplexity'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Perplexity API Key')}
|
||||
</div>
|
||||
|
||||
@@ -708,12 +705,12 @@
|
||||
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class="self-center text-xs font-normal mb-1">
|
||||
<div class="self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Perplexity Model')}
|
||||
</div>
|
||||
<input
|
||||
list="perplexity-model-list"
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
bind:value={webConfig.PERPLEXITY_MODEL}
|
||||
/>
|
||||
|
||||
@@ -729,11 +726,11 @@
|
||||
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Perplexity Search Context Usage')}
|
||||
</div>
|
||||
<select
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
bind:value={webConfig.PERPLEXITY_SEARCH_CONTEXT_USAGE}
|
||||
>
|
||||
<option value="low">{$i18n.t('Low')}</option>
|
||||
@@ -745,14 +742,14 @@
|
||||
{:else if webConfig.WEB_SEARCH_ENGINE === 'microsoft_web_iq'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Microsoft Web IQ API Base URL')}
|
||||
</div>
|
||||
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter Microsoft Web IQ API Base URL')}
|
||||
bind:value={webConfig.MICROSOFT_WEB_IQ_API_BASE_URL}
|
||||
@@ -763,7 +760,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Microsoft Web IQ API Key')}
|
||||
</div>
|
||||
|
||||
@@ -774,12 +771,12 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Language')}
|
||||
</div>
|
||||
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter language')}
|
||||
bind:value={webConfig.MICROSOFT_WEB_IQ_LANGUAGE}
|
||||
@@ -790,7 +787,7 @@
|
||||
{:else if webConfig.WEB_SEARCH_ENGINE === 'sougou'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Sougou Search API sID')}
|
||||
</div>
|
||||
|
||||
@@ -802,7 +799,7 @@
|
||||
</div>
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Sougou Search API SK')}
|
||||
</div>
|
||||
|
||||
@@ -815,14 +812,14 @@
|
||||
{:else if webConfig.WEB_SEARCH_ENGINE === 'firecrawl'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Firecrawl API Base URL')}
|
||||
</div>
|
||||
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter Firecrawl API Base URL')}
|
||||
bind:value={webConfig.FIRECRAWL_API_BASE_URL}
|
||||
@@ -833,7 +830,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Firecrawl API Key')}
|
||||
</div>
|
||||
|
||||
@@ -844,14 +841,14 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Firecrawl Timeout (s)')}
|
||||
</div>
|
||||
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
type="number"
|
||||
placeholder={$i18n.t('Enter Firecrawl Timeout')}
|
||||
bind:value={webConfig.FIRECRAWL_TIMEOUT}
|
||||
@@ -864,14 +861,14 @@
|
||||
{:else if webConfig.WEB_SEARCH_ENGINE === 'external'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('External Web Search URL')}
|
||||
</div>
|
||||
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter External Web Search URL')}
|
||||
bind:value={webConfig.EXTERNAL_WEB_SEARCH_URL}
|
||||
@@ -882,7 +879,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('External Web Search API Key')}
|
||||
</div>
|
||||
|
||||
@@ -895,14 +892,14 @@
|
||||
{:else if webConfig.WEB_SEARCH_ENGINE === 'yandex'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Yandex Web Search URL')}
|
||||
</div>
|
||||
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter Yandex Web Search URL')}
|
||||
bind:value={webConfig.YANDEX_WEB_SEARCH_URL}
|
||||
@@ -913,7 +910,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Yandex Web Search API Key')}
|
||||
</div>
|
||||
|
||||
@@ -924,7 +921,9 @@
|
||||
</div>
|
||||
|
||||
<div class="mb-2.5">
|
||||
<div class=" mb-1 text-xs font-normal">{$i18n.t('Yandex Web Search config')}</div>
|
||||
<div class=" mb-1 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Yandex Web Search config')}
|
||||
</div>
|
||||
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
@@ -933,6 +932,7 @@
|
||||
placement="top-start"
|
||||
>
|
||||
<Textarea
|
||||
className={textareaClass}
|
||||
bind:value={webConfig.YANDEX_WEB_SEARCH_CONFIG}
|
||||
placeholder={$i18n.t(
|
||||
'Leave empty to use the default config, or enter a valid json (see https://yandex.cloud/en/docs/search-api/api-ref/WebSearch/search#yandex.cloud.searchapi.v2.WebSearchRequest)'
|
||||
@@ -944,7 +944,7 @@
|
||||
{:else if webConfig.WEB_SEARCH_ENGINE === 'youcom'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('You.com API Key')}
|
||||
</div>
|
||||
|
||||
@@ -957,7 +957,7 @@
|
||||
{:else if webConfig.WEB_SEARCH_ENGINE === 'linkup'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Linkup API Key')}
|
||||
</div>
|
||||
|
||||
@@ -968,11 +968,12 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Parameters')}
|
||||
</div>
|
||||
|
||||
<Textarea
|
||||
className={textareaClass}
|
||||
bind:value={webConfig.LINKUP_SEARCH_PARAMS}
|
||||
placeholder={`{\n "depth": "standard",\n "outputType": "sourcedAnswer"\n}`}
|
||||
/>
|
||||
@@ -983,14 +984,14 @@
|
||||
{#if webConfig.WEB_SEARCH_ENGINE === 'duckduckgo'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('DDGS Backend')}
|
||||
</div>
|
||||
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<select
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
bind:value={webConfig.DDGS_BACKEND}
|
||||
>
|
||||
<option value="auto">{$i18n.t('Auto (Random)')}</option>
|
||||
@@ -1015,12 +1016,12 @@
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div class="flex gap-2">
|
||||
<div class="w-full">
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Search Result Count')}
|
||||
</div>
|
||||
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
placeholder={$i18n.t('Search Result Count')}
|
||||
bind:value={webConfig.WEB_SEARCH_RESULT_COUNT}
|
||||
required
|
||||
@@ -1028,7 +1029,7 @@
|
||||
</div>
|
||||
|
||||
<div class="w-full">
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
'Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).'
|
||||
@@ -1040,7 +1041,7 @@
|
||||
</div>
|
||||
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
placeholder={$i18n.t('Concurrent Requests')}
|
||||
bind:value={webConfig.WEB_SEARCH_CONCURRENT_REQUESTS}
|
||||
type="number"
|
||||
@@ -1051,7 +1052,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mb-2.5 w-full">
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
'Maximum characters to return from fetched URLs. Leave empty for no limit.'
|
||||
@@ -1063,7 +1064,7 @@
|
||||
</div>
|
||||
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
placeholder={$i18n.t('No limit')}
|
||||
bind:value={webConfig.WEB_FETCH_MAX_CONTENT_LENGTH}
|
||||
type="number"
|
||||
@@ -1072,12 +1073,12 @@
|
||||
</div>
|
||||
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div class=" text-xs font-normal mb-1">
|
||||
<div class=" text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Domain Filter List')}
|
||||
</div>
|
||||
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
placeholder={$i18n.t(
|
||||
'Enter domains separated by commas (e.g., example.com,site.org,!excludedsite.com)'
|
||||
)}
|
||||
@@ -1087,7 +1088,7 @@
|
||||
{/if}
|
||||
|
||||
<div class=" mb-2.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
<Tooltip content={$i18n.t('Full Context Mode')} placement="top-start">
|
||||
{$i18n.t('Bypass Embedding and Retrieval')}
|
||||
</Tooltip>
|
||||
@@ -1108,7 +1109,7 @@
|
||||
</div>
|
||||
|
||||
<div class=" mb-2.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
<Tooltip content={$i18n.t('Bypass Web Loader')} placement="top-start">
|
||||
{$i18n.t('Bypass Web Loader')}
|
||||
</Tooltip>
|
||||
@@ -1121,7 +1122,7 @@
|
||||
</div>
|
||||
|
||||
<div class=" mb-2.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Trust Proxy Environment')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
@@ -1138,20 +1139,18 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class=" mt-0.5 mb-2.5 text-xs text-gray-400 dark:text-gray-600">
|
||||
<div class="mb-5">
|
||||
<div class=" mb-2 text-xs text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('Loader')}
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-100/20 dark:border-white/[0.025] my-2" />
|
||||
|
||||
<div class=" mb-2.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Web Loader Engine')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
<select
|
||||
class="w-fit pr-8 rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
|
||||
class="w-fit pr-8 rounded-lg px-2 p-1 text-xs bg-transparent outline-hidden text-right"
|
||||
bind:value={webConfig.WEB_LOADER_ENGINE}
|
||||
placeholder={$i18n.t('Select a engine')}
|
||||
>
|
||||
@@ -1165,12 +1164,12 @@
|
||||
|
||||
{#if webConfig.WEB_LOADER_ENGINE === '' || webConfig.WEB_LOADER_ENGINE === 'safe_web'}
|
||||
<div class=" mb-2.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Timeout')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
<input
|
||||
class="flex-1 w-full text-sm bg-transparent outline-hidden"
|
||||
class="flex-1 w-full text-xs bg-transparent outline-hidden"
|
||||
placeholder={$i18n.t('Timeout')}
|
||||
bind:value={webConfig.WEB_LOADER_TIMEOUT}
|
||||
/>
|
||||
@@ -1178,7 +1177,7 @@
|
||||
</div>
|
||||
|
||||
<div class=" mb-2.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Verify SSL Certificate')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
@@ -1188,14 +1187,14 @@
|
||||
{:else if webConfig.WEB_LOADER_ENGINE === 'playwright'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Playwright WebSocket URL')}
|
||||
</div>
|
||||
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter Playwright WebSocket URL')}
|
||||
bind:value={webConfig.PLAYWRIGHT_WS_URL}
|
||||
@@ -1206,14 +1205,14 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Playwright Timeout (ms)')}
|
||||
</div>
|
||||
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
placeholder={$i18n.t('Enter Playwright Timeout')}
|
||||
bind:value={webConfig.PLAYWRIGHT_TIMEOUT}
|
||||
autocomplete="off"
|
||||
@@ -1225,14 +1224,14 @@
|
||||
{:else if webConfig.WEB_LOADER_ENGINE === 'firecrawl' && webConfig.WEB_SEARCH_ENGINE !== 'firecrawl'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Firecrawl API Base URL')}
|
||||
</div>
|
||||
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter Firecrawl API Base URL')}
|
||||
bind:value={webConfig.FIRECRAWL_API_BASE_URL}
|
||||
@@ -1243,7 +1242,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Firecrawl API Key')}
|
||||
</div>
|
||||
|
||||
@@ -1256,14 +1255,14 @@
|
||||
{:else if webConfig.WEB_LOADER_ENGINE === 'tavily'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Tavily Extract Depth')}
|
||||
</div>
|
||||
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter Tavily Extract Depth')}
|
||||
bind:value={webConfig.TAVILY_EXTRACT_DEPTH}
|
||||
@@ -1275,7 +1274,7 @@
|
||||
|
||||
{#if webConfig.WEB_SEARCH_ENGINE !== 'tavily'}
|
||||
<div class="mt-2">
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Tavily API Key')}
|
||||
</div>
|
||||
|
||||
@@ -1290,14 +1289,14 @@
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
{#if webConfig.WEB_SEARCH_ENGINE !== 'microsoft_web_iq'}
|
||||
<div>
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Microsoft Web IQ API Base URL')}
|
||||
</div>
|
||||
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter Microsoft Web IQ API Base URL')}
|
||||
bind:value={webConfig.MICROSOFT_WEB_IQ_API_BASE_URL}
|
||||
@@ -1308,7 +1307,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Microsoft Web IQ API Key')}
|
||||
</div>
|
||||
|
||||
@@ -1319,12 +1318,12 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Language')}
|
||||
</div>
|
||||
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter language')}
|
||||
bind:value={webConfig.MICROSOFT_WEB_IQ_LANGUAGE}
|
||||
@@ -1336,14 +1335,14 @@
|
||||
{:else if webConfig.WEB_LOADER_ENGINE === 'external'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('External Web Loader URL')}
|
||||
</div>
|
||||
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter External Web Loader URL')}
|
||||
bind:value={webConfig.EXTERNAL_WEB_LOADER_URL}
|
||||
@@ -1354,7 +1353,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('External Web Loader API Key')}
|
||||
</div>
|
||||
|
||||
@@ -1367,12 +1366,12 @@
|
||||
{/if}
|
||||
|
||||
<div class="mb-2.5 w-full">
|
||||
<div class=" self-center text-xs font-normal mb-1">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
{$i18n.t('Concurrent Requests')}
|
||||
</div>
|
||||
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
placeholder={$i18n.t('Concurrent Requests')}
|
||||
bind:value={webConfig.WEB_LOADER_CONCURRENT_REQUESTS}
|
||||
required
|
||||
@@ -1380,12 +1379,12 @@
|
||||
</div>
|
||||
|
||||
<div class=" mb-2.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Youtube Language')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
<input
|
||||
class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden"
|
||||
class="flex-1 w-full rounded-lg text-xs bg-transparent outline-hidden"
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter language codes')}
|
||||
bind:value={webConfig.YOUTUBE_LOADER_LANGUAGE}
|
||||
@@ -1395,12 +1394,12 @@
|
||||
</div>
|
||||
|
||||
<div class=" mb-2.5 flex flex-col w-full justify-between">
|
||||
<div class=" mb-1 text-xs font-normal">
|
||||
<div class=" mb-1 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Youtube Proxy URL')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
<input
|
||||
class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden"
|
||||
class="flex-1 w-full rounded-lg text-xs bg-transparent outline-hidden"
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter proxy URL (e.g. https://user:password@host:port)')}
|
||||
bind:value={webConfig.YOUTUBE_LOADER_PROXY_URL}
|
||||
@@ -1412,7 +1411,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex justify-end pt-3 text-sm font-normal">
|
||||
<div class="flex justify-end pt-6 text-sm font-normal">
|
||||
<button
|
||||
class="px-3.5 py-1.5 text-sm font-normal bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
|
||||
type="submit"
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
|
||||
import {
|
||||
archiveChatById,
|
||||
cloneChatById,
|
||||
compactChatById,
|
||||
createNewChat,
|
||||
deleteChatById,
|
||||
@@ -2265,6 +2266,53 @@
|
||||
document.getElementById('chat-input')?.focus();
|
||||
};
|
||||
|
||||
const handleForkChat = async () => {
|
||||
if (!$chatId || !history?.currentId) {
|
||||
toast.message($i18n.t('No chat to fork'));
|
||||
return;
|
||||
}
|
||||
if (!($user?.role === 'admin' || ($user?.permissions?.chat?.import ?? true))) {
|
||||
toast.error($i18n.t('Access prohibited'));
|
||||
return;
|
||||
}
|
||||
|
||||
const currentMessage = history.messages?.[history.currentId];
|
||||
if (
|
||||
generating ||
|
||||
taskIds?.length ||
|
||||
(currentMessage?.role === 'assistant' && !currentMessage.done)
|
||||
) {
|
||||
toast.warning($i18n.t('Wait for the current response to finish before forking.'));
|
||||
return;
|
||||
}
|
||||
|
||||
const toastId = toast.loading($i18n.t('Forking chat...'));
|
||||
|
||||
try {
|
||||
const result = await cloneChatById(
|
||||
localStorage.token,
|
||||
$chatId,
|
||||
$i18n.t('Clone of {{TITLE}}', {
|
||||
TITLE: $chatTitle || 'Chat'
|
||||
})
|
||||
);
|
||||
|
||||
if (result?.id) {
|
||||
await goto(`/c/${result.id}`);
|
||||
await refreshChatList(localStorage.token, { refreshPinned: true });
|
||||
toast.success($i18n.t('Chat forked'), { id: toastId });
|
||||
} else {
|
||||
toast.error($i18n.t('Failed to fork chat'), { id: toastId });
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error(`${error}`, { id: toastId });
|
||||
} finally {
|
||||
messageInput?.setText('');
|
||||
prompt = '';
|
||||
document.getElementById('chat-input')?.focus();
|
||||
}
|
||||
};
|
||||
|
||||
const submitHandler = async (userPrompt, { _raw = false } = {}) => {
|
||||
console.log('submitHandler', userPrompt, $chatId);
|
||||
|
||||
@@ -2284,6 +2332,10 @@
|
||||
handleStatusCommand();
|
||||
return;
|
||||
}
|
||||
if (String(userPrompt).trim() === '/fork') {
|
||||
await handleForkChat();
|
||||
return;
|
||||
}
|
||||
|
||||
if (pendingOAuthTools.length > 0) {
|
||||
toast.warning($i18n.t('Please connect all required integrations before sending a message'));
|
||||
@@ -3451,6 +3503,7 @@
|
||||
{contextUsage}
|
||||
compactHandler={handleManualCompact}
|
||||
statusHandler={handleStatusCommand}
|
||||
forkHandler={handleForkChat}
|
||||
toolServers={$toolServers}
|
||||
{generating}
|
||||
{stopResponse}
|
||||
|
||||
@@ -116,6 +116,7 @@
|
||||
export let stopResponse: Function;
|
||||
export let compactHandler: Function = () => {};
|
||||
export let statusHandler: Function = () => {};
|
||||
export let forkHandler: Function = () => {};
|
||||
export let chatId = '';
|
||||
export let contextUsage = null;
|
||||
|
||||
@@ -1086,9 +1087,12 @@
|
||||
canCompact: () => !!history?.currentId,
|
||||
compactDisabled: () => isActive,
|
||||
canStatus: () => !!history?.currentId,
|
||||
canFork: () => !!history?.currentId,
|
||||
forkDisabled: () => isActive,
|
||||
contextUsage: () => contextUsage,
|
||||
onCompact: compactHandler,
|
||||
onStatus: statusHandler,
|
||||
onFork: forkHandler,
|
||||
onSelect: (e) => {
|
||||
const { type, data } = e;
|
||||
|
||||
|
||||
@@ -14,16 +14,21 @@
|
||||
export let onUpload: (e: any) => void = () => {};
|
||||
export let onCompact: () => void = () => {};
|
||||
export let onStatus: () => void = () => {};
|
||||
export let onFork: () => void = () => {};
|
||||
export let insertTextHandler: (text: string) => void = () => {};
|
||||
export let canCompact: boolean | (() => boolean) = false;
|
||||
export let compactDisabled: boolean | (() => boolean) = false;
|
||||
export let canStatus: boolean | (() => boolean) = false;
|
||||
export let canFork: boolean | (() => boolean) = false;
|
||||
export let forkDisabled: boolean | (() => boolean) = false;
|
||||
export let contextUsage = null;
|
||||
|
||||
$: compactAvailable = typeof canCompact === 'function' ? canCompact() : canCompact;
|
||||
$: isCompactDisabled =
|
||||
typeof compactDisabled === 'function' ? compactDisabled() : compactDisabled;
|
||||
$: statusAvailable = typeof canStatus === 'function' ? canStatus() : canStatus;
|
||||
$: forkAvailable = typeof canFork === 'function' ? canFork() : canFork;
|
||||
$: isForkDisabled = typeof forkDisabled === 'function' ? forkDisabled() : forkDisabled;
|
||||
$: resolvedContextUsage = typeof contextUsage === 'function' ? contextUsage() : contextUsage;
|
||||
$: contextPercent = Math.max(0, Math.round(resolvedContextUsage?.percent ?? 0));
|
||||
|
||||
@@ -77,6 +82,8 @@
|
||||
canCompact={compactAvailable}
|
||||
compactDisabled={isCompactDisabled}
|
||||
canStatus={statusAvailable}
|
||||
canFork={forkAvailable}
|
||||
forkDisabled={isForkDisabled}
|
||||
{contextPercent}
|
||||
onSelect={(e) => {
|
||||
const { type, data } = e;
|
||||
@@ -89,6 +96,9 @@
|
||||
} else if (type === 'command' && data.id === 'status') {
|
||||
insertTextHandler('');
|
||||
onStatus();
|
||||
} else if (type === 'command' && data.id === 'fork') {
|
||||
insertTextHandler('');
|
||||
onFork();
|
||||
} else if (type === 'skill') {
|
||||
command({
|
||||
id: `${data.id}|${data.name}`,
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
export let canCompact = false;
|
||||
export let compactDisabled = false;
|
||||
export let canStatus = false;
|
||||
export let canFork = false;
|
||||
export let forkDisabled = false;
|
||||
export let contextPercent = 0;
|
||||
|
||||
let selectedIdx = 0;
|
||||
@@ -28,6 +30,9 @@
|
||||
...(canCompact && 'compact'.startsWith(query.toLowerCase())
|
||||
? [{ type: 'command', data: { id: 'compact' } }]
|
||||
: []),
|
||||
...(canFork && 'fork'.startsWith(query.toLowerCase())
|
||||
? [{ type: 'command', data: { id: 'fork' } }]
|
||||
: []),
|
||||
...(canStatus && 'status'.startsWith(query.toLowerCase())
|
||||
? [{ type: 'command', data: { id: 'status' } }]
|
||||
: [])
|
||||
@@ -86,6 +91,9 @@
|
||||
if (item?.type === 'command' && item.data?.id === 'compact' && compactDisabled) {
|
||||
return;
|
||||
}
|
||||
if (item?.type === 'command' && item.data?.id === 'fork' && forkDisabled) {
|
||||
return;
|
||||
}
|
||||
if (item) {
|
||||
onSelect(item);
|
||||
}
|
||||
@@ -167,6 +175,50 @@
|
||||
</span>
|
||||
</button>
|
||||
</Tooltip>
|
||||
{:else if item.data.id === 'fork'}
|
||||
<Tooltip content="Clone this chat into a new copy." placement="top">
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Fork: clone this chat into a new copy."
|
||||
class="slash-command-row flex items-center gap-2 w-full h-6 px-2 rounded-xl text-xs text-left transition-colors duration-75
|
||||
{commandIdx === selectedIdx ? 'app-interactive-active' : ''} disabled:opacity-50"
|
||||
disabled={forkDisabled}
|
||||
on:mousedown={(e) => e.preventDefault()}
|
||||
on:click={() => {
|
||||
if (!forkDisabled) {
|
||||
onSelect(item);
|
||||
}
|
||||
}}
|
||||
on:mouseenter={() => {
|
||||
selectedIdx = commandIdx;
|
||||
}}
|
||||
on:focus={() => {}}
|
||||
data-selected={commandIdx === selectedIdx}
|
||||
>
|
||||
<span class="app-icon-muted flex items-center justify-center w-4 shrink-0">
|
||||
<svg
|
||||
class="size-3.5"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.8"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M4 12H9" />
|
||||
<path d="M9 12C12.5 12 12.5 7 16 7H20" />
|
||||
<path d="M17 4L20 7L17 10" />
|
||||
<path d="M9 12C12.5 12 12.5 17 16 17H20" />
|
||||
<path d="M17 14L20 17L17 20" />
|
||||
</svg>
|
||||
</span>
|
||||
<span class="flex-1 min-w-0 flex items-baseline gap-1.5 overflow-hidden">
|
||||
<span class="truncate">Fork</span>
|
||||
<span class="app-muted text-[0.625rem] truncate shrink-0">Clone chat</span>
|
||||
</span>
|
||||
</button>
|
||||
</Tooltip>
|
||||
{:else if item.data.id === 'status'}
|
||||
<Tooltip content="Check what is running in this chat." placement="top">
|
||||
<button
|
||||
|
||||
@@ -124,119 +124,108 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="tab-account" class="flex flex-col h-full justify-between text-sm">
|
||||
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5 -mr-1.5">
|
||||
<div class="space-y-1">
|
||||
<div>
|
||||
<div class="text-xs text-gray-400 dark:text-gray-600 mb-2">
|
||||
{$i18n.t('Your Account')}
|
||||
</div>
|
||||
|
||||
<div class="text-xs text-gray-500 mt-0.5">
|
||||
{$i18n.t('Manage your account information.')}
|
||||
</div>
|
||||
</div>
|
||||
<div id="tab-account" class="flex h-full flex-col text-sm">
|
||||
<div class="flex-1 min-h-0 w-full overflow-y-auto scrollbar-hover pr-1.5 -mr-1.5 py-1">
|
||||
<h2 class="mb-4 text-sm font-medium text-gray-900 dark:text-white">{$i18n.t('Account')}</h2>
|
||||
|
||||
<div class="space-y-3">
|
||||
<!-- <div class=" text-sm font-normal">{$i18n.t('Account')}</div> -->
|
||||
|
||||
<div class="flex space-x-5 my-4">
|
||||
<UserProfileImage bind:profileImageUrl user={$user} />
|
||||
<UserProfileImage
|
||||
bind:profileImageUrl
|
||||
user={$user}
|
||||
variant="account"
|
||||
displayName={$user?.name}
|
||||
/>
|
||||
|
||||
<div class="flex flex-1 flex-col">
|
||||
<div class=" flex-1">
|
||||
<div class="flex flex-col w-full">
|
||||
<div class=" mb-1 text-xs font-normal">{$i18n.t('Name')}</div>
|
||||
<div class="flex flex-col w-full">
|
||||
<div class="mb-1 text-xs text-gray-400 dark:text-gray-600">{$i18n.t('Name')}</div>
|
||||
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full text-sm dark:text-gray-300 bg-transparent outline-hidden"
|
||||
type="text"
|
||||
bind:value={name}
|
||||
aria-label={$i18n.t('Name')}
|
||||
required
|
||||
placeholder={$i18n.t('Enter your name')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
class="w-full bg-transparent py-1 text-[0.8125rem] text-gray-700 outline-hidden placeholder:text-gray-300 dark:text-gray-300 dark:placeholder:text-gray-700"
|
||||
type="text"
|
||||
bind:value={name}
|
||||
aria-label={$i18n.t('Name')}
|
||||
required
|
||||
placeholder={$i18n.t('Enter your name')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col w-full mt-2">
|
||||
<div class=" mb-1 text-xs font-normal">{$i18n.t('Bio')}</div>
|
||||
<div class="mt-2 flex flex-col w-full">
|
||||
<div class="mb-1 text-xs text-gray-400 dark:text-gray-600">{$i18n.t('Bio')}</div>
|
||||
|
||||
<div class="flex-1">
|
||||
<Textarea
|
||||
className="w-full text-sm dark:text-gray-300 bg-transparent outline-hidden"
|
||||
minSize={60}
|
||||
bind:value={bio}
|
||||
ariaLabel={$i18n.t('Bio')}
|
||||
placeholder={$i18n.t('Share your background and interests')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Textarea
|
||||
className="w-full bg-transparent py-1 text-[0.8125rem] text-gray-700 outline-hidden placeholder:text-gray-300 dark:text-gray-300 dark:placeholder:text-gray-700"
|
||||
minSize={60}
|
||||
bind:value={bio}
|
||||
ariaLabel={$i18n.t('Bio')}
|
||||
placeholder={$i18n.t('Share your background and interests')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col w-full mt-2">
|
||||
<div class=" mb-1 text-xs font-normal">{$i18n.t('Gender')}</div>
|
||||
|
||||
<div class="flex-1">
|
||||
<select
|
||||
class="w-full text-sm dark:text-gray-300 bg-transparent outline-hidden"
|
||||
bind:value={_gender}
|
||||
aria-label={$i18n.t('Gender')}
|
||||
on:change={(e) => {
|
||||
console.log(_gender);
|
||||
|
||||
if (_gender === 'custom') {
|
||||
// Handle custom gender input
|
||||
gender = '';
|
||||
} else {
|
||||
gender = _gender;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<option value="" selected>{$i18n.t('Prefer not to say')}</option>
|
||||
<option value="male">{$i18n.t('Male')}</option>
|
||||
<option value="female">{$i18n.t('Female')}</option>
|
||||
<option value="custom">{$i18n.t('Custom')}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{#if _gender === 'custom'}
|
||||
<input
|
||||
class="w-full text-sm dark:text-gray-300 bg-transparent outline-hidden mt-1"
|
||||
type="text"
|
||||
required
|
||||
aria-label={$i18n.t('Custom Gender')}
|
||||
placeholder={$i18n.t('Enter your gender')}
|
||||
bind:value={gender}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col w-full mt-2">
|
||||
<div class=" mb-1 text-xs font-normal">{$i18n.t('Birth Date')}</div>
|
||||
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full text-sm dark:text-gray-300 dark:placeholder:text-gray-300 bg-transparent outline-hidden"
|
||||
type="date"
|
||||
aria-label={$i18n.t('Birth Date')}
|
||||
bind:value={dateOfBirth}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 flex flex-col w-full">
|
||||
<div class="mb-1 text-xs text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('Gender')}
|
||||
</div>
|
||||
|
||||
<select
|
||||
class="w-full bg-transparent py-1 text-[0.8125rem] text-gray-700 outline-hidden dark:text-gray-300"
|
||||
bind:value={_gender}
|
||||
aria-label={$i18n.t('Gender')}
|
||||
on:change={(e) => {
|
||||
console.log(_gender);
|
||||
|
||||
if (_gender === 'custom') {
|
||||
// Handle custom gender input
|
||||
gender = '';
|
||||
} else {
|
||||
gender = _gender;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<option value="" selected>{$i18n.t('Prefer not to say')}</option>
|
||||
<option value="male">{$i18n.t('Male')}</option>
|
||||
<option value="female">{$i18n.t('Female')}</option>
|
||||
<option value="custom">{$i18n.t('Custom')}</option>
|
||||
</select>
|
||||
|
||||
{#if _gender === 'custom'}
|
||||
<input
|
||||
class="mt-1 w-full bg-transparent py-1 text-[0.8125rem] text-gray-700 outline-hidden placeholder:text-gray-300 dark:text-gray-300 dark:placeholder:text-gray-700"
|
||||
type="text"
|
||||
required
|
||||
aria-label={$i18n.t('Custom Gender')}
|
||||
placeholder={$i18n.t('Enter your gender')}
|
||||
bind:value={gender}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="mt-2 flex flex-col w-full">
|
||||
<div class="mb-1 text-xs text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('Birth Date')}
|
||||
</div>
|
||||
|
||||
<input
|
||||
class="w-full bg-transparent py-1 text-[0.8125rem] text-gray-700 outline-hidden dark:text-gray-300 dark:placeholder:text-gray-700"
|
||||
type="date"
|
||||
aria-label={$i18n.t('Birth Date')}
|
||||
bind:value={dateOfBirth}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if $config?.features?.enable_user_webhooks && ($user?.role === 'admin' || ($user?.permissions?.features?.webhooks ?? false))}
|
||||
<div class="mt-2">
|
||||
<div class="mt-3">
|
||||
<div class="flex flex-col w-full">
|
||||
<div class=" mb-1 text-xs font-normal">{$i18n.t('Notification Webhook')}</div>
|
||||
<div class="mb-1 text-xs text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('Notification Webhook')}
|
||||
</div>
|
||||
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full text-sm outline-hidden"
|
||||
class="w-full bg-transparent py-1 text-[0.8125rem] text-gray-700 outline-hidden placeholder:text-gray-300 dark:text-gray-300 dark:placeholder:text-gray-700"
|
||||
type="url"
|
||||
placeholder={$i18n.t('Enter your webhook URL')}
|
||||
aria-label={$i18n.t('Notification Webhook')}
|
||||
@@ -248,7 +237,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<hr class="border-gray-50 dark:border-gray-850/30 my-4" />
|
||||
<hr class="my-4 border-gray-100/40 dark:border-white/[0.03]" />
|
||||
|
||||
{#if $config?.features.enable_login_form && $config?.features.enable_password_change_form}
|
||||
<div class="mt-2">
|
||||
@@ -257,10 +246,10 @@
|
||||
{/if}
|
||||
|
||||
{#if ($config?.features?.enable_api_keys ?? true) && ($user?.role === 'admin' || ($user?.permissions?.features?.api_keys ?? false))}
|
||||
<div class="flex justify-between items-center text-sm mt-2">
|
||||
<div class=" font-normal">{$i18n.t('API keys')}</div>
|
||||
<div class="mt-3 flex items-center justify-between text-sm">
|
||||
<div class="text-xs text-gray-400 dark:text-gray-600">{$i18n.t('API keys')}</div>
|
||||
<button
|
||||
class=" text-xs font-normal text-gray-500"
|
||||
class="text-xs font-normal text-gray-500 transition-colors hover:text-gray-700 dark:hover:text-gray-300"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
showAPIKeys = !showAPIKeys;
|
||||
@@ -273,14 +262,16 @@
|
||||
{#if $user?.role === 'admin'}
|
||||
<div class="justify-between w-full mt-2">
|
||||
<div class="flex justify-between w-full">
|
||||
<div class="self-center text-xs font-normal mb-1">{$i18n.t('JWT Token')}</div>
|
||||
<div class="mb-1 self-center text-xs text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('JWT Token')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex">
|
||||
<SensitiveInput value={localStorage.token} readOnly={true} />
|
||||
|
||||
<button
|
||||
class="ml-1.5 px-1.5 py-1 dark:hover:bg-gray-850 transition rounded-lg"
|
||||
class="ml-1.5 rounded-sm px-1.5 py-1 text-gray-500 transition hover:bg-gray-100 hover:text-gray-700 dark:hover:bg-gray-850 dark:hover:text-gray-300"
|
||||
aria-label={$i18n.t('Copy Token')}
|
||||
on:click={() => {
|
||||
copyToClipboard(localStorage.token);
|
||||
@@ -331,7 +322,9 @@
|
||||
<div class="justify-between w-full mt-2">
|
||||
{#if $user?.role === 'admin'}
|
||||
<div class="flex justify-between w-full">
|
||||
<div class="self-center text-xs font-normal mb-1">{$i18n.t('API Key')}</div>
|
||||
<div class="mb-1 self-center text-xs text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('API Key')}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex">
|
||||
@@ -339,7 +332,7 @@
|
||||
<SensitiveInput value={APIKey} readOnly={true} />
|
||||
|
||||
<button
|
||||
class="ml-1.5 px-1.5 py-1 dark:hover:bg-gray-850 transition rounded-lg"
|
||||
class="ml-1.5 rounded-sm px-1.5 py-1 text-gray-500 transition hover:bg-gray-100 hover:text-gray-700 dark:hover:bg-gray-850 dark:hover:text-gray-300"
|
||||
aria-label={$i18n.t('Copy API Key')}
|
||||
on:click={() => {
|
||||
copyToClipboard(APIKey);
|
||||
@@ -385,7 +378,7 @@
|
||||
|
||||
<Tooltip content={$i18n.t('Create new key')}>
|
||||
<button
|
||||
class=" px-1.5 py-1 dark:hover:bg-gray-850transition rounded-lg"
|
||||
class="rounded-sm px-1.5 py-1 text-gray-500 transition hover:bg-gray-100 hover:text-gray-700 dark:hover:bg-gray-850 dark:hover:text-gray-300"
|
||||
aria-label={$i18n.t('Create new key')}
|
||||
on:click={() => {
|
||||
createAPIKeyHandler();
|
||||
@@ -409,7 +402,7 @@
|
||||
</Tooltip>
|
||||
{:else}
|
||||
<button
|
||||
class="flex gap-1.5 items-center font-normal px-3.5 py-1.5 rounded-lg bg-gray-100/70 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-850 transition"
|
||||
class="flex items-center gap-1.5 text-[0.8125rem] font-normal text-gray-600 transition-colors hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|
||||
on:click={() => {
|
||||
createAPIKeyHandler();
|
||||
}}
|
||||
@@ -427,7 +420,7 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="shrink-0 flex justify-end pt-3 text-sm font-normal">
|
||||
<div class="shrink-0 flex w-full justify-end pt-3 text-sm font-normal">
|
||||
<button
|
||||
class="px-3.5 py-1.5 text-sm font-normal bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
|
||||
on:click={async () => {
|
||||
|
||||
@@ -44,9 +44,9 @@
|
||||
}}
|
||||
>
|
||||
<div class="flex justify-between items-center text-sm">
|
||||
<div class=" font-normal">{$i18n.t('Change Password')}</div>
|
||||
<div class="text-xs text-gray-400 dark:text-gray-600">{$i18n.t('Change Password')}</div>
|
||||
<button
|
||||
class=" text-xs font-normal text-gray-500"
|
||||
class="text-xs font-normal text-gray-500 transition-colors hover:text-gray-700 dark:hover:text-gray-300"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
show = !show;
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
export let user = null;
|
||||
|
||||
export let imageClassName = 'size-14 md:size-18';
|
||||
export let variant = 'default';
|
||||
export let displayName = '';
|
||||
|
||||
let profileImageInputElement;
|
||||
</script>
|
||||
@@ -79,10 +81,11 @@
|
||||
}}
|
||||
/>
|
||||
|
||||
<div class="flex flex-col self-start group">
|
||||
<div class="self-center flex">
|
||||
{#if variant === 'account'}
|
||||
<div class="mb-6 flex items-center gap-4">
|
||||
<button
|
||||
class="relative rounded-full dark:bg-gray-700"
|
||||
class="group relative flex h-10 w-10 shrink-0 items-center justify-center overflow-hidden rounded-full bg-gray-100
|
||||
ring-1 ring-gray-200 transition-all duration-200 hover:ring-2 hover:ring-gray-300 dark:bg-white/8 dark:ring-white/10 dark:hover:ring-white/20"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
profileImageInputElement.click();
|
||||
@@ -91,61 +94,154 @@
|
||||
<img
|
||||
src={profileImageUrl !== '' ? profileImageUrl : generateInitialsImage(user?.name)}
|
||||
alt="profile"
|
||||
class=" rounded-full {imageClassName} object-cover"
|
||||
class="h-full w-full object-cover"
|
||||
/>
|
||||
|
||||
<div class="absolute bottom-0 right-0 opacity-0 group-hover:opacity-100 transition">
|
||||
<div class="p-1 rounded-full bg-white text-black border-gray-100 shadow">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="size-3"
|
||||
>
|
||||
<path
|
||||
d="m2.695 14.762-1.262 3.155a.5.5 0 0 0 .65.65l3.155-1.262a4 4 0 0 0 1.343-.886L17.5 5.501a2.121 2.121 0 0 0-3-3L3.58 13.419a4 4 0 0 0-.885 1.343Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="absolute inset-0 flex items-center justify-center bg-black/0 transition-colors duration-200 group-hover:bg-black/40"
|
||||
>
|
||||
<svg
|
||||
class="h-4 w-4 text-white opacity-0 drop-shadow-sm transition-opacity duration-200 group-hover:opacity-100"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M6.827 6.175A2.31 2.31 0 0 1 5.186 7.23c-.38.054-.757.112-1.134.175C2.999 7.58 2.25 8.507 2.25 9.574V18a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18V9.574c0-1.067-.75-1.994-1.802-2.169a47.865 47.865 0 0 0-1.134-.175 2.31 2.31 0 0 1-1.64-1.055l-.822-1.316a2.192 2.192 0 0 0-1.736-1.039 48.774 48.774 0 0 0-5.232 0 2.192 2.192 0 0 0-1.736 1.039l-.821 1.316Z"
|
||||
/>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M16.5 12.75a4.5 4.5 0 1 1-9 0 4.5 4.5 0 0 1 9 0Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex flex-col w-full justify-center mt-2">
|
||||
<button
|
||||
class=" text-xs text-center text-gray-500 rounded-lg py-0.5 opacity-0 group-hover:opacity-100 transition-all"
|
||||
type="button"
|
||||
on:click={async () => {
|
||||
profileImageUrl = `${WEBUI_BASE_URL}/user.png`;
|
||||
}}>{$i18n.t('Remove')}</button
|
||||
>
|
||||
|
||||
<button
|
||||
class=" text-xs text-center text-gray-800 dark:text-gray-400 rounded-lg py-0.5 opacity-0 group-hover:opacity-100 transition-all"
|
||||
type="button"
|
||||
on:click={async () => {
|
||||
if (canvasPixelTest()) {
|
||||
profileImageUrl = generateInitialsImage(user?.name);
|
||||
} else {
|
||||
toast.info(
|
||||
$i18n.t(
|
||||
'Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.'
|
||||
),
|
||||
{
|
||||
duration: 1000 * 10
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-sm font-medium text-gray-900 dark:text-white"
|
||||
>{displayName || user?.name}</span
|
||||
>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<button
|
||||
class="text-[0.6875rem] text-gray-400 transition-colors duration-100 hover:text-gray-600 dark:hover:text-gray-300"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
profileImageInputElement.click();
|
||||
}}>{$i18n.t('Upload Photo')}</button
|
||||
>
|
||||
<span class="text-[0.6875rem] text-gray-300 dark:text-gray-700">·</span>
|
||||
<button
|
||||
class="text-[0.6875rem] text-gray-400 transition-colors duration-100 hover:text-gray-600 dark:hover:text-gray-300"
|
||||
type="button"
|
||||
on:click={async () => {
|
||||
profileImageUrl = `${WEBUI_BASE_URL}/user.png`;
|
||||
}}>{$i18n.t('Remove')}</button
|
||||
>
|
||||
<span class="text-[0.6875rem] text-gray-300 dark:text-gray-700">·</span>
|
||||
<button
|
||||
class="text-[0.6875rem] text-gray-400 transition-colors duration-100 hover:text-gray-600 dark:hover:text-gray-300"
|
||||
type="button"
|
||||
on:click={async () => {
|
||||
if (canvasPixelTest()) {
|
||||
profileImageUrl = generateInitialsImage(user?.name);
|
||||
} else {
|
||||
toast.info(
|
||||
$i18n.t(
|
||||
'Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.'
|
||||
),
|
||||
{
|
||||
duration: 1000 * 10
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
}}>{$i18n.t('Initials')}</button
|
||||
>
|
||||
}}>{$i18n.t('Initials')}</button
|
||||
>
|
||||
<span class="text-[0.6875rem] text-gray-300 dark:text-gray-700">·</span>
|
||||
<button
|
||||
class="text-[0.6875rem] text-gray-400 transition-colors duration-100 hover:text-gray-600 dark:hover:text-gray-300"
|
||||
type="button"
|
||||
on:click={async () => {
|
||||
const url = await getGravatarUrl(localStorage.token, user?.email);
|
||||
|
||||
<button
|
||||
class=" text-xs text-center text-gray-800 dark:text-gray-400 rounded-lg py-0.5 opacity-0 group-hover:opacity-100 transition-all"
|
||||
type="button"
|
||||
on:click={async () => {
|
||||
const url = await getGravatarUrl(localStorage.token, user?.email);
|
||||
|
||||
profileImageUrl = url;
|
||||
}}>{$i18n.t('Gravatar')}</button
|
||||
>
|
||||
profileImageUrl = url;
|
||||
}}>{$i18n.t('Gravatar')}</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-col self-start group">
|
||||
<div class="self-center flex">
|
||||
<button
|
||||
class="relative rounded-full dark:bg-gray-700"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
profileImageInputElement.click();
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={profileImageUrl !== '' ? profileImageUrl : generateInitialsImage(user?.name)}
|
||||
alt="profile"
|
||||
class=" rounded-full {imageClassName} object-cover"
|
||||
/>
|
||||
|
||||
<div class="absolute bottom-0 right-0 opacity-0 group-hover:opacity-100 transition">
|
||||
<div class="p-1 rounded-full bg-white text-black border-gray-100 shadow">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="size-3"
|
||||
>
|
||||
<path
|
||||
d="m2.695 14.762-1.262 3.155a.5.5 0 0 0 .65.65l3.155-1.262a4 4 0 0 0 1.343-.886L17.5 5.501a2.121 2.121 0 0 0-3-3L3.58 13.419a4 4 0 0 0-.885 1.343Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex flex-col w-full justify-center mt-2">
|
||||
<button
|
||||
class=" text-xs text-center text-gray-500 rounded-lg py-0.5 opacity-0 group-hover:opacity-100 transition-all"
|
||||
type="button"
|
||||
on:click={async () => {
|
||||
profileImageUrl = `${WEBUI_BASE_URL}/user.png`;
|
||||
}}>{$i18n.t('Remove')}</button
|
||||
>
|
||||
|
||||
<button
|
||||
class=" text-xs text-center text-gray-800 dark:text-gray-400 rounded-lg py-0.5 opacity-0 group-hover:opacity-100 transition-all"
|
||||
type="button"
|
||||
on:click={async () => {
|
||||
if (canvasPixelTest()) {
|
||||
profileImageUrl = generateInitialsImage(user?.name);
|
||||
} else {
|
||||
toast.info(
|
||||
$i18n.t(
|
||||
'Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.'
|
||||
),
|
||||
{
|
||||
duration: 1000 * 10
|
||||
}
|
||||
);
|
||||
}
|
||||
}}>{$i18n.t('Initials')}</button
|
||||
>
|
||||
|
||||
<button
|
||||
class=" text-xs text-center text-gray-800 dark:text-gray-400 rounded-lg py-0.5 opacity-0 group-hover:opacity-100 transition-all"
|
||||
type="button"
|
||||
on:click={async () => {
|
||||
const url = await getGravatarUrl(localStorage.token, user?.email);
|
||||
|
||||
profileImageUrl = url;
|
||||
}}>{$i18n.t('Gravatar')}</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -592,7 +592,8 @@
|
||||
|
||||
<Modal
|
||||
size="full"
|
||||
className="w-full max-w-3xl lg:max-w-4xl xl:max-w-5xl mx-4 md:mx-0 flex flex-col md:flex-row max-h-[85vh] lg:max-h-[90vh] md:h-[35rem] lg:h-[42rem] xl:h-[46rem] bg-white dark:bg-gray-900 rounded-4xl"
|
||||
containerClassName="p-4 sm:p-6 lg:p-8"
|
||||
className="w-full max-w-[88rem] h-[min(50rem,calc(100dvh-4rem))] max-h-[calc(100dvh-4rem)] flex flex-col md:flex-row bg-white dark:bg-gray-900 rounded-4xl"
|
||||
bind:show
|
||||
>
|
||||
<nav
|
||||
@@ -806,9 +807,11 @@
|
||||
</nav>
|
||||
|
||||
<div class="flex-1 min-h-0 p-4 md:px-5 flex flex-col">
|
||||
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">
|
||||
{$i18n.t(selectedTabTitle)}
|
||||
</h2>
|
||||
{#if selectedTab !== 'account'}
|
||||
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">
|
||||
{$i18n.t(selectedTabTitle)}
|
||||
</h2>
|
||||
{/if}
|
||||
|
||||
<div class="flex-1 min-h-0 overflow-hidden">
|
||||
{#if selectedTab === 'general'}
|
||||
|
||||
@@ -1569,7 +1569,7 @@
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class=" flex items-center rounded-xl py-2 px-1 w-full hover:bg-gray-100/50 dark:hover:bg-gray-900/50 transition"
|
||||
class=" flex items-center rounded-xl py-1.5 px-1.5 w-full hover:bg-gray-100/50 dark:hover:bg-gray-900/50 transition"
|
||||
aria-label={$i18n.t('User menu')}
|
||||
>
|
||||
<div class=" self-center mr-3 relative flex-shrink-0">
|
||||
|
||||
+78
-21
@@ -118,9 +118,48 @@
|
||||
let heartbeatInterval = null;
|
||||
let disconnectToastTimer = null;
|
||||
let disconnectWarningShown = false;
|
||||
let pageIsVisible = true;
|
||||
let pageWasHidden = false;
|
||||
let lastVisibleAt = Date.now();
|
||||
let disconnectReason = null;
|
||||
|
||||
const BREAKPOINT = 768;
|
||||
const DISCONNECT_TOAST_DELAY_MS = 2000;
|
||||
const RECENT_RESUME_GRACE_MS = 8000;
|
||||
const RESUME_DISCONNECT_REASONS = new Set(['ping timeout', 'transport close', 'transport error']);
|
||||
|
||||
const clearDisconnectToastTimer = () => {
|
||||
if (disconnectToastTimer) {
|
||||
clearTimeout(disconnectToastTimer);
|
||||
disconnectToastTimer = null;
|
||||
}
|
||||
};
|
||||
|
||||
const recentlyResumed = () =>
|
||||
pageWasHidden && Date.now() - lastVisibleAt < RECENT_RESUME_GRACE_MS;
|
||||
|
||||
const isLikelyResumeDisconnect = (reason) => {
|
||||
return (!pageIsVisible || recentlyResumed()) && RESUME_DISCONNECT_REASONS.has(reason);
|
||||
};
|
||||
|
||||
const scheduleDisconnectToast = () => {
|
||||
clearDisconnectToastTimer();
|
||||
|
||||
const resumeDelay = isLikelyResumeDisconnect(disconnectReason)
|
||||
? Math.max(RECENT_RESUME_GRACE_MS - (Date.now() - lastVisibleAt), 0)
|
||||
: 0;
|
||||
|
||||
disconnectToastTimer = setTimeout(() => {
|
||||
disconnectToastTimer = null;
|
||||
|
||||
if ($socket?.connected || !pageIsVisible || isLikelyResumeDisconnect(disconnectReason)) {
|
||||
return;
|
||||
}
|
||||
|
||||
disconnectWarningShown = true;
|
||||
toast.warning($i18n.t('Connection lost. Reconnecting...'));
|
||||
}, resumeDelay + DISCONNECT_TOAST_DELAY_MS);
|
||||
};
|
||||
|
||||
const setupSocket = async (enableWebsocket) => {
|
||||
const _socket = io(`${WEBUI_BASE_URL}` || undefined, {
|
||||
@@ -144,19 +183,17 @@
|
||||
console.log('connected', _socket.id);
|
||||
|
||||
// Cancel any pending disconnect toast if we reconnected quickly
|
||||
if (disconnectToastTimer) {
|
||||
clearTimeout(disconnectToastTimer);
|
||||
disconnectToastTimer = null;
|
||||
}
|
||||
clearDisconnectToastTimer();
|
||||
|
||||
if (hasConnectedOnce) {
|
||||
socketConnected.set(true);
|
||||
// Only show "Reconnected" if the user actually saw the disconnect warning
|
||||
if (disconnectWarningShown) {
|
||||
toast.success($i18n.t('Reconnected'));
|
||||
disconnectWarningShown = false;
|
||||
}
|
||||
}
|
||||
disconnectWarningShown = false;
|
||||
disconnectReason = null;
|
||||
hasConnectedOnce = true;
|
||||
|
||||
const res = await getVersion(localStorage.token);
|
||||
@@ -213,18 +250,15 @@
|
||||
_socket.on('disconnect', (reason, details) => {
|
||||
console.log(`Socket ${_socket.id} disconnected due to ${reason}`);
|
||||
socketConnected.set(false);
|
||||
|
||||
// Delay showing the disconnect toast so brief interruptions
|
||||
// (e.g. mobile tab backgrounding) don't flash a nuisance warning
|
||||
if (disconnectToastTimer) {
|
||||
clearTimeout(disconnectToastTimer);
|
||||
}
|
||||
disconnectReason = reason;
|
||||
disconnectWarningShown = false;
|
||||
disconnectToastTimer = setTimeout(() => {
|
||||
disconnectToastTimer = null;
|
||||
disconnectWarningShown = true;
|
||||
toast.warning($i18n.t('Connection lost. Reconnecting...'));
|
||||
}, DISCONNECT_TOAST_DELAY_MS);
|
||||
|
||||
// Delay visible warnings while mobile browsers resume suspended tabs.
|
||||
if (isLikelyResumeDisconnect(reason)) {
|
||||
clearDisconnectToastTimer();
|
||||
} else {
|
||||
scheduleDisconnectToast();
|
||||
}
|
||||
|
||||
if (heartbeatInterval) {
|
||||
clearInterval(heartbeatInterval);
|
||||
@@ -1044,18 +1078,39 @@
|
||||
};
|
||||
|
||||
// Set yourself as the last active tab when this tab is focused
|
||||
const handlePageHidden = () => {
|
||||
pageIsVisible = false;
|
||||
pageWasHidden = true;
|
||||
clearDisconnectToastTimer();
|
||||
};
|
||||
|
||||
const handlePageVisible = () => {
|
||||
pageIsVisible = true;
|
||||
lastVisibleAt = Date.now();
|
||||
|
||||
isLastActiveTab.set(true); // This tab is now the active tab
|
||||
bc.postMessage('active'); // Notify other tabs that this tab is active
|
||||
|
||||
// Check token expiry when the tab becomes active
|
||||
checkTokenExpiry();
|
||||
|
||||
if ($socket && !$socket.connected) {
|
||||
scheduleDisconnectToast();
|
||||
}
|
||||
};
|
||||
|
||||
const handleVisibilityChange = () => {
|
||||
if (document.visibilityState === 'visible') {
|
||||
isLastActiveTab.set(true); // This tab is now the active tab
|
||||
bc.postMessage('active'); // Notify other tabs that this tab is active
|
||||
|
||||
// Check token expiry when the tab becomes active
|
||||
checkTokenExpiry();
|
||||
handlePageVisible();
|
||||
} else {
|
||||
handlePageHidden();
|
||||
}
|
||||
};
|
||||
|
||||
// Add event listener for visibility state changes
|
||||
document.addEventListener('visibilitychange', handleVisibilityChange);
|
||||
window.addEventListener('pagehide', handlePageHidden);
|
||||
window.addEventListener('pageshow', handlePageVisible);
|
||||
|
||||
// Call visibility change handler initially to set state on load
|
||||
handleVisibilityChange();
|
||||
@@ -1228,6 +1283,8 @@
|
||||
document.removeEventListener('touchmove', touchmoveHandler);
|
||||
document.removeEventListener('touchend', touchendHandler);
|
||||
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
||||
window.removeEventListener('pagehide', handlePageHidden);
|
||||
window.removeEventListener('pageshow', handlePageVisible);
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user