diff --git a/examples/server/public/index.html.gz b/examples/server/public/index.html.gz index 646988ad8..84bde767f 100644 Binary files a/examples/server/public/index.html.gz and b/examples/server/public/index.html.gz differ diff --git a/examples/server/webui/src/components/SettingDialog.tsx b/examples/server/webui/src/components/SettingDialog.tsx index ae8117fd2..eca951edc 100644 --- a/examples/server/webui/src/components/SettingDialog.tsx +++ b/examples/server/webui/src/components/SettingDialog.tsx @@ -3,6 +3,7 @@ import { useAppContext } from '../utils/app.context'; import { CONFIG_DEFAULT, CONFIG_INFO } from '../Config'; import { isDev } from '../Config'; import StorageUtils from '../utils/storage'; +import { isBoolean, isNumeric, isString } from '../utils/misc'; type SettKey = keyof typeof CONFIG_DEFAULT; @@ -52,7 +53,41 @@ export default function SettingDialog({ }; const handleSave = () => { - saveConfig(localConfig); + // copy the local config to prevent direct mutation + const newConfig: typeof CONFIG_DEFAULT = JSON.parse( + JSON.stringify(localConfig) + ); + // validate the config + for (const key in newConfig) { + const value = newConfig[key as SettKey]; + const mustBeBoolean = isBoolean(CONFIG_DEFAULT[key as SettKey]); + const mustBeString = isString(CONFIG_DEFAULT[key as SettKey]); + const mustBeNumeric = isNumeric(CONFIG_DEFAULT[key as SettKey]); + if (mustBeString) { + if (!isString(value)) { + alert(`Value for ${key} must be string`); + return; + } + } else if (mustBeNumeric) { + const trimedValue = value.toString().trim(); + const numVal = Number(trimedValue); + if (isNaN(numVal) || !isNumeric(numVal) || trimedValue.length === 0) { + alert(`Value for ${key} must be numeric`); + return; + } + // @ts-expect-error this is safe + newConfig[key] = numVal; + } else if (mustBeBoolean) { + if (!isBoolean(value)) { + alert(`Value for ${key} must be boolean`); + return; + } + } else { + console.error(`Unknown default type for key ${key}`); + } + } + if (isDev) console.log('Saving config', newConfig); + saveConfig(newConfig); onClose(); }; @@ -66,6 +101,10 @@ export default function SettingDialog({ onClose(); }; + const onChange = (key: SettKey) => (value: string | boolean) => { + setLocalConfig({ ...localConfig, [key]: value }); + }; + return ( @@ -79,9 +118,7 @@ export default function SettingDialog({ configKey="apiKey" configDefault={CONFIG_DEFAULT} value={localConfig.apiKey} - onChange={(value) => - setLocalConfig({ ...localConfig, apiKey: value }) - } + onChange={onChange('apiKey')} /> @@ -92,12 +129,7 @@ export default function SettingDialog({ className="textarea textarea-bordered h-24" placeholder={`Default: ${CONFIG_DEFAULT.systemMessage}`} value={localConfig.systemMessage} - onChange={(e) => - setLocalConfig({ - ...localConfig, - systemMessage: e.target.value, - }) - } + onChange={(e) => onChange('systemMessage')(e.target.value)} /> @@ -107,9 +139,7 @@ export default function SettingDialog({ configKey={key} configDefault={CONFIG_DEFAULT} value={localConfig[key]} - onChange={(value) => - setLocalConfig({ ...localConfig, [key]: value }) - } + onChange={onChange(key)} /> ))} @@ -123,9 +153,7 @@ export default function SettingDialog({ configKey="samplers" configDefault={CONFIG_DEFAULT} value={localConfig.samplers} - onChange={(value) => - setLocalConfig({ ...localConfig, samplers: value }) - } + onChange={onChange('samplers')} /> {OTHER_SAMPLER_KEYS.map((key) => ( - setLocalConfig({ ...localConfig, [key]: value }) - } + onChange={onChange(key)} /> ))} @@ -152,9 +178,7 @@ export default function SettingDialog({ configKey={key} configDefault={CONFIG_DEFAULT} value={localConfig[key]} - onChange={(value) => - setLocalConfig({ ...localConfig, [key]: value }) - } + onChange={onChange(key)} /> ))} @@ -171,10 +195,7 @@ export default function SettingDialog({ className="checkbox" checked={localConfig.showThoughtInProgress} onChange={(e) => - setLocalConfig({ - ...localConfig, - showThoughtInProgress: e.target.checked, - }) + onChange('showThoughtInProgress')(e.target.checked) } /> @@ -187,10 +208,7 @@ export default function SettingDialog({ className="checkbox" checked={localConfig.excludeThoughtOnReq} onChange={(e) => - setLocalConfig({ - ...localConfig, - excludeThoughtOnReq: e.target.checked, - }) + onChange('excludeThoughtOnReq')(e.target.checked) } /> @@ -220,10 +238,7 @@ export default function SettingDialog({ className="checkbox" checked={localConfig.showTokensPerSecond} onChange={(e) => - setLocalConfig({ - ...localConfig, - showTokensPerSecond: e.target.checked, - }) + onChange('showTokensPerSecond')(e.target.checked) } /> Show tokens per second @@ -245,9 +260,7 @@ export default function SettingDialog({ className="textarea textarea-bordered h-24" placeholder='Example: { "mirostat": 1, "min_p": 0.1 }' value={localConfig.custom} - onChange={(e) => - setLocalConfig({ ...localConfig, custom: e.target.value }) - } + onChange={(e) => onChange('custom')(e.target.value)} />