@@ -105,6 +105,7 @@ type CustomModelSectionProps = NewCustomModelSectionProps | ExistingCustomModelS
105105const CustomModelSection = ( { isNew, onChange, model : customModel } : CustomModelSectionProps ) => {
106106 const id = customModel ?. id || "" ;
107107 const [ isEditing , setIsEditing ] = useState ( isNew ) ;
108+ const [ isSaving , setIsSaving ] = useState ( false ) ;
108109 const [ baseUrl , setBaseUrl ] = useState ( customModel ?. baseUrl || "" ) ;
109110 const [ slug , setSlug ] = useState ( customModel ?. slug ?? "" ) ;
110111 const [ apiKey , setApiKey ] = useState ( customModel ?. apiKey || "" ) ;
@@ -126,6 +127,8 @@ const CustomModelSection = ({ isNew, onChange, model: customModel }: CustomModel
126127 const errorInputClassName = "!border-red-500 focus:!border-red-500" ;
127128
128129 const handleOnChange = async ( isDelete : boolean ) => {
130+ if ( isSaving ) return ;
131+
129132 if (
130133 modelName . trim ( ) . length < 1 ||
131134 slug . trim ( ) . length < 1 ||
@@ -139,32 +142,39 @@ const CustomModelSection = ({ isNew, onChange, model: customModel }: CustomModel
139142 return ;
140143 }
141144
142- await onChange (
143- {
144- id : id ,
145- name : modelName . trim ( ) ,
146- baseUrl : baseUrl . trim ( ) ,
147- slug : slug . trim ( ) ,
148- apiKey : apiKey . trim ( ) ,
149- contextWindow : contextWindow ,
150- maxOutput : maxOutput ,
151- inputPrice : inputPrice ,
152- outputPrice : outputPrice ,
153- } ,
154- isDelete ,
155- ) ;
145+ const isSaveAction = ! isDelete ;
146+ if ( isSaveAction ) setIsSaving ( true ) ;
156147
157- if ( isNew ) {
158- setModelName ( "" ) ;
159- setBaseUrl ( "" ) ;
160- setSlug ( "" ) ;
161- setApiKey ( "" ) ;
162- setContextWindow ( 0 ) ;
163- setMaxOutput ( 0 ) ;
164- setInputPrice ( 0 ) ;
165- setOutputPrice ( 0 ) ;
166- } else {
167- setIsEditing ( false ) ;
148+ try {
149+ await onChange (
150+ {
151+ id : id ,
152+ name : modelName . trim ( ) ,
153+ baseUrl : baseUrl . trim ( ) ,
154+ slug : slug . trim ( ) ,
155+ apiKey : apiKey . trim ( ) ,
156+ contextWindow : contextWindow ,
157+ maxOutput : maxOutput ,
158+ inputPrice : inputPrice ,
159+ outputPrice : outputPrice ,
160+ } ,
161+ isDelete ,
162+ ) ;
163+
164+ if ( isNew ) {
165+ setModelName ( "" ) ;
166+ setBaseUrl ( "" ) ;
167+ setSlug ( "" ) ;
168+ setApiKey ( "" ) ;
169+ setContextWindow ( 0 ) ;
170+ setMaxOutput ( 0 ) ;
171+ setInputPrice ( 0 ) ;
172+ setOutputPrice ( 0 ) ;
173+ } else if ( isSaveAction ) {
174+ setIsEditing ( false ) ;
175+ }
176+ } finally {
177+ if ( isSaveAction ) setIsSaving ( false ) ;
168178 }
169179 } ;
170180
@@ -176,7 +186,7 @@ const CustomModelSection = ({ isNew, onChange, model: customModel }: CustomModel
176186 value = { modelName }
177187 placeholder = "My Model"
178188 type = "text"
179- disabled = { ! isEditing }
189+ disabled = { ! isEditing || isSaving }
180190 onChange = { ( e ) => {
181191 setIsModelNameValid ( true ) ;
182192 setModelName ( e . target . value ) ;
@@ -185,8 +195,16 @@ const CustomModelSection = ({ isNew, onChange, model: customModel }: CustomModel
185195
186196 { isNew ? (
187197 < Tooltip content = "Add" placement = "bottom" className = "noselect" delay = { 500 } >
188- < button onClick = { ( ) => handleOnChange ( false ) } className = "p-1 hover:bg-default-100 rounded" >
189- < Icon icon = "tabler:device-floppy" width = "16" />
198+ < button
199+ onClick = { ( ) => handleOnChange ( false ) }
200+ disabled = { isSaving }
201+ className = "p-1 hover:bg-default-100 rounded disabled:opacity-60"
202+ >
203+ < Icon
204+ icon = { isSaving ? "tabler:loader-2" : "tabler:device-floppy" }
205+ width = "16"
206+ className = { isSaving ? "animate-spin" : "" }
207+ />
190208 </ button >
191209 </ Tooltip >
192210 ) : (
@@ -200,13 +218,22 @@ const CustomModelSection = ({ isNew, onChange, model: customModel }: CustomModel
200218 setIsEditing ( true ) ;
201219 }
202220 } }
203- className = "p-1 hover:bg-default-100 rounded"
221+ disabled = { isSaving }
222+ className = "p-1 hover:bg-default-100 rounded disabled:opacity-60"
204223 >
205- < Icon icon = { isEditing ? "tabler:device-floppy" : "tabler:pencil" } width = "16" />
224+ < Icon
225+ icon = { isEditing ? ( isSaving ? "tabler:loader-2" : "tabler:device-floppy" ) : "tabler:pencil" }
226+ width = "16"
227+ className = { isEditing && isSaving ? "animate-spin" : "" }
228+ />
206229 </ button >
207230 </ Tooltip >
208231 < Tooltip content = "Delete" placement = "bottom" className = "noselect" delay = { 500 } >
209- < button onClick = { ( ) => handleOnChange ( true ) } className = "p-1 hover:bg-default-100 rounded" >
232+ < button
233+ onClick = { ( ) => handleOnChange ( true ) }
234+ disabled = { isSaving }
235+ className = "p-1 hover:bg-default-100 rounded disabled:opacity-60"
236+ >
210237 < Icon icon = "tabler:trash" width = "16" />
211238 </ button >
212239 </ Tooltip >
@@ -221,7 +248,7 @@ const CustomModelSection = ({ isNew, onChange, model: customModel }: CustomModel
221248 value = { slug }
222249 placeholder = "e.g., gemini-2.5-flash"
223250 type = "text"
224- disabled = { ! isEditing }
251+ disabled = { ! isEditing || isSaving }
225252 onChange = { ( e ) => {
226253 setIsSlugValid ( true ) ;
227254 setSlug ( e . target . value ) ;
@@ -236,7 +263,7 @@ const CustomModelSection = ({ isNew, onChange, model: customModel }: CustomModel
236263 value = { baseUrl }
237264 placeholder = "An OpenAI-compatible endpoint"
238265 type = "text"
239- disabled = { ! isEditing }
266+ disabled = { ! isEditing || isSaving }
240267 onChange = { ( e ) => {
241268 setIsBaseUrlValid ( true ) ;
242269 setBaseUrl ( e . target . value ) ;
@@ -251,7 +278,7 @@ const CustomModelSection = ({ isNew, onChange, model: customModel }: CustomModel
251278 value = { apiKey }
252279 placeholder = "Your API Key"
253280 type = { ! isEditing && ! isNew ? "password" : "text" }
254- disabled = { ! isEditing }
281+ disabled = { ! isEditing || isSaving }
255282 onChange = { ( e ) => {
256283 setIsApiKeyValid ( true ) ;
257284 setApiKey ( e . target . value ) ;
@@ -278,7 +305,7 @@ const CustomModelSection = ({ isNew, onChange, model: customModel }: CustomModel
278305 type = "number"
279306 min = { 0 }
280307 step = "1"
281- disabled = { ! isEditing }
308+ disabled = { ! isEditing || isSaving }
282309 onChange = { ( e ) => setContextWindow ( e . target . value === "" ? 0 : Math . trunc ( Number ( e . target . value ) ) ) }
283310 />
284311 </ div >
@@ -291,7 +318,7 @@ const CustomModelSection = ({ isNew, onChange, model: customModel }: CustomModel
291318 type = "number"
292319 min = { 0 }
293320 step = "1"
294- disabled = { ! isEditing }
321+ disabled = { ! isEditing || isSaving }
295322 onChange = { ( e ) => setMaxOutput ( e . target . value === "" ? 0 : Math . trunc ( Number ( e . target . value ) ) ) }
296323 />
297324 </ div >
@@ -304,7 +331,7 @@ const CustomModelSection = ({ isNew, onChange, model: customModel }: CustomModel
304331 type = "number"
305332 min = { 0 }
306333 step = "1"
307- disabled = { ! isEditing }
334+ disabled = { ! isEditing || isSaving }
308335 onChange = { ( e ) => setInputPrice ( e . target . value === "" ? 0 : Math . trunc ( Number ( e . target . value ) ) ) }
309336 />
310337 </ div >
@@ -318,7 +345,7 @@ const CustomModelSection = ({ isNew, onChange, model: customModel }: CustomModel
318345 min = { 0 }
319346 step = "1"
320347 pattern = "[0-9]*"
321- disabled = { ! isEditing }
348+ disabled = { ! isEditing || isSaving }
322349 onChange = { ( e ) => setOutputPrice ( e . target . value === "" ? 0 : Math . trunc ( Number ( e . target . value ) ) ) }
323350 />
324351 </ div >
0 commit comments