]> git.djapps.eu Git - pkg/ggml/sources/llama.cpp/commitdiff
server : (webui) fix numeric settings being saved as string (#11739)
authorXuan-Son Nguyen <redacted>
Sat, 8 Feb 2025 09:42:34 +0000 (10:42 +0100)
committerGitHub <redacted>
Sat, 8 Feb 2025 09:42:34 +0000 (10:42 +0100)
* server : (webui) fix numeric settings being saved as string

* add some more comments

examples/server/public/index.html.gz
examples/server/webui/src/components/MarkdownDisplay.tsx
examples/server/webui/src/components/SettingDialog.tsx

index 646988ad8e494e1bcccfd2fba6f549796aebd983..84bde767fba7f5ad4a7029b3cded5ebe61e4fdbf 100644 (file)
Binary files a/examples/server/public/index.html.gz and b/examples/server/public/index.html.gz differ
index 8ab8de6550779e63a7e7f5d3f62dd7b91e69d8e5..814920a74deaa5ee55cfe5e35a5d701082ae7c5f 100644 (file)
@@ -23,6 +23,7 @@ export default function MarkdownDisplay({ content }: { content: string }) {
         button: (props) => (
           <CopyCodeButton {...props} origContent={preprocessedContent} />
         ),
+        // 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)
       }}
     >
       {preprocessedContent}
index ae8117fd212fb9aea495706b58ea07173d297d36..5565ab7bbda62a540fe0e867f5c2053136d58a5e 100644 (file)
@@ -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,42 @@ 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;
+        }
+        // force conversion to number
+        // @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 +102,11 @@ export default function SettingDialog({
     onClose();
   };
 
+  const onChange = (key: SettKey) => (value: string | boolean) => {
+    // note: we do not perform validation here, because we may get incomplete value as user is still typing it
+    setLocalConfig({ ...localConfig, [key]: value });
+  };
+
   return (
     <dialog className={`modal ${show ? 'modal-open' : ''}`}>
       <div className="modal-box">
@@ -79,9 +120,7 @@ export default function SettingDialog({
             configKey="apiKey"
             configDefault={CONFIG_DEFAULT}
             value={localConfig.apiKey}
-            onChange={(value) =>
-              setLocalConfig({ ...localConfig, apiKey: value })
-            }
+            onChange={onChange('apiKey')}
           />
 
           <label className="form-control mb-2">
@@ -92,12 +131,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)}
             />
           </label>
 
@@ -107,9 +141,7 @@ export default function SettingDialog({
               configKey={key}
               configDefault={CONFIG_DEFAULT}
               value={localConfig[key]}
-              onChange={(value) =>
-                setLocalConfig({ ...localConfig, [key]: value })
-              }
+              onChange={onChange(key)}
             />
           ))}
 
@@ -123,9 +155,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) => (
                 <SettingsModalShortInput
@@ -133,9 +163,7 @@ export default function SettingDialog({
                   configKey={key}
                   configDefault={CONFIG_DEFAULT}
                   value={localConfig[key]}
-                  onChange={(value) =>
-                    setLocalConfig({ ...localConfig, [key]: value })
-                  }
+                  onChange={onChange(key)}
                 />
               ))}
             </div>
@@ -152,9 +180,7 @@ export default function SettingDialog({
                   configKey={key}
                   configDefault={CONFIG_DEFAULT}
                   value={localConfig[key]}
-                  onChange={(value) =>
-                    setLocalConfig({ ...localConfig, [key]: value })
-                  }
+                  onChange={onChange(key)}
                 />
               ))}
             </div>
@@ -171,10 +197,7 @@ export default function SettingDialog({
                   className="checkbox"
                   checked={localConfig.showThoughtInProgress}
                   onChange={(e) =>
-                    setLocalConfig({
-                      ...localConfig,
-                      showThoughtInProgress: e.target.checked,
-                    })
+                    onChange('showThoughtInProgress')(e.target.checked)
                   }
                 />
                 <span className="ml-4">
@@ -187,10 +210,7 @@ export default function SettingDialog({
                   className="checkbox"
                   checked={localConfig.excludeThoughtOnReq}
                   onChange={(e) =>
-                    setLocalConfig({
-                      ...localConfig,
-                      excludeThoughtOnReq: e.target.checked,
-                    })
+                    onChange('excludeThoughtOnReq')(e.target.checked)
                   }
                 />
                 <span className="ml-4">
@@ -220,10 +240,7 @@ export default function SettingDialog({
                   className="checkbox"
                   checked={localConfig.showTokensPerSecond}
                   onChange={(e) =>
-                    setLocalConfig({
-                      ...localConfig,
-                      showTokensPerSecond: e.target.checked,
-                    })
+                    onChange('showTokensPerSecond')(e.target.checked)
                   }
                 />
                 <span className="ml-4">Show tokens per second</span>
@@ -245,9 +262,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)}
                 />
               </label>
             </div>