From efd94fde63882818d451370a2b39edb93ba6a8f5 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Wed, 7 Oct 2026 00:54:53 +0400 Subject: [PATCH] refac --- backend/open_webui/models/models.py | 5 +- backend/open_webui/utils/models.py | 2 +- src/lib/apis/index.ts | 1 + .../Settings/Models/ModelControls.svelte | 26 +++++ src/lib/components/chat/Chat.svelte | 3 + src/lib/components/chat/MessageInput.svelte | 25 ++++- .../MessageInput/ModelControlSlider.svelte | 104 ++++++++++++++++++ .../chat/MessageInput/ModelControls.svelte | 10 +- src/lib/components/chat/Placeholder.svelte | 2 + 9 files changed, 173 insertions(+), 5 deletions(-) create mode 100644 src/lib/components/chat/MessageInput/ModelControlSlider.svelte diff --git a/backend/open_webui/models/models.py b/backend/open_webui/models/models.py index 1d035cf714..d518030d60 100755 --- a/backend/open_webui/models/models.py +++ b/backend/open_webui/models/models.py @@ -4,7 +4,7 @@ import logging import re import time from copy import deepcopy -from typing import Annotated, Any +from typing import Annotated, Any, Literal from open_webui.internal.db import Base, JSONField, get_async_db_context from open_webui.models.access_grants import AccessGrantModel, AccessGrants @@ -77,6 +77,7 @@ class ModelControlOption(BaseModel): class ModelControl(BaseModel): + display: Literal['menu', 'slider'] = Field(default='menu', exclude_if=lambda value: value == 'menu') label: str = Field(pattern=r'\S') description: str | None = Field(default=None, exclude_if=lambda value: value is None) default: str | None = Field(default=None, exclude_if=lambda value: value is None) @@ -84,6 +85,8 @@ class ModelControl(BaseModel): @model_validator(mode='after') def check_default(self): + if self.display == 'slider' and len(self.options) < 2: + raise ValueError('A slider needs at least two options.') if self.default is not None and self.default not in self.options: raise ValueError('Default must name an approved option.') return self diff --git a/backend/open_webui/utils/models.py b/backend/open_webui/utils/models.py index f30b1beaa9..bf4585f555 100644 --- a/backend/open_webui/utils/models.py +++ b/backend/open_webui/utils/models.py @@ -38,7 +38,7 @@ def public_model_params(params): { 'model_controls': { key: { - **{field: control[field] for field in ('label', 'description', 'default') if field in control}, + **{field: control[field] for field in ('label', 'description', 'default', 'display') if field in control}, 'options': {key: {'label': option['label']} for key, option in control['options'].items()}, } for key, control in controls.items() diff --git a/src/lib/apis/index.ts b/src/lib/apis/index.ts index 5935363902..6eb0d111ac 100644 --- a/src/lib/apis/index.ts +++ b/src/lib/apis/index.ts @@ -1792,6 +1792,7 @@ export interface ModelMeta { } export interface ModelControl { + display?: 'menu' | 'slider'; label: string; description?: string; default?: string | null; diff --git a/src/lib/components/admin/Settings/Models/ModelControls.svelte b/src/lib/components/admin/Settings/Models/ModelControls.svelte index 520bf08010..b9eb4aa9a7 100644 --- a/src/lib/components/admin/Settings/Models/ModelControls.svelte +++ b/src/lib/components/admin/Settings/Models/ModelControls.svelte @@ -16,6 +16,7 @@ let editingKey = ''; let label = ''; let description = ''; + let display: 'menu' | 'slider' = 'menu'; let defaultOption = ''; let options: { id: string; key?: string; label: string; params: Record }[] = []; let nameInput: HTMLInputElement; @@ -46,6 +47,7 @@ const control = controls?.[key]; label = control?.label ?? ''; description = control?.description ?? ''; + display = control?.display ?? 'menu'; defaultOption = control?.default ?? ''; options = Object.entries(control?.options ?? {}).map(([key, option]) => ({ id: key, @@ -75,6 +77,7 @@ ...controls, [editingKey || keyFor(label, controls ?? {})]: { label: label.trim(), + ...(display === 'slider' ? { display } : {}), ...(description.trim() ? { description: description.trim() } : {}), default: selected, options: saved @@ -228,6 +231,23 @@ {$i18n.t('Description')} +
+ {$i18n.t('Display')} +
+ {#each ['menu', 'slider'] as mode} + + {/each} +
+
{$i18n.t('Options')} @@ -274,6 +294,11 @@ {/each}
+ {#if display === 'slider' && options.length < 2} +

+ {$i18n.t('Add at least two options for a slider.')} +

+ {/if}
- + @@ -2809,3 +2816,17 @@
{/if} + + diff --git a/src/lib/components/chat/MessageInput/ModelControlSlider.svelte b/src/lib/components/chat/MessageInput/ModelControlSlider.svelte new file mode 100644 index 0000000000..2804813a45 --- /dev/null +++ b/src/lib/components/chat/MessageInput/ModelControlSlider.svelte @@ -0,0 +1,104 @@ + + +
+
+ {control.label} + {label} + {#if control.description} + + + + {/if} +
+
+ +
+ + (editing = true)} + on:change={() => { + dispatch('change', options[index][0]); + editing = false; + }} + on:pointerup={() => { + if (!current) dispatch('change', options[index][0]); + }} + /> +
+
+ +
+ + diff --git a/src/lib/components/chat/MessageInput/ModelControls.svelte b/src/lib/components/chat/MessageInput/ModelControls.svelte index 08a38ab9f0..5673e0caa1 100644 --- a/src/lib/components/chat/MessageInput/ModelControls.svelte +++ b/src/lib/components/chat/MessageInput/ModelControls.svelte @@ -4,6 +4,7 @@ import Dropdown from '$lib/components/common/Dropdown.svelte'; import DropdownMenu from '$lib/components/common/DropdownMenu.svelte'; import DropdownSub from '$lib/components/common/DropdownSub.svelte'; + import ModelControlSlider from './ModelControlSlider.svelte'; import type { ModelControl } from '$lib/apis'; import { updateUserSettings } from '$lib/apis/users'; import { toast } from 'svelte-sonner'; @@ -143,7 +144,14 @@ {/if} {#each Object.entries(model.info?.params?.model_controls ?? {}) as [key, control] (key)} - {#if $mobile} + {#if control.display === 'slider'} + select(model.id, key, event.detail)} + /> + {:else if $mobile}