@@ -3,6 +3,7 @@ import { useAppContext } from '../utils/app.context';
33import { CONFIG_DEFAULT , CONFIG_INFO } from '../Config' ;
44import { isDev } from '../Config' ;
55import StorageUtils from '../utils/storage' ;
6+ import { isBoolean , isNumeric , isString } from '../utils/misc' ;
67
78type 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