Skip to content

Commit a54c54a

Browse files
committed
server : (webui) fix numeric settings being saved as string
1 parent d2fe216 commit a54c54a

File tree

2 files changed

+50
-37
lines changed

2 files changed

+50
-37
lines changed
103 Bytes
Binary file not shown.

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

Lines changed: 50 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,41 @@ 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+
// @ts-expect-error this is safe
79+
newConfig[key] = numVal;
80+
} else if (mustBeBoolean) {
81+
if (!isBoolean(value)) {
82+
alert(`Value for ${key} must be boolean`);
83+
return;
84+
}
85+
} else {
86+
console.error(`Unknown default type for key ${key}`);
87+
}
88+
}
89+
if (isDev) console.log('Saving config', newConfig);
90+
saveConfig(newConfig);
5691
onClose();
5792
};
5893

@@ -66,6 +101,10 @@ export default function SettingDialog({
66101
onClose();
67102
};
68103

104+
const onChange = (key: SettKey) => (value: string | boolean) => {
105+
setLocalConfig({ ...localConfig, [key]: value });
106+
};
107+
69108
return (
70109
<dialog className={`modal ${show ? 'modal-open' : ''}`}>
71110
<div className="modal-box">
@@ -79,9 +118,7 @@ export default function SettingDialog({
79118
configKey="apiKey"
80119
configDefault={CONFIG_DEFAULT}
81120
value={localConfig.apiKey}
82-
onChange={(value) =>
83-
setLocalConfig({ ...localConfig, apiKey: value })
84-
}
121+
onChange={onChange('apiKey')}
85122
/>
86123

87124
<label className="form-control mb-2">
@@ -92,12 +129,7 @@ export default function SettingDialog({
92129
className="textarea textarea-bordered h-24"
93130
placeholder={`Default: ${CONFIG_DEFAULT.systemMessage}`}
94131
value={localConfig.systemMessage}
95-
onChange={(e) =>
96-
setLocalConfig({
97-
...localConfig,
98-
systemMessage: e.target.value,
99-
})
100-
}
132+
onChange={(e) => onChange('systemMessage')(e.target.value)}
101133
/>
102134
</label>
103135

@@ -107,9 +139,7 @@ export default function SettingDialog({
107139
configKey={key}
108140
configDefault={CONFIG_DEFAULT}
109141
value={localConfig[key]}
110-
onChange={(value) =>
111-
setLocalConfig({ ...localConfig, [key]: value })
112-
}
142+
onChange={onChange(key)}
113143
/>
114144
))}
115145

@@ -123,19 +153,15 @@ export default function SettingDialog({
123153
configKey="samplers"
124154
configDefault={CONFIG_DEFAULT}
125155
value={localConfig.samplers}
126-
onChange={(value) =>
127-
setLocalConfig({ ...localConfig, samplers: value })
128-
}
156+
onChange={onChange('samplers')}
129157
/>
130158
{OTHER_SAMPLER_KEYS.map((key) => (
131159
<SettingsModalShortInput
132160
key={key}
133161
configKey={key}
134162
configDefault={CONFIG_DEFAULT}
135163
value={localConfig[key]}
136-
onChange={(value) =>
137-
setLocalConfig({ ...localConfig, [key]: value })
138-
}
164+
onChange={onChange(key)}
139165
/>
140166
))}
141167
</div>
@@ -152,9 +178,7 @@ export default function SettingDialog({
152178
configKey={key}
153179
configDefault={CONFIG_DEFAULT}
154180
value={localConfig[key]}
155-
onChange={(value) =>
156-
setLocalConfig({ ...localConfig, [key]: value })
157-
}
181+
onChange={onChange(key)}
158182
/>
159183
))}
160184
</div>
@@ -171,10 +195,7 @@ export default function SettingDialog({
171195
className="checkbox"
172196
checked={localConfig.showThoughtInProgress}
173197
onChange={(e) =>
174-
setLocalConfig({
175-
...localConfig,
176-
showThoughtInProgress: e.target.checked,
177-
})
198+
onChange('showThoughtInProgress')(e.target.checked)
178199
}
179200
/>
180201
<span className="ml-4">
@@ -187,10 +208,7 @@ export default function SettingDialog({
187208
className="checkbox"
188209
checked={localConfig.excludeThoughtOnReq}
189210
onChange={(e) =>
190-
setLocalConfig({
191-
...localConfig,
192-
excludeThoughtOnReq: e.target.checked,
193-
})
211+
onChange('excludeThoughtOnReq')(e.target.checked)
194212
}
195213
/>
196214
<span className="ml-4">
@@ -220,10 +238,7 @@ export default function SettingDialog({
220238
className="checkbox"
221239
checked={localConfig.showTokensPerSecond}
222240
onChange={(e) =>
223-
setLocalConfig({
224-
...localConfig,
225-
showTokensPerSecond: e.target.checked,
226-
})
241+
onChange('showTokensPerSecond')(e.target.checked)
227242
}
228243
/>
229244
<span className="ml-4">Show tokens per second</span>
@@ -245,9 +260,7 @@ export default function SettingDialog({
245260
className="textarea textarea-bordered h-24"
246261
placeholder='Example: { "mirostat": 1, "min_p": 0.1 }'
247262
value={localConfig.custom}
248-
onChange={(e) =>
249-
setLocalConfig({ ...localConfig, custom: e.target.value })
250-
}
263+
onChange={(e) => onChange('custom')(e.target.value)}
251264
/>
252265
</label>
253266
</div>

0 commit comments

Comments
 (0)