This commit is contained in:
Timothy Jaeryang Baek 2026-10-06 22:02:23 +04:00
parent 32b653d4ea
commit e9cca320b4
6 changed files with 45 additions and 29 deletions

View file

@ -1197,7 +1197,7 @@ async def chat_completion(
request_params = {key: value for key, value in (form_data.get('params') or {}).items() if value is not None}
model_controls = request_params.pop('model_controls', {})
if not isinstance(model_controls, dict):
raise HTTPException(400, 'Model control selections must be keyed by model.')
raise HTTPException(400, 'Model control options must be keyed by model.')
model_controls = {} if form_data.get('automation_id') else model_controls
if any(model_controls.values()) and user.role != 'admin':
permissions = await Config.get('user.permissions')
@ -1205,7 +1205,7 @@ async def chat_completion(
if not await has_permission(user.id, permission, permissions):
raise HTTPException(403, 'You cannot change model parameters.')
if missing_base_model and model_controls.get(model_id):
raise HTTPException(400, 'Model control selections cannot be applied to the fallback model.')
raise HTTPException(400, 'Model control options cannot be applied to the fallback model.')
if model_info_params or request_params:
form_data['params'] = merge_model_params(model_info_params, request_params)

View file

@ -17,15 +17,15 @@ from open_webui.utils.task import prompt_template, prompt_variables_template
log = logging.getLogger(__name__)
def apply_model_controls(params: dict, controls: dict, selections: dict) -> dict:
def apply_model_controls(params: dict, controls: dict, model_options: dict) -> dict:
"""Expand approved choices into ordinary Custom Params before normal request processing."""
if not isinstance(selections, dict):
raise HTTPException(400, 'Model control selections must be an object.')
for key, choice in selections.items():
if not isinstance(model_options, dict):
raise HTTPException(400, 'Model control options must be an object.')
for key, choice in model_options.items():
if key not in controls or not isinstance(choice, str) or choice not in controls[key]['options']:
raise HTTPException(400, f'Model control {key}: the selected option is no longer available.')
for key, control in controls.items():
choice = selections.get(key, control.get('default'))
choice = model_options.get(key, control.get('default'))
if choice is not None:
params['custom_params'] = deep_update(
deepcopy(params.get('custom_params') or {}), deepcopy(control['options'][choice]['params'])

View file

@ -3636,6 +3636,7 @@
params: {
...$settings?.params,
...params,
model_controls: $settings?.params?.model_controls ?? {},
stop: getStopTokens()
},
@ -4504,7 +4505,6 @@
class=" pb-2 {dragged ? 'z-0' : 'z-10'}"
>
<MessageInput
bind:params
bind:this={messageInput}
{history}
{taskIds}
@ -4597,7 +4597,6 @@
{/if}
<div id={embedded ? messageInputDropzoneId : undefined} class="pb-2 z-10">
<MessageInput
bind:params
bind:this={messageInput}
{history}
{taskIds}
@ -4659,7 +4658,6 @@
{:else}
<div class="flex items-center h-full">
<Placeholder
bind:params
bind:selectedModelIdx
{history}
bind:selectedModels

View file

@ -142,7 +142,6 @@
export let statusHandler: Function = () => {};
export let forkHandler: Function = () => {};
export let chatId = '';
export let params = {};
export let contextUsage = null;
export let contextCompactionEnabled = false;
export let embedded = false;
@ -2618,7 +2617,7 @@
triggerClassName="items-center gap-1.5 rounded-lg pl-2 pr-1.5 py-1 text-[0.8125rem] font-normal text-gray-600 transition-colors duration-100 hover:bg-gray-50/40 hover:text-gray-700 dark:text-gray-300 dark:hover:bg-gray-800/40 dark:hover:text-gray-200"
/>
</div>
<ModelControls selectedModels={selectedModelIds} bind:params />
<ModelControls selectedModels={selectedModelIds} />
{#if isActive && prompt === '' && files.length === 0}
<div class=" flex items-center">

View file

@ -5,6 +5,8 @@
import DropdownMenu from '$lib/components/common/DropdownMenu.svelte';
import DropdownSub from '$lib/components/common/DropdownSub.svelte';
import type { ModelControl } from '$lib/apis';
import { updateUserSettings } from '$lib/apis/users';
import { toast } from 'svelte-sonner';
import ChevronRight from '$lib/components/icons/ChevronRight.svelte';
import ChevronLeft from '$lib/components/icons/ChevronLeft.svelte';
import Check from '$lib/components/icons/Check.svelte';
@ -14,7 +16,8 @@
const i18n: any = getContext('i18n');
export let selectedModels: string[] = [];
export let params: Record<string, any> = {};
let saving = false;
$: modelControls = $settings?.params?.model_controls ?? {};
let active: {
model: Model;
key: string;
@ -43,18 +46,36 @@
$: selectedClass = $settings?.highContrastMode
? 'bg-gray-200 dark:bg-gray-800'
: 'bg-gray-50/70 dark:bg-gray-800/60';
const select = (modelId: string, controlId: string, value: string) => {
const selections = { ...params.model_controls?.[modelId] };
if (value) selections[controlId] = value;
else delete selections[controlId];
params = { ...params, model_controls: { ...params.model_controls, [modelId]: selections } };
const select = async (modelId: string, controlId: string, value: string) => {
const previous = modelControls;
const modelOptions = { ...modelControls[modelId] };
if (value) modelOptions[controlId] = value;
else delete modelOptions[controlId];
settings.set({
...$settings,
params: {
...$settings.params,
model_controls: { ...modelControls, [modelId]: modelOptions }
}
});
saving = true;
try {
if (!(await updateUserSettings(localStorage.token, { ui: { params: $settings.params } }))) {
throw new Error($i18n.t('Failed to save settings'));
}
} catch (error) {
settings.set({ ...$settings, params: { ...$settings.params, model_controls: previous } });
toast.error(String(error));
} finally {
saving = false;
}
};
</script>
{#snippet summary(model: Model, key: string, control: ModelControl)}
<span class="min-w-0 flex-1 truncate text-left">{control.label}</span>
<span class="max-w-[55%] truncate text-gray-500 dark:text-gray-400">
{control.options[params.model_controls?.[model.id]?.[key] ?? control.default ?? '']?.label ??
{control.options[modelControls[model.id]?.[key] ?? control.default ?? '']?.label ??
$i18n.t('Default')}
</span>
<ChevronRight className="size-3.5 shrink-0 text-gray-400" />
@ -69,28 +90,28 @@
<button
type="button"
role="menuitemradio"
aria-checked={!params.model_controls?.[model.id]?.[key]}
class={`${rowClass} ${!params.model_controls?.[model.id]?.[key] ? selectedClass : ''}`}
disabled={saving}
aria-checked={!modelControls[model.id]?.[key]}
class={`${rowClass} ${!modelControls[model.id]?.[key] ? selectedClass : ''}`}
on:click={() => select(model.id, key, '')}
>
<span class="min-w-0 flex-1 truncate text-left">
{$i18n.t('Default')}{#if control.options[control.default ?? '']?.label}{' · '}{control
.options[control.default ?? ''].label}{/if}
</span>
{#if !params.model_controls?.[model.id]?.[key]}<Check className="size-3! shrink-0" />{/if}
{#if !modelControls[model.id]?.[key]}<Check className="size-3! shrink-0" />{/if}
</button>
{#each Object.entries(control.options) as [value, option] (value)}
<button
type="button"
role="menuitemradio"
aria-checked={params.model_controls?.[model.id]?.[key] === value}
class={`${rowClass} ${params.model_controls?.[model.id]?.[key] === value ? selectedClass : ''}`}
disabled={saving}
aria-checked={modelControls[model.id]?.[key] === value}
class={`${rowClass} ${modelControls[model.id]?.[key] === value ? selectedClass : ''}`}
on:click={() => select(model.id, key, value)}
>
<span class="min-w-0 flex-1 truncate text-left">{option.label}</span>
{#if params.model_controls?.[model.id]?.[key] === value}<Check
className="size-3! shrink-0"
/>{/if}
{#if modelControls[model.id]?.[key] === value}<Check className="size-3! shrink-0" />{/if}
</button>
{/each}
{/snippet}

View file

@ -47,7 +47,6 @@
export let history;
export let prompt = '';
export let params = {};
export let files = [];
export let messageInput = null;
@ -242,7 +241,6 @@
<div class="text-base font-normal @md:max-w-3xl w-full py-3 {atSelectedModel ? 'mt-2' : ''}">
{#if !($selectedFolder && folderReadOnly)}
<MessageInput
bind:params
bind:this={messageInput}
{history}
bind:selectedModels