Skip to content

Commit 0cf8671

Browse files
authored
server : (webui) fix numeric settings being saved as string (#11739)
* server : (webui) fix numeric settings being saved as string * add some more comments
1 parent d2fe216 commit 0cf8671

File tree

3 files changed

+53
-37
lines changed

3 files changed

+53
-37
lines changed

examples/server/public/index.html.gz

103 Bytes
Binary file not shown.

examples/server/webui/src/components/MarkdownDisplay.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export default function MarkdownDisplay({ content }: { content: string }) {
2323
button: (props) => (
2424
<CopyCodeButton {...props} origContent={preprocessedContent} />
2525
),
26+
// note: do not use "pre", "p" or other basic html elements here, it will cause the node to re-render when the message is being generated (this should be a bug with react-markdown, not sure how to fix it)
2627
}}
2728
>
2829
{preprocessedContent}

examples/server/webui/src/components/SettingDialog.tsx

Lines changed: 52 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useAppContext } from '../utils/app.context';
33
import { CONFIG_DEFAULT, CONFIG_INFO } from '../Config';
44
import { isDev } from '../Config';
55
import StorageUtils from '../utils/storage';
6+
import { isBoolean, isNumeric, isString } from '../utils/misc';
67

78
type SettKey = keyof typeof CONFIG_DEFAULT;
89

