This commit is contained in:
Timothy Jaeryang Baek 2026-09-18 10:51:00 -04:00
parent ad9da98168
commit 7cbaabe02f
111 changed files with 70942 additions and 2334 deletions

View file

@ -26,7 +26,7 @@ export default {
namespaceSeparator: false,
output: 'src/lib/i18n/locales/$LOCALE/$NAMESPACE.json',
pluralSeparator: '_',
input: 'src/**/*.{js,svelte}',
input: 'src/**/*.{js,ts,svelte}',
sort: true,
verbose: true,
failOnWarnings: false,

View file

@ -244,7 +244,9 @@
dispatch('save');
}}
>
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">{$i18n.t('Audio')}</h2>
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">
{$i18n.t('settings.admin.audio.title')}
</h2>
{#if $config?.features?.slim === true}
<p class="mb-4 text-xs text-gray-500">
{$i18n.t(
@ -254,10 +256,10 @@
{/if}
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5">
<AdminSettingSection title={$i18n.t('Speech-to-Text')} first>
<AdminSettingSection title={$i18n.t('settings.admin.audio.sections.speechToText.title')} first>
<AdminSettingRow
label={$i18n.t('Speech-to-Text Engine')}
description={$i18n.t('Choose the transcription provider used for audio input.')}
label={$i18n.t('settings.admin.audio.speechToTextEngine.label')}
description={$i18n.t('settings.admin.audio.speechToTextEngine.description')}
>
<SettingsSelect bind:value={STT_ENGINE} placeholder={$i18n.t('Select an engine')}>
<option value="" disabled={$config?.features?.slim === true}
@ -273,8 +275,8 @@
{#if STT_ENGINE !== 'web'}
<AdminSettingField
label={$i18n.t('Supported MIME Types')}
description={$i18n.t('Comma-separated audio or video MIME types accepted for upload.')}
label={$i18n.t('settings.admin.audio.supportedMimeTypes.label')}
description={$i18n.t('settings.admin.audio.supportedMimeTypes.description')}
>
<input
class={inputClass}
@ -286,26 +288,26 @@
{#if STT_ENGINE === 'openai'}
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2">
<AdminSettingField label={$i18n.t('API Base URL')}>
<AdminSettingField label={$i18n.t('settings.admin.audio.sttOpenaiApiBaseUrl.label')}>
<input
class={inputClass}
placeholder={$i18n.t('API Base URL')}
placeholder={$i18n.t('settings.admin.audio.sttOpenaiApiBaseUrl.label')}
bind:value={STT_OPENAI_API_BASE_URL}
required
/>
</AdminSettingField>
<AdminSettingField label={$i18n.t('API Key')}>
<AdminSettingField label={$i18n.t('settings.admin.audio.sttOpenaiApiKey.label')}>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('API Key')}
placeholder={$i18n.t('settings.admin.audio.sttOpenaiApiKey.label')}
bind:value={STT_OPENAI_API_KEY}
/>
</AdminSettingField>
</div>
<AdminSettingRow
label={$i18n.t('Request Format')}
description={$i18n.t('Select how audio is sent to the OpenAI-compatible endpoint.')}
label={$i18n.t('settings.admin.audio.requestFormat.label')}
description={$i18n.t('settings.admin.audio.requestFormat.description')}
>
<SettingsSelect bind:value={STT_OPENAI_API_REQUEST_FORMAT}>
<option value="multipart">{$i18n.t('Multipart Upload')}</option>
@ -313,7 +315,7 @@
</SettingsSelect>
</AdminSettingRow>
<AdminSettingField label={$i18n.t('STT Model')}>
<AdminSettingField label={$i18n.t('settings.admin.audio.sttModel.label')}>
<input
list="stt-openai-model-list"
class={inputClass}
@ -325,17 +327,17 @@
</datalist>
</AdminSettingField>
{:else if STT_ENGINE === 'deepgram'}
<AdminSettingField label={$i18n.t('API Key')}>
<AdminSettingField label={$i18n.t('settings.admin.audio.sttDeepgramApiKey.label')}>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('API Key')}
placeholder={$i18n.t('settings.admin.audio.sttDeepgramApiKey.label')}
bind:value={STT_DEEPGRAM_API_KEY}
/>
</AdminSettingField>
<AdminSettingField
label={$i18n.t('STT Model')}
description={$i18n.t('Leave model field empty to use the default model.')}
label={$i18n.t('settings.admin.audio.sttModel.label')}
description={$i18n.t('settings.admin.audio.sttModel.description')}
>
<input
class={inputClass}
@ -349,38 +351,38 @@
</div>
</AdminSettingField>
{:else if STT_ENGINE === 'azure'}
<AdminSettingField label={$i18n.t('API Key')}>
<AdminSettingField label={$i18n.t('settings.admin.audio.sttAzureApiKey.label')}>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('API Key')}
placeholder={$i18n.t('settings.admin.audio.sttAzureApiKey.label')}
bind:value={STT_AZURE_API_KEY}
required
/>
</AdminSettingField>
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2">
<AdminSettingField label={$i18n.t('Azure Region')}>
<AdminSettingField label={$i18n.t('settings.admin.audio.sttAzureRegion.label')}>
<input
class={inputClass}
bind:value={STT_AZURE_REGION}
placeholder={$i18n.t('e.g., westus (leave blank for eastus)')}
/>
</AdminSettingField>
<AdminSettingField label={$i18n.t('Language Locales')}>
<AdminSettingField label={$i18n.t('settings.admin.audio.languageLocales.label')}>
<input
class={inputClass}
bind:value={STT_AZURE_LOCALES}
placeholder={$i18n.t('e.g., en-US,ja-JP (leave blank for auto-detect)')}
/>
</AdminSettingField>
<AdminSettingField label={$i18n.t('Endpoint URL')}>
<AdminSettingField label={$i18n.t('settings.admin.audio.sttAzureBaseUrl.label')}>
<input
class={inputClass}
bind:value={STT_AZURE_BASE_URL}
placeholder={$i18n.t('(leave blank for to use commercial endpoint)')}
/>
</AdminSettingField>
<AdminSettingField label={$i18n.t('Max Speakers')}>
<AdminSettingField label={$i18n.t('settings.admin.audio.maxSpeakers.label')}>
<input
class={inputClass}
bind:value={STT_AZURE_MAX_SPEAKERS}
@ -390,26 +392,26 @@
</div>
{:else if STT_ENGINE === 'mistral'}
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2">
<AdminSettingField label={$i18n.t('API Base URL')}>
<AdminSettingField label={$i18n.t('settings.admin.audio.sttMistralApiBaseUrl.label')}>
<input
class={inputClass}
placeholder={$i18n.t('API Base URL')}
placeholder={$i18n.t('settings.admin.audio.sttMistralApiBaseUrl.label')}
bind:value={STT_MISTRAL_API_BASE_URL}
required
/>
</AdminSettingField>
<AdminSettingField label={$i18n.t('API Key')}>
<AdminSettingField label={$i18n.t('settings.admin.audio.sttMistralApiKey.label')}>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('API Key')}
placeholder={$i18n.t('settings.admin.audio.sttMistralApiKey.label')}
bind:value={STT_MISTRAL_API_KEY}
/>
</AdminSettingField>
</div>
<AdminSettingField
label={$i18n.t('STT Model')}
description={$i18n.t('Leave empty to use the default model (voxtral-mini-latest).')}
label={$i18n.t('settings.admin.audio.mistralSttModel.label')}
description={$i18n.t('settings.admin.audio.mistralSttModel.description')}
>
<input class={inputClass} bind:value={STT_MODEL} placeholder="voxtral-mini-latest" />
<div class="mt-1 {linkedHelpClass}">
@ -420,10 +422,8 @@
</AdminSettingField>
<AdminSettingRow
label={$i18n.t('Use Chat Completions API')}
description={$i18n.t(
'Use /v1/chat/completions endpoint instead of /v1/audio/transcriptions for potentially better accuracy.'
)}
label={$i18n.t('settings.admin.audio.useChatCompletionsApi.label')}
description={$i18n.t('settings.admin.audio.useChatCompletionsApi.description')}
let:labelId
>
<Switch bind:state={STT_MISTRAL_USE_CHAT_COMPLETIONS} ariaLabelledbyId={labelId} />
@ -433,8 +433,8 @@
Do not alter, remove, obscure, or replace it except as LICENSE permits:
https://docs.openwebui.com/license. -->
<AdminSettingField
label={$i18n.t('STT Model')}
description={$i18n.t('Open WebUI uses faster-whisper internally.')}
label={$i18n.t('settings.admin.audio.sttWhisperModel.label')}
description={$i18n.t('settings.admin.audio.sttWhisperModel.description')}
>
<div class="flex w-full gap-2">
<input
@ -449,7 +449,7 @@
sttModelUpdateHandler();
}}
disabled={STT_WHISPER_MODEL_LOADING}
aria-label={$i18n.t('Update model')}
aria-label={$i18n.t('settings.admin.audio.updateModel.label')}
>
{#if STT_WHISPER_MODEL_LOADING}
<Spinner />
@ -481,10 +481,10 @@
{/if}
</AdminSettingSection>
<AdminSettingSection title={$i18n.t('Text-to-Speech')}>
<AdminSettingSection title={$i18n.t('settings.admin.audio.sections.textToSpeech.title')}>
<AdminSettingRow
label={$i18n.t('Text-to-Speech Engine')}
description={$i18n.t('Choose the speech provider used for assistant audio output.')}
label={$i18n.t('settings.admin.audio.textToSpeechEngine.label')}
description={$i18n.t('settings.admin.audio.textToSpeechEngine.description')}
>
<SettingsSelect
bind:value={TTS_ENGINE}
@ -521,50 +521,50 @@
{#if TTS_ENGINE === 'openai'}
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2">
<AdminSettingField label={$i18n.t('API Base URL')}>
<AdminSettingField label={$i18n.t('settings.admin.audio.ttsOpenaiApiBaseUrl.label')}>
<input
class={inputClass}
placeholder={$i18n.t('API Base URL')}
placeholder={$i18n.t('settings.admin.audio.ttsOpenaiApiBaseUrl.label')}
bind:value={TTS_OPENAI_API_BASE_URL}
required
/>
</AdminSettingField>
<AdminSettingField label={$i18n.t('API Key')}>
<AdminSettingField label={$i18n.t('settings.admin.audio.ttsOpenaiApiKey.label')}>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('API Key')}
placeholder={$i18n.t('settings.admin.audio.ttsOpenaiApiKey.label')}
bind:value={TTS_OPENAI_API_KEY}
/>
</AdminSettingField>
</div>
{:else if TTS_ENGINE === 'elevenlabs'}
<AdminSettingField label={$i18n.t('API Key')}>
<AdminSettingField label={$i18n.t('settings.admin.audio.ttsApiKey.label')}>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('API Key')}
placeholder={$i18n.t('settings.admin.audio.ttsApiKey.label')}
bind:value={TTS_API_KEY}
required
/>
</AdminSettingField>
{:else if TTS_ENGINE === 'azure'}
<AdminSettingField label={$i18n.t('API Key')}>
<AdminSettingField label={$i18n.t('settings.admin.audio.ttsApiKey.label')}>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('API Key')}
placeholder={$i18n.t('settings.admin.audio.ttsApiKey.label')}
bind:value={TTS_API_KEY}
required
/>
</AdminSettingField>
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2">
<AdminSettingField label={$i18n.t('Azure Region')}>
<AdminSettingField label={$i18n.t('settings.admin.audio.ttsAzureSpeechRegion.label')}>
<input
class={inputClass}
bind:value={TTS_AZURE_SPEECH_REGION}
placeholder={$i18n.t('e.g., westus (leave blank for eastus)')}
/>
</AdminSettingField>
<AdminSettingField label={$i18n.t('Endpoint URL')}>
<AdminSettingField label={$i18n.t('settings.admin.audio.ttsAzureSpeechBaseUrl.label')}>
<input
class={inputClass}
bind:value={TTS_AZURE_SPEECH_BASE_URL}
@ -574,18 +574,18 @@
</div>
{:else if TTS_ENGINE === 'mistral'}
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2">
<AdminSettingField label={$i18n.t('API Base URL')}>
<AdminSettingField label={$i18n.t('settings.admin.audio.ttsMistralApiBaseUrl.label')}>
<input
class={inputClass}
placeholder={$i18n.t('API Base URL')}
placeholder={$i18n.t('settings.admin.audio.ttsMistralApiBaseUrl.label')}
bind:value={TTS_MISTRAL_API_BASE_URL}
required
/>
</AdminSettingField>
<AdminSettingField label={$i18n.t('API Key')}>
<AdminSettingField label={$i18n.t('settings.admin.audio.ttsMistralApiKey.label')}>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('API Key')}
placeholder={$i18n.t('settings.admin.audio.ttsMistralApiKey.label')}
bind:value={TTS_MISTRAL_API_KEY}
/>
</AdminSettingField>
@ -593,7 +593,7 @@
{/if}
{#if TTS_ENGINE === ''}
<AdminSettingField label={$i18n.t('TTS Voice')}>
<AdminSettingField label={$i18n.t('settings.admin.audio.ttsVoice.label')}>
<SettingsSelect bind:value={TTS_VOICE} className="w-full">
<option value="" selected={TTS_VOICE !== ''}>{$i18n.t('Default')}</option>
{#each voices as voice}
@ -610,8 +610,8 @@
Do not alter, remove, obscure, or replace it except as LICENSE permits:
https://docs.openwebui.com/license. -->
<AdminSettingField
label={$i18n.t('TTS Model')}
description={$i18n.t('Open WebUI uses SpeechT5 and CMU Arctic speaker embeddings.')}
label={$i18n.t('settings.admin.audio.ttsModel.label')}
description={$i18n.t('settings.admin.audio.ttsModel.description')}
>
<input
list="tts-transformers-model-list"
@ -635,7 +635,7 @@
</AdminSettingField>
{:else if TTS_ENGINE === 'openai'}
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2">
<AdminSettingField label={$i18n.t('TTS Voice')}>
<AdminSettingField label={$i18n.t('settings.admin.audio.ttsVoice.label')}>
<TTSVoiceInput
bind:value={TTS_VOICE}
voices={providerVoices}
@ -643,7 +643,7 @@
className={inputClass}
/>
</AdminSettingField>
<AdminSettingField label={$i18n.t('TTS Model')}>
<AdminSettingField label={$i18n.t('settings.admin.audio.ttsModel.label')}>
<input
list="tts-model-list"
class={inputClass}
@ -653,10 +653,8 @@
</AdminSettingField>
</div>
<AdminSettingField
label={$i18n.t('Additional Parameters')}
description={$i18n.t(
'Enter additional OpenAI-compatible TTS request parameters as JSON.'
)}
label={$i18n.t('settings.admin.audio.additionalParameters.label')}
description={$i18n.t('settings.admin.audio.additionalParameters.description')}
>
<Textarea
className={textareaClass}
@ -666,7 +664,7 @@
</AdminSettingField>
{:else if TTS_ENGINE === 'elevenlabs' || TTS_ENGINE === 'mistral'}
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2">
<AdminSettingField label={$i18n.t('TTS Voice')}>
<AdminSettingField label={$i18n.t('settings.admin.audio.ttsVoice.label')}>
<TTSVoiceInput
bind:value={TTS_VOICE}
voices={providerVoices}
@ -674,7 +672,7 @@
className={inputClass}
/>
</AdminSettingField>
<AdminSettingField label={$i18n.t('TTS Model')}>
<AdminSettingField label={$i18n.t('settings.admin.audio.ttsModel.label')}>
<input
list="tts-model-list"
class={inputClass}
@ -685,7 +683,7 @@
</div>
{:else if TTS_ENGINE === 'azure'}
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2">
<AdminSettingField label={$i18n.t('TTS Voice')}>
<AdminSettingField label={$i18n.t('settings.admin.audio.ttsVoice.label')}>
<TTSVoiceInput
bind:value={TTS_VOICE}
voices={providerVoices}
@ -693,7 +691,7 @@
className={inputClass}
/>
</AdminSettingField>
<AdminSettingField label={$i18n.t('Output format')}>
<AdminSettingField label={$i18n.t('settings.admin.audio.outputFormat.label')}>
<input
class={inputClass}
bind:value={TTS_AZURE_SPEECH_OUTPUT_FORMAT}
@ -718,10 +716,8 @@
</datalist>
<AdminSettingRow
label={$i18n.t('Response Splitting')}
description={$i18n.t(
"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string."
)}
label={$i18n.t('settings.admin.audio.responseSplitting.label')}
description={$i18n.t('settings.admin.audio.responseSplitting.description')}
>
<SettingsSelect
aria-label={$i18n.t('Select how to split message text for TTS requests')}

View file

@ -131,15 +131,18 @@
<form class="flex h-full flex-col justify-between text-sm" on:submit|preventDefault={submitHandler}>
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">
{$i18n.t('Authentication')}
{$i18n.t('settings.admin.authentication.title')}
</h2>
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5">
{#if adminConfig !== null}
<AdminSettingSection title={$i18n.t('User Access')} first>
<AdminSettingSection
title={$i18n.t('settings.admin.authentication.sections.userAccess.title')}
first
>
<AdminSettingRow
label={$i18n.t('Default User Role')}
description={$i18n.t('Role assigned to new users when they create an account.')}
label={$i18n.t('settings.admin.authentication.defaultUserRole.label')}
description={$i18n.t('settings.admin.authentication.defaultUserRole.description')}
>
<SettingsSelect
bind:value={adminConfig.DEFAULT_USER_ROLE}
@ -152,8 +155,8 @@
</AdminSettingRow>
<AdminSettingRow
label={$i18n.t('Default Group')}
description={$i18n.t('Group assigned to new users by default.')}
label={$i18n.t('settings.admin.authentication.defaultGroup.label')}
description={$i18n.t('settings.admin.authentication.defaultGroup.description')}
>
<SettingsSelect
bind:value={adminConfig.DEFAULT_GROUP_ID}
@ -167,26 +170,24 @@
</AdminSettingRow>
<AdminSettingRow
label={$i18n.t('Login Form')}
description={$i18n.t(
'Show email and password sign-in on the login page. Set up SSO or LDAP before disabling. Disabling also blocks local sign-ups; password API access is unchanged.'
)}
label={$i18n.t('settings.admin.authentication.loginForm.label')}
description={$i18n.t('settings.admin.authentication.loginForm.description')}
let:labelId
>
<Switch bind:state={adminConfig.ENABLE_LOGIN_FORM} ariaLabelledbyId={labelId} />
</AdminSettingRow>
<AdminSettingRow
label={$i18n.t('New Sign Ups')}
description={$i18n.t('Allow new users to create accounts.')}
label={$i18n.t('settings.admin.authentication.newSignUps.label')}
description={$i18n.t('settings.admin.authentication.newSignUps.description')}
let:labelId
>
<Switch bind:state={adminConfig.ENABLE_SIGNUP} ariaLabelledbyId={labelId} />
</AdminSettingRow>
<AdminSettingRow
label={$i18n.t('API Keys')}
description={$i18n.t('Allow users to create API keys for programmatic access.')}
label={$i18n.t('settings.admin.authentication.apiKeys.label')}
description={$i18n.t('settings.admin.authentication.apiKeys.description')}
let:labelId
>
<Switch bind:state={adminConfig.ENABLE_API_KEYS} ariaLabelledbyId={labelId} />
@ -194,8 +195,10 @@
{#if adminConfig?.ENABLE_API_KEYS}
<AdminSettingRow
label={$i18n.t('API Key Endpoint Restrictions')}
description={$i18n.t('Limit API keys to configured endpoints.')}
label={$i18n.t('settings.admin.authentication.apiKeyEndpointRestrictions.label')}
description={$i18n.t(
'settings.admin.authentication.apiKeyEndpointRestrictions.description'
)}
let:labelId
>
<Switch
@ -206,8 +209,8 @@
{#if adminConfig?.ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS}
<AdminSettingField
label={$i18n.t('Allowed Endpoints')}
description={$i18n.t('Comma-separated API paths that API keys can access.')}
label={$i18n.t('settings.admin.authentication.allowedEndpoints.label')}
description={$i18n.t('settings.admin.authentication.allowedEndpoints.description')}
>
<input
class={inputClass}
@ -227,10 +230,8 @@
{/if}
<AdminSettingField
label={$i18n.t('JWT Expiration')}
description={$i18n.t(
"Valid time units: 's', 'm', 'h', 'd', 'w' or '-1' for no expiration."
)}
label={$i18n.t('settings.admin.authentication.jwtExpiration.label')}
description={$i18n.t('settings.admin.authentication.jwtExpiration.description')}
>
<input
class={inputClass}
@ -251,10 +252,12 @@
</AdminSettingField>
</AdminSettingSection>
<AdminSettingSection title={$i18n.t('Pending Accounts')}>
<AdminSettingSection
title={$i18n.t('settings.admin.authentication.sections.pendingAccounts.title')}
>
<AdminSettingRow
label={$i18n.t('Admin Details')}
description={$i18n.t('Show admin contact details while an account waits for approval.')}
label={$i18n.t('settings.admin.authentication.adminDetails.label')}
description={$i18n.t('settings.admin.authentication.adminDetails.description')}
let:labelId
>
<Switch bind:state={adminConfig.SHOW_ADMIN_DETAILS} ariaLabelledbyId={labelId} />
@ -262,8 +265,8 @@
{#if adminConfig.SHOW_ADMIN_DETAILS}
<AdminSettingField
label={$i18n.t('Admin Contact Email')}
description={$i18n.t('Email shown in the pending account overlay.')}
label={$i18n.t('settings.admin.authentication.adminContactEmail.label')}
description={$i18n.t('settings.admin.authentication.adminContactEmail.description')}
>
<input
class={inputClass}
@ -275,8 +278,8 @@
{/if}
<AdminSettingField
label={$i18n.t('Pending User Overlay Title')}
description={$i18n.t('Custom title shown while an account waits for approval.')}
label={$i18n.t('settings.admin.authentication.pendingUserOverlayTitle.label')}
description={$i18n.t('settings.admin.authentication.pendingUserOverlayTitle.description')}
>
<Textarea
className={textareaClass}
@ -288,8 +291,10 @@
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Pending User Overlay Content')}
description={$i18n.t('Custom message shown while an account waits for approval.')}
label={$i18n.t('settings.admin.authentication.pendingUserOverlayContent.label')}
description={$i18n.t(
'settings.admin.authentication.pendingUserOverlayContent.description'
)}
>
<Textarea
className={textareaClass}
@ -302,10 +307,10 @@
</AdminSettingSection>
{/if}
<AdminSettingSection title={$i18n.t('LDAP')}>
<AdminSettingSection title={$i18n.t('settings.admin.authentication.sections.ldap.title')}>
<AdminSettingRow
label={$i18n.t('LDAP')}
description={$i18n.t('Allow users to authenticate with an LDAP directory.')}
label={$i18n.t('settings.admin.authentication.ldap.label')}
description={$i18n.t('settings.admin.authentication.ldap.description')}
let:labelId
>
<Switch bind:state={ENABLE_LDAP} ariaLabelledbyId={labelId} />
@ -314,8 +319,8 @@
{#if ENABLE_LDAP}
<div class="grid grid-cols-1 gap-x-3 gap-y-2.5 sm:grid-cols-2">
<AdminSettingField
label={$i18n.t('Label')}
description={$i18n.t('Display name for this LDAP connection.')}
label={$i18n.t('settings.admin.authentication.label.label')}
description={$i18n.t('settings.admin.authentication.label.description')}
>
<input
class={inputClass}
@ -328,8 +333,8 @@
<div class="grid grid-cols-1 gap-x-3 gap-y-2.5 sm:grid-cols-2">
<AdminSettingField
label={$i18n.t('Host')}
description={$i18n.t('LDAP server hostname or IP address.')}
label={$i18n.t('settings.admin.authentication.host.label')}
description={$i18n.t('settings.admin.authentication.host.description')}
>
<input
class={inputClass}
@ -339,7 +344,10 @@
/>
</AdminSettingField>
<AdminSettingField label={$i18n.t('Port')} description={$i18n.t('LDAP server port.')}>
<AdminSettingField
label={$i18n.t('settings.admin.authentication.port.label')}
description={$i18n.t('settings.admin.authentication.port.description')}
>
<Tooltip
placement="top-start"
content={$i18n.t('Default to 389 or 636 if TLS is enabled')}
@ -357,8 +365,8 @@
<div class="grid grid-cols-1 gap-x-3 gap-y-2.5 sm:grid-cols-2">
<AdminSettingField
label={$i18n.t('Application DN')}
description={$i18n.t('Bind DN used for directory search.')}
label={$i18n.t('settings.admin.authentication.applicationDn.label')}
description={$i18n.t('settings.admin.authentication.applicationDn.description')}
>
<Tooltip
content={$i18n.t('The Application Account DN you bind with for search')}
@ -373,8 +381,8 @@
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Application DN Password')}
description={$i18n.t('Password for the bind DN.')}
label={$i18n.t('settings.admin.authentication.applicationDnPassword.label')}
description={$i18n.t('settings.admin.authentication.applicationDnPassword.description')}
>
<SensitiveInput
variant="settings"
@ -387,8 +395,8 @@
<div class="grid grid-cols-1 gap-x-3 gap-y-2.5 sm:grid-cols-2">
<AdminSettingField
label={$i18n.t('Attribute for Mail')}
description={$i18n.t('LDAP attribute used as the user email address.')}
label={$i18n.t('settings.admin.authentication.attributeForMail.label')}
description={$i18n.t('settings.admin.authentication.attributeForMail.description')}
>
<Tooltip
content={$i18n.t(
@ -406,8 +414,8 @@
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Attribute for Username')}
description={$i18n.t('LDAP attribute used as the username.')}
label={$i18n.t('settings.admin.authentication.attributeForUsername.label')}
description={$i18n.t('settings.admin.authentication.attributeForUsername.description')}
>
<Tooltip
content={$i18n.t(
@ -426,8 +434,8 @@
</div>
<AdminSettingField
label={$i18n.t('Search Base')}
description={$i18n.t('Base DN used when searching for users.')}
label={$i18n.t('settings.admin.authentication.searchBase.label')}
description={$i18n.t('settings.admin.authentication.searchBase.description')}
>
<Tooltip content={$i18n.t('The base to search for users')} placement="top-start">
<input
@ -440,8 +448,8 @@
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Search Filters')}
description={$i18n.t('LDAP filter used to match signing-in users.')}
label={$i18n.t('settings.admin.authentication.searchFilters.label')}
description={$i18n.t('settings.admin.authentication.searchFilters.description')}
>
<input
class={inputClass}
@ -458,8 +466,8 @@
</AdminSettingField>
<AdminSettingRow
label={$i18n.t('TLS')}
description={$i18n.t('Use TLS when connecting to the LDAP server.')}
label={$i18n.t('settings.admin.authentication.tls.label')}
description={$i18n.t('settings.admin.authentication.tls.description')}
let:labelId
>
<Switch bind:state={LDAP_SERVER.use_tls} ariaLabelledbyId={labelId} />
@ -467,8 +475,8 @@
{#if LDAP_SERVER.use_tls}
<AdminSettingField
label={$i18n.t('Certificate Path')}
description={$i18n.t('Certificate file used for TLS verification.')}
label={$i18n.t('settings.admin.authentication.certificatePath.label')}
description={$i18n.t('settings.admin.authentication.certificatePath.description')}
>
<input
class={inputClass}
@ -478,16 +486,16 @@
</AdminSettingField>
<AdminSettingRow
label={$i18n.t('Validate Certificate')}
description={$i18n.t('Verify the LDAP server certificate when TLS is enabled.')}
label={$i18n.t('settings.admin.authentication.validateCertificate.label')}
description={$i18n.t('settings.admin.authentication.validateCertificate.description')}
let:labelId
>
<Switch bind:state={LDAP_SERVER.validate_cert} ariaLabelledbyId={labelId} />
</AdminSettingRow>
<AdminSettingField
label={$i18n.t('Ciphers')}
description={$i18n.t('TLS cipher list for LDAP connections.')}
label={$i18n.t('settings.admin.authentication.ciphers.label')}
description={$i18n.t('settings.admin.authentication.ciphers.description')}
>
<Tooltip content={$i18n.t('Default to ALL')} placement="top-start">
<input
@ -503,8 +511,8 @@
Do not alter, remove, obscure, or replace it except as LICENSE permits:
https://docs.openwebui.com/license. -->
<AdminSettingRow
label={$i18n.t('Group Mapping')}
description={$i18n.t('Map LDAP groups to Open WebUI groups.')}
label={$i18n.t('settings.admin.authentication.enableGroupManagement.label')}
description={$i18n.t('settings.admin.authentication.enableGroupManagement.description')}
let:labelId
>
<Switch bind:state={LDAP_SERVER.enable_group_management} ariaLabelledbyId={labelId} />
@ -512,16 +520,16 @@
{#if LDAP_SERVER.enable_group_management}
<AdminSettingRow
label={$i18n.t('Auto-Create Groups')}
description={$i18n.t('Create missing groups from LDAP groups.')}
label={$i18n.t('settings.admin.authentication.enableGroupCreation.label')}
description={$i18n.t('settings.admin.authentication.enableGroupCreation.description')}
let:labelId
>
<Switch bind:state={LDAP_SERVER.enable_group_creation} ariaLabelledbyId={labelId} />
</AdminSettingRow>
<AdminSettingField
label={$i18n.t('Group Attribute')}
description={$i18n.t('LDAP attribute containing the user group memberships.')}
label={$i18n.t('settings.admin.authentication.groupAttribute.label')}
description={$i18n.t('settings.admin.authentication.groupAttribute.description')}
>
<Tooltip content={$i18n.t('Default to memberOf')} placement="top-start">
<input
@ -536,7 +544,9 @@
</AdminSettingSection>
{#if oauthConfig}
<AdminSettingSection title={$i18n.t('OAuth / OIDC')}>
<AdminSettingSection
title={$i18n.t('settings.admin.authentication.sections.oauthOidc.title')}
>
{#if !oauthEditable}
<div
class="rounded-lg bg-yellow-500/10 px-2 py-1.5 text-[0.6875rem] text-yellow-700 dark:text-yellow-200"
@ -553,8 +563,8 @@
disabled={!oauthEditable}
>
<AdminSettingRow
label={$i18n.t('OAuth / OIDC')}
description={$i18n.t('Allow users to authenticate with an OAuth / OIDC provider.')}
label={$i18n.t('settings.admin.authentication.oauthOidc.label')}
description={$i18n.t('settings.admin.authentication.oauthOidc.description')}
let:labelId
>
<Switch bind:state={oauthConfig.ENABLE_OAUTH} ariaLabelledbyId={labelId} />
@ -563,8 +573,8 @@
{#if oauthConfig.ENABLE_OAUTH}
<div class="grid grid-cols-1 gap-x-3 gap-y-2.5 sm:grid-cols-2">
<AdminSettingField
label={$i18n.t('Provider Name')}
description={$i18n.t('Display name shown for the OAuth provider.')}
label={$i18n.t('settings.admin.authentication.providerName.label')}
description={$i18n.t('settings.admin.authentication.providerName.description')}
>
<input
class={inputClass}
@ -574,8 +584,8 @@
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Provider URL')}
description={$i18n.t('OpenID discovery URL for this provider.')}
label={$i18n.t('settings.admin.authentication.providerUrl.label')}
description={$i18n.t('settings.admin.authentication.providerUrl.description')}
>
<input
class={inputClass}
@ -587,8 +597,8 @@
<div class="grid grid-cols-1 gap-x-3 gap-y-2.5 sm:grid-cols-2">
<AdminSettingField
label={$i18n.t('Client ID')}
description={$i18n.t('OAuth client identifier from the provider.')}
label={$i18n.t('settings.admin.authentication.clientId.label')}
description={$i18n.t('settings.admin.authentication.clientId.description')}
>
<input
class={inputClass}
@ -598,8 +608,8 @@
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Client Secret')}
description={$i18n.t('OAuth client secret from the provider.')}
label={$i18n.t('settings.admin.authentication.clientSecret.label')}
description={$i18n.t('settings.admin.authentication.clientSecret.description')}
>
<SensitiveInput
variant="settings"
@ -612,8 +622,8 @@
<div class="grid grid-cols-1 gap-x-3 gap-y-2.5 sm:grid-cols-2">
<AdminSettingField
label={$i18n.t('Redirect URI')}
description={$i18n.t('Callback URI registered with the provider.')}
label={$i18n.t('settings.admin.authentication.redirectUri.label')}
description={$i18n.t('settings.admin.authentication.redirectUri.description')}
>
<input
class={inputClass}
@ -623,8 +633,8 @@
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Scopes')}
description={$i18n.t('OAuth scopes requested during sign-in.')}
label={$i18n.t('settings.admin.authentication.scopes.label')}
description={$i18n.t('settings.admin.authentication.scopes.description')}
>
<input
class={inputClass}
@ -636,8 +646,8 @@
<div class="grid grid-cols-1 gap-x-3 gap-y-2.5 sm:grid-cols-2">
<AdminSettingField
label={$i18n.t('Email Claim')}
description={$i18n.t('Claim used as the user email address.')}
label={$i18n.t('settings.admin.authentication.emailClaim.label')}
description={$i18n.t('settings.admin.authentication.emailClaim.description')}
>
<input
class={inputClass}
@ -647,8 +657,8 @@
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Username Claim')}
description={$i18n.t('Claim used as the display name.')}
label={$i18n.t('settings.admin.authentication.usernameClaim.label')}
description={$i18n.t('settings.admin.authentication.usernameClaim.description')}
>
<input
class={inputClass}
@ -660,8 +670,8 @@
<div class="grid grid-cols-1 gap-x-3 gap-y-2.5 sm:grid-cols-2">
<AdminSettingField
label={$i18n.t('Picture Claim')}
description={$i18n.t('Claim used as the profile picture URL.')}
label={$i18n.t('settings.admin.authentication.pictureClaim.label')}
description={$i18n.t('settings.admin.authentication.pictureClaim.description')}
>
<input
class={inputClass}
@ -671,8 +681,8 @@
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Sub Claim')}
description={$i18n.t('Claim used as the stable user identifier.')}
label={$i18n.t('settings.admin.authentication.subClaim.label')}
description={$i18n.t('settings.admin.authentication.subClaim.description')}
>
<input
class={inputClass}
@ -683,16 +693,18 @@
</div>
<AdminSettingRow
label={$i18n.t('OAuth Signup')}
description={$i18n.t('Allow users to create accounts through OAuth.')}
label={$i18n.t('settings.admin.authentication.oauthSignup.label')}
description={$i18n.t('settings.admin.authentication.oauthSignup.description')}
let:labelId
>
<Switch bind:state={oauthConfig.ENABLE_OAUTH_SIGNUP} ariaLabelledbyId={labelId} />
</AdminSettingRow>
<AdminSettingRow
label={$i18n.t('Merge Accounts by Email')}
description={$i18n.t('Link OAuth sign-ins to existing accounts with the same email.')}
label={$i18n.t('settings.admin.authentication.mergeAccountsByEmail.label')}
description={$i18n.t(
'settings.admin.authentication.mergeAccountsByEmail.description'
)}
let:labelId
>
<Switch
@ -702,18 +714,16 @@
</AdminSettingRow>
<AdminSettingRow
label={$i18n.t('Auto Redirect')}
description={$i18n.t(
'Send users directly to the OAuth provider from the sign-in page.'
)}
label={$i18n.t('settings.admin.authentication.autoRedirect.label')}
description={$i18n.t('settings.admin.authentication.autoRedirect.description')}
let:labelId
>
<Switch bind:state={oauthConfig.OAUTH_AUTO_REDIRECT} ariaLabelledbyId={labelId} />
</AdminSettingRow>
<AdminSettingField
label={$i18n.t('Allowed Domains')}
description={$i18n.t('Email domains allowed to sign in with OAuth.')}
label={$i18n.t('settings.admin.authentication.allowedDomains.label')}
description={$i18n.t('settings.admin.authentication.allowedDomains.description')}
>
<input
class={inputClass}
@ -726,8 +736,8 @@
Do not alter, remove, obscure, or replace it except as LICENSE permits:
https://docs.openwebui.com/license. -->
<AdminSettingRow
label={$i18n.t('Role Mapping')}
description={$i18n.t('Map OAuth claims to Open WebUI roles.')}
label={$i18n.t('settings.admin.authentication.roleMapping.label')}
description={$i18n.t('settings.admin.authentication.roleMapping.description')}
let:labelId
>
<Switch
@ -739,8 +749,8 @@
{#if oauthConfig.ENABLE_OAUTH_ROLE_MANAGEMENT}
<div class="grid grid-cols-1 gap-x-3 gap-y-2.5 sm:grid-cols-2">
<AdminSettingField
label={$i18n.t('Roles Claim')}
description={$i18n.t('Claim containing provider roles.')}
label={$i18n.t('settings.admin.authentication.rolesClaim.label')}
description={$i18n.t('settings.admin.authentication.rolesClaim.description')}
>
<input
class={inputClass}
@ -750,8 +760,8 @@
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Admin Roles')}
description={$i18n.t('Provider roles that grant admin access.')}
label={$i18n.t('settings.admin.authentication.adminRoles.label')}
description={$i18n.t('settings.admin.authentication.adminRoles.description')}
>
<input
class={inputClass}
@ -762,8 +772,8 @@
</div>
<AdminSettingField
label={$i18n.t('Allowed Roles')}
description={$i18n.t('Provider roles allowed to sign in.')}
label={$i18n.t('settings.admin.authentication.allowedRoles.label')}
description={$i18n.t('settings.admin.authentication.allowedRoles.description')}
>
<input
class={inputClass}
@ -777,8 +787,10 @@
Do not alter, remove, obscure, or replace it except as LICENSE permits:
https://docs.openwebui.com/license. -->
<AdminSettingRow
label={$i18n.t('Group Mapping')}
description={$i18n.t('Map OAuth claims to Open WebUI groups.')}
label={$i18n.t('settings.admin.authentication.enableOauthGroupManagement.label')}
description={$i18n.t(
'settings.admin.authentication.enableOauthGroupManagement.description'
)}
let:labelId
>
<Switch
@ -789,8 +801,10 @@
{#if oauthConfig.ENABLE_OAUTH_GROUP_MANAGEMENT}
<AdminSettingRow
label={$i18n.t('Auto-Create Groups')}
description={$i18n.t('Create missing groups from OAuth claims.')}
label={$i18n.t('settings.admin.authentication.enableOauthGroupCreation.label')}
description={$i18n.t(
'settings.admin.authentication.enableOauthGroupCreation.description'
)}
let:labelId
>
<Switch
@ -801,8 +815,8 @@
<div class="grid grid-cols-1 gap-x-3 gap-y-2.5 sm:grid-cols-2">
<AdminSettingField
label={$i18n.t('Group Claim')}
description={$i18n.t('Claim containing provider groups.')}
label={$i18n.t('settings.admin.authentication.groupClaim.label')}
description={$i18n.t('settings.admin.authentication.groupClaim.description')}
>
<input
class={inputClass}
@ -812,8 +826,8 @@
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Blocked Groups')}
description={$i18n.t('Provider groups blocked from signing in.')}
label={$i18n.t('settings.admin.authentication.blockedGroups.label')}
description={$i18n.t('settings.admin.authentication.blockedGroups.description')}
>
<input
class={inputClass}
@ -825,8 +839,8 @@
{/if}
<AdminSettingRow
label={$i18n.t('Update Email')}
description={$i18n.t('Refresh the account email from OAuth on sign-in.')}
label={$i18n.t('settings.admin.authentication.updateEmail.label')}
description={$i18n.t('settings.admin.authentication.updateEmail.description')}
let:labelId
>
<Switch
@ -836,8 +850,8 @@
</AdminSettingRow>
<AdminSettingRow
label={$i18n.t('Update Name')}
description={$i18n.t('Refresh the account name from OAuth on sign-in.')}
label={$i18n.t('settings.admin.authentication.updateName.label')}
description={$i18n.t('settings.admin.authentication.updateName.description')}
let:labelId
>
<Switch
@ -847,8 +861,8 @@
</AdminSettingRow>
<AdminSettingRow
label={$i18n.t('Update Picture')}
description={$i18n.t('Refresh the profile picture from OAuth on sign-in.')}
label={$i18n.t('settings.admin.authentication.updatePicture.label')}
description={$i18n.t('settings.admin.authentication.updatePicture.description')}
let:labelId
>
<Switch

View file

@ -44,15 +44,18 @@
}}
>
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">
{$i18n.t('Code Execution')}
{$i18n.t('settings.admin.codeExecution.title')}
</h2>
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5">
{#if config}
<AdminSettingSection title={$i18n.t('Code Execution')} first>
<AdminSettingSection
title={$i18n.t('settings.admin.codeExecution.sections.codeExecution.title')}
first
>
<AdminSettingRow
label={$i18n.t('Enable Code Execution')}
description={$i18n.t('Allow models to run generated code and return execution results.')}
label={$i18n.t('settings.admin.codeExecution.enableCodeExecution.label')}
description={$i18n.t('settings.admin.codeExecution.enableCodeExecution.description')}
let:labelId
>
<Switch bind:state={config.ENABLE_CODE_EXECUTION} ariaLabelledbyId={labelId} />
@ -60,12 +63,12 @@
{#if config.ENABLE_CODE_EXECUTION}
<AdminSettingRow
label={$i18n.t('Code Execution Engine')}
label={$i18n.t('settings.admin.codeExecution.codeExecutionEngine.label')}
description={config.CODE_EXECUTION_ENGINE === 'jupyter'
? $i18n.t(
'Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.'
'Warning: Jupyter execution enables arbitrary code execution, posing severe security risks\u2014proceed with extreme caution.'
)
: $i18n.t('Choose the runtime used for generated code blocks.')}
: $i18n.t('settings.admin.codeExecution.codeExecutionEngine.description')}
>
<SettingsSelect
bind:value={config.CODE_EXECUTION_ENGINE}
@ -81,8 +84,10 @@
{#if config.CODE_EXECUTION_ENGINE === 'jupyter'}
<AdminSettingField
label={$i18n.t('Jupyter URL')}
description={$i18n.t('Connect code execution to a Jupyter server endpoint.')}
label={$i18n.t('settings.admin.codeExecution.codeExecutionJupyterUrl.label')}
description={$i18n.t(
'settings.admin.codeExecution.codeExecutionJupyterUrl.description'
)}
>
<input
class={inputClass}
@ -97,8 +102,10 @@
Do not alter, remove, obscure, or replace it except as LICENSE permits:
https://docs.openwebui.com/license. -->
<AdminSettingRow
label={$i18n.t('Jupyter Auth')}
description={$i18n.t('Select how Open WebUI authenticates with the Jupyter server.')}
label={$i18n.t('settings.admin.codeExecution.codeExecutionJupyterAuth.label')}
description={$i18n.t(
'settings.admin.codeExecution.codeExecutionJupyterAuth.description'
)}
>
<SettingsSelect
bind:value={config.CODE_EXECUTION_JUPYTER_AUTH}
@ -113,9 +120,11 @@
{#if config.CODE_EXECUTION_JUPYTER_AUTH}
<AdminSettingField
label={config.CODE_EXECUTION_JUPYTER_AUTH === 'password'
? $i18n.t('Jupyter Password')
: $i18n.t('Jupyter Token')}
description={$i18n.t('Credentials used to authenticate with the Jupyter server.')}
? $i18n.t('settings.admin.codeExecution.codeExecutionJupyterAuthPassword.label')
: $i18n.t('settings.admin.codeExecution.codeExecutionJupyterAuthToken.label')}
description={$i18n.t(
'settings.admin.codeExecution.codeExecutionJupyterAuthPassword.description'
)}
>
{#if config.CODE_EXECUTION_JUPYTER_AUTH === 'password'}
<SensitiveInput
@ -138,8 +147,10 @@
{/if}
<AdminSettingField
label={$i18n.t('Code Execution Timeout')}
description={$i18n.t('Maximum runtime in seconds before execution is stopped.')}
label={$i18n.t('settings.admin.codeExecution.codeExecutionJupyterTimeout.label')}
description={$i18n.t(
'settings.admin.codeExecution.codeExecutionJupyterTimeout.description'
)}
>
<input
class={inputClass}
@ -153,10 +164,12 @@
{/if}
</AdminSettingSection>
<AdminSettingSection title={$i18n.t('Code Interpreter')}>
<AdminSettingSection
title={$i18n.t('settings.admin.codeExecution.sections.codeInterpreter.title')}
>
<AdminSettingRow
label={$i18n.t('Enable Code Interpreter')}
description={$i18n.t('Allow models to use the code interpreter tool during chats.')}
label={$i18n.t('settings.admin.codeExecution.enableCodeInterpreter.label')}
description={$i18n.t('settings.admin.codeExecution.enableCodeInterpreter.description')}
let:labelId
>
<Switch bind:state={config.ENABLE_CODE_INTERPRETER} ariaLabelledbyId={labelId} />
@ -164,12 +177,12 @@
{#if config.ENABLE_CODE_INTERPRETER}
<AdminSettingRow
label={$i18n.t('Code Interpreter Engine')}
label={$i18n.t('settings.admin.codeExecution.codeInterpreterEngine.label')}
description={config.CODE_INTERPRETER_ENGINE === 'jupyter'
? $i18n.t(
'Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.'
'Warning: Jupyter execution enables arbitrary code execution, posing severe security risks\u2014proceed with extreme caution.'
)
: $i18n.t('Choose the runtime used by the code interpreter tool.')}
: $i18n.t('settings.admin.codeExecution.codeInterpreterEngine.description')}
>
<SettingsSelect
bind:value={config.CODE_INTERPRETER_ENGINE}
@ -185,8 +198,10 @@
{#if config.CODE_INTERPRETER_ENGINE === 'jupyter'}
<AdminSettingField
label={$i18n.t('Jupyter URL')}
description={$i18n.t('Connect code interpreter to a Jupyter server endpoint.')}
label={$i18n.t('settings.admin.codeExecution.codeInterpreterJupyterUrl.label')}
description={$i18n.t(
'settings.admin.codeExecution.codeInterpreterJupyterUrl.description'
)}
>
<input
class={inputClass}
@ -201,8 +216,10 @@
Do not alter, remove, obscure, or replace it except as LICENSE permits:
https://docs.openwebui.com/license. -->
<AdminSettingRow
label={$i18n.t('Jupyter Auth')}
description={$i18n.t('Select how Open WebUI authenticates with the Jupyter server.')}
label={$i18n.t('settings.admin.codeExecution.codeInterpreterJupyterAuth.label')}
description={$i18n.t(
'settings.admin.codeExecution.codeInterpreterJupyterAuth.description'
)}
>
<SettingsSelect
bind:value={config.CODE_INTERPRETER_JUPYTER_AUTH}
@ -217,9 +234,11 @@
{#if config.CODE_INTERPRETER_JUPYTER_AUTH}
<AdminSettingField
label={config.CODE_INTERPRETER_JUPYTER_AUTH === 'password'
? $i18n.t('Jupyter Password')
: $i18n.t('Jupyter Token')}
description={$i18n.t('Credentials used to authenticate with the Jupyter server.')}
? $i18n.t('settings.admin.codeExecution.codeInterpreterJupyterAuthPassword.label')
: $i18n.t('settings.admin.codeExecution.codeInterpreterJupyterAuthToken.label')}
description={$i18n.t(
'settings.admin.codeExecution.codeInterpreterJupyterAuthPassword.description'
)}
>
{#if config.CODE_INTERPRETER_JUPYTER_AUTH === 'password'}
<SensitiveInput
@ -242,8 +261,10 @@
{/if}
<AdminSettingField
label={$i18n.t('Code Execution Timeout')}
description={$i18n.t('Maximum runtime in seconds before execution is stopped.')}
label={$i18n.t('settings.admin.codeExecution.codeInterpreterJupyterTimeout.label')}
description={$i18n.t(
'settings.admin.codeExecution.codeInterpreterJupyterTimeout.description'
)}
>
<input
class={inputClass}
@ -256,9 +277,9 @@
{/if}
<AdminSettingField
label={$i18n.t('Code Interpreter Prompt Template')}
label={$i18n.t('settings.admin.codeExecution.codeInterpreterPromptTemplate.label')}
description={$i18n.t(
'Leave empty to use the default prompt, or enter a custom prompt.'
'settings.admin.codeExecution.codeInterpreterPromptTemplate.description'
)}
>
<Textarea

View file

@ -233,12 +233,14 @@
/>
<form class="flex h-full flex-col justify-between text-sm" on:submit|preventDefault={submitHandler}>
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">{$i18n.t('Connections')}</h2>
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">
{$i18n.t('settings.admin.connections.title')}
</h2>
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5">
{#if ENABLE_OPENAI_API !== null && ENABLE_OLLAMA_API !== null && connectionsConfig !== null}
<AdminSettingSection first>
<AdminSettingRow label={$i18n.t('OpenAI API')} let:labelId>
<AdminSettingRow label={$i18n.t('settings.admin.connections.openaiApi.label')} let:labelId>
<Switch
bind:state={ENABLE_OPENAI_API}
on:change={async () => {
@ -252,10 +254,10 @@
<div>
<div class="mb-2 flex items-center justify-between gap-4">
<div class="text-xs text-gray-600 dark:text-gray-400">
{$i18n.t('Manage OpenAI API Connections')}
{$i18n.t('settings.admin.connections.manageOpenaiApiConnections.label')}
</div>
<Tooltip content={$i18n.t(`Add Connection`)}>
<Tooltip content={$i18n.t('settings.admin.connections.addConnection.label')}>
<button
class="flex size-6 items-center justify-center rounded-lg text-gray-400 transition-colors hover:bg-black/5 hover:text-gray-700 dark:text-gray-600 dark:hover:bg-white/5 dark:hover:text-gray-300"
on:click={() => {
@ -297,7 +299,7 @@
</div>
{/if}
<AdminSettingRow label={$i18n.t('Ollama API')} let:labelId>
<AdminSettingRow label={$i18n.t('settings.admin.connections.ollamaApi.label')} let:labelId>
<Switch
bind:state={ENABLE_OLLAMA_API}
on:change={async () => {
@ -311,10 +313,10 @@
<div>
<div class="mb-2 flex items-center justify-between gap-4">
<div class="text-xs text-gray-600 dark:text-gray-400">
{$i18n.t('Manage Ollama API Connections')}
{$i18n.t('settings.admin.connections.manageOllamaApiConnections.label')}
</div>
<Tooltip content={$i18n.t(`Add Connection`)}>
<Tooltip content={$i18n.t('settings.admin.connections.addConnection.label')}>
<button
class="flex size-6 items-center justify-center rounded-lg text-gray-400 transition-colors hover:bg-black/5 hover:text-gray-700 dark:text-gray-600 dark:hover:bg-white/5 dark:hover:text-gray-300"
on:click={() => {
@ -351,7 +353,7 @@
</div>
<div class="mt-1 text-[0.6875rem] text-gray-400 dark:text-gray-600">
{$i18n.t('Trouble accessing Ollama?')}
{$i18n.t('settings.admin.connections.manageOllamaApiConnections.description')}
<a
class="font-normal underline hover:text-gray-700 dark:hover:text-gray-300"
href="https://github.com/open-webui/open-webui#troubleshooting"
@ -364,12 +366,12 @@
{/if}
</AdminSettingSection>
<AdminSettingSection title={$i18n.t('User Connections')}>
<AdminSettingSection
title={$i18n.t('settings.admin.connections.sections.userConnections.title')}
>
<AdminSettingRow
label={$i18n.t('Direct Connections')}
description={$i18n.t(
'Direct Connections allow users to connect to their own OpenAI compatible API endpoints.'
)}
label={$i18n.t('settings.admin.connections.directConnections.label')}
description={$i18n.t('settings.admin.connections.directConnections.description')}
let:labelId
>
<Switch
@ -382,10 +384,8 @@
</AdminSettingRow>
<AdminSettingRow
label={$i18n.t('Direct Integrations')}
description={$i18n.t(
'Show the Integrations tab for users with permission to manage their own tool and terminal connections, including admins. Hiding it does not disable existing connections.'
)}
label={$i18n.t('settings.admin.connections.directIntegrations.label')}
description={$i18n.t('settings.admin.connections.directIntegrations.description')}
let:labelId
>
<Switch
@ -396,21 +396,19 @@
</AdminSettingRow>
<AdminSettingRow
label={$i18n.t('Cache Base Model List')}
description={$i18n.t(
'Base Model List Cache speeds up access by fetching base models only at startup or on settings save—faster, but may not show recent base model changes.'
)}
label={$i18n.t('settings.admin.connections.cacheBaseModelList.label')}
description={$i18n.t('settings.admin.connections.cacheBaseModelList.description')}
let:labelId
>
<div class="flex items-center gap-1.5">
{#if connectionsConfig.ENABLE_BASE_MODELS_CACHE}
<Tooltip content={$i18n.t('Refresh')}>
<Tooltip content={$i18n.t('settings.admin.connections.refreshModels.label')}>
<button
class="flex size-6 items-center justify-center rounded-lg text-gray-400 transition-colors hover:bg-black/5 hover:text-gray-700 disabled:cursor-not-allowed disabled:opacity-50 dark:text-gray-600 dark:hover:bg-white/5 dark:hover:text-gray-300"
type="button"
disabled={modelListRefreshing}
on:click={refreshModelListHandler}
aria-label={$i18n.t('Refresh')}
aria-label={$i18n.t('settings.admin.connections.refreshModels.label')}
>
{#if modelListRefreshing}
<Spinner className="size-3.5" />

View file

@ -52,7 +52,9 @@
</script>
<div class="flex flex-col h-full justify-between text-sm">
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">{$i18n.t('Database')}</h2>
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">
{$i18n.t('settings.admin.db.title')}
</h2>
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5">
<input
@ -82,10 +84,10 @@
}}
/>
<AdminSettingSection title={$i18n.t('Config')} first>
<AdminSettingSection title={$i18n.t('settings.admin.db.sections.config.title')} first>
<AdminSettingRow
label={$i18n.t('Import Config')}
description={$i18n.t('Import admin configuration from a JSON export file.')}
label={$i18n.t('settings.admin.db.importConfig.label')}
description={$i18n.t('settings.admin.db.importConfig.description')}
>
<button
class={actionButtonClass}
@ -99,8 +101,8 @@
</AdminSettingRow>
<AdminSettingRow
label={$i18n.t('Export Config')}
description={$i18n.t('Download the current admin configuration as JSON.')}
label={$i18n.t('settings.admin.db.exportConfig.label')}
description={$i18n.t('settings.admin.db.exportConfig.description')}
>
<button
class={actionButtonClass}
@ -119,10 +121,10 @@
</AdminSettingSection>
{#if $config?.features.enable_admin_export ?? true}
<AdminSettingSection title={$i18n.t('Export')}>
<AdminSettingSection title={$i18n.t('settings.admin.db.sections.export.title')}>
<AdminSettingRow
label={$i18n.t('Database')}
description={$i18n.t('Download the application database when supported.')}
label={$i18n.t('settings.admin.db.database.label')}
description={$i18n.t('settings.admin.db.database.description')}
>
<button
class={actionButtonClass}
@ -133,13 +135,13 @@
}}
type="button"
>
{$i18n.t('Database')}
{$i18n.t('settings.admin.db.database.label')}
</button>
</AdminSettingRow>
<AdminSettingRow
label={$i18n.t('All Chats')}
description={$i18n.t("Download every user's chat history as JSON.")}
label={$i18n.t('settings.admin.db.allChats.label')}
description={$i18n.t('settings.admin.db.allChats.description')}
>
<button class={actionButtonClass} on:click={exportAllUserChats} type="button">
{$i18n.t('Export')}
@ -147,8 +149,8 @@
</AdminSettingRow>
<AdminSettingRow
label={$i18n.t('Users')}
description={$i18n.t('Download all users as CSV.')}
label={$i18n.t('settings.admin.db.users.label')}
description={$i18n.t('settings.admin.db.users.description')}
>
<button class={actionButtonClass} on:click={exportUsers} type="button">
{$i18n.t('Export')}

View file

@ -421,7 +421,9 @@
submitHandler();
}}
>
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">{$i18n.t('Documents')}</h2>
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">
{$i18n.t('settings.admin.documents.title')}
</h2>
{#if $config?.features?.slim === true}
<p class="mb-4 text-xs text-gray-500">
{$i18n.t(
@ -432,10 +434,13 @@
{#if RAGConfig}
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5">
<AdminSettingSection title={$i18n.t('Content Extraction')} first>
<AdminSettingSection
title={$i18n.t('settings.admin.documents.sections.contentExtraction.title')}
first
>
<AdminSettingRow
label={$i18n.t('Content Extraction Engine')}
description={$i18n.t('Choose how uploaded documents are parsed before indexing.')}
label={$i18n.t('settings.admin.documents.contentExtractionEngine.label')}
description={$i18n.t('settings.admin.documents.contentExtractionEngine.description')}
>
<SettingsSelect bind:value={RAGConfig.CONTENT_EXTRACTION_ENGINE}>
<option value=""
@ -455,10 +460,8 @@
</AdminSettingRow>
<AdminSettingField
label={$i18n.t('Supported Media MIME Types')}
description={$i18n.t(
'Media upload MIME types the content extraction engine may process.'
)}
label={$i18n.t('settings.admin.documents.supportedMediaMimeTypes.label')}
description={$i18n.t('settings.admin.documents.supportedMediaMimeTypes.description')}
>
<input
class={inputClass}
@ -469,18 +472,16 @@
{#if RAGConfig.CONTENT_EXTRACTION_ENGINE === '' && $config?.features?.slim !== true}
<AdminSettingRow
label={$i18n.t('PDF Extract Images (OCR)')}
description={$i18n.t('Extract images from PDFs so OCR can process image-only pages.')}
label={$i18n.t('settings.admin.documents.pdfExtractImagesOcr.label')}
description={$i18n.t('settings.admin.documents.pdfExtractImagesOcr.description')}
let:labelId
>
<Switch bind:state={RAGConfig.PDF_EXTRACT_IMAGES} ariaLabelledbyId={labelId} />
</AdminSettingRow>
<AdminSettingRow
label={$i18n.t('PDF Loader Mode')}
description={$i18n.t(
'Page mode creates one document per page. Single mode keeps pages together for chunking across boundaries.'
)}
label={$i18n.t('settings.admin.documents.pdfLoaderMode.label')}
description={$i18n.t('settings.admin.documents.pdfLoaderMode.description')}
>
<SettingsSelect bind:value={RAGConfig.PDF_LOADER_MODE}>
<option value="page">{$i18n.t('Page')}</option>
@ -489,8 +490,8 @@
</AdminSettingRow>
{:else if RAGConfig.CONTENT_EXTRACTION_ENGINE === 'datalab_marker'}
<AdminSettingField
label={$i18n.t('API Base URL')}
description={$i18n.t('Datalab Marker service endpoint used for document parsing.')}
label={$i18n.t('settings.admin.documents.datalabMarkerApiBaseUrl.label')}
description={$i18n.t('settings.admin.documents.datalabMarkerApiBaseUrl.description')}
>
<input
class={inputClass}
@ -500,8 +501,8 @@
</AdminSettingField>
<AdminSettingField
label={$i18n.t('API Key')}
description={$i18n.t('API key used to authenticate with Datalab Marker.')}
label={$i18n.t('settings.admin.documents.datalabMarkerApiKey.label')}
description={$i18n.t('settings.admin.documents.datalabMarkerApiKey.description')}
>
<SensitiveInput
variant="settings"
@ -512,8 +513,8 @@
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Additional Config')}
description={$i18n.t('JSON options passed to Marker for advanced parsing behavior.')}
label={$i18n.t('settings.admin.documents.additionalConfig.label')}
description={$i18n.t('settings.admin.documents.additionalConfig.description')}
>
<Tooltip
content={$i18n.t(
@ -531,38 +532,36 @@
</AdminSettingField>
<AdminSettingRow
label={$i18n.t('Use LLM')}
description={$i18n.t(
'Use an LLM to improve tables, forms, math, and layout detection.'
)}
label={$i18n.t('settings.admin.documents.useLlm.label')}
description={$i18n.t('settings.admin.documents.useLlm.description')}
let:labelId
>
<Switch bind:state={RAGConfig.DATALAB_MARKER_USE_LLM} ariaLabelledbyId={labelId} />
</AdminSettingRow>
<AdminSettingRow
label={$i18n.t('Skip Cache')}
description={$i18n.t('Skip cached Marker results and rerun inference.')}
label={$i18n.t('settings.admin.documents.skipCache.label')}
description={$i18n.t('settings.admin.documents.skipCache.description')}
let:labelId
>
<Switch bind:state={RAGConfig.DATALAB_MARKER_SKIP_CACHE} ariaLabelledbyId={labelId} />
</AdminSettingRow>
<AdminSettingRow
label={$i18n.t('Force OCR')}
description={$i18n.t('Run OCR on all PDF pages, even pages with embedded text.')}
label={$i18n.t('settings.admin.documents.forceOcr.label')}
description={$i18n.t('settings.admin.documents.forceOcr.description')}
let:labelId
>
<Switch bind:state={RAGConfig.DATALAB_MARKER_FORCE_OCR} ariaLabelledbyId={labelId} />
</AdminSettingRow>
<AdminSettingRow
label={$i18n.t('Paginate')}
description={$i18n.t('Separate output by page with page markers.')}
label={$i18n.t('settings.admin.documents.paginate.label')}
description={$i18n.t('settings.admin.documents.paginate.description')}
let:labelId
>
<Switch bind:state={RAGConfig.DATALAB_MARKER_PAGINATE} ariaLabelledbyId={labelId} />
</AdminSettingRow>
<AdminSettingRow
label={$i18n.t('Strip Existing OCR')}
description={$i18n.t('Remove existing OCR text and rerun OCR when Force OCR is off.')}
label={$i18n.t('settings.admin.documents.stripExistingOcr.label')}
description={$i18n.t('settings.admin.documents.stripExistingOcr.description')}
let:labelId
>
<Switch
@ -571,8 +570,8 @@
/>
</AdminSettingRow>
<AdminSettingRow
label={$i18n.t('Disable Image Extraction')}
description={$i18n.t('Do not extract images from PDFs during Marker processing.')}
label={$i18n.t('settings.admin.documents.disableImageExtraction.label')}
description={$i18n.t('settings.admin.documents.disableImageExtraction.description')}
let:labelId
>
<Switch
@ -581,15 +580,15 @@
/>
</AdminSettingRow>
<AdminSettingRow
label={$i18n.t('Format Lines')}
description={$i18n.t('Format lines to detect inline math and styles.')}
label={$i18n.t('settings.admin.documents.formatLines.label')}
description={$i18n.t('settings.admin.documents.formatLines.description')}
let:labelId
>
<Switch bind:state={RAGConfig.DATALAB_MARKER_FORMAT_LINES} ariaLabelledbyId={labelId} />
</AdminSettingRow>
<AdminSettingRow
label={$i18n.t('Output Format')}
description={$i18n.t('Text output format returned by Marker.')}
label={$i18n.t('settings.admin.documents.outputFormat.label')}
description={$i18n.t('settings.admin.documents.outputFormat.description')}
>
<SettingsSelect bind:value={RAGConfig.DATALAB_MARKER_OUTPUT_FORMAT}>
<option value="markdown">{$i18n.t('Markdown')}</option>
@ -600,8 +599,8 @@
{:else if RAGConfig.CONTENT_EXTRACTION_ENGINE === 'external'}
<div class="grid grid-cols-1 gap-x-3 gap-y-2.5 sm:grid-cols-2">
<AdminSettingField
label={$i18n.t('Document Loader URL')}
description={$i18n.t('External service endpoint used to load document content.')}
label={$i18n.t('settings.admin.documents.documentLoaderUrl.label')}
description={$i18n.t('settings.admin.documents.documentLoaderUrl.description')}
>
<input
class={inputClass}
@ -610,8 +609,10 @@
/>
</AdminSettingField>
<AdminSettingField
label={$i18n.t('API Key')}
description={$i18n.t('API key sent to the external document loader.')}
label={$i18n.t('settings.admin.documents.externalDocumentLoaderApiKey.label')}
description={$i18n.t(
'settings.admin.documents.externalDocumentLoaderApiKey.description'
)}
>
<SensitiveInput
variant="settings"
@ -623,8 +624,8 @@
</div>
<AdminSettingField
label={$i18n.t('Headers')}
description={$i18n.t('Additional JSON headers sent to the external document loader.')}
label={$i18n.t('settings.admin.documents.headers.label')}
description={$i18n.t('settings.admin.documents.headers.description')}
>
<Tooltip
content={$i18n.t(
@ -644,7 +645,7 @@
on:click={() =>
(showExternalDocumentLoaderHeadersHint = !showExternalDocumentLoaderHeadersHint)}
>
{$i18n.t('Header variables')}
{$i18n.t('settings.admin.documents.headerVariables.label')}
</button>
{#if showExternalDocumentLoaderHeadersHint}
<div class="mt-1 text-[0.6875rem] leading-5 text-gray-500 dark:text-gray-400">
@ -667,8 +668,8 @@
{:else if RAGConfig.CONTENT_EXTRACTION_ENGINE === 'tika'}
<div class="grid grid-cols-1 gap-x-3 gap-y-2.5 sm:grid-cols-2">
<AdminSettingField
label={$i18n.t('Tika Server URL')}
description={$i18n.t('Tika server endpoint used for content extraction.')}
label={$i18n.t('settings.admin.documents.tikaServerUrl.label')}
description={$i18n.t('settings.admin.documents.tikaServerUrl.description')}
>
<input
class={inputClass}
@ -677,8 +678,8 @@
/>
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Tika Server Version')}
description={$i18n.t('Select the Tika server API version.')}
label={$i18n.t('settings.admin.documents.tikaServerVersion.label')}
description={$i18n.t('settings.admin.documents.tikaServerVersion.description')}
>
<SettingsSelect bind:value={RAGConfig.TIKA_SERVER_VERSION}>
<option value="3">{$i18n.t('Tika 3.x')}</option>
@ -689,8 +690,8 @@
{:else if RAGConfig.CONTENT_EXTRACTION_ENGINE === 'docling'}
<div class="grid grid-cols-1 gap-x-3 gap-y-2.5 sm:grid-cols-2">
<AdminSettingField
label={$i18n.t('Docling Server URL')}
description={$i18n.t('Docling service endpoint used for parsing.')}
label={$i18n.t('settings.admin.documents.doclingServerUrl.label')}
description={$i18n.t('settings.admin.documents.doclingServerUrl.description')}
>
<input
class={inputClass}
@ -699,8 +700,8 @@
/>
</AdminSettingField>
<AdminSettingField
label={$i18n.t('API Key')}
description={$i18n.t('API key sent to Docling.')}
label={$i18n.t('settings.admin.documents.doclingApiKey.label')}
description={$i18n.t('settings.admin.documents.doclingApiKey.description')}
>
<SensitiveInput
variant="settings"
@ -712,8 +713,8 @@
</div>
<AdminSettingField
label={$i18n.t('Parameters')}
description={$i18n.t('Additional Docling parameters in JSON format.')}
label={$i18n.t('settings.admin.documents.doclingParams.label')}
description={$i18n.t('settings.admin.documents.doclingParams.description')}
>
<Textarea
className={textareaClass}
@ -725,8 +726,8 @@
{:else if RAGConfig.CONTENT_EXTRACTION_ENGINE === 'document_intelligence'}
<div class="grid grid-cols-1 gap-x-3 gap-y-2.5 sm:grid-cols-2">
<AdminSettingField
label={$i18n.t('Endpoint')}
description={$i18n.t('Document Intelligence endpoint used for parsing.')}
label={$i18n.t('settings.admin.documents.endpoint.label')}
description={$i18n.t('settings.admin.documents.endpoint.description')}
>
<input
class={inputClass}
@ -735,8 +736,8 @@
/>
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Key')}
description={$i18n.t('Credential used for Document Intelligence.')}
label={$i18n.t('settings.admin.documents.documentIntelligenceKey.label')}
description={$i18n.t('settings.admin.documents.documentIntelligenceKey.description')}
>
<SensitiveInput
variant="settings"
@ -748,8 +749,8 @@
</div>
<AdminSettingField
label={$i18n.t('Document Intelligence Model')}
description={$i18n.t('Model name used by Document Intelligence.')}
label={$i18n.t('settings.admin.documents.documentIntelligenceModel.label')}
description={$i18n.t('settings.admin.documents.documentIntelligenceModel.description')}
>
<input
class={inputClass}
@ -760,8 +761,8 @@
{:else if RAGConfig.CONTENT_EXTRACTION_ENGINE === 'mistral_ocr'}
<div class="grid grid-cols-1 gap-x-3 gap-y-2.5 sm:grid-cols-2">
<AdminSettingField
label={$i18n.t('API Base URL')}
description={$i18n.t('Mistral OCR service endpoint.')}
label={$i18n.t('settings.admin.documents.mistralOcrApiBaseUrl.label')}
description={$i18n.t('settings.admin.documents.mistralOcrApiBaseUrl.description')}
>
<input
class={inputClass}
@ -770,8 +771,8 @@
/>
</AdminSettingField>
<AdminSettingField
label={$i18n.t('API Key')}
description={$i18n.t('API key sent to Mistral OCR.')}
label={$i18n.t('settings.admin.documents.mistralOcrApiKey.label')}
description={$i18n.t('settings.admin.documents.mistralOcrApiKey.description')}
>
<SensitiveInput
variant="settings"
@ -781,8 +782,8 @@
</AdminSettingField>
</div>
<AdminSettingRow
label={$i18n.t('Use Base64')}
description={$i18n.t('Send PDFs as base64 data URLs instead of uploading first.')}
label={$i18n.t('settings.admin.documents.useBase64.label')}
description={$i18n.t('settings.admin.documents.useBase64.description')}
let:labelId
>
<Switch bind:state={RAGConfig.MISTRAL_OCR_USE_BASE64} ariaLabelledbyId={labelId} />
@ -790,8 +791,8 @@
{:else if RAGConfig.CONTENT_EXTRACTION_ENGINE === 'paddleocr_vl'}
<div class="grid grid-cols-1 gap-x-3 gap-y-2.5 sm:grid-cols-2">
<AdminSettingField
label={$i18n.t('API Base URL')}
description={$i18n.t('PaddleOCR-vl service endpoint.')}
label={$i18n.t('settings.admin.documents.paddleocrVlBaseUrl.label')}
description={$i18n.t('settings.admin.documents.paddleocrVlBaseUrl.description')}
>
<input
class={inputClass}
@ -800,8 +801,8 @@
/>
</AdminSettingField>
<AdminSettingField
label={$i18n.t('API Token')}
description={$i18n.t('API token sent to PaddleOCR-vl.')}
label={$i18n.t('settings.admin.documents.apiToken.label')}
description={$i18n.t('settings.admin.documents.apiToken.description')}
>
<SensitiveInput
variant="settings"
@ -813,8 +814,8 @@
</div>
{:else if RAGConfig.CONTENT_EXTRACTION_ENGINE === 'mineru'}
<AdminSettingRow
label={$i18n.t('API Mode')}
description={$i18n.t('Choose the local or cloud MinerU API mode.')}
label={$i18n.t('settings.admin.documents.apiMode.label')}
description={$i18n.t('settings.admin.documents.apiMode.description')}
>
<SettingsSelect
bind:value={RAGConfig.MINERU_API_MODE}
@ -839,8 +840,8 @@
</AdminSettingRow>
<AdminSettingField
label={$i18n.t('API URL')}
description={$i18n.t('MinerU API endpoint for the selected mode.')}
label={$i18n.t('settings.admin.documents.apiUrl.label')}
description={$i18n.t('settings.admin.documents.apiUrl.description')}
>
<input
class={inputClass}
@ -852,8 +853,8 @@
</AdminSettingField>
<AdminSettingField
label={$i18n.t('API Key')}
description={$i18n.t('API key used for MinerU cloud mode.')}
label={$i18n.t('settings.admin.documents.mineruApiKey.label')}
description={$i18n.t('settings.admin.documents.mineruApiKey.description')}
>
<SensitiveInput
variant="settings"
@ -863,8 +864,8 @@
</AdminSettingField>
<AdminSettingRow
label={$i18n.t('API Timeout')}
description={$i18n.t('Maximum time in seconds to wait for MinerU API responses.')}
label={$i18n.t('settings.admin.documents.apiTimeout.label')}
description={$i18n.t('settings.admin.documents.apiTimeout.description')}
>
<input
class="w-16 rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 text-right text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
@ -876,8 +877,8 @@
</AdminSettingRow>
<AdminSettingField
label={$i18n.t('Parameters')}
description={$i18n.t('Advanced MinerU parsing parameters in JSON format.')}
label={$i18n.t('settings.admin.documents.mineruParams.label')}
description={$i18n.t('settings.admin.documents.mineruParams.description')}
>
<Textarea
className={textareaClass}
@ -888,8 +889,8 @@
</AdminSettingField>
<AdminSettingField
label={$i18n.t('File Extensions')}
description={$i18n.t('Comma-separated extensions MinerU should handle.')}
label={$i18n.t('settings.admin.documents.fileExtensions.label')}
description={$i18n.t('settings.admin.documents.fileExtensions.description')}
>
<input
class={inputClass}
@ -900,9 +901,9 @@
{/if}
<AdminSettingRow
label={$i18n.t('Bypass Embedding and Retrieval')}
label={$i18n.t('settings.admin.documents.bypassEmbeddingAndRetrieval.label')}
description={RAGConfig.BYPASS_EMBEDDING_AND_RETRIEVAL
? $i18n.t('Inject the entire content as context for comprehensive processing.')
? $i18n.t('settings.admin.documents.bypassEmbeddingAndRetrieval.description')
: $i18n.t('Use segmented retrieval for focused and relevant context.')}
let:labelId
>
@ -914,8 +915,8 @@
{#if !RAGConfig.BYPASS_EMBEDDING_AND_RETRIEVAL}
<AdminSettingRow
label={$i18n.t('Text Splitter')}
description={$i18n.t('Choose how extracted text is split before indexing.')}
label={$i18n.t('settings.admin.documents.textSplitter.label')}
description={$i18n.t('settings.admin.documents.textSplitter.description')}
>
<SettingsSelect bind:value={RAGConfig.TEXT_SPLITTER}>
<option value="">{$i18n.t('Default')} ({$i18n.t('Character')})</option>
@ -928,8 +929,8 @@
{#if RAGConfig.TEXT_SPLITTER === 'token_transformers'}
<AdminSettingField
label={$i18n.t('Tokenizer Model')}
description={$i18n.t('Tokenizer model used for transformer token splitting.')}
label={$i18n.t('settings.admin.documents.tokenizerModel.label')}
description={$i18n.t('settings.admin.documents.tokenizerModel.description')}
>
<input
class={inputClass}
@ -942,10 +943,8 @@
{/if}
<AdminSettingRow
label={$i18n.t('Markdown Header Text Splitter')}
description={$i18n.t(
'Split documents by markdown headers before character or token splitting.'
)}
label={$i18n.t('settings.admin.documents.markdownHeaderTextSplitter.label')}
description={$i18n.t('settings.admin.documents.markdownHeaderTextSplitter.description')}
let:labelId
>
<Switch
@ -956,8 +955,8 @@
<div class="grid grid-cols-1 gap-x-3 gap-y-2.5 sm:grid-cols-2">
<AdminSettingField
label={$i18n.t('Chunk Size')}
description={$i18n.t('Maximum size for each text chunk.')}
label={$i18n.t('settings.admin.documents.chunkSize.label')}
description={$i18n.t('settings.admin.documents.chunkSize.description')}
>
<input
class={inputClass}
@ -969,8 +968,8 @@
/>
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Chunk Overlap')}
description={$i18n.t('Overlap preserved between neighboring chunks.')}
label={$i18n.t('settings.admin.documents.chunkOverlap.label')}
description={$i18n.t('settings.admin.documents.chunkOverlap.description')}
>
<input
class={inputClass}
@ -985,10 +984,8 @@
{#if RAGConfig.ENABLE_MARKDOWN_HEADER_TEXT_SPLITTER}
<AdminSettingField
label={$i18n.t('Chunk Min Size Target')}
description={$i18n.t(
'Merge chunks smaller than this threshold when possible. Set to 0 to disable merging.'
)}
label={$i18n.t('settings.admin.documents.chunkMinSizeTarget.label')}
description={$i18n.t('settings.admin.documents.chunkMinSizeTarget.description')}
>
<input
class={inputClass}
@ -1004,10 +1001,10 @@
</AdminSettingSection>
{#if !RAGConfig.BYPASS_EMBEDDING_AND_RETRIEVAL}
<AdminSettingSection title={$i18n.t('Embedding')}>
<AdminSettingSection title={$i18n.t('settings.admin.documents.sections.embedding.title')}>
<AdminSettingRow
label={$i18n.t('Embedding Model Engine')}
description={$i18n.t('Provider used to generate document embeddings.')}
label={$i18n.t('settings.admin.documents.embeddingModelEngine.label')}
description={$i18n.t('settings.admin.documents.embeddingModelEngine.description')}
>
<SettingsSelect
bind:value={RAG_EMBEDDING_ENGINE}
@ -1036,23 +1033,23 @@
{#if RAG_EMBEDDING_ENGINE === 'openai'}
<div class="grid grid-cols-1 gap-x-3 gap-y-2.5 sm:grid-cols-2">
<AdminSettingField
label={$i18n.t('API Base URL')}
description={$i18n.t('OpenAI-compatible embeddings endpoint.')}
label={$i18n.t('settings.admin.documents.openaiurl.label')}
description={$i18n.t('settings.admin.documents.openaiurl.description')}
>
<input
class={inputClass}
placeholder={$i18n.t('API Base URL')}
placeholder={$i18n.t('settings.admin.documents.openaiurl.label')}
bind:value={OpenAIUrl}
required
/>
</AdminSettingField>
<AdminSettingField
label={$i18n.t('API Key')}
description={$i18n.t('API key for embedding requests.')}
label={$i18n.t('settings.admin.documents.openaikey.label')}
description={$i18n.t('settings.admin.documents.openaikey.description')}
>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('API Key')}
placeholder={$i18n.t('settings.admin.documents.openaikey.label')}
bind:value={OpenAIKey}
required={false}
/>
@ -1061,23 +1058,23 @@
{:else if RAG_EMBEDDING_ENGINE === 'ollama'}
<div class="grid grid-cols-1 gap-x-3 gap-y-2.5 sm:grid-cols-2">
<AdminSettingField
label={$i18n.t('API Base URL')}
description={$i18n.t('Ollama endpoint used for embeddings.')}
label={$i18n.t('settings.admin.documents.ollamaurl.label')}
description={$i18n.t('settings.admin.documents.ollamaurl.description')}
>
<input
class={inputClass}
placeholder={$i18n.t('API Base URL')}
placeholder={$i18n.t('settings.admin.documents.ollamaurl.label')}
bind:value={OllamaUrl}
required
/>
</AdminSettingField>
<AdminSettingField
label={$i18n.t('API Key')}
description={$i18n.t('Optional API key for Ollama requests.')}
label={$i18n.t('settings.admin.documents.ollamakey.label')}
description={$i18n.t('settings.admin.documents.ollamakey.description')}
>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('API Key')}
placeholder={$i18n.t('settings.admin.documents.ollamakey.label')}
bind:value={OllamaKey}
required={false}
/>
@ -1086,33 +1083,33 @@
{:else if RAG_EMBEDDING_ENGINE === 'azure_openai'}
<div class="grid grid-cols-1 gap-x-3 gap-y-2.5 sm:grid-cols-2">
<AdminSettingField
label={$i18n.t('API Base URL')}
description={$i18n.t('Azure OpenAI endpoint used for embeddings.')}
label={$i18n.t('settings.admin.documents.azureopenaiurl.label')}
description={$i18n.t('settings.admin.documents.azureopenaiurl.description')}
>
<input
class={inputClass}
placeholder={$i18n.t('API Base URL')}
placeholder={$i18n.t('settings.admin.documents.azureopenaiurl.label')}
bind:value={AzureOpenAIUrl}
required
/>
</AdminSettingField>
<AdminSettingField
label={$i18n.t('API Key')}
description={$i18n.t('Azure OpenAI API key.')}
label={$i18n.t('settings.admin.documents.azureopenaikey.label')}
description={$i18n.t('settings.admin.documents.azureopenaikey.description')}
>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('API Key')}
placeholder={$i18n.t('settings.admin.documents.azureopenaikey.label')}
bind:value={AzureOpenAIKey}
/>
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Version')}
description={$i18n.t('Azure OpenAI API version.')}
label={$i18n.t('settings.admin.documents.version.label')}
description={$i18n.t('settings.admin.documents.version.description')}
>
<input
class={inputClass}
placeholder={$i18n.t('Version')}
placeholder={$i18n.t('settings.admin.documents.version.label')}
bind:value={AzureOpenAIVersion}
required
/>
@ -1121,10 +1118,8 @@
{/if}
<AdminSettingField
label={$i18n.t('Embedding Model')}
description={$i18n.t(
'Model used to generate embeddings. Reindex knowledge after changing this.'
)}
label={$i18n.t('settings.admin.documents.embeddingModel.label')}
description={$i18n.t('settings.admin.documents.embeddingModel.description')}
>
<div class="flex w-full gap-2">
<input
@ -1146,7 +1141,7 @@
embeddingModelUpdateHandler();
}}
disabled={updateEmbeddingModelLoading}
aria-label={$i18n.t('Update embedding model')}
aria-label={$i18n.t('settings.admin.documents.updateEmbeddingModel.label')}
>
{#if updateEmbeddingModelLoading}
<Spinner />
@ -1176,8 +1171,8 @@
</AdminSettingField>
<AdminSettingRow
label={$i18n.t('Embedding Batch Size')}
description={$i18n.t('Number of items processed per embedding batch.')}
label={$i18n.t('settings.admin.documents.embeddingBatchSize.label')}
description={$i18n.t('settings.admin.documents.embeddingBatchSize.description')}
>
<input
bind:value={RAG_EMBEDDING_BATCH_SIZE}
@ -1191,16 +1186,16 @@
{#if RAG_EMBEDDING_ENGINE === 'ollama' || RAG_EMBEDDING_ENGINE === 'openai' || RAG_EMBEDDING_ENGINE === 'azure_openai'}
<AdminSettingRow
label={$i18n.t('Async Embedding Processing')}
description={$i18n.t('Run embedding tasks concurrently to speed up processing.')}
label={$i18n.t('settings.admin.documents.asyncEmbeddingProcessing.label')}
description={$i18n.t('settings.admin.documents.asyncEmbeddingProcessing.description')}
let:labelId
>
<Switch bind:state={ENABLE_ASYNC_EMBEDDING} ariaLabelledbyId={labelId} />
</AdminSettingRow>
<AdminSettingRow
label={$i18n.t('Embedding Concurrent Requests')}
label={$i18n.t('settings.admin.documents.embeddingConcurrentRequests.label')}
description={$i18n.t(
'Maximum concurrent embedding requests. Set to 0 for unlimited.'
'settings.admin.documents.embeddingConcurrentRequests.description'
)}
>
<input
@ -1215,12 +1210,12 @@
</AdminSettingSection>
{/if}
<AdminSettingSection title={$i18n.t('Retrieval')}>
<AdminSettingSection title={$i18n.t('settings.admin.documents.sections.retrieval.title')}>
{#if !RAGConfig.BYPASS_EMBEDDING_AND_RETRIEVAL}
<AdminSettingRow
label={$i18n.t('Full Context Mode')}
label={$i18n.t('settings.admin.documents.fullContextMode.label')}
description={RAGConfig.RAG_FULL_CONTEXT
? $i18n.t('Inject entire documents as context for comprehensive processing.')
? $i18n.t('settings.admin.documents.fullContextMode.description')
: $i18n.t('Use segmented retrieval for focused context.')}
let:labelId
>
@ -1229,8 +1224,8 @@
{#if !RAGConfig.RAG_FULL_CONTEXT}
<AdminSettingRow
label={$i18n.t('Hybrid Search')}
description={$i18n.t('Combine semantic and keyword retrieval.')}
label={$i18n.t('settings.admin.documents.hybridSearch.label')}
description={$i18n.t('settings.admin.documents.hybridSearch.description')}
let:labelId
>
<Switch bind:state={RAGConfig.ENABLE_RAG_HYBRID_SEARCH} ariaLabelledbyId={labelId} />
@ -1238,10 +1233,8 @@
{#if RAGConfig.ENABLE_RAG_HYBRID_SEARCH === true}
<AdminSettingRow
label={$i18n.t('Enrich Hybrid Search Text')}
description={$i18n.t(
'Add filenames, titles, sections, and snippets to improve lexical recall.'
)}
label={$i18n.t('settings.admin.documents.enrichHybridSearchText.label')}
description={$i18n.t('settings.admin.documents.enrichHybridSearchText.description')}
let:labelId
>
<Switch
@ -1251,8 +1244,8 @@
</AdminSettingRow>
<AdminSettingRow
label={$i18n.t('Reranking Engine')}
description={$i18n.t('Provider used to rerank hybrid search results.')}
label={$i18n.t('settings.admin.documents.rerankingEngine.label')}
description={$i18n.t('settings.admin.documents.rerankingEngine.description')}
>
<SettingsSelect
bind:value={RAGConfig.RAG_RERANKING_ENGINE}
@ -1275,23 +1268,29 @@
{#if RAGConfig.RAG_RERANKING_ENGINE === 'external'}
<div class="grid grid-cols-1 gap-x-3 gap-y-2.5 sm:grid-cols-2">
<AdminSettingField
label={$i18n.t('API Base URL')}
description={$i18n.t('External reranker endpoint.')}
label={$i18n.t('settings.admin.documents.ragExternalRerankerUrl.label')}
description={$i18n.t(
'settings.admin.documents.ragExternalRerankerUrl.description'
)}
>
<input
class={inputClass}
placeholder={$i18n.t('API Base URL')}
placeholder={$i18n.t('settings.admin.documents.ragExternalRerankerUrl.label')}
bind:value={RAGConfig.RAG_EXTERNAL_RERANKER_URL}
required
/>
</AdminSettingField>
<AdminSettingField
label={$i18n.t('API Key')}
description={$i18n.t('API key sent to the external reranker.')}
label={$i18n.t('settings.admin.documents.ragExternalRerankerApiKey.label')}
description={$i18n.t(
'settings.admin.documents.ragExternalRerankerApiKey.description'
)}
>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('API Key')}
placeholder={$i18n.t(
'settings.admin.documents.ragExternalRerankerApiKey.label'
)}
bind:value={RAGConfig.RAG_EXTERNAL_RERANKER_API_KEY}
required={false}
/>
@ -1300,8 +1299,8 @@
{/if}
<AdminSettingField
label={$i18n.t('Reranking Model')}
description={$i18n.t('Model used to rerank retrieved results.')}
label={$i18n.t('settings.admin.documents.rerankingModel.label')}
description={$i18n.t('settings.admin.documents.rerankingModel.description')}
>
<input
class={inputClass}
@ -1314,8 +1313,8 @@
{/if}
<AdminSettingRow
label={$i18n.t('Reranking Batch Size')}
description={$i18n.t('Number of results processed per reranking batch.')}
label={$i18n.t('settings.admin.documents.rerankingBatchSize.label')}
description={$i18n.t('settings.admin.documents.rerankingBatchSize.description')}
>
<input
bind:value={RAGConfig.RAG_RERANKING_BATCH_SIZE}
@ -1328,8 +1327,8 @@
</AdminSettingRow>
<AdminSettingField
label={$i18n.t('Top K')}
description={$i18n.t('Maximum number of retrieved chunks returned to the model.')}
label={$i18n.t('settings.admin.documents.topK.label')}
description={$i18n.t('settings.admin.documents.topK.description')}
>
<input
class={inputClass}
@ -1343,8 +1342,8 @@
{#if RAGConfig.ENABLE_RAG_HYBRID_SEARCH === true}
<AdminSettingField
label={$i18n.t('Top K Reranker')}
description={$i18n.t('Maximum number of hybrid results sent to the reranker.')}
label={$i18n.t('settings.admin.documents.topKReranker.label')}
description={$i18n.t('settings.admin.documents.topKReranker.description')}
>
<input
class={inputClass}
@ -1357,10 +1356,8 @@
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Relevance Threshold')}
description={$i18n.t(
'Only return documents with a score greater than or equal to this value.'
)}
label={$i18n.t('settings.admin.documents.relevanceThreshold.label')}
description={$i18n.t('settings.admin.documents.relevanceThreshold.description')}
>
<input
class={inputClass}
@ -1375,8 +1372,8 @@
</AdminSettingField>
<AdminSettingRow
label={$i18n.t('BM25 Weight')}
description={$i18n.t('Balance semantic and lexical weighting for hybrid search.')}
label={$i18n.t('settings.admin.documents.bm25Weight.label')}
description={$i18n.t('settings.admin.documents.bm25Weight.description')}
>
<button
class={actionButtonClass}
@ -1426,8 +1423,8 @@
{/if}
<AdminSettingField
label={$i18n.t('RAG Template')}
description={$i18n.t('Prompt template used when retrieved context is injected.')}
label={$i18n.t('settings.admin.documents.ragTemplate.label')}
description={$i18n.t('settings.admin.documents.ragTemplate.description')}
>
<Tooltip
content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
@ -1453,12 +1450,10 @@
</AdminSettingField>
</AdminSettingSection>
<AdminSettingSection title={$i18n.t('Files')}>
<AdminSettingSection title={$i18n.t('settings.admin.documents.sections.files.title')}>
<AdminSettingField
label={$i18n.t('Allowed File Extensions')}
description={$i18n.t(
'Comma-separated upload extensions. Leave empty for all file types.'
)}
label={$i18n.t('settings.admin.documents.allowedFileExtensions.label')}
description={$i18n.t('settings.admin.documents.allowedFileExtensions.description')}
>
<input
class={inputClass}
@ -1471,8 +1466,8 @@
<div class="grid grid-cols-1 gap-x-3 gap-y-2.5 sm:grid-cols-2">
<AdminSettingField
label={$i18n.t('Max Upload Size')}
description={$i18n.t('Maximum file size in MB. Leave empty for unlimited.')}
label={$i18n.t('settings.admin.documents.maxUploadSize.label')}
description={$i18n.t('settings.admin.documents.maxUploadSize.description')}
>
<input
class={inputClass}
@ -1484,8 +1479,8 @@
/>
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Max Upload Count')}
description={$i18n.t('Maximum number of files that can be used at once in chat.')}
label={$i18n.t('settings.admin.documents.maxUploadCount.label')}
description={$i18n.t('settings.admin.documents.maxUploadCount.description')}
>
<input
class={inputClass}
@ -1500,10 +1495,8 @@
<div class="grid grid-cols-1 gap-x-3 gap-y-2.5 sm:grid-cols-2">
<AdminSettingField
label={$i18n.t('Image Compression Width')}
description={$i18n.t(
'Width in pixels to compress images to. Leave empty for no compression.'
)}
label={$i18n.t('settings.admin.documents.imageCompressionWidth.label')}
description={$i18n.t('settings.admin.documents.imageCompressionWidth.description')}
>
<input
class={inputClass}
@ -1515,10 +1508,8 @@
/>
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Image Compression Height')}
description={$i18n.t(
'Height in pixels to compress images to. Leave empty for no compression.'
)}
label={$i18n.t('settings.admin.documents.imageCompressionHeight.label')}
description={$i18n.t('settings.admin.documents.imageCompressionHeight.description')}
>
<input
class={inputClass}
@ -1532,10 +1523,10 @@
</div>
</AdminSettingSection>
<AdminSettingSection title={$i18n.t('Integration')}>
<AdminSettingSection title={$i18n.t('settings.admin.documents.sections.integration.title')}>
<AdminSettingRow
label={$i18n.t('Google Drive')}
description={$i18n.t('Allow Google Drive as a document source.')}
label={$i18n.t('settings.admin.documents.googleDrive.label')}
description={$i18n.t('settings.admin.documents.googleDrive.description')}
let:labelId
>
<Switch
@ -1544,18 +1535,18 @@
/>
</AdminSettingRow>
<AdminSettingRow
label={$i18n.t('OneDrive')}
description={$i18n.t('Allow OneDrive as a document source.')}
label={$i18n.t('settings.admin.documents.onedrive.label')}
description={$i18n.t('settings.admin.documents.onedrive.description')}
let:labelId
>
<Switch bind:state={RAGConfig.ENABLE_ONEDRIVE_INTEGRATION} ariaLabelledbyId={labelId} />
</AdminSettingRow>
</AdminSettingSection>
<AdminSettingSection title={$i18n.t('Danger Zone')}>
<AdminSettingSection title={$i18n.t('settings.admin.documents.sections.dangerZone.title')}>
<AdminSettingRow
label={$i18n.t('Reset Upload Directory')}
description={$i18n.t('Delete uploaded files from the upload directory.')}
label={$i18n.t('settings.admin.documents.resetUploadDirectory.label')}
description={$i18n.t('settings.admin.documents.resetUploadDirectory.description')}
>
<button
class={actionButtonClass}
@ -1568,8 +1559,8 @@
</button>
</AdminSettingRow>
<AdminSettingRow
label={$i18n.t('Reset Vector Storage/Knowledge')}
description={$i18n.t('Clear vector storage and knowledge indexing data.')}
label={$i18n.t('settings.admin.documents.resetVectorStorageKnowledge.label')}
description={$i18n.t('settings.admin.documents.resetVectorStorageKnowledge.description')}
>
<button
class={actionButtonClass}
@ -1582,9 +1573,9 @@
</button>
</AdminSettingRow>
<AdminSettingRow
label={$i18n.t('Reindex Knowledge and Memory Vectors')}
label={$i18n.t('settings.admin.documents.reindexKnowledgeAndMemoryVectors.label')}
description={$i18n.t(
'Rebuild vectors for existing knowledge files, knowledge search, and memories.'
'settings.admin.documents.reindexKnowledgeAndMemoryVectors.description'
)}
>
<button
@ -1594,7 +1585,7 @@
showReindexConfirm = true;
}}
>
{$i18n.t('Reindex')}
{$i18n.t('settings.admin.documents.reindex.label')}
</button>
</AdminSettingRow>
</AdminSettingSection>

View file

@ -91,14 +91,16 @@
dispatch('save');
}}
>
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">{$i18n.t('Evaluations')}</h2>
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">
{$i18n.t('settings.admin.evaluations.title')}
</h2>
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5">
{#if evaluationConfig !== null}
<AdminSettingSection first>
<AdminSettingRow
label={$i18n.t('Arena Models')}
description={$i18n.t('Message rating should be enabled to use this feature')}
label={$i18n.t('settings.admin.evaluations.arenaModels.label')}
description={$i18n.t('settings.admin.evaluations.arenaModels.description')}
let:labelId
>
<Tooltip content={$i18n.t(`Message rating should be enabled to use this feature`)}>
@ -111,11 +113,13 @@
</AdminSettingSection>
{#if evaluationConfig.ENABLE_EVALUATION_ARENA_MODELS}
<AdminSettingSection title={$i18n.t('Models')}>
<AdminSettingSection title={$i18n.t('settings.admin.evaluations.sections.models.title')}>
<div class="mb-2 flex items-center justify-between">
<div class="text-xs text-gray-600 dark:text-gray-400">{$i18n.t('Arena Models')}</div>
<div class="text-xs text-gray-600 dark:text-gray-400">
{$i18n.t('settings.admin.evaluations.arenaModels.label')}
</div>
<Tooltip content={$i18n.t('Add Arena Model')}>
<Tooltip content={$i18n.t('settings.admin.evaluations.addArenaModel.label')}>
<button
class="flex size-6 items-center justify-center rounded-lg text-gray-400 transition-colors hover:bg-black/5 hover:text-gray-900 dark:text-gray-600 dark:hover:bg-white/5 dark:hover:text-white"
type="button"

View file

@ -565,7 +565,7 @@
<div class="mt-3">
<div class="flex justify-between items-center">
<label for="event-webhook-all-events" class={`text-xs text-gray-500`}
>{$i18n.t('Events')}</label
>{$i18n.t('settings.admin.general.events.events.label')}</label
>
<label class="flex items-center gap-1.5 text-xs text-gray-700 dark:text-gray-300">
<input
@ -690,16 +690,18 @@
/>
<div class="mt-5">
<div class="mb-2 text-xs text-gray-400 dark:text-gray-600">{$i18n.t('Events')}</div>
<div class="mb-2 text-xs text-gray-400 dark:text-gray-600">
{$i18n.t('settings.admin.general.events.events.label')}
</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="text-xs text-gray-600 dark:text-gray-400">
{$i18n.t('settings.admin.general.events.webhooks.label')}
</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.'
)}
{$i18n.t('settings.admin.general.events.webhooks.description')}
</div>
</div>
<Tooltip content={$i18n.t('Add webhook')}>

View file

@ -797,11 +797,11 @@
<div class="flex w-full flex-col justify-between text-xs">
<div class="mb-2 flex items-center justify-between">
<div class="flex items-center gap-2 leading-none text-gray-600 dark:text-gray-400">
<div>{$i18n.t('External Knowledge Sources')}</div>
<div>{$i18n.t('settings.admin.integrations.externalKnowledge.title')}</div>
<ExperimentalBadge />
</div>
<Tooltip content={$i18n.t('Add Connection')}>
<Tooltip content={$i18n.t('settings.admin.integrations.externalKnowledge.addConnection.label')}>
<button
class="flex size-6 items-center justify-center rounded-lg text-gray-400 transition-colors hover:bg-black/5 hover:text-gray-900 dark:text-gray-600 dark:hover:bg-white/5 dark:hover:text-white"
on:click={openCreateSource}

View file

@ -124,14 +124,18 @@
updateHandler();
}}
>
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">{$i18n.t('General')}</h2>
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">
{$i18n.t('settings.admin.general.title')}
</h2>
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5">
{#if adminConfig !== null}
<AdminSettingSection first>
<div class="flex items-start justify-between gap-4">
<div class="min-w-0 text-xs">
<div class="text-gray-600 dark:text-gray-400">{$i18n.t('Version')}</div>
<div class="text-gray-600 dark:text-gray-400">
{$i18n.t('settings.admin.general.version.label')}
</div>
<div class="mt-1 flex flex-wrap gap-x-1 text-gray-700 dark:text-gray-200">
<Tooltip content={WEBUI_BUILD_HASH}>v{WEBUI_VERSION}</Tooltip>
@ -163,7 +167,7 @@
showChangelog.set(true);
}}
>
{$i18n.t("See what's new")}
{$i18n.t('settings.admin.general.seeWhatSNew.label')}
</button>
</div>
@ -175,7 +179,7 @@
checkForVersionUpdates();
}}
>
{$i18n.t('Check for updates')}
{$i18n.t('settings.admin.general.checkForUpdates.label')}
</button>
{/if}
</div>
@ -183,12 +187,14 @@
<div class="text-xs">
<div class="flex items-start justify-between gap-4">
<div class="min-w-0">
<div class="text-gray-600 dark:text-gray-400">{$i18n.t('Help')}</div>
<div class="text-gray-600 dark:text-gray-400">
{$i18n.t('settings.admin.general.help.label')}
</div>
<div class="mt-0.5 text-gray-400 dark:text-gray-600">
<!-- LICENSE covers this Open WebUI wordmark.
Do not alter, remove, obscure, or replace it except as LICENSE permits:
https://docs.openwebui.com/license. -->
{$i18n.t('Discover how to use Open WebUI and seek support from the community.')}
{$i18n.t('settings.admin.general.help.description')}
</div>
</div>
@ -224,7 +230,9 @@
<!-- LICENSE covers this Open WebUI license attribution.
Do not alter, remove, obscure, or replace it except as LICENSE permits:
https://docs.openwebui.com/license. -->
<div class="text-gray-600 dark:text-gray-400">{$i18n.t('License')}</div>
<div class="text-gray-600 dark:text-gray-400">
{$i18n.t('settings.admin.general.license.label')}
</div>
{#if $config?.license_metadata}
<a
@ -263,10 +271,10 @@
</div>
</AdminSettingSection>
<AdminSettingSection title={$i18n.t('Features')}>
<AdminSettingSection title={$i18n.t('settings.admin.general.sections.features.title')}>
<AdminSettingRow
label={$i18n.t('Community Sharing')}
description={$i18n.t('Allow users to share chats with the Open WebUI community.')}
label={$i18n.t('settings.admin.general.communitySharing.label')}
description={$i18n.t('settings.admin.general.communitySharing.description')}
let:labelId
>
<!-- LICENSE covers this Open WebUI Community wordmark.
@ -275,15 +283,15 @@
<Switch bind:state={adminConfig.ENABLE_COMMUNITY_SHARING} ariaLabelledbyId={labelId} />
</AdminSettingRow>
<AdminSettingRow
label={$i18n.t('Message Rating')}
description={$i18n.t('Let users rate assistant responses.')}
label={$i18n.t('settings.admin.general.messageRating.label')}
description={$i18n.t('settings.admin.general.messageRating.description')}
let:labelId
>
<Switch bind:state={adminConfig.ENABLE_MESSAGE_RATING} ariaLabelledbyId={labelId} />
</AdminSettingRow>
<AdminSettingRow
label={$i18n.t('Folders')}
description={$i18n.t('Allow users to organize chats into folders.')}
label={$i18n.t('settings.admin.general.folders.label')}
description={$i18n.t('settings.admin.general.folders.description')}
let:labelId
>
<Switch bind:state={adminConfig.ENABLE_FOLDERS} ariaLabelledbyId={labelId} />
@ -291,8 +299,8 @@
{#if adminConfig.ENABLE_FOLDERS}
<AdminSettingField
label={$i18n.t('Folder Max File Count')}
description={$i18n.t('Maximum number of files allowed per folder.')}
label={$i18n.t('settings.admin.general.folderMaxFileCount.label')}
description={$i18n.t('settings.admin.general.folderMaxFileCount.description')}
>
<input
class={inputClass}
@ -305,16 +313,16 @@
{/if}
<AdminSettingRow
label={$i18n.t('Memories')}
description={$i18n.t('Allow users to save memories for more personalized responses.')}
label={$i18n.t('settings.admin.general.memories.label')}
description={$i18n.t('settings.admin.general.memories.description')}
let:labelId
>
<Switch bind:state={adminConfig.ENABLE_MEMORIES} ariaLabelledbyId={labelId} />
</AdminSettingRow>
{#if adminConfig.ENABLE_MEMORIES}
<AdminSettingRow
label={$i18n.t('Memory System Context')}
description={$i18n.t('Include saved memories in the system context.')}
label={$i18n.t('settings.admin.general.memorySystemContext.label')}
description={$i18n.t('settings.admin.general.memorySystemContext.description')}
labelClassName="text-gray-500 dark:text-gray-500"
let:labelId
>
@ -325,25 +333,23 @@
</AdminSettingRow>
{/if}
<AdminSettingRow
label={$i18n.t('Notes')}
description={$i18n.t('Allow users to create and manage notes.')}
label={$i18n.t('settings.admin.general.notes.label')}
description={$i18n.t('settings.admin.general.notes.description')}
let:labelId
>
<Switch bind:state={adminConfig.ENABLE_NOTES} ariaLabelledbyId={labelId} />
</AdminSettingRow>
<AdminSettingRow
label={$i18n.t('Channels')}
description={$i18n.t('Allow users to use channels for shared conversations.')}
label={$i18n.t('settings.admin.general.channels.label')}
description={$i18n.t('settings.admin.general.channels.description')}
let:labelId
>
<Switch bind:state={adminConfig.ENABLE_CHANNELS} ariaLabelledbyId={labelId} />
</AdminSettingRow>
{#if adminConfig.ENABLE_CHANNELS}
<AdminSettingRow
label={$i18n.t('Model Response Mode')}
description={$i18n.t(
'Choose where model responses to root-level channel mentions are posted.'
)}
label={$i18n.t('settings.admin.general.modelResponseMode.label')}
description={$i18n.t('settings.admin.general.modelResponseMode.description')}
labelClassName="text-gray-500 dark:text-gray-500"
let:labelId
>
@ -357,37 +363,37 @@
</AdminSettingRow>
{/if}
<AdminSettingRow
label={$i18n.t('Calendar')}
description={$i18n.t('Allow users to access calendar features.')}
label={$i18n.t('settings.admin.general.calendar.label')}
description={$i18n.t('settings.admin.general.calendar.description')}
let:labelId
>
<Switch bind:state={adminConfig.ENABLE_CALENDAR} ariaLabelledbyId={labelId} />
</AdminSettingRow>
<AdminSettingRow
label={$i18n.t('Automations')}
description={$i18n.t('Allow users to create and run automations.')}
label={$i18n.t('settings.admin.general.automations.label')}
description={$i18n.t('settings.admin.general.automations.description')}
let:labelId
>
<Switch bind:state={adminConfig.ENABLE_AUTOMATIONS} ariaLabelledbyId={labelId} />
</AdminSettingRow>
<AdminSettingRow
label={$i18n.t('User Webhooks')}
description={$i18n.t('Allow users to configure webhooks from their account.')}
label={$i18n.t('settings.admin.general.userWebhooks.label')}
description={$i18n.t('settings.admin.general.userWebhooks.description')}
let:labelId
>
<Switch bind:state={adminConfig.ENABLE_USER_WEBHOOKS} ariaLabelledbyId={labelId} />
</AdminSettingRow>
<AdminSettingRow
label={$i18n.t('User Status')}
description={$i18n.t('Show user status information in the app.')}
label={$i18n.t('settings.admin.general.userStatus.label')}
description={$i18n.t('settings.admin.general.userStatus.description')}
let:labelId
>
<Switch bind:state={adminConfig.ENABLE_USER_STATUS} ariaLabelledbyId={labelId} />
</AdminSettingRow>
<AdminSettingField
label={$i18n.t('Response Watermark')}
description={$i18n.t('Append a watermark to assistant responses when configured.')}
label={$i18n.t('settings.admin.general.responseWatermark.label')}
description={$i18n.t('settings.admin.general.responseWatermark.description')}
>
<Textarea
className={textareaClass}
@ -397,10 +403,8 @@
</AdminSettingField>
<AdminSettingField
label={$i18n.t('WebUI URL')}
description={$i18n.t(
'Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.'
)}
label={$i18n.t('settings.admin.general.webuiUrl.label')}
description={$i18n.t('settings.admin.general.webuiUrl.description')}
>
<input
class={inputClass}
@ -413,7 +417,7 @@
<Events />
<AdminSettingSection title={$i18n.t('UI')}>
<AdminSettingSection title={$i18n.t('settings.admin.general.sections.ui.title')}>
<fieldset id="ui-i18n-settings" disabled={saving} class="min-w-0">
<I18nSettings bind:entries={uiI18nEntries} />
</fieldset>
@ -478,7 +482,9 @@
<div>
<div class="mb-1 flex min-h-7 w-full items-center justify-between gap-2">
<div class="min-w-0 text-xs text-gray-600 dark:text-gray-400">{$i18n.t('Banners')}</div>
<div class="min-w-0 text-xs text-gray-600 dark:text-gray-400">
{$i18n.t('settings.admin.general.banners.label')}
</div>
<div class="flex shrink-0 items-center gap-1">
{#if banners.length > 0}
<LanguageModeSelect bind:value={bannerLocale} className="w-fit" />
@ -486,7 +492,7 @@
<button
class="flex size-6 items-center justify-center text-gray-400 dark:text-gray-600"
type="button"
aria-label={$i18n.t('Add banner')}
aria-label={$i18n.t('settings.admin.general.addBanner.label')}
on:click={() => {
if (banners.length === 0 || banners[banners.length - 1]?.content !== '') {
banners = [

View file

@ -27,17 +27,19 @@
<div class="space-y-1.5">
<div class="flex min-h-7 flex-wrap items-center justify-between gap-x-2 gap-y-1">
<div class="text-xs text-gray-600 dark:text-gray-400">{$i18n.t('UI Translations')}</div>
<div class="text-xs text-gray-600 dark:text-gray-400">
{$i18n.t('settings.admin.general.translations.uiTranslations.label')}
</div>
<div class="ms-auto flex shrink-0 items-center gap-1">
{#if entries.length}<LanguageModeSelect
bind:value={locale}
includeDefault={false}
className="w-fit"
/>{/if}
<Tooltip content={$i18n.t('Add translation')}>
<Tooltip content={$i18n.t('settings.admin.general.translations.addTranslation.label')}>
<button
type="button"
aria-label={$i18n.t('Add translation')}
aria-label={$i18n.t('settings.admin.general.translations.addTranslation.label')}
on:click={add}
class="flex size-6 items-center justify-center text-gray-400 dark:text-gray-600"
>

View file

@ -299,25 +299,27 @@
saveHandler();
}}
>
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">{$i18n.t('Images')}</h2>
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">
{$i18n.t('settings.admin.images.title')}
</h2>
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5">
{#if config}
<div class="flex flex-col">
<AdminSettingSection first>
<AdminSettingRow
label={$i18n.t('Image Generation')}
description={$i18n.t('Allow users to generate images from prompts.')}
label={$i18n.t('settings.admin.images.imageGeneration.label')}
description={$i18n.t('settings.admin.images.imageGeneration.description')}
let:labelId
>
<Switch bind:state={config.ENABLE_IMAGE_GENERATION} ariaLabelledbyId={labelId} />
</AdminSettingRow>
</AdminSettingSection>
<AdminSettingSection title={$i18n.t('Create Image')}>
<AdminSettingSection title={$i18n.t('settings.admin.images.sections.createImage.title')}>
<AdminSettingRow
label={$i18n.t('Image Generation Engine')}
description={$i18n.t('Choose the provider used for image generation.')}
label={$i18n.t('settings.admin.images.imageGenerationEngine.label')}
description={$i18n.t('settings.admin.images.imageGenerationEngine.description')}
>
<SettingsSelect
bind:value={config.IMAGE_GENERATION_ENGINE}
@ -332,7 +334,9 @@
{#if config.ENABLE_IMAGE_GENERATION}
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2">
<AdminSettingField label={$i18n.t('Model')}>
<AdminSettingField
label={$i18n.t('settings.admin.images.imageGenerationModel.label')}
>
<input
list="model-list"
class={inputClass}
@ -348,7 +352,7 @@
</datalist>
</AdminSettingField>
<AdminSettingField label={$i18n.t('Image Size')}>
<AdminSettingField label={$i18n.t('settings.admin.images.imageSize.label')}>
<input
class={inputClass}
placeholder={$i18n.t('Enter Image Size (e.g. 512x512)')}
@ -357,7 +361,7 @@
</AdminSettingField>
{#if ['comfyui', 'automatic1111', ''].includes(config?.IMAGE_GENERATION_ENGINE)}
<AdminSettingField label={$i18n.t('Steps')}>
<AdminSettingField label={$i18n.t('settings.admin.images.steps.label')}>
<input
class={inputClass}
placeholder={$i18n.t('Enter Number of Steps (e.g. 50)')}
@ -369,8 +373,8 @@
</div>
<AdminSettingRow
label={$i18n.t('Image Prompt Generation')}
description={$i18n.t('Generate an image prompt before sending the request.')}
label={$i18n.t('settings.admin.images.imagePromptGeneration.label')}
description={$i18n.t('settings.admin.images.imagePromptGeneration.description')}
let:labelId
>
<Switch
@ -382,37 +386,39 @@
{#if config?.IMAGE_GENERATION_ENGINE === 'openai'}
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2">
<AdminSettingField label={$i18n.t('API Base URL')}>
<AdminSettingField
label={$i18n.t('settings.admin.images.imagesOpenaiApiBaseUrl.label')}
>
<input
class={inputClass}
placeholder={$i18n.t('API Base URL')}
placeholder={$i18n.t('settings.admin.images.imagesOpenaiApiBaseUrl.label')}
bind:value={config.IMAGES_OPENAI_API_BASE_URL}
/>
</AdminSettingField>
<AdminSettingField label={$i18n.t('API Key')}>
<AdminSettingField label={$i18n.t('settings.admin.images.imagesOpenaiApiKey.label')}>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('API Key')}
placeholder={$i18n.t('settings.admin.images.imagesOpenaiApiKey.label')}
bind:value={config.IMAGES_OPENAI_API_KEY}
required={false}
/>
</AdminSettingField>
</div>
<AdminSettingField label={$i18n.t('API Version')}>
<AdminSettingField
label={$i18n.t('settings.admin.images.imagesOpenaiApiVersion.label')}
>
<input
class={inputClass}
placeholder={$i18n.t('API Version')}
placeholder={$i18n.t('settings.admin.images.imagesOpenaiApiVersion.label')}
bind:value={config.IMAGES_OPENAI_API_VERSION}
/>
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Additional Parameters')}
description={$i18n.t(
'Send extra JSON parameters with each image generation request.'
)}
label={$i18n.t('settings.admin.images.imagesOpenaiApiParams.label')}
description={$i18n.t('settings.admin.images.imagesOpenaiApiParams.description')}
>
<Textarea
className={textareaClass}
@ -423,10 +429,8 @@
</AdminSettingField>
{:else if (config?.IMAGE_GENERATION_ENGINE ?? 'automatic1111') === 'automatic1111'}
<AdminSettingField
label={$i18n.t('Base URL')}
description={$i18n.t(
'Connect to a stable-diffusion-webui server running with the `--api` flag.'
)}
label={$i18n.t('settings.admin.images.automatic1111BaseUrl.label')}
description={$i18n.t('settings.admin.images.automatic1111BaseUrl.description')}
>
<div class="flex w-full gap-2">
<input
@ -437,7 +441,7 @@
<button
class="shrink-0 text-gray-400 transition-colors hover:text-gray-900 dark:text-gray-600 dark:hover:text-white"
type="button"
aria-label={$i18n.t('verify connection')}
aria-label={$i18n.t('settings.admin.images.verifyConnection.label')}
on:click={async () => {
await updateConfigHandler();
const res = await verifyConfigUrl(localStorage.token).catch((error) => {
@ -446,7 +450,9 @@
});
if (res) {
toast.success($i18n.t('Server connection verified'));
toast.success(
$i18n.t('settings.admin.images.serverConnectionVerified.label')
);
}
}}
>
@ -467,8 +473,8 @@
</AdminSettingField>
<AdminSettingField
label={$i18n.t('API Auth String')}
description={$i18n.t('Provide the --api-auth username and password when required.')}
label={$i18n.t('settings.admin.images.apiAuthString.label')}
description={$i18n.t('settings.admin.images.apiAuthString.description')}
>
<SensitiveInput
variant="settings"
@ -479,8 +485,8 @@
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Additional Parameters')}
description={$i18n.t('Send extra JSON parameters with each AUTOMATIC1111 request.')}
label={$i18n.t('settings.admin.images.automatic1111Params.label')}
description={$i18n.t('settings.admin.images.automatic1111Params.description')}
>
<Textarea
className={textareaClass}
@ -491,8 +497,8 @@
</AdminSettingField>
{:else if config?.IMAGE_GENERATION_ENGINE === 'comfyui'}
<AdminSettingField
label={$i18n.t('Base URL')}
description={$i18n.t('Connect to the ComfyUI server used for generation.')}
label={$i18n.t('settings.admin.images.comfyuiBaseUrl.label')}
description={$i18n.t('settings.admin.images.comfyuiBaseUrl.description')}
>
<div class="flex w-full gap-2">
<input
@ -503,7 +509,7 @@
<button
class="shrink-0 text-gray-400 transition-colors hover:text-gray-900 dark:text-gray-600 dark:hover:text-white"
type="button"
aria-label={$i18n.t('verify connection')}
aria-label={$i18n.t('settings.admin.images.verifyConnection.label')}
on:click={async () => {
await updateConfigHandler();
const res = await verifyConfigUrl(localStorage.token).catch((error) => {
@ -512,7 +518,9 @@
});
if (res) {
toast.success($i18n.t('Server connection verified'));
toast.success(
$i18n.t('settings.admin.images.serverConnectionVerified.label')
);
}
}}
>
@ -533,8 +541,8 @@
</AdminSettingField>
<AdminSettingField
label={$i18n.t('API Key')}
description={$i18n.t('Use an API key when your ComfyUI server requires one.')}
label={$i18n.t('settings.admin.images.comfyuiApiKey.label')}
description={$i18n.t('settings.admin.images.comfyuiApiKey.description')}
>
<SensitiveInput
variant="settings"
@ -563,17 +571,15 @@
}}
/>
<AdminSettingRow
label={$i18n.t('ComfyUI Workflow')}
description={$i18n.t(
'Upload a workflow.json file exported as API format from ComfyUI.'
)}
label={$i18n.t('settings.admin.images.comfyuiComfyuiWorkflow.label')}
description={$i18n.t('settings.admin.images.comfyuiComfyuiWorkflow.description')}
>
<div class="flex items-center justify-end gap-2">
{#if config.COMFYUI_WORKFLOW}
<button
class="text-xs text-gray-500 transition-colors hover:text-gray-900 hover:underline dark:text-gray-500 dark:hover:text-white"
type="button"
aria-label={$i18n.t('Edit workflow.json content')}
aria-label={$i18n.t('settings.admin.images.editWorkflowJsonContent.label')}
on:click={() => {
// open code editor modal
showComfyUIWorkflowEditor = true;
@ -583,11 +589,17 @@
</button>
{/if}
<Tooltip content={$i18n.t('Click here to upload a workflow.json file.')}>
<Tooltip
content={$i18n.t(
'settings.admin.images.clickHereToUploadAWorkflowJsonFile.label'
)}
>
<button
class="text-xs text-gray-500 transition-colors hover:text-gray-900 hover:underline dark:text-gray-500 dark:hover:text-white"
type="button"
aria-label={$i18n.t('Click here to upload a workflow.json file.')}
aria-label={$i18n.t(
'settings.admin.images.clickHereToUploadAWorkflowJsonFile.label'
)}
on:click={() => {
document.getElementById('upload-comfyui-workflow-input')?.click();
}}
@ -623,8 +635,8 @@
{#if config.COMFYUI_WORKFLOW}
<AdminSettingField
label={$i18n.t('ComfyUI Workflow Nodes')}
description={$i18n.t('Map workflow node inputs used for image generation.')}
label={$i18n.t('settings.admin.images.generationWorkflowNodes.label')}
description={$i18n.t('settings.admin.images.generationWorkflowNodes.description')}
>
<div class="flex flex-col gap-1.5 text-xs">
{#each REQUIRED_WORKFLOW_NODES as node}
@ -673,31 +685,31 @@
{/if}
{:else if config?.IMAGE_GENERATION_ENGINE === 'gemini'}
<AdminSettingField
label={$i18n.t('Base URL')}
description={$i18n.t('Override the Gemini image generation endpoint.')}
label={$i18n.t('settings.admin.images.imagesGeminiApiBaseUrl.label')}
description={$i18n.t('settings.admin.images.imagesGeminiApiBaseUrl.description')}
>
<input
class={inputClass}
placeholder={$i18n.t('API Base URL')}
placeholder={$i18n.t('settings.admin.images.imagesOpenaiApiBaseUrl.label')}
bind:value={config.IMAGES_GEMINI_API_BASE_URL}
/>
</AdminSettingField>
<AdminSettingField
label={$i18n.t('API Key')}
description={$i18n.t('Use a Gemini API key for image generation.')}
label={$i18n.t('settings.admin.images.imagesGeminiApiKey.label')}
description={$i18n.t('settings.admin.images.imagesGeminiApiKey.description')}
>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('API Key')}
placeholder={$i18n.t('settings.admin.images.imagesGeminiApiKey.label')}
bind:value={config.IMAGES_GEMINI_API_KEY}
required={true}
/>
</AdminSettingField>
<AdminSettingRow
label={$i18n.t('Gemini Endpoint Method')}
description={$i18n.t('Select the Gemini endpoint method to call.')}
label={$i18n.t('settings.admin.images.geminiEndpointMethod.label')}
description={$i18n.t('settings.admin.images.geminiEndpointMethod.description')}
>
<SettingsSelect
bind:value={config.IMAGES_GEMINI_ENDPOINT_METHOD}
@ -710,18 +722,18 @@
{/if}
</AdminSettingSection>
<AdminSettingSection title={$i18n.t('Edit Image')}>
<AdminSettingSection title={$i18n.t('settings.admin.images.sections.editImage.title')}>
<AdminSettingRow
label={$i18n.t('Image Edit')}
description={$i18n.t('Allow users to edit existing images.')}
label={$i18n.t('settings.admin.images.imageEdit.label')}
description={$i18n.t('settings.admin.images.imageEdit.description')}
let:labelId
>
<Switch bind:state={config.ENABLE_IMAGE_EDIT} ariaLabelledbyId={labelId} />
</AdminSettingRow>
<AdminSettingRow
label={$i18n.t('Image Edit Engine')}
description={$i18n.t('Choose the provider used for image edits.')}
label={$i18n.t('settings.admin.images.imageEditEngine.label')}
description={$i18n.t('settings.admin.images.imageEditEngine.description')}
>
<SettingsSelect
bind:value={config.IMAGE_EDIT_ENGINE}
@ -735,7 +747,7 @@
{#if config?.ENABLE_IMAGE_GENERATION && config?.ENABLE_IMAGE_EDIT}
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2">
<AdminSettingField label={$i18n.t('Model')}>
<AdminSettingField label={$i18n.t('settings.admin.images.imageEditModel.label')}>
<input
list="model-list"
class={inputClass}
@ -750,7 +762,7 @@
</datalist>
</AdminSettingField>
<AdminSettingField label={$i18n.t('Image Size')}>
<AdminSettingField label={$i18n.t('settings.admin.images.imageEditSize.label')}>
<input
class={inputClass}
placeholder={$i18n.t('Enter Image Size (e.g. 512x512)')}
@ -762,35 +774,41 @@
{#if config?.IMAGE_EDIT_ENGINE === 'openai'}
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2">
<AdminSettingField label={$i18n.t('API Base URL')}>
<AdminSettingField
label={$i18n.t('settings.admin.images.imagesEditOpenaiApiBaseUrl.label')}
>
<input
class={inputClass}
placeholder={$i18n.t('API Base URL')}
placeholder={$i18n.t('settings.admin.images.imagesEditOpenaiApiBaseUrl.label')}
bind:value={config.IMAGES_EDIT_OPENAI_API_BASE_URL}
/>
</AdminSettingField>
<AdminSettingField label={$i18n.t('API Key')}>
<AdminSettingField
label={$i18n.t('settings.admin.images.imagesEditOpenaiApiKey.label')}
>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('API Key')}
placeholder={$i18n.t('settings.admin.images.imagesEditOpenaiApiKey.label')}
bind:value={config.IMAGES_EDIT_OPENAI_API_KEY}
required={false}
/>
</AdminSettingField>
</div>
<AdminSettingField label={$i18n.t('API Version')}>
<AdminSettingField
label={$i18n.t('settings.admin.images.imagesEditOpenaiApiVersion.label')}
>
<input
class={inputClass}
placeholder={$i18n.t('API Version')}
placeholder={$i18n.t('settings.admin.images.imagesEditOpenaiApiVersion.label')}
bind:value={config.IMAGES_EDIT_OPENAI_API_VERSION}
/>
</AdminSettingField>
{:else if config?.IMAGE_EDIT_ENGINE === 'comfyui'}
<AdminSettingField
label={$i18n.t('Base URL')}
description={$i18n.t('Connect to the ComfyUI server used for image edits.')}
label={$i18n.t('settings.admin.images.imagesEditComfyuiBaseUrl.label')}
description={$i18n.t('settings.admin.images.imagesEditComfyuiBaseUrl.description')}
>
<div class="flex w-full gap-2">
<input
@ -801,7 +819,7 @@
<button
class="shrink-0 text-gray-400 transition-colors hover:text-gray-900 dark:text-gray-600 dark:hover:text-white"
type="button"
aria-label={$i18n.t('verify connection')}
aria-label={$i18n.t('settings.admin.images.verifyConnection.label')}
on:click={async () => {
await updateConfigHandler();
const res = await verifyConfigUrl(localStorage.token).catch((error) => {
@ -810,7 +828,9 @@
});
if (res) {
toast.success($i18n.t('Server connection verified'));
toast.success(
$i18n.t('settings.admin.images.serverConnectionVerified.label')
);
}
}}
>
@ -831,8 +851,8 @@
</AdminSettingField>
<AdminSettingField
label={$i18n.t('API Key')}
description={$i18n.t('Use an API key when your ComfyUI server requires one.')}
label={$i18n.t('settings.admin.images.imagesEditComfyuiApiKey.label')}
description={$i18n.t('settings.admin.images.imagesEditComfyuiApiKey.description')}
>
<SensitiveInput
variant="settings"
@ -861,17 +881,15 @@
}}
/>
<AdminSettingRow
label={$i18n.t('ComfyUI Workflow')}
description={$i18n.t(
'Upload a workflow.json file exported as API format from ComfyUI.'
)}
label={$i18n.t('settings.admin.images.comfyuiComfyuiWorkflow.label')}
description={$i18n.t('settings.admin.images.comfyuiComfyuiWorkflow.description')}
>
<div class="flex items-center justify-end gap-2">
{#if config.IMAGES_EDIT_COMFYUI_WORKFLOW}
<button
class="text-xs text-gray-500 transition-colors hover:text-gray-900 hover:underline dark:text-gray-500 dark:hover:text-white"
type="button"
aria-label={$i18n.t('Edit workflow.json content')}
aria-label={$i18n.t('settings.admin.images.editWorkflowJsonContent.label')}
on:click={() => {
// open code editor modal
showComfyUIEditWorkflowEditor = true;
@ -881,11 +899,17 @@
</button>
{/if}
<Tooltip content={$i18n.t('Click here to upload a workflow.json file.')}>
<Tooltip
content={$i18n.t(
'settings.admin.images.clickHereToUploadAWorkflowJsonFile.label'
)}
>
<button
class="text-xs text-gray-500 transition-colors hover:text-gray-900 hover:underline dark:text-gray-500 dark:hover:text-white"
type="button"
aria-label={$i18n.t('Click here to upload a workflow.json file.')}
aria-label={$i18n.t(
'settings.admin.images.clickHereToUploadAWorkflowJsonFile.label'
)}
on:click={() => {
document.getElementById('upload-comfyui-edit-workflow-input')?.click();
}}
@ -911,8 +935,8 @@
{#if config.IMAGES_EDIT_COMFYUI_WORKFLOW}
<AdminSettingField
label={$i18n.t('ComfyUI Workflow Nodes')}
description={$i18n.t('Map workflow node inputs used for image edits.')}
label={$i18n.t('settings.admin.images.editingWorkflowNodes.label')}
description={$i18n.t('settings.admin.images.editingWorkflowNodes.description')}
>
<div class="flex flex-col gap-1.5 text-xs">
{#each REQUIRED_EDIT_WORKFLOW_NODES as node}
@ -961,18 +985,22 @@
{/if}
{:else if config?.IMAGE_EDIT_ENGINE === 'gemini'}
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2">
<AdminSettingField label={$i18n.t('Base URL')}>
<AdminSettingField
label={$i18n.t('settings.admin.images.imagesEditGeminiApiBaseUrl.label')}
>
<input
class={inputClass}
placeholder={$i18n.t('API Base URL')}
placeholder={$i18n.t('settings.admin.images.imagesOpenaiApiBaseUrl.label')}
bind:value={config.IMAGES_EDIT_GEMINI_API_BASE_URL}
/>
</AdminSettingField>
<AdminSettingField label={$i18n.t('API Key')}>
<AdminSettingField
label={$i18n.t('settings.admin.images.imagesEditGeminiApiKey.label')}
>
<SensitiveInput
variant="settings"
placeholder={$i18n.t('API Key')}
placeholder={$i18n.t('settings.admin.images.imagesEditGeminiApiKey.label')}
bind:value={config.IMAGES_EDIT_GEMINI_API_KEY}
required={true}
/>

View file

@ -164,18 +164,23 @@
updateHandler();
}}
>
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">{$i18n.t('Integrations')}</h2>
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">
{$i18n.t('settings.admin.integrations.title')}
</h2>
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5">
{#if servers !== null}
<AdminSettingSection title={$i18n.t('Tools')} first>
<AdminSettingSection
title={$i18n.t('settings.admin.integrations.sections.tools.title')}
first
>
<div>
<div class="mb-2 flex items-center justify-between">
<div class="text-xs text-gray-600 dark:text-gray-400">
{$i18n.t('External Tool Servers')}
{$i18n.t('settings.admin.integrations.externalToolServers.label')}
</div>
<Tooltip content={$i18n.t(`Add Connection`)}>
<Tooltip content={$i18n.t('settings.admin.integrations.addConnection.label')}>
<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={() => {
@ -215,12 +220,14 @@
</div>
</AdminSettingSection>
<AdminSettingSection title={$i18n.t('Terminal')}>
<AdminSettingSection title={$i18n.t('settings.admin.integrations.sections.terminal.title')}>
<div>
<div class="mb-2 flex items-center justify-between">
<div class="text-xs text-gray-600 dark:text-gray-400">{$i18n.t('Open Terminal')}</div>
<div class="text-xs text-gray-600 dark:text-gray-400">
{$i18n.t('settings.admin.integrations.openTerminal.label')}
</div>
<Tooltip content={$i18n.t('Add Connection')}>
<Tooltip content={$i18n.t('settings.admin.integrations.addConnection.label')}>
<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={() => {
@ -245,7 +252,9 @@
? 'opacity-50'
: ''}"
>
<Tooltip content={$i18n.t('Terminal')}>
<Tooltip
content={$i18n.t('settings.admin.integrations.sections.terminal.title')}
>
<Cloud className="size-4" strokeWidth="1.5" />
</Tooltip>
@ -311,7 +320,7 @@
</div>
</AdminSettingSection>
<AdminSettingSection title={$i18n.t('Knowledge')}>
<AdminSettingSection title={$i18n.t('settings.admin.integrations.sections.knowledge.title')}>
<ExternalKnowledge />
</AdminSettingSection>
{:else}

View file

@ -169,23 +169,23 @@
}}
>
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">
{$i18n.t('Interface')}
{$i18n.t('settings.admin.interface.title')}
</h2>
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5">
<AdminSettingSection title={$i18n.t('Tasks')} first>
<AdminSettingSection title={$i18n.t('settings.admin.interface.sections.tasks.title')} first>
<div>
<div class="mb-2">
<div class="text-xs text-gray-600 dark:text-gray-400">{$i18n.t('Task Model')}</div>
<div class="text-xs text-gray-600 dark:text-gray-400">
{$i18n.t('settings.admin.interface.taskModel.label')}
</div>
<div class="mt-1.5 text-[0.6875rem] text-gray-400 dark:text-gray-600">
{$i18n.t(
'Choose fallback models for background tasks. Current Model follows the active chat model.'
)}
{$i18n.t('settings.admin.interface.taskModel.description')}
</div>
</div>
<div class="grid w-full grid-cols-1 gap-2.5 sm:grid-cols-2">
<AdminSettingField label={$i18n.t('Local Task Model')}>
<AdminSettingField label={$i18n.t('settings.admin.interface.localTaskModel.label')}>
<SettingsSelect
bind:value={taskConfig.TASK_MODEL}
className="w-full"
@ -204,7 +204,7 @@
</SettingsSelect>
</AdminSettingField>
<AdminSettingField label={$i18n.t('External Task Model')}>
<AdminSettingField label={$i18n.t('settings.admin.interface.externalTaskModel.label')}>
<SettingsSelect
bind:value={taskConfig.TASK_MODEL_EXTERNAL}
className="w-full"
@ -235,7 +235,7 @@
}}
>
<span class="text-xs text-gray-600 dark:text-gray-400">
{$i18n.t('Task Model Parameters')}
{$i18n.t('settings.admin.interface.taskModelParameters.label')}
</span>
<span class="text-[0.6875rem] text-gray-400 dark:text-gray-600">
{showTaskParameters ? $i18n.t('Close') : $i18n.t('Configure')}
@ -255,24 +255,22 @@
</div>
</AdminSettingSection>
<AdminSettingSection title={$i18n.t('Chat')}>
<AdminSettingSection title={$i18n.t('settings.admin.interface.sections.chat.title')}>
<AdminSettingRow
label={$i18n.t('Tool Permissions')}
description={$i18n.t('Show Full access and Ask for approval in the chat input menu.')}
label={$i18n.t('settings.admin.interface.toolPermissions.label')}
description={$i18n.t('settings.admin.interface.toolPermissions.description')}
let:labelId
>
<div slot="label" class="flex items-center gap-2">
<span>{$i18n.t('Tool Permissions')}</span>
<span>{$i18n.t('settings.admin.interface.toolPermissions.label')}</span>
<ExperimentalBadge />
</div>
<Switch bind:state={chatConfig.ENABLE_TOOL_PERMISSIONS} ariaLabelledbyId={labelId} />
</AdminSettingRow>
<AdminSettingRow
label={$i18n.t('Context Compaction')}
description={$i18n.t(
'Summarize older chat history when the conversation context grows large.'
)}
label={$i18n.t('settings.admin.interface.contextCompaction.label')}
description={$i18n.t('settings.admin.interface.contextCompaction.description')}
let:labelId
>
<Switch bind:state={chatConfig.ENABLE_CONTEXT_COMPACTION} ariaLabelledbyId={labelId} />
@ -280,10 +278,8 @@
{#if chatConfig.ENABLE_CONTEXT_COMPACTION}
<AdminSettingField
label={$i18n.t('Context Compaction Model')}
description={$i18n.t(
'Choose a dedicated model for context compaction summaries. Current Model follows the active chat model.'
)}
label={$i18n.t('settings.admin.interface.contextCompactionModel.label')}
description={$i18n.t('settings.admin.interface.contextCompactionModel.description')}
>
<SettingsSelect
bind:value={chatConfig.CONTEXT_COMPACTION_MODEL}
@ -306,10 +302,8 @@
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Token Threshold')}
description={$i18n.t(
'Older messages are summarized when estimated context exceeds this token limit.'
)}
label={$i18n.t('settings.admin.interface.tokenThreshold.label')}
description={$i18n.t('settings.admin.interface.tokenThreshold.description')}
>
<input
type="number"
@ -321,10 +315,8 @@
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Token Cap')}
description={$i18n.t(
'Model-specific context compaction thresholds cannot exceed this token limit.'
)}
label={$i18n.t('settings.admin.interface.tokenCap.label')}
description={$i18n.t('settings.admin.interface.tokenCap.description')}
>
<input
type="number"
@ -336,10 +328,8 @@
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Retained Messages')}
description={$i18n.t(
'Percentage of recent messages to keep after older messages are summarized.'
)}
label={$i18n.t('settings.admin.interface.retainedMessages.label')}
description={$i18n.t('settings.admin.interface.retainedMessages.description')}
>
<input
type="number"
@ -352,10 +342,8 @@
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Context Compaction Prompt')}
description={$i18n.t(
'Controls how older messages are rewritten into a running summary.'
)}
label={$i18n.t('settings.admin.interface.contextCompactionPrompt.label')}
description={$i18n.t('settings.admin.interface.contextCompactionPrompt.description')}
>
<Textarea
className={textareaClass}
@ -365,7 +353,7 @@
)}
/>
<div class="mt-1 text-[0.6875rem] text-gray-400 dark:text-gray-600">
{$i18n.t('Available variables')}:
{$i18n.t('settings.admin.interface.availableVariables.label')}:
<code>{'{{PREVIOUS_SUMMARY}}'}</code>,
<code>{'{{COMPACTED_MESSAGES}}'}</code>,
<code>{'{{RECENT_MESSAGES}}'}</code>,
@ -376,10 +364,10 @@
{/if}
</AdminSettingSection>
<AdminSettingSection title={$i18n.t('Generation')}>
<AdminSettingSection title={$i18n.t('settings.admin.interface.sections.generation.title')}>
<AdminSettingRow
label={$i18n.t('Title Generation')}
description={$i18n.t('Allow automatic names for new chats.')}
label={$i18n.t('settings.admin.interface.titleGeneration.label')}
description={$i18n.t('settings.admin.interface.titleGeneration.description')}
let:labelId
>
<Switch bind:state={taskConfig.ENABLE_TITLE_GENERATION} ariaLabelledbyId={labelId} />
@ -387,8 +375,8 @@
{#if taskConfig.ENABLE_TITLE_GENERATION}
<AdminSettingField
label={$i18n.t('Title Generation Prompt')}
description={$i18n.t('Shapes the short label generated for each chat.')}
label={$i18n.t('settings.admin.interface.titleGenerationPrompt.label')}
description={$i18n.t('settings.admin.interface.titleGenerationPrompt.description')}
>
<Textarea
className={textareaClass}
@ -401,8 +389,8 @@
{/if}
<AdminSettingRow
label={$i18n.t('Voice Mode Prompt')}
description={$i18n.t('Apply voice-specific instructions while voice mode is active.')}
label={$i18n.t('settings.admin.interface.voiceModePrompt.label')}
description={$i18n.t('settings.admin.interface.voiceModePrompt.description')}
let:labelId
>
<Switch bind:state={taskConfig.ENABLE_VOICE_MODE_PROMPT} ariaLabelledbyId={labelId} />
@ -410,8 +398,8 @@
{#if taskConfig.ENABLE_VOICE_MODE_PROMPT}
<AdminSettingField
label={$i18n.t('Prompt Template')}
description={$i18n.t('Defines the behavior used for spoken conversations.')}
label={$i18n.t('settings.admin.interface.promptTemplate.label')}
description={$i18n.t('settings.admin.interface.promptTemplate.description')}
>
<Textarea
className={textareaClass}
@ -424,8 +412,8 @@
{/if}
<AdminSettingRow
label={$i18n.t('Follow Up Generation')}
description={$i18n.t('Show suggested next questions after assistant responses.')}
label={$i18n.t('settings.admin.interface.followUpGeneration.label')}
description={$i18n.t('settings.admin.interface.followUpGeneration.description')}
let:labelId
>
<Switch bind:state={taskConfig.ENABLE_FOLLOW_UP_GENERATION} ariaLabelledbyId={labelId} />
@ -433,8 +421,8 @@
{#if taskConfig.ENABLE_FOLLOW_UP_GENERATION}
<AdminSettingField
label={$i18n.t('Follow Up Generation Prompt')}
description={$i18n.t('Guides the suggestions shown after an assistant response.')}
label={$i18n.t('settings.admin.interface.followUpGenerationPrompt.label')}
description={$i18n.t('settings.admin.interface.followUpGenerationPrompt.description')}
>
<Textarea
className={textareaClass}
@ -447,8 +435,8 @@
{/if}
<AdminSettingRow
label={$i18n.t('Tags Generation')}
description={$i18n.t('Create chat tags from conversation content.')}
label={$i18n.t('settings.admin.interface.tagsGeneration.label')}
description={$i18n.t('settings.admin.interface.tagsGeneration.description')}
let:labelId
>
<Switch bind:state={taskConfig.ENABLE_TAGS_GENERATION} ariaLabelledbyId={labelId} />
@ -456,8 +444,8 @@
{#if taskConfig.ENABLE_TAGS_GENERATION}
<AdminSettingField
label={$i18n.t('Tags Generation Prompt')}
description={$i18n.t('Controls how chat tags are inferred.')}
label={$i18n.t('settings.admin.interface.tagsGenerationPrompt.label')}
description={$i18n.t('settings.admin.interface.tagsGenerationPrompt.description')}
>
<Textarea
className={textareaClass}
@ -470,8 +458,8 @@
{/if}
<AdminSettingRow
label={$i18n.t('Retrieval Query Generation')}
description={$i18n.t('Rewrite user requests for knowledge retrieval.')}
label={$i18n.t('settings.admin.interface.retrievalQueryGeneration.label')}
description={$i18n.t('settings.admin.interface.retrievalQueryGeneration.description')}
let:labelId
>
<Switch
@ -481,8 +469,8 @@
</AdminSettingRow>
<AdminSettingRow
label={$i18n.t('Web Search Query Generation')}
description={$i18n.t('Rewrite user requests into web-search queries.')}
label={$i18n.t('settings.admin.interface.webSearchQueryGeneration.label')}
description={$i18n.t('settings.admin.interface.webSearchQueryGeneration.description')}
let:labelId
>
<Switch
@ -492,8 +480,8 @@
</AdminSettingRow>
<AdminSettingField
label={$i18n.t('Query Generation Prompt')}
description={$i18n.t('Shared prompt for retrieval and web-search query rewriting.')}
label={$i18n.t('settings.admin.interface.queryGenerationPrompt.label')}
description={$i18n.t('settings.admin.interface.queryGenerationPrompt.description')}
>
<Textarea
className={textareaClass}
@ -503,8 +491,8 @@
</AdminSettingField>
<AdminSettingRow
label={$i18n.t('Autocomplete Generation')}
description={$i18n.t('Suggest completions while users type chat messages.')}
label={$i18n.t('settings.admin.interface.autocompleteGeneration.label')}
description={$i18n.t('settings.admin.interface.autocompleteGeneration.description')}
let:labelId
>
<Switch
@ -515,8 +503,10 @@
{#if taskConfig.ENABLE_AUTOCOMPLETE_GENERATION}
<AdminSettingField
label={$i18n.t('Autocomplete Generation Input Max Length')}
description={$i18n.t('Limit how much draft text is sent for suggestion generation.')}
label={$i18n.t('settings.admin.interface.autocompleteGenerationInputMaxLength.label')}
description={$i18n.t(
'settings.admin.interface.autocompleteGenerationInputMaxLength.description'
)}
>
<input
type="number"
@ -529,8 +519,10 @@
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Autocomplete Generation Prompt')}
description={$i18n.t('Guides inline completions while users type a message.')}
label={$i18n.t('settings.admin.interface.autocompleteGenerationPrompt.label')}
description={$i18n.t(
'settings.admin.interface.autocompleteGenerationPrompt.description'
)}
>
<Textarea
className={textareaClass}
@ -543,8 +535,8 @@
{/if}
<AdminSettingField
label={$i18n.t('Image Prompt Generation Prompt')}
description={$i18n.t('Rewrites user intent into an image-generation prompt.')}
label={$i18n.t('settings.admin.interface.imagePromptGenerationPrompt.label')}
description={$i18n.t('settings.admin.interface.imagePromptGenerationPrompt.description')}
>
<Textarea
className={textareaClass}
@ -554,8 +546,8 @@
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Tools Function Calling Prompt')}
description={$i18n.t('Guides how the assistant formats tool and function calls.')}
label={$i18n.t('settings.admin.interface.toolsFunctionCallingPrompt.label')}
description={$i18n.t('settings.admin.interface.toolsFunctionCallingPrompt.description')}
>
<Textarea
className={textareaClass}

View file

@ -726,7 +726,7 @@
<div class="flex h-full min-h-0 flex-col text-sm">
<div class="mb-2 flex items-center justify-between">
<h2 class="text-sm font-medium text-gray-900 dark:text-white">
{$i18n.t('Models')}
{$i18n.t('settings.admin.models.title')}
<span class="ml-2 font-normal text-gray-500 dark:text-gray-500">
{filteredModels.length}
</span>
@ -855,7 +855,9 @@
}}
>
<DocumentArrowUp className="size-3.5" />
<div class="flex items-center">{$i18n.t('Import')}</div>
<div class="flex items-center">
{$i18n.t('settings.admin.models.importModels.label')}
</div>
</button>
<button
@ -866,7 +868,9 @@
}}
>
<Download className="size-3.5" />
<div class="flex items-center">{$i18n.t('Export')}</div>
<div class="flex items-center">
{$i18n.t('settings.admin.models.exportModels.label')}
</div>
</button>
{/if}
@ -878,7 +882,9 @@
}}
>
<Wrench className="size-3.5" />
<div class="flex items-center">{$i18n.t('Manage')}</div>
<div class="flex items-center">
{$i18n.t('settings.admin.models.manageModels.label')}
</div>
</button>
<button
@ -889,7 +895,9 @@
}}
>
<GarbageBin className="size-3.5" />
<div class="flex items-center">{$i18n.t('Reset')}</div>
<div class="flex items-center">
{$i18n.t('settings.admin.models.resetModels.label')}
</div>
</button>
<hr class="mx-1 my-0.5 border-gray-100 dark:border-gray-800" />
@ -902,7 +910,9 @@
}}
>
<CheckCircle className="size-3.5" />
<div class="flex items-center">{$i18n.t('Enable All')}</div>
<div class="flex items-center">
{$i18n.t('settings.admin.models.enableAllModels.label')}
</div>
</button>
<button
@ -913,7 +923,9 @@
}}
>
<Minus className="size-3.5" />
<div class="flex items-center">{$i18n.t('Disable All')}</div>
<div class="flex items-center">
{$i18n.t('settings.admin.models.disableAllModels.label')}
</div>
</button>
<hr class="mx-1 my-0.5 border-gray-100 dark:border-gray-800" />
@ -926,7 +938,9 @@
}}
>
<Eye className="size-3.5" />
<div class="flex items-center">{$i18n.t('Show All')}</div>
<div class="flex items-center">
{$i18n.t('settings.admin.models.showAllModels.label')}
</div>
</button>
<button
@ -937,7 +951,9 @@
}}
>
<EyeSlash className="size-3.5" />
<div class="flex items-center">{$i18n.t('Hide All')}</div>
<div class="flex items-center">
{$i18n.t('settings.admin.models.hideAllModels.label')}
</div>
</button>
</DropdownMenu>
</div>

View file

@ -150,7 +150,7 @@
expanded = !expanded;
}}
>
{$i18n.t('Model Defaults')}
{$i18n.t('settings.admin.models.defaults.modelDefaults.label')}
</button>
<button
@ -178,7 +178,7 @@
}}
>
<span class="text-xs text-gray-600 dark:text-gray-400">
{$i18n.t('Model Capabilities')}
{$i18n.t('settings.admin.models.defaults.modelCapabilities.label')}
</span>
<span class="text-[0.6875rem] text-gray-400 dark:text-gray-600">
{showCapabilities ? $i18n.t('Close') : $i18n.t('Configure')}
@ -217,7 +217,7 @@
}}
>
<span class="text-xs text-gray-600 dark:text-gray-400">
{$i18n.t('Model Parameters')}
{$i18n.t('settings.admin.models.defaults.modelParameters.label')}
</span>
<span class="text-[0.6875rem] text-gray-400 dark:text-gray-600">
{showParameters ? $i18n.t('Close') : $i18n.t('Configure')}
@ -245,7 +245,7 @@
}}
>
<span class="text-xs text-gray-600 dark:text-gray-400">
{$i18n.t('Prompt Suggestions')}
{$i18n.t('settings.admin.models.defaults.promptSuggestions.label')}
</span>
<span class="text-[0.6875rem] text-gray-400 dark:text-gray-600">
{showPromptSuggestions ? $i18n.t('Close') : $i18n.t('Configure')}

View file

@ -254,7 +254,9 @@
updateHandler();
}}
>
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">{$i18n.t('Pipelines')}</h2>
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">
{$i18n.t('settings.admin.pipelines.title')}
</h2>
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5">
{#if PIPELINES_LIST !== null}
@ -267,10 +269,13 @@
hidden
/>
<AdminSettingSection title={$i18n.t('Source')} first>
<AdminSettingSection
title={$i18n.t('settings.admin.pipelines.sections.source.title')}
first
>
<AdminSettingField
label={$i18n.t('Pipeline URL')}
description={$i18n.t('Select the Pipelines server to manage.')}
label={$i18n.t('settings.admin.pipelines.pipelineUrl.label')}
description={$i18n.t('settings.admin.pipelines.pipelineUrl.description')}
>
<SettingsSelect
bind:value={selectedPipelinesUrlIdx}
@ -294,8 +299,8 @@
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Upload Pipeline')}
description={$i18n.t('Upload a local Python pipeline file to the selected server.')}
label={$i18n.t('settings.admin.pipelines.uploadPipeline.label')}
description={$i18n.t('settings.admin.pipelines.uploadPipeline.description')}
>
<div class="flex gap-2">
<button
@ -308,7 +313,7 @@
{#if pipelineFiles}
{$i18n.t('{{COUNT}} pipeline(s) selected', { COUNT: pipelineFiles.length })}
{:else}
{$i18n.t('Select a .py file')}
{$i18n.t('settings.admin.pipelines.selectAPyFile.label')}
{/if}
</button>
@ -326,7 +331,7 @@
</AdminSettingField>
<AdminSettingField
label={$i18n.t('GitHub URL')}
label={$i18n.t('settings.admin.pipelines.githubUrl.label')}
description={`${$i18n.t('Pipelines are a plugin system with arbitrary code execution.')} ${$i18n.t("Don't fetch random pipelines from sources you don't trust.")}`}
>
<div class="flex gap-2">
@ -343,7 +348,9 @@
disabled={downloading}
type="button"
>
{downloading ? $i18n.t('Installing') : $i18n.t('Install')}
{downloading
? $i18n.t('settings.admin.pipelines.installing.label')
: $i18n.t('settings.admin.pipelines.install.label')}
</button>
</div>
</AdminSettingField>
@ -351,10 +358,12 @@
{#if pipelines !== null}
{#if pipelines.length > 0}
<AdminSettingSection title={$i18n.t('Pipelines')}>
<AdminSettingSection
title={$i18n.t('settings.admin.pipelines.sections.pipelines.title')}
>
<AdminSettingField
label={$i18n.t('Pipeline')}
description={$i18n.t('Select an installed pipeline to configure or remove.')}
label={$i18n.t('settings.admin.pipelines.pipeline.label')}
description={$i18n.t('settings.admin.pipelines.pipeline.description')}
>
<div class="flex gap-2">
<SettingsSelect
@ -386,7 +395,7 @@
</AdminSettingField>
</AdminSettingSection>
<AdminSettingSection title={$i18n.t('Valves')}>
<AdminSettingSection title={$i18n.t('settings.admin.pipelines.sections.valves.title')}>
{#if pipelines[selectedPipelineIdx ?? 0]?.valves}
{#if valves && valves_spec}
{#each Object.keys(valves_spec.properties) as property}
@ -450,7 +459,9 @@
{/if}
</AdminSettingSection>
{:else}
<AdminSettingSection title={$i18n.t('Pipelines')}>
<AdminSettingSection
title={$i18n.t('settings.admin.pipelines.sections.pipelines.title')}
>
<div class={mutedMessageClass}>{$i18n.t('Pipelines Not Detected')}</div>
</AdminSettingSection>
{/if}
@ -460,7 +471,10 @@
</div>
{/if}
{:else}
<AdminSettingSection title={$i18n.t('Source')} first>
<AdminSettingSection
title={$i18n.t('settings.admin.pipelines.sections.source.title')}
first
>
<div class={mutedMessageClass}>{$i18n.t('Pipelines Not Detected')}</div>
</AdminSettingSection>
{/if}

View file

@ -57,7 +57,9 @@
</script>
<form class="flex h-full flex-col justify-between text-sm" on:submit|preventDefault={save}>
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">{$i18n.t('Sub-agents')}</h2>
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">
{$i18n.t('settings.admin.subagents.title')}
</h2>
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5">
{#if loading}
@ -66,7 +68,7 @@
<div class="flex flex-col gap-2.5">
<label class="flex cursor-pointer items-center justify-between">
<span class="text-xs text-gray-600 dark:text-gray-400">
{$i18n.t('Enable sub-agents')}
{$i18n.t('settings.admin.subagents.enableSubAgents.label')}
</span>
<Switch bind:state={enabled} />
</label>
@ -79,7 +81,7 @@
{#if enabled}
<div>
<label class="text-xs text-gray-600 dark:text-gray-400" for="sa-concurrent">
{$i18n.t('Max concurrent')}
{$i18n.t('settings.admin.subagents.maxConcurrent.label')}
</label>
<div class="mt-1 flex items-center gap-1.5">
<input
@ -98,21 +100,19 @@
<div>
<label class="flex cursor-pointer items-center justify-between">
<span class="text-xs text-gray-600 dark:text-gray-400">
{$i18n.t('Enable background sub-agents')}
{$i18n.t('settings.admin.subagents.enableBackgroundSubAgents.label')}
</span>
<Switch bind:state={backgroundEnabled} />
</label>
<p class="mt-1 text-[0.6875rem] text-gray-400 dark:text-gray-600">
{$i18n.t(
'Allow delegated sub-agents to keep running while the parent chat continues.'
)}
{$i18n.t('settings.admin.subagents.enableBackgroundSubAgents.description')}
</p>
</div>
{#if backgroundEnabled}
<div>
<label class="text-xs text-gray-600 dark:text-gray-400" for="sa-async">
{$i18n.t('Max background')}
{$i18n.t('settings.admin.subagents.maxBackground.label')}
</label>
<div class="mt-1 flex items-center gap-1.5">
<input
@ -131,7 +131,7 @@
<div>
<label class="text-xs text-gray-600 dark:text-gray-400" for="sa-iterations">
{$i18n.t('Max iterations')}
{$i18n.t('settings.admin.subagents.maxIterations.label')}
</label>
<div class="mt-1 flex items-center gap-1.5">
<input
@ -150,7 +150,7 @@
<div>
<label class="text-xs text-gray-600 dark:text-gray-400" for="sa-output">
{$i18n.t('Max output')}
{$i18n.t('settings.admin.subagents.maxOutput.label')}
</label>
<div class="mt-1 flex items-center gap-1.5">
<input
@ -168,7 +168,7 @@
<div>
<label class="text-xs text-gray-600 dark:text-gray-400" for="sa-prompt">
{$i18n.t('System prompt')}
{$i18n.t('settings.admin.subagents.systemPrompt.label')}
</label>
<textarea
id="sa-prompt"
@ -178,7 +178,7 @@
class="mt-1 w-full resize-y rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 font-mono text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
></textarea>
<p class="mt-0.5 text-[0.6875rem] text-gray-400 dark:text-gray-600">
{$i18n.t('Leave empty for the built-in default.')}
{$i18n.t('settings.admin.subagents.systemPrompt.description')}
</p>
</div>
{/if}

View file

@ -163,22 +163,24 @@
saveHandler();
}}
>
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">{$i18n.t('Web Search')}</h2>
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">
{$i18n.t('settings.admin.web.title')}
</h2>
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5">
{#if webConfig}
<AdminSettingSection first title={$i18n.t('Search')}>
<AdminSettingSection first title={$i18n.t('settings.admin.web.sections.search.title')}>
<AdminSettingRow
label={$i18n.t('Web Search')}
description={$i18n.t('Allow users to search the web from chats.')}
label={$i18n.t('settings.admin.web.webSearch.label')}
description={$i18n.t('settings.admin.web.webSearch.description')}
let:labelId
>
<Switch bind:state={webConfig.ENABLE_WEB_SEARCH} ariaLabelledbyId={labelId} />
</AdminSettingRow>
<AdminSettingRow
label={$i18n.t('Web Search Confirmation')}
description={$i18n.t('Require users to confirm before using Web Search.')}
label={$i18n.t('settings.admin.web.webSearchConfirmation.label')}
description={$i18n.t('settings.admin.web.webSearchConfirmation.description')}
let:labelId
>
<Switch
@ -189,8 +191,8 @@
{#if webConfig.ENABLE_WEB_SEARCH_CONFIRMATION}
<AdminSettingField
label={$i18n.t('Web Search Confirmation Content')}
description={$i18n.t('Message shown before web search runs.')}
label={$i18n.t('settings.admin.web.webSearchConfirmationContent.label')}
description={$i18n.t('settings.admin.web.webSearchConfirmationContent.description')}
>
<Textarea
className={textareaClass}
@ -203,8 +205,8 @@
{/if}
<AdminSettingRow
label={$i18n.t('Web Search Engine')}
description={$i18n.t('Choose the provider used for web search queries.')}
label={$i18n.t('settings.admin.web.webSearchEngine.label')}
description={$i18n.t('settings.admin.web.webSearchEngine.description')}
>
<SettingsSelect
bind:value={webConfig.WEB_SEARCH_ENGINE}
@ -229,7 +231,7 @@
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Ollama Cloud API Key')}
{$i18n.t('settings.admin.web.ollamaCloudApiKey.label')}
</div>
<div class="flex w-full">
@ -247,7 +249,7 @@
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Perplexity Search API URL')}
{$i18n.t('settings.admin.web.perplexitySearchApiUrl.label')}
</div>
<div class="flex w-full">
@ -267,7 +269,7 @@
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Perplexity API Key')}
{$i18n.t('settings.admin.web.perplexityApiKey.label')}
</div>
<div class="flex w-full">
@ -285,7 +287,7 @@
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-left text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Searxng Query URL')}
{$i18n.t('settings.admin.web.searxngQueryUrl.label')}
</div>
<div class="flex w-full">
@ -303,7 +305,7 @@
</div>
<div class="mb-2.5 flex w-full flex-col">
<div class=" self-left text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Searxng search language (all, en, es, de, fr, etc.)')}
{$i18n.t('settings.admin.web.searxngSearchLanguageAllEnEsDeFrEtc.label')}
</div>
<div class="flex w-full">
@ -324,7 +326,7 @@
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Yacy Instance URL')}
{$i18n.t('settings.admin.web.yacyInstanceUrl.label')}
</div>
<div class="flex w-full">
@ -344,7 +346,7 @@
<div class="flex gap-2">
<div class="w-full">
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Yacy Username')}
{$i18n.t('settings.admin.web.yacyUsername.label')}
</div>
<input
@ -357,7 +359,7 @@
<div class="w-full">
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Yacy Password')}
{$i18n.t('settings.admin.web.yacyPassword.label')}
</div>
<SensitiveInput
@ -372,7 +374,7 @@
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Google PSE API Key')}
{$i18n.t('settings.admin.web.googlePseApiKey.label')}
</div>
<SensitiveInput
@ -383,7 +385,7 @@
</div>
<div class="mt-1.5">
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Google PSE Engine Id')}
{$i18n.t('settings.admin.web.googlePseEngineId.label')}
</div>
<div class="flex w-full">
@ -403,7 +405,7 @@
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Brave Search API Key')}
{$i18n.t('settings.admin.web.braveSearchApiKey.label')}
</div>
<SensitiveInput
@ -417,7 +419,7 @@
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Brave Search API Key')}
{$i18n.t('settings.admin.web.braveSearchApiKey.label')}
</div>
<SensitiveInput
@ -428,7 +430,7 @@
</div>
<div class="mt-1.5">
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Context Tokens')}
{$i18n.t('settings.admin.web.contextTokens.label')}
</div>
<div class="flex w-full">
@ -451,7 +453,7 @@
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Kagi Search API Key')}
{$i18n.t('settings.admin.web.kagiSearchApiKey.label')}
</div>
<SensitiveInput
@ -465,7 +467,7 @@
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Mojeek Search API Key')}
{$i18n.t('settings.admin.web.mojeekSearchApiKey.label')}
</div>
<SensitiveInput
@ -479,7 +481,7 @@
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Bocha Search API Key')}
{$i18n.t('settings.admin.web.bochaSearchApiKey.label')}
</div>
<SensitiveInput
@ -493,7 +495,7 @@
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Serpstack API Key')}
{$i18n.t('settings.admin.web.serpstackApiKey.label')}
</div>
<SensitiveInput
@ -507,7 +509,7 @@
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Serper API Key')}
{$i18n.t('settings.admin.web.serperApiKey.label')}
</div>
<SensitiveInput
@ -521,7 +523,7 @@
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('SERPHouse API Key')}
{$i18n.t('settings.admin.web.serphouseApiKey.label')}
</div>
<SensitiveInput
@ -532,7 +534,7 @@
</div>
<div class="mt-1.5">
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('SERPHouse Domain')}
{$i18n.t('settings.admin.web.serphouseDomain.label')}
</div>
<div class="flex w-full">
@ -552,7 +554,7 @@
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Serply API Key')}
{$i18n.t('settings.admin.web.serplyApiKey.label')}
</div>
<SensitiveInput
@ -566,7 +568,7 @@
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Tavily API Key')}
{$i18n.t('settings.admin.web.tavilyApiKey.label')}
</div>
<SensitiveInput
@ -580,7 +582,7 @@
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('SearchApi API Key')}
{$i18n.t('settings.admin.web.searchapiApiKey.label')}
</div>
<SensitiveInput
@ -591,7 +593,7 @@
</div>
<div class="mt-1.5">
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('SearchApi Engine')}
{$i18n.t('settings.admin.web.searchapiEngine.label')}
</div>
<div class="flex w-full">
@ -611,7 +613,7 @@
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('SerpApi API Key')}
{$i18n.t('settings.admin.web.serpapiApiKey.label')}
</div>
<SensitiveInput
@ -622,7 +624,7 @@
</div>
<div class="mt-1.5">
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('SerpApi Engine')}
{$i18n.t('settings.admin.web.serpapiEngine.label')}
</div>
<div class="flex w-full">
@ -642,7 +644,7 @@
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Jina API Base URL')}
{$i18n.t('settings.admin.web.jinaApiBaseUrl.label')}
</div>
<div class="flex w-full">
@ -660,7 +662,7 @@
<div class="mt-2">
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Jina API Key')}
{$i18n.t('settings.admin.web.jinaApiKey.label')}
</div>
<SensitiveInput
@ -674,7 +676,7 @@
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Bing Search V7 Endpoint')}
{$i18n.t('settings.admin.web.bingSearchV7Endpoint.label')}
</div>
<div class="flex w-full">
@ -692,7 +694,7 @@
<div class="mt-2">
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Bing Search V7 Subscription Key')}
{$i18n.t('settings.admin.web.bingSearchV7SubscriptionKey.label')}
</div>
<SensitiveInput
@ -706,7 +708,7 @@
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Exa API Key')}
{$i18n.t('settings.admin.web.exaApiKey.label')}
</div>
<SensitiveInput
@ -717,11 +719,9 @@
</div>
<AdminSettingField
className="mt-1.5"
label={$i18n.t('Max Content Length')}
label={$i18n.t('settings.admin.web.maxContentLength.label')}
forId="exa-max-content-length"
description={$i18n.t(
'Maximum characters per Exa search result. Leave empty for no limit.'
)}
description={$i18n.t('settings.admin.web.maxContentLength.description')}
>
<input
id="exa-max-content-length"
@ -738,7 +738,7 @@
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Perplexity API Key')}
{$i18n.t('settings.admin.web.perplexityApiKey.label')}
</div>
<SensitiveInput
@ -752,7 +752,7 @@
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class="self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Perplexity Model')}
{$i18n.t('settings.admin.web.perplexityModel.label')}
</div>
<input
list="perplexity-model-list"
@ -773,7 +773,7 @@
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Perplexity Search Context Usage')}
{$i18n.t('settings.admin.web.perplexitySearchContextUsage.label')}
</div>
<SettingsSelect
bind:value={webConfig.PERPLEXITY_SEARCH_CONTEXT_USAGE}
@ -789,7 +789,7 @@
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Microsoft Web IQ API Base URL')}
{$i18n.t('settings.admin.web.microsoftWebIqApiBaseUrl.label')}
</div>
<div class="flex w-full">
@ -807,7 +807,7 @@
<div class="mt-2">
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Microsoft Web IQ API Key')}
{$i18n.t('settings.admin.web.microsoftWebIqApiKey.label')}
</div>
<SensitiveInput
@ -819,7 +819,7 @@
<div class="mt-2">
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Language')}
{$i18n.t('settings.admin.web.language.label')}
</div>
<input
@ -835,7 +835,7 @@
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Sougou Search API sID')}
{$i18n.t('settings.admin.web.sougouSearchApiSid.label')}
</div>
<SensitiveInput
@ -848,7 +848,7 @@
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Sougou Search API SK')}
{$i18n.t('settings.admin.web.sougouSearchApiSk.label')}
</div>
<SensitiveInput
@ -862,7 +862,7 @@
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Firecrawl API Base URL')}
{$i18n.t('settings.admin.web.firecrawlApiBaseUrl.label')}
</div>
<div class="flex w-full">
@ -880,7 +880,7 @@
<div class="mt-2">
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Firecrawl API Key')}
{$i18n.t('settings.admin.web.firecrawlApiKey.label')}
</div>
<SensitiveInput
@ -892,7 +892,7 @@
<div class="mt-2">
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Firecrawl Timeout (s)')}
{$i18n.t('settings.admin.web.firecrawlTimeoutS.label')}
</div>
<div class="flex w-full">
@ -912,7 +912,7 @@
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('External Web Search URL')}
{$i18n.t('settings.admin.web.externalWebSearchUrl.label')}
</div>
<div class="flex w-full">
@ -930,7 +930,7 @@
<div class="mt-2">
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('External Web Search API Key')}
{$i18n.t('settings.admin.web.externalWebSearchApiKey.label')}
</div>
<SensitiveInput
@ -944,7 +944,7 @@
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Yandex Web Search URL')}
{$i18n.t('settings.admin.web.yandexWebSearchUrl.label')}
</div>
<div class="flex w-full">
@ -962,7 +962,7 @@
<div class="mt-2">
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Yandex Web Search API Key')}
{$i18n.t('settings.admin.web.yandexWebSearchApiKey.label')}
</div>
<SensitiveInput
@ -974,7 +974,7 @@
<div class="mb-2.5">
<div class=" mb-1 text-xs text-gray-600 dark:text-gray-400">
{$i18n.t('Yandex Web Search config')}
{$i18n.t('settings.admin.web.yandexWebSearchConfig.label')}
</div>
<Tooltip
@ -997,7 +997,7 @@
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('You.com API Key')}
{$i18n.t('settings.admin.web.youComApiKey.label')}
</div>
<SensitiveInput
@ -1011,7 +1011,7 @@
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Linkup API Key')}
{$i18n.t('settings.admin.web.linkupApiKey.label')}
</div>
<SensitiveInput
@ -1023,7 +1023,7 @@
<div class="mt-2">
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Parameters')}
{$i18n.t('settings.admin.web.parameters.label')}
</div>
<Textarea
@ -1037,7 +1037,7 @@
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('OpenSERP URL')}
{$i18n.t('settings.admin.web.openserpUrl.label')}
</div>
<div class="flex-1">
@ -1058,7 +1058,7 @@
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('DDGS Backend')}
{$i18n.t('settings.admin.web.ddgsBackend.label')}
</div>
<div class="flex w-full">
@ -1084,18 +1084,18 @@
{#if webConfig.ENABLE_WEB_SEARCH}
<AdminSettingField
label={$i18n.t('Search Limits')}
description={$i18n.t('Control result volume and parallel search requests.')}
label={$i18n.t('settings.admin.web.searchLimits.label')}
description={$i18n.t('settings.admin.web.searchLimits.description')}
>
<div class="flex gap-2">
<div class="w-full">
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Search Result Count')}
{$i18n.t('settings.admin.web.searchResultCount.label')}
</div>
<input
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
placeholder={$i18n.t('Search Result Count')}
placeholder={$i18n.t('settings.admin.web.searchResultCount.label')}
bind:value={webConfig.WEB_SEARCH_RESULT_COUNT}
required
/>
@ -1104,18 +1104,16 @@
<div class="w-full">
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
<Tooltip
content={$i18n.t(
'Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).'
)}
content={$i18n.t('settings.admin.web.searchResultCount.description')}
placement="top-start"
>
{$i18n.t('Concurrent Requests')}
{$i18n.t('settings.admin.web.concurrentRequests.label')}
</Tooltip>
</div>
<input
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
placeholder={$i18n.t('Concurrent Requests')}
placeholder={$i18n.t('settings.admin.web.concurrentRequests.label')}
bind:value={webConfig.WEB_SEARCH_CONCURRENT_REQUESTS}
type="number"
min="0"
@ -1125,10 +1123,8 @@
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Fetch URL Content Length Limit')}
description={$i18n.t(
'Maximum characters to return from fetched URLs. Leave empty for no limit.'
)}
label={$i18n.t('settings.admin.web.fetchUrlContentLengthLimit.label')}
description={$i18n.t('settings.admin.web.fetchUrlContentLengthLimit.description')}
>
<input
class={inputClass}
@ -1140,8 +1136,8 @@
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Domain Filter List')}
description={$i18n.t('Restrict or exclude domains using a comma-separated list.')}
label={$i18n.t('settings.admin.web.domainFilterList.label')}
description={$i18n.t('settings.admin.web.domainFilterList.description')}
>
<input
class={inputClass}
@ -1154,9 +1150,9 @@
{/if}
<AdminSettingRow
label={$i18n.t('Bypass Embedding and Retrieval')}
label={$i18n.t('settings.admin.web.bypassEmbeddingAndRetrieval.label')}
description={webConfig.BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL
? $i18n.t('Inject the entire content as context for comprehensive processing.')
? $i18n.t('settings.admin.web.bypassEmbeddingAndRetrieval.description')
: $i18n.t('Use segmented retrieval for focused and relevant content extraction.')}
let:labelId
>
@ -1167,17 +1163,17 @@
</AdminSettingRow>
<AdminSettingRow
label={$i18n.t('Bypass Web Loader')}
description={$i18n.t('Use search results without fetching page contents.')}
label={$i18n.t('settings.admin.web.bypassWebLoader.label')}
description={$i18n.t('settings.admin.web.bypassWebLoader.description')}
let:labelId
>
<Switch bind:state={webConfig.BYPASS_WEB_SEARCH_WEB_LOADER} ariaLabelledbyId={labelId} />
</AdminSettingRow>
<AdminSettingRow
label={$i18n.t('Trust Proxy Environment')}
label={$i18n.t('settings.admin.web.trustProxyEnvironment.label')}
description={webConfig.WEB_SEARCH_TRUST_ENV
? $i18n.t('Use proxy environment variables to fetch page contents.')
? $i18n.t('settings.admin.web.trustProxyEnvironment.description')
: $i18n.t('Fetch page contents without proxy environment variables.')}
let:labelId
>
@ -1185,10 +1181,10 @@
</AdminSettingRow>
</AdminSettingSection>
<AdminSettingSection title={$i18n.t('Loader')}>
<AdminSettingSection title={$i18n.t('settings.admin.web.sections.loader.title')}>
<AdminSettingRow
label={$i18n.t('Web Loader Engine')}
description={$i18n.t('Choose how web result pages are fetched and read.')}
label={$i18n.t('settings.admin.web.webLoaderEngine.label')}
description={$i18n.t('settings.admin.web.webLoaderEngine.description')}
>
<SettingsSelect
bind:value={webConfig.WEB_LOADER_ENGINE}
@ -1205,19 +1201,19 @@
{#if webConfig.WEB_LOADER_ENGINE === '' || webConfig.WEB_LOADER_ENGINE === 'safe_web'}
<AdminSettingField
label={$i18n.t('Timeout')}
description={$i18n.t('Maximum time to wait while loading web content.')}
label={$i18n.t('settings.admin.web.timeout.label')}
description={$i18n.t('settings.admin.web.timeout.description')}
>
<input
class={inputClass}
placeholder={$i18n.t('Timeout')}
placeholder={$i18n.t('settings.admin.web.timeout.label')}
bind:value={webConfig.WEB_LOADER_TIMEOUT}
/>
</AdminSettingField>
<AdminSettingRow
label={$i18n.t('Verify SSL Certificate')}
description={$i18n.t('Validate SSL certificates when fetching web content.')}
label={$i18n.t('settings.admin.web.verifySslCertificate.label')}
description={$i18n.t('settings.admin.web.verifySslCertificate.description')}
let:labelId
>
<Switch
@ -1229,7 +1225,7 @@
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Playwright WebSocket URL')}
{$i18n.t('settings.admin.web.playwrightWebsocketUrl.label')}
</div>
<div class="flex w-full">
@ -1247,7 +1243,7 @@
<div class="mt-2">
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Playwright Timeout (ms)')}
{$i18n.t('settings.admin.web.playwrightTimeoutMs.label')}
</div>
<div class="flex w-full">
@ -1266,7 +1262,7 @@
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Firecrawl API Base URL')}
{$i18n.t('settings.admin.web.firecrawlApiBaseUrl.label')}
</div>
<div class="flex w-full">
@ -1284,7 +1280,7 @@
<div class="mt-2">
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Firecrawl API Key')}
{$i18n.t('settings.admin.web.firecrawlApiKey.label')}
</div>
<SensitiveInput
@ -1298,7 +1294,7 @@
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Tavily Extract Depth')}
{$i18n.t('settings.admin.web.tavilyExtractDepth.label')}
</div>
<div class="flex w-full">
@ -1317,7 +1313,7 @@
{#if webConfig.WEB_SEARCH_ENGINE !== 'tavily'}
<div class="mt-2">
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Tavily API Key')}
{$i18n.t('settings.admin.web.tavilyApiKey.label')}
</div>
<SensitiveInput
@ -1333,7 +1329,7 @@
{#if webConfig.WEB_SEARCH_ENGINE !== 'microsoft_web_iq'}
<div>
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Microsoft Web IQ API Base URL')}
{$i18n.t('settings.admin.web.microsoftWebIqApiBaseUrl.label')}
</div>
<div class="flex w-full">
@ -1351,7 +1347,7 @@
<div class="mt-2">
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Microsoft Web IQ API Key')}
{$i18n.t('settings.admin.web.microsoftWebIqApiKey.label')}
</div>
<SensitiveInput
@ -1363,7 +1359,7 @@
<div class="mt-2">
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('Language')}
{$i18n.t('settings.admin.web.language.label')}
</div>
<input
@ -1380,7 +1376,7 @@
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('External Web Loader URL')}
{$i18n.t('settings.admin.web.externalWebLoaderUrl.label')}
</div>
<div class="flex w-full">
@ -1398,7 +1394,7 @@
<div class="mt-2">
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('External Web Loader API Key')}
{$i18n.t('settings.admin.web.externalWebLoaderApiKey.label')}
</div>
<SensitiveInput
@ -1411,20 +1407,20 @@
{/if}
<AdminSettingField
label={$i18n.t('Concurrent Requests')}
description={$i18n.t('Limit parallel web loader requests.')}
label={$i18n.t('settings.admin.web.concurrentRequests.label')}
description={$i18n.t('settings.admin.web.concurrentRequests.description')}
>
<input
class={inputClass}
placeholder={$i18n.t('Concurrent Requests')}
placeholder={$i18n.t('settings.admin.web.concurrentRequests.label')}
bind:value={webConfig.WEB_LOADER_CONCURRENT_REQUESTS}
required
/>
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Youtube Language')}
description={$i18n.t('Preferred transcript language codes, separated by commas.')}
label={$i18n.t('settings.admin.web.youtubeLanguage.label')}
description={$i18n.t('settings.admin.web.youtubeLanguage.description')}
>
<input
class={inputClass}
@ -1436,8 +1432,8 @@
</AdminSettingField>
<AdminSettingField
label={$i18n.t('Youtube Proxy URL')}
description={$i18n.t('Proxy URL used for Youtube loader requests.')}
label={$i18n.t('settings.admin.web.youtubeProxyUrl.label')}
description={$i18n.t('settings.admin.web.youtubeProxyUrl.description')}
>
<input
class={inputClass}

View file

@ -49,16 +49,19 @@
</script>
<div id="tab-about" class="flex flex-col h-full justify-between text-sm">
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">{$i18n.t('About')}</h2>
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">
{$i18n.t('settings.personal.about.title')}
</h2>
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5">
<!-- LICENSE covers this Open WebUI About identifier.
Do not alter, remove, obscure, or replace it except as LICENSE permits:
https://docs.openwebui.com/license. -->
<UserSettingSection title={`${$WEBUI_NAME} ${$i18n.t('Version')}`} first>
<UserSettingRow
description={$i18n.t('View the installed version and check release updates.')}
>
<UserSettingSection
title={`${$WEBUI_NAME} ${$i18n.t('settings.personal.about.sections.version.title')}`}
first
>
<UserSettingRow description={$i18n.t('settings.personal.about.seeWhatSNew.description')}>
<div slot="label" class="flex flex-col text-xs text-gray-600 dark:text-gray-400">
<div class="flex gap-1">
<Tooltip content={WEBUI_BUILD_HASH}>
@ -89,7 +92,7 @@
showChangelog.set(true);
}}
>
<div>{$i18n.t("See what's new")}</div>
<div>{$i18n.t('settings.personal.about.seeWhatSNew.label')}</div>
</button>
</div>
@ -100,21 +103,21 @@
checkForVersionUpdates();
}}
>
{$i18n.t('Check for updates')}
{$i18n.t('settings.personal.about.checkForUpdates.label')}
</button>
{/if}
</UserSettingRow>
</UserSettingSection>
{#if ollamaVersion}
<UserSettingSection title={$i18n.t('Ollama Version')}>
<UserSettingSection title={$i18n.t('settings.personal.about.sections.ollamaVersion.title')}>
<div class="text-xs text-gray-600 dark:text-gray-400">
{ollamaVersion ?? 'N/A'}
</div>
</UserSettingSection>
{/if}
<UserSettingSection title={$i18n.t('Community')}>
<UserSettingSection title={$i18n.t('settings.personal.about.sections.community.title')}>
{#if $config?.license_metadata}
<!-- LICENSE covers this Open WebUI license attribution.
Do not alter, remove, obscure, or replace it except as LICENSE permits:

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { canUseApiKeys } from '$lib/utils/settings-access';
import { toast } from 'svelte-sonner';
import { onMount, getContext } from 'svelte';
@ -237,9 +238,11 @@
<div id="tab-account" class="flex h-full flex-col text-sm">
<div class="flex-1 min-h-0 w-full overflow-y-auto scrollbar-hover pr-1.5">
<h2 class="mb-4 text-sm font-medium text-gray-900 dark:text-white">{$i18n.t('Account')}</h2>
<h2 class="mb-4 text-sm font-medium text-gray-900 dark:text-white">
{$i18n.t('settings.personal.account.title')}
</h2>
<UserSettingSection title={$i18n.t('Profile')} first>
<UserSettingSection title={$i18n.t('settings.personal.account.sections.profile.title')} first>
<UserProfileImage
bind:profileImageUrl
user={$user}
@ -248,40 +251,40 @@
/>
<UserSettingField
label={$i18n.t('Name')}
description={$i18n.t('Set the display name shown across your account.')}
label={$i18n.t('settings.personal.account.name.label')}
description={$i18n.t('settings.personal.account.name.description')}
>
<input
class={inputClass}
type="text"
bind:value={name}
aria-label={$i18n.t('Name')}
aria-label={$i18n.t('settings.personal.account.name.label')}
required
placeholder={$i18n.t('Enter your name')}
/>
</UserSettingField>
<UserSettingField
label={$i18n.t('Bio')}
description={$i18n.t('Add optional profile context visible where profiles are shown.')}
label={$i18n.t('settings.personal.account.bio.label')}
description={$i18n.t('settings.personal.account.bio.description')}
>
<Textarea
className={textareaClass}
minSize={60}
bind:value={bio}
ariaLabel={$i18n.t('Bio')}
ariaLabel={$i18n.t('settings.personal.account.bio.label')}
placeholder={$i18n.t('Share your background and interests')}
/>
</UserSettingField>
<UserSettingField
label={$i18n.t('Gender')}
description={$i18n.t('Choose the gender value stored on your profile.')}
label={$i18n.t('settings.personal.account.gender.label')}
description={$i18n.t('settings.personal.account.gender.description')}
>
<SettingsSelect
bind:value={_gender}
className="w-full"
ariaLabel={$i18n.t('Gender')}
ariaLabel={$i18n.t('settings.personal.account.gender.label')}
on:change={() => {
console.log(_gender);
@ -312,13 +315,13 @@
</UserSettingField>
<UserSettingField
label={$i18n.t('Birth Date')}
description={$i18n.t('Set the birth date saved with your profile.')}
label={$i18n.t('settings.personal.account.birthDate.label')}
description={$i18n.t('settings.personal.account.birthDate.description')}
>
<input
class="{inputClass} dark:scheme-dark"
type="date"
aria-label={$i18n.t('Birth Date')}
aria-label={$i18n.t('settings.personal.account.birthDate.label')}
bind:value={dateOfBirth}
required
/>
@ -367,15 +370,15 @@
</section>
{#if $config?.features.enable_login_form && $config?.features.enable_password_change_form}
<UserSettingSection title={$i18n.t('Password')}>
<UserSettingSection title={$i18n.t('settings.personal.account.sections.password.title')}>
<UpdatePassword />
</UserSettingSection>
{/if}
{#if ($config?.features?.enable_api_keys ?? true) && ($user?.role === 'admin' || ($user?.permissions?.features?.api_keys ?? false))}
<UserSettingSection title={$i18n.t('API keys')}>
<UserSettingRow description={$i18n.t('Show or hide sensitive account secrets.')}>
<span slot="label">{$i18n.t('Secrets')}</span>
{#if canUseApiKeys({ user: $user, config: $config })}
<UserSettingSection title={$i18n.t('settings.personal.account.sections.apiKeys.title')}>
<UserSettingRow description={$i18n.t('settings.personal.account.secrets.description')}>
<span slot="label">{$i18n.t('settings.personal.account.secrets.label')}</span>
<button
class={actionButtonClass}
type="button"
@ -389,8 +392,8 @@
<div class="flex flex-col gap-2.5">
{#if $user?.role === 'admin'}
<UserSettingField
label={$i18n.t('JWT Token')}
description={$i18n.t('Copy the current session token for authenticated requests.')}
label={$i18n.t('settings.personal.account.jwtToken.label')}
description={$i18n.t('settings.personal.account.jwtToken.description')}
>
<div class="flex">
<SensitiveInput variant="settings" value={localStorage.token} readOnly={true} />
@ -443,10 +446,10 @@
</UserSettingField>
{/if}
{#if ($config?.features?.enable_api_keys ?? true) && ($user?.role === 'admin' || ($user?.permissions?.features?.api_keys ?? false))}
{#if canUseApiKeys({ user: $user, config: $config })}
<UserSettingField
label={$i18n.t('API Key')}
description={$i18n.t('Create, copy, or rotate your API key.')}
label={$i18n.t('settings.personal.account.apiKey.label')}
description={$i18n.t('settings.personal.account.apiKey.description')}
>
<div class="flex">
{#if APIKey}
@ -546,7 +549,7 @@
>
<Plus strokeWidth="2" className="size-3.5" />
{$i18n.t('Create new secret key')}</button
{$i18n.t('settings.personal.account.createNewSecretKey.label')}</button
>
{/if}
</div>

View file

@ -55,7 +55,9 @@
}}
>
<div class="flex items-center justify-between gap-2.5">
<div class="text-xs text-gray-600 dark:text-gray-400">{$i18n.t('Change Password')}</div>
<div class="text-xs text-gray-600 dark:text-gray-400">
{$i18n.t('settings.personal.account.changePassword.label')}
</div>
<button
class={actionButtonClass}
type="button"
@ -65,14 +67,14 @@
>
</div>
<p class="mt-0.5 text-[0.6875rem] text-gray-400 dark:text-gray-600">
{$i18n.t('Update the password used for email and password sign-in.')}
{$i18n.t('settings.personal.account.changePassword.description')}
</p>
{#if show}
<div class="py-2.5 space-y-2.5">
<div class="flex flex-col w-full">
<div class="mb-1 text-xs text-gray-600 dark:text-gray-400">
{$i18n.t('Current Password')}
{$i18n.t('settings.personal.account.currentPassword.label')}
</div>
<div class="flex-1">
@ -89,7 +91,7 @@
<div class="flex flex-col w-full">
<div class="mb-1 text-xs text-gray-600 dark:text-gray-400">
{$i18n.t('New Password')}
{$i18n.t('settings.personal.account.newPassword.label')}
</div>
<div class="flex-1">
@ -106,7 +108,7 @@
<div class="flex flex-col w-full">
<div class="mb-1 text-xs text-gray-600 dark:text-gray-400">
{$i18n.t('Confirm Password')}
{$i18n.t('settings.personal.account.confirmPassword.label')}
</div>
<div class="flex-1">
@ -124,7 +126,7 @@
<div class="flex justify-end">
<button class={actionButtonClass}>
{$i18n.t('Update password')}
{$i18n.t('settings.personal.account.updatePassword.label')}
</button>
</div>
{/if}

View file

@ -113,15 +113,13 @@
>
<div>
<Tooltip
content={$i18n.t(
'When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.'
)}
content={$i18n.t('settings.personal.general.parameters.streamChatResponse.description')}
placement="top-start"
className="inline-tooltip"
>
<div class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs">
{$i18n.t('Stream Chat Response')}
{$i18n.t('settings.personal.general.parameters.streamChatResponse.label')}
</div>
<button
class="p-1 px-3 text-xs flex rounded-sm transition"
@ -150,15 +148,13 @@
{#if admin}
<div>
<Tooltip
content={$i18n.t(
'The stream delta chunk size for the model. Increasing the chunk size will make the model respond with larger pieces of text at once.'
)}
content={$i18n.t('settings.personal.general.parameters.streamDeltaChunkSize.description')}
placement="top-start"
className="inline-tooltip"
>
<div class="flex w-full justify-between">
<div class=" self-center text-xs">
{$i18n.t('Stream Delta Chunk Size')}
{$i18n.t('settings.personal.general.parameters.streamDeltaChunkSize.label')}
</div>
<button
class="p-1 px-3 text-xs flex rounded-sm transition shrink-0 outline-hidden"
@ -180,7 +176,7 @@
{#if (params?.stream_delta_chunk_size ?? null) !== null}
{@render rangeParam(
'stream_delta_chunk_size',
$i18n.t('Stream Delta Chunk Size'),
$i18n.t('settings.personal.general.parameters.streamDeltaChunkSize.label'),
1,
128,
1,
@ -192,15 +188,13 @@
<div>
<Tooltip
content={$i18n.t(
'Set a model-specific context compaction token threshold. When set, this overrides the global threshold up to the global cap.'
)}
content={$i18n.t('settings.personal.general.parameters.contextCompactionThreshold.description')}
placement="top-start"
className="inline-tooltip"
>
<div class="flex w-full justify-between">
<div class=" self-center text-xs">
{$i18n.t('Context Compaction Threshold')}
{$i18n.t('settings.personal.general.parameters.contextCompactionThreshold.label')}
</div>
<button
class="p-1 px-3 text-xs flex rounded-sm transition shrink-0 outline-hidden"
@ -225,7 +219,7 @@
<input
class="text-sm w-full bg-transparent outline-hidden outline-none"
type="number"
aria-label={$i18n.t('Context Compaction Threshold')}
aria-label={$i18n.t('settings.personal.general.parameters.contextCompactionThreshold.label')}
placeholder={$i18n.t('Enter token threshold')}
bind:value={params.compact_token_threshold}
autocomplete="off"
@ -240,15 +234,13 @@
<div>
<Tooltip
content={$i18n.t(
"Native mode (default) leverages the model's built-in tool-calling capabilities. Legacy mode works with a wider range of models by calling tools once before execution via prompt injection."
)}
content={$i18n.t('settings.personal.general.parameters.functionCalling.description')}
placement="top-start"
className="inline-tooltip"
>
<div class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs">
{$i18n.t('Function Calling')}
{$i18n.t('settings.personal.general.parameters.functionCalling.label')}
</div>
<button
class="p-1 px-3 text-xs flex rounded-sm transition"
@ -277,15 +269,13 @@
<div class=" py-0.5 w-full justify-between">
<Tooltip
content={$i18n.t(
'Enable, disable, or customize the reasoning tags used by the model. "Enabled" uses default tags, "Disabled" turns off reasoning tags, and "Custom" lets you specify your own start and end tags.'
)}
content={$i18n.t('settings.personal.general.parameters.reasoningTags.description')}
placement="top-start"
className="inline-tooltip"
>
<div class="flex w-full justify-between">
<div class=" self-center text-xs">
{$i18n.t('Reasoning Tags')}
{$i18n.t('settings.personal.general.parameters.reasoningTags.label')}
</div>
<button
class="p-1 px-3 text-xs flex rounded-sm transition shrink-0 outline-hidden"
@ -344,15 +334,13 @@
<div class=" py-0.5 w-full justify-between">
<Tooltip
content={$i18n.t(
'Sets the random number seed to use for generation. Setting this to a specific number will make the model generate the same text for the same prompt.'
)}
content={$i18n.t('settings.personal.general.parameters.seed.description')}
placement="top-start"
className="inline-tooltip"
>
<div class="flex w-full justify-between">
<div class=" self-center text-xs">
{$i18n.t('Seed')}
{$i18n.t('settings.personal.general.parameters.seed.label')}
</div>
<button
@ -377,7 +365,7 @@
<input
class="text-sm w-full bg-transparent outline-hidden outline-none"
type="number"
aria-label={$i18n.t('Seed')}
aria-label={$i18n.t('settings.personal.general.parameters.seed.label')}
placeholder={$i18n.t('Enter Seed')}
bind:value={params.seed}
autocomplete="off"
@ -390,15 +378,13 @@
<div class=" py-0.5 w-full justify-between">
<Tooltip
content={$i18n.t(
'Sets the stop sequences to use. When this pattern is encountered, the LLM will stop generating text and return. Multiple stop patterns may be set by specifying multiple separate stop parameters in a modelfile.'
)}
content={$i18n.t('settings.personal.general.parameters.stopSequence.description')}
placement="top-start"
className="inline-tooltip"
>
<div class="flex w-full justify-between">
<div class=" self-center text-xs">
{$i18n.t('Stop Sequence')}
{$i18n.t('settings.personal.general.parameters.stopSequence.label')}
</div>
<button
@ -423,7 +409,7 @@
<input
class="text-sm w-full bg-transparent outline-hidden outline-none"
type="text"
aria-label={$i18n.t('Stop Sequence')}
aria-label={$i18n.t('settings.personal.general.parameters.stopSequence.label')}
placeholder={$i18n.t('Enter stop sequence')}
bind:value={params.stop}
autocomplete="off"
@ -435,15 +421,13 @@
<div class=" py-0.5 w-full justify-between">
<Tooltip
content={$i18n.t(
'The temperature of the model. Increasing the temperature will make the model answer more creatively.'
)}
content={$i18n.t('settings.personal.general.parameters.temperature.description')}
placement="top-start"
className="inline-tooltip"
>
<div class="flex w-full justify-between">
<div class=" self-center text-xs">
{$i18n.t('Temperature')}
{$i18n.t('settings.personal.general.parameters.temperature.label')}
</div>
<button
class="p-1 px-3 text-xs flex rounded-sm transition shrink-0 outline-hidden"
@ -462,21 +446,25 @@
</Tooltip>
{#if (params?.temperature ?? null) !== null}
{@render rangeParam('temperature', $i18n.t('Temperature'), 0, 2, 0.05)}
{@render rangeParam(
'temperature',
$i18n.t('settings.personal.general.parameters.temperature.label'),
0,
2,
0.05
)}
{/if}
</div>
<div class=" py-0.5 w-full justify-between">
<Tooltip
content={$i18n.t(
'Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort.'
)}
content={$i18n.t('settings.personal.general.parameters.reasoningEffort.description')}
placement="top-start"
className="inline-tooltip"
>
<div class="flex w-full justify-between">
<div class=" self-center text-xs">
{$i18n.t('Reasoning Effort')}
{$i18n.t('settings.personal.general.parameters.reasoningEffort.label')}
</div>
<button
class="p-1 px-3 text-xs flex rounded-sm transition shrink-0 outline-hidden"
@ -500,7 +488,7 @@
<input
class="text-sm w-full bg-transparent outline-hidden outline-none"
type="text"
aria-label={$i18n.t('Reasoning Effort')}
aria-label={$i18n.t('settings.personal.general.parameters.reasoningEffort.label')}
placeholder={$i18n.t('Enter reasoning effort')}
bind:value={params.reasoning_effort}
autocomplete="off"
@ -520,7 +508,7 @@
>
<div class="flex w-full justify-between">
<div class=" self-center text-xs">
{'logit_bias'}
{$i18n.t('settings.personal.general.parameters.logitBias.label')}
</div>
<button
class="p-1 px-3 text-xs flex rounded-sm transition shrink-0 outline-hidden"
@ -544,7 +532,7 @@
<input
class="text-sm w-full bg-transparent outline-hidden outline-none"
type="text"
aria-label="logit_bias"
aria-label={$i18n.t('settings.personal.general.parameters.logitBias.label')}
placeholder={$i18n.t(
'Enter comma-separated "token:bias_value" pairs (example: 5432:100, 413:-100)'
)}
@ -566,7 +554,7 @@
>
<div class="flex w-full justify-between">
<div class=" self-center text-xs">
{'max_tokens'}
{$i18n.t('settings.personal.general.parameters.maxTokens.label')}
</div>
<button
@ -586,7 +574,15 @@
</Tooltip>
{#if (params?.max_tokens ?? null) !== null}
{@render rangeParam('max_tokens', 'max_tokens', -2, 131072, 1, 1, undefined)}
{@render rangeParam(
'max_tokens',
$i18n.t('settings.personal.general.parameters.maxTokens.label'),
-2,
131072,
1,
1,
undefined
)}
{/if}
</div>
@ -600,7 +596,7 @@
>
<div class="flex w-full justify-between">
<div class=" self-center text-xs">
{'top_k'}
{$i18n.t('settings.personal.general.parameters.topK.label')}
</div>
<button
class="p-1 px-3 text-xs flex rounded-sm transition shrink-0 outline-hidden"
@ -623,7 +619,7 @@
<div class=" flex-1">
<input
type="range"
aria-label="top_k"
aria-label={$i18n.t('settings.personal.general.parameters.topK.label')}
min="0"
max="1000"
step="1"
@ -636,7 +632,7 @@
<input
value={params.top_k}
type="number"
aria-label="top_k"
aria-label={$i18n.t('settings.personal.general.parameters.topK.label')}
class=" bg-transparent text-center w-14"
min="0"
max="1000"
@ -658,7 +654,7 @@
>
<div class="flex w-full justify-between">
<div class=" self-center text-xs">
{'top_p'}
{$i18n.t('settings.personal.general.parameters.topP.label')}
</div>
<button
@ -678,7 +674,7 @@
</Tooltip>
{#if (params?.top_p ?? null) !== null}
{@render rangeParam('top_p', 'top_p', 0, 1, 0.05)}
{@render rangeParam('top_p', $i18n.t('settings.personal.general.parameters.topP.label'), 0, 1, 0.05)}
{/if}
</div>
@ -692,7 +688,7 @@
>
<div class="flex w-full justify-between">
<div class=" self-center text-xs">
{'min_p'}
{$i18n.t('settings.personal.general.parameters.minP.label')}
</div>
<button
class="p-1 px-3 text-xs flex rounded-sm transition shrink-0 outline-hidden"
@ -711,7 +707,7 @@
</Tooltip>
{#if (params?.min_p ?? null) !== null}
{@render rangeParam('min_p', 'min_p', 0, 1, 0.05)}
{@render rangeParam('min_p', $i18n.t('settings.personal.general.parameters.minP.label'), 0, 1, 0.05)}
{/if}
</div>
@ -725,7 +721,7 @@
>
<div class="flex w-full justify-between">
<div class=" self-center text-xs">
{'frequency_penalty'}
{$i18n.t('settings.personal.general.parameters.frequencyPenalty.label')}
</div>
<button
@ -745,7 +741,13 @@
</Tooltip>
{#if (params?.frequency_penalty ?? null) !== null}
{@render rangeParam('frequency_penalty', 'frequency_penalty', -2, 2, 0.05)}
{@render rangeParam(
'frequency_penalty',
$i18n.t('settings.personal.general.parameters.frequencyPenalty.label'),
-2,
2,
0.05
)}
{/if}
</div>
@ -759,7 +761,7 @@
>
<div class="flex w-full justify-between">
<div class=" self-center text-xs">
{'presence_penalty'}
{$i18n.t('settings.personal.general.parameters.presencePenalty.label')}
</div>
<button
@ -779,7 +781,13 @@
</Tooltip>
{#if (params?.presence_penalty ?? null) !== null}
{@render rangeParam('presence_penalty', 'presence_penalty', -2, 2, 0.05)}
{@render rangeParam(
'presence_penalty',
$i18n.t('settings.personal.general.parameters.presencePenalty.label'),
-2,
2,
0.05
)}
{/if}
</div>
@ -791,7 +799,7 @@
>
<div class="flex w-full justify-between">
<div class=" self-center text-xs">
{'mirostat'}
{$i18n.t('settings.personal.general.parameters.mirostat.label')}
</div>
<button
class="p-1 px-3 text-xs flex rounded-sm transition shrink-0 outline-hidden"
@ -810,7 +818,14 @@
</Tooltip>
{#if (params?.mirostat ?? null) !== null}
{@render rangeParam('mirostat', 'mirostat', 0, 2, 1, 1)}
{@render rangeParam(
'mirostat',
$i18n.t('settings.personal.general.parameters.mirostat.label'),
0,
2,
1,
1
)}
{/if}
</div>
@ -824,7 +839,7 @@
>
<div class="flex w-full justify-between">
<div class=" self-center text-xs">
{'mirostat_eta'}
{$i18n.t('settings.personal.general.parameters.mirostatEta.label')}
</div>
<button
class="p-1 px-3 text-xs flex rounded-sm transition shrink-0 outline-hidden"
@ -843,7 +858,13 @@
</Tooltip>
{#if (params?.mirostat_eta ?? null) !== null}
{@render rangeParam('mirostat_eta', 'mirostat_eta', 0, 1, 0.05)}
{@render rangeParam(
'mirostat_eta',
$i18n.t('settings.personal.general.parameters.mirostatEta.label'),
0,
1,
0.05
)}
{/if}
</div>
@ -857,7 +878,7 @@
>
<div class="flex w-full justify-between">
<div class=" self-center text-xs">
{'mirostat_tau'}
{$i18n.t('settings.personal.general.parameters.mirostatTau.label')}
</div>
<button
@ -877,7 +898,13 @@
</Tooltip>
{#if (params?.mirostat_tau ?? null) !== null}
{@render rangeParam('mirostat_tau', 'mirostat_tau', 0, 10, 0.5)}
{@render rangeParam(
'mirostat_tau',
$i18n.t('settings.personal.general.parameters.mirostatTau.label'),
0,
10,
0.5
)}
{/if}
</div>
@ -889,7 +916,7 @@
>
<div class="flex w-full justify-between">
<div class=" self-center text-xs">
{'repeat_last_n'}
{$i18n.t('settings.personal.general.parameters.repeatLastN.label')}
</div>
<button
@ -909,7 +936,14 @@
</Tooltip>
{#if (params?.repeat_last_n ?? null) !== null}
{@render rangeParam('repeat_last_n', 'repeat_last_n', -1, 128, 1, 1)}
{@render rangeParam(
'repeat_last_n',
$i18n.t('settings.personal.general.parameters.repeatLastN.label'),
-1,
128,
1,
1
)}
{/if}
</div>
@ -923,7 +957,7 @@
>
<div class="flex w-full justify-between">
<div class=" self-center text-xs">
{'tfs_z'}
{$i18n.t('settings.personal.general.parameters.tfsZ.label')}
</div>
<button
@ -943,7 +977,7 @@
</Tooltip>
{#if (params?.tfs_z ?? null) !== null}
{@render rangeParam('tfs_z', 'tfs_z', 0, 2, 0.05)}
{@render rangeParam('tfs_z', $i18n.t('settings.personal.general.parameters.tfsZ.label'), 0, 2, 0.05)}
{/if}
</div>
@ -957,7 +991,7 @@
>
<div class="flex w-full justify-between">
<div class=" self-center text-xs">
{'repeat_penalty'}
{$i18n.t('settings.personal.general.parameters.repeatPenalty.label')}
</div>
<button
@ -977,7 +1011,13 @@
</Tooltip>
{#if (params?.repeat_penalty ?? null) !== null}
{@render rangeParam('repeat_penalty', 'repeat_penalty', -2, 2, 0.05)}
{@render rangeParam(
'repeat_penalty',
$i18n.t('settings.personal.general.parameters.repeatPenalty.label'),
-2,
2,
0.05
)}
{/if}
</div>
@ -992,7 +1032,7 @@
>
<div class="flex w-full justify-between">
<div class=" self-center text-xs">
{'use_mmap'}
{$i18n.t('settings.personal.general.parameters.useMmap.label')}
</div>
<button
class="p-1 px-3 text-xs flex rounded-sm transition shrink-0 outline-hidden"
@ -1016,7 +1056,10 @@
{params.use_mmap ? $i18n.t('Enabled') : $i18n.t('Disabled')}
</div>
<div class=" pr-2">
<Switch bind:state={params.use_mmap} ariaLabel="use_mmap" />
<Switch
bind:state={params.use_mmap}
ariaLabel={$i18n.t('settings.personal.general.parameters.useMmap.label')}
/>
</div>
</div>
{/if}
@ -1032,7 +1075,7 @@
>
<div class="flex w-full justify-between">
<div class=" self-center text-xs">
{'use_mlock'}
{$i18n.t('settings.personal.general.parameters.useMlock.label')}
</div>
<button
@ -1058,7 +1101,10 @@
</div>
<div class=" pr-2">
<Switch bind:state={params.use_mlock} ariaLabel="use_mlock" />
<Switch
bind:state={params.use_mlock}
ariaLabel={$i18n.t('settings.personal.general.parameters.useMlock.label')}
/>
</div>
</div>
{/if}
@ -1067,15 +1113,15 @@
<div class=" py-0.5 w-full justify-between">
<Tooltip
content={$i18n.t(
'This option enables or disables the use of the reasoning feature in Ollama, which allows the model to think before generating a response. When enabled, the model can take a moment to process the conversation context and generate a more thoughtful response.'
)}
content={$i18n.t('settings.personal.general.parameters.ollama.description')}
placement="top-start"
className="inline-tooltip"
>
<div class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs">
{'think'} ({$i18n.t('Ollama')})
{$i18n.t('settings.personal.general.parameters.think.label')} ({$i18n.t(
'settings.personal.general.parameters.ollama.label'
)})
</div>
<button
class="p-1 px-3 text-xs flex rounded-sm transition"
@ -1111,7 +1157,7 @@
<input
class="text-sm w-full bg-transparent outline-hidden outline-none"
type="text"
aria-label={`think (${$i18n.t('Ollama')})`}
aria-label={`${$i18n.t('settings.personal.general.parameters.think.label')} (${$i18n.t('settings.personal.general.parameters.ollama.label')})`}
placeholder={$i18n.t("e.g. 'low', 'medium', 'high'")}
bind:value={params.think}
autocomplete="off"
@ -1129,7 +1175,9 @@
>
<div class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs">
{'format'} ({$i18n.t('Ollama')})
{$i18n.t('settings.personal.general.parameters.format.label')} ({$i18n.t(
'settings.personal.general.parameters.ollama.label'
)})
</div>
<button
class="p-1 px-3 text-xs flex rounded-sm transition"
@ -1151,7 +1199,7 @@
<div class="flex mt-0.5 space-x-2">
<Textarea
className="w-full text-sm bg-transparent outline-hidden"
ariaLabel={`format (${$i18n.t('Ollama')})`}
ariaLabel={`${$i18n.t('settings.personal.general.parameters.format.label')} (${$i18n.t('settings.personal.general.parameters.ollama.label')})`}
placeholder={$i18n.t('e.g. "json" or a JSON schema')}
bind:value={params.format}
/>
@ -1169,7 +1217,9 @@
>
<div class="flex w-full justify-between">
<div class=" self-center text-xs">
{'num_keep'} ({$i18n.t('Ollama')})
{$i18n.t('settings.personal.general.parameters.numKeep.label')} ({$i18n.t(
'settings.personal.general.parameters.ollama.label'
)})
</div>
<button
@ -1191,7 +1241,7 @@
{#if (params?.num_keep ?? null) !== null}
{@render rangeParam(
'num_keep',
`num_keep (${$i18n.t('Ollama')})`,
`${$i18n.t('settings.personal.general.parameters.numKeep.label')} (${$i18n.t('settings.personal.general.parameters.ollama.label')})`,
-1,
10240000,
1,
@ -1209,7 +1259,9 @@
>
<div class="flex w-full justify-between">
<div class=" self-center text-xs">
{'num_ctx'} ({$i18n.t('Ollama')})
{$i18n.t('settings.personal.general.parameters.numCtx.label')} ({$i18n.t(
'settings.personal.general.parameters.ollama.label'
)})
</div>
<button
@ -1231,7 +1283,7 @@
{#if (params?.num_ctx ?? null) !== null}
{@render rangeParam(
'num_ctx',
`num_ctx (${$i18n.t('Ollama')})`,
`${$i18n.t('settings.personal.general.parameters.numCtx.label')} (${$i18n.t('settings.personal.general.parameters.ollama.label')})`,
-1,
10240000,
1,
@ -1251,7 +1303,9 @@
>
<div class="flex w-full justify-between">
<div class=" self-center text-xs">
{'num_batch'} ({$i18n.t('Ollama')})
{$i18n.t('settings.personal.general.parameters.numBatch.label')} ({$i18n.t(
'settings.personal.general.parameters.ollama.label'
)})
</div>
<button
@ -1273,7 +1327,7 @@
{#if (params?.num_batch ?? null) !== null}
{@render rangeParam(
'num_batch',
`num_batch (${$i18n.t('Ollama')})`,
`${$i18n.t('settings.personal.general.parameters.numBatch.label')} (${$i18n.t('settings.personal.general.parameters.ollama.label')})`,
256,
8192,
256,
@ -1294,7 +1348,9 @@
>
<div class="flex w-full justify-between">
<div class=" self-center text-xs">
{'num_thread'} ({$i18n.t('Ollama')})
{$i18n.t('settings.personal.general.parameters.numThread.label')} ({$i18n.t(
'settings.personal.general.parameters.ollama.label'
)})
</div>
<button
@ -1314,7 +1370,14 @@
</Tooltip>
{#if (params?.num_thread ?? null) !== null}
{@render rangeParam('num_thread', `num_thread (${$i18n.t('Ollama')})`, 1, 256, 1, 1)}
{@render rangeParam(
'num_thread',
`${$i18n.t('settings.personal.general.parameters.numThread.label')} (${$i18n.t('settings.personal.general.parameters.ollama.label')})`,
1,
256,
1,
1
)}
{/if}
</div>
@ -1328,7 +1391,9 @@
>
<div class="flex w-full justify-between">
<div class=" self-center text-xs">
{'num_gpu'} ({$i18n.t('Ollama')})
{$i18n.t('settings.personal.general.parameters.numGpu.label')} ({$i18n.t(
'settings.personal.general.parameters.ollama.label'
)})
</div>
<button
@ -1348,7 +1413,14 @@
</Tooltip>
{#if (params?.num_gpu ?? null) !== null}
{@render rangeParam('num_gpu', `num_gpu (${$i18n.t('Ollama')})`, 0, 256, 1, 1)}
{@render rangeParam(
'num_gpu',
`${$i18n.t('settings.personal.general.parameters.numGpu.label')} (${$i18n.t('settings.personal.general.parameters.ollama.label')})`,
0,
256,
1,
1
)}
{/if}
</div>
@ -1362,7 +1434,9 @@
>
<div class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs">
{'keep_alive'} ({$i18n.t('Ollama')})
{$i18n.t('settings.personal.general.parameters.keepAlive.label')} ({$i18n.t(
'settings.personal.general.parameters.ollama.label'
)})
</div>
<button
class="p-1 px-3 text-xs flex rounded-sm transition"
@ -1385,7 +1459,7 @@
<input
class="w-full text-sm bg-transparent outline-hidden"
type="text"
aria-label={`keep_alive (${$i18n.t('Ollama')})`}
aria-label={`${$i18n.t('settings.personal.general.parameters.keepAlive.label')} (${$i18n.t('settings.personal.general.parameters.ollama.label')})`}
placeholder={$i18n.t("e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.")}
bind:value={params.keep_alive}
/>

View file

@ -174,7 +174,7 @@
<ConfirmDialog
bind:show={showUnarchiveAllConfirmDialog}
message={$i18n.t('Are you sure you want to unarchive all archived chats?')}
confirmLabel={$i18n.t('Unarchive All')}
confirmLabel={$i18n.t('settings.personal.archivedChats.unarchiveAll.label')}
on:confirm={() => {
unarchiveAllHandler();
}}
@ -183,7 +183,7 @@
<div id="tab-archived-chats" class="flex flex-col h-full text-sm">
<div class="mb-3 flex items-center justify-between">
<h2 class="text-sm font-medium text-gray-900 dark:text-white">
{$i18n.t('Archived Chats')}
{$i18n.t('settings.personal.archivedChats.title')}
{#if chatCount !== null}
<span class="ml-2 font-normal text-gray-500 dark:text-gray-500">
{formatNumber(chatCount)}
@ -246,7 +246,9 @@
{:else}
<UndoAction className="size-3.5 shrink-0" strokeWidth="1.5" />
{/if}
<div class="min-w-0 flex-1 truncate text-left">{$i18n.t('Unarchive All')}</div>
<div class="min-w-0 flex-1 truncate text-left">
{$i18n.t('settings.personal.archivedChats.unarchiveAll.label')}
</div>
</button>
<button
@ -256,7 +258,9 @@
on:click={exportChatsHandler}
>
<Download className="size-3.5 shrink-0" strokeWidth="1.5" />
<div class="min-w-0 flex-1 truncate text-left">{$i18n.t('Export')}</div>
<div class="min-w-0 flex-1 truncate text-left">
{$i18n.t('settings.personal.archivedChats.exportArchivedChats.label')}
</div>
</button>
</DropdownMenu>
</div>

View file

@ -181,18 +181,20 @@
dispatch('save');
}}
>
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">{$i18n.t('Audio')}</h2>
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">
{$i18n.t('settings.personal.audio.title')}
</h2>
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5">
<UserSettingSection title={$i18n.t('STT Settings')} first>
<UserSettingSection title={$i18n.t('settings.personal.audio.sections.sttSettings.title')} first>
{#if $config.audio.stt.engine !== 'web'}
<UserSettingRow
label={$i18n.t('Speech-to-Text Engine')}
description={$i18n.t('Choose the engine used to transcribe voice input.')}
label={$i18n.t('settings.personal.audio.speechToTextEngine.label')}
description={$i18n.t('settings.personal.audio.speechToTextEngine.description')}
>
<SettingsSelect
bind:value={STTEngine}
ariaLabel={$i18n.t('Speech-to-Text Engine')}
ariaLabel={$i18n.t('settings.personal.audio.speechToTextEngine.label')}
placeholder={$i18n.t('Select an engine')}
>
<option value="">{$i18n.t('Default')}</option>
@ -201,10 +203,8 @@
</UserSettingRow>
<UserSettingRow
label={$i18n.t('Language')}
description={$i18n.t(
'Set a speech recognition language or leave it blank to detect automatically.'
)}
label={$i18n.t('settings.personal.audio.language.label')}
description={$i18n.t('settings.personal.audio.language.description')}
>
<Tooltip
content={$i18n.t(
@ -224,14 +224,16 @@
{/if}
<UserSettingRow
label={$i18n.t('Instant Auto-Send After Voice Transcription')}
label={$i18n.t('settings.personal.audio.instantAutoSendAfterVoiceTranscription.label')}
description={$i18n.t(
'Send transcribed voice input immediately after speech recognition finishes.'
'settings.personal.audio.instantAutoSendAfterVoiceTranscription.description'
)}
>
<Switch
state={speechAutoSend}
ariaLabel={$i18n.t('Instant Auto-Send After Voice Transcription')}
ariaLabel={$i18n.t(
'settings.personal.audio.instantAutoSendAfterVoiceTranscription.label'
)}
on:change={(event) => {
setSpeechAutoSend(event.detail);
}}
@ -239,14 +241,14 @@
</UserSettingRow>
</UserSettingSection>
<UserSettingSection title={$i18n.t('TTS Settings')}>
<UserSettingSection title={$i18n.t('settings.personal.audio.sections.ttsSettings.title')}>
<UserSettingRow
label={$i18n.t('Text-to-Speech Engine')}
description={$i18n.t('Choose the engine used to read assistant responses aloud.')}
label={$i18n.t('settings.personal.audio.textToSpeechEngine.label')}
description={$i18n.t('settings.personal.audio.textToSpeechEngine.description')}
>
<SettingsSelect
bind:value={TTSEngine}
ariaLabel={$i18n.t('Text-to-Speech Engine')}
ariaLabel={$i18n.t('settings.personal.audio.textToSpeechEngine.label')}
placeholder={$i18n.t('Select an engine')}
>
<option value="">{$i18n.t('Default')}</option>
@ -256,12 +258,12 @@
{#if TTSEngine === 'browser-kokoro'}
<UserSettingRow
label={$i18n.t('Kokoro.js Dtype')}
description={$i18n.t('Select the local model precision used by Kokoro.js.')}
label={$i18n.t('settings.personal.audio.kokoroJsDtype.label')}
description={$i18n.t('settings.personal.audio.kokoroJsDtype.description')}
>
<SettingsSelect
bind:value={TTSEngineConfig.dtype}
ariaLabel={$i18n.t('Kokoro.js Dtype')}
ariaLabel={$i18n.t('settings.personal.audio.kokoroJsDtype.label')}
placeholder={$i18n.t('Select dtype')}
>
<option value="" disabled selected>{$i18n.t('Select dtype')}</option>
@ -274,12 +276,12 @@
{/if}
<UserSettingRow
label={$i18n.t('Auto-Playback Response')}
description={$i18n.t('Play assistant responses aloud automatically.')}
label={$i18n.t('settings.personal.audio.autoPlaybackResponse.label')}
description={$i18n.t('settings.personal.audio.autoPlaybackResponse.description')}
>
<Switch
state={responseAutoPlayback}
ariaLabel={$i18n.t('Auto-Playback Response')}
ariaLabel={$i18n.t('settings.personal.audio.autoPlaybackResponse.label')}
on:change={(event) => {
setResponseAutoPlayback(event.detail);
}}
@ -287,8 +289,8 @@
</UserSettingRow>
<UserSettingRow
label={$i18n.t('Speech Playback Speed')}
description={$i18n.t('Adjust how quickly spoken responses are played.')}
label={$i18n.t('settings.personal.audio.speechPlaybackSpeed.label')}
description={$i18n.t('settings.personal.audio.speechPlaybackSpeed.description')}
>
<div class="relative flex items-center gap-1.5 text-xs text-gray-400 dark:text-gray-600">
<input
@ -296,7 +298,7 @@
min="0"
step="0.01"
bind:value={playbackRate}
aria-label={$i18n.t('Speech Playback Speed')}
aria-label={$i18n.t('settings.personal.audio.speechPlaybackSpeed.label')}
class="h-7 w-16 rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 text-right text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
/>
x
@ -306,16 +308,16 @@
{#if TTSEngine === 'browser-kokoro'}
{#if TTSModel}
<UserSettingSection title={$i18n.t('Voice')}>
<UserSettingSection title={$i18n.t('settings.personal.audio.sections.voice.title')}>
<UserSettingField
label={$i18n.t('Set Voice')}
description={$i18n.t('Choose the Kokoro.js voice used for speech output.')}
label={$i18n.t('settings.personal.audio.kokoroVoice.label')}
description={$i18n.t('settings.personal.audio.kokoroVoice.description')}
>
<input
list="voice-list"
class={inputClass}
bind:value={voice}
aria-label={$i18n.t('Voice')}
aria-label={$i18n.t('settings.personal.audio.sections.voice.title')}
placeholder={$i18n.t('Select a voice')}
/>
@ -327,7 +329,7 @@
</UserSettingField>
</UserSettingSection>
{:else}
<UserSettingSection title={$i18n.t('Voice')}>
<UserSettingSection title={$i18n.t('settings.personal.audio.sections.voice.title')}>
<div class="flex items-center gap-2 text-xs text-gray-600 dark:text-gray-400">
<Spinner className="size-4" />
@ -345,12 +347,16 @@
</UserSettingSection>
{/if}
{:else if $config.audio.tts.engine === ''}
<UserSettingSection title={$i18n.t('Voice')}>
<UserSettingSection title={$i18n.t('settings.personal.audio.sections.voice.title')}>
<UserSettingField
label={$i18n.t('Set Voice')}
description={$i18n.t('Choose the browser voice used for speech output.')}
label={$i18n.t('settings.personal.audio.browserVoice.label')}
description={$i18n.t('settings.personal.audio.browserVoice.description')}
>
<SettingsSelect bind:value={voice} className="w-full" ariaLabel={$i18n.t('Voice')}>
<SettingsSelect
bind:value={voice}
className="w-full"
ariaLabel={$i18n.t('settings.personal.audio.sections.voice.title')}
>
<option value="" selected={voice !== ''}>{$i18n.t('Default')}</option>
{#each voices.filter((v) => nonLocalVoices || v.localService === true) as _voice}
<option
@ -362,23 +368,23 @@
</SettingsSelect>
</UserSettingField>
<UserSettingRow
label={$i18n.t('Allow non-local voices')}
description={$i18n.t('Include voices that are not provided by a local speech service.')}
label={$i18n.t('settings.personal.audio.allowNonLocalVoices.label')}
description={$i18n.t('settings.personal.audio.allowNonLocalVoices.description')}
>
<Switch bind:state={nonLocalVoices} />
</UserSettingRow>
</UserSettingSection>
{:else if $config.audio.tts.engine !== ''}
<UserSettingSection title={$i18n.t('Voice')}>
<UserSettingSection title={$i18n.t('settings.personal.audio.sections.voice.title')}>
<UserSettingField
label={$i18n.t('Set Voice')}
description={$i18n.t('Choose the configured text-to-speech service voice.')}
label={$i18n.t('settings.personal.audio.serviceVoice.label')}
description={$i18n.t('settings.personal.audio.serviceVoice.description')}
>
<input
list="voice-list"
class={inputClass}
bind:value={voice}
aria-label={$i18n.t('Voice')}
aria-label={$i18n.t('settings.personal.audio.sections.voice.title')}
placeholder={$i18n.t('Select a voice')}
/>

View file

@ -72,7 +72,9 @@
updateHandler();
}}
>
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">{$i18n.t('Connections')}</h2>
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">
{$i18n.t('settings.personal.connections.title')}
</h2>
<div class="flex flex-1 min-h-0 flex-col overflow-y-auto scrollbar-hover pr-1.5">
{#if config !== null}
@ -80,17 +82,17 @@
<div class="flex items-start justify-between gap-3">
<div class="min-w-0">
<h3 id="direct-connections-heading" class="text-xs text-gray-600 dark:text-gray-400">
{$i18n.t('Direct Connections')}
{$i18n.t('settings.personal.connections.sections.directConnections.title')}
</h3>
<p class="mt-1 text-[0.6875rem] leading-relaxed text-gray-400 dark:text-gray-600">
{$i18n.t('Connect to your own OpenAI compatible API endpoints.')}
{$i18n.t('settings.personal.connections.addConnection.description')}
</p>
</div>
<Tooltip content={$i18n.t(`Add Connection`)}>
<Tooltip content={$i18n.t('settings.personal.connections.addConnection.label')}>
<button
class="flex size-7 shrink-0 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"
aria-label={$i18n.t('Add Connection')}
aria-label={$i18n.t('settings.personal.connections.addConnection.label')}
on:click={() => {
showConnectionModal = true;
}}

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { canManageChats } from '$lib/utils/settings-access';
import fileSaver from 'file-saver';
const { saveAs } = fileSaver;
@ -117,7 +118,7 @@
<FilesModal bind:show={showFilesModal} />
<ConfirmDialog
title={$i18n.t('Archive All Chats')}
title={$i18n.t('settings.personal.dataControls.archiveAllChats.label')}
message={$i18n.t('Are you sure you want to archive all chats? This action cannot be undone.')}
bind:show={showArchiveConfirmDialog}
on:confirm={archiveAllChatsHandler}
@ -127,7 +128,7 @@
/>
<ConfirmDialog
title={$i18n.t('Delete All Chats')}
title={$i18n.t('settings.personal.dataControls.deleteAllChats.label')}
message={$i18n.t('Are you sure you want to delete all chats? This action cannot be undone.')}
bind:show={showDeleteConfirmDialog}
on:confirm={deleteAllChatsHandler}
@ -138,7 +139,7 @@
<div id="tab-chats" class="flex flex-col h-full text-sm">
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">
{$i18n.t('Data Controls')}
{$i18n.t('settings.personal.dataControls.title')}
</h2>
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5">
@ -151,11 +152,14 @@
hidden
/>
<UserSettingSection title={$i18n.t('Chats')} first>
{#if $user?.role === 'admin' || ($user.permissions?.chat?.import ?? true)}
<UserSettingSection
title={$i18n.t('settings.personal.dataControls.sections.chats.title')}
first
>
{#if canManageChats({ user: $user, config: null }, 'import')}
<UserSettingRow
label={$i18n.t('Import Chats')}
description={$i18n.t('Import chat history from a JSON export file.')}
label={$i18n.t('settings.personal.dataControls.importChats.label')}
description={$i18n.t('settings.personal.dataControls.importChats.description')}
>
<button
class={actionButtonClass}
@ -169,10 +173,10 @@
</UserSettingRow>
{/if}
{#if $user?.role === 'admin' || ($user.permissions?.chat?.export ?? true)}
{#if canManageChats({ user: $user, config: null }, 'export')}
<UserSettingRow
label={$i18n.t('Export Chats')}
description={$i18n.t('Download your chat history as a JSON export.')}
label={$i18n.t('settings.personal.dataControls.exportChats.label')}
description={$i18n.t('settings.personal.dataControls.exportChats.description')}
>
<button
class={actionButtonClass}
@ -187,8 +191,8 @@
{/if}
<UserSettingRow
label={$i18n.t('Shared Chats')}
description={$i18n.t('Review and manage chats you have shared.')}
label={$i18n.t('settings.personal.dataControls.sharedChats.label')}
description={$i18n.t('settings.personal.dataControls.sharedChats.description')}
>
<button
class={actionButtonClass}
@ -202,8 +206,8 @@
</UserSettingRow>
<UserSettingRow
label={$i18n.t('Archive All Chats')}
description={$i18n.t('Move every chat into the archive after confirmation.')}
label={$i18n.t('settings.personal.dataControls.archiveAllChats.label')}
description={$i18n.t('settings.personal.dataControls.archiveAllChats.description')}
>
<button
class={actionButtonClass}
@ -212,14 +216,14 @@
}}
type="button"
>
{$i18n.t('Archive All')}
{$i18n.t('settings.personal.dataControls.archiveAll.label')}
</button>
</UserSettingRow>
{#if $user?.role === 'admin' || ($user?.permissions?.chat?.delete ?? true)}
{#if canManageChats({ user: $user, config: null }, 'delete')}
<UserSettingRow
label={$i18n.t('Delete All Chats')}
description={$i18n.t('Permanently delete every chat after confirmation.')}
label={$i18n.t('settings.personal.dataControls.deleteAllChats.label')}
description={$i18n.t('settings.personal.dataControls.deleteAllChats.description')}
>
<button
class={actionButtonClass}
@ -234,10 +238,10 @@
{/if}
</UserSettingSection>
<UserSettingSection title={$i18n.t('Files')}>
<UserSettingSection title={$i18n.t('settings.personal.dataControls.sections.files.title')}>
<UserSettingRow
label={$i18n.t('Manage Files')}
description={$i18n.t('Open the file manager for uploaded files.')}
label={$i18n.t('settings.personal.dataControls.manageFiles.label')}
description={$i18n.t('settings.personal.dataControls.manageFiles.description')}
>
<button
class={actionButtonClass}

View file

@ -1,4 +1,8 @@
<script lang="ts">
import {
canEditSystemPrompt as allowSystemPrompt,
canEditParameters
} from '$lib/utils/settings-access';
import { createEventDispatcher, onMount, getContext } from 'svelte';
import { getLanguages, changeLanguage } from '$lib/i18n';
const dispatch = createEventDispatcher();
@ -66,13 +70,8 @@
keep_alive: null
};
$: canEditSystemPrompt =
$user?.role === 'admin' ||
(($user?.permissions.chat?.controls ?? true) &&
($user?.permissions.chat?.system_prompt ?? true));
$: canEditParams =
$user?.role === 'admin' ||
(($user?.permissions.chat?.controls ?? true) && ($user?.permissions.chat?.params ?? true));
$: canEditSystemPrompt = allowSystemPrompt({ user: $user, config: $config });
$: canEditParams = canEditParameters({ user: $user, config: $config });
const saveHandler = async () => {
const updated: Record<string, any> = {};
@ -213,17 +212,22 @@
</script>
<div class="flex flex-col h-full justify-between text-sm" id="tab-general">
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">{$i18n.t('General')}</h2>
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">
{$i18n.t('settings.personal.general.title')}
</h2>
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5">
<UserSettingSection title={$i18n.t('WebUI Settings')} first>
<UserSettingSection
title={$i18n.t('settings.personal.general.sections.webuiSettings.title')}
first
>
<UserSettingRow
label={$i18n.t('Theme')}
description={$i18n.t('Choose the color theme used by the interface.')}
label={$i18n.t('settings.personal.general.theme.label')}
description={$i18n.t('settings.personal.general.theme.description')}
>
<SettingsSelect
bind:value={selectedTheme}
ariaLabel={$i18n.t('Theme')}
ariaLabel={$i18n.t('settings.personal.general.theme.label')}
placeholder={$i18n.t('Select a theme')}
on:change={() => themeChangeHandler(selectedTheme)}
>
@ -238,12 +242,12 @@
</UserSettingRow>
<UserSettingRow
label={$i18n.t('Language')}
description={$i18n.t('Choose the language used for interface text.')}
label={$i18n.t('settings.personal.general.language.label')}
description={$i18n.t('settings.personal.general.language.description')}
>
<SettingsSelect
bind:value={lang}
ariaLabel={$i18n.t('Language')}
ariaLabel={$i18n.t('settings.personal.general.language.label')}
placeholder={$i18n.t('Select a language')}
on:change={(e) => {
changeLanguage(lang);
@ -272,8 +276,10 @@
</UserSettingSection>
{#if canEditSystemPrompt}
<UserSettingSection title={$i18n.t('System Prompt')}>
<UserSettingField description={$i18n.t('Set the default system prompt for new chats.')}>
<UserSettingSection title={$i18n.t('settings.personal.general.sections.systemPrompt.title')}>
<UserSettingField
description={$i18n.t('settings.personal.general.sections.systemPrompt.description')}
>
<Textarea
bind:value={system}
className={systemPromptTextareaClass}
@ -285,9 +291,13 @@
{/if}
{#if canEditParams}
<UserSettingSection title={$i18n.t('Advanced Parameters')}>
<UserSettingRow description={$i18n.t('Show or hide custom generation parameters.')}>
<span slot="label">{$i18n.t('Model parameters')}</span>
<UserSettingSection
title={$i18n.t('settings.personal.general.sections.advancedParameters.title')}
>
<UserSettingRow
description={$i18n.t('settings.personal.general.modelParameters.description')}
>
<span slot="label">{$i18n.t('settings.personal.general.modelParameters.label')}</span>
<button
class={actionButtonClass}
type="button"

View file

@ -100,7 +100,9 @@
updateHandler();
}}
>
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">{$i18n.t('Integrations')}</h2>
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">
{$i18n.t('settings.personal.tools.title')}
</h2>
<div class="flex flex-1 min-h-0 flex-col overflow-y-auto scrollbar-hover pr-1.5">
{#if servers !== null}
@ -109,16 +111,16 @@
<div class="mb-3 flex items-start justify-between gap-3">
<div class="min-w-0">
<h3 id="tool-servers-heading" class="text-xs text-gray-600 dark:text-gray-400">
{$i18n.t('External Tool Servers')}
{$i18n.t('settings.personal.tools.sections.externalToolServers.title')}
</h3>
<p class="mt-1 text-[0.6875rem] leading-relaxed text-gray-400 dark:text-gray-600">
{$i18n.t('Connect to your own OpenAPI compatible external tool servers.')}
{$i18n.t('settings.personal.tools.addConnection.description')}
</p>
</div>
<Tooltip content={$i18n.t('Add Connection')}>
<Tooltip content={$i18n.t('settings.personal.tools.addConnection.label')}>
<button
aria-label={$i18n.t('Add Connection')}
aria-label={$i18n.t('settings.personal.tools.addConnection.label')}
class="flex size-7 shrink-0 items-center justify-center rounded-lg text-gray-400 transition-colors hover:bg-black/5 hover:text-gray-900 dark:text-gray-600 dark:hover:bg-white/5 dark:hover:text-white"
on:click={() => (showConnectionModal = true)}
type="button"

View file

@ -60,20 +60,18 @@
<div class="flex justify-between items-start gap-3 mb-3">
<div class="min-w-0">
<h3 id="terminal-connections-heading" class="text-xs text-gray-600 dark:text-gray-400">
{$i18n.t('Open Terminal')}
{$i18n.t('settings.personal.tools.terminals.sections.openTerminal.title')}
</h3>
<p class="mt-1 text-[0.6875rem] leading-relaxed text-gray-400 dark:text-gray-600">
{$i18n.t(
'Connect to Open Terminal instances to browse files and use them as always-on tools. Only one can be active at a time.'
)}
{$i18n.t('settings.personal.tools.terminals.addConnection.description')}
</p>
</div>
<Tooltip content={$i18n.t('Add Connection')}>
<Tooltip content={$i18n.t('settings.personal.tools.terminals.addConnection.label')}>
<button
class="flex size-7 shrink-0 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={() => (showAddModal = true)}
type="button"
aria-label={$i18n.t('Add Connection')}
aria-label={$i18n.t('settings.personal.tools.terminals.addConnection.label')}
>
<Plus />
</button>

View file

@ -19,7 +19,9 @@
dispatch('save');
}}
>
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">{$i18n.t('Interface')}</h2>
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">
{$i18n.t('settings.personal.interface.title')}
</h2>
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5">
<InterfaceSettings bind:this={interfaceSettings} {saveSettings} {personalSettingsValue} />

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { canUseNotificationTargets } from '$lib/utils/settings-access';
import { getContext, onMount } from 'svelte';
import type { Writable } from 'svelte/store';
import { toast } from 'svelte-sonner';
@ -50,9 +51,7 @@
};
let loadedTargets = false;
$: canUseWebhooks =
($config?.features as any)?.enable_user_webhooks &&
($user?.role === 'admin' || ($user?.permissions?.features?.webhooks ?? false));
$: canUseWebhooks = canUseNotificationTargets({ user: $user, config: $config });
$: if (canUseWebhooks && !loadedTargets) {
void loadTargets();
@ -192,17 +191,17 @@
<div id="tab-notifications" class="flex h-full flex-col text-sm">
<div class="flex-1 min-h-0 w-full overflow-y-auto scrollbar-hover pr-1.5">
<h2 class="mb-4 text-sm font-medium text-gray-900 dark:text-white">
{$i18n.t('Notifications')}
{$i18n.t('settings.personal.notifications.title')}
</h2>
<div class="flex flex-col gap-2.5">
<label class="flex cursor-pointer items-center justify-between">
<span class="text-xs text-gray-600 dark:text-gray-400">
{$i18n.t('Browser Notifications')}
{$i18n.t('settings.personal.notifications.browserNotifications.label')}
</span>
<Switch
state={notificationEnabled}
ariaLabel={$i18n.t('Browser Notifications')}
ariaLabel={$i18n.t('settings.personal.notifications.browserNotifications.label')}
on:change={toggleNotifications}
/>
</label>
@ -212,11 +211,11 @@
<label class="flex cursor-pointer items-center justify-between">
<span class="text-xs text-gray-600 dark:text-gray-400">
{$i18n.t('Notification Sound')}
{$i18n.t('settings.personal.notifications.notificationSound.label')}
</span>
<Switch
bind:state={notificationSound}
ariaLabel={$i18n.t('Notification Sound')}
ariaLabel={$i18n.t('settings.personal.notifications.notificationSound.label')}
on:change={() => {
saveSettings({ notificationSound });
}}
@ -226,14 +225,14 @@
{#if canUseWebhooks}
<div class="mt-3 flex items-center justify-between">
<span class="text-xs font-medium text-gray-700 dark:text-gray-300">
{$i18n.t('Notification Targets')}
{$i18n.t('settings.personal.notifications.notificationTargets.label')}
</span>
<button
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"
type="button"
on:click={openNewTarget}
title={$i18n.t('Add Notification Target')}
aria-label={$i18n.t('Add Notification Target')}
title={$i18n.t('settings.personal.notifications.addNotificationTarget.label')}
aria-label={$i18n.t('settings.personal.notifications.addNotificationTarget.label')}
>
<Plus className="size-3.5" strokeWidth="2" />
</button>
@ -345,7 +344,9 @@
<Modal size="sm" bind:show={formOpen}>
<div class="p-4">
<h2 class="mb-3 text-sm font-medium text-gray-900 dark:text-white">
{editingId ? $i18n.t('Edit') : $i18n.t('Add Notification Target')}
{editingId
? $i18n.t('Edit')
: $i18n.t('settings.personal.notifications.addNotificationTarget.label')}
</h2>
<div class="text-[0.625rem] text-gray-400 dark:text-gray-600">

View file

@ -110,20 +110,24 @@
}}
>
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">
{$i18n.t('Personalization')}
{$i18n.t('settings.personal.personalization.title')}
</h2>
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5">
<UserSettingSection title={$i18n.t('Memory')} first>
<UserSettingSection
title={$i18n.t('settings.personal.personalization.sections.memory.title')}
first
>
<UserSettingRow
description={$i18n
.t(
"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you."
)
.replace($i18n.t('Manage'), $i18n.t('Add Memory'))}
.t('settings.personal.personalization.memory.description')
.replace(
$i18n.t('settings.personal.personalization.manageMemories.label'),
$i18n.t('Add Memory')
)}
>
<div slot="label" class="flex items-center gap-2">
{$i18n.t('Memory')}
{$i18n.t('settings.personal.personalization.memory.label')}
<ExperimentalBadge />
</div>
@ -139,7 +143,7 @@
<div>
<div class="mb-1 flex items-center">
<div class="text-xs text-gray-600 dark:text-gray-400">
{$i18n.t('Saved Memories')}
{$i18n.t('settings.personal.personalization.savedMemories.label')}
{#if !loadingMemories}
<span class="ml-1 text-gray-400 dark:text-gray-600">{memories.length}</span>
{/if}
@ -215,7 +219,9 @@
}}
>
<Trash className="size-3.5 shrink-0" strokeWidth="1.5" />
<div class="min-w-0 flex-1 truncate text-left">{$i18n.t('Clear memory')}</div>
<div class="min-w-0 flex-1 truncate text-left">
{$i18n.t('settings.personal.personalization.clearMemory.label')}
</div>
</button>
</DropdownMenu>
</div>
@ -225,7 +231,7 @@
{#if sortedMemories.length === 0}
<div class="min-h-16 text-[0.6875rem] text-gray-400 dark:text-gray-600">
{#if memories.length === 0}
{$i18n.t('Memories accessible by LLMs will be shown here.')}
{$i18n.t('settings.personal.personalization.noResultsFound.description')}
{:else}
{$i18n.t('No results found')}
{/if}

View file

@ -106,14 +106,14 @@
<div id="tab-shortcuts" class="flex h-full flex-col text-sm">
<div class="mb-4 flex items-center justify-between">
<h2 class="text-sm font-medium text-gray-900 dark:text-white">
{$i18n.t('Keyboard')}
{$i18n.t('settings.personal.shortcuts.title')}
</h2>
<button
class="text-xs text-gray-500 transition-colors hover:text-gray-900 dark:text-gray-500 dark:hover:text-white"
on:click={resetBindings}
>
{$i18n.t('Reset Defaults')}
{$i18n.t('settings.personal.shortcuts.resetDefaults.label')}
</button>
</div>
@ -123,7 +123,7 @@
id="enable-keyboard-shortcuts-label"
class="min-w-0 text-xs text-gray-600 dark:text-gray-400"
>
{$i18n.t('Enable Keyboard Shortcuts')}
{$i18n.t('settings.personal.shortcuts.enableKeyboardShortcuts.label')}
</div>
<div class="flex shrink-0 items-center justify-end gap-1.5">
@ -138,14 +138,14 @@
</div>
</div>
<p class="mt-1.5 text-[0.6875rem] text-gray-400 dark:text-gray-600">
{$i18n.t('When disabled, keyboard shortcuts will not trigger any actions.')}
{$i18n.t('settings.personal.shortcuts.enableKeyboardShortcuts.description')}
</p>
</div>
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5">
<div class="flex items-center gap-2 px-1 pb-1">
<span class="flex-1 text-[0.625rem] text-gray-400 dark:text-gray-600">
{$i18n.t('Command')}
{$i18n.t('settings.personal.shortcuts.command.label')}
</span>
<span class="w-[9.5rem] shrink-0 text-right text-[0.625rem] text-gray-400 dark:text-gray-600">
{$i18n.t('Key')}
@ -240,7 +240,7 @@
<span
class="text-[0.5625rem] text-amber-500"
title={$i18n.t('Also bound to {{action}}', {
action: $i18n.t(shortcuts[conflict]?.name ?? conflict)
action: shortcuts[conflict]?.name($i18n.t) ?? conflict
})}>!</span
>
{/if}

View file

@ -271,7 +271,9 @@
<div class="flex h-full min-h-0 flex-col">
<div class="mb-4">
<h2 class="text-sm font-medium text-gray-900 dark:text-white">{$i18n.t('Usage')}</h2>
<h2 class="text-sm font-medium text-gray-900 dark:text-white">
{$i18n.t('settings.personal.usage.title')}
</h2>
</div>
{#if loading}
@ -284,7 +286,7 @@
</div>
{:else}
<div class="scrollbar-hover min-h-0 flex-1 overflow-y-auto pr-1.5">
<UserSettingSection title={$i18n.t('Overview')} first>
<UserSettingSection title={$i18n.t('settings.personal.usage.sections.overview.title')} first>
<div class="grid grid-cols-2 gap-x-6 gap-y-3 md:grid-cols-5">
<div>
<div class="text-sm font-medium text-gray-900 dark:text-white">
@ -406,13 +408,15 @@
</section>
{#if !hasUsage}
<UserSettingSection title={$i18n.t('Activity')}>
<UserSettingSection title={$i18n.t('settings.personal.usage.sections.activity.title')}>
<div class="text-xs text-gray-500 dark:text-gray-400">
{$i18n.t('No usage data found')}
</div>
</UserSettingSection>
{:else}
<UserSettingSection title={$i18n.t('Activity insights')}>
<UserSettingSection
title={$i18n.t('settings.personal.usage.sections.activityInsights.title')}
>
<UserSettingRow label={$i18n.t('Models')}>
<span class="text-xs text-gray-900 dark:text-white">
{usage.totals.models_used.toLocaleString()}
@ -446,7 +450,7 @@
</UserSettingRow>
</UserSettingSection>
<UserSettingSection title={$i18n.t('Top models')}>
<UserSettingSection title={$i18n.t('settings.personal.usage.sections.topModels.title')}>
{#if usage.top_models.length === 0}
<div class="text-xs text-gray-500 dark:text-gray-400">
{$i18n.t('No model usage found')}
@ -464,7 +468,9 @@
</UserSettingSection>
{#if usage.top_tools.length > 0}
<UserSettingSection title={$i18n.t('Most used tools')}>
<UserSettingSection
title={$i18n.t('settings.personal.usage.sections.mostUsedTools.title')}
>
{#each usage.top_tools as tool}
<UserSettingRow label={tool.name}>
<span class="text-xs text-gray-500 dark:text-gray-400">

File diff suppressed because it is too large Load diff

View file

@ -123,12 +123,12 @@
{#if shortcut.tooltip}
<Tooltip content={$i18n.t(shortcut.tooltip)}>
<span class="inline-flex max-w-full items-baseline gap-1">
<span class="truncate whitespace-pre-line">{$i18n.t(shortcut.name)}</span>
<span class="truncate whitespace-pre-line">{shortcut.name($i18n.t)}</span>
<span class="text-[0.625rem] text-gray-400 dark:text-gray-600">*</span>
</span>
</Tooltip>
{:else}
<span class="whitespace-pre-line">{$i18n.t(shortcut.name)}</span>
<span class="whitespace-pre-line">{shortcut.name($i18n.t)}</span>
{/if}
</div>
{#if !compact}

View file

@ -1,4 +1,5 @@
<script lang="ts">
import { canUseTemporaryChats, isSettingsAdmin } from '$lib/utils/settings-access';
import { config, settings, user } from '$lib/stores';
import { onMount, getContext } from 'svelte';
import { toast } from 'svelte-sonner';
@ -458,12 +459,12 @@
/>
<div class="flex flex-col gap-2.5">
<h3 class={firstSectionHeadingClass}>{$i18n.t('UI')}</h3>
<h3 class={firstSectionHeadingClass}>{$i18n.t('settings.personal.interface.sections.ui.title')}</h3>
<div>
<div class={settingRowClass}>
<label id="ui-scale-label" class={settingLabelClass} for="ui-scale-slider">
{$i18n.t('UI Scale')}
{$i18n.t('settings.personal.interface.uiScale.label')}
</label>
<div class={settingControlClass}>
@ -504,7 +505,7 @@
setTextScaleHandler(textScale);
}}
aria-labelledby="ui-scale-label"
aria-label={$i18n.t('Decrease UI Scale')}
aria-label={$i18n.t('settings.personal.interface.decreaseUiScale.label')}
>
<Minus className="h-3.5 w-3.5" />
</button>
@ -537,7 +538,7 @@
setTextScaleHandler(textScale);
}}
aria-labelledby="ui-scale-label"
aria-label={$i18n.t('Increase UI Scale')}
aria-label={$i18n.t('settings.personal.interface.increaseUiScale.label')}
>
<Plus className="h-3.5 w-3.5" />
</button>
@ -551,7 +552,7 @@
<div>
<div class={settingRowClass}>
<label id="font-family-label" class={settingLabelClass} for="font-family-input">
{$i18n.t('Font Family')}
{$i18n.t('settings.personal.interface.fontFamily.label')}
</label>
<div class={settingControlClass}>
@ -584,14 +585,14 @@
</div>
{/if}
<p class={settingDescriptionClass}>
{$i18n.t('Use a local font family for the app interface.')}
{$i18n.t('settings.personal.interface.fontFamily.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="accessibility-mode-label" class={settingLabelClass}>
{$i18n.t('Accessibility Mode')}
{$i18n.t('settings.personal.interface.accessibilityMode.label')}
</div>
<div class={settingControlClass}>
@ -607,14 +608,14 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Enable accessibility-focused visual enhancements.')}
{$i18n.t('settings.personal.interface.accessibilityMode.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="use-chat-title-as-tab-title-label" class={settingLabelClass}>
{$i18n.t('Display Chat Title in Tab')}
{$i18n.t('settings.personal.interface.displayChatTitleInTab.label')}
</div>
<div class={settingControlClass}>
@ -630,13 +631,15 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Use the active chat title as the browser tab title.')}
{$i18n.t('settings.personal.interface.displayChatTitleInTab.description')}
</p>
</div>
<div>
<div id="allow-user-location-label" class={settingRowClass}>
<div class={settingLabelClass}>{$i18n.t('Allow User Location')}</div>
<div class={settingLabelClass}>
{$i18n.t('settings.personal.interface.allowUserLocation.label')}
</div>
<div class={settingControlClass}>
<Switch
@ -651,14 +654,16 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Share your current location with features that can use it.')}
{$i18n.t('settings.personal.interface.allowUserLocation.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="haptic-feedback-label" class={settingLabelClass}>
{$i18n.t('Haptic Feedback')} ({$i18n.t('Android')})
{$i18n.t('settings.personal.interface.hapticFeedback.label')} ({$i18n.t(
'settings.personal.interface.android.label'
)})
</div>
<div class={settingControlClass}>
@ -681,7 +686,7 @@
<div>
<div class={settingRowClass}>
<div id="copy-formatted-label" class={settingLabelClass}>
{$i18n.t('Copy Formatted Text')}
{$i18n.t('settings.personal.interface.copyFormattedText.label')}
</div>
<div class={settingControlClass}>
@ -697,15 +702,15 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Copy rich formatted content instead of plain text.')}
{$i18n.t('settings.personal.interface.copyFormattedText.description')}
</p>
</div>
{#if $user?.role === 'admin'}
{#if isSettingsAdmin({ user: $user, config: $config })}
<div>
<div class={settingRowClass}>
<div id="toast-notifications-label" class={settingLabelClass}>
{$i18n.t('Toast Notifications for New Updates')}
{$i18n.t('settings.personal.interface.toastNotificationsForNewUpdates.label')}
</div>
<div class={settingControlClass}>
@ -721,7 +726,7 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Show update toasts to admins when new versions are available.')}
{$i18n.t('settings.personal.interface.toastNotificationsForNewUpdates.description')}
</p>
</div>
@ -749,12 +754,12 @@
</div>
{/if}
<div class={sectionHeadingClass}>{$i18n.t('Chat')}</div>
<div class={sectionHeadingClass}>{$i18n.t('settings.personal.interface.sections.chat.title')}</div>
<div>
<div class={settingRowClass}>
<div id="enable-message-queue-label" class={settingLabelClass}>
{$i18n.t('Enable Message Queue')}
{$i18n.t('settings.personal.interface.enableMessageQueue.label')}
</div>
<div class={settingControlClass}>
@ -770,14 +775,14 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Queue outgoing messages instead of interrupting active responses.')}
{$i18n.t('settings.personal.interface.enableMessageQueue.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="chat-direction-label" class={settingLabelClass}>
{$i18n.t('Chat Direction')}
{$i18n.t('settings.personal.interface.chatDirection.label')}
</div>
<button
@ -796,14 +801,14 @@
</button>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Choose automatic, left-to-right, or right-to-left text flow.')}
{$i18n.t('settings.personal.interface.chatDirection.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="landing-page-mode-label" class={settingLabelClass}>
{$i18n.t('Landing Page Mode')}
{$i18n.t('settings.personal.interface.landingPageMode.label')}
</div>
<button
@ -815,19 +820,21 @@
type="button"
>
<span id="notification-sound-state"
>{landingPageMode === '' ? $i18n.t('Default') : $i18n.t('Chat')}</span
>{landingPageMode === ''
? $i18n.t('Default')
: $i18n.t('settings.personal.interface.sections.chat.title')}</span
>
</button>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Choose whether the app opens to the default home or chat view.')}
{$i18n.t('settings.personal.interface.landingPageMode.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="chat-background-label" class={settingLabelClass}>
{$i18n.t('Chat Background Image')}
{$i18n.t('settings.personal.interface.chatBackgroundImage.label')}
</div>
<button
@ -849,14 +856,14 @@
</button>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Upload or reset the image shown behind chat content.')}
{$i18n.t('settings.personal.interface.chatBackgroundImage.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="chat-bubble-ui-label" class={settingLabelClass}>
{$i18n.t('Chat Bubble UI')}
{$i18n.t('settings.personal.interface.chatBubbleUi.label')}
</div>
<div class={settingControlClass}>
@ -872,7 +879,7 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Render messages in compact bubble containers.')}
{$i18n.t('settings.personal.interface.chatBubbleUi.description')}
</p>
</div>
@ -880,7 +887,7 @@
<div>
<div class={settingRowClass}>
<div id="chat-bubble-username-label" class={settingLabelClass}>
{$i18n.t('Display the Username Instead of You in the Chat')}
{$i18n.t('settings.personal.interface.displayTheUsernameInsteadOfYouInTheChat.label')}
</div>
<div class={settingControlClass}>
@ -896,7 +903,7 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Show your username label instead of You in chat bubbles.')}
{$i18n.t('settings.personal.interface.displayTheUsernameInsteadOfYouInTheChat.description')}
</p>
</div>
{/if}
@ -904,7 +911,7 @@
<div>
<div class={settingRowClass}>
<div id="widescreen-mode-label" class={settingLabelClass}>
{$i18n.t('Widescreen Mode')}
{$i18n.t('settings.personal.interface.widescreenMode.label')}
</div>
<div class={settingControlClass}>
@ -920,15 +927,15 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Use a wider chat layout on large displays.')}
{$i18n.t('settings.personal.interface.widescreenMode.description')}
</p>
</div>
{#if $user?.role === 'admin' || $user?.permissions?.chat?.temporary}
{#if canUseTemporaryChats({ user: $user, config: $config })}
<div>
<div class={settingRowClass}>
<div id="temp-chat-default-label" class={settingLabelClass}>
{$i18n.t('Temporary Chat by Default')}
{$i18n.t('settings.personal.interface.temporaryChatByDefault.label')}
</div>
<div class={settingControlClass}>
@ -944,7 +951,7 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Start new chats as temporary unless changed.')}
{$i18n.t('settings.personal.interface.temporaryChatByDefault.description')}
</p>
</div>
{/if}
@ -952,7 +959,7 @@
<div>
<div class={settingRowClass}>
<div id="fade-streaming-label" class={settingLabelClass}>
{$i18n.t('Fade Effect for Streaming Text')}
{$i18n.t('settings.personal.interface.fadeEffectForStreamingText.label')}
</div>
<div class={settingControlClass}>
@ -968,14 +975,14 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Fade streaming text as it arrives.')}
{$i18n.t('settings.personal.interface.fadeEffectForStreamingText.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="render-markdown-user-label" class={settingLabelClass}>
{$i18n.t('Render Markdown in User Messages')}
{$i18n.t('settings.personal.interface.renderMarkdownInUserMessages.label')}
</div>
<div class={settingControlClass}>
@ -991,14 +998,14 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Format Markdown syntax in your own messages.')}
{$i18n.t('settings.personal.interface.renderMarkdownInUserMessages.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="render-markdown-assistant-label" class={settingLabelClass}>
{$i18n.t('Render Markdown in Assistant Messages')}
{$i18n.t('settings.personal.interface.renderMarkdownInAssistantMessages.label')}
</div>
<div class={settingControlClass}>
@ -1014,14 +1021,14 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Format Markdown syntax in assistant responses.')}
{$i18n.t('settings.personal.interface.renderMarkdownInAssistantMessages.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="render-markdown-in-previews-label" class={settingLabelClass}>
{$i18n.t('Render Markdown in Previews')}
{$i18n.t('settings.personal.interface.renderMarkdownInPreviews.label')}
</div>
<div class={settingControlClass}>
@ -1037,14 +1044,14 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Format Markdown in previews and compact content surfaces.')}
{$i18n.t('settings.personal.interface.renderMarkdownInPreviews.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="auto-generation-label" class={settingLabelClass}>
{$i18n.t('Title Auto-Generation')}
{$i18n.t('settings.personal.interface.titleAutoGeneration.label')}
</div>
<div class={settingControlClass}>
@ -1060,14 +1067,14 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Generate chat titles automatically from conversation content.')}
{$i18n.t('settings.personal.interface.titleAutoGeneration.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div class={settingLabelClass} id="follow-up-auto-generation-label">
{$i18n.t('Follow-Up Auto-Generation')}
{$i18n.t('settings.personal.interface.followUpAutoGeneration.label')}
</div>
<div class={settingControlClass}>
@ -1083,14 +1090,14 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Generate suggested follow-up prompts after responses.')}
{$i18n.t('settings.personal.interface.followUpAutoGeneration.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="chat-tags-label" class={settingLabelClass}>
{$i18n.t('Chat Tags Auto-Generation')}
{$i18n.t('settings.personal.interface.chatTagsAutoGeneration.label')}
</div>
<div class={settingControlClass}>
@ -1106,14 +1113,14 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Generate tags for chats automatically.')}
{$i18n.t('settings.personal.interface.chatTagsAutoGeneration.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="auto-copy-label" class={settingLabelClass}>
{$i18n.t('Auto-Copy Response to Clipboard')}
{$i18n.t('settings.personal.interface.autoCopyResponseToClipboard.label')}
</div>
<div class={settingControlClass}>
@ -1129,14 +1136,14 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Copy the latest assistant response when it completes.')}
{$i18n.t('settings.personal.interface.autoCopyResponseToClipboard.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="response-auto-scroll-label" class={settingLabelClass}>
{$i18n.t('Response Auto-Scroll')}
{$i18n.t('settings.personal.interface.responseAutoScroll.label')}
</div>
<div class={settingControlClass}>
@ -1152,14 +1159,14 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Follow assistant responses as they are generated.')}
{$i18n.t('settings.personal.interface.responseAutoScroll.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="scroll-on-branch-change-label" class={settingLabelClass}>
{$i18n.t('Scroll On Branch Change')}
{$i18n.t('settings.personal.interface.scrollOnBranchChange.label')}
</div>
<div class={settingControlClass}>
@ -1175,14 +1182,14 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Scroll to the active branch when switching response branches.')}
{$i18n.t('settings.personal.interface.scrollOnBranchChange.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="insert-suggestion-prompt-label" class={settingLabelClass}>
{$i18n.t('Insert Suggestion Prompt to Input')}
{$i18n.t('settings.personal.interface.insertSuggestionPromptToInput.label')}
</div>
<div class={settingControlClass}>
@ -1198,14 +1205,14 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Place selected suggestion text into the composer.')}
{$i18n.t('settings.personal.interface.insertSuggestionPromptToInput.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="keep-follow-up-prompts-label" class={settingLabelClass}>
{$i18n.t('Keep Follow-Up Prompts in Chat')}
{$i18n.t('settings.personal.interface.keepFollowUpPromptsInChat.label')}
</div>
<div class={settingControlClass}>
@ -1221,14 +1228,14 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Keep generated follow-up prompts visible in the chat.')}
{$i18n.t('settings.personal.interface.keepFollowUpPromptsInChat.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="insert-follow-up-prompt-label" class={settingLabelClass}>
{$i18n.t('Insert Follow-Up Prompt to Input')}
{$i18n.t('settings.personal.interface.insertFollowUpPromptToInput.label')}
</div>
<div class={settingControlClass}>
@ -1244,14 +1251,14 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Insert selected follow-up prompts directly into the composer.')}
{$i18n.t('settings.personal.interface.insertFollowUpPromptToInput.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="regenerate-menu-label" class={settingLabelClass}>
{$i18n.t('Regenerate Menu')}
{$i18n.t('settings.personal.interface.regenerateMenu.label')}
</div>
<div class={settingControlClass}>
@ -1267,14 +1274,14 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Show the regenerate action menu for assistant responses.')}
{$i18n.t('settings.personal.interface.regenerateMenu.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="always-collapse-label" class={settingLabelClass}>
{$i18n.t('Always Collapse Code Blocks')}
{$i18n.t('settings.personal.interface.alwaysCollapseCodeBlocks.label')}
</div>
<div class={settingControlClass}>
@ -1290,14 +1297,14 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Collapse code blocks by default.')}
{$i18n.t('settings.personal.interface.alwaysCollapseCodeBlocks.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="always-expand-label" class={settingLabelClass}>
{$i18n.t('Always Expand Details')}
{$i18n.t('settings.personal.interface.alwaysExpandDetails.label')}
</div>
<div class={settingControlClass}>
@ -1313,14 +1320,14 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Open detail blocks by default.')}
{$i18n.t('settings.personal.interface.alwaysExpandDetails.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="chat-hover-preview-label" class={settingLabelClass}>
{$i18n.t('Chat Hover Previews')}
{$i18n.t('settings.personal.interface.chatHoverPreviews.label')}
</div>
<div class={settingControlClass}>
@ -1336,14 +1343,14 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Show a floating preview of recent messages when hovering a chat in the sidebar.')}
{$i18n.t('settings.personal.interface.chatHoverPreviews.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="keep-followup-prompts-label" class={settingLabelClass}>
{$i18n.t('Display Multi-model Responses in Tabs')}
{$i18n.t('settings.personal.interface.displayMultiModelResponsesInTabs.label')}
</div>
<div class={settingControlClass}>
@ -1359,14 +1366,14 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Group multi-model responses into tabs.')}
{$i18n.t('settings.personal.interface.displayMultiModelResponsesInTabs.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="terminal-file-display-label" class={settingLabelClass}>
{$i18n.t('Terminal File Display')}
{$i18n.t('settings.personal.interface.terminalFileDisplay.label')}
</div>
<button
@ -1384,14 +1391,14 @@
</button>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Choose where terminal display_file results appear by default.')}
{$i18n.t('settings.personal.interface.terminalFileDisplay.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="show-files-on-terminal-select-label" class={settingLabelClass}>
{$i18n.t('Show Files on Terminal Select')}
{$i18n.t('settings.personal.interface.showFilesOnTerminalSelect.label')}
</div>
<div class={settingControlClass}>
@ -1407,14 +1414,14 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Open the file browser after selecting a terminal.')}
{$i18n.t('settings.personal.interface.showFilesOnTerminalSelect.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="terminal-preview-allow-same-origin-label" class={settingLabelClass}>
{$i18n.t('Terminal Preview Allow Same Origin')}
{$i18n.t('settings.personal.interface.terminalPreviewAllowSameOrigin.label')}
</div>
<div class={settingControlClass}>
@ -1430,14 +1437,14 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Allow terminal previews to access same-origin browser APIs.')}
{$i18n.t('settings.personal.interface.terminalPreviewAllowSameOrigin.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="stylized-pdf-export-label" class={settingLabelClass}>
{$i18n.t('Stylized PDF Export')}
{$i18n.t('settings.personal.interface.stylizedPdfExport.label')}
</div>
<div class={settingControlClass}>
@ -1453,14 +1460,14 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Use styled formatting when exporting chats to PDF.')}
{$i18n.t('settings.personal.interface.stylizedPdfExport.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="floating-action-buttons-label" class={settingLabelClass}>
{$i18n.t('Floating Quick Actions')}
{$i18n.t('settings.personal.interface.floatingQuickActions.label')}
</div>
<div class={settingControlClass}>
@ -1468,7 +1475,9 @@
<button
class={actionButtonClass}
type="button"
aria-label={$i18n.t('Open Modal To Manage Floating Quick Actions')}
aria-label={$i18n.t(
'settings.personal.interface.openModalToManageFloatingQuickActions.label'
)}
on:click={() => {
showManageFloatingActionButtonsModal = true;
}}
@ -1496,7 +1505,7 @@
<div>
<div class={settingRowClass}>
<div id="web-search-in-chat-label" class={settingLabelClass}>
{$i18n.t('Web Search in Chat')}
{$i18n.t('settings.personal.interface.webSearchInChat.label')}
</div>
<button
@ -1513,16 +1522,16 @@
</button>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Set web search availability for new chats.')}
{$i18n.t('settings.personal.interface.webSearchInChat.description')}
</p>
</div>
<div class={sectionHeadingClass}>{$i18n.t('Input')}</div>
<div class={sectionHeadingClass}>{$i18n.t('settings.personal.interface.sections.input.title')}</div>
<div>
<div class={settingRowClass}>
<div id="enter-key-behavior-label ctrl-enter-to-send-state" class={settingLabelClass}>
{$i18n.t('Enter Key Behavior')}
{$i18n.t('settings.personal.interface.enterKeyBehavior.label')}
</div>
<button
@ -1541,14 +1550,14 @@
</button>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Choose whether Enter sends immediately or uses Ctrl+Enter.')}
{$i18n.t('settings.personal.interface.enterKeyBehavior.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="rich-input-label" class={settingLabelClass}>
{$i18n.t('Rich Text Input for Chat')}
{$i18n.t('settings.personal.interface.richTextInputForChat.label')}
</div>
<div class={settingControlClass}>
@ -1564,7 +1573,7 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Use the rich composer instead of a plain textarea.')}
{$i18n.t('settings.personal.interface.richTextInputForChat.description')}
</p>
</div>
@ -1572,7 +1581,7 @@
<div>
<div class={settingRowClass}>
<div id="prompt-autocompletion-label" class={settingLabelClass}>
{$i18n.t('Prompt Autocompletion')}
{$i18n.t('settings.personal.interface.promptAutocompletion.label')}
</div>
<div class={settingControlClass}>
@ -1588,7 +1597,7 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Suggest completions while composing prompts.')}
{$i18n.t('settings.personal.interface.promptAutocompletion.description')}
</p>
</div>
{/if}
@ -1597,7 +1606,7 @@
<div>
<div class={settingRowClass}>
<div id="show-formatting-toolbar-label" class={settingLabelClass}>
{$i18n.t('Show Formatting Toolbar')}
{$i18n.t('settings.personal.interface.showFormattingToolbar.label')}
</div>
<div class={settingControlClass}>
@ -1613,14 +1622,14 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Show formatting controls in the rich text composer.')}
{$i18n.t('settings.personal.interface.showFormattingToolbar.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="insert-prompt-as-rich-text-label" class={settingLabelClass}>
{$i18n.t('Insert Prompt as Rich Text')}
{$i18n.t('settings.personal.interface.insertPromptAsRichText.label')}
</div>
<div class={settingControlClass}>
@ -1636,7 +1645,7 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Paste inserted prompts as rich text when possible.')}
{$i18n.t('settings.personal.interface.insertPromptAsRichText.description')}
</p>
</div>
{/if}
@ -1644,7 +1653,7 @@
<div>
<div class={settingRowClass}>
<div id="paste-large-label" class={settingLabelClass}>
{$i18n.t('Paste Large Text as File')}
{$i18n.t('settings.personal.interface.pasteLargeTextAsFile.label')}
</div>
<div class={settingControlClass}>
@ -1660,16 +1669,18 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Convert long pasted text into a file attachment.')}
{$i18n.t('settings.personal.interface.pasteLargeTextAsFile.description')}
</p>
</div>
<div class={sectionHeadingClass}>{$i18n.t('Artifacts')}</div>
<div class={sectionHeadingClass}>
{$i18n.t('settings.personal.interface.sections.artifacts.title')}
</div>
<div>
<div class={settingRowClass}>
<div id="detect-artifacts-label" class={settingLabelClass}>
{$i18n.t('Detect Artifacts Automatically')}
{$i18n.t('settings.personal.interface.detectArtifactsAutomatically.label')}
</div>
<div class={settingControlClass}>
@ -1685,14 +1696,14 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Detect generated artifacts and show them in the artifact workspace.')}
{$i18n.t('settings.personal.interface.detectArtifactsAutomatically.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="iframe-sandbox-allow-scripts-label" class={settingLabelClass}>
{$i18n.t('iframe Sandbox Allow Scripts')}
{$i18n.t('settings.personal.interface.iframeSandboxAllowScripts.label')}
</div>
<div class={settingControlClass}>
@ -1708,14 +1719,14 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Allow scripts inside sandboxed iframes.')}
{$i18n.t('settings.personal.interface.iframeSandboxAllowScripts.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="iframe-sandbox-allow-same-origin-label" class={settingLabelClass}>
{$i18n.t('iframe Sandbox Allow Same Origin')}
{$i18n.t('settings.personal.interface.iframeSandboxAllowSameOrigin.label')}
</div>
<div class={settingControlClass}>
@ -1731,14 +1742,14 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Allow artifacts to access same-origin browser APIs inside the sandbox.')}
{$i18n.t('settings.personal.interface.iframeSandboxAllowSameOrigin.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="iframe-sandbox-allow-forms-label" class={settingLabelClass}>
{$i18n.t('iframe Sandbox Allow Forms')}
{$i18n.t('settings.personal.interface.iframeSandboxAllowForms.label')}
</div>
<div class={settingControlClass}>
@ -1754,14 +1765,14 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Allow forms inside sandboxed artifact iframes.')}
{$i18n.t('settings.personal.interface.iframeSandboxAllowForms.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="iframe-sandbox-allow-downloads-label" class={settingLabelClass}>
{$i18n.t('iframe Sandbox Allow Downloads')}
{$i18n.t('settings.personal.interface.iframeSandboxAllowDownloads.label')}
</div>
<div class={settingControlClass}>
@ -1777,16 +1788,16 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Allow downloads inside sandboxed iframes.')}
{$i18n.t('settings.personal.interface.iframeSandboxAllowDownloads.description')}
</p>
</div>
<div class={sectionHeadingClass}>{$i18n.t('Voice')}</div>
<div class={sectionHeadingClass}>{$i18n.t('settings.personal.interface.sections.voice.title')}</div>
<div>
<div class={settingRowClass}>
<div class={settingLabelClass} id="allow-voice-interruption-in-call-label">
{$i18n.t('Allow Voice Interruption in Call')}
{$i18n.t('settings.personal.interface.allowVoiceInterruptionInCall.label')}
</div>
<div class={settingControlClass}>
@ -1802,14 +1813,14 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Let speech interrupt the assistant during a voice call.')}
{$i18n.t('settings.personal.interface.allowVoiceInterruptionInCall.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="display-emoji-label" class={settingLabelClass}>
{$i18n.t('Display Emoji in Call')}
{$i18n.t('settings.personal.interface.displayEmojiInCall.label')}
</div>
<div class={settingControlClass}>
@ -1825,16 +1836,16 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Show emoji feedback in the call interface.')}
{$i18n.t('settings.personal.interface.displayEmojiInCall.description')}
</p>
</div>
<div class={sectionHeadingClass}>{$i18n.t('File')}</div>
<div class={sectionHeadingClass}>{$i18n.t('settings.personal.interface.sections.file.title')}</div>
<div>
<div class={settingRowClass}>
<div id="default-upload-mode-label" class={settingLabelClass}>
{$i18n.t('Default Upload Mode')}
{$i18n.t('settings.personal.interface.defaultUploadMode.label')}
</div>
<button
@ -1854,14 +1865,14 @@
</button>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Attach files with full content or focused retrieval by default.')}
{$i18n.t('settings.personal.interface.defaultUploadMode.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="image-compression-label" class={settingLabelClass}>
{$i18n.t('Image Compression')}
{$i18n.t('settings.personal.interface.imageCompression.label')}
</div>
<div class={settingControlClass}>
@ -1869,7 +1880,9 @@
<button
class={actionButtonClass}
type="button"
aria-label={$i18n.t('Open Modal To Manage Image Compression')}
aria-label={$i18n.t(
'settings.personal.interface.openModalToManageImageCompression.label'
)}
on:click={() => {
showManageImageCompressionModal = true;
}}
@ -1898,7 +1911,7 @@
<div>
<div class={settingRowClass}>
<div id="image-compression-in-channels-label" class={settingLabelClass}>
{$i18n.t('Compress Images in Channels')}
{$i18n.t('settings.personal.interface.compressImagesInChannels.label')}
</div>
<div class={settingControlClass}>
@ -1914,7 +1927,7 @@
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Apply image compression to channel uploads too.')}
{$i18n.t('settings.personal.interface.compressImagesInChannels.description')}
</p>
</div>
{/if}

View file

@ -8,70 +8,71 @@
const i18n: Writable<i18nType> = getContext('i18n');
const toolLabels = {
let toolLabels;
$: toolLabels = {
time: {
label: $i18n.t('Time & Calculation'),
description: $i18n.t('Get current time and perform date/time calculations')
label: $i18n.t('settings.admin.models.builtinTools.time.label'),
description: $i18n.t('settings.admin.models.builtinTools.time.description')
},
user_input: {
label: $i18n.t('Ask User'),
description: $i18n.t('Pause a response to ask the user a clarifying question')
label: $i18n.t('settings.admin.models.builtinTools.userInput.label'),
description: $i18n.t('settings.admin.models.builtinTools.userInput.description')
},
memory: {
label: $i18n.t('Memory'),
description: $i18n.t('Search and manage user memories')
label: $i18n.t('settings.admin.models.builtinTools.memory.label'),
description: $i18n.t('settings.admin.models.builtinTools.memory.description')
},
chats: {
label: $i18n.t('Chat History'),
description: $i18n.t('Search and view user chat history')
label: $i18n.t('settings.admin.models.builtinTools.chats.label'),
description: $i18n.t('settings.admin.models.builtinTools.chats.description')
},
notes: {
label: $i18n.t('Notes'),
description: $i18n.t('Search, view, and manage user notes')
label: $i18n.t('settings.admin.models.builtinTools.notes.label'),
description: $i18n.t('settings.admin.models.builtinTools.notes.description')
},
knowledge: {
label: $i18n.t('Knowledge Base'),
description: $i18n.t('Browse and query knowledge bases')
label: $i18n.t('settings.admin.models.builtinTools.knowledge.label'),
description: $i18n.t('settings.admin.models.builtinTools.knowledge.description')
},
files: {
label: $i18n.t('Files'),
description: $i18n.t('List, search, and read files attached to the current chat')
label: $i18n.t('settings.admin.models.builtinTools.files.label'),
description: $i18n.t('settings.admin.models.builtinTools.files.description')
},
channels: {
label: $i18n.t('Channels'),
description: $i18n.t('Search channels and channel messages')
label: $i18n.t('settings.admin.models.builtinTools.channels.label'),
description: $i18n.t('settings.admin.models.builtinTools.channels.description')
},
notifications: {
label: $i18n.t('Notifications'),
description: $i18n.t('Send notifications to configured webhook targets')
label: $i18n.t('settings.admin.models.builtinTools.notifications.label'),
description: $i18n.t('settings.admin.models.builtinTools.notifications.description')
},
web_search: {
label: $i18n.t('Web Search'),
description: $i18n.t('Search the web and fetch URLs')
label: $i18n.t('settings.admin.models.builtinTools.webSearch.label'),
description: $i18n.t('settings.admin.models.builtinTools.webSearch.description')
},
image_generation: {
label: $i18n.t('Image Generation'),
description: $i18n.t('Generate and edit images')
label: $i18n.t('settings.admin.models.builtinTools.imageGeneration.label'),
description: $i18n.t('settings.admin.models.builtinTools.imageGeneration.description')
},
code_interpreter: {
label: $i18n.t('Code Interpreter'),
description: $i18n.t('Execute code')
label: $i18n.t('settings.admin.models.builtinTools.codeInterpreter.label'),
description: $i18n.t('settings.admin.models.builtinTools.codeInterpreter.description')
},
tasks: {
label: $i18n.t('Task Management'),
description: $i18n.t('Break down complex requests into trackable steps')
label: $i18n.t('settings.admin.models.builtinTools.tasks.label'),
description: $i18n.t('settings.admin.models.builtinTools.tasks.description')
},
automations: {
label: $i18n.t('Automations'),
description: $i18n.t('Create and manage scheduled automations')
label: $i18n.t('settings.admin.models.builtinTools.automations.label'),
description: $i18n.t('settings.admin.models.builtinTools.automations.description')
},
calendar: {
label: $i18n.t('Calendar'),
description: $i18n.t('List calendars, search, create, update, and delete calendar events')
label: $i18n.t('settings.admin.models.builtinTools.calendar.label'),
description: $i18n.t('settings.admin.models.builtinTools.calendar.description')
},
subagents: {
label: $i18n.t('Sub-agents'),
description: $i18n.t('Delegate focused work to parallel sub-agents')
label: $i18n.t('settings.admin.models.builtinTools.subagents.label'),
description: $i18n.t('settings.admin.models.builtinTools.subagents.description')
}
};
@ -90,7 +91,9 @@
</script>
<div>
<div class="mb-1.5 text-xs text-gray-400 dark:text-gray-600">{$i18n.t('Builtin Tools')}</div>
<div class="mb-1.5 text-xs text-gray-400 dark:text-gray-600">
{$i18n.t('settings.admin.models.builtinTools.title')}
</div>
<div class="grid grid-cols-1 gap-x-5 gap-y-1 sm:grid-cols-2 lg:grid-cols-3">
{#each allTools as tool}
<div class="flex min-h-6 items-center gap-2.5">

View file

@ -8,60 +8,55 @@
const i18n: Writable<i18nType> = getContext('i18n');
const capabilityLabels = {
let capabilityLabels;
$: capabilityLabels = {
vision: {
label: $i18n.t('Vision'),
description: $i18n.t('Model accepts image inputs')
label: $i18n.t('settings.admin.models.capabilities.vision.label'),
description: $i18n.t('settings.admin.models.capabilities.vision.description')
},
file_upload: {
label: $i18n.t('File Upload'),
description: $i18n.t('Model accepts file inputs')
label: $i18n.t('settings.admin.models.capabilities.fileUpload.label'),
description: $i18n.t('settings.admin.models.capabilities.fileUpload.description')
},
file_context: {
label: $i18n.t('File Context'),
description: $i18n.t('Inject file content into conversation context')
label: $i18n.t('settings.admin.models.capabilities.fileContext.label'),
description: $i18n.t('settings.admin.models.capabilities.fileContext.description')
},
web_search: {
label: $i18n.t('Web Search'),
description: $i18n.t('Model can search the web for information')
label: $i18n.t('settings.admin.models.capabilities.webSearch.label'),
description: $i18n.t('settings.admin.models.capabilities.webSearch.description')
},
image_generation: {
label: $i18n.t('Image Generation'),
description: $i18n.t('Model can generate images based on text prompts')
label: $i18n.t('settings.admin.models.capabilities.imageGeneration.label'),
description: $i18n.t('settings.admin.models.capabilities.imageGeneration.description')
},
code_interpreter: {
label: $i18n.t('Code Interpreter'),
description: $i18n.t('Model can execute code and perform calculations')
label: $i18n.t('settings.admin.models.capabilities.codeInterpreter.label'),
description: $i18n.t('settings.admin.models.capabilities.codeInterpreter.description')
},
terminal: {
label: $i18n.t('Terminal'),
description: $i18n.t(
'Model can access Open Terminal for command execution and file management'
)
label: $i18n.t('settings.admin.models.capabilities.terminal.label'),
description: $i18n.t('settings.admin.models.capabilities.terminal.description')
},
usage: {
label: $i18n.t('Usage'),
description: $i18n.t(
'Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.'
)
label: $i18n.t('settings.admin.models.capabilities.usage.label'),
description: $i18n.t('settings.admin.models.capabilities.usage.description')
},
citations: {
label: $i18n.t('Citations'),
description: $i18n.t('Displays citations in the response')
label: $i18n.t('settings.admin.models.capabilities.citations.label'),
description: $i18n.t('settings.admin.models.capabilities.citations.description')
},
status_updates: {
label: $i18n.t('Status Updates'),
description: $i18n.t('Displays status updates (e.g., web search progress) in the response')
label: $i18n.t('settings.admin.models.capabilities.statusUpdates.label'),
description: $i18n.t('settings.admin.models.capabilities.statusUpdates.description')
},
memory: {
label: $i18n.t('Memory'),
description: $i18n.t('Inject stored memories into conversation context')
label: $i18n.t('settings.admin.models.capabilities.memory.label'),
description: $i18n.t('settings.admin.models.capabilities.memory.description')
},
builtin_tools: {
label: $i18n.t('Builtin Tools'),
description: $i18n.t(
'Automatically inject system tools in native function calling mode (e.g., timestamps, memory, chat history, notes, etc.)'
)
label: $i18n.t('settings.admin.models.capabilities.builtinTools.label'),
description: $i18n.t('settings.admin.models.capabilities.builtinTools.description')
}
};
@ -84,7 +79,9 @@
</script>
<div>
<div class="mb-1.5 text-xs text-gray-400 dark:text-gray-600">{$i18n.t('Capabilities')}</div>
<div class="mb-1.5 text-xs text-gray-400 dark:text-gray-600">
{$i18n.t('settings.admin.models.capabilities.title')}
</div>
<div class="grid grid-cols-1 gap-x-5 gap-y-1 sm:grid-cols-2 lg:grid-cols-3">
{#each visibleCapabilities as capability}
<div class="flex min-h-6 items-center gap-2.5">

View file

@ -8,18 +8,19 @@
const i18n: Writable<i18nType> = getContext('i18n');
const featureLabels = {
let featureLabels;
$: featureLabels = {
web_search: {
label: $i18n.t('Web Search'),
description: $i18n.t('Model can search the web for information')
label: $i18n.t('settings.admin.models.defaultFeatures.webSearch.label'),
description: $i18n.t('settings.admin.models.defaultFeatures.webSearch.description')
},
image_generation: {
label: $i18n.t('Image Generation'),
description: $i18n.t('Model can generate images based on text prompts')
label: $i18n.t('settings.admin.models.defaultFeatures.imageGeneration.label'),
description: $i18n.t('settings.admin.models.defaultFeatures.imageGeneration.description')
},
code_interpreter: {
label: $i18n.t('Code Interpreter'),
description: $i18n.t('Model can execute code and perform calculations')
label: $i18n.t('settings.admin.models.defaultFeatures.codeInterpreter.label'),
description: $i18n.t('settings.admin.models.defaultFeatures.codeInterpreter.description')
}
};
@ -42,7 +43,9 @@
</script>
<div>
<div class="mb-1.5 text-xs text-gray-400 dark:text-gray-600">{$i18n.t('Default Features')}</div>
<div class="mb-1.5 text-xs text-gray-400 dark:text-gray-600">
{$i18n.t('settings.admin.models.defaultFeatures.title')}
</div>
<div class="grid grid-cols-1 gap-x-5 gap-y-1 sm:grid-cols-2 lg:grid-cols-3">
{#each availableFeatures as feature}
<div class="flex min-h-6 items-center gap-2.5">

View file

@ -5,15 +5,20 @@ import type { i18n as i18nType } from 'i18next';
import { writable } from 'svelte/store';
import type { I18nOverrides } from '$lib/utils/translationDictionary';
import { assembleSettingsTranslations } from './settings-translations';
let overrides: I18nOverrides = {};
export const loadBundledResource = async (language: string): Promise<Record<string, string>> =>
(await import(`./locales/${language}/translation.json`)).default;
const loadResource = async (language: string) => ({
...(await loadBundledResource(language)),
...overrides[language]
});
const loadResource = async (language: string) =>
assembleSettingsTranslations(
await loadBundledResource(language),
await loadBundledResource('en-US'),
overrides[language],
overrides['en-US']
);
export const updateI18n = async (value: I18nOverrides = {}) => {
overrides = value;

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

Some files were not shown because too many files have changed in this diff Show more