From 7bff7aa5ac331effbac13b4b154e8d21656c65be Mon Sep 17 00:00:00 2001 From: Pascal Date: Sat, 14 Mar 2026 20:03:15 +0100 Subject: [PATCH] webui: make server the source of truth for sampling defaults --- .../ChatSettings/ChatSettingsFields.svelte | 39 ++++++++++------- .../src/lib/constants/settings-config.ts | 43 ++++++++++--------- .../webui/src/lib/stores/settings.svelte.ts | 43 +++++-------------- 3 files changed, 57 insertions(+), 68 deletions(-) diff --git a/tools/server/webui/src/lib/components/app/chat/ChatSettings/ChatSettingsFields.svelte b/tools/server/webui/src/lib/components/app/chat/ChatSettings/ChatSettingsFields.svelte index b9015c196c..383e5301fe 100644 --- a/tools/server/webui/src/lib/components/app/chat/ChatSettings/ChatSettingsFields.svelte +++ b/tools/server/webui/src/lib/components/app/chat/ChatSettings/ChatSettingsFields.svelte @@ -5,9 +5,12 @@ import Label from '$lib/components/ui/label/label.svelte'; import * as Select from '$lib/components/ui/select'; import { Textarea } from '$lib/components/ui/textarea'; - import { SETTING_CONFIG_DEFAULT, SETTING_CONFIG_INFO, SETTINGS_KEYS } from '$lib/constants'; + import { SETTING_CONFIG_INFO, SETTINGS_KEYS } from '$lib/constants'; import { SettingsFieldType } from '$lib/enums/settings'; import { settingsStore } from '$lib/stores/settings.svelte'; + import { serverStore } from '$lib/stores/server.svelte'; + import { modelsStore, selectedModelName } from '$lib/stores/models.svelte'; + import { normalizeFloatingPoint } from '$lib/utils/precision'; import { ChatSettingsParameterSourceIndicator } from '$lib/components/app'; import type { Component } from 'svelte'; @@ -20,6 +23,18 @@ let { fields, localConfig, onConfigChange, onThemeChange }: Props = $props(); + // server sampling defaults for placeholders + let sp = $derived.by(() => { + if (serverStore.isRouterMode) { + const m = selectedModelName(); + if (m) { + const p = modelsStore.getModelProps(m); + return (p?.default_generation_settings?.params ?? {}) as Record; + } + } + return (serverStore.defaultParams ?? {}) as Record; + }); + // Helper function to get parameter source info for syncable parameters function getParameterSourceInfo(key: string) { if (!settingsStore.canSyncParameter(key)) { @@ -38,13 +53,12 @@ {@const propsDefault = paramInfo?.serverDefault} {@const isCustomRealTime = (() => { if (!paramInfo || propsDefault === undefined) return false; + if (currentValue === '') return false; - // Apply same rounding logic for real-time comparison - const inputValue = currentValue; - const numericInput = parseFloat(inputValue); + const numericInput = parseFloat(currentValue); const normalizedInput = !isNaN(numericInput) ? Math.round(numericInput * 1000000) / 1000000 - : inputValue; + : currentValue; const normalizedDefault = typeof propsDefault === 'number' ? Math.round(propsDefault * 1000000) / 1000000 @@ -74,7 +88,7 @@ // Update local config immediately for real-time badge feedback onConfigChange(field.key, e.currentTarget.value); }} - placeholder={`Default: ${SETTING_CONFIG_DEFAULT[field.key] ?? 'none'}`} + placeholder={sp[field.key] != null ? `Default: ${normalizeFloatingPoint(sp[field.key])}` : ''} class="w-full {isCustomRealTime ? 'pr-8' : ''}" /> {#if isCustomRealTime} @@ -82,9 +96,7 @@ type="button" onclick={() => { settingsStore.resetParameterToServerDefault(field.key); - // Trigger UI update by calling onConfigChange with the default value - const defaultValue = propsDefault ?? SETTING_CONFIG_DEFAULT[field.key]; - onConfigChange(field.key, String(defaultValue)); + onConfigChange(field.key, ''); }} class="absolute top-1/2 right-2 inline-flex h-5 w-5 -translate-y-1/2 items-center justify-center rounded transition-colors hover:bg-muted" aria-label="Reset to default" @@ -112,7 +124,7 @@ id={field.key} value={String(localConfig[field.key] ?? '')} onchange={(e) => onConfigChange(field.key, e.currentTarget.value)} - placeholder={`Default: ${SETTING_CONFIG_DEFAULT[field.key] ?? 'none'}`} + placeholder="" class="min-h-[10rem] w-full md:max-w-2xl" /> @@ -145,8 +157,7 @@ {@const propsDefault = paramInfo?.serverDefault} {@const isCustomRealTime = (() => { if (!paramInfo || propsDefault === undefined) return false; - - // For select fields, do direct comparison (no rounding needed) + if (currentValue === '' || currentValue === undefined) return false; return currentValue !== propsDefault; })()} @@ -190,9 +201,7 @@ type="button" onclick={() => { settingsStore.resetParameterToServerDefault(field.key); - // Trigger UI update by calling onConfigChange with the default value - const defaultValue = propsDefault ?? SETTING_CONFIG_DEFAULT[field.key]; - onConfigChange(field.key, String(defaultValue)); + onConfigChange(field.key, ''); }} class="absolute top-1/2 right-8 inline-flex h-5 w-5 -translate-y-1/2 items-center justify-center rounded transition-colors hover:bg-muted" aria-label="Reset to default" diff --git a/tools/server/webui/src/lib/constants/settings-config.ts b/tools/server/webui/src/lib/constants/settings-config.ts index e76fa89e9a..e1ff5f30eb 100644 --- a/tools/server/webui/src/lib/constants/settings-config.ts +++ b/tools/server/webui/src/lib/constants/settings-config.ts @@ -30,27 +30,30 @@ export const SETTING_CONFIG_DEFAULT: Record = agenticMaxToolPreviewLines: 25, showToolCallInProgress: false, alwaysShowAgenticTurns: false, - // make sure these default values are in sync with `common.h` - samplers: 'top_k;typ_p;top_p;min_p;temperature', + // sampling params: empty means "use server default" + // the server / preset is the source of truth + // empty values are shown as placeholders from /props in the UI + // and are NOT sent in API requests, letting the server decide + samplers: '', backend_sampling: false, - temperature: 0.8, - dynatemp_range: 0.0, - dynatemp_exponent: 1.0, - top_k: 40, - top_p: 0.95, - min_p: 0.05, - xtc_probability: 0.0, - xtc_threshold: 0.1, - typ_p: 1.0, - repeat_last_n: 64, - repeat_penalty: 1.0, - presence_penalty: 0.0, - frequency_penalty: 0.0, - dry_multiplier: 0.0, - dry_base: 1.75, - dry_allowed_length: 2, - dry_penalty_last_n: -1, - max_tokens: -1, + temperature: '', + dynatemp_range: '', + dynatemp_exponent: '', + top_k: '', + top_p: '', + min_p: '', + xtc_probability: '', + xtc_threshold: '', + typ_p: '', + repeat_last_n: '', + repeat_penalty: '', + presence_penalty: '', + frequency_penalty: '', + dry_multiplier: '', + dry_base: '', + dry_allowed_length: '', + dry_penalty_last_n: '', + max_tokens: '', custom: '', // custom json-stringified object // experimental features pyInterpreterEnabled: false, diff --git a/tools/server/webui/src/lib/stores/settings.svelte.ts b/tools/server/webui/src/lib/stores/settings.svelte.ts index 8ab817c071..1d612f1b90 100644 --- a/tools/server/webui/src/lib/stores/settings.svelte.ts +++ b/tools/server/webui/src/lib/stores/settings.svelte.ts @@ -289,16 +289,10 @@ class SettingsStore { const serverDefaults = this.getServerDefaults(); if (serverDefaults[key] !== undefined) { - const value = normalizeFloatingPoint(serverDefaults[key]); - - this.config[key as keyof SettingsConfigType] = - value as SettingsConfigType[keyof SettingsConfigType]; - } else { - if (key in SETTING_CONFIG_DEFAULT) { - const defaultValue = getConfigValue(SETTING_CONFIG_DEFAULT, key); - - setConfigValue(this.config, key, defaultValue); - } + // sampling param known by server: clear it, let server decide + setConfigValue(this.config, key, ''); + } else if (key in SETTING_CONFIG_DEFAULT) { + setConfigValue(this.config, key, getConfigValue(SETTING_CONFIG_DEFAULT, key)); } this.userOverrides.delete(key); @@ -319,12 +313,7 @@ class SettingsStore { */ syncWithServerDefaults(): void { const propsDefaults = this.getServerDefaults(); - - if (Object.keys(propsDefaults).length === 0) { - console.warn('No server defaults available for initialization'); - - return; - } + if (Object.keys(propsDefaults).length === 0) return; for (const [key, propsValue] of Object.entries(propsDefaults)) { const currentValue = getConfigValue(this.config, key); @@ -332,17 +321,13 @@ class SettingsStore { const normalizedCurrent = normalizeFloatingPoint(currentValue); const normalizedDefault = normalizeFloatingPoint(propsValue); + // if user value matches server, it's not a real override if (normalizedCurrent === normalizedDefault) { this.userOverrides.delete(key); - setConfigValue(this.config, key, propsValue); - } else if (!this.userOverrides.has(key)) { - setConfigValue(this.config, key, propsValue); } } this.saveConfig(); - console.log('Settings initialized with props defaults:', propsDefaults); - console.log('Current user overrides after sync:', Array.from(this.userOverrides)); } /** @@ -352,19 +337,11 @@ class SettingsStore { */ forceSyncWithServerDefaults(): void { const propsDefaults = this.getServerDefaults(); - const syncableKeys = ParameterSyncService.getSyncableParameterKeys(); - - for (const key of syncableKeys) { + for (const key of ParameterSyncService.getSyncableParameterKeys()) { if (propsDefaults[key] !== undefined) { - const normalizedValue = normalizeFloatingPoint(propsDefaults[key]); - - setConfigValue(this.config, key, normalizedValue); - } else { - if (key in SETTING_CONFIG_DEFAULT) { - const defaultValue = getConfigValue(SETTING_CONFIG_DEFAULT, key); - - setConfigValue(this.config, key, defaultValue); - } + setConfigValue(this.config, key, ''); + } else if (key in SETTING_CONFIG_DEFAULT) { + setConfigValue(this.config, key, getConfigValue(SETTING_CONFIG_DEFAULT, key)); } this.userOverrides.delete(key);