Skip to content

Commit f0039fe

Browse files
allozaurpwilkin
authored andcommitted
Add server-driven parameter defaults and syncing (ggml-org#16515)
1 parent 0ed6745 commit f0039fe

File tree

14 files changed

+777
-38
lines changed

14 files changed

+777
-38
lines changed

tools/server/public/index.html.gz

2.75 KB
Binary file not shown.

tools/server/webui/src/lib/components/app/chat/ChatSettings/ChatSettingsDialog.svelte

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@
1414
import { ChatSettingsFooter, ChatSettingsFields } from '$lib/components/app';
1515
import * as Dialog from '$lib/components/ui/dialog';
1616
import { ScrollArea } from '$lib/components/ui/scroll-area';
17-
import { SETTING_CONFIG_DEFAULT } from '$lib/constants/settings-config';
18-
import { config, updateMultipleConfig, resetConfig } from '$lib/stores/settings.svelte';
17+
import { config, updateMultipleConfig } from '$lib/stores/settings.svelte';
1918
import { setMode } from 'mode-watcher';
2019
import type { Component } from 'svelte';
2120
@@ -267,16 +266,13 @@
267266
}
268267
269268
function handleReset() {
270-
resetConfig();
269+
localConfig = { ...config() };
271270
272-
localConfig = { ...SETTING_CONFIG_DEFAULT };
273-
274-
setMode(SETTING_CONFIG_DEFAULT.theme as 'light' | 'dark' | 'system');
275-
originalTheme = SETTING_CONFIG_DEFAULT.theme as string;
271+
setMode(localConfig.theme as 'light' | 'dark' | 'system');
272+
originalTheme = localConfig.theme as string;
276273
}
277274
278275
function handleSave() {
279-
// Validate custom JSON if provided
280276
if (localConfig.custom && typeof localConfig.custom === 'string' && localConfig.custom.trim()) {
281277
try {
282278
JSON.parse(localConfig.custom);

tools/server/webui/src/lib/components/app/chat/ChatSettings/ChatSettingsFields.svelte

Lines changed: 115 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
<script lang="ts">
2+
import { RotateCcw } from '@lucide/svelte';
23
import { Checkbox } from '$lib/components/ui/checkbox';
34
import { Input } from '$lib/components/ui/input';
45
import Label from '$lib/components/ui/label/label.svelte';
56
import * as Select from '$lib/components/ui/select';
67
import { Textarea } from '$lib/components/ui/textarea';
78
import { SETTING_CONFIG_DEFAULT, SETTING_CONFIG_INFO } from '$lib/constants/settings-config';
89
import { supportsVision } from '$lib/stores/server.svelte';
10+
import { getParameterInfo, resetParameterToServerDefault } from '$lib/stores/settings.svelte';
11+
import { ParameterSyncService } from '$lib/services/parameter-sync';
12+
import ParameterSourceIndicator from './ParameterSourceIndicator.svelte';
913
import type { Component } from 'svelte';
1014
1115
interface Props {
@@ -16,22 +20,77 @@
1620
}
1721
1822
let { fields, localConfig, onConfigChange, onThemeChange }: Props = $props();
23+
24+
// Helper function to get parameter source info for syncable parameters
25+
function getParameterSourceInfo(key: string) {
26+
if (!ParameterSyncService.canSyncParameter(key)) {
27+
return null;
28+
}
29+
30+
return getParameterInfo(key);
31+
}
1932
</script>
2033

2134
{#each fields as field (field.key)}
2235
<div class="space-y-2">
2336
{#if field.type === 'input'}
24-
<Label for={field.key} class="block text-sm font-medium">
25-
{field.label}
26-
</Label>
37+
{@const paramInfo = getParameterSourceInfo(field.key)}
38+
{@const currentValue = String(localConfig[field.key] ?? '')}
39+
{@const propsDefault = paramInfo?.serverDefault}
40+
{@const isCustomRealTime = (() => {
41+
if (!paramInfo || propsDefault === undefined) return false;
2742

28-
<Input
29-
id={field.key}
30-
value={String(localConfig[field.key] ?? '')}
31-
onchange={(e) => onConfigChange(field.key, e.currentTarget.value)}
32-
placeholder={`Default: ${SETTING_CONFIG_DEFAULT[field.key] ?? 'none'}`}
33-
class="w-full md:max-w-md"
34-
/>
43+
// Apply same rounding logic for real-time comparison
44+
const inputValue = currentValue;
45+
const numericInput = parseFloat(inputValue);
46+
const normalizedInput = !isNaN(numericInput)
47+
? Math.round(numericInput * 1000000) / 1000000
48+
: inputValue;
49+
const normalizedDefault =
50+
typeof propsDefault === 'number'
51+
? Math.round(propsDefault * 1000000) / 1000000
52+
: propsDefault;
53+
54+
return normalizedInput !== normalizedDefault;
55+
})()}
56+
57+
<div class="flex items-center gap-2">
58+
<Label for={field.key} class="text-sm font-medium">
59+
{field.label}
60+
</Label>
61+
{#if isCustomRealTime}
62+
<ParameterSourceIndicator />
63+
{/if}
64+
</div>
65+
66+
<div class="relative w-full md:max-w-md">
67+
<Input
68+
id={field.key}
69+
value={currentValue}
70+
oninput={(e) => {
71+
// Update local config immediately for real-time badge feedback
72+
onConfigChange(field.key, e.currentTarget.value);
73+
}}
74+
placeholder={`Default: ${SETTING_CONFIG_DEFAULT[field.key] ?? 'none'}`}
75+
class="w-full {isCustomRealTime ? 'pr-8' : ''}"
76+
/>
77+
{#if isCustomRealTime}
78+
<button
79+
type="button"
80+
onclick={() => {
81+
resetParameterToServerDefault(field.key);
82+
// Trigger UI update by calling onConfigChange with the default value
83+
const defaultValue = propsDefault ?? SETTING_CONFIG_DEFAULT[field.key];
84+
onConfigChange(field.key, String(defaultValue));
85+
}}
86+
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"
87+
aria-label="Reset to default"
88+
title="Reset to default"
89+
>
90+
<RotateCcw class="h-3 w-3" />
91+
</button>
92+
{/if}
93+
</div>
3594
{#if field.help || SETTING_CONFIG_INFO[field.key]}
3695
<p class="mt-1 text-xs text-muted-foreground">
3796
{field.help || SETTING_CONFIG_INFO[field.key]}
@@ -59,14 +118,28 @@
59118
(opt: { value: string; label: string; icon?: Component }) =>
60119
opt.value === localConfig[field.key]
61120
)}
121+
{@const paramInfo = getParameterSourceInfo(field.key)}
122+
{@const currentValue = localConfig[field.key]}
123+
{@const propsDefault = paramInfo?.serverDefault}
124+
{@const isCustomRealTime = (() => {
125+
if (!paramInfo || propsDefault === undefined) return false;
62126

63-
<Label for={field.key} class="block text-sm font-medium">
64-
{field.label}
65-
</Label>
127+
// For select fields, do direct comparison (no rounding needed)
128+
return currentValue !== propsDefault;
129+
})()}
130+
131+
<div class="flex items-center gap-2">
132+
<Label for={field.key} class="text-sm font-medium">
133+
{field.label}
134+
</Label>
135+
{#if isCustomRealTime}
136+
<ParameterSourceIndicator />
137+
{/if}
138+
</div>
66139

67140
<Select.Root
68141
type="single"
69-
value={localConfig[field.key]}
142+
value={currentValue}
70143
onValueChange={(value) => {
71144
if (field.key === 'theme' && value && onThemeChange) {
72145
onThemeChange(value);
@@ -75,16 +148,34 @@
75148
}
76149
}}
77150
>
78-
<Select.Trigger class="w-full md:w-auto md:max-w-md">
79-
<div class="flex items-center gap-2">
80-
{#if selectedOption?.icon}
81-
{@const IconComponent = selectedOption.icon}
82-
<IconComponent class="h-4 w-4" />
83-
{/if}
84-
85-
{selectedOption?.label || `Select ${field.label.toLowerCase()}`}
86-
</div>
87-
</Select.Trigger>
151+
<div class="relative w-full md:w-auto md:max-w-md">
152+
<Select.Trigger class="w-full">
153+
<div class="flex items-center gap-2">
154+
{#if selectedOption?.icon}
155+
{@const IconComponent = selectedOption.icon}
156+
<IconComponent class="h-4 w-4" />
157+
{/if}
158+
159+
{selectedOption?.label || `Select ${field.label.toLowerCase()}`}
160+
</div>
161+
</Select.Trigger>
162+
{#if isCustomRealTime}
163+
<button
164+
type="button"
165+
onclick={() => {
166+
resetParameterToServerDefault(field.key);
167+
// Trigger UI update by calling onConfigChange with the default value
168+
const defaultValue = propsDefault ?? SETTING_CONFIG_DEFAULT[field.key];
169+
onConfigChange(field.key, String(defaultValue));
170+
}}
171+
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"
172+
aria-label="Reset to default"
173+
title="Reset to default"
174+
>
175+
<RotateCcw class="h-3 w-3" />
176+
</button>
177+
{/if}
178+
</div>
88179
<Select.Content>
89180
{#if field.options}
90181
{#each field.options as option (option.value)}

tools/server/webui/src/lib/components/app/chat/ChatSettings/ChatSettingsFooter.svelte

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
<script lang="ts">
22
import { Button } from '$lib/components/ui/button';
33
import * as AlertDialog from '$lib/components/ui/alert-dialog';
4+
import { forceSyncWithServerDefaults } from '$lib/stores/settings.svelte';
5+
import { RotateCcw } from '@lucide/svelte';
46
57
interface Props {
68
onReset?: () => void;
@@ -16,7 +18,9 @@
1618
}
1719
1820
function handleConfirmReset() {
21+
forceSyncWithServerDefaults();
1922
onReset?.();
23+
2024
showResetDialog = false;
2125
}
2226
@@ -26,7 +30,13 @@
2630
</script>
2731

2832
<div class="flex justify-between border-t border-border/30 p-6">
29-
<Button variant="outline" onclick={handleResetClick}>Reset to default</Button>
33+
<div class="flex gap-2">
34+
<Button variant="outline" onclick={handleResetClick}>
35+
<RotateCcw class="h-3 w-3" />
36+
37+
Reset to default
38+
</Button>
39+
</div>
3040

3141
<Button onclick={handleSave}>Save settings</Button>
3242
</div>
@@ -36,8 +46,9 @@
3646
<AlertDialog.Header>
3747
<AlertDialog.Title>Reset Settings to Default</AlertDialog.Title>
3848
<AlertDialog.Description>
39-
Are you sure you want to reset all settings to their default values? This action cannot be
40-
undone and will permanently remove all your custom configurations.
49+
Are you sure you want to reset all settings to their default values? This will reset all
50+
parameters to the values provided by the server's /props endpoint and remove all your custom
51+
configurations.
4152
</AlertDialog.Description>
4253
</AlertDialog.Header>
4354
<AlertDialog.Footer>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<script lang="ts">
2+
import { Wrench } from '@lucide/svelte';
3+
import { Badge } from '$lib/components/ui/badge';
4+
5+
interface Props {
6+
class?: string;
7+
}
8+
9+
let { class: className = '' }: Props = $props();
10+
</script>
11+
12+
<Badge
13+
variant="secondary"
14+
class="h-5 bg-orange-100 px-1.5 py-0.5 text-xs text-orange-800 dark:bg-orange-900 dark:text-orange-200 {className}"
15+
>
16+
<Wrench class="mr-1 h-3 w-3" />
17+
Custom
18+
</Badge>

tools/server/webui/src/lib/components/app/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export { default as ChatScreen } from './chat/ChatScreen/ChatScreen.svelte';
2525
export { default as ChatSettingsDialog } from './chat/ChatSettings/ChatSettingsDialog.svelte';
2626
export { default as ChatSettingsFooter } from './chat/ChatSettings/ChatSettingsFooter.svelte';
2727
export { default as ChatSettingsFields } from './chat/ChatSettings/ChatSettingsFields.svelte';
28+
export { default as ParameterSourceIndicator } from './chat/ChatSettings/ParameterSourceIndicator.svelte';
2829

2930
export { default as ChatSidebar } from './chat/ChatSidebar/ChatSidebar.svelte';
3031
export { default as ChatSidebarConversationItem } from './chat/ChatSidebar/ChatSidebarConversationItem.svelte';
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export const PRECISION_MULTIPLIER = 1000000;
2+
export const PRECISION_DECIMAL_PLACES = 6;

0 commit comments

Comments
 (0)