@@ -52,7 +53,42 @@ export default function SettingDialog({
5253
};
5354

5455
const handleSave = () => {
55-
saveConfig(localConfig);
56+
// copy the local config to prevent direct mutation
57+
const newConfig: typeof CONFIG_DEFAULT = JSON.parse(
58+
JSON.stringify(localConfig)
59+
);
60+
// validate the config
61+
for (const key in newConfig) {
62+
const value = newConfig[key as SettKey];
63+
const mustBeBoolean = isBoolean(CONFIG_DEFAULT[key as SettKey]);
64+
const mustBeString = isString(CONFIG_DEFAULT[key as SettKey]);
65+
const mustBeNumeric = isNumeric(CONFIG_DEFAULT[key as SettKey]);
66+
if (mustBeString) {
67+
if (!isString(value)) {
68+
alert(`Value for ${key} must be string`);
69+
return;
70+
}
71+
} else if (mustBeNumeric) {
72+
const trimedValue = value.toString().trim();
73+
const numVal = Number(trimedValue);
74+
if (isNaN(numVal) || !isNumeric(numVal) || trimedValue.length === 0) {
75+
alert(`Value for ${key} must be numeric`);
76+
return;
77+
}
78+
// force conversion to number
79+
// @ts-expect-error this is safe
80+
newConfig[key] = numVal;
81+
} else if (mustBeBoolean) {
82+
if (!isBoolean(value)) {
83+
alert(`Value for ${key} must be boolean`);
84+
return;
85+
}
86+
} else {
87+
console.error(`Unknown default type for key ${key}`);
88+
}
89+
}
90+
if (isDev) console.log('Saving config', newConfig);
91+
saveConfig(newConfig);
5692
onClose();
5793
};
5894

@@ -66,6 +102,11 @@ export default function SettingDialog({
66102
onClose();
67103
};
68104

105+
const onChange = (key: SettKey) => (value: string | boolean) => {
106+
// note: we do not perform validation here, because we may get incomplete value as user is still typing it
107+
setLocalConfig({ ...localConfig, [key]: value });
108+
};
109+
69110
return (
70111
<dialog className={`modal ${show ? 'modal-open' : ''}`}>
71112
<div className="modal-box">
@@ -79,9 +120,7 @@ export default function SettingDialog({
79120
configKey="apiKey"
80121
configDefault={CONFIG_DEFAULT}
81122
value={localConfig.apiKey}
82-
onChange={(value) =>
83-
setLocalConfig({ ...localConfig, apiKey: value })
84-
}
123+
onChange={onChange('apiKey')}
85124
/>
86125

87126
<label className="form-control mb-2">
@@ -92,12 +131,7 @@ export default function SettingDialog({
92131
className="textarea textarea-bordered h-24"
93132
placeholder={`Default: ${CONFIG_DEFAULT.systemMessage}`}
94133
value={localConfig.systemMessage}
95-
onChange={(e) =>
96-
setLocalConfig({
97-
...localConfig,
98-
systemMessage: e.target.value,
99-
})
100-
}
134+
onChange={(e) => onChange('systemMessage')(e.target.value)}
101135
/>
102136
</label>
103137

@@ -107,9 +141,7 @@ export default function SettingDialog({
107141
configKey={key}
108142
configDefault={CONFIG_DEFAULT}
109143
value={localConfig[key]}
110-
onChange={(value) =>
111-
setLocalConfig({ ...localConfig, [key]: value })
112-
}
144+
onChange={onChange(key)}
113145
/>
114146
))}
115147

@@ -123,19 +155,15 @@ export default function SettingDialog({
123155
configKey="samplers"
124156
configDefault={CONFIG_DEFAULT}
125157
value={localConfig.samplers}
126-
onChange={(value) =>
127-
setLocalConfig({ ...localConfig, samplers: value })
128-
}
158+
onChange={onChange('samplers')}
129159
/>
130160
{OTHER_SAMPLER_KEYS.map((key) => (
131161
<SettingsModalShortInput
132162
key={key}
133163
configKey={key}
134164
configDefault={CONFIG_DEFAULT}
135165
value={localConfig[key]}
136-
onChange={(value) =>
137-
setLocalConfig({ ...localConfig, [key]: value })
138-
}
166+
onChange={onChange(key)}
139167
/>
140168
))}
141169
</div>
@@ -152,9 +180,7 @@ export default function SettingDialog({
152180
configKey={key}
153181
configDefault={CONFIG_DEFAULT}
154182
value={localConfig[key]}
155-
onChange={(value) =>
156-
setLocalConfig({ ...localConfig, [key]: value })
157-
}
183+
onChange={onChange(key)}
158184
/>
159185
))}
160186
</div>
@@ -171,10 +197,7 @@ export default function SettingDialog({
171197
className="checkbox"
172198
checked={localConfig.showThoughtInProgress}
173199
onChange={(e) =>
174-
setLocalConfig({
175-
...localConfig,
176-
showThoughtInProgress: e.target.checked,
177-
})
200+
onChange('showThoughtInProgress')(e.target.checked)
178201
}
179202
/>
180203
<span className="ml-4">
@@ -187,10 +210,7 @@ export default function SettingDialog({
187210
className="checkbox"
188211
checked={localConfig.excludeThoughtOnReq}
189212
onChange={(e) =>
190-
setLocalConfig({
191-
...localConfig,
192-
excludeThoughtOnReq: e.target.checked,
193-
})
213+
onChange('excludeThoughtOnReq')(e.target.checked)
194214
}
195215
/>
196216
<span className="ml-4">
@@ -220,10 +240,7 @@ export default function SettingDialog({
220240
className="checkbox"
221241
checked={localConfig.showTokensPerSecond}
222242
onChange={(e) =>
223-
setLocalConfig({
224-
...localConfig,
225-
showTokensPerSecond: e.target.checked,
226-
})
243+
onChange('showTokensPerSecond')(e.target.checked)
227244
}
228245
/>
229246
<span className="ml-4">Show tokens per second</span>
@@ -245,9 +262,7 @@ export default function SettingDialog({
245262
className="textarea textarea-bordered h-24"
246263
placeholder='Example: { "mirostat": 1, "min_p": 0.1 }'
247264
value={localConfig.custom}
248-
onChange={(e) =>
249-
setLocalConfig({ ...localConfig, custom: e.target.value })
250-
}
265+
onChange={(e) => onChange('custom')(e.target.value)}
251266
/>
252267
</label>
253268
</div>

0 commit comments

Comments
 (0)