This commit is contained in:
Pascal 2026-03-15 23:04:17 +01:00 committed by GitHub
commit 900a76be93
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 66 additions and 85 deletions

Binary file not shown.

View File

@ -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,35 +23,36 @@
let { fields, localConfig, onConfigChange, onThemeChange }: Props = $props();
// Helper function to get parameter source info for syncable parameters
function getParameterSourceInfo(key: string) {
if (!settingsStore.canSyncParameter(key)) {
return null;
// 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<string, unknown>;
}
}
return settingsStore.getParameterInfo(key);
}
return (serverStore.defaultParams ?? {}) as Record<string, unknown>;
});
</script>
{#each fields as field (field.key)}
<div class="space-y-2">
{#if field.type === SettingsFieldType.INPUT}
{@const paramInfo = getParameterSourceInfo(field.key)}
{@const currentValue = String(localConfig[field.key] ?? '')}
{@const propsDefault = paramInfo?.serverDefault}
{@const serverDefault = sp[field.key]}
{@const isCustomRealTime = (() => {
if (!paramInfo || propsDefault === undefined) return false;
if (serverDefault == null) 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
: propsDefault;
typeof serverDefault === 'number'
? Math.round(serverDefault * 1000000) / 1000000
: serverDefault;
return normalizedInput !== normalizedDefault;
})()}
@ -74,7 +78,9 @@
// 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 +88,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 +116,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"
/>
@ -140,14 +144,12 @@
(opt: { value: string; label: string; icon?: Component }) =>
opt.value === localConfig[field.key]
)}
{@const paramInfo = getParameterSourceInfo(field.key)}
{@const currentValue = localConfig[field.key]}
{@const propsDefault = paramInfo?.serverDefault}
{@const serverDefault = sp[field.key]}
{@const isCustomRealTime = (() => {
if (!paramInfo || propsDefault === undefined) return false;
// For select fields, do direct comparison (no rounding needed)
return currentValue !== propsDefault;
if (serverDefault == null) return false;
if (currentValue === '' || currentValue === undefined) return false;
return currentValue !== serverDefault;
})()}
<div class="flex items-center gap-2">
@ -190,9 +192,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"

View File

@ -30,27 +30,30 @@ export const SETTING_CONFIG_DEFAULT: Record<string, string | number | boolean> =
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,

View File

@ -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,14 @@ 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));
console.log('User overrides after sync:', Array.from(this.userOverrides));
}
/**
@ -352,19 +338,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);