This commit is contained in:
Timothy Jaeryang Baek
2026-07-15 18:21:24 -04:00
parent 4b0122a120
commit c3878b418a
20 changed files with 264 additions and 124 deletions
+38 -7
View File
@@ -288,7 +288,11 @@
required
/>
<SensitiveInput placeholder={$i18n.t('API Key')} bind:value={STT_OPENAI_API_KEY} />
<SensitiveInput
variant="settings"
placeholder={$i18n.t('API Key')}
bind:value={STT_OPENAI_API_KEY}
/>
</div>
</div>
@@ -326,7 +330,11 @@
{: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} />
<SensitiveInput
variant="settings"
placeholder={$i18n.t('API Key')}
bind:value={STT_DEEPGRAM_API_KEY}
/>
</div>
</div>
@@ -358,6 +366,7 @@
<div>
<div class="mt-1 flex gap-2 mb-1">
<SensitiveInput
variant="settings"
placeholder={$i18n.t('API Key')}
bind:value={STT_AZURE_API_KEY}
required
@@ -434,7 +443,11 @@
required
/>
<SensitiveInput placeholder={$i18n.t('API Key')} bind:value={STT_MISTRAL_API_KEY} />
<SensitiveInput
variant="settings"
placeholder={$i18n.t('API Key')}
bind:value={STT_MISTRAL_API_KEY}
/>
</div>
</div>
@@ -596,19 +609,33 @@
required
/>
<SensitiveInput placeholder={$i18n.t('API Key')} bind:value={TTS_OPENAI_API_KEY} />
<SensitiveInput
variant="settings"
placeholder={$i18n.t('API Key')}
bind:value={TTS_OPENAI_API_KEY}
/>
</div>
</div>
{:else if TTS_ENGINE === 'elevenlabs'}
<div>
<div class="mt-1 flex gap-2 mb-1">
<SensitiveInput placeholder={$i18n.t('API Key')} bind:value={TTS_API_KEY} required />
<SensitiveInput
variant="settings"
placeholder={$i18n.t('API Key')}
bind:value={TTS_API_KEY}
required
/>
</div>
</div>
{:else if TTS_ENGINE === 'azure'}
<div>
<div class="mt-1 flex gap-2 mb-1">
<SensitiveInput placeholder={$i18n.t('API Key')} bind:value={TTS_API_KEY} required />
<SensitiveInput
variant="settings"
placeholder={$i18n.t('API Key')}
bind:value={TTS_API_KEY}
required
/>
</div>
<div>
@@ -651,7 +678,11 @@
required
/>
<SensitiveInput placeholder={$i18n.t('API Key')} bind:value={TTS_MISTRAL_API_KEY} />
<SensitiveInput
variant="settings"
placeholder={$i18n.t('API Key')}
bind:value={TTS_MISTRAL_API_KEY}
/>
</div>
</div>
{/if}
@@ -137,7 +137,6 @@
<AdminSettingRow label={$i18n.t('Default Group')}>
<SettingsSelect
className="w-fit max-w-48"
bind:value={adminConfig.DEFAULT_GROUP_ID}
placeholder={$i18n.t('Select a group')}
>
@@ -302,10 +301,9 @@
<AdminSettingField label={$i18n.t('Application DN Password')}>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter Application DN Password')}
required={false}
outerClassName="flex flex-1"
inputClassName={inputClass}
bind:value={LDAP_SERVER.app_dn_password}
/>
</AdminSettingField>
@@ -432,10 +430,9 @@
<AdminSettingField label={$i18n.t('Client Secret')}>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter Client Secret')}
required={false}
outerClassName="flex flex-1"
inputClassName={inputClass}
bind:value={oauthConfig.OAUTH_CLIENT_SECRET}
/>
</AdminSettingField>
@@ -108,21 +108,19 @@
>
{#if config.CODE_EXECUTION_JUPYTER_AUTH === 'password'}
<SensitiveInput
variant="settings"
type="text"
placeholder={$i18n.t('Enter Jupyter Password')}
bind:value={config.CODE_EXECUTION_JUPYTER_AUTH_PASSWORD}
autocomplete="off"
outerClassName="flex flex-1"
inputClassName={inputClass}
/>
{:else}
<SensitiveInput
variant="settings"
type="text"
placeholder={$i18n.t('Enter Jupyter Token')}
bind:value={config.CODE_EXECUTION_JUPYTER_AUTH_TOKEN}
autocomplete="off"
outerClassName="flex flex-1"
inputClassName={inputClass}
/>
{/if}
</AdminSettingField>
@@ -199,21 +197,19 @@
>
{#if config.CODE_INTERPRETER_JUPYTER_AUTH === 'password'}
<SensitiveInput
variant="settings"
type="text"
placeholder={$i18n.t('Enter Jupyter Password')}
bind:value={config.CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD}
autocomplete="off"
outerClassName="flex flex-1"
inputClassName={inputClass}
/>
{:else}
<SensitiveInput
variant="settings"
type="text"
placeholder={$i18n.t('Enter Jupyter Token')}
bind:value={config.CODE_INTERPRETER_JUPYTER_AUTH_TOKEN}
autocomplete="off"
outerClassName="flex flex-1"
inputClassName={inputClass}
/>
{/if}
</AdminSettingField>
@@ -451,6 +451,7 @@
</div>
<div class="my-0.5 flex gap-2 pr-2">
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter Datalab Marker API Key')}
required={false}
bind:value={RAGConfig.DATALAB_MARKER_API_KEY}
@@ -611,6 +612,7 @@
bind:value={RAGConfig.EXTERNAL_DOCUMENT_LOADER_URL}
/>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter External Document Loader API Key')}
required={false}
bind:value={RAGConfig.EXTERNAL_DOCUMENT_LOADER_API_KEY}
@@ -694,6 +696,7 @@
bind:value={RAGConfig.DOCLING_SERVER_URL}
/>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter Docling API Key')}
bind:value={RAGConfig.DOCLING_API_KEY}
required={false}
@@ -723,6 +726,7 @@
bind:value={RAGConfig.DOCUMENT_INTELLIGENCE_ENDPOINT}
/>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter Document Intelligence Key')}
bind:value={RAGConfig.DOCUMENT_INTELLIGENCE_KEY}
required={false}
@@ -750,6 +754,7 @@
bind:value={RAGConfig.MISTRAL_OCR_API_BASE_URL}
/>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter Mistral API Key')}
bind:value={RAGConfig.MISTRAL_OCR_API_KEY}
/>
@@ -777,6 +782,7 @@
bind:value={RAGConfig.PADDLEOCR_VL_BASE_URL}
/>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter PaddleOCR-vl API Token')}
bind:value={RAGConfig.PADDLEOCR_VL_TOKEN}
required={false}
@@ -826,6 +832,7 @@
<div class="flex w-full mt-2">
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter MinerU API Key')}
bind:value={RAGConfig.MINERU_API_KEY}
/>
@@ -1076,6 +1083,7 @@
/>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('API Key')}
bind:value={OpenAIKey}
required={false}
@@ -1091,6 +1099,7 @@
/>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('API Key')}
bind:value={OllamaKey}
required={false}
@@ -1105,7 +1114,11 @@
bind:value={AzureOpenAIUrl}
required
/>
<SensitiveInput placeholder={$i18n.t('API Key')} bind:value={AzureOpenAIKey} />
<SensitiveInput
variant="settings"
placeholder={$i18n.t('API Key')}
bind:value={AzureOpenAIKey}
/>
</div>
<div class="flex gap-2">
<input
@@ -1331,6 +1344,7 @@
/>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('API Key')}
bind:value={RAGConfig.RAG_EXTERNAL_RERANKER_API_KEY}
required={false}
+17 -16
View File
@@ -686,16 +686,25 @@
}}
/>
<div class="mb-3">
<div class="mt-0.5 mb-2.5 text-xs text-gray-400 dark:text-gray-600">{$i18n.t('Events')}</div>
<hr class="border-gray-100/10 dark:border-white/[0.015] my-2" />
<div class="mb-2.5 flex flex-col w-full justify-between">
<div class="flex justify-between items-center mb-1">
<div class="font-normal text-xs">{$i18n.t('Webhooks')}</div>
<div class="mt-5">
<div class="mb-2 text-xs text-gray-400 dark:text-gray-600">{$i18n.t('Events')}</div>
<div class="flex flex-col w-full justify-between gap-2.5">
<div class="flex w-full items-start justify-between gap-4">
<div class="min-w-0">
<div class="text-xs text-gray-600 dark:text-gray-400">{$i18n.t('Webhooks')}</div>
<div class="mt-1.5 text-[0.6875rem] text-gray-400 dark:text-gray-600">
{$i18n.t(
'Send product events as JSON to external services. Chat destinations receive readable messages.'
)}
</div>
</div>
<Tooltip content={$i18n.t('Add webhook')}>
<button class="px-1" on:click={newWebhook} type="button">
<button
class="flex size-6 items-center justify-center rounded-lg text-gray-400 transition-colors hover:bg-black/5 hover:text-gray-900 dark:text-gray-600 dark:hover:bg-white/5 dark:hover:text-white"
on:click={newWebhook}
type="button"
>
<Plus />
</button>
</Tooltip>
@@ -746,13 +755,5 @@
{$i18n.t('No event webhooks configured.')}
</div>
{/if}
<div class="mt-1.5">
<div class="text-xs text-gray-500">
{$i18n.t(
'Send product events as JSON to external services. Chat destinations receive readable messages.'
)}
</div>
</div>
</div>
</div>
@@ -462,8 +462,8 @@
<SettingsSelect
id="external-source-provider"
bind:value={sourceForm.provider}
on:change={updateProvider}
className="w-full"
on:change={updateProvider}
selectClassName="text-sm"
>
<option value="qdrant">Qdrant</option>
@@ -19,6 +19,7 @@
import AdminSettingField from './AdminSettingField.svelte';
import AdminSettingRow from './AdminSettingRow.svelte';
import AdminSettingSection from './AdminSettingSection.svelte';
import Plus from '$lib/components/icons/Plus.svelte';
const i18n: any = getContext('i18n');
@@ -212,13 +213,22 @@
</AdminSettingSection>
<AdminSettingSection title={$i18n.t('Features')}>
<AdminSettingRow label={$i18n.t('Enable Community Sharing')}>
<AdminSettingRow
label={$i18n.t('Community Sharing')}
description={$i18n.t('Allow users to share chats with the Open WebUI community.')}
>
<Switch bind:state={adminConfig.ENABLE_COMMUNITY_SHARING} />
</AdminSettingRow>
<AdminSettingRow label={$i18n.t('Enable Message Rating')}>
<AdminSettingRow
label={$i18n.t('Message Rating')}
description={$i18n.t('Let users rate assistant responses.')}
>
<Switch bind:state={adminConfig.ENABLE_MESSAGE_RATING} />
</AdminSettingRow>
<AdminSettingRow label={$i18n.t('Folders')}>
<AdminSettingRow
label={$i18n.t('Folders')}
description={$i18n.t('Allow users to organize chats into folders.')}
>
<Switch bind:state={adminConfig.ENABLE_FOLDERS} />
</AdminSettingRow>
@@ -237,37 +247,62 @@
</AdminSettingField>
{/if}
<AdminSettingRow label={$i18n.t('Memories')}>
<AdminSettingRow
label={$i18n.t('Memories')}
description={$i18n.t('Allow users to save memories for more personalized responses.')}
>
<Switch bind:state={adminConfig.ENABLE_MEMORIES} />
</AdminSettingRow>
{#if adminConfig.ENABLE_MEMORIES}
<AdminSettingRow
label={$i18n.t('Memory System Context')}
labelClassName="pl-4 text-gray-500 dark:text-gray-500"
description={$i18n.t('Include saved memories in the system context.')}
labelClassName="text-gray-500 dark:text-gray-500"
>
<Switch bind:state={adminConfig.ENABLE_MEMORY_SYSTEM_CONTEXT} />
</AdminSettingRow>
{/if}
<AdminSettingRow label={$i18n.t('Notes')}>
<AdminSettingRow
label={$i18n.t('Notes')}
description={$i18n.t('Allow users to create and manage notes.')}
>
<Switch bind:state={adminConfig.ENABLE_NOTES} />
</AdminSettingRow>
<AdminSettingRow label={$i18n.t('Channels')}>
<AdminSettingRow
label={$i18n.t('Channels')}
description={$i18n.t('Allow users to use channels for shared conversations.')}
>
<Switch bind:state={adminConfig.ENABLE_CHANNELS} />
</AdminSettingRow>
<AdminSettingRow label={$i18n.t('Calendar')}>
<AdminSettingRow
label={$i18n.t('Calendar')}
description={$i18n.t('Allow users to access calendar features.')}
>
<Switch bind:state={adminConfig.ENABLE_CALENDAR} />
</AdminSettingRow>
<AdminSettingRow label={$i18n.t('Automations')}>
<AdminSettingRow
label={$i18n.t('Automations')}
description={$i18n.t('Allow users to create and run automations.')}
>
<Switch bind:state={adminConfig.ENABLE_AUTOMATIONS} />
</AdminSettingRow>
<AdminSettingRow label={$i18n.t('User Webhooks')}>
<AdminSettingRow
label={$i18n.t('User Webhooks')}
description={$i18n.t('Allow users to configure webhooks from their account.')}
>
<Switch bind:state={adminConfig.ENABLE_USER_WEBHOOKS} />
</AdminSettingRow>
<AdminSettingRow label={$i18n.t('User Status')}>
<AdminSettingRow
label={$i18n.t('User Status')}
description={$i18n.t('Show user status information in the app.')}
>
<Switch bind:state={adminConfig.ENABLE_USER_STATUS} />
</AdminSettingRow>
<AdminSettingField label={$i18n.t('Response Watermark')}>
<AdminSettingField
label={$i18n.t('Response Watermark')}
description={$i18n.t('Append a watermark to assistant responses when configured.')}
>
<Textarea
className={textareaClass}
placeholder={$i18n.t('Enter a watermark for the response. Leave empty for none.')}
@@ -294,11 +329,16 @@
<AdminSettingSection title={$i18n.t('UI')}>
<div>
<div class="mb-2 flex w-full items-center justify-between">
<div class="text-xs text-gray-600 dark:text-gray-400">{$i18n.t('Banners')}</div>
<div class="mb-2 flex w-full items-start justify-between gap-4">
<div class="min-w-0">
<div class="text-xs text-gray-600 dark:text-gray-400">{$i18n.t('Banners')}</div>
<div class="mt-1.5 text-[0.6875rem] text-gray-400 dark:text-gray-600">
{$i18n.t('Create announcements shown to users in the app.')}
</div>
</div>
<button
class="flex size-6 items-center justify-center rounded-lg text-gray-400 transition-colors hover:text-gray-700 dark:text-gray-600 dark:hover:text-gray-300"
class="flex size-6 items-center justify-center rounded-lg text-gray-400 transition-colors hover:bg-black/5 hover:text-gray-900 dark:text-gray-600 dark:hover:bg-white/5 dark:hover:text-white"
type="button"
aria-label={$i18n.t('Add banner')}
on:click={() => {
@@ -317,16 +357,7 @@
}
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="size-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>
<Plus />
</button>
</div>
@@ -448,6 +448,7 @@
<div class="flex w-full">
<div class="flex-1">
<SensitiveInput
variant="settings"
inputClassName="text-right w-full"
placeholder={$i18n.t('API Key')}
bind:value={config.IMAGES_OPENAI_API_KEY}
@@ -569,6 +570,7 @@
<div class="flex w-full">
<div class="flex-1">
<SensitiveInput
variant="settings"
inputClassName="text-right w-full"
placeholder={$i18n.t('Enter api auth string (e.g. username:password)')}
bind:value={config.AUTOMATIC1111_API_AUTH}
@@ -672,6 +674,7 @@
<div class="flex w-full">
<div class="flex-1">
<SensitiveInput
variant="settings"
inputClassName="text-right w-full"
placeholder={$i18n.t('sk-1234')}
bind:value={config.COMFYUI_API_KEY}
@@ -850,6 +853,7 @@
<div class="flex w-full">
<div class="flex-1">
<SensitiveInput
variant="settings"
inputClassName="text-right w-full"
placeholder={$i18n.t('API Key')}
bind:value={config.IMAGES_GEMINI_API_KEY}
@@ -993,6 +997,7 @@
<div class="flex w-full">
<div class="flex-1">
<SensitiveInput
variant="settings"
inputClassName="text-right w-full"
placeholder={$i18n.t('API Key')}
bind:value={config.IMAGES_EDIT_OPENAI_API_KEY}
@@ -1083,6 +1088,7 @@
<div class="flex w-full">
<div class="flex-1">
<SensitiveInput
variant="settings"
inputClassName="text-right w-full"
placeholder={$i18n.t('sk-1234')}
bind:value={config.IMAGES_EDIT_COMFYUI_API_KEY}
@@ -1253,6 +1259,7 @@
<div class="flex w-full">
<div class="flex-1">
<SensitiveInput
variant="settings"
inputClassName="text-right w-full"
placeholder={$i18n.t('API Key')}
bind:value={config.IMAGES_EDIT_GEMINI_API_KEY}
@@ -147,8 +147,8 @@
<div class="flex-1">
<div class=" text-xs mb-1">{$i18n.t('Local Task Model')}</div>
<SettingsSelect
className="w-full"
bind:value={taskConfig.TASK_MODEL}
className="w-full"
placeholder={$i18n.t('Select a model')}
on:change={() => {
if (taskConfig.TASK_MODEL) {
@@ -190,8 +190,8 @@
<div class="flex-1">
<div class=" text-xs mb-1">{$i18n.t('External Task Model')}</div>
<SettingsSelect
className="w-full"
bind:value={taskConfig.TASK_MODEL_EXTERNAL}
className="w-full"
placeholder={$i18n.t('Select a model')}
on:change={() => {
if (taskConfig.TASK_MODEL_EXTERNAL) {
+47 -35
View File
@@ -39,6 +39,10 @@
import ChevronDown from '$lib/components/icons/ChevronDown.svelte';
import CheckCircle from '$lib/components/icons/CheckCircle.svelte';
import Minus from '$lib/components/icons/Minus.svelte';
import DocumentArrowUp from '$lib/components/icons/DocumentArrowUp.svelte';
import Download from '$lib/components/icons/Download.svelte';
import Wrench from '$lib/components/icons/Wrench.svelte';
import SettingsIcon from '$lib/components/icons/Settings.svelte';
import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
import { goto } from '$app/navigation';
@@ -440,7 +444,7 @@
{#if models !== null}
{#if selectedModelId === null}
<div class="flex h-full min-h-0 flex-col text-sm">
<div class="flex items-center justify-between mb-2">
<div class="mb-2 flex items-center justify-between">
<h2 class="text-sm font-medium text-gray-900 dark:text-white">
{$i18n.t('Models')}
<span class="ml-2 font-normal text-gray-500 dark:text-gray-500">
@@ -448,47 +452,17 @@
</span>
</h2>
<div class="flex items-center gap-2">
{#if $user?.role === 'admin'}
<button
type="button"
class="text-[0.625rem] text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors duration-100 disabled:opacity-30 disabled:pointer-events-none"
disabled={modelsImportInProgress}
on:click={() => {
modelsImportInputElement?.click();
}}
>
{$i18n.t('Import')}
</button>
<button
type="button"
class="text-[0.625rem] text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors duration-100"
on:click={() => {
downloadModels(models ?? []);
}}
>
{$i18n.t('Export')}
</button>
{/if}
<Tooltip content={$i18n.t('Settings')}>
<button
type="button"
class="text-[0.625rem] text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors duration-100"
on:click={() => {
showManageModal = true;
}}
>
{$i18n.t('Manage')}
</button>
<button
type="button"
class="text-[0.625rem] text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors duration-100"
class="flex h-6 w-6 items-center justify-center rounded-lg text-gray-400 transition-colors duration-75 hover:text-gray-700 dark:text-gray-600 dark:hover:text-gray-300"
on:click={() => {
showConfigModal = true;
}}
>
{$i18n.t('Settings')}
<SettingsIcon className="size-3.5" />
</button>
</div>
</Tooltip>
</div>
{#if $user?.role === 'admin'}
@@ -598,6 +572,44 @@
<div slot="content">
<DropdownMenu className="w-[170px] shadow-sm">
{#if $user?.role === 'admin'}
<button
class="flex h-[1.6875rem] w-full cursor-pointer select-none items-center gap-2 rounded-xl bg-transparent px-2 text-[13px] disabled:pointer-events-none disabled:opacity-40 hover:text-gray-900 dark:hover:text-gray-100"
type="button"
disabled={modelsImportInProgress}
on:click={() => {
modelsImportInputElement?.click();
}}
>
<DocumentArrowUp className="size-3.5" />
<div class="flex items-center">{$i18n.t('Import')}</div>
</button>
<button
class="flex h-[1.6875rem] w-full cursor-pointer select-none items-center gap-2 rounded-xl bg-transparent px-2 text-[13px] hover:text-gray-900 dark:hover:text-gray-100"
type="button"
on:click={() => {
downloadModels(models ?? []);
}}
>
<Download className="size-3.5" />
<div class="flex items-center">{$i18n.t('Export')}</div>
</button>
{/if}
<button
class="flex h-[1.6875rem] w-full cursor-pointer select-none items-center gap-2 rounded-xl bg-transparent px-2 text-[13px] hover:text-gray-900 dark:hover:text-gray-100"
type="button"
on:click={() => {
showManageModal = true;
}}
>
<Wrench className="size-3.5" />
<div class="flex items-center">{$i18n.t('Manage')}</div>
</button>
<hr class="mx-1 my-0.5 border-gray-100 dark:border-gray-800" />
<button
class="flex h-[1.6875rem] w-full cursor-pointer select-none items-center gap-2 rounded-xl bg-transparent px-2 text-[13px] hover:text-gray-900 dark:hover:text-gray-100"
type="button"
@@ -14,8 +14,8 @@
<div class="flex-1 mb-2.5 pr-1.5 rounded-lg bg-gray-50 dark:text-gray-300 dark:bg-gray-850">
<SettingsSelect
bind:value={selectedUrlIdx}
placeholder={$i18n.t('Select an Ollama instance')}
className="w-full"
placeholder={$i18n.t('Select an Ollama instance')}
selectClassName="text-sm"
>
{#each ollamaConfig.OLLAMA_BASE_URLS as url, idx}
@@ -814,8 +814,8 @@
>
<SettingsSelect
bind:value={deleteModelTag}
placeholder={$i18n.t('Select a model')}
className="w-full"
placeholder={$i18n.t('Select a model')}
selectClassName="text-sm"
>
<option value="" disabled selected>{$i18n.t('Select a model')}</option>
@@ -253,8 +253,8 @@
<div class="flex flex-col gap-2.5">
<AdminSettingField label={$i18n.t('Pipeline URL')}>
<SettingsSelect
className="w-full"
bind:value={selectedPipelinesUrlIdx}
className="w-full"
placeholder={$i18n.t('Select a pipeline url')}
on:change={async () => {
await tick();
@@ -330,8 +330,8 @@
<AdminSettingField label={$i18n.t('Pipeline')}>
<div class="flex gap-2">
<SettingsSelect
className="w-full"
bind:value={selectedPipelineIdx}
className="w-full"
placeholder={$i18n.t('Select a pipeline')}
on:change={async () => {
await tick();
@@ -229,6 +229,7 @@
<div class="flex w-full">
<div class="flex-1">
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter Ollama Cloud API Key')}
bind:value={webConfig.OLLAMA_CLOUD_WEB_SEARCH_API_KEY}
/>
@@ -266,6 +267,7 @@
<div class="flex w-full">
<div class="flex-1">
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter Perplexity API Key')}
bind:value={webConfig.PERPLEXITY_API_KEY}
/>
@@ -353,6 +355,7 @@
</div>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter Yacy Password')}
bind:value={webConfig.YACY_PASSWORD}
/>
@@ -367,6 +370,7 @@
</div>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter Google PSE API Key')}
bind:value={webConfig.GOOGLE_PSE_API_KEY}
/>
@@ -397,6 +401,7 @@
</div>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter Brave Search API Key')}
bind:value={webConfig.BRAVE_SEARCH_API_KEY}
/>
@@ -410,6 +415,7 @@
</div>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter Brave Search API Key')}
bind:value={webConfig.BRAVE_SEARCH_API_KEY}
/>
@@ -443,6 +449,7 @@
</div>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter Kagi Search API Key')}
bind:value={webConfig.KAGI_SEARCH_API_KEY}
/>
@@ -456,6 +463,7 @@
</div>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter Mojeek Search API Key')}
bind:value={webConfig.MOJEEK_SEARCH_API_KEY}
/>
@@ -469,6 +477,7 @@
</div>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter Bocha Search API Key')}
bind:value={webConfig.BOCHA_SEARCH_API_KEY}
/>
@@ -482,6 +491,7 @@
</div>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter Serpstack API Key')}
bind:value={webConfig.SERPSTACK_API_KEY}
/>
@@ -495,6 +505,7 @@
</div>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter Serper API Key')}
bind:value={webConfig.SERPER_API_KEY}
/>
@@ -508,6 +519,7 @@
</div>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter SERPHouse API Key')}
bind:value={webConfig.SERPHOUSE_API_KEY}
/>
@@ -538,6 +550,7 @@
</div>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter Serply API Key')}
bind:value={webConfig.SERPLY_API_KEY}
/>
@@ -551,6 +564,7 @@
</div>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter Tavily API Key')}
bind:value={webConfig.TAVILY_API_KEY}
/>
@@ -564,6 +578,7 @@
</div>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter SearchApi API Key')}
bind:value={webConfig.SEARCHAPI_API_KEY}
/>
@@ -594,6 +609,7 @@
</div>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter SerpApi API Key')}
bind:value={webConfig.SERPAPI_API_KEY}
/>
@@ -642,6 +658,7 @@
</div>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter Jina API Key')}
bind:value={webConfig.JINA_API_KEY}
/>
@@ -673,6 +690,7 @@
</div>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter Bing Search V7 Subscription Key')}
bind:value={webConfig.BING_SEARCH_V7_SUBSCRIPTION_KEY}
/>
@@ -686,6 +704,7 @@
</div>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter Exa API Key')}
bind:value={webConfig.EXA_API_KEY}
/>
@@ -699,6 +718,7 @@
</div>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter Perplexity API Key')}
bind:value={webConfig.PERPLEXITY_API_KEY}
/>
@@ -767,6 +787,7 @@
</div>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter Microsoft Web IQ API Key')}
bind:value={webConfig.MICROSOFT_WEB_IQ_API_KEY}
/>
@@ -794,6 +815,7 @@
</div>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter Sougou Search API sID')}
bind:value={webConfig.SOUGOU_API_SID}
/>
@@ -806,6 +828,7 @@
</div>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter Sougou Search API SK')}
bind:value={webConfig.SOUGOU_API_SK}
/>
@@ -837,6 +860,7 @@
</div>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter Firecrawl API Key')}
bind:value={webConfig.FIRECRAWL_API_KEY}
/>
@@ -886,6 +910,7 @@
</div>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter External Web Search API Key')}
bind:value={webConfig.EXTERNAL_WEB_SEARCH_API_KEY}
/>
@@ -917,6 +942,7 @@
</div>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter Yandex Web Search API Key')}
bind:value={webConfig.YANDEX_WEB_SEARCH_API_KEY}
/>
@@ -951,6 +977,7 @@
</div>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter You.com API Key')}
bind:value={webConfig.YOUCOM_API_KEY}
/>
@@ -964,6 +991,7 @@
</div>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter Linkup API Key')}
bind:value={webConfig.LINKUP_API_KEY}
/>
@@ -1245,6 +1273,7 @@
</div>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter Firecrawl API Key')}
bind:value={webConfig.FIRECRAWL_API_KEY}
/>
@@ -1277,6 +1306,7 @@
</div>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter Tavily API Key')}
bind:value={webConfig.TAVILY_API_KEY}
/>
@@ -1310,6 +1340,7 @@
</div>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter Microsoft Web IQ API Key')}
bind:value={webConfig.MICROSOFT_WEB_IQ_API_KEY}
/>
@@ -1356,6 +1387,7 @@
</div>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('Enter External Web Loader API Key')}
bind:value={webConfig.EXTERNAL_WEB_LOADER_API_KEY}
/>
@@ -173,8 +173,8 @@
description={$i18n.t('Choose the gender value stored on your profile.')}
>
<SettingsSelect
className="w-full"
bind:value={_gender}
className="w-full"
ariaLabel={$i18n.t('Gender')}
on:change={(e) => {
console.log(_gender);
@@ -264,7 +264,7 @@
description={$i18n.t('Copy the current session token for authenticated requests.')}
>
<div class="flex">
<SensitiveInput value={localStorage.token} readOnly={true} />
<SensitiveInput variant="settings" value={localStorage.token} readOnly={true} />
<button
class="ml-1.5 rounded-sm px-1.5 py-1 text-gray-500 transition hover:bg-gray-100 hover:text-gray-700 dark:hover:bg-gray-850 dark:hover:text-gray-300"
@@ -321,7 +321,7 @@
>
<div class="flex">
{#if APIKey}
<SensitiveInput value={APIKey} readOnly={true} />
<SensitiveInput variant="settings" value={APIKey} readOnly={true} />
<button
class="ml-1.5 rounded-sm px-1.5 py-1 text-gray-500 transition hover:bg-gray-100 hover:text-gray-700 dark:hover:bg-gray-850 dark:hover:text-gray-300"
@@ -10,8 +10,6 @@
let currentPassword = '';
let newPassword = '';
let newPasswordConfirm = '';
const inputClass =
'h-7 w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 text-xs text-gray-700 outline-hidden transition-colors placeholder:text-gray-300 focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:placeholder:text-gray-700 dark:focus:border-blue-500';
const actionButtonClass =
'text-xs text-gray-500 transition-colors hover:text-gray-900 dark:text-gray-500 dark:hover:text-white';
@@ -70,7 +68,7 @@
<div class="flex-1">
<SensitiveInput
class={inputClass}
variant="settings"
type="password"
bind:value={currentPassword}
placeholder={$i18n.t('Enter your current password')}
@@ -87,7 +85,7 @@
<div class="flex-1">
<SensitiveInput
class={inputClass}
variant="settings"
type="password"
bind:value={newPassword}
placeholder={$i18n.t('Enter your new password')}
@@ -104,7 +102,7 @@
<div class="flex-1">
<SensitiveInput
class={inputClass}
variant="settings"
type="password"
bind:value={newPasswordConfirm}
placeholder={$i18n.t('Confirm your new password')}
@@ -360,7 +360,7 @@
label={$i18n.t('Set Voice')}
description={$i18n.t('Choose the browser voice used for speech output.')}
>
<SettingsSelect className="w-full" bind:value={voice} ariaLabel={$i18n.t('Voice')}>
<SettingsSelect bind:value={voice} className="w-full" ariaLabel={$i18n.t('Voice')}>
<option value="" selected={voice !== ''}>{$i18n.t('Default')}</option>
{#each voices.filter((v) => nonLocalVoices || v.localService === true) as _voice}
<option
@@ -246,7 +246,6 @@
<SettingsSelect
bind:value={lang}
ariaLabel={$i18n.t('Language')}
className="w-48"
placeholder={$i18n.t('Select a language')}
on:change={(e) => {
changeLanguage(lang);
@@ -7,26 +7,42 @@
export let type = 'text';
export let required = true;
export let readOnly = false;
export let outerClassName = 'flex flex-1 bg-transparent';
export let inputClassName = 'w-full text-sm py-0.5 bg-transparent';
export let showButtonClassName = 'pl-1.5 transition bg-transparent';
export let variant: 'plain' | 'settings' = 'plain';
export let outerClassName = 'flex flex-1';
export let inputClassName = '';
export let showButtonClassName = '';
export let screenReader = true;
export let autocomplete = 'off';
export let name: string | undefined = undefined;
let className = '';
export { className as class };
let show = false;
const inputChromeClass =
'rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-gray-700 outline-hidden transition-colors placeholder:text-gray-300 focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:placeholder:text-gray-700 dark:focus:border-blue-500';
$: outerClass =
variant === 'settings'
? `${outerClassName} h-7 items-center rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 transition-colors focus-within:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:focus-within:border-blue-500`
: outerClassName;
$: inputBaseClass =
variant === 'settings'
? 'min-w-0 flex-1 bg-transparent text-xs text-gray-700 outline-hidden placeholder:text-gray-300 disabled:text-gray-500 dark:text-gray-300 dark:placeholder:text-gray-700'
: 'w-full bg-transparent py-0.5 text-sm outline-hidden';
$: resolvedInputClass = `${inputBaseClass} ${variant === 'plain' ? className : ''} ${inputClassName} ${
show ? '' : 'password'
}`;
$: buttonClass =
variant === 'settings'
? `ml-1.5 bg-transparent text-gray-400 transition hover:text-gray-700 dark:text-gray-500 dark:hover:text-gray-300 ${showButtonClassName}`
: `pl-1.5 bg-transparent transition ${showButtonClassName}`;
</script>
<div class={outerClassName}>
<div class={outerClass}>
{#if screenReader}
<label class="sr-only" for={id}>{placeholder || $i18n.t('Password')}</label>
{/if}
<input
{id}
class={`${inputClassName} ${show ? '' : 'password'} ${inputChromeClass}`}
class={resolvedInputClass}
{placeholder}
type={type === 'password' && !show ? 'password' : 'text'}
bind:value
@@ -36,7 +52,7 @@
{autocomplete}
/>
<button
class={showButtonClassName}
class={buttonClass}
type="button"
aria-pressed={show}
aria-label={$i18n.t('Make password visible in the user interface')}
@@ -1,4 +1,6 @@
<script lang="ts">
import ChevronDown from '$lib/components/icons/ChevronDown.svelte';
export let value: string | number | boolean = '';
export let id = '';
export let name = '';
@@ -6,7 +8,7 @@
export let placeholder = '';
export let required = false;
export let disabled = false;
export let className = 'w-fit min-w-32';
export let className = 'w-fit max-w-full';
export let selectClassName = '';
</script>
@@ -20,9 +22,13 @@
title={placeholder || undefined}
{required}
{disabled}
class="h-7 w-full truncate rounded-lg border border-gray-100/50 !bg-gray-50/40 ps-2.5 pe-9 text-left text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 disabled:opacity-50 dark:border-white/[0.04] dark:!bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500 {selectClassName}"
class="h-7 w-full max-w-full appearance-none truncate rounded-lg border border-gray-100/50 !bg-gray-50/40 ps-2.5 pe-8 text-left text-xs text-gray-700 outline-hidden transition-colors [field-sizing:content] focus:border-blue-400 disabled:opacity-50 dark:border-white/[0.04] dark:!bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500 {selectClassName}"
on:change
>
<slot />
</select>
<ChevronDown
className="pointer-events-none absolute end-2 top-1/2 size-3.5 -translate-y-1/2 text-gray-400 dark:text-gray-500"
strokeWidth="2"
/>
</div>