mirror of
https://github.com/open-webui/open-webui
synced 2025-06-26 18:26:48 +00:00
feat: merge with main
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
getModels as _getModels,
|
||||
getVoices as _getVoices
|
||||
} from '$lib/apis/audio';
|
||||
import { config } from '$lib/stores';
|
||||
import { config, settings } from '$lib/stores';
|
||||
|
||||
import SensitiveInput from '$lib/components/common/SensitiveInput.svelte';
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
let STT_ENGINE = '';
|
||||
let STT_MODEL = '';
|
||||
let STT_WHISPER_MODEL = '';
|
||||
let STT_DEEPGRAM_API_KEY = '';
|
||||
|
||||
let STT_WHISPER_MODEL_LOADING = false;
|
||||
|
||||
@@ -50,8 +51,11 @@
|
||||
if (TTS_ENGINE === '') {
|
||||
models = [];
|
||||
} else {
|
||||
const res = await _getModels(localStorage.token).catch((e) => {
|
||||
toast.error(e);
|
||||
const res = await _getModels(
|
||||
localStorage.token,
|
||||
$config?.features?.enable_direct_connections && ($settings?.directConnections ?? null)
|
||||
).catch((e) => {
|
||||
toast.error(`${e}`);
|
||||
});
|
||||
|
||||
if (res) {
|
||||
@@ -74,7 +78,7 @@
|
||||
}, 100);
|
||||
} else {
|
||||
const res = await _getVoices(localStorage.token).catch((e) => {
|
||||
toast.error(e);
|
||||
toast.error(`${e}`);
|
||||
});
|
||||
|
||||
if (res) {
|
||||
@@ -103,7 +107,8 @@
|
||||
OPENAI_API_KEY: STT_OPENAI_API_KEY,
|
||||
ENGINE: STT_ENGINE,
|
||||
MODEL: STT_MODEL,
|
||||
WHISPER_MODEL: STT_WHISPER_MODEL
|
||||
WHISPER_MODEL: STT_WHISPER_MODEL,
|
||||
DEEPGRAM_API_KEY: STT_DEEPGRAM_API_KEY
|
||||
}
|
||||
});
|
||||
|
||||
@@ -143,6 +148,7 @@
|
||||
STT_ENGINE = res.stt.ENGINE;
|
||||
STT_MODEL = res.stt.MODEL;
|
||||
STT_WHISPER_MODEL = res.stt.WHISPER_MODEL;
|
||||
STT_DEEPGRAM_API_KEY = res.stt.DEEPGRAM_API_KEY;
|
||||
}
|
||||
|
||||
await getVoices();
|
||||
@@ -166,13 +172,14 @@
|
||||
<div class=" self-center text-xs font-medium">{$i18n.t('Speech-to-Text Engine')}</div>
|
||||
<div class="flex items-center relative">
|
||||
<select
|
||||
class="dark:bg-gray-900 cursor-pointer w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
|
||||
class="dark:bg-gray-900 cursor-pointer w-fit pr-8 rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
|
||||
bind:value={STT_ENGINE}
|
||||
placeholder="Select an engine"
|
||||
>
|
||||
<option value="">{$i18n.t('Whisper (Local)')}</option>
|
||||
<option value="openai">OpenAI</option>
|
||||
<option value="web">{$i18n.t('Web API')}</option>
|
||||
<option value="deepgram">Deepgram</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -181,7 +188,7 @@
|
||||
<div>
|
||||
<div class="mt-1 flex gap-2 mb-1">
|
||||
<input
|
||||
class="flex-1 w-full bg-transparent outline-none"
|
||||
class="flex-1 w-full bg-transparent outline-hidden"
|
||||
placeholder={$i18n.t('API Base URL')}
|
||||
bind:value={STT_OPENAI_API_BASE_URL}
|
||||
required
|
||||
@@ -191,7 +198,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class=" dark:border-gray-850 my-2" />
|
||||
<hr class="border-gray-100 dark:border-gray-850 my-2" />
|
||||
|
||||
<div>
|
||||
<div class=" mb-1.5 text-sm font-medium">{$i18n.t('STT Model')}</div>
|
||||
@@ -199,7 +206,7 @@
|
||||
<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-none"
|
||||
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={STT_MODEL}
|
||||
placeholder="Select a model"
|
||||
/>
|
||||
@@ -210,6 +217,37 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else if STT_ENGINE === 'deepgram'}
|
||||
<div>
|
||||
<div class="mt-1 flex gap-2 mb-1">
|
||||
<SensitiveInput placeholder={$i18n.t('API Key')} bind:value={STT_DEEPGRAM_API_KEY} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="border-gray-100 dark:border-gray-850 my-2" />
|
||||
|
||||
<div>
|
||||
<div class=" mb-1.5 text-sm font-medium">{$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"
|
||||
bind:value={STT_MODEL}
|
||||
placeholder="Select a model (optional)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 mb-1 text-xs text-gray-400 dark:text-gray-500">
|
||||
{$i18n.t('Leave model field empty to use the default model.')}
|
||||
<a
|
||||
class=" hover:underline dark:text-gray-200 text-gray-800"
|
||||
href="https://developers.deepgram.com/docs/models"
|
||||
target="_blank"
|
||||
>
|
||||
{$i18n.t('Click here to see available models.')}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{:else if STT_ENGINE === ''}
|
||||
<div>
|
||||
<div class=" mb-1.5 text-sm font-medium">{$i18n.t('STT Model')}</div>
|
||||
@@ -217,7 +255,7 @@
|
||||
<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-none"
|
||||
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('Set whisper model')}
|
||||
bind:value={STT_WHISPER_MODEL}
|
||||
/>
|
||||
@@ -295,7 +333,7 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<hr class=" dark:border-gray-800" />
|
||||
<hr class="border-gray-100 dark:border-gray-850" />
|
||||
|
||||
<div>
|
||||
<div class=" mb-1 text-sm font-medium">{$i18n.t('TTS Settings')}</div>
|
||||
@@ -304,7 +342,7 @@
|
||||
<div class=" self-center text-xs font-medium">{$i18n.t('Text-to-Speech Engine')}</div>
|
||||
<div class="flex items-center relative">
|
||||
<select
|
||||
class=" dark:bg-gray-900 w-fit pr-8 cursor-pointer rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
|
||||
class=" dark:bg-gray-900 w-fit pr-8 cursor-pointer rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
|
||||
bind:value={TTS_ENGINE}
|
||||
placeholder="Select a mode"
|
||||
on:change={async (e) => {
|
||||
@@ -334,7 +372,7 @@
|
||||
<div>
|
||||
<div class="mt-1 flex gap-2 mb-1">
|
||||
<input
|
||||
class="flex-1 w-full bg-transparent outline-none"
|
||||
class="flex-1 w-full bg-transparent outline-hidden"
|
||||
placeholder={$i18n.t('API Base URL')}
|
||||
bind:value={TTS_OPENAI_API_BASE_URL}
|
||||
required
|
||||
@@ -347,7 +385,7 @@
|
||||
<div>
|
||||
<div class="mt-1 flex gap-2 mb-1">
|
||||
<input
|
||||
class="flex-1 w-full rounded-lg py-2 pl-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||
class="flex-1 w-full rounded-lg py-2 pl-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
placeholder={$i18n.t('API Key')}
|
||||
bind:value={TTS_API_KEY}
|
||||
required
|
||||
@@ -358,13 +396,13 @@
|
||||
<div>
|
||||
<div class="mt-1 flex gap-2 mb-1">
|
||||
<input
|
||||
class="flex-1 w-full rounded-lg py-2 pl-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||
class="flex-1 w-full rounded-lg py-2 pl-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
placeholder={$i18n.t('API Key')}
|
||||
bind:value={TTS_API_KEY}
|
||||
required
|
||||
/>
|
||||
<input
|
||||
class="flex-1 w-full rounded-lg py-2 pl-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||
class="flex-1 w-full rounded-lg py-2 pl-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
placeholder={$i18n.t('Azure Region')}
|
||||
bind:value={TTS_AZURE_SPEECH_REGION}
|
||||
required
|
||||
@@ -373,7 +411,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<hr class=" dark:border-gray-850 my-2" />
|
||||
<hr class="border-gray-100 dark:border-gray-850 my-2" />
|
||||
|
||||
{#if TTS_ENGINE === ''}
|
||||
<div>
|
||||
@@ -381,7 +419,7 @@
|
||||
<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-none"
|
||||
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={TTS_VOICE}
|
||||
>
|
||||
<option value="" selected={TTS_VOICE !== ''}>{$i18n.t('Default')}</option>
|
||||
@@ -404,7 +442,7 @@
|
||||
<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-none"
|
||||
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={TTS_MODEL}
|
||||
placeholder="CMU ARCTIC speaker embedding name"
|
||||
/>
|
||||
@@ -446,7 +484,7 @@
|
||||
<div class="flex-1">
|
||||
<input
|
||||
list="voice-list"
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||
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={TTS_VOICE}
|
||||
placeholder="Select a voice"
|
||||
/>
|
||||
@@ -465,7 +503,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-none"
|
||||
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={TTS_MODEL}
|
||||
placeholder="Select a model"
|
||||
/>
|
||||
@@ -487,7 +525,7 @@
|
||||
<div class="flex-1">
|
||||
<input
|
||||
list="voice-list"
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||
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={TTS_VOICE}
|
||||
placeholder="Select a voice"
|
||||
/>
|
||||
@@ -506,7 +544,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-none"
|
||||
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={TTS_MODEL}
|
||||
placeholder="Select a model"
|
||||
/>
|
||||
@@ -528,7 +566,7 @@
|
||||
<div class="flex-1">
|
||||
<input
|
||||
list="voice-list"
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||
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={TTS_VOICE}
|
||||
placeholder="Select a voice"
|
||||
/>
|
||||
@@ -555,7 +593,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-none"
|
||||
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={TTS_AZURE_SPEECH_OUTPUT_FORMAT}
|
||||
placeholder="Select a output format"
|
||||
/>
|
||||
@@ -565,13 +603,13 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<hr class="dark:border-gray-850 my-2" />
|
||||
<hr class="border-gray-100 dark:border-gray-850 my-2" />
|
||||
|
||||
<div class="pt-0.5 flex w-full justify-between">
|
||||
<div class="self-center text-xs font-medium">{$i18n.t('Response splitting')}</div>
|
||||
<div class="flex items-center relative">
|
||||
<select
|
||||
class="dark:bg-gray-900 w-fit pr-8 cursor-pointer rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
|
||||
class="dark:bg-gray-900 w-fit pr-8 cursor-pointer rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
|
||||
aria-label="Select how to split message text for TTS requests"
|
||||
bind:value={TTS_SPLIT_ON}
|
||||
>
|
||||
|
||||
317
src/lib/components/admin/Settings/CodeExecution.svelte
Normal file
317
src/lib/components/admin/Settings/CodeExecution.svelte
Normal file
@@ -0,0 +1,317 @@
|
||||
<script lang="ts">
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { onMount, getContext } from 'svelte';
|
||||
import { getCodeExecutionConfig, setCodeExecutionConfig } from '$lib/apis/configs';
|
||||
|
||||
import SensitiveInput from '$lib/components/common/SensitiveInput.svelte';
|
||||
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
import Textarea from '$lib/components/common/Textarea.svelte';
|
||||
import Switch from '$lib/components/common/Switch.svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
export let saveHandler: Function;
|
||||
|
||||
let config = null;
|
||||
|
||||
let engines = ['pyodide', 'jupyter'];
|
||||
|
||||
const submitHandler = async () => {
|
||||
const res = await setCodeExecutionConfig(localStorage.token, config);
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
const res = await getCodeExecutionConfig(localStorage.token);
|
||||
|
||||
if (res) {
|
||||
config = res;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<form
|
||||
class="flex flex-col h-full justify-between space-y-3 text-sm"
|
||||
on:submit|preventDefault={async () => {
|
||||
await submitHandler();
|
||||
saveHandler();
|
||||
}}
|
||||
>
|
||||
<div class=" space-y-3 overflow-y-scroll scrollbar-hidden h-full">
|
||||
{#if config}
|
||||
<div>
|
||||
<div class="mb-3.5">
|
||||
<div class=" mb-2.5 text-base font-medium">{$i18n.t('General')}</div>
|
||||
|
||||
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
|
||||
|
||||
<div class="mb-2.5">
|
||||
<div class="flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">{$i18n.t('Code Execution Engine')}</div>
|
||||
<div class="flex items-center relative">
|
||||
<select
|
||||
class="dark:bg-gray-900 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}</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-medium">
|
||||
{$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-medium">
|
||||
{$i18n.t('Jupyter Auth')}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<select
|
||||
class="dark:bg-gray-900 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-medium">
|
||||
{$i18n.t('Code Execution Timeout')}
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<Tooltip content={$i18n.t('Enter timeout in seconds')}>
|
||||
<input
|
||||
class="dark:bg-gray-900 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}
|
||||
</div>
|
||||
|
||||
<div class="mb-3.5">
|
||||
<div class=" mb-2.5 text-base font-medium">{$i18n.t('Code Interpreter')}</div>
|
||||
|
||||
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
|
||||
|
||||
<div class="mb-2.5">
|
||||
<div class=" flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$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-medium">
|
||||
{$i18n.t('Code Interpreter Engine')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
<select
|
||||
class="dark:bg-gray-900 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}</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-medium">
|
||||
{$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-medium">
|
||||
{$i18n.t('Jupyter Auth')}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<select
|
||||
class="dark:bg-gray-900 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-medium">
|
||||
{$i18n.t('Code Execution Timeout')}
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<Tooltip content={$i18n.t('Enter timeout in seconds')}>
|
||||
<input
|
||||
class="dark:bg-gray-900 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 dark:border-gray-850 my-2" />
|
||||
|
||||
<div>
|
||||
<div class="py-0.5 w-full">
|
||||
<div class=" mb-2.5 text-xs font-medium">
|
||||
{$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'
|
||||
)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex justify-end pt-3 text-sm font-medium">
|
||||
<button
|
||||
class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
|
||||
type="submit"
|
||||
>
|
||||
{$i18n.t('Save')}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -7,8 +7,9 @@
|
||||
import { getOllamaConfig, updateOllamaConfig } from '$lib/apis/ollama';
|
||||
import { getOpenAIConfig, updateOpenAIConfig, getOpenAIModels } from '$lib/apis/openai';
|
||||
import { getModels as _getModels } from '$lib/apis';
|
||||
import { getDirectConnectionsConfig, setDirectConnectionsConfig } from '$lib/apis/configs';
|
||||
|
||||
import { models, user } from '$lib/stores';
|
||||
import { config, models, settings, user } from '$lib/stores';
|
||||
|
||||
import Switch from '$lib/components/common/Switch.svelte';
|
||||
import Spinner from '$lib/components/common/Spinner.svelte';
|
||||
@@ -16,13 +17,16 @@
|
||||
import Plus from '$lib/components/icons/Plus.svelte';
|
||||
|
||||
import OpenAIConnection from './Connections/OpenAIConnection.svelte';
|
||||
import AddConnectionModal from './Connections/AddConnectionModal.svelte';
|
||||
import AddConnectionModal from '$lib/components/AddConnectionModal.svelte';
|
||||
import OllamaConnection from './Connections/OllamaConnection.svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
const getModels = async () => {
|
||||
const models = await _getModels(localStorage.token);
|
||||
const models = await _getModels(
|
||||
localStorage.token,
|
||||
$config?.features?.enable_direct_connections && ($settings?.directConnections ?? null)
|
||||
);
|
||||
return models;
|
||||
};
|
||||
|
||||
@@ -37,6 +41,8 @@
|
||||
let ENABLE_OPENAI_API: null | boolean = null;
|
||||
let ENABLE_OLLAMA_API: null | boolean = null;
|
||||
|
||||
let directConnectionsConfig = null;
|
||||
|
||||
let pipelineUrls = {};
|
||||
let showAddOpenAIConnectionModal = false;
|
||||
let showAddOllamaConnectionModal = false;
|
||||
@@ -98,17 +104,33 @@
|
||||
}
|
||||
};
|
||||
|
||||
const updateDirectConnectionsHandler = async () => {
|
||||
const res = await setDirectConnectionsConfig(localStorage.token, directConnectionsConfig).catch(
|
||||
(error) => {
|
||||
toast.error(`${error}`);
|
||||
}
|
||||
);
|
||||
|
||||
if (res) {
|
||||
toast.success($i18n.t('Direct Connections settings updated'));
|
||||
await models.set(await getModels());
|
||||
}
|
||||
};
|
||||
|
||||
const addOpenAIConnectionHandler = async (connection) => {
|
||||
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] = connection.config;
|
||||
OPENAI_API_CONFIGS[OPENAI_API_BASE_URLS.length - 1] = connection.config;
|
||||
|
||||
await updateOpenAIHandler();
|
||||
};
|
||||
|
||||
const addOllamaConnectionHandler = async (connection) => {
|
||||
OLLAMA_BASE_URLS = [...OLLAMA_BASE_URLS, connection.url];
|
||||
OLLAMA_API_CONFIGS[OLLAMA_BASE_URLS.length] = connection.config;
|
||||
OLLAMA_API_CONFIGS[OLLAMA_BASE_URLS.length - 1] = {
|
||||
...connection.config,
|
||||
key: connection.key
|
||||
};
|
||||
|
||||
await updateOllamaHandler();
|
||||
};
|
||||
@@ -124,6 +146,9 @@
|
||||
})(),
|
||||
(async () => {
|
||||
openaiConfig = await getOpenAIConfig(localStorage.token);
|
||||
})(),
|
||||
(async () => {
|
||||
directConnectionsConfig = await getDirectConnectionsConfig(localStorage.token);
|
||||
})()
|
||||
]);
|
||||
|
||||
@@ -167,6 +192,14 @@
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const submitHandler = async () => {
|
||||
updateOpenAIHandler();
|
||||
updateOllamaHandler();
|
||||
updateDirectConnectionsHandler();
|
||||
|
||||
dispatch('save');
|
||||
};
|
||||
</script>
|
||||
|
||||
<AddConnectionModal
|
||||
@@ -180,17 +213,9 @@
|
||||
onSubmit={addOllamaConnectionHandler}
|
||||
/>
|
||||
|
||||
<form
|
||||
class="flex flex-col h-full justify-between text-sm"
|
||||
on:submit|preventDefault={() => {
|
||||
updateOpenAIHandler();
|
||||
updateOllamaHandler();
|
||||
|
||||
dispatch('save');
|
||||
}}
|
||||
>
|
||||
<form class="flex flex-col h-full justify-between text-sm" on:submit|preventDefault={submitHandler}>
|
||||
<div class=" overflow-y-scroll scrollbar-hidden h-full">
|
||||
{#if ENABLE_OPENAI_API !== null && ENABLE_OLLAMA_API !== null}
|
||||
{#if ENABLE_OPENAI_API !== null && ENABLE_OLLAMA_API !== null && directConnectionsConfig !== null}
|
||||
<div class="my-2">
|
||||
<div class="mt-2 space-y-2 pr-1.5">
|
||||
<div class="flex justify-between items-center text-sm">
|
||||
@@ -209,7 +234,7 @@
|
||||
</div>
|
||||
|
||||
{#if ENABLE_OPENAI_API}
|
||||
<hr class=" border-gray-50 dark:border-gray-850" />
|
||||
<hr class=" border-gray-100 dark:border-gray-850" />
|
||||
|
||||
<div class="">
|
||||
<div class="flex justify-between items-center">
|
||||
@@ -244,7 +269,12 @@
|
||||
);
|
||||
OPENAI_API_KEYS = OPENAI_API_KEYS.filter((key, keyIdx) => idx !== keyIdx);
|
||||
|
||||
delete OPENAI_API_CONFIGS[idx];
|
||||
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}
|
||||
@@ -254,7 +284,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-50 dark:border-gray-850" />
|
||||
<hr class=" border-gray-100 dark:border-gray-850" />
|
||||
|
||||
<div class="pr-1.5 my-2">
|
||||
<div class="flex justify-between items-center text-sm mb-2">
|
||||
@@ -271,7 +301,7 @@
|
||||
</div>
|
||||
|
||||
{#if ENABLE_OLLAMA_API}
|
||||
<hr class=" border-gray-50 dark:border-gray-850 my-2" />
|
||||
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
|
||||
|
||||
<div class="">
|
||||
<div class="flex justify-between items-center">
|
||||
@@ -302,7 +332,12 @@
|
||||
}}
|
||||
onDelete={() => {
|
||||
OLLAMA_BASE_URLS = OLLAMA_BASE_URLS.filter((url, urlIdx) => idx !== urlIdx);
|
||||
delete OLLAMA_API_CONFIGS[idx];
|
||||
|
||||
let newConfig = {};
|
||||
OLLAMA_BASE_URLS.forEach((url, newIdx) => {
|
||||
newConfig[newIdx] = OLLAMA_API_CONFIGS[newIdx < idx ? newIdx : newIdx + 1];
|
||||
});
|
||||
OLLAMA_API_CONFIGS = newConfig;
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
@@ -322,6 +357,33 @@
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-100 dark:border-gray-850" />
|
||||
|
||||
<div class="pr-1.5 my-2">
|
||||
<div class="flex justify-between items-center text-sm">
|
||||
<div class=" font-medium">{$i18n.t('Direct Connections')}</div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<div class="">
|
||||
<Switch
|
||||
bind:state={directConnectionsConfig.ENABLE_DIRECT_CONNECTIONS}
|
||||
on:change={async () => {
|
||||
updateDirectConnectionsHandler();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-1.5">
|
||||
<div class="text-xs text-gray-500">
|
||||
{$i18n.t(
|
||||
'Direct Connections allow users to connect to their own OpenAI compatible API endpoints.'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex h-full justify-center">
|
||||
<div class="my-auto">
|
||||
|
||||
@@ -1,365 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { getContext, onMount } from 'svelte';
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
import { models } from '$lib/stores';
|
||||
import { verifyOpenAIConnection } from '$lib/apis/openai';
|
||||
import { verifyOllamaConnection } from '$lib/apis/ollama';
|
||||
|
||||
import Modal from '$lib/components/common/Modal.svelte';
|
||||
import Plus from '$lib/components/icons/Plus.svelte';
|
||||
import Minus from '$lib/components/icons/Minus.svelte';
|
||||
import PencilSolid from '$lib/components/icons/PencilSolid.svelte';
|
||||
import SensitiveInput from '$lib/components/common/SensitiveInput.svelte';
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
import Switch from '$lib/components/common/Switch.svelte';
|
||||
|
||||
export let onSubmit: Function = () => {};
|
||||
export let onDelete: Function = () => {};
|
||||
|
||||
export let show = false;
|
||||
export let edit = false;
|
||||
export let ollama = false;
|
||||
|
||||
export let connection = null;
|
||||
|
||||
let url = '';
|
||||
let key = '';
|
||||
|
||||
let prefixId = '';
|
||||
let enable = true;
|
||||
|
||||
let modelId = '';
|
||||
let modelIds = [];
|
||||
|
||||
let loading = false;
|
||||
|
||||
const verifyOllamaHandler = async () => {
|
||||
const res = await verifyOllamaConnection(localStorage.token, url, key).catch((error) => {
|
||||
toast.error(`${error}`);
|
||||
});
|
||||
|
||||
if (res) {
|
||||
toast.success($i18n.t('Server connection verified'));
|
||||
}
|
||||
};
|
||||
|
||||
const verifyOpenAIHandler = async () => {
|
||||
const res = await verifyOpenAIConnection(localStorage.token, url, key).catch((error) => {
|
||||
toast.error(`${error}`);
|
||||
});
|
||||
|
||||
if (res) {
|
||||
toast.success($i18n.t('Server connection verified'));
|
||||
}
|
||||
};
|
||||
|
||||
const verifyHandler = () => {
|
||||
if (ollama) {
|
||||
verifyOllamaHandler();
|
||||
} else {
|
||||
verifyOpenAIHandler();
|
||||
}
|
||||
};
|
||||
|
||||
const addModelHandler = () => {
|
||||
if (modelId) {
|
||||
modelIds = [...modelIds, modelId];
|
||||
modelId = '';
|
||||
}
|
||||
};
|
||||
|
||||
const submitHandler = async () => {
|
||||
loading = true;
|
||||
|
||||
if (!ollama && (!url || !key)) {
|
||||
loading = false;
|
||||
toast.error('URL and Key are required');
|
||||
return;
|
||||
}
|
||||
|
||||
const connection = {
|
||||
url,
|
||||
key,
|
||||
config: {
|
||||
enable: enable,
|
||||
prefix_id: prefixId,
|
||||
model_ids: modelIds
|
||||
}
|
||||
};
|
||||
|
||||
await onSubmit(connection);
|
||||
|
||||
loading = false;
|
||||
show = false;
|
||||
|
||||
url = '';
|
||||
key = '';
|
||||
prefixId = '';
|
||||
modelIds = [];
|
||||
};
|
||||
|
||||
const init = () => {
|
||||
if (connection) {
|
||||
url = connection.url;
|
||||
key = connection.key;
|
||||
|
||||
enable = connection.config?.enable ?? true;
|
||||
prefixId = connection.config?.prefix_id ?? '';
|
||||
modelIds = connection.config?.model_ids ?? [];
|
||||
}
|
||||
};
|
||||
|
||||
$: if (show) {
|
||||
init();
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
init();
|
||||
});
|
||||
</script>
|
||||
|
||||
<Modal size="sm" bind:show>
|
||||
<div>
|
||||
<div class=" flex justify-between dark:text-gray-100 px-5 pt-4 pb-2">
|
||||
<div class=" text-lg font-medium self-center font-primary">
|
||||
{#if edit}
|
||||
{$i18n.t('Edit Connection')}
|
||||
{:else}
|
||||
{$i18n.t('Add Connection')}
|
||||
{/if}
|
||||
</div>
|
||||
<button
|
||||
class="self-center"
|
||||
on:click={() => {
|
||||
show = false;
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="w-5 h-5"
|
||||
>
|
||||
<path
|
||||
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col md:flex-row w-full px-4 pb-4 md:space-x-4 dark:text-gray-200">
|
||||
<div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6">
|
||||
<form
|
||||
class="flex flex-col w-full"
|
||||
on:submit={(e) => {
|
||||
e.preventDefault();
|
||||
submitHandler();
|
||||
}}
|
||||
>
|
||||
<div class="px-1">
|
||||
<div class="flex gap-2">
|
||||
<div class="flex flex-col w-full">
|
||||
<div class=" mb-0.5 text-xs text-gray-500">{$i18n.t('URL')}</div>
|
||||
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-none"
|
||||
type="text"
|
||||
bind:value={url}
|
||||
placeholder={$i18n.t('API Base URL')}
|
||||
autocomplete="off"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Tooltip content="Verify Connection" className="self-end -mb-1">
|
||||
<button
|
||||
class="self-center p-1 bg-transparent hover:bg-gray-100 dark:bg-gray-900 dark:hover:bg-gray-850 rounded-lg transition"
|
||||
on:click={() => {
|
||||
verifyHandler();
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M15.312 11.424a5.5 5.5 0 01-9.201 2.466l-.312-.311h2.433a.75.75 0 000-1.5H3.989a.75.75 0 00-.75.75v4.242a.75.75 0 001.5 0v-2.43l.31.31a7 7 0 0011.712-3.138.75.75 0 00-1.449-.39zm1.23-3.723a.75.75 0 00.219-.53V2.929a.75.75 0 00-1.5 0V5.36l-.31-.31A7 7 0 003.239 8.188a.75.75 0 101.448.389A5.5 5.5 0 0113.89 6.11l.311.31h-2.432a.75.75 0 000 1.5h4.243a.75.75 0 00.53-.219z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
||||
<div class="flex flex-col flex-shrink-0 self-end">
|
||||
<Tooltip content={enable ? $i18n.t('Enabled') : $i18n.t('Disabled')}>
|
||||
<Switch bind:state={enable} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 mt-2">
|
||||
<div class="flex flex-col w-full">
|
||||
<div class=" mb-0.5 text-xs text-gray-500">{$i18n.t('Key')}</div>
|
||||
|
||||
<div class="flex-1">
|
||||
<SensitiveInput
|
||||
className="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-none"
|
||||
bind:value={key}
|
||||
placeholder={$i18n.t('API Key')}
|
||||
required={!ollama}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col w-full">
|
||||
<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Prefix ID')}</div>
|
||||
|
||||
<div class="flex-1">
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
'Prefix ID is used to avoid conflicts with other connections by adding a prefix to the model IDs - leave empty to disable'
|
||||
)}
|
||||
>
|
||||
<input
|
||||
class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-none"
|
||||
type="text"
|
||||
bind:value={prefixId}
|
||||
placeholder={$i18n.t('Prefix ID')}
|
||||
autocomplete="off"
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-100 dark:border-gray-700/10 my-2.5 w-full" />
|
||||
|
||||
<div class="flex flex-col w-full">
|
||||
<div class="mb-1 flex justify-between">
|
||||
<div class="text-xs text-gray-500">{$i18n.t('Model IDs')}</div>
|
||||
</div>
|
||||
|
||||
{#if modelIds.length > 0}
|
||||
<div class="flex flex-col">
|
||||
{#each modelIds as modelId, modelIdx}
|
||||
<div class=" flex gap-2 w-full justify-between items-center">
|
||||
<div class=" text-sm flex-1 py-1 rounded-lg">
|
||||
{modelId}
|
||||
</div>
|
||||
<div class="flex-shrink-0">
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => {
|
||||
modelIds = modelIds.filter((_, idx) => idx !== modelIdx);
|
||||
}}
|
||||
>
|
||||
<Minus strokeWidth="2" className="size-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-gray-500 text-xs text-center py-2 px-10">
|
||||
{#if ollama}
|
||||
{$i18n.t('Leave empty to include all models from "{{URL}}/api/tags" endpoint', {
|
||||
URL: url
|
||||
})}
|
||||
{:else}
|
||||
{$i18n.t('Leave empty to include all models from "{{URL}}/models" endpoint', {
|
||||
URL: url
|
||||
})}
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-100 dark:border-gray-700/10 my-2.5 w-full" />
|
||||
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
class="w-full py-1 text-sm rounded-lg bg-transparent {modelId
|
||||
? ''
|
||||
: 'text-gray-500'} placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-none"
|
||||
bind:value={modelId}
|
||||
placeholder={$i18n.t('Add a model ID')}
|
||||
/>
|
||||
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => {
|
||||
addModelHandler();
|
||||
}}
|
||||
>
|
||||
<Plus className="size-3.5" strokeWidth="2" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end pt-3 text-sm font-medium gap-1.5">
|
||||
{#if edit}
|
||||
<button
|
||||
class="px-3.5 py-1.5 text-sm font-medium dark:bg-black dark:hover:bg-gray-900 dark:text-white bg-white text-black hover:bg-gray-100 transition rounded-full flex flex-row space-x-1 items-center"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
onDelete();
|
||||
show = false;
|
||||
}}
|
||||
>
|
||||
{$i18n.t('Delete')}
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<button
|
||||
class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full flex flex-row space-x-1 items-center {loading
|
||||
? ' cursor-not-allowed'
|
||||
: ''}"
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
>
|
||||
{$i18n.t('Save')}
|
||||
|
||||
{#if loading}
|
||||
<div class="ml-2 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;
|
||||
}
|
||||
@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>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
@@ -16,7 +16,7 @@
|
||||
<div
|
||||
class="flex w-full justify-between items-center text-lg font-medium self-center font-primary"
|
||||
>
|
||||
<div class=" flex-shrink-0">
|
||||
<div class=" shrink-0">
|
||||
{$i18n.t('Manage Ollama')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
import SensitiveInput from '$lib/components/common/SensitiveInput.svelte';
|
||||
import AddConnectionModal from './AddConnectionModal.svelte';
|
||||
import AddConnectionModal from '$lib/components/AddConnectionModal.svelte';
|
||||
|
||||
import Cog6 from '$lib/components/icons/Cog6.svelte';
|
||||
import Wrench from '$lib/components/icons/Wrench.svelte';
|
||||
@@ -56,7 +56,7 @@
|
||||
{/if}
|
||||
|
||||
<input
|
||||
class="w-full text-sm bg-transparent outline-none"
|
||||
class="w-full text-sm bg-transparent outline-hidden"
|
||||
placeholder={$i18n.t('Enter URL (e.g. http://localhost:11434)')}
|
||||
bind:value={url}
|
||||
/>
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
import SensitiveInput from '$lib/components/common/SensitiveInput.svelte';
|
||||
import Cog6 from '$lib/components/icons/Cog6.svelte';
|
||||
import AddConnectionModal from './AddConnectionModal.svelte';
|
||||
import AddConnectionModal from '$lib/components/AddConnectionModal.svelte';
|
||||
|
||||
import { connect } from 'socket.io-client';
|
||||
|
||||
export let onDelete = () => {};
|
||||
@@ -53,7 +54,7 @@
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1 relative">
|
||||
<input
|
||||
class=" outline-none w-full bg-transparent {pipeline ? 'pr-8' : ''}"
|
||||
class=" outline-hidden w-full bg-transparent {pipeline ? 'pr-8' : ''}"
|
||||
placeholder={$i18n.t('API Base URL')}
|
||||
bind:value={url}
|
||||
autocomplete="off"
|
||||
@@ -84,7 +85,7 @@
|
||||
</div>
|
||||
|
||||
<SensitiveInput
|
||||
inputClassName=" outline-none bg-transparent w-full"
|
||||
inputClassName=" outline-hidden bg-transparent w-full"
|
||||
placeholder={$i18n.t('API Key')}
|
||||
bind:value={key}
|
||||
/>
|
||||
|
||||
@@ -119,7 +119,7 @@
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<hr class=" dark:border-gray-850 my-1" />
|
||||
<hr class="border-gray-100 dark:border-gray-850 my-1" />
|
||||
|
||||
{#if $config?.features.enable_admin_export ?? true}
|
||||
<div class=" flex w-full justify-between">
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { models, user } from '$lib/stores';
|
||||
import { models, settings, user, config } from '$lib/stores';
|
||||
import { createEventDispatcher, onMount, getContext, tick } from 'svelte';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
@@ -16,49 +16,69 @@
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
let config = null;
|
||||
let evaluationConfig = null;
|
||||
let showAddModel = false;
|
||||
|
||||
const submitHandler = async () => {
|
||||
config = await updateConfig(localStorage.token, config).catch((err) => {
|
||||
evaluationConfig = await updateConfig(localStorage.token, evaluationConfig).catch((err) => {
|
||||
toast.error(err);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (config) {
|
||||
if (evaluationConfig) {
|
||||
toast.success('Settings saved successfully');
|
||||
models.set(await getModels(localStorage.token));
|
||||
models.set(
|
||||
await getModels(
|
||||
localStorage.token,
|
||||
$config?.features?.enable_direct_connections && ($settings?.directConnections ?? null)
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const addModelHandler = async (model) => {
|
||||
config.EVALUATION_ARENA_MODELS.push(model);
|
||||
config.EVALUATION_ARENA_MODELS = [...config.EVALUATION_ARENA_MODELS];
|
||||
evaluationConfig.EVALUATION_ARENA_MODELS.push(model);
|
||||
evaluationConfig.EVALUATION_ARENA_MODELS = [...evaluationConfig.EVALUATION_ARENA_MODELS];
|
||||
|
||||
await submitHandler();
|
||||
models.set(await getModels(localStorage.token));
|
||||
models.set(
|
||||
await getModels(
|
||||
localStorage.token,
|
||||
$config?.features?.enable_direct_connections && ($settings?.directConnections ?? null)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const editModelHandler = async (model, modelIdx) => {
|
||||
config.EVALUATION_ARENA_MODELS[modelIdx] = model;
|
||||
config.EVALUATION_ARENA_MODELS = [...config.EVALUATION_ARENA_MODELS];
|
||||
evaluationConfig.EVALUATION_ARENA_MODELS[modelIdx] = model;
|
||||
evaluationConfig.EVALUATION_ARENA_MODELS = [...evaluationConfig.EVALUATION_ARENA_MODELS];
|
||||
|
||||
await submitHandler();
|
||||
models.set(await getModels(localStorage.token));
|
||||
models.set(
|
||||
await getModels(
|
||||
localStorage.token,
|
||||
$config?.features?.enable_direct_connections && ($settings?.directConnections ?? null)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const deleteModelHandler = async (modelIdx) => {
|
||||
config.EVALUATION_ARENA_MODELS = config.EVALUATION_ARENA_MODELS.filter(
|
||||
evaluationConfig.EVALUATION_ARENA_MODELS = evaluationConfig.EVALUATION_ARENA_MODELS.filter(
|
||||
(m, mIdx) => mIdx !== modelIdx
|
||||
);
|
||||
|
||||
await submitHandler();
|
||||
models.set(await getModels(localStorage.token));
|
||||
models.set(
|
||||
await getModels(
|
||||
localStorage.token,
|
||||
$config?.features?.enable_direct_connections && ($settings?.directConnections ?? null)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
if ($user.role === 'admin') {
|
||||
config = await getConfig(localStorage.token).catch((err) => {
|
||||
evaluationConfig = await getConfig(localStorage.token).catch((err) => {
|
||||
toast.error(err);
|
||||
return null;
|
||||
});
|
||||
@@ -81,61 +101,67 @@
|
||||
}}
|
||||
>
|
||||
<div class="overflow-y-scroll scrollbar-hidden h-full">
|
||||
{#if config !== null}
|
||||
{#if evaluationConfig !== null}
|
||||
<div class="">
|
||||
<div class="text-sm font-medium mb-2">{$i18n.t('General Settings')}</div>
|
||||
<div class="mb-3">
|
||||
<div class=" mb-2.5 text-base font-medium">{$i18n.t('General')}</div>
|
||||
|
||||
<div class=" mb-2">
|
||||
<div class="flex justify-between items-center text-xs">
|
||||
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
|
||||
|
||||
<div class="mb-2.5 flex w-full justify-between">
|
||||
<div class=" text-xs font-medium">{$i18n.t('Arena Models')}</div>
|
||||
|
||||
<Tooltip content={$i18n.t(`Message rating should be enabled to use this feature`)}>
|
||||
<Switch bind:state={config.ENABLE_EVALUATION_ARENA_MODELS} />
|
||||
<Switch bind:state={evaluationConfig.ENABLE_EVALUATION_ARENA_MODELS} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if config.ENABLE_EVALUATION_ARENA_MODELS}
|
||||
<hr class=" border-gray-50 dark:border-gray-700/10 my-2" />
|
||||
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<div class="text-sm font-medium">{$i18n.t('Manage Arena 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>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
{#if (config?.EVALUATION_ARENA_MODELS ?? []).length > 0}
|
||||
{#each config.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.`
|
||||
)}
|
||||
{#if evaluationConfig.ENABLE_EVALUATION_ARENA_MODELS}
|
||||
<div class="mb-3">
|
||||
<div class=" mb-2.5 text-base font-medium flex justify-between items-center">
|
||||
<div>
|
||||
{$i18n.t('Manage')}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<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 dark:border-gray-850 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>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -245,7 +245,7 @@
|
||||
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-none"
|
||||
class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden"
|
||||
type="text"
|
||||
bind:value={name}
|
||||
placeholder={$i18n.t('Model Name')}
|
||||
@@ -260,7 +260,7 @@
|
||||
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-none"
|
||||
class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden"
|
||||
type="text"
|
||||
bind:value={id}
|
||||
placeholder={$i18n.t('Model ID')}
|
||||
@@ -277,7 +277,7 @@
|
||||
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-none"
|
||||
class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden"
|
||||
type="text"
|
||||
bind:value={description}
|
||||
placeholder={$i18n.t('Enter description')}
|
||||
@@ -324,7 +324,7 @@
|
||||
<div class=" text-sm flex-1 py-1 rounded-lg">
|
||||
{$models.find((model) => model.id === modelId)?.name}
|
||||
</div>
|
||||
<div class="flex-shrink-0">
|
||||
<div class="shrink-0">
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => {
|
||||
@@ -350,7 +350,7 @@
|
||||
<select
|
||||
class="w-full py-1 text-sm rounded-lg bg-transparent {selectedModelId
|
||||
? ''
|
||||
: 'text-gray-500'} placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-none"
|
||||
: 'text-gray-500'} placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden"
|
||||
bind:value={selectedModelId}
|
||||
>
|
||||
<option value="">{$i18n.t('Select a model')}</option>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
|
||||
<div class="w-full flex flex-col">
|
||||
<div class="flex items-center gap-1">
|
||||
<div class="flex-shrink-0 line-clamp-1">
|
||||
<div class="shrink-0 line-clamp-1">
|
||||
{model.name}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { getBackendConfig, getWebhookUrl, updateWebhookUrl } from '$lib/apis';
|
||||
import DOMPurify from 'dompurify';
|
||||
|
||||
import { getBackendConfig, getVersionUpdates, getWebhookUrl, updateWebhookUrl } from '$lib/apis';
|
||||
import {
|
||||
getAdminConfig,
|
||||
getLdapConfig,
|
||||
@@ -11,7 +13,9 @@
|
||||
import SensitiveInput from '$lib/components/common/SensitiveInput.svelte';
|
||||
import Switch from '$lib/components/common/Switch.svelte';
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
import { config } from '$lib/stores';
|
||||
import { WEBUI_BUILD_HASH, WEBUI_VERSION } from '$lib/constants';
|
||||
import { config, showChangelog } from '$lib/stores';
|
||||
import { compareVersion } from '$lib/utils';
|
||||
import { onMount, getContext } from 'svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
@@ -19,6 +23,12 @@
|
||||
|
||||
export let saveHandler: Function;
|
||||
|
||||
let updateAvailable = null;
|
||||
let version = {
|
||||
current: '',
|
||||
latest: ''
|
||||
};
|
||||
|
||||
let adminConfig = null;
|
||||
let webhookUrl = '';
|
||||
|
||||
@@ -39,6 +49,21 @@
|
||||
ciphers: ''
|
||||
};
|
||||
|
||||
const checkForVersionUpdates = async () => {
|
||||
updateAvailable = null;
|
||||
version = await getVersionUpdates(localStorage.token).catch((error) => {
|
||||
return {
|
||||
current: WEBUI_VERSION,
|
||||
latest: WEBUI_VERSION
|
||||
};
|
||||
});
|
||||
|
||||
console.log(version);
|
||||
|
||||
updateAvailable = compareVersion(version.latest, version.current);
|
||||
console.log(updateAvailable);
|
||||
};
|
||||
|
||||
const updateLdapServerHandler = async () => {
|
||||
if (!ENABLE_LDAP) return;
|
||||
const res = await updateLdapServer(localStorage.token, LDAP_SERVER).catch((error) => {
|
||||
@@ -63,6 +88,8 @@
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
checkForVersionUpdates();
|
||||
|
||||
await Promise.all([
|
||||
(async () => {
|
||||
adminConfig = await getAdminConfig(localStorage.token);
|
||||
@@ -87,381 +114,540 @@
|
||||
updateHandler();
|
||||
}}
|
||||
>
|
||||
<div class=" space-y-3 overflow-y-scroll scrollbar-hidden h-full">
|
||||
<div class="mt-0.5 space-y-3 overflow-y-scroll scrollbar-hidden h-full">
|
||||
{#if adminConfig !== null}
|
||||
<div>
|
||||
<div class=" mb-3 text-sm font-medium">{$i18n.t('General Settings')}</div>
|
||||
<div class="">
|
||||
<div class="mb-3.5">
|
||||
<div class=" mb-2.5 text-base font-medium">{$i18n.t('General')}</div>
|
||||
|
||||
<div class=" flex w-full justify-between pr-2">
|
||||
<div class=" self-center text-xs font-medium">{$i18n.t('Enable New Sign Ups')}</div>
|
||||
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
|
||||
|
||||
<Switch bind:state={adminConfig.ENABLE_SIGNUP} />
|
||||
</div>
|
||||
|
||||
<div class=" my-3 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">{$i18n.t('Default User Role')}</div>
|
||||
<div class="flex items-center relative">
|
||||
<select
|
||||
class="dark:bg-gray-900 w-fit pr-8 rounded px-2 text-xs bg-transparent outline-none text-right"
|
||||
bind:value={adminConfig.DEFAULT_USER_ROLE}
|
||||
placeholder="Select a role"
|
||||
>
|
||||
<option value="pending">{$i18n.t('pending')}</option>
|
||||
<option value="user">{$i18n.t('user')}</option>
|
||||
<option value="admin">{$i18n.t('admin')}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class=" flex w-full justify-between pr-2 my-3">
|
||||
<div class=" self-center text-xs font-medium">{$i18n.t('Enable API Key')}</div>
|
||||
|
||||
<Switch bind:state={adminConfig.ENABLE_API_KEY} />
|
||||
</div>
|
||||
|
||||
{#if adminConfig?.ENABLE_API_KEY}
|
||||
<div class=" flex w-full justify-between pr-2 my-3">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('API Key Endpoint Restrictions')}
|
||||
<div class="mb-2.5">
|
||||
<div class=" mb-1 text-xs font-medium 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>
|
||||
|
||||
<Switch bind:state={adminConfig.ENABLE_API_KEY_ENDPOINT_RESTRICTIONS} />
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
{#if adminConfig?.ENABLE_API_KEY_ENDPOINT_RESTRICTIONS}
|
||||
<div class=" flex w-full flex-col pr-2">
|
||||
<div class=" text-xs font-medium">
|
||||
{$i18n.t('Allowed Endpoints')}
|
||||
<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>
|
||||
|
||||
<input
|
||||
class="w-full mt-1 rounded-lg text-sm dark:text-gray-300 bg-transparent outline-none"
|
||||
type="text"
|
||||
placeholder={`e.g.) /api/v1/messages, /api/v1/channels`}
|
||||
bind:value={adminConfig.API_KEY_ALLOWED_ENDPOINTS}
|
||||
/>
|
||||
<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-medium"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
checkForVersionUpdates();
|
||||
}}
|
||||
>
|
||||
{$i18n.t('Check for updates')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
|
||||
<!-- https://docs.openwebui.com/getting-started/advanced-topics/api-endpoints -->
|
||||
<a
|
||||
href="https://docs.openwebui.com/getting-started/advanced-topics/api-endpoints"
|
||||
target="_blank"
|
||||
class=" text-gray-300 font-medium underline"
|
||||
>
|
||||
{$i18n.t('To learn more about available endpoints, visit our documentation.')}
|
||||
<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>
|
||||
|
||||
<a
|
||||
class="flex-shrink-0 text-xs font-medium 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>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<hr class=" border-gray-50 dark:border-gray-850 my-2" />
|
||||
|
||||
<div class="my-3 flex w-full items-center justify-between pr-2">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Show Admin Details in Account Pending Overlay')}
|
||||
</div>
|
||||
|
||||
<Switch bind:state={adminConfig.SHOW_ADMIN_DETAILS} />
|
||||
</div>
|
||||
|
||||
<div class="my-3 flex w-full items-center justify-between pr-2">
|
||||
<div class=" self-center text-xs font-medium">{$i18n.t('Enable Community Sharing')}</div>
|
||||
|
||||
<Switch bind:state={adminConfig.ENABLE_COMMUNITY_SHARING} />
|
||||
</div>
|
||||
|
||||
<div class="my-3 flex w-full items-center justify-between pr-2">
|
||||
<div class=" self-center text-xs font-medium">{$i18n.t('Enable Message Rating')}</div>
|
||||
|
||||
<Switch bind:state={adminConfig.ENABLE_MESSAGE_RATING} />
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-50 dark:border-gray-850 my-2" />
|
||||
|
||||
<div class=" w-full justify-between">
|
||||
<div class="flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">{$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-none"
|
||||
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">
|
||||
{$i18n.t(
|
||||
'Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-50 dark:border-gray-850 my-2" />
|
||||
|
||||
<div class=" w-full justify-between">
|
||||
<div class="flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">{$i18n.t('JWT Expiration')}</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-none"
|
||||
type="text"
|
||||
placeholder={`e.g.) "30m","1h", "10d". `}
|
||||
bind:value={adminConfig.JWT_EXPIRES_IN}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
|
||||
{$i18n.t('Valid time units:')}
|
||||
<span class=" text-gray-300 font-medium"
|
||||
>{$i18n.t("'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.")}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-50 dark:border-gray-850 my-2" />
|
||||
|
||||
<div class=" w-full justify-between">
|
||||
<div class="flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">{$i18n.t('Webhook 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-none"
|
||||
type="text"
|
||||
placeholder={`https://example.com/webhook`}
|
||||
bind:value={webhookUrl}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-50 dark:border-gray-850 my-2" />
|
||||
|
||||
<div class="pt-1 flex w-full justify-between pr-2">
|
||||
<div class=" self-center text-sm font-medium">
|
||||
{$i18n.t('Channels')} ({$i18n.t('Beta')})
|
||||
</div>
|
||||
|
||||
<Switch bind:state={adminConfig.ENABLE_CHANNELS} />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<hr class=" border-gray-50 dark:border-gray-850" />
|
||||
|
||||
<div class=" space-y-3">
|
||||
<div class="mt-2 space-y-2 pr-1.5">
|
||||
<div class="flex justify-between items-center text-sm">
|
||||
<div class=" font-medium">{$i18n.t('LDAP')}</div>
|
||||
|
||||
<div class="mt-1">
|
||||
<Switch
|
||||
bind:state={ENABLE_LDAP}
|
||||
on:change={async () => {
|
||||
updateLdapConfig(localStorage.token, ENABLE_LDAP);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if ENABLE_LDAP}
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex w-full gap-2">
|
||||
<div class="w-full">
|
||||
<div class=" self-center text-xs font-medium min-w-fit mb-1">
|
||||
{$i18n.t('Label')}
|
||||
<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>
|
||||
<input
|
||||
class="w-full bg-transparent outline-none py-0.5"
|
||||
required
|
||||
placeholder={$i18n.t('Enter server label')}
|
||||
bind:value={LDAP_SERVER.label}
|
||||
/>
|
||||
|
||||
{#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-medium 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>
|
||||
<div class="w-full"></div>
|
||||
</div>
|
||||
<div class="flex w-full gap-2">
|
||||
<div class="w-full">
|
||||
<div class=" self-center text-xs font-medium min-w-fit mb-1">
|
||||
{$i18n.t('Host')}
|
||||
</div>
|
||||
<input
|
||||
class="w-full bg-transparent outline-none py-0.5"
|
||||
required
|
||||
placeholder={$i18n.t('Enter server host')}
|
||||
bind:value={LDAP_SERVER.host}
|
||||
/>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<div class=" self-center text-xs font-medium min-w-fit mb-1">
|
||||
{$i18n.t('Port')}
|
||||
</div>
|
||||
<Tooltip
|
||||
placement="top-start"
|
||||
content={$i18n.t('Default to 389 or 636 if TLS is enabled')}
|
||||
className="w-full"
|
||||
>
|
||||
<input
|
||||
class="w-full bg-transparent outline-none py-0.5"
|
||||
type="number"
|
||||
placeholder={$i18n.t('Enter server port')}
|
||||
bind:value={LDAP_SERVER.port}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex w-full gap-2">
|
||||
<div class="w-full">
|
||||
<div class=" self-center text-xs font-medium min-w-fit mb-1">
|
||||
{$i18n.t('Application DN')}
|
||||
</div>
|
||||
<Tooltip
|
||||
content={$i18n.t('The Application Account DN you bind with for search')}
|
||||
placement="top-start"
|
||||
>
|
||||
<input
|
||||
class="w-full bg-transparent outline-none py-0.5"
|
||||
required
|
||||
placeholder={$i18n.t('Enter Application DN')}
|
||||
bind:value={LDAP_SERVER.app_dn}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<div class=" self-center text-xs font-medium min-w-fit mb-1">
|
||||
{$i18n.t('Application DN Password')}
|
||||
</div>
|
||||
<SensitiveInput
|
||||
placeholder={$i18n.t('Enter Application DN Password')}
|
||||
bind:value={LDAP_SERVER.app_dn_password}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex w-full gap-2">
|
||||
<div class="w-full">
|
||||
<div class=" self-center text-xs font-medium min-w-fit mb-1">
|
||||
{$i18n.t('Attribute for Mail')}
|
||||
</div>
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
'The LDAP attribute that maps to the mail that users use to sign in.'
|
||||
)}
|
||||
placement="top-start"
|
||||
>
|
||||
<input
|
||||
class="w-full bg-transparent outline-none py-0.5"
|
||||
required
|
||||
placeholder={$i18n.t('Example: mail')}
|
||||
bind:value={LDAP_SERVER.attribute_for_mail}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex w-full gap-2">
|
||||
<div class="w-full">
|
||||
<div class=" self-center text-xs font-medium min-w-fit mb-1">
|
||||
{$i18n.t('Attribute for Username')}
|
||||
</div>
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
'The LDAP attribute that maps to the username that users use to sign in.'
|
||||
)}
|
||||
placement="top-start"
|
||||
>
|
||||
<input
|
||||
class="w-full bg-transparent outline-none py-0.5"
|
||||
required
|
||||
placeholder={$i18n.t('Example: sAMAccountName or uid or userPrincipalName')}
|
||||
bind:value={LDAP_SERVER.attribute_for_username}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex w-full gap-2">
|
||||
<div class="w-full">
|
||||
<div class=" self-center text-xs font-medium min-w-fit mb-1">
|
||||
{$i18n.t('Search Base')}
|
||||
</div>
|
||||
<Tooltip content={$i18n.t('The base to search for users')} placement="top-start">
|
||||
<input
|
||||
class="w-full bg-transparent outline-none py-0.5"
|
||||
required
|
||||
placeholder={$i18n.t('Example: ou=users,dc=foo,dc=example')}
|
||||
bind:value={LDAP_SERVER.search_base}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex w-full gap-2">
|
||||
<div class="w-full">
|
||||
<div class=" self-center text-xs font-medium min-w-fit mb-1">
|
||||
{$i18n.t('Search Filters')}
|
||||
</div>
|
||||
<input
|
||||
class="w-full bg-transparent outline-none py-0.5"
|
||||
placeholder={$i18n.t('Example: (&(objectClass=inetOrgPerson)(uid=%s))')}
|
||||
bind:value={LDAP_SERVER.search_filters}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-xs text-gray-400 dark:text-gray-500">
|
||||
<a
|
||||
class=" text-gray-300 font-medium underline"
|
||||
href="https://ldap.com/ldap-filters/"
|
||||
target="_blank"
|
||||
|
||||
<!-- <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-medium"
|
||||
>
|
||||
{$i18n.t('Click here for filter guides.')}
|
||||
</a>
|
||||
{$i18n.t('Activate')}
|
||||
</button> -->
|
||||
</div>
|
||||
<div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class=" mb-2.5 text-base font-medium">{$i18n.t('Authentication')}</div>
|
||||
|
||||
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
|
||||
|
||||
<div class=" mb-2.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">{$i18n.t('Default User Role')}</div>
|
||||
<div class="flex items-center relative">
|
||||
<select
|
||||
class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 text-xs bg-transparent outline-hidden text-right"
|
||||
bind:value={adminConfig.DEFAULT_USER_ROLE}
|
||||
placeholder="Select a role"
|
||||
>
|
||||
<option value="pending">{$i18n.t('pending')}</option>
|
||||
<option value="user">{$i18n.t('user')}</option>
|
||||
<option value="admin">{$i18n.t('admin')}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class=" mb-2.5 flex w-full justify-between pr-2">
|
||||
<div class=" self-center text-xs font-medium">{$i18n.t('Enable New Sign Ups')}</div>
|
||||
|
||||
<Switch bind:state={adminConfig.ENABLE_SIGNUP} />
|
||||
</div>
|
||||
|
||||
<div class="mb-2.5 flex w-full items-center justify-between pr-2">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Show Admin Details in Account Pending Overlay')}
|
||||
</div>
|
||||
|
||||
<Switch bind:state={adminConfig.SHOW_ADMIN_DETAILS} />
|
||||
</div>
|
||||
|
||||
<div class="mb-2.5 flex w-full justify-between pr-2">
|
||||
<div class=" self-center text-xs font-medium">{$i18n.t('Enable API Key')}</div>
|
||||
|
||||
<Switch bind:state={adminConfig.ENABLE_API_KEY} />
|
||||
</div>
|
||||
|
||||
{#if adminConfig?.ENABLE_API_KEY}
|
||||
<div class="mb-2.5 flex w-full justify-between pr-2">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('API Key Endpoint Restrictions')}
|
||||
</div>
|
||||
|
||||
<Switch bind:state={adminConfig.ENABLE_API_KEY_ENDPOINT_RESTRICTIONS} />
|
||||
</div>
|
||||
|
||||
{#if adminConfig?.ENABLE_API_KEY_ENDPOINT_RESTRICTIONS}
|
||||
<div class=" flex w-full flex-col pr-2">
|
||||
<div class=" text-xs font-medium">
|
||||
{$i18n.t('Allowed Endpoints')}
|
||||
</div>
|
||||
|
||||
<input
|
||||
class="w-full mt-1 rounded-lg text-sm dark:text-gray-300 bg-transparent outline-hidden"
|
||||
type="text"
|
||||
placeholder={`e.g.) /api/v1/messages, /api/v1/channels`}
|
||||
bind:value={adminConfig.API_KEY_ALLOWED_ENDPOINTS}
|
||||
/>
|
||||
|
||||
<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
|
||||
<!-- https://docs.openwebui.com/getting-started/advanced-topics/api-endpoints -->
|
||||
<a
|
||||
href="https://docs.openwebui.com/getting-started/api-endpoints"
|
||||
target="_blank"
|
||||
class=" text-gray-300 font-medium underline"
|
||||
>
|
||||
{$i18n.t('To learn more about available endpoints, visit our documentation.')}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<div class=" mb-2.5 w-full justify-between">
|
||||
<div class="flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">{$i18n.t('JWT Expiration')}</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.) "30m","1h", "10d". `}
|
||||
bind:value={adminConfig.JWT_EXPIRES_IN}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
|
||||
{$i18n.t('Valid time units:')}
|
||||
<span class=" text-gray-300 font-medium"
|
||||
>{$i18n.t("'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.")}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class=" space-y-3">
|
||||
<div class="mt-2 space-y-2 pr-1.5">
|
||||
<div class="flex justify-between items-center text-sm">
|
||||
<div class=" font-medium">{$i18n.t('TLS')}</div>
|
||||
<div class=" font-medium">{$i18n.t('LDAP')}</div>
|
||||
|
||||
<div class="mt-1">
|
||||
<Switch bind:state={LDAP_SERVER.use_tls} />
|
||||
<Switch
|
||||
bind:state={ENABLE_LDAP}
|
||||
on:change={async () => {
|
||||
updateLdapConfig(localStorage.token, ENABLE_LDAP);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{#if LDAP_SERVER.use_tls}
|
||||
<div class="flex w-full gap-2">
|
||||
<div class="w-full">
|
||||
<div class=" self-center text-xs font-medium min-w-fit mb-1 mt-1">
|
||||
{$i18n.t('Certificate Path')}
|
||||
</div>
|
||||
<input
|
||||
class="w-full bg-transparent outline-none py-0.5"
|
||||
required
|
||||
placeholder={$i18n.t('Enter certificate path')}
|
||||
bind:value={LDAP_SERVER.certificate_path}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex w-full gap-2">
|
||||
<div class="w-full">
|
||||
<div class=" self-center text-xs font-medium min-w-fit mb-1">
|
||||
{$i18n.t('Ciphers')}
|
||||
</div>
|
||||
<Tooltip content={$i18n.t('Default to ALL')} placement="top-start">
|
||||
|
||||
{#if ENABLE_LDAP}
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex w-full gap-2">
|
||||
<div class="w-full">
|
||||
<div class=" self-center text-xs font-medium min-w-fit mb-1">
|
||||
{$i18n.t('Label')}
|
||||
</div>
|
||||
<input
|
||||
class="w-full bg-transparent outline-none py-0.5"
|
||||
placeholder={$i18n.t('Example: ALL')}
|
||||
bind:value={LDAP_SERVER.ciphers}
|
||||
class="w-full bg-transparent outline-hidden py-0.5"
|
||||
required
|
||||
placeholder={$i18n.t('Enter server label')}
|
||||
bind:value={LDAP_SERVER.label}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div class="w-full"></div>
|
||||
</div>
|
||||
<div class="flex w-full gap-2">
|
||||
<div class="w-full">
|
||||
<div class=" self-center text-xs font-medium min-w-fit mb-1">
|
||||
{$i18n.t('Host')}
|
||||
</div>
|
||||
<input
|
||||
class="w-full bg-transparent outline-hidden py-0.5"
|
||||
required
|
||||
placeholder={$i18n.t('Enter server host')}
|
||||
bind:value={LDAP_SERVER.host}
|
||||
/>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<div class=" self-center text-xs font-medium min-w-fit mb-1">
|
||||
{$i18n.t('Port')}
|
||||
</div>
|
||||
<Tooltip
|
||||
placement="top-start"
|
||||
content={$i18n.t('Default to 389 or 636 if TLS is enabled')}
|
||||
className="w-full"
|
||||
>
|
||||
<input
|
||||
class="w-full bg-transparent outline-hidden py-0.5"
|
||||
type="number"
|
||||
placeholder={$i18n.t('Enter server port')}
|
||||
bind:value={LDAP_SERVER.port}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex w-full gap-2">
|
||||
<div class="w-full">
|
||||
<div class=" self-center text-xs font-medium min-w-fit mb-1">
|
||||
{$i18n.t('Application DN')}
|
||||
</div>
|
||||
<Tooltip
|
||||
content={$i18n.t('The Application Account DN you bind with for search')}
|
||||
placement="top-start"
|
||||
>
|
||||
<input
|
||||
class="w-full bg-transparent outline-hidden py-0.5"
|
||||
required
|
||||
placeholder={$i18n.t('Enter Application DN')}
|
||||
bind:value={LDAP_SERVER.app_dn}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<div class=" self-center text-xs font-medium min-w-fit mb-1">
|
||||
{$i18n.t('Application DN Password')}
|
||||
</div>
|
||||
<SensitiveInput
|
||||
placeholder={$i18n.t('Enter Application DN Password')}
|
||||
bind:value={LDAP_SERVER.app_dn_password}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex w-full gap-2">
|
||||
<div class="w-full">
|
||||
<div class=" self-center text-xs font-medium min-w-fit mb-1">
|
||||
{$i18n.t('Attribute for Mail')}
|
||||
</div>
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
'The LDAP attribute that maps to the mail that users use to sign in.'
|
||||
)}
|
||||
placement="top-start"
|
||||
>
|
||||
<input
|
||||
class="w-full bg-transparent outline-hidden py-0.5"
|
||||
required
|
||||
placeholder={$i18n.t('Example: mail')}
|
||||
bind:value={LDAP_SERVER.attribute_for_mail}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex w-full gap-2">
|
||||
<div class="w-full">
|
||||
<div class=" self-center text-xs font-medium min-w-fit mb-1">
|
||||
{$i18n.t('Attribute for Username')}
|
||||
</div>
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
'The LDAP attribute that maps to the username that users use to sign in.'
|
||||
)}
|
||||
placement="top-start"
|
||||
>
|
||||
<input
|
||||
class="w-full bg-transparent outline-hidden py-0.5"
|
||||
required
|
||||
placeholder={$i18n.t(
|
||||
'Example: sAMAccountName or uid or userPrincipalName'
|
||||
)}
|
||||
bind:value={LDAP_SERVER.attribute_for_username}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex w-full gap-2">
|
||||
<div class="w-full">
|
||||
<div class=" self-center text-xs font-medium min-w-fit mb-1">
|
||||
{$i18n.t('Search Base')}
|
||||
</div>
|
||||
<Tooltip
|
||||
content={$i18n.t('The base to search for users')}
|
||||
placement="top-start"
|
||||
>
|
||||
<input
|
||||
class="w-full bg-transparent outline-hidden py-0.5"
|
||||
required
|
||||
placeholder={$i18n.t('Example: ou=users,dc=foo,dc=example')}
|
||||
bind:value={LDAP_SERVER.search_base}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex w-full gap-2">
|
||||
<div class="w-full">
|
||||
<div class=" self-center text-xs font-medium min-w-fit mb-1">
|
||||
{$i18n.t('Search Filters')}
|
||||
</div>
|
||||
<input
|
||||
class="w-full bg-transparent outline-hidden py-0.5"
|
||||
placeholder={$i18n.t('Example: (&(objectClass=inetOrgPerson)(uid=%s))')}
|
||||
bind:value={LDAP_SERVER.search_filters}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-xs text-gray-400 dark:text-gray-500">
|
||||
<a
|
||||
class=" text-gray-300 font-medium underline"
|
||||
href="https://ldap.com/ldap-filters/"
|
||||
target="_blank"
|
||||
>
|
||||
{$i18n.t('Click here for filter guides.')}
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex justify-between items-center text-sm">
|
||||
<div class=" font-medium">{$i18n.t('TLS')}</div>
|
||||
|
||||
<div class="mt-1">
|
||||
<Switch bind:state={LDAP_SERVER.use_tls} />
|
||||
</div>
|
||||
</div>
|
||||
{#if LDAP_SERVER.use_tls}
|
||||
<div class="flex w-full gap-2">
|
||||
<div class="w-full">
|
||||
<div class=" self-center text-xs font-medium min-w-fit mb-1 mt-1">
|
||||
{$i18n.t('Certificate Path')}
|
||||
</div>
|
||||
<input
|
||||
class="w-full bg-transparent outline-hidden py-0.5"
|
||||
required
|
||||
placeholder={$i18n.t('Enter certificate path')}
|
||||
bind:value={LDAP_SERVER.certificate_path}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex w-full gap-2">
|
||||
<div class="w-full">
|
||||
<div class=" self-center text-xs font-medium min-w-fit mb-1">
|
||||
{$i18n.t('Ciphers')}
|
||||
</div>
|
||||
<Tooltip content={$i18n.t('Default to ALL')} placement="top-start">
|
||||
<input
|
||||
class="w-full bg-transparent outline-hidden py-0.5"
|
||||
placeholder={$i18n.t('Example: ALL')}
|
||||
bind:value={LDAP_SERVER.ciphers}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div class="w-full"></div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="w-full"></div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class=" mb-2.5 text-base font-medium">{$i18n.t('Features')}</div>
|
||||
|
||||
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
|
||||
|
||||
<div class="mb-2.5 flex w-full items-center justify-between pr-2">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$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-medium">{$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-medium">
|
||||
{$i18n.t('Channels')} ({$i18n.t('Beta')})
|
||||
</div>
|
||||
|
||||
<Switch bind:state={adminConfig.ENABLE_CHANNELS} />
|
||||
</div>
|
||||
|
||||
<div class="mb-2.5 w-full justify-between">
|
||||
<div class="flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">{$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">
|
||||
{$i18n.t(
|
||||
'Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class=" w-full justify-between">
|
||||
<div class="flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">{$i18n.t('Webhook 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={`https://example.com/webhook`}
|
||||
bind:value={webhookUrl}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end pt-3 text-sm font-medium">
|
||||
|
||||
@@ -261,6 +261,9 @@
|
||||
} else if (config.engine === 'openai' && config.openai.OPENAI_API_KEY === '') {
|
||||
toast.error($i18n.t('OpenAI API Key is required.'));
|
||||
config.enabled = false;
|
||||
} else if (config.engine === 'gemini' && config.gemini.GEMINI_API_KEY === '') {
|
||||
toast.error($i18n.t('Gemini API Key is required.'));
|
||||
config.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,7 +287,7 @@
|
||||
<div class=" self-center text-xs font-medium">{$i18n.t('Image Generation Engine')}</div>
|
||||
<div class="flex items-center relative">
|
||||
<select
|
||||
class=" dark:bg-gray-900 w-fit pr-8 cursor-pointer rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
|
||||
class=" dark:bg-gray-900 w-fit pr-8 cursor-pointer rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
|
||||
bind:value={config.engine}
|
||||
placeholder={$i18n.t('Select Engine')}
|
||||
on:change={async () => {
|
||||
@@ -294,11 +297,12 @@
|
||||
<option value="openai">{$i18n.t('Default (Open AI)')}</option>
|
||||
<option value="comfyui">{$i18n.t('ComfyUI')}</option>
|
||||
<option value="automatic1111">{$i18n.t('Automatic1111')}</option>
|
||||
<option value="gemini">{$i18n.t('Gemini')}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr class=" dark:border-gray-850" />
|
||||
<hr class=" border-gray-100 dark:border-gray-850" />
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
{#if (config?.engine ?? 'automatic1111') === 'automatic1111'}
|
||||
@@ -307,7 +311,7 @@
|
||||
<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-none"
|
||||
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 URL (e.g. http://127.0.0.1:7860/)')}
|
||||
bind:value={config.automatic1111.AUTOMATIC1111_BASE_URL}
|
||||
/>
|
||||
@@ -386,7 +390,7 @@
|
||||
<Tooltip content={$i18n.t('Enter Sampler (e.g. Euler a)')} placement="top-start">
|
||||
<input
|
||||
list="sampler-list"
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||
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 Sampler (e.g. Euler a)')}
|
||||
bind:value={config.automatic1111.AUTOMATIC1111_SAMPLER}
|
||||
/>
|
||||
@@ -408,7 +412,7 @@
|
||||
<Tooltip content={$i18n.t('Enter Scheduler (e.g. Karras)')} placement="top-start">
|
||||
<input
|
||||
list="scheduler-list"
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||
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 Scheduler (e.g. Karras)')}
|
||||
bind:value={config.automatic1111.AUTOMATIC1111_SCHEDULER}
|
||||
/>
|
||||
@@ -429,7 +433,7 @@
|
||||
<div class="flex-1 mr-2">
|
||||
<Tooltip content={$i18n.t('Enter CFG Scale (e.g. 7.0)')} placement="top-start">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||
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 CFG Scale (e.g. 7.0)')}
|
||||
bind:value={config.automatic1111.AUTOMATIC1111_CFG_SCALE}
|
||||
/>
|
||||
@@ -443,7 +447,7 @@
|
||||
<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-none"
|
||||
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 URL (e.g. http://127.0.0.1:7860/)')}
|
||||
bind:value={config.comfyui.COMFYUI_BASE_URL}
|
||||
/>
|
||||
@@ -497,7 +501,7 @@
|
||||
|
||||
{#if config.comfyui.COMFYUI_WORKFLOW}
|
||||
<textarea
|
||||
class="w-full rounded-lg mb-1 py-2 px-4 text-xs bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none disabled:text-gray-600 resize-none"
|
||||
class="w-full rounded-lg mb-1 py-2 px-4 text-xs bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden disabled:text-gray-600 resize-none"
|
||||
rows="10"
|
||||
bind:value={config.comfyui.COMFYUI_WORKFLOW}
|
||||
required
|
||||
@@ -525,7 +529,7 @@
|
||||
/>
|
||||
|
||||
<button
|
||||
class="w-full text-sm font-medium py-2 bg-transparent hover:bg-gray-100 border border-dashed dark:border-gray-800 dark:hover:bg-gray-850 text-center rounded-xl"
|
||||
class="w-full text-sm font-medium py-2 bg-transparent hover:bg-gray-100 border border-dashed dark:border-gray-850 dark:hover:bg-gray-850 text-center rounded-xl"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
document.getElementById('upload-comfyui-workflow-input')?.click();
|
||||
@@ -548,7 +552,7 @@
|
||||
<div class="text-xs flex flex-col gap-1.5">
|
||||
{#each requiredWorkflowNodes as node}
|
||||
<div class="flex w-full items-center border dark:border-gray-850 rounded-lg">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="shrink-0">
|
||||
<div
|
||||
class=" capitalize line-clamp-1 font-medium px-3 py-1 w-20 text-center rounded-l-lg bg-green-500/10 text-green-700 dark:text-green-200"
|
||||
>
|
||||
@@ -558,7 +562,7 @@
|
||||
<div class="">
|
||||
<Tooltip content="Input Key (e.g. text, unet_name, steps)">
|
||||
<input
|
||||
class="py-1 px-3 w-24 text-xs text-center bg-transparent outline-none border-r dark:border-gray-850"
|
||||
class="py-1 px-3 w-24 text-xs text-center bg-transparent outline-hidden border-r dark:border-gray-850"
|
||||
placeholder="Key"
|
||||
bind:value={node.key}
|
||||
required
|
||||
@@ -572,7 +576,7 @@
|
||||
placement="top-start"
|
||||
>
|
||||
<input
|
||||
class="w-full py-1 px-4 rounded-r-lg text-xs bg-transparent outline-none"
|
||||
class="w-full py-1 px-4 rounded-r-lg text-xs bg-transparent outline-hidden"
|
||||
placeholder="Node Ids"
|
||||
bind:value={node.node_ids}
|
||||
/>
|
||||
@@ -593,7 +597,7 @@
|
||||
|
||||
<div class="flex gap-2 mb-1">
|
||||
<input
|
||||
class="flex-1 w-full text-sm bg-transparent outline-none"
|
||||
class="flex-1 w-full text-sm bg-transparent outline-hidden"
|
||||
placeholder={$i18n.t('API Base URL')}
|
||||
bind:value={config.openai.OPENAI_API_BASE_URL}
|
||||
required
|
||||
@@ -605,11 +609,29 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{:else if config?.engine === 'gemini'}
|
||||
<div>
|
||||
<div class=" mb-1.5 text-sm font-medium">{$i18n.t('Gemini API Config')}</div>
|
||||
|
||||
<div class="flex gap-2 mb-1">
|
||||
<input
|
||||
class="flex-1 w-full text-sm bg-transparent outline-none"
|
||||
placeholder={$i18n.t('API Base URL')}
|
||||
bind:value={config.gemini.GEMINI_API_BASE_URL}
|
||||
required
|
||||
/>
|
||||
|
||||
<SensitiveInput
|
||||
placeholder={$i18n.t('API Key')}
|
||||
bind:value={config.gemini.GEMINI_API_KEY}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if config?.enabled}
|
||||
<hr class=" dark:border-gray-850" />
|
||||
<hr class=" border-gray-100 dark:border-gray-850" />
|
||||
|
||||
<div>
|
||||
<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Default Model')}</div>
|
||||
@@ -620,7 +642,7 @@
|
||||
<Tooltip content={$i18n.t('Enter Model ID')} placement="top-start">
|
||||
<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-none"
|
||||
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={imageGenerationConfig.MODEL}
|
||||
placeholder="Select a model"
|
||||
required
|
||||
@@ -644,7 +666,7 @@
|
||||
<div class="flex-1 mr-2">
|
||||
<Tooltip content={$i18n.t('Enter Image Size (e.g. 512x512)')} placement="top-start">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||
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 Image Size (e.g. 512x512)')}
|
||||
bind:value={imageGenerationConfig.IMAGE_SIZE}
|
||||
required
|
||||
@@ -660,7 +682,7 @@
|
||||
<div class="flex-1 mr-2">
|
||||
<Tooltip content={$i18n.t('Enter Number of Steps (e.g. 50)')} placement="top-start">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||
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 Number of Steps (e.g. 50)')}
|
||||
bind:value={imageGenerationConfig.IMAGE_STEPS}
|
||||
required
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
let taskConfig = {
|
||||
TASK_MODEL: '',
|
||||
TASK_MODEL_EXTERNAL: '',
|
||||
ENABLE_TITLE_GENERATION: true,
|
||||
TITLE_GENERATION_PROMPT_TEMPLATE: '',
|
||||
IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE: '',
|
||||
ENABLE_AUTOCOMPLETE_GENERATION: true,
|
||||
@@ -31,7 +32,8 @@
|
||||
ENABLE_TAGS_GENERATION: true,
|
||||
ENABLE_SEARCH_QUERY_GENERATION: true,
|
||||
ENABLE_RETRIEVAL_QUERY_GENERATION: true,
|
||||
QUERY_GENERATION_PROMPT_TEMPLATE: ''
|
||||
QUERY_GENERATION_PROMPT_TEMPLATE: '',
|
||||
TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE: ''
|
||||
};
|
||||
|
||||
let promptSuggestions = [];
|
||||
@@ -49,7 +51,7 @@
|
||||
onMount(async () => {
|
||||
taskConfig = await getTaskConfig(localStorage.token);
|
||||
|
||||
promptSuggestions = $config?.default_prompt_suggestions;
|
||||
promptSuggestions = $config?.default_prompt_suggestions ?? [];
|
||||
banners = await getBanners(localStorage.token);
|
||||
});
|
||||
|
||||
@@ -67,9 +69,13 @@
|
||||
}}
|
||||
>
|
||||
<div class=" overflow-y-scroll scrollbar-hidden h-full pr-1.5">
|
||||
<div>
|
||||
<div class=" mb-2.5 text-sm font-medium flex items-center">
|
||||
<div class=" mr-1">{$i18n.t('Set Task Model')}</div>
|
||||
<div class="mb-3.5">
|
||||
<div class=" mb-2.5 text-base font-medium">{$i18n.t('Tasks')}</div>
|
||||
|
||||
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
|
||||
|
||||
<div class=" mb-1 font-medium flex items-center">
|
||||
<div class=" text-xs mr-1">{$i18n.t('Set Task Model')}</div>
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
'A task model is used when performing tasks such as generating titles for chats and web search queries'
|
||||
@@ -91,11 +97,12 @@
|
||||
</svg>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div class="flex w-full gap-2">
|
||||
|
||||
<div class=" mb-2.5 flex w-full gap-2">
|
||||
<div class="flex-1">
|
||||
<div class=" text-xs mb-1">{$i18n.t('Local Models')}</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-none"
|
||||
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={taskConfig.TASK_MODEL}
|
||||
placeholder={$i18n.t('Select a model')}
|
||||
>
|
||||
@@ -111,7 +118,7 @@
|
||||
<div class="flex-1">
|
||||
<div class=" text-xs mb-1">{$i18n.t('External Models')}</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-none"
|
||||
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={taskConfig.TASK_MODEL_EXTERNAL}
|
||||
placeholder={$i18n.t('Select a model')}
|
||||
>
|
||||
@@ -125,72 +132,33 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<div class=" mb-2.5 text-xs font-medium">{$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
|
||||
bind:value={taskConfig.TITLE_GENERATION_PROMPT_TEMPLATE}
|
||||
placeholder={$i18n.t(
|
||||
'Leave empty to use the default prompt, or enter a custom prompt'
|
||||
)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<div class=" mb-2.5 text-xs font-medium">{$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
|
||||
bind:value={taskConfig.IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE}
|
||||
placeholder={$i18n.t(
|
||||
'Leave empty to use the default prompt, or enter a custom prompt'
|
||||
)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-50 dark:border-gray-850 my-3" />
|
||||
|
||||
<div class="my-3 flex w-full items-center justify-between">
|
||||
<div class="mb-2.5 flex w-full items-center justify-between">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Autocomplete Generation')}
|
||||
{$i18n.t('Title Generation')}
|
||||
</div>
|
||||
|
||||
<Tooltip content={$i18n.t('Enable autocomplete generation for chat messages')}>
|
||||
<Switch bind:state={taskConfig.ENABLE_AUTOCOMPLETE_GENERATION} />
|
||||
</Tooltip>
|
||||
<Switch bind:state={taskConfig.ENABLE_TITLE_GENERATION} />
|
||||
</div>
|
||||
|
||||
{#if taskConfig.ENABLE_AUTOCOMPLETE_GENERATION}
|
||||
<div class="mt-3">
|
||||
<div class=" mb-2.5 text-xs font-medium">
|
||||
{$i18n.t('Autocomplete Generation Input Max Length')}
|
||||
</div>
|
||||
{#if taskConfig.ENABLE_TITLE_GENERATION}
|
||||
<div class="mb-2.5">
|
||||
<div class=" mb-1 text-xs font-medium">{$i18n.t('Title Generation Prompt')}</div>
|
||||
|
||||
<Tooltip
|
||||
content={$i18n.t('Character limit for autocomplete generation input')}
|
||||
content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
|
||||
placement="top-start"
|
||||
>
|
||||
<input
|
||||
class="w-full outline-none bg-transparent"
|
||||
bind:value={taskConfig.AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH}
|
||||
placeholder={$i18n.t('-1 for no limit, or a positive integer for a specific limit')}
|
||||
<Textarea
|
||||
bind:value={taskConfig.TITLE_GENERATION_PROMPT_TEMPLATE}
|
||||
placeholder={$i18n.t(
|
||||
'Leave empty to use the default prompt, or enter a custom prompt'
|
||||
)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<hr class=" border-gray-50 dark:border-gray-850 my-3" />
|
||||
|
||||
<div class="my-3 flex w-full items-center justify-between">
|
||||
<div class="mb-2.5 flex w-full items-center justify-between">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Tags Generation')}
|
||||
</div>
|
||||
@@ -199,8 +167,8 @@
|
||||
</div>
|
||||
|
||||
{#if taskConfig.ENABLE_TAGS_GENERATION}
|
||||
<div class="mt-3">
|
||||
<div class=" mb-2.5 text-xs font-medium">{$i18n.t('Tags Generation Prompt')}</div>
|
||||
<div class="mb-2.5">
|
||||
<div class=" mb-1 text-xs font-medium">{$i18n.t('Tags Generation Prompt')}</div>
|
||||
|
||||
<Tooltip
|
||||
content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
|
||||
@@ -216,9 +184,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<hr class=" border-gray-50 dark:border-gray-850 my-3" />
|
||||
|
||||
<div class="my-3 flex w-full items-center justify-between">
|
||||
<div class="mb-2.5 flex w-full items-center justify-between">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Retrieval Query Generation')}
|
||||
</div>
|
||||
@@ -226,7 +192,7 @@
|
||||
<Switch bind:state={taskConfig.ENABLE_RETRIEVAL_QUERY_GENERATION} />
|
||||
</div>
|
||||
|
||||
<div class="my-3 flex w-full items-center justify-between">
|
||||
<div class="mb-2.5 flex w-full items-center justify-between">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Web Search Query Generation')}
|
||||
</div>
|
||||
@@ -234,8 +200,8 @@
|
||||
<Switch bind:state={taskConfig.ENABLE_SEARCH_QUERY_GENERATION} />
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<div class=" mb-2.5 text-xs font-medium">{$i18n.t('Query Generation Prompt')}</div>
|
||||
<div class="mb-2.5">
|
||||
<div class=" mb-1 text-xs font-medium">{$i18n.t('Query Generation Prompt')}</div>
|
||||
|
||||
<Tooltip
|
||||
content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
|
||||
@@ -249,117 +215,96 @@
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-50 dark:border-gray-850 my-3" />
|
||||
|
||||
<div class=" space-y-3 {banners.length > 0 ? ' mb-3' : ''}">
|
||||
<div class="flex w-full justify-between">
|
||||
<div class=" self-center text-sm font-semibold">
|
||||
{$i18n.t('Banners')}
|
||||
<div class="mb-2.5 flex w-full items-center justify-between">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Autocomplete Generation')}
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded 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)
|
||||
}
|
||||
];
|
||||
}
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
<Tooltip content={$i18n.t('Enable autocomplete generation for chat messages')}>
|
||||
<Switch bind:state={taskConfig.ENABLE_AUTOCOMPLETE_GENERATION} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div class="flex flex-col space-y-1">
|
||||
{#each banners as banner, bannerIdx}
|
||||
<div class=" flex justify-between">
|
||||
<div class="flex flex-row flex-1 border rounded-xl dark:border-gray-800">
|
||||
<select
|
||||
class="w-fit capitalize rounded-xl py-2 px-4 text-xs bg-transparent outline-none"
|
||||
bind:value={banner.type}
|
||||
required
|
||||
>
|
||||
{#if banner.type == ''}
|
||||
<option value="" selected disabled class="text-gray-900"
|
||||
>{$i18n.t('Type')}</option
|
||||
>
|
||||
{/if}
|
||||
<option value="info" class="text-gray-900">{$i18n.t('Info')}</option>
|
||||
<option value="warning" class="text-gray-900">{$i18n.t('Warning')}</option>
|
||||
<option value="error" class="text-gray-900">{$i18n.t('Error')}</option>
|
||||
<option value="success" class="text-gray-900">{$i18n.t('Success')}</option>
|
||||
</select>
|
||||
|
||||
<input
|
||||
class="pr-5 py-1.5 text-xs w-full bg-transparent outline-none"
|
||||
placeholder={$i18n.t('Content')}
|
||||
bind:value={banner.content}
|
||||
/>
|
||||
|
||||
<div class="relative top-1.5 -left-2">
|
||||
<Tooltip content={$i18n.t('Dismissible')} className="flex h-fit items-center">
|
||||
<Switch bind:state={banner.dismissible} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="px-2"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
banners.splice(bannerIdx, 1);
|
||||
banners = banners;
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
{#if taskConfig.ENABLE_AUTOCOMPLETE_GENERATION}
|
||||
<div class="mb-2.5">
|
||||
<div class=" mb-1 text-xs font-medium">
|
||||
{$i18n.t('Autocomplete Generation Input Max Length')}
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
<Tooltip
|
||||
content={$i18n.t('Character limit for autocomplete generation input')}
|
||||
placement="top-start"
|
||||
>
|
||||
<input
|
||||
class="w-full outline-hidden bg-transparent"
|
||||
bind:value={taskConfig.AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH}
|
||||
placeholder={$i18n.t('-1 for no limit, or a positive integer for a specific limit')}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="mb-2.5">
|
||||
<div class=" mb-1 text-xs font-medium">{$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
|
||||
bind:value={taskConfig.IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE}
|
||||
placeholder={$i18n.t(
|
||||
'Leave empty to use the default prompt, or enter a custom prompt'
|
||||
)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div class="mb-2.5">
|
||||
<div class=" mb-1 text-xs font-medium">{$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
|
||||
bind:value={taskConfig.TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE}
|
||||
placeholder={$i18n.t(
|
||||
'Leave empty to use the default prompt, or enter a custom prompt'
|
||||
)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if $user.role === 'admin'}
|
||||
<div class=" space-y-3">
|
||||
<div class="flex w-full justify-between mb-2">
|
||||
<div class="mb-3.5">
|
||||
<div class=" mb-2.5 text-base font-medium">{$i18n.t('UI')}</div>
|
||||
|
||||
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
|
||||
|
||||
<div class=" {banners.length > 0 ? ' mb-3' : ''}">
|
||||
<div class="mb-2.5 flex w-full justify-between">
|
||||
<div class=" self-center text-sm font-semibold">
|
||||
{$i18n.t('Default Prompt Suggestions')}
|
||||
{$i18n.t('Banners')}
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded transition"
|
||||
class="p-1 px-3 text-xs flex rounded-sm transition"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
if (promptSuggestions.length === 0 || promptSuggestions.at(-1).content !== '') {
|
||||
promptSuggestions = [...promptSuggestions, { content: '', title: ['', ''] }];
|
||||
if (banners.length === 0 || banners.at(-1).content !== '') {
|
||||
banners = [
|
||||
...banners,
|
||||
{
|
||||
id: uuidv4(),
|
||||
type: '',
|
||||
title: '',
|
||||
content: '',
|
||||
dismissible: true,
|
||||
timestamp: Math.floor(Date.now() / 1000)
|
||||
}
|
||||
];
|
||||
}
|
||||
}}
|
||||
>
|
||||
@@ -375,40 +320,48 @@
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="grid lg:grid-cols-2 flex-col gap-1.5">
|
||||
{#each promptSuggestions as prompt, promptIdx}
|
||||
<div
|
||||
class=" flex border border-gray-100 dark:border-none dark:bg-gray-850 rounded-xl py-1.5"
|
||||
>
|
||||
<div class="flex flex-col flex-1 pl-1">
|
||||
<div class="flex border-b border-gray-100 dark:border-gray-800 w-full">
|
||||
<input
|
||||
class="px-3 py-1.5 text-xs w-full bg-transparent outline-none border-r border-gray-100 dark:border-gray-800"
|
||||
placeholder={$i18n.t('Title (e.g. Tell me a fun fact)')}
|
||||
bind:value={prompt.title[0]}
|
||||
/>
|
||||
|
||||
<input
|
||||
class="px-3 py-1.5 text-xs w-full bg-transparent outline-none border-r border-gray-100 dark:border-gray-800"
|
||||
placeholder={$i18n.t('Subtitle (e.g. about the Roman Empire)')}
|
||||
bind:value={prompt.title[1]}
|
||||
/>
|
||||
</div>
|
||||
<div class=" flex flex-col space-y-1">
|
||||
{#each banners as banner, bannerIdx}
|
||||
<div class=" flex justify-between">
|
||||
<div
|
||||
class="flex flex-row flex-1 border rounded-xl border-gray-100 dark:border-gray-850"
|
||||
>
|
||||
<select
|
||||
class="w-fit capitalize rounded-xl py-2 px-4 text-xs bg-transparent outline-hidden"
|
||||
bind:value={banner.type}
|
||||
required
|
||||
>
|
||||
{#if banner.type == ''}
|
||||
<option value="" selected disabled class="text-gray-900"
|
||||
>{$i18n.t('Type')}</option
|
||||
>
|
||||
{/if}
|
||||
<option value="info" class="text-gray-900">{$i18n.t('Info')}</option>
|
||||
<option value="warning" class="text-gray-900">{$i18n.t('Warning')}</option>
|
||||
<option value="error" class="text-gray-900">{$i18n.t('Error')}</option>
|
||||
<option value="success" class="text-gray-900">{$i18n.t('Success')}</option>
|
||||
</select>
|
||||
|
||||
<textarea
|
||||
class="px-3 py-1.5 text-xs w-full bg-transparent outline-none border-r border-gray-100 dark:border-gray-800 resize-none"
|
||||
placeholder={$i18n.t('Prompt (e.g. Tell me a fun fact about the Roman Empire)')}
|
||||
rows="3"
|
||||
bind:value={prompt.content}
|
||||
<input
|
||||
class="pr-5 py-1.5 text-xs w-full bg-transparent outline-hidden"
|
||||
placeholder={$i18n.t('Content')}
|
||||
bind:value={banner.content}
|
||||
/>
|
||||
|
||||
<div class="relative top-1.5 -left-2">
|
||||
<Tooltip content={$i18n.t('Dismissible')} className="flex h-fit items-center">
|
||||
<Switch bind:state={banner.dismissible} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="px-3"
|
||||
class="px-2"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
promptSuggestions.splice(promptIdx, 1);
|
||||
promptSuggestions = promptSuggestions;
|
||||
banners.splice(bannerIdx, 1);
|
||||
banners = banners;
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
@@ -425,14 +378,97 @@
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
{#if promptSuggestions.length > 0}
|
||||
<div class="text-xs text-left w-full mt-2">
|
||||
{$i18n.t('Adjusting these settings will apply changes universally to all users.')}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $user.role === 'admin'}
|
||||
<div class=" space-y-3">
|
||||
<div class="flex w-full justify-between mb-2">
|
||||
<div class=" self-center text-sm font-semibold">
|
||||
{$i18n.t('Default Prompt Suggestions')}
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded-sm transition"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
if (promptSuggestions.length === 0 || promptSuggestions.at(-1).content !== '') {
|
||||
promptSuggestions = [...promptSuggestions, { content: '', title: ['', ''] }];
|
||||
}
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
<div class="grid lg:grid-cols-2 flex-col gap-1.5">
|
||||
{#each promptSuggestions as prompt, promptIdx}
|
||||
<div
|
||||
class=" flex border border-gray-100 dark:border-none dark:bg-gray-850 rounded-xl py-1.5"
|
||||
>
|
||||
<div class="flex flex-col flex-1 pl-1">
|
||||
<div class="flex border-b border-gray-100 dark:border-gray-850 w-full">
|
||||
<input
|
||||
class="px-3 py-1.5 text-xs w-full bg-transparent outline-hidden border-r border-gray-100 dark:border-gray-850"
|
||||
placeholder={$i18n.t('Title (e.g. Tell me a fun fact)')}
|
||||
bind:value={prompt.title[0]}
|
||||
/>
|
||||
|
||||
<input
|
||||
class="px-3 py-1.5 text-xs w-full bg-transparent outline-hidden border-r border-gray-100 dark:border-gray-850"
|
||||
placeholder={$i18n.t('Subtitle (e.g. about the Roman Empire)')}
|
||||
bind:value={prompt.title[1]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<textarea
|
||||
class="px-3 py-1.5 text-xs w-full bg-transparent outline-hidden border-r border-gray-100 dark:border-gray-850 resize-none"
|
||||
placeholder={$i18n.t(
|
||||
'Prompt (e.g. Tell me a fun fact about the Roman Empire)'
|
||||
)}
|
||||
rows="3"
|
||||
bind:value={prompt.content}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="px-3"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
promptSuggestions.splice(promptIdx, 1);
|
||||
promptSuggestions = promptSuggestions;
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
{#if promptSuggestions.length > 0}
|
||||
<div class="text-xs text-left w-full mt-2">
|
||||
{$i18n.t('Adjusting these settings will apply changes universally to all users.')}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end text-sm font-medium">
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
|
||||
const init = async () => {
|
||||
workspaceModels = await getBaseModels(localStorage.token);
|
||||
baseModels = await getModels(localStorage.token, true);
|
||||
baseModels = await getModels(localStorage.token, null, true);
|
||||
|
||||
models = baseModels.map((m) => {
|
||||
const workspaceModel = workspaceModels.find((wm) => wm.id === m.id);
|
||||
@@ -111,7 +111,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
_models.set(await getModels(localStorage.token));
|
||||
_models.set(
|
||||
await getModels(
|
||||
localStorage.token,
|
||||
$config?.features?.enable_direct_connections && ($settings?.directConnections ?? null)
|
||||
)
|
||||
);
|
||||
await init();
|
||||
};
|
||||
|
||||
@@ -133,7 +138,12 @@
|
||||
}
|
||||
|
||||
// await init();
|
||||
_models.set(await getModels(localStorage.token));
|
||||
_models.set(
|
||||
await getModels(
|
||||
localStorage.token,
|
||||
$config?.features?.enable_direct_connections && ($settings?.directConnections ?? null)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
@@ -189,7 +199,7 @@
|
||||
<Search className="size-3.5" />
|
||||
</div>
|
||||
<input
|
||||
class=" w-full text-sm py-1 rounded-r-xl outline-none bg-transparent"
|
||||
class=" w-full text-sm py-1 rounded-r-xl outline-hidden bg-transparent"
|
||||
bind:value={searchValue}
|
||||
placeholder={$i18n.t('Search Models')}
|
||||
/>
|
||||
@@ -330,7 +340,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
await _models.set(await getModels(localStorage.token));
|
||||
await _models.set(
|
||||
await getModels(
|
||||
localStorage.token,
|
||||
$config?.features?.enable_direct_connections &&
|
||||
($settings?.directConnections ?? null)
|
||||
)
|
||||
);
|
||||
init();
|
||||
};
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
import Spinner from '$lib/components/common/Spinner.svelte';
|
||||
import Minus from '$lib/components/icons/Minus.svelte';
|
||||
import Plus from '$lib/components/icons/Plus.svelte';
|
||||
import ChevronUp from '$lib/components/icons/ChevronUp.svelte';
|
||||
import ChevronDown from '$lib/components/icons/ChevronDown.svelte';
|
||||
|
||||
export let show = false;
|
||||
export let initHandler = () => {};
|
||||
@@ -26,6 +28,9 @@
|
||||
let defaultModelIds = [];
|
||||
let modelIds = [];
|
||||
|
||||
let sortKey = '';
|
||||
let sortOrder = '';
|
||||
|
||||
let loading = false;
|
||||
let showResetModal = false;
|
||||
|
||||
@@ -71,6 +76,9 @@
|
||||
// Add remaining IDs not in MODEL_ORDER_LIST, sorted alphabetically
|
||||
...allModelIds.filter((id) => !orderedSet.has(id)).sort((a, b) => a.localeCompare(b))
|
||||
];
|
||||
|
||||
sortKey = '';
|
||||
sortOrder = '';
|
||||
};
|
||||
const submitHandler = async () => {
|
||||
loading = true;
|
||||
@@ -145,9 +153,45 @@
|
||||
>
|
||||
<div>
|
||||
<div class="flex flex-col w-full">
|
||||
<div class="mb-1 flex justify-between">
|
||||
<button
|
||||
class="mb-1 flex gap-2"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
sortKey = 'model';
|
||||
|
||||
if (sortOrder === 'asc') {
|
||||
sortOrder = 'desc';
|
||||
} else {
|
||||
sortOrder = 'asc';
|
||||
}
|
||||
|
||||
modelIds = modelIds
|
||||
.filter((id) => id !== '')
|
||||
.sort((a, b) => {
|
||||
const nameA = $models.find((model) => model.id === a)?.name || a;
|
||||
const nameB = $models.find((model) => model.id === b)?.name || b;
|
||||
return sortOrder === 'desc'
|
||||
? nameA.localeCompare(nameB)
|
||||
: nameB.localeCompare(nameA);
|
||||
});
|
||||
}}
|
||||
>
|
||||
<div class="text-xs text-gray-500">{$i18n.t('Reorder Models')}</div>
|
||||
</div>
|
||||
|
||||
{#if sortKey === 'model'}
|
||||
<span class="font-normal self-center">
|
||||
{#if sortOrder === 'asc'}
|
||||
<ChevronUp className="size-3" />
|
||||
{:else}
|
||||
<ChevronDown className="size-3" />
|
||||
{/if}
|
||||
</span>
|
||||
{:else}
|
||||
<span class="invisible">
|
||||
<ChevronUp className="size-3" />
|
||||
</span>
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
<ModelList bind:modelIds />
|
||||
</div>
|
||||
@@ -165,7 +209,7 @@
|
||||
<select
|
||||
class="w-full py-1 text-sm rounded-lg bg-transparent {selectedModelId
|
||||
? ''
|
||||
: 'text-gray-500'} placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-none"
|
||||
: 'text-gray-500'} placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden"
|
||||
bind:value={selectedModelId}
|
||||
>
|
||||
<option value="">{$i18n.t('Select a model')}</option>
|
||||
@@ -186,7 +230,7 @@
|
||||
<div class=" text-sm flex-1 py-1 rounded-lg">
|
||||
{$models.find((model) => model.id === modelId)?.name}
|
||||
</div>
|
||||
<div class="flex-shrink-0">
|
||||
<div class="shrink-0">
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
{#if ollamaConfig}
|
||||
<div class="flex-1 mb-2.5 pr-1.5 rounded-lg bg-gray-50 dark:text-gray-300 dark:bg-gray-850">
|
||||
<select
|
||||
class="w-full py-2 px-4 text-sm outline-none bg-transparent"
|
||||
class="w-full py-2 px-4 text-sm outline-hidden bg-transparent"
|
||||
bind:value={selectedUrlIdx}
|
||||
placeholder={$i18n.t('Select an Ollama instance')}
|
||||
>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { getContext, onMount } from 'svelte';
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
import { WEBUI_NAME, models, MODEL_DOWNLOAD_POOL, user, config } from '$lib/stores';
|
||||
import { WEBUI_NAME, models, MODEL_DOWNLOAD_POOL, user, config, settings } from '$lib/stores';
|
||||
import { splitStream } from '$lib/utils';
|
||||
|
||||
import {
|
||||
@@ -235,7 +235,12 @@
|
||||
})
|
||||
);
|
||||
|
||||
models.set(await getModels(localStorage.token));
|
||||
models.set(
|
||||
await getModels(
|
||||
localStorage.token,
|
||||
$config?.features?.enable_direct_connections && ($settings?.directConnections ?? null)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
toast.error($i18n.t('Download canceled'));
|
||||
}
|
||||
@@ -394,7 +399,12 @@
|
||||
modelTransferring = false;
|
||||
uploadProgress = null;
|
||||
|
||||
models.set(await getModels(localStorage.token));
|
||||
models.set(
|
||||
await getModels(
|
||||
localStorage.token,
|
||||
$config?.features?.enable_direct_connections && ($settings?.directConnections ?? null)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const deleteModelHandler = async () => {
|
||||
@@ -407,7 +417,12 @@
|
||||
}
|
||||
|
||||
deleteModelTag = '';
|
||||
models.set(await getModels(localStorage.token));
|
||||
models.set(
|
||||
await getModels(
|
||||
localStorage.token,
|
||||
$config?.features?.enable_direct_connections && ($settings?.directConnections ?? null)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const cancelModelPullHandler = async (model: string) => {
|
||||
@@ -506,7 +521,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
models.set(await getModels(localStorage.token));
|
||||
models.set(
|
||||
await getModels(
|
||||
localStorage.token,
|
||||
$config?.features?.enable_direct_connections && ($settings?.directConnections ?? null)
|
||||
)
|
||||
);
|
||||
|
||||
createModelLoading = false;
|
||||
|
||||
@@ -578,7 +598,7 @@
|
||||
<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-none"
|
||||
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 model tag (e.g. {{modelTag}})', {
|
||||
modelTag: 'mistral:7b'
|
||||
})}
|
||||
@@ -720,7 +740,7 @@
|
||||
class="flex-1 mr-2 pr-1.5 rounded-lg bg-gray-50 dark:text-gray-300 dark:bg-gray-850"
|
||||
>
|
||||
<select
|
||||
class="w-full py-2 px-4 text-sm outline-none bg-transparent"
|
||||
class="w-full py-2 px-4 text-sm outline-hidden bg-transparent"
|
||||
bind:value={deleteModelTag}
|
||||
placeholder={$i18n.t('Select a model')}
|
||||
>
|
||||
@@ -761,7 +781,7 @@
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1 mr-2 flex flex-col 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-none"
|
||||
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 model tag (e.g. {{modelTag}})', {
|
||||
modelTag: 'my-modelfile'
|
||||
})}
|
||||
@@ -771,7 +791,7 @@
|
||||
|
||||
<textarea
|
||||
bind:value={createModelObject}
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-100 dark:bg-gray-850 outline-none resize-none scrollbar-hidden"
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-100 dark:bg-gray-850 outline-hidden resize-none scrollbar-hidden"
|
||||
rows="6"
|
||||
placeholder={`e.g. {"model": "my-modelfile", "from": "ollama:7b"})`}
|
||||
disabled={createModelLoading}
|
||||
@@ -850,7 +870,7 @@
|
||||
<div class=" text-sm font-medium">{$i18n.t('Upload a GGUF model')}</div>
|
||||
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded transition"
|
||||
class="p-1 px-3 text-xs flex rounded-sm transition"
|
||||
on:click={() => {
|
||||
if (modelUploadMode === 'file') {
|
||||
modelUploadMode = 'url';
|
||||
@@ -902,7 +922,7 @@
|
||||
{:else}
|
||||
<div class="flex-1 {modelFileUrl !== '' ? 'mr-2' : ''}">
|
||||
<input
|
||||
class="w-full rounded-lg text-left py-2 px-4 bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none {modelFileUrl !==
|
||||
class="w-full rounded-lg text-left py-2 px-4 bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden {modelFileUrl !==
|
||||
''
|
||||
? 'mr-2'
|
||||
: ''}"
|
||||
@@ -978,7 +998,7 @@
|
||||
</div>
|
||||
<textarea
|
||||
bind:value={modelFileContent}
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-100 dark:bg-gray-850 outline-none resize-none"
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-100 dark:bg-gray-850 outline-hidden resize-none"
|
||||
rows="6"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -21,14 +21,24 @@
|
||||
modelIds = modelList;
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
sortable = Sortable.create(modelListElement, {
|
||||
animation: 150,
|
||||
onUpdate: async (event) => {
|
||||
positionChangeHandler();
|
||||
}
|
||||
});
|
||||
});
|
||||
$: if (modelIds) {
|
||||
init();
|
||||
}
|
||||
|
||||
const init = () => {
|
||||
if (sortable) {
|
||||
sortable.destroy();
|
||||
}
|
||||
|
||||
if (modelListElement) {
|
||||
sortable = Sortable.create(modelListElement, {
|
||||
animation: 150,
|
||||
onUpdate: async (event) => {
|
||||
positionChangeHandler();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
{#if modelIds.length > 0}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { models } from '$lib/stores';
|
||||
import { config, models, settings } from '$lib/stores';
|
||||
import { getContext, onMount, tick } from 'svelte';
|
||||
import type { Writable } from 'svelte/store';
|
||||
import type { i18n as i18nType } from 'i18next';
|
||||
@@ -63,7 +63,12 @@
|
||||
if (res) {
|
||||
toast.success($i18n.t('Valves updated successfully'));
|
||||
setPipelines();
|
||||
models.set(await getModels(localStorage.token));
|
||||
models.set(
|
||||
await getModels(
|
||||
localStorage.token,
|
||||
$config?.features?.enable_direct_connections && ($settings?.directConnections ?? null)
|
||||
)
|
||||
);
|
||||
saveHandler();
|
||||
}
|
||||
} else {
|
||||
@@ -125,7 +130,12 @@
|
||||
if (res) {
|
||||
toast.success($i18n.t('Pipeline downloaded successfully'));
|
||||
setPipelines();
|
||||
models.set(await getModels(localStorage.token));
|
||||
models.set(
|
||||
await getModels(
|
||||
localStorage.token,
|
||||
$config?.features?.enable_direct_connections && ($settings?.directConnections ?? null)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
downloading = false;
|
||||
@@ -150,7 +160,12 @@
|
||||
if (res) {
|
||||
toast.success($i18n.t('Pipeline downloaded successfully'));
|
||||
setPipelines();
|
||||
models.set(await getModels(localStorage.token));
|
||||
models.set(
|
||||
await getModels(
|
||||
localStorage.token,
|
||||
$config?.features?.enable_direct_connections && ($settings?.directConnections ?? null)
|
||||
)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
toast.error($i18n.t('No file selected'));
|
||||
@@ -179,7 +194,12 @@
|
||||
if (res) {
|
||||
toast.success($i18n.t('Pipeline deleted successfully'));
|
||||
setPipelines();
|
||||
models.set(await getModels(localStorage.token));
|
||||
models.set(
|
||||
await getModels(
|
||||
localStorage.token,
|
||||
$config?.features?.enable_direct_connections && ($settings?.directConnections ?? null)
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -214,7 +234,7 @@
|
||||
<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-none"
|
||||
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 () => {
|
||||
@@ -251,7 +271,7 @@
|
||||
/>
|
||||
|
||||
<button
|
||||
class="w-full text-sm font-medium py-2 bg-transparent hover:bg-gray-100 border border-dashed dark:border-gray-800 dark:hover:bg-gray-850 text-center rounded-xl"
|
||||
class="w-full text-sm font-medium py-2 bg-transparent hover:bg-gray-100 border border-dashed dark:border-gray-850 dark:hover:bg-gray-850 text-center rounded-xl"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
document.getElementById('pipelines-upload-input')?.click();
|
||||
@@ -328,7 +348,7 @@
|
||||
<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-none"
|
||||
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')}
|
||||
bind:value={pipelineDownloadUrl}
|
||||
/>
|
||||
@@ -398,7 +418,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class=" dark:border-gray-800 my-3 w-full" />
|
||||
<hr class="border-gray-100 dark:border-gray-850 my-3 w-full" />
|
||||
|
||||
{#if pipelines !== null}
|
||||
{#if pipelines.length > 0}
|
||||
@@ -412,7 +432,7 @@
|
||||
<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-none"
|
||||
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 () => {
|
||||
@@ -462,7 +482,7 @@
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded transition"
|
||||
class="p-1 px-3 text-xs flex rounded-sm transition"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
valves[property] = (valves[property] ?? null) === null ? '' : null;
|
||||
@@ -482,7 +502,7 @@
|
||||
<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-none"
|
||||
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}
|
||||
@@ -503,7 +523,7 @@
|
||||
</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-none"
|
||||
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]}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { onMount, getContext } from 'svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import SensitiveInput from '$lib/components/common/SensitiveInput.svelte';
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
@@ -18,14 +19,18 @@
|
||||
'brave',
|
||||
'kagi',
|
||||
'mojeek',
|
||||
'bocha',
|
||||
'serpstack',
|
||||
'serper',
|
||||
'serply',
|
||||
'searchapi',
|
||||
'serpapi',
|
||||
'duckduckgo',
|
||||
'tavily',
|
||||
'jina',
|
||||
'bing'
|
||||
'bing',
|
||||
'exa',
|
||||
'perplexity'
|
||||
];
|
||||
|
||||
let youtubeLanguage = 'en';
|
||||
@@ -33,6 +38,16 @@
|
||||
let youtubeProxyUrl = '';
|
||||
|
||||
const submitHandler = async () => {
|
||||
// Convert domain filter string to array before sending
|
||||
if (webConfig.search.domain_filter_list) {
|
||||
webConfig.search.domain_filter_list = webConfig.search.domain_filter_list
|
||||
.split(',')
|
||||
.map((domain) => domain.trim())
|
||||
.filter((domain) => domain.length > 0);
|
||||
} else {
|
||||
webConfig.search.domain_filter_list = [];
|
||||
}
|
||||
|
||||
const res = await updateRAGConfig(localStorage.token, {
|
||||
web: webConfig,
|
||||
youtube: {
|
||||
@@ -41,6 +56,8 @@
|
||||
proxy_url: youtubeProxyUrl
|
||||
}
|
||||
});
|
||||
|
||||
webConfig.search.domain_filter_list = webConfig.search.domain_filter_list.join(', ');
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
@@ -48,6 +65,10 @@
|
||||
|
||||
if (res) {
|
||||
webConfig = res.web;
|
||||
// Convert array back to comma-separated string for display
|
||||
if (webConfig?.search?.domain_filter_list) {
|
||||
webConfig.search.domain_filter_list = webConfig.search.domain_filter_list.join(', ');
|
||||
}
|
||||
|
||||
youtubeLanguage = res.youtube.language.join(',');
|
||||
youtubeTranslation = res.youtube.translation;
|
||||
@@ -65,306 +86,427 @@
|
||||
>
|
||||
<div class=" space-y-3 overflow-y-scroll scrollbar-hidden h-full">
|
||||
{#if webConfig}
|
||||
<div>
|
||||
<div class=" mb-1 text-sm font-medium">
|
||||
{$i18n.t('Web Search')}
|
||||
</div>
|
||||
<div class="">
|
||||
<div class="mb-3">
|
||||
<div class=" mb-2.5 text-base font-medium">{$i18n.t('General')}</div>
|
||||
|
||||
<div>
|
||||
<div class=" py-0.5 flex w-full justify-between">
|
||||
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
|
||||
|
||||
<div class=" mb-2.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Enable Web Search')}
|
||||
{$i18n.t('Web Search')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
<Switch bind:state={webConfig.search.enabled} />
|
||||
</div>
|
||||
|
||||
<Switch bind:state={webConfig.search.enabled} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class=" py-0.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">{$i18n.t('Web Search Engine')}</div>
|
||||
<div class="flex items-center relative">
|
||||
<select
|
||||
class="dark:bg-gray-900 w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
|
||||
bind:value={webConfig.search.engine}
|
||||
placeholder={$i18n.t('Select a engine')}
|
||||
required
|
||||
>
|
||||
<option disabled selected value="">{$i18n.t('Select a engine')}</option>
|
||||
{#each webSearchEngines as engine}
|
||||
<option value={engine}>{engine}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<div class=" mb-2.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Web Search Engine')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
<select
|
||||
class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
|
||||
bind:value={webConfig.search.engine}
|
||||
placeholder={$i18n.t('Select a engine')}
|
||||
required
|
||||
>
|
||||
<option disabled selected value="">{$i18n.t('Select a engine')}</option>
|
||||
{#each webSearchEngines as engine}
|
||||
<option value={engine}>{engine}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if webConfig.search.engine !== ''}
|
||||
<div class="mt-1.5">
|
||||
{#if webConfig.search.engine !== ''}
|
||||
{#if webConfig.search.engine === 'searxng'}
|
||||
<div>
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
{$i18n.t('Searxng Query URL')}
|
||||
</div>
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-medium 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-none"
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter Searxng Query URL')}
|
||||
bind:value={webConfig.search.searxng_query_url}
|
||||
autocomplete="off"
|
||||
/>
|
||||
<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"
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter Searxng Query URL')}
|
||||
bind:value={webConfig.search.searxng_query_url}
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else if webConfig.search.engine === 'google_pse'}
|
||||
<div>
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
{$i18n.t('Google PSE API Key')}
|
||||
</div>
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
{$i18n.t('Google PSE API Key')}
|
||||
</div>
|
||||
|
||||
<SensitiveInput
|
||||
placeholder={$i18n.t('Enter Google PSE API Key')}
|
||||
bind:value={webConfig.search.google_pse_api_key}
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-1.5">
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
{$i18n.t('Google PSE Engine Id')}
|
||||
<SensitiveInput
|
||||
placeholder={$i18n.t('Enter Google PSE API Key')}
|
||||
bind:value={webConfig.search.google_pse_api_key}
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-1.5">
|
||||
<div class=" self-center text-xs font-medium 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-none"
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter Google PSE Engine Id')}
|
||||
bind:value={webConfig.search.google_pse_engine_id}
|
||||
autocomplete="off"
|
||||
/>
|
||||
<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"
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter Google PSE Engine Id')}
|
||||
bind:value={webConfig.search.google_pse_engine_id}
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else if webConfig.search.engine === 'brave'}
|
||||
<div>
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
{$i18n.t('Brave Search API Key')}
|
||||
</div>
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
{$i18n.t('Brave Search API Key')}
|
||||
</div>
|
||||
|
||||
<SensitiveInput
|
||||
placeholder={$i18n.t('Enter Brave Search API Key')}
|
||||
bind:value={webConfig.search.brave_search_api_key}
|
||||
/>
|
||||
<SensitiveInput
|
||||
placeholder={$i18n.t('Enter Brave Search API Key')}
|
||||
bind:value={webConfig.search.brave_search_api_key}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{:else if webConfig.search.engine === 'kagi'}
|
||||
<div>
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
{$i18n.t('Kagi Search API Key')}
|
||||
</div>
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
{$i18n.t('Kagi Search API Key')}
|
||||
</div>
|
||||
|
||||
<SensitiveInput
|
||||
placeholder={$i18n.t('Enter Kagi Search API Key')}
|
||||
bind:value={webConfig.search.kagi_search_api_key}
|
||||
/>
|
||||
<SensitiveInput
|
||||
placeholder={$i18n.t('Enter Kagi Search API Key')}
|
||||
bind:value={webConfig.search.kagi_search_api_key}
|
||||
/>
|
||||
</div>
|
||||
.
|
||||
</div>
|
||||
{:else if webConfig.search.engine === 'mojeek'}
|
||||
<div>
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
{$i18n.t('Mojeek Search API Key')}
|
||||
</div>
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
{$i18n.t('Mojeek Search API Key')}
|
||||
</div>
|
||||
|
||||
<SensitiveInput
|
||||
placeholder={$i18n.t('Enter Mojeek Search API Key')}
|
||||
bind:value={webConfig.search.mojeek_search_api_key}
|
||||
/>
|
||||
<SensitiveInput
|
||||
placeholder={$i18n.t('Enter Mojeek Search API Key')}
|
||||
bind:value={webConfig.search.mojeek_search_api_key}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{:else if webConfig.search.engine === 'bocha'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
{$i18n.t('Bocha Search API Key')}
|
||||
</div>
|
||||
|
||||
<SensitiveInput
|
||||
placeholder={$i18n.t('Enter Bocha Search API Key')}
|
||||
bind:value={webConfig.search.bocha_search_api_key}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{:else if webConfig.search.engine === 'serpstack'}
|
||||
<div>
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
{$i18n.t('Serpstack API Key')}
|
||||
</div>
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
{$i18n.t('Serpstack API Key')}
|
||||
</div>
|
||||
|
||||
<SensitiveInput
|
||||
placeholder={$i18n.t('Enter Serpstack API Key')}
|
||||
bind:value={webConfig.search.serpstack_api_key}
|
||||
/>
|
||||
<SensitiveInput
|
||||
placeholder={$i18n.t('Enter Serpstack API Key')}
|
||||
bind:value={webConfig.search.serpstack_api_key}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{:else if webConfig.search.engine === 'serper'}
|
||||
<div>
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
{$i18n.t('Serper API Key')}
|
||||
</div>
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
{$i18n.t('Serper API Key')}
|
||||
</div>
|
||||
|
||||
<SensitiveInput
|
||||
placeholder={$i18n.t('Enter Serper API Key')}
|
||||
bind:value={webConfig.search.serper_api_key}
|
||||
/>
|
||||
<SensitiveInput
|
||||
placeholder={$i18n.t('Enter Serper API Key')}
|
||||
bind:value={webConfig.search.serper_api_key}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{:else if webConfig.search.engine === 'serply'}
|
||||
<div>
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
{$i18n.t('Serply API Key')}
|
||||
</div>
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
{$i18n.t('Serply API Key')}
|
||||
</div>
|
||||
|
||||
<SensitiveInput
|
||||
placeholder={$i18n.t('Enter Serply API Key')}
|
||||
bind:value={webConfig.search.serply_api_key}
|
||||
/>
|
||||
<SensitiveInput
|
||||
placeholder={$i18n.t('Enter Serply API Key')}
|
||||
bind:value={webConfig.search.serply_api_key}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{:else if webConfig.search.engine === 'searchapi'}
|
||||
<div>
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
{$i18n.t('SearchApi API Key')}
|
||||
</div>
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
{$i18n.t('SearchApi API Key')}
|
||||
</div>
|
||||
|
||||
<SensitiveInput
|
||||
placeholder={$i18n.t('Enter SearchApi API Key')}
|
||||
bind:value={webConfig.search.searchapi_api_key}
|
||||
/>
|
||||
<SensitiveInput
|
||||
placeholder={$i18n.t('Enter SearchApi API Key')}
|
||||
bind:value={webConfig.search.searchapi_api_key}
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-1.5">
|
||||
<div class=" self-center text-xs font-medium 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"
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter SearchApi Engine')}
|
||||
bind:value={webConfig.search.searchapi_engine}
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-1.5">
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
{$i18n.t('SearchApi Engine')}
|
||||
</div>
|
||||
{:else if webConfig.search.engine === 'serpapi'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
{$i18n.t('SerpApi API Key')}
|
||||
</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-none"
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter SearchApi Engine')}
|
||||
bind:value={webConfig.search.searchapi_engine}
|
||||
autocomplete="off"
|
||||
/>
|
||||
<SensitiveInput
|
||||
placeholder={$i18n.t('Enter SerpApi API Key')}
|
||||
bind:value={webConfig.search.serpapi_api_key}
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-1.5">
|
||||
<div class=" self-center text-xs font-medium 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"
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter SerpApi Engine')}
|
||||
bind:value={webConfig.search.serpapi_engine}
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else if webConfig.search.engine === 'tavily'}
|
||||
<div>
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
{$i18n.t('Tavily API Key')}
|
||||
</div>
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
{$i18n.t('Tavily API Key')}
|
||||
</div>
|
||||
|
||||
<SensitiveInput
|
||||
placeholder={$i18n.t('Enter Tavily API Key')}
|
||||
bind:value={webConfig.search.tavily_api_key}
|
||||
/>
|
||||
<SensitiveInput
|
||||
placeholder={$i18n.t('Enter Tavily API Key')}
|
||||
bind:value={webConfig.search.tavily_api_key}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{:else if webConfig.search.engine === 'jina'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
{$i18n.t('Jina API Key')}
|
||||
</div>
|
||||
|
||||
<SensitiveInput
|
||||
placeholder={$i18n.t('Enter Jina API Key')}
|
||||
bind:value={webConfig.search.jina_api_key}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{:else if webConfig.search.engine === 'exa'}
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
{$i18n.t('Exa API Key')}
|
||||
</div>
|
||||
|
||||
<SensitiveInput
|
||||
placeholder={$i18n.t('Enter Exa API Key')}
|
||||
bind:value={webConfig.search.exa_api_key}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{:else if webConfig.search.engine === 'perplexity'}
|
||||
<div>
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
{$i18n.t('Jina API Key')}
|
||||
{$i18n.t('Perplexity API Key')}
|
||||
</div>
|
||||
|
||||
<SensitiveInput
|
||||
placeholder={$i18n.t('Enter Jina API Key')}
|
||||
bind:value={webConfig.search.jina_api_key}
|
||||
placeholder={$i18n.t('Enter Perplexity API Key')}
|
||||
bind:value={webConfig.search.perplexity_api_key}
|
||||
/>
|
||||
</div>
|
||||
{:else if webConfig.search.engine === 'bing'}
|
||||
<div>
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
{$i18n.t('Bing Search V7 Endpoint')}
|
||||
</div>
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div>
|
||||
<div class=" self-center text-xs font-medium 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-none"
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter Bing Search V7 Endpoint')}
|
||||
bind:value={webConfig.search.bing_search_v7_endpoint}
|
||||
autocomplete="off"
|
||||
/>
|
||||
<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"
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter Bing Search V7 Endpoint')}
|
||||
bind:value={webConfig.search.bing_search_v7_endpoint}
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
{$i18n.t('Bing Search V7 Subscription Key')}
|
||||
<div class="mt-2">
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
{$i18n.t('Bing Search V7 Subscription Key')}
|
||||
</div>
|
||||
|
||||
<SensitiveInput
|
||||
placeholder={$i18n.t('Enter Bing Search V7 Subscription Key')}
|
||||
bind:value={webConfig.search.bing_search_v7_subscription_key}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<SensitiveInput
|
||||
placeholder={$i18n.t('Enter Bing Search V7 Subscription Key')}
|
||||
bind:value={webConfig.search.bing_search_v7_subscription_key}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if webConfig.search.enabled}
|
||||
<div class="mt-2 flex gap-2 mb-1">
|
||||
<div class="w-full">
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
{$i18n.t('Search Result Count')}
|
||||
{#if webConfig.search.enabled}
|
||||
<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-medium 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"
|
||||
placeholder={$i18n.t('Search Result Count')}
|
||||
bind:value={webConfig.search.result_count}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="w-full">
|
||||
<div class=" self-center text-xs font-medium 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"
|
||||
placeholder={$i18n.t('Concurrent Requests')}
|
||||
bind:value={webConfig.search.concurrent_requests}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-2.5 flex w-full flex-col">
|
||||
<div class=" text-xs font-medium 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-none"
|
||||
placeholder={$i18n.t('Search Result Count')}
|
||||
bind:value={webConfig.search.result_count}
|
||||
required
|
||||
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 domains separated by commas (e.g., example.com,site.org)'
|
||||
)}
|
||||
bind:value={webConfig.search.domain_filter_list}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="w-full">
|
||||
<div class=" self-center text-xs font-medium 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-none"
|
||||
placeholder={$i18n.t('Concurrent Requests')}
|
||||
bind:value={webConfig.search.concurrent_requests}
|
||||
required
|
||||
/>
|
||||
<div class=" mb-2.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
<Tooltip content={$i18n.t('Full Context Mode')} placement="top-start">
|
||||
{$i18n.t('Bypass Embedding and Retrieval')}
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
<Tooltip
|
||||
content={webConfig.BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL
|
||||
? 'Inject the entire content as context for comprehensive processing, this is recommended for complex queries.'
|
||||
: 'Default to segmented retrieval for focused and relevant content extraction, this is recommended for most cases.'}
|
||||
>
|
||||
<Switch bind:state={webConfig.BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<hr class=" dark:border-gray-850 my-2" />
|
||||
|
||||
<div>
|
||||
<div class=" mb-1 text-sm font-medium">
|
||||
{$i18n.t('Web Loader Settings')}
|
||||
<div class=" mb-2.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Trust Proxy Environment')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
<Tooltip
|
||||
content={webConfig.search.trust_env
|
||||
? 'Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents'
|
||||
: 'Use no proxy to fetch page contents.'}
|
||||
>
|
||||
<Switch bind:state={webConfig.search.trust_env} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class=" py-0.5 flex w-full justify-between">
|
||||
<div class="mb-3">
|
||||
<div class=" mb-2.5 text-base font-medium">{$i18n.t('Loader')}</div>
|
||||
|
||||
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
|
||||
|
||||
<div class=" mb-2.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Bypass SSL verification for Websites')}
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded transition"
|
||||
on:click={() => {
|
||||
webConfig.web_loader_ssl_verification = !webConfig.web_loader_ssl_verification;
|
||||
submitHandler();
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
{#if webConfig.web_loader_ssl_verification === false}
|
||||
<span class="ml-2 self-center">{$i18n.t('On')}</span>
|
||||
{:else}
|
||||
<span class="ml-2 self-center">{$i18n.t('Off')}</span>
|
||||
{/if}
|
||||
</button>
|
||||
<div class="flex items-center relative">
|
||||
<Switch bind:state={webConfig.ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class=" mt-2 mb-1 text-sm font-medium">
|
||||
{$i18n.t('Youtube Loader Settings')}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class=" py-0.5 flex w-full justify-between">
|
||||
<div class=" w-20 text-xs font-medium self-center">{$i18n.t('Language')}</div>
|
||||
<div class=" flex-1 self-center">
|
||||
<div class=" mb-2.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Youtube Language')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||
class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden"
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter language codes')}
|
||||
bind:value={youtubeLanguage}
|
||||
@@ -372,14 +514,14 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class=" py-0.5 flex w-full justify-between">
|
||||
<div class=" w-20 text-xs font-medium self-center">{$i18n.t('Proxy URL')}</div>
|
||||
<div class=" flex-1 self-center">
|
||||
<div class=" mb-2.5 flex flex-col w-full justify-between">
|
||||
<div class=" mb-1 text-xs font-medium">
|
||||
{$i18n.t('Youtube Proxy URL')}
|
||||
</div>
|
||||
<div class="flex items-center relative">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||
class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden"
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter proxy URL (e.g. https://user:password@host:port)')}
|
||||
bind:value={youtubeProxyUrl}
|
||||
|
||||
Reference in New Issue
Block a user