mirror of
https://github.com/open-webui/open-webui.git
synced 2026-08-28 05:27:35 +00:00
refac
This commit is contained in:
parent
6abee5cc3c
commit
b3255a3656
10 changed files with 3529 additions and 4394 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
import SensitiveInput from '$lib/components/common/SensitiveInput.svelte';
|
||||
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
import Textarea from '$lib/components/common/Textarea.svelte';
|
||||
import Switch from '$lib/components/common/Switch.svelte';
|
||||
import AdminSettingField from './AdminSettingField.svelte';
|
||||
|
|
@ -23,8 +22,6 @@
|
|||
'w-full h-7 rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 text-xs text-gray-700 outline-hidden transition-colors placeholder:text-gray-300 focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:placeholder:text-gray-700 dark:focus:border-blue-500';
|
||||
const textareaClass =
|
||||
'w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors placeholder:text-gray-300 focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:placeholder:text-gray-700 dark:focus:border-blue-500';
|
||||
const inlineInputClass =
|
||||
'w-fit h-7 rounded-lg border border-transparent bg-transparent px-2 text-xs text-gray-600 outline-hidden transition-colors hover:bg-black/5 focus:border-blue-400 dark:text-gray-400 dark:hover:bg-white/5 dark:focus:border-blue-500';
|
||||
|
||||
const submitHandler = async () => {
|
||||
const res = await setCodeExecutionConfig(localStorage.token, config);
|
||||
|
|
@ -52,8 +49,11 @@
|
|||
|
||||
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5">
|
||||
{#if config}
|
||||
<AdminSettingSection first>
|
||||
<AdminSettingRow label={$i18n.t('Enable Code Execution')}>
|
||||
<AdminSettingSection title={$i18n.t('Code Execution')} first>
|
||||
<AdminSettingRow
|
||||
label={$i18n.t('Enable Code Execution')}
|
||||
description={$i18n.t('Allow models to run generated code and return execution results.')}
|
||||
>
|
||||
<Switch bind:state={config.ENABLE_CODE_EXECUTION} />
|
||||
</AdminSettingRow>
|
||||
|
||||
|
|
@ -64,7 +64,7 @@
|
|||
? $i18n.t(
|
||||
'Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.'
|
||||
)
|
||||
: ''}
|
||||
: $i18n.t('Choose the runtime used for generated code blocks.')}
|
||||
>
|
||||
<SettingsSelect
|
||||
bind:value={config.CODE_EXECUTION_ENGINE}
|
||||
|
|
@ -79,7 +79,10 @@
|
|||
</AdminSettingRow>
|
||||
|
||||
{#if config.CODE_EXECUTION_ENGINE === 'jupyter'}
|
||||
<AdminSettingField label={$i18n.t('Jupyter URL')}>
|
||||
<AdminSettingField
|
||||
label={$i18n.t('Jupyter URL')}
|
||||
description={$i18n.t('Connect code execution to a Jupyter server endpoint.')}
|
||||
>
|
||||
<input
|
||||
class={inputClass}
|
||||
type="text"
|
||||
|
|
@ -89,7 +92,10 @@
|
|||
/>
|
||||
</AdminSettingField>
|
||||
|
||||
<AdminSettingRow label={$i18n.t('Jupyter Auth')}>
|
||||
<AdminSettingRow
|
||||
label={$i18n.t('Jupyter Auth')}
|
||||
description={$i18n.t('Select how Open WebUI authenticates with the Jupyter server.')}
|
||||
>
|
||||
<SettingsSelect
|
||||
bind:value={config.CODE_EXECUTION_JUPYTER_AUTH}
|
||||
placeholder={$i18n.t('Select an auth method')}
|
||||
|
|
@ -105,6 +111,7 @@
|
|||
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.')}
|
||||
>
|
||||
{#if config.CODE_EXECUTION_JUPYTER_AUTH === 'password'}
|
||||
<SensitiveInput
|
||||
|
|
@ -126,23 +133,27 @@
|
|||
</AdminSettingField>
|
||||
{/if}
|
||||
|
||||
<AdminSettingRow label={$i18n.t('Code Execution Timeout')}>
|
||||
<Tooltip content={$i18n.t('Enter timeout in seconds')}>
|
||||
<input
|
||||
class={inlineInputClass}
|
||||
type="number"
|
||||
bind:value={config.CODE_EXECUTION_JUPYTER_TIMEOUT}
|
||||
placeholder={$i18n.t('e.g. 60')}
|
||||
autocomplete="off"
|
||||
/>
|
||||
</Tooltip>
|
||||
</AdminSettingRow>
|
||||
<AdminSettingField
|
||||
label={$i18n.t('Code Execution Timeout')}
|
||||
description={$i18n.t('Maximum runtime in seconds before execution is stopped.')}
|
||||
>
|
||||
<input
|
||||
class={inputClass}
|
||||
type="number"
|
||||
bind:value={config.CODE_EXECUTION_JUPYTER_TIMEOUT}
|
||||
placeholder={$i18n.t('e.g. 60')}
|
||||
autocomplete="off"
|
||||
/>
|
||||
</AdminSettingField>
|
||||
{/if}
|
||||
{/if}
|
||||
</AdminSettingSection>
|
||||
|
||||
<AdminSettingSection title={$i18n.t('Code Interpreter')}>
|
||||
<AdminSettingRow label={$i18n.t('Enable Code Interpreter')}>
|
||||
<AdminSettingRow
|
||||
label={$i18n.t('Enable Code Interpreter')}
|
||||
description={$i18n.t('Allow models to use the code interpreter tool during chats.')}
|
||||
>
|
||||
<Switch bind:state={config.ENABLE_CODE_INTERPRETER} />
|
||||
</AdminSettingRow>
|
||||
|
||||
|
|
@ -153,7 +164,7 @@
|
|||
? $i18n.t(
|
||||
'Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.'
|
||||
)
|
||||
: ''}
|
||||
: $i18n.t('Choose the runtime used by the code interpreter tool.')}
|
||||
>
|
||||
<SettingsSelect
|
||||
bind:value={config.CODE_INTERPRETER_ENGINE}
|
||||
|
|
@ -168,7 +179,10 @@
|
|||
</AdminSettingRow>
|
||||
|
||||
{#if config.CODE_INTERPRETER_ENGINE === 'jupyter'}
|
||||
<AdminSettingField label={$i18n.t('Jupyter URL')}>
|
||||
<AdminSettingField
|
||||
label={$i18n.t('Jupyter URL')}
|
||||
description={$i18n.t('Connect code interpreter to a Jupyter server endpoint.')}
|
||||
>
|
||||
<input
|
||||
class={inputClass}
|
||||
type="text"
|
||||
|
|
@ -178,7 +192,10 @@
|
|||
/>
|
||||
</AdminSettingField>
|
||||
|
||||
<AdminSettingRow label={$i18n.t('Jupyter Auth')}>
|
||||
<AdminSettingRow
|
||||
label={$i18n.t('Jupyter Auth')}
|
||||
description={$i18n.t('Select how Open WebUI authenticates with the Jupyter server.')}
|
||||
>
|
||||
<SettingsSelect
|
||||
bind:value={config.CODE_INTERPRETER_JUPYTER_AUTH}
|
||||
placeholder={$i18n.t('Select an auth method')}
|
||||
|
|
@ -194,6 +211,7 @@
|
|||
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.')}
|
||||
>
|
||||
{#if config.CODE_INTERPRETER_JUPYTER_AUTH === 'password'}
|
||||
<SensitiveInput
|
||||
|
|
@ -215,32 +233,33 @@
|
|||
</AdminSettingField>
|
||||
{/if}
|
||||
|
||||
<AdminSettingRow label={$i18n.t('Code Execution Timeout')}>
|
||||
<Tooltip content={$i18n.t('Enter timeout in seconds')}>
|
||||
<input
|
||||
class={inlineInputClass}
|
||||
type="number"
|
||||
bind:value={config.CODE_INTERPRETER_JUPYTER_TIMEOUT}
|
||||
placeholder={$i18n.t('e.g. 60')}
|
||||
autocomplete="off"
|
||||
/>
|
||||
</Tooltip>
|
||||
</AdminSettingRow>
|
||||
<AdminSettingField
|
||||
label={$i18n.t('Code Execution Timeout')}
|
||||
description={$i18n.t('Maximum runtime in seconds before execution is stopped.')}
|
||||
>
|
||||
<input
|
||||
class={inputClass}
|
||||
type="number"
|
||||
bind:value={config.CODE_INTERPRETER_JUPYTER_TIMEOUT}
|
||||
placeholder={$i18n.t('e.g. 60')}
|
||||
autocomplete="off"
|
||||
/>
|
||||
</AdminSettingField>
|
||||
{/if}
|
||||
|
||||
<AdminSettingField label={$i18n.t('Code Interpreter Prompt Template')}>
|
||||
<Tooltip
|
||||
content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
|
||||
placement="top-start"
|
||||
>
|
||||
<Textarea
|
||||
className={textareaClass}
|
||||
bind:value={config.CODE_INTERPRETER_PROMPT_TEMPLATE}
|
||||
placeholder={$i18n.t(
|
||||
'Leave empty to use the default prompt, or enter a custom prompt'
|
||||
)}
|
||||
/>
|
||||
</Tooltip>
|
||||
<AdminSettingField
|
||||
label={$i18n.t('Code Interpreter Prompt Template')}
|
||||
description={$i18n.t(
|
||||
'Leave empty to use the default prompt, or enter a custom prompt.'
|
||||
)}
|
||||
>
|
||||
<Textarea
|
||||
className={textareaClass}
|
||||
bind:value={config.CODE_INTERPRETER_PROMPT_TEMPLATE}
|
||||
placeholder={$i18n.t(
|
||||
'Leave empty to use the default prompt, or enter a custom prompt'
|
||||
)}
|
||||
/>
|
||||
</AdminSettingField>
|
||||
{/if}
|
||||
</AdminSettingSection>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,17 +1,18 @@
|
|||
<script lang="ts">
|
||||
import { getModels, getTaskConfig, updateTaskConfig } from '$lib/apis';
|
||||
import { getChatConfig, updateChatConfig } from '$lib/apis/chats';
|
||||
import { config, settings } from '$lib/stores';
|
||||
import { createEventDispatcher, onMount, getContext } from 'svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
import { getBaseModels } from '$lib/apis/models';
|
||||
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
import Switch from '$lib/components/common/Switch.svelte';
|
||||
import Textarea from '$lib/components/common/Textarea.svelte';
|
||||
import Spinner from '$lib/components/common/Spinner.svelte';
|
||||
import SettingsSelect from '$lib/components/common/SettingsSelect.svelte';
|
||||
import AdminSettingField from './AdminSettingField.svelte';
|
||||
import AdminSettingRow from './AdminSettingRow.svelte';
|
||||
import AdminSettingSection from './AdminSettingSection.svelte';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
|
|
@ -52,10 +53,13 @@
|
|||
]);
|
||||
};
|
||||
|
||||
let workspaceModels = null;
|
||||
let baseModels = null;
|
||||
let workspaceModels: any[] = [];
|
||||
let baseModels: any[] = [];
|
||||
|
||||
let models = null;
|
||||
let models: any[] | null = null;
|
||||
$: modelOptions = models ?? [];
|
||||
const inputClass =
|
||||
'w-full h-7 rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 text-xs text-gray-700 outline-hidden transition-colors placeholder:text-gray-300 focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:placeholder:text-gray-700 dark:focus:border-blue-500';
|
||||
const textareaClass =
|
||||
'w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors placeholder:text-gray-300 focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:placeholder:text-gray-700 dark:focus:border-blue-500';
|
||||
|
||||
|
|
@ -69,8 +73,8 @@
|
|||
workspaceModels = await getBaseModels(localStorage.token);
|
||||
baseModels = await getModels(localStorage.token, null, false);
|
||||
|
||||
models = baseModels.map((m) => {
|
||||
const workspaceModel = workspaceModels.find((wm) => wm.id === m.id);
|
||||
models = baseModels.map((m: any) => {
|
||||
const workspaceModel = workspaceModels.find((wm: any) => wm.id === m.id);
|
||||
|
||||
if (workspaceModel) {
|
||||
return {
|
||||
|
|
@ -90,8 +94,9 @@
|
|||
|
||||
console.debug('models', models);
|
||||
} catch (err) {
|
||||
const error = err as { detail?: string; message?: string };
|
||||
console.error('Failed to initialize Interface settings:', err);
|
||||
toast.error(err?.detail ?? err?.message ?? $i18n.t('Failed to load Interface settings'));
|
||||
toast.error(error?.detail ?? error?.message ?? $i18n.t('Failed to load Interface settings'));
|
||||
models = [];
|
||||
}
|
||||
};
|
||||
|
|
@ -113,191 +118,163 @@
|
|||
{$i18n.t('Interface')}
|
||||
</h2>
|
||||
|
||||
<div class=" flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5">
|
||||
<div class="mb-5">
|
||||
<div class=" mb-2 text-xs text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('Tasks')}
|
||||
</div>
|
||||
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5">
|
||||
<AdminSettingSection title={$i18n.t('Tasks')} first>
|
||||
<div>
|
||||
<div class="mb-2">
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400">{$i18n.t('Task Model')}</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.'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class=" mb-2 font-normal flex items-center">
|
||||
<div class=" text-xs mr-1">{$i18n.t('Task Model')}</div>
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
'A task model is used when performing tasks such as generating titles for chats and web search queries'
|
||||
)}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="size-3.5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z"
|
||||
/>
|
||||
</svg>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div class=" mb-2.5 flex w-full gap-2">
|
||||
<div class="flex-1">
|
||||
<div class=" text-xs mb-1">{$i18n.t('Local Task Model')}</div>
|
||||
<SettingsSelect
|
||||
bind:value={taskConfig.TASK_MODEL}
|
||||
className="w-full"
|
||||
placeholder={$i18n.t('Select a model')}
|
||||
on:change={() => {
|
||||
if (taskConfig.TASK_MODEL) {
|
||||
const model = models.find((m) => m.id === taskConfig.TASK_MODEL);
|
||||
if (model) {
|
||||
if (
|
||||
model?.access_grants &&
|
||||
!model.access_grants.some(
|
||||
(g) =>
|
||||
g.principal_type === 'user' &&
|
||||
g.principal_id === '*' &&
|
||||
g.permission === 'read'
|
||||
)
|
||||
) {
|
||||
toast.error(
|
||||
$i18n.t(
|
||||
'This model is not publicly available. Please select another model.'
|
||||
<div class="grid w-full grid-cols-1 gap-2.5 sm:grid-cols-2">
|
||||
<AdminSettingField label={$i18n.t('Local Task Model')}>
|
||||
<SettingsSelect
|
||||
bind:value={taskConfig.TASK_MODEL}
|
||||
className="w-full"
|
||||
placeholder={$i18n.t('Select a model')}
|
||||
on:change={() => {
|
||||
if (taskConfig.TASK_MODEL) {
|
||||
const model = modelOptions.find((m: any) => m.id === taskConfig.TASK_MODEL);
|
||||
if (model) {
|
||||
if (
|
||||
model?.access_grants &&
|
||||
!model.access_grants.some(
|
||||
(g: any) =>
|
||||
g.principal_type === 'user' &&
|
||||
g.principal_id === '*' &&
|
||||
g.permission === 'read'
|
||||
)
|
||||
);
|
||||
) {
|
||||
toast.error(
|
||||
$i18n.t(
|
||||
'This model is not publicly available. Please select another model.'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
taskConfig.TASK_MODEL = model.id;
|
||||
} else {
|
||||
taskConfig.TASK_MODEL = '';
|
||||
}
|
||||
|
||||
taskConfig.TASK_MODEL = model.id;
|
||||
} else {
|
||||
taskConfig.TASK_MODEL = '';
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<option value="" selected>{$i18n.t('Current Model')}</option>
|
||||
{#each models as model}
|
||||
<option value={model.id} class="bg-gray-100 dark:bg-gray-700">
|
||||
{model.name}
|
||||
{model?.connection_type === 'local' ? `(${$i18n.t('Local')})` : ''}
|
||||
</option>
|
||||
{/each}
|
||||
</SettingsSelect>
|
||||
</div>
|
||||
}}
|
||||
>
|
||||
<option value="" selected>{$i18n.t('Current Model')}</option>
|
||||
{#each modelOptions as model}
|
||||
<option value={model.id} class="bg-gray-100 dark:bg-gray-700">
|
||||
{model.name}
|
||||
{model?.connection_type === 'local' ? `(${$i18n.t('Local')})` : ''}
|
||||
</option>
|
||||
{/each}
|
||||
</SettingsSelect>
|
||||
</AdminSettingField>
|
||||
|
||||
<div class="flex-1">
|
||||
<div class=" text-xs mb-1">{$i18n.t('External Task Model')}</div>
|
||||
<SettingsSelect
|
||||
bind:value={taskConfig.TASK_MODEL_EXTERNAL}
|
||||
className="w-full"
|
||||
placeholder={$i18n.t('Select a model')}
|
||||
on:change={() => {
|
||||
if (taskConfig.TASK_MODEL_EXTERNAL) {
|
||||
const model = models.find((m) => m.id === taskConfig.TASK_MODEL_EXTERNAL);
|
||||
if (model) {
|
||||
if (
|
||||
model?.access_grants &&
|
||||
!model.access_grants.some(
|
||||
(g) =>
|
||||
g.principal_type === 'user' &&
|
||||
g.principal_id === '*' &&
|
||||
g.permission === 'read'
|
||||
)
|
||||
) {
|
||||
toast.error(
|
||||
$i18n.t(
|
||||
'This model is not publicly available. Please select another model.'
|
||||
<AdminSettingField label={$i18n.t('External Task Model')}>
|
||||
<SettingsSelect
|
||||
bind:value={taskConfig.TASK_MODEL_EXTERNAL}
|
||||
className="w-full"
|
||||
placeholder={$i18n.t('Select a model')}
|
||||
on:change={() => {
|
||||
if (taskConfig.TASK_MODEL_EXTERNAL) {
|
||||
const model = modelOptions.find(
|
||||
(m: any) => m.id === taskConfig.TASK_MODEL_EXTERNAL
|
||||
);
|
||||
if (model) {
|
||||
if (
|
||||
model?.access_grants &&
|
||||
!model.access_grants.some(
|
||||
(g: any) =>
|
||||
g.principal_type === 'user' &&
|
||||
g.principal_id === '*' &&
|
||||
g.permission === 'read'
|
||||
)
|
||||
);
|
||||
}
|
||||
) {
|
||||
toast.error(
|
||||
$i18n.t(
|
||||
'This model is not publicly available. Please select another model.'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
taskConfig.TASK_MODEL_EXTERNAL = model.id;
|
||||
} else {
|
||||
taskConfig.TASK_MODEL_EXTERNAL = '';
|
||||
taskConfig.TASK_MODEL_EXTERNAL = model.id;
|
||||
} else {
|
||||
taskConfig.TASK_MODEL_EXTERNAL = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<option value="" selected>{$i18n.t('Current Model')}</option>
|
||||
{#each models as model}
|
||||
<option value={model.id} class="bg-gray-100 dark:bg-gray-700">
|
||||
{model.name}
|
||||
{model?.connection_type === 'local' ? `(${$i18n.t('Local')})` : ''}
|
||||
</option>
|
||||
{/each}
|
||||
</SettingsSelect>
|
||||
}}
|
||||
>
|
||||
<option value="" selected>{$i18n.t('Current Model')}</option>
|
||||
{#each modelOptions as model}
|
||||
<option value={model.id} class="bg-gray-100 dark:bg-gray-700">
|
||||
{model.name}
|
||||
{model?.connection_type === 'local' ? `(${$i18n.t('Local')})` : ''}
|
||||
</option>
|
||||
{/each}
|
||||
</SettingsSelect>
|
||||
</AdminSettingField>
|
||||
</div>
|
||||
</div>
|
||||
</AdminSettingSection>
|
||||
|
||||
<div class="mb-2.5 flex w-full items-center justify-between">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Context Compaction')}
|
||||
</div>
|
||||
|
||||
<AdminSettingSection title={$i18n.t('Chat')}>
|
||||
<AdminSettingRow
|
||||
label={$i18n.t('Context Compaction')}
|
||||
description={$i18n.t(
|
||||
'Summarize older chat history when the conversation context grows large.'
|
||||
)}
|
||||
>
|
||||
<Switch bind:state={chatConfig.ENABLE_CONTEXT_COMPACTION} />
|
||||
</div>
|
||||
</AdminSettingRow>
|
||||
|
||||
{#if chatConfig.ENABLE_CONTEXT_COMPACTION}
|
||||
<div class="mb-2.5">
|
||||
<div class=" mb-1 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Token Threshold')}
|
||||
</div>
|
||||
<AdminSettingField
|
||||
label={$i18n.t('Token Threshold')}
|
||||
description={$i18n.t(
|
||||
'Older messages are summarized when estimated context exceeds this token limit.'
|
||||
)}
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
step="1"
|
||||
class={inputClass}
|
||||
bind:value={chatConfig.CONTEXT_COMPACTION_TOKEN_THRESHOLD}
|
||||
/>
|
||||
</AdminSettingField>
|
||||
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
'Older messages are summarized when estimated context exceeds this token limit.'
|
||||
<AdminSettingField
|
||||
label={$i18n.t('Token Cap')}
|
||||
description={$i18n.t(
|
||||
'Model-specific context compaction thresholds cannot exceed this token limit.'
|
||||
)}
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
step="1"
|
||||
class={inputClass}
|
||||
bind:value={chatConfig.CONTEXT_COMPACTION_TOKEN_CAP}
|
||||
/>
|
||||
</AdminSettingField>
|
||||
|
||||
<AdminSettingField
|
||||
label={$i18n.t('Context Compaction Prompt')}
|
||||
description={$i18n.t(
|
||||
'Controls how older messages are rewritten into a running summary.'
|
||||
)}
|
||||
>
|
||||
<Textarea
|
||||
className={textareaClass}
|
||||
bind:value={chatConfig.CONTEXT_COMPACTION_PROMPT_TEMPLATE}
|
||||
placeholder={$i18n.t(
|
||||
'Leave empty to use the default prompt, or enter a custom prompt'
|
||||
)}
|
||||
placement="top-start"
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
step="1"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
bind:value={chatConfig.CONTEXT_COMPACTION_TOKEN_THRESHOLD}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div class="mb-2.5">
|
||||
<div class=" mb-1 text-xs text-gray-600 dark:text-gray-400">{$i18n.t('Token Cap')}</div>
|
||||
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
'Model-specific context compaction thresholds cannot exceed this token limit.'
|
||||
)}
|
||||
placement="top-start"
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
step="1"
|
||||
class="w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
|
||||
bind:value={chatConfig.CONTEXT_COMPACTION_TOKEN_CAP}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div class="mb-2.5">
|
||||
<div class=" mb-1 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Context Compaction Prompt')}
|
||||
</div>
|
||||
|
||||
<Tooltip
|
||||
content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
|
||||
placement="top-start"
|
||||
>
|
||||
<Textarea
|
||||
className={textareaClass}
|
||||
bind:value={chatConfig.CONTEXT_COMPACTION_PROMPT_TEMPLATE}
|
||||
placeholder={$i18n.t(
|
||||
'Leave empty to use the default prompt, or enter a custom prompt'
|
||||
)}
|
||||
/>
|
||||
</Tooltip>
|
||||
<div class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
/>
|
||||
<div class="mt-1 text-[0.6875rem] text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('Available variables')}:
|
||||
<code>{'{{PREVIOUS_SUMMARY}}'}</code>,
|
||||
<code>{'{{COMPACTED_MESSAGES}}'}</code>,
|
||||
|
|
@ -305,245 +282,182 @@
|
|||
<code>{'{{MESSAGES}}'}</code>,
|
||||
<code>{'{{CURRENT_DATE}}'}</code>
|
||||
</div>
|
||||
</div>
|
||||
</AdminSettingField>
|
||||
{/if}
|
||||
</AdminSettingSection>
|
||||
|
||||
<div class="mb-2.5 flex w-full items-center justify-between">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Title Generation')}
|
||||
</div>
|
||||
|
||||
<AdminSettingSection title={$i18n.t('Generation')}>
|
||||
<AdminSettingRow
|
||||
label={$i18n.t('Title Generation')}
|
||||
description={$i18n.t('Allow automatic names for new chats.')}
|
||||
>
|
||||
<Switch bind:state={taskConfig.ENABLE_TITLE_GENERATION} />
|
||||
</div>
|
||||
</AdminSettingRow>
|
||||
|
||||
{#if taskConfig.ENABLE_TITLE_GENERATION}
|
||||
<div class="mb-2.5">
|
||||
<div class=" mb-1 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Title Generation Prompt')}
|
||||
</div>
|
||||
|
||||
<Tooltip
|
||||
content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
|
||||
placement="top-start"
|
||||
>
|
||||
<Textarea
|
||||
className={textareaClass}
|
||||
bind:value={taskConfig.TITLE_GENERATION_PROMPT_TEMPLATE}
|
||||
placeholder={$i18n.t(
|
||||
'Leave empty to use the default prompt, or enter a custom prompt'
|
||||
)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<AdminSettingField
|
||||
label={$i18n.t('Title Generation Prompt')}
|
||||
description={$i18n.t('Shapes the short label generated for each chat.')}
|
||||
>
|
||||
<Textarea
|
||||
className={textareaClass}
|
||||
bind:value={taskConfig.TITLE_GENERATION_PROMPT_TEMPLATE}
|
||||
placeholder={$i18n.t(
|
||||
'Leave empty to use the default prompt, or enter a custom prompt'
|
||||
)}
|
||||
/>
|
||||
</AdminSettingField>
|
||||
{/if}
|
||||
|
||||
<div class="mb-2.5 flex w-full items-center justify-between">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Voice Mode Prompt')}
|
||||
</div>
|
||||
|
||||
<AdminSettingRow
|
||||
label={$i18n.t('Voice Mode Prompt')}
|
||||
description={$i18n.t('Apply voice-specific instructions while voice mode is active.')}
|
||||
>
|
||||
<Switch bind:state={taskConfig.ENABLE_VOICE_MODE_PROMPT} />
|
||||
</div>
|
||||
</AdminSettingRow>
|
||||
|
||||
{#if taskConfig.ENABLE_VOICE_MODE_PROMPT}
|
||||
<div class="mb-2.5">
|
||||
<div class=" mb-1 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Prompt Template')}
|
||||
</div>
|
||||
|
||||
<Tooltip
|
||||
content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
|
||||
placement="top-start"
|
||||
>
|
||||
<Textarea
|
||||
className={textareaClass}
|
||||
bind:value={taskConfig.VOICE_MODE_PROMPT_TEMPLATE}
|
||||
placeholder={$i18n.t(
|
||||
'Leave empty to use the default prompt, or enter a custom prompt'
|
||||
)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<AdminSettingField
|
||||
label={$i18n.t('Prompt Template')}
|
||||
description={$i18n.t('Defines the behavior used for spoken conversations.')}
|
||||
>
|
||||
<Textarea
|
||||
className={textareaClass}
|
||||
bind:value={taskConfig.VOICE_MODE_PROMPT_TEMPLATE}
|
||||
placeholder={$i18n.t(
|
||||
'Leave empty to use the default prompt, or enter a custom prompt'
|
||||
)}
|
||||
/>
|
||||
</AdminSettingField>
|
||||
{/if}
|
||||
|
||||
<div class="mb-2.5 flex w-full items-center justify-between">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Follow Up Generation')}
|
||||
</div>
|
||||
|
||||
<AdminSettingRow
|
||||
label={$i18n.t('Follow Up Generation')}
|
||||
description={$i18n.t('Show suggested next questions after assistant responses.')}
|
||||
>
|
||||
<Switch bind:state={taskConfig.ENABLE_FOLLOW_UP_GENERATION} />
|
||||
</div>
|
||||
</AdminSettingRow>
|
||||
|
||||
{#if taskConfig.ENABLE_FOLLOW_UP_GENERATION}
|
||||
<div class="mb-2.5">
|
||||
<div class=" mb-1 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Follow Up Generation Prompt')}
|
||||
</div>
|
||||
|
||||
<Tooltip
|
||||
content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
|
||||
placement="top-start"
|
||||
>
|
||||
<Textarea
|
||||
className={textareaClass}
|
||||
bind:value={taskConfig.FOLLOW_UP_GENERATION_PROMPT_TEMPLATE}
|
||||
placeholder={$i18n.t(
|
||||
'Leave empty to use the default prompt, or enter a custom prompt'
|
||||
)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<AdminSettingField
|
||||
label={$i18n.t('Follow Up Generation Prompt')}
|
||||
description={$i18n.t('Guides the suggestions shown after an assistant response.')}
|
||||
>
|
||||
<Textarea
|
||||
className={textareaClass}
|
||||
bind:value={taskConfig.FOLLOW_UP_GENERATION_PROMPT_TEMPLATE}
|
||||
placeholder={$i18n.t(
|
||||
'Leave empty to use the default prompt, or enter a custom prompt'
|
||||
)}
|
||||
/>
|
||||
</AdminSettingField>
|
||||
{/if}
|
||||
|
||||
<div class="mb-2.5 flex w-full items-center justify-between">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Tags Generation')}
|
||||
</div>
|
||||
|
||||
<AdminSettingRow
|
||||
label={$i18n.t('Tags Generation')}
|
||||
description={$i18n.t('Create chat tags from conversation content.')}
|
||||
>
|
||||
<Switch bind:state={taskConfig.ENABLE_TAGS_GENERATION} />
|
||||
</div>
|
||||
</AdminSettingRow>
|
||||
|
||||
{#if taskConfig.ENABLE_TAGS_GENERATION}
|
||||
<div class="mb-2.5">
|
||||
<div class=" mb-1 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Tags Generation Prompt')}
|
||||
</div>
|
||||
|
||||
<Tooltip
|
||||
content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
|
||||
placement="top-start"
|
||||
>
|
||||
<Textarea
|
||||
className={textareaClass}
|
||||
bind:value={taskConfig.TAGS_GENERATION_PROMPT_TEMPLATE}
|
||||
placeholder={$i18n.t(
|
||||
'Leave empty to use the default prompt, or enter a custom prompt'
|
||||
)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="mb-2.5 flex w-full items-center justify-between">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Retrieval Query Generation')}
|
||||
</div>
|
||||
|
||||
<Switch bind:state={taskConfig.ENABLE_RETRIEVAL_QUERY_GENERATION} />
|
||||
</div>
|
||||
|
||||
<div class="mb-2.5 flex w-full items-center justify-between">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Web Search Query Generation')}
|
||||
</div>
|
||||
|
||||
<Switch bind:state={taskConfig.ENABLE_SEARCH_QUERY_GENERATION} />
|
||||
</div>
|
||||
|
||||
<div class="mb-2.5">
|
||||
<div class=" mb-1 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Query Generation Prompt')}
|
||||
</div>
|
||||
|
||||
<Tooltip
|
||||
content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
|
||||
placement="top-start"
|
||||
<AdminSettingField
|
||||
label={$i18n.t('Tags Generation Prompt')}
|
||||
description={$i18n.t('Controls how chat tags are inferred.')}
|
||||
>
|
||||
<Textarea
|
||||
className={textareaClass}
|
||||
bind:value={taskConfig.QUERY_GENERATION_PROMPT_TEMPLATE}
|
||||
bind:value={taskConfig.TAGS_GENERATION_PROMPT_TEMPLATE}
|
||||
placeholder={$i18n.t(
|
||||
'Leave empty to use the default prompt, or enter a custom prompt'
|
||||
)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</AdminSettingField>
|
||||
{/if}
|
||||
|
||||
<div class="mb-2.5 flex w-full items-center justify-between">
|
||||
<div class=" self-center text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Autocomplete Generation')}
|
||||
</div>
|
||||
<AdminSettingRow
|
||||
label={$i18n.t('Retrieval Query Generation')}
|
||||
description={$i18n.t('Rewrite user requests for knowledge retrieval.')}
|
||||
>
|
||||
<Switch bind:state={taskConfig.ENABLE_RETRIEVAL_QUERY_GENERATION} />
|
||||
</AdminSettingRow>
|
||||
|
||||
<Tooltip content={$i18n.t('Enable autocomplete generation for chat messages')}>
|
||||
<Switch bind:state={taskConfig.ENABLE_AUTOCOMPLETE_GENERATION} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
<AdminSettingRow
|
||||
label={$i18n.t('Web Search Query Generation')}
|
||||
description={$i18n.t('Rewrite user requests into web-search queries.')}
|
||||
>
|
||||
<Switch bind:state={taskConfig.ENABLE_SEARCH_QUERY_GENERATION} />
|
||||
</AdminSettingRow>
|
||||
|
||||
<AdminSettingField
|
||||
label={$i18n.t('Query Generation Prompt')}
|
||||
description={$i18n.t('Shared prompt for retrieval and web-search query rewriting.')}
|
||||
>
|
||||
<Textarea
|
||||
className={textareaClass}
|
||||
bind:value={taskConfig.QUERY_GENERATION_PROMPT_TEMPLATE}
|
||||
placeholder={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
|
||||
/>
|
||||
</AdminSettingField>
|
||||
|
||||
<AdminSettingRow
|
||||
label={$i18n.t('Autocomplete Generation')}
|
||||
description={$i18n.t('Suggest completions while users type chat messages.')}
|
||||
>
|
||||
<Switch bind:state={taskConfig.ENABLE_AUTOCOMPLETE_GENERATION} />
|
||||
</AdminSettingRow>
|
||||
|
||||
{#if taskConfig.ENABLE_AUTOCOMPLETE_GENERATION}
|
||||
<div class="mb-2.5">
|
||||
<div class=" mb-1 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Autocomplete Generation Input Max Length')}
|
||||
</div>
|
||||
<AdminSettingField
|
||||
label={$i18n.t('Autocomplete Generation Input Max Length')}
|
||||
description={$i18n.t('Limit how much draft text is sent for suggestion generation.')}
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
min="-1"
|
||||
step="1"
|
||||
class={inputClass}
|
||||
bind:value={taskConfig.AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH}
|
||||
placeholder={$i18n.t('-1 for no limit, or a positive integer for a specific limit')}
|
||||
/>
|
||||
</AdminSettingField>
|
||||
|
||||
<Tooltip
|
||||
content={$i18n.t('Character limit for autocomplete generation input')}
|
||||
placement="top-start"
|
||||
>
|
||||
<input
|
||||
class="w-full outline-hidden bg-transparent"
|
||||
bind:value={taskConfig.AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH}
|
||||
placeholder={$i18n.t('-1 for no limit, or a positive integer for a specific limit')}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div class="mb-2.5">
|
||||
<div class=" mb-1 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Autocomplete Generation Prompt')}
|
||||
</div>
|
||||
|
||||
<Tooltip
|
||||
content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
|
||||
placement="top-start"
|
||||
>
|
||||
<Textarea
|
||||
className={textareaClass}
|
||||
bind:value={taskConfig.AUTOCOMPLETE_GENERATION_PROMPT_TEMPLATE}
|
||||
placeholder={$i18n.t(
|
||||
'Leave empty to use the default prompt, or enter a custom prompt'
|
||||
)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<AdminSettingField
|
||||
label={$i18n.t('Autocomplete Generation Prompt')}
|
||||
description={$i18n.t('Guides inline completions while users type a message.')}
|
||||
>
|
||||
<Textarea
|
||||
className={textareaClass}
|
||||
bind:value={taskConfig.AUTOCOMPLETE_GENERATION_PROMPT_TEMPLATE}
|
||||
placeholder={$i18n.t(
|
||||
'Leave empty to use the default prompt, or enter a custom prompt'
|
||||
)}
|
||||
/>
|
||||
</AdminSettingField>
|
||||
{/if}
|
||||
|
||||
<div class="mb-2.5">
|
||||
<div class=" mb-1 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Image Prompt Generation Prompt')}
|
||||
</div>
|
||||
<AdminSettingField
|
||||
label={$i18n.t('Image Prompt Generation Prompt')}
|
||||
description={$i18n.t('Rewrites user intent into an image-generation prompt.')}
|
||||
>
|
||||
<Textarea
|
||||
className={textareaClass}
|
||||
bind:value={taskConfig.IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE}
|
||||
placeholder={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
|
||||
/>
|
||||
</AdminSettingField>
|
||||
|
||||
<Tooltip
|
||||
content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
|
||||
placement="top-start"
|
||||
>
|
||||
<Textarea
|
||||
className={textareaClass}
|
||||
bind:value={taskConfig.IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE}
|
||||
placeholder={$i18n.t(
|
||||
'Leave empty to use the default prompt, or enter a custom prompt'
|
||||
)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div class="mb-2.5">
|
||||
<div class=" mb-1 text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Tools Function Calling Prompt')}
|
||||
</div>
|
||||
|
||||
<Tooltip
|
||||
content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
|
||||
placement="top-start"
|
||||
>
|
||||
<Textarea
|
||||
className={textareaClass}
|
||||
bind:value={taskConfig.TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE}
|
||||
placeholder={$i18n.t(
|
||||
'Leave empty to use the default prompt, or enter a custom prompt'
|
||||
)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<AdminSettingField
|
||||
label={$i18n.t('Tools Function Calling Prompt')}
|
||||
description={$i18n.t('Guides how the assistant formats tool and function calls.')}
|
||||
>
|
||||
<Textarea
|
||||
className={textareaClass}
|
||||
bind:value={taskConfig.TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE}
|
||||
placeholder={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
|
||||
/>
|
||||
</AdminSettingField>
|
||||
</AdminSettingSection>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end pt-6 text-sm font-normal">
|
||||
|
|
@ -556,7 +470,7 @@
|
|||
</div>
|
||||
</form>
|
||||
{:else}
|
||||
<div class=" h-full w-full flex justify-center items-center">
|
||||
<div class="flex h-full w-full items-center justify-center">
|
||||
<Spinner className="size-5" />
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
import Switch from '$lib/components/common/Switch.svelte';
|
||||
import AdminSettingField from './AdminSettingField.svelte';
|
||||
import AdminSettingRow from './AdminSettingRow.svelte';
|
||||
import AdminSettingSection from './AdminSettingSection.svelte';
|
||||
import SettingsSelect from '$lib/components/common/SettingsSelect.svelte';
|
||||
|
||||
const i18n: Writable<i18nType> = getContext('i18n');
|
||||
|
|
@ -38,24 +39,27 @@
|
|||
|
||||
let valves: any = null;
|
||||
let valves_spec: any = null;
|
||||
let selectedPipelineIdx: number | null = null;
|
||||
let selectedPipelineIdx = 0;
|
||||
|
||||
let pipelineDownloadUrl = '';
|
||||
const inputClass =
|
||||
'w-full h-7 rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 text-xs text-gray-700 outline-hidden transition-colors placeholder:text-gray-300 focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:placeholder:text-gray-700 dark:focus:border-blue-500';
|
||||
const actionButtonClass =
|
||||
'rounded-lg px-2 py-1 text-xs text-gray-500 transition-colors hover:bg-black/5 hover:text-gray-900 disabled:opacity-50 dark:text-gray-500 dark:hover:bg-white/5 dark:hover:text-white';
|
||||
const iconButtonClass =
|
||||
'flex size-7 items-center justify-center rounded-lg text-gray-400 transition-colors hover:bg-black/5 hover:text-gray-900 disabled:opacity-50 dark:text-gray-600 dark:hover:bg-white/5 dark:hover:text-white';
|
||||
'shrink-0 text-xs text-gray-500 transition-colors hover:text-gray-900 disabled:opacity-50 dark:text-gray-500 dark:hover:text-white';
|
||||
const mutedMessageClass = 'text-xs text-gray-400 dark:text-gray-600';
|
||||
|
||||
const updateHandler = async () => {
|
||||
if (!pipelines) {
|
||||
toast.error($i18n.t('No valves to update'));
|
||||
return;
|
||||
}
|
||||
|
||||
const pipeline = pipelines[selectedPipelineIdx];
|
||||
|
||||
if (pipeline && (pipeline?.valves ?? false)) {
|
||||
for (const property in valves_spec.properties) {
|
||||
if (valves_spec.properties[property]?.type === 'array') {
|
||||
valves[property] = (valves[property] ?? '').split(',').map((v) => v.trim());
|
||||
valves[property] = (valves[property] ?? '').split(',').map((v: string) => v.trim());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -86,7 +90,11 @@
|
|||
}
|
||||
};
|
||||
|
||||
const getValves = async (idx) => {
|
||||
const getValves = async (idx: number) => {
|
||||
if (!pipelines?.[idx]) {
|
||||
return;
|
||||
}
|
||||
|
||||
valves = null;
|
||||
valves_spec = null;
|
||||
|
||||
|
|
@ -113,11 +121,12 @@
|
|||
valves = null;
|
||||
valves_spec = null;
|
||||
|
||||
if (PIPELINES_LIST.length > 0) {
|
||||
if ((PIPELINES_LIST ?? []).length > 0) {
|
||||
console.debug(selectedPipelinesUrlIdx);
|
||||
pipelines = await getPipelines(localStorage.token, selectedPipelinesUrlIdx);
|
||||
const loadedPipelines = await getPipelines(localStorage.token, selectedPipelinesUrlIdx);
|
||||
pipelines = loadedPipelines;
|
||||
|
||||
if (pipelines.length > 0) {
|
||||
if (loadedPipelines.length > 0) {
|
||||
selectedPipelineIdx = 0;
|
||||
await getValves(selectedPipelineIdx);
|
||||
}
|
||||
|
|
@ -186,19 +195,26 @@
|
|||
}
|
||||
|
||||
pipelineFiles = null;
|
||||
const pipelineUploadInputElement = document.getElementById('pipelines-upload-input');
|
||||
const pipelineUploadInputElement = document.getElementById(
|
||||
'pipelines-upload-input'
|
||||
) as HTMLInputElement | null;
|
||||
|
||||
if (pipelineUploadInputElement) {
|
||||
pipelineUploadInputElement.value = null;
|
||||
pipelineUploadInputElement.value = '';
|
||||
}
|
||||
|
||||
uploading = false;
|
||||
};
|
||||
|
||||
const deletePipelineHandler = async () => {
|
||||
const pipeline = pipelines?.[selectedPipelineIdx];
|
||||
if (!pipeline) {
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await deletePipeline(
|
||||
localStorage.token,
|
||||
pipelines[selectedPipelineIdx].id,
|
||||
pipeline.id,
|
||||
selectedPipelinesUrlIdx
|
||||
).catch((error) => {
|
||||
toast.error(`${error}`);
|
||||
|
|
@ -223,8 +239,9 @@
|
|||
PIPELINES_LIST = await getPipelinesList(localStorage.token);
|
||||
console.log(PIPELINES_LIST);
|
||||
|
||||
if (PIPELINES_LIST.length > 0) {
|
||||
selectedPipelinesUrlIdx = PIPELINES_LIST[0]['idx'].toString();
|
||||
const pipelinesList = PIPELINES_LIST ?? [];
|
||||
if (pipelinesList.length > 0) {
|
||||
selectedPipelinesUrlIdx = pipelinesList[0]['idx'].toString();
|
||||
}
|
||||
|
||||
await setPipelines();
|
||||
|
|
@ -250,8 +267,11 @@
|
|||
hidden
|
||||
/>
|
||||
|
||||
<div class="flex flex-col gap-2.5">
|
||||
<AdminSettingField label={$i18n.t('Pipeline URL')}>
|
||||
<AdminSettingSection title={$i18n.t('Source')} first>
|
||||
<AdminSettingField
|
||||
label={$i18n.t('Pipeline URL')}
|
||||
description={$i18n.t('Select the Pipelines server to manage.')}
|
||||
>
|
||||
<SettingsSelect
|
||||
bind:value={selectedPipelinesUrlIdx}
|
||||
className="w-full"
|
||||
|
|
@ -273,7 +293,10 @@
|
|||
</SettingsSelect>
|
||||
</AdminSettingField>
|
||||
|
||||
<AdminSettingField label={$i18n.t('Upload Pipeline')}>
|
||||
<AdminSettingField
|
||||
label={$i18n.t('Upload Pipeline')}
|
||||
description={$i18n.t('Upload a local Python pipeline file to the selected server.')}
|
||||
>
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
class="h-7 flex-1 rounded-lg border border-dashed border-gray-100/50 bg-transparent px-2 text-left text-xs text-gray-500 transition-colors hover:bg-black/5 hover:text-gray-900 dark:border-white/[0.04] dark:text-gray-500 dark:hover:bg-white/5 dark:hover:text-white"
|
||||
|
|
@ -324,10 +347,15 @@
|
|||
</button>
|
||||
</div>
|
||||
</AdminSettingField>
|
||||
</AdminSettingSection>
|
||||
|
||||
{#if pipelines !== null}
|
||||
{#if pipelines.length > 0}
|
||||
<AdminSettingField label={$i18n.t('Pipeline')}>
|
||||
{#if pipelines !== null}
|
||||
{#if pipelines.length > 0}
|
||||
<AdminSettingSection title={$i18n.t('Pipelines')}>
|
||||
<AdminSettingField
|
||||
label={$i18n.t('Pipeline')}
|
||||
description={$i18n.t('Select an installed pipeline to configure or remove.')}
|
||||
>
|
||||
<div class="flex gap-2">
|
||||
<SettingsSelect
|
||||
bind:value={selectedPipelineIdx}
|
||||
|
|
@ -356,12 +384,17 @@
|
|||
</button>
|
||||
</div>
|
||||
</AdminSettingField>
|
||||
</AdminSettingSection>
|
||||
|
||||
<AdminSettingSection title={$i18n.t('Valves')}>
|
||||
{#if pipelines[selectedPipelineIdx ?? 0]?.valves}
|
||||
{#if valves}
|
||||
{#if valves && valves_spec}
|
||||
{#each Object.keys(valves_spec.properties) as property}
|
||||
<div>
|
||||
<AdminSettingRow label={valves_spec.properties[property].title}>
|
||||
<AdminSettingRow
|
||||
label={valves_spec.properties[property].title}
|
||||
description={valves_spec.properties[property]?.description ?? ''}
|
||||
>
|
||||
<button
|
||||
class={actionButtonClass}
|
||||
type="button"
|
||||
|
|
@ -407,22 +440,28 @@
|
|||
</div>
|
||||
{/each}
|
||||
{:else}
|
||||
<Spinner className="size-5" />
|
||||
<div class="flex justify-center py-2">
|
||||
<Spinner className="size-4" />
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class={mutedMessageClass}>{$i18n.t('No valves')}</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class={mutedMessageClass}>{$i18n.t('Pipelines Not Detected')}</div>
|
||||
{/if}
|
||||
</AdminSettingSection>
|
||||
{:else}
|
||||
<div class="flex justify-center py-4">
|
||||
<Spinner className="size-4" />
|
||||
</div>
|
||||
<AdminSettingSection title={$i18n.t('Pipelines')}>
|
||||
<div class={mutedMessageClass}>{$i18n.t('Pipelines Not Detected')}</div>
|
||||
</AdminSettingSection>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex justify-center py-4">
|
||||
<Spinner className="size-4" />
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class={mutedMessageClass}>{$i18n.t('Pipelines Not Detected')}</div>
|
||||
<AdminSettingSection title={$i18n.t('Source')} first>
|
||||
<div class={mutedMessageClass}>{$i18n.t('Pipelines Not Detected')}</div>
|
||||
</AdminSettingSection>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="flex justify-center h-full">
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -154,7 +154,7 @@
|
|||
<div class="text-xs text-gray-400 dark:text-gray-500">
|
||||
{$i18n.t('Created by')}
|
||||
<a class="text-gray-500 dark:text-gray-400" href="https://github.com/tjbck" target="_blank"
|
||||
>Timothy J. Baek</a
|
||||
>Tim J. Baek</a
|
||||
>
|
||||
</div>
|
||||
</UserSettingSection>
|
||||
|
|
|
|||
|
|
@ -41,8 +41,6 @@
|
|||
let playbackRate = 1;
|
||||
const inputClass =
|
||||
'h-7 w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 text-xs text-gray-700 outline-hidden transition-colors placeholder:text-gray-300 focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:placeholder:text-gray-700 dark:focus:border-blue-500';
|
||||
const actionButtonClass =
|
||||
'text-xs text-gray-500 transition-colors hover:text-gray-900 dark:text-gray-500 dark:hover:text-white';
|
||||
|
||||
const getVoices = async () => {
|
||||
if (TTSEngine === 'browser-kokoro') {
|
||||
|
|
@ -80,13 +78,13 @@
|
|||
}
|
||||
};
|
||||
|
||||
const toggleResponseAutoPlayback = async () => {
|
||||
responseAutoPlayback = !responseAutoPlayback;
|
||||
const setResponseAutoPlayback = async (enabled: boolean) => {
|
||||
responseAutoPlayback = enabled;
|
||||
saveSettings({ responseAutoPlayback: responseAutoPlayback });
|
||||
};
|
||||
|
||||
const toggleSpeechAutoSend = async () => {
|
||||
speechAutoSend = !speechAutoSend;
|
||||
const setSpeechAutoSend = async (enabled: boolean) => {
|
||||
speechAutoSend = enabled;
|
||||
saveSettings({ speechAutoSend: speechAutoSend });
|
||||
};
|
||||
|
||||
|
|
@ -231,17 +229,13 @@
|
|||
'Send transcribed voice input immediately after speech recognition finishes.'
|
||||
)}
|
||||
>
|
||||
<button
|
||||
class={actionButtonClass}
|
||||
on:click={() => {
|
||||
toggleSpeechAutoSend();
|
||||
<Switch
|
||||
state={speechAutoSend}
|
||||
ariaLabel={$i18n.t('Instant Auto-Send After Voice Transcription')}
|
||||
on:change={(event) => {
|
||||
setSpeechAutoSend(event.detail);
|
||||
}}
|
||||
type="button"
|
||||
role="switch"
|
||||
aria-checked={speechAutoSend}
|
||||
>
|
||||
{speechAutoSend === true ? $i18n.t('On') : $i18n.t('Off')}
|
||||
</button>
|
||||
/>
|
||||
</UserSettingRow>
|
||||
</UserSettingSection>
|
||||
|
||||
|
|
@ -283,17 +277,13 @@
|
|||
label={$i18n.t('Auto-Playback Response')}
|
||||
description={$i18n.t('Play assistant responses aloud automatically.')}
|
||||
>
|
||||
<button
|
||||
class={actionButtonClass}
|
||||
on:click={() => {
|
||||
toggleResponseAutoPlayback();
|
||||
<Switch
|
||||
state={responseAutoPlayback}
|
||||
ariaLabel={$i18n.t('Auto-Playback Response')}
|
||||
on:change={(event) => {
|
||||
setResponseAutoPlayback(event.detail);
|
||||
}}
|
||||
type="button"
|
||||
role="switch"
|
||||
aria-checked={responseAutoPlayback}
|
||||
>
|
||||
{responseAutoPlayback === true ? $i18n.t('On') : $i18n.t('Off')}
|
||||
</button>
|
||||
/>
|
||||
</UserSettingRow>
|
||||
|
||||
<UserSettingRow
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
import UserSettingRow from './UserSettingRow.svelte';
|
||||
import UserSettingSection from './UserSettingSection.svelte';
|
||||
import SettingsSelect from '$lib/components/common/SettingsSelect.svelte';
|
||||
import Switch from '$lib/components/common/Switch.svelte';
|
||||
export let saveSettings: Function;
|
||||
export let getModels: Function;
|
||||
|
||||
|
|
@ -33,13 +34,14 @@
|
|||
const actionButtonClass =
|
||||
'text-xs text-gray-500 transition-colors hover:text-gray-900 dark:text-gray-500 dark:hover:text-white';
|
||||
|
||||
const toggleNotification = async () => {
|
||||
const permission = await Notification.requestPermission();
|
||||
const setNotificationEnabled = async (enabled: boolean) => {
|
||||
const permission = enabled ? await Notification.requestPermission() : 'granted';
|
||||
|
||||
if (permission === 'granted') {
|
||||
notificationEnabled = !notificationEnabled;
|
||||
notificationEnabled = enabled;
|
||||
saveSettings({ notificationEnabled: notificationEnabled });
|
||||
} else {
|
||||
notificationEnabled = false;
|
||||
toast.error(
|
||||
$i18n.t(
|
||||
'Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.'
|
||||
|
|
@ -273,17 +275,13 @@
|
|||
label={$i18n.t('Notifications')}
|
||||
description={$i18n.t('Allow browser notifications for completed responses.')}
|
||||
>
|
||||
<button
|
||||
class={actionButtonClass}
|
||||
on:click={() => {
|
||||
toggleNotification();
|
||||
<Switch
|
||||
state={notificationEnabled}
|
||||
ariaLabel={$i18n.t('Notifications')}
|
||||
on:change={(event) => {
|
||||
setNotificationEnabled(event.detail);
|
||||
}}
|
||||
type="button"
|
||||
role="switch"
|
||||
aria-checked={notificationEnabled}
|
||||
>
|
||||
{notificationEnabled === true ? $i18n.t('On') : $i18n.t('Off')}
|
||||
</button>
|
||||
/>
|
||||
</UserSettingRow>
|
||||
</UserSettingSection>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue