Skip to content

Commit de7add3

Browse files
committed
refactor: Chat Settings Dialog structure
1 parent 362f743 commit de7add3

File tree

2 files changed

+203
-48
lines changed

2 files changed

+203
-48
lines changed

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

Lines changed: 171 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
<script lang="ts">
2-
import { Settings, Filter, Hand, MessageSquare, Plus, Beaker } from '@lucide/svelte';
2+
import { Settings, Filter, AlertTriangle, Brain, Cog } from '@lucide/svelte';
33
import { ChatSettingsFooter, ChatSettingsSection } from '$lib/components/app';
44
import { Checkbox } from '$lib/components/ui/checkbox';
55
import * as Dialog from '$lib/components/ui/dialog';
66
import { Input } from '$lib/components/ui/input';
77
import { ScrollArea } from '$lib/components/ui/scroll-area';
88
import { Textarea } from '$lib/components/ui/textarea';
9-
import { SETTING_CONFIG_DEFAULT } from '$lib/constants/settings-config';
9+
import { SETTING_CONFIG_DEFAULT, SETTING_CONFIG_INFO } from '$lib/constants/settings-config';
1010
import {
1111
config,
1212
updateMultipleConfig,
@@ -32,8 +32,38 @@
3232
const defaultConfig = SETTING_CONFIG_DEFAULT;
3333
3434
function handleSave() {
35-
updateMultipleConfig(localConfig);
35+
// Validate custom JSON if provided
36+
if (localConfig.custom && typeof localConfig.custom === 'string' && localConfig.custom.trim()) {
37+
try {
38+
JSON.parse(localConfig.custom);
39+
} catch (error) {
40+
alert('Invalid JSON in custom parameters. Please check the format and try again.');
41+
return;
42+
}
43+
}
44+
45+
// Convert numeric strings to numbers for numeric fields
46+
const processedConfig = { ...localConfig };
47+
const numericFields = [
48+
'temperature', 'top_k', 'top_p', 'min_p', 'max_tokens', 'pasteLongTextToFileLen',
49+
'dynatemp_range', 'dynatemp_exponent', 'typical_p', 'xtc_probability', 'xtc_threshold',
50+
'repeat_last_n', 'repeat_penalty', 'presence_penalty', 'frequency_penalty',
51+
'dry_multiplier', 'dry_base', 'dry_allowed_length', 'dry_penalty_last_n'
52+
];
53+
54+
for (const field of numericFields) {
55+
if (processedConfig[field] !== undefined && processedConfig[field] !== '') {
56+
const numValue = Number(processedConfig[field]);
57+
if (!isNaN(numValue)) {
58+
processedConfig[field] = numValue;
59+
} else {
60+
alert(`Invalid numeric value for ${field}. Please enter a valid number.`);
61+
return;
62+
}
63+
}
64+
}
3665
66+
updateMultipleConfig(processedConfig);
3767
onOpenChange?.(false);
3868
}
3969
@@ -64,77 +94,170 @@
6494
label: 'System Message (will be disabled if left empty)',
6595
type: 'textarea'
6696
},
67-
{ key: 'temperature', label: 'Temperature', type: 'input' },
68-
{ key: 'top_k', label: 'Top K', type: 'input' },
69-
{ key: 'top_p', label: 'Top P', type: 'input' },
70-
{ key: 'min_p', label: 'Min P', type: 'input' },
71-
{ key: 'max_tokens', label: 'Max Tokens', type: 'input' },
72-
{ key: 'pasteLongTextToFileLen', label: 'Paste length to file', type: 'input' },
73-
{ key: 'pdfAsImage', label: 'Parse PDF as image instead of text', type: 'checkbox' }
97+
{
98+
key: 'showTokensPerSecond',
99+
label: 'Show tokens per second',
100+
type: 'checkbox'
101+
},
102+
{
103+
key: 'pasteLongTextToFileLen',
104+
label: 'Paste long text to file length',
105+
type: 'input'
106+
},
107+
{
108+
key: 'pdfAsImage',
109+
label: 'Parse PDF as image',
110+
type: 'checkbox'
111+
}
74112
]
75113
},
76114
{
77115
title: 'Samplers',
78116
icon: Filter,
79117
fields: [
80-
{ key: 'samplers', label: 'Samplers queue', type: 'input' },
81-
{ key: 'dynatemp_range', label: 'Dynamic Temperature Range', type: 'input' },
82-
{ key: 'dynatemp_exponent', label: 'Dynamic Temperature Exponent', type: 'input' },
83-
{ key: 'typical_p', label: 'Typical P', type: 'input' },
84-
{ key: 'xtc_probability', label: 'XTC Probability', type: 'input' },
85-
{ key: 'xtc_threshold', label: 'XTC Threshold', type: 'input' }
118+
{
119+
key: 'samplers',
120+
label: 'Samplers',
121+
type: 'input'
122+
}
86123
]
87124
},
88125
{
89126
title: 'Penalties',
90-
icon: Hand,
127+
icon: AlertTriangle,
91128
fields: [
92-
{ key: 'repeat_last_n', label: 'Repeat Last N', type: 'input' },
93-
{ key: 'repeat_penalty', label: 'Repeat Penalty', type: 'input' },
94-
{ key: 'presence_penalty', label: 'Presence Penalty', type: 'input' },
95-
{ key: 'frequency_penalty', label: 'Frequency Penalty', type: 'input' },
96-
{ key: 'dry_multiplier', label: 'DRY Multiplier', type: 'input' },
97-
{ key: 'dry_base', label: 'DRY Base', type: 'input' },
98-
{ key: 'dry_allowed_length', label: 'DRY Allowed Length', type: 'input' },
99-
{ key: 'dry_penalty_last_n', label: 'DRY Penalty Last N', type: 'input' }
129+
{
130+
key: 'repeat_last_n',
131+
label: 'Repeat last N',
132+
type: 'input'
133+
},
134+
{
135+
key: 'repeat_penalty',
136+
label: 'Repeat penalty',
137+
type: 'input'
138+
},
139+
{
140+
key: 'presence_penalty',
141+
label: 'Presence penalty',
142+
type: 'input'
143+
},
144+
{
145+
key: 'frequency_penalty',
146+
label: 'Frequency penalty',
147+
type: 'input'
148+
},
149+
{
150+
key: 'dry_multiplier',
151+
label: 'DRY multiplier',
152+
type: 'input'
153+
},
154+
{
155+
key: 'dry_base',
156+
label: 'DRY base',
157+
type: 'input'
158+
},
159+
{
160+
key: 'dry_allowed_length',
161+
label: 'DRY allowed length',
162+
type: 'input'
163+
},
164+
{
165+
key: 'dry_penalty_last_n',
166+
label: 'DRY penalty last N',
167+
type: 'input'
168+
}
100169
]
101170
},
102171
{
103172
title: 'Reasoning',
104-
icon: MessageSquare,
173+
icon: Brain,
105174
fields: [
106175
{
107176
key: 'showThoughtInProgress',
108-
label: 'Expand thought process by default when generating messages',
177+
label: 'Show thought in progress',
109178
type: 'checkbox'
110179
},
111180
{
112181
key: 'excludeThoughtOnReq',
113-
label: 'Exclude thought process when sending requests to API (Recommended for DeepSeek-R1)',
182+
label: 'Exclude thought on request',
114183
type: 'checkbox'
115184
}
116185
]
117186
},
118187
{
119188
title: 'Advanced',
120-
icon: Plus,
121-
fields: [
122-
{ key: 'showTokensPerSecond', label: 'Show tokens per second', type: 'checkbox' },
123-
{ key: 'custom', label: 'Custom parameters (JSON format)', type: 'textarea' }
124-
]
125-
},
126-
{
127-
title: 'Experimental',
128-
icon: Beaker,
189+
icon: Cog,
129190
fields: [
130191
{
131-
key: 'pyInterpreterEnabled',
132-
label: 'Enable Python interpreter',
133-
type: 'checkbox',
134-
help: 'This feature uses Pyodide to run Python code inside a Markdown code block. You will see a "Run" button on the code block, near the "Copy" button.'
192+
key: 'temperature',
193+
label: 'Temperature',
194+
type: 'input'
195+
},
196+
{
197+
key: 'dynatemp_range',
198+
label: 'Dynamic temperature range',
199+
type: 'input'
200+
},
201+
{
202+
key: 'dynatemp_exponent',
203+
label: 'Dynamic temperature exponent',
204+
type: 'input'
205+
},
206+
{
207+
key: 'top_k',
208+
label: 'Top K',
209+
type: 'input'
210+
},
211+
{
212+
key: 'top_p',
213+
label: 'Top P',
214+
type: 'input'
215+
},
216+
{
217+
key: 'min_p',
218+
label: 'Min P',
219+
type: 'input'
220+
},
221+
{
222+
key: 'xtc_probability',
223+
label: 'XTC probability',
224+
type: 'input'
225+
},
226+
{
227+
key: 'xtc_threshold',
228+
label: 'XTC threshold',
229+
type: 'input'
230+
},
231+
{
232+
key: 'typical_p',
233+
label: 'Typical P',
234+
type: 'input'
235+
},
236+
{
237+
key: 'max_tokens',
238+
label: 'Max tokens',
239+
type: 'input'
240+
},
241+
{
242+
key: 'custom',
243+
label: 'Custom JSON',
244+
type: 'textarea'
135245
}
136246
]
137247
}
248+
// TODO: Experimental features section will be implemented after initial release
249+
// This includes Python interpreter (Pyodide integration) and other experimental features
250+
// {
251+
// title: 'Experimental',
252+
// icon: Beaker,
253+
// fields: [
254+
// {
255+
// key: 'pyInterpreterEnabled',
256+
// label: 'Enable Python interpreter',
257+
// type: 'checkbox'
258+
// }
259+
// ]
260+
// }
138261
];
139262
140263
let currentSection = $derived(
@@ -166,6 +289,7 @@
166289

167290
<ScrollArea class="flex-1">
168291
<div class="space-y-6 p-6">
292+
169293
<ChatSettingsSection title={currentSection.title} icon={currentSection.icon}>
170294
{#each currentSection.fields as field}
171295
<div class="space-y-2">
@@ -182,9 +306,9 @@
182306
placeholder={`Default: ${defaultConfig[field.key] || 'none'}`}
183307
class="max-w-md"
184308
/>
185-
{#if field.help}
309+
{#if field.help || SETTING_CONFIG_INFO[field.key]}
186310
<p class="text-muted-foreground mt-1 text-xs">
187-
{field.help}
311+
{field.help || SETTING_CONFIG_INFO[field.key]}
188312
</p>
189313
{/if}
190314
{:else if field.type === 'textarea'}
@@ -200,9 +324,9 @@
200324
placeholder={`Default: ${defaultConfig[field.key] || 'none'}`}
201325
class="min-h-[100px] max-w-2xl"
202326
/>
203-
{#if field.help}
327+
{#if field.help || SETTING_CONFIG_INFO[field.key]}
204328
<p class="text-muted-foreground mt-1 text-xs">
205-
{field.help}
329+
{field.help || SETTING_CONFIG_INFO[field.key]}
206330
</p>
207331
{/if}
208332
{:else if field.type === 'checkbox'}
@@ -225,9 +349,9 @@
225349
{field.label}
226350
</label>
227351

228-
{#if field.help}
352+
{#if field.help || SETTING_CONFIG_INFO[field.key]}
229353
<p class="text-muted-foreground text-xs">
230-
{field.help}
354+
{field.help || SETTING_CONFIG_INFO[field.key]}
231355
</p>
232356
{:else if field.key === 'pdfAsImage' && !supportsVision()}
233357
<p class="text-muted-foreground text-xs">

tools/server/webui/src/lib/constants/settings-config.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export const SETTING_CONFIG_DEFAULT: Record<string, string | number | boolean> =
66
showTokensPerSecond: false,
77
showThoughtInProgress: true,
88
excludeThoughtOnReq: false,
9-
pasteLongTextToFileLen: 2000,
9+
pasteLongTextToFileLen: 2500,
1010
pdfAsImage: false,
1111
// make sure these default values are in sync with `common.h`
1212
samplers: 'top_k;tfs_z;typical_p;top_p;min_p;temperature',
@@ -31,4 +31,35 @@ export const SETTING_CONFIG_DEFAULT: Record<string, string | number | boolean> =
3131
custom: '', // custom json-stringified object
3232
// experimental features
3333
pyInterpreterEnabled: false
34+
};
35+
36+
export const SETTING_CONFIG_INFO: Record<string, string> = {
37+
apiKey: 'Set the API Key if you are using --api-key option for the server.',
38+
systemMessage: 'The starting message that defines how model should behave.',
39+
pasteLongTextToFileLen: 'On pasting long text, it will be converted to a file. You can control the file length by setting the value of this parameter. Value 0 means disable.',
40+
samplers: 'The order at which samplers are applied, in simplified way. Default is "top_k;tfs_z;typical_p;top_p;min_p;temperature": top_k->tfs_z->typical_p->top_p->min_p->temperature',
41+
temperature: 'Controls the randomness of the generated text by affecting the probability distribution of the output tokens. Higher = more random, lower = more focused.',
42+
dynatemp_range: 'Addon for the temperature sampler. The added value to the range of dynamic temperature, which adjusts probabilities by entropy of tokens.',
43+
dynatemp_exponent: 'Addon for the temperature sampler. Smoothes out the probability redistribution based on the most probable token.',
44+
top_k: 'Keeps only k top tokens.',
45+
top_p: 'Limits tokens to those that together have a cumulative probability of at least p',
46+
min_p: 'Limits tokens based on the minimum probability for a token to be considered, relative to the probability of the most likely token.',
47+
xtc_probability: 'XTC sampler cuts out top tokens; this parameter controls the chance of cutting tokens at all. 0 disables XTC.',
48+
xtc_threshold: 'XTC sampler cuts out top tokens; this parameter controls the token probability that is required to cut that token.',
49+
typical_p: 'Sorts and limits tokens based on the difference between log-probability and entropy.',
50+
repeat_last_n: 'Last n tokens to consider for penalizing repetition',
51+
repeat_penalty: 'Controls the repetition of token sequences in the generated text',
52+
presence_penalty: 'Limits tokens based on whether they appear in the output or not.',
53+
frequency_penalty: 'Limits tokens based on how often they appear in the output.',
54+
dry_multiplier: 'DRY sampling reduces repetition in generated text even across long contexts. This parameter sets the DRY sampling multiplier.',
55+
dry_base: 'DRY sampling reduces repetition in generated text even across long contexts. This parameter sets the DRY sampling base value.',
56+
dry_allowed_length: 'DRY sampling reduces repetition in generated text even across long contexts. This parameter sets the allowed length for DRY sampling.',
57+
dry_penalty_last_n: 'DRY sampling reduces repetition in generated text even across long contexts. This parameter sets DRY penalty for the last n tokens.',
58+
max_tokens: 'The maximum number of token per output.',
59+
custom: 'Custom JSON parameters to send to the API. Must be valid JSON format.',
60+
showTokensPerSecond: 'Display generation speed in tokens per second during streaming.',
61+
showThoughtInProgress: 'Expand thought process by default when generating messages.',
62+
excludeThoughtOnReq: 'Exclude thought process when sending requests to API (Recommended for DeepSeek-R1).',
63+
pdfAsImage: 'Parse PDF as image instead of text (requires vision-capable model).',
64+
pyInterpreterEnabled: 'Enable Python interpreter using Pyodide. Allows running Python code in markdown code blocks.'
3465
};

0 commit comments

Comments
 (0)