@@ -8,6 +8,7 @@ import { getAdminErrorMessage } from "../admin_errors.js";
88export const settingsTab = {
99 id : "settings" ,
1010 title : "Settings" ,
11+ icon : "pi pi-cog" ,
1112 render : async ( container ) => {
1213 // IMPORTANT (UI layout): `.moltbot-content` has `overflow: hidden`.
1314 // This tab MUST render its own scroll container (`.moltbot-scroll-area`),
@@ -128,7 +129,14 @@ export const settingsTab = {
128129 // -- LLM Settings Section --
129130 const llmSec = createSection ( "LLM Settings" ) ;
130131 if ( configRes . ok ) {
131- const { config, sources, providers } = configRes . data ;
132+ // R54: Null-safe destructuring with defaults
133+ const data = configRes . data || { } ;
134+ const config = data . config || { } ;
135+ const sources = data . sources || { } ;
136+ const providers = data . providers || [ ] ;
137+ // R53: Apply feedback (optional, for debug/toast later)
138+ const applyInfo = data . apply || { } ;
139+
132140
133141 // Provider dropdown
134142 const providerRow = createFormRow ( "Provider" , sources . provider === "env" ) ;
@@ -380,9 +388,22 @@ export const settingsTab = {
380388 } ;
381389
382390 const res = await moltbotApi . putConfig ( updates , token ) ;
391+ // R53: Hot-Reload Feedback
383392 if ( res . ok ) {
384- statusDiv . textContent = "✓ Saved! Restart ComfyUI to apply." ;
385- statusDiv . className = "moltbot-status ok" ;
393+ const apply = res . data ?. apply || { } ;
394+ let msg = "✓ Saved!" ;
395+
396+ if ( apply . restart_required ?. length > 0 ) {
397+ msg += " Restart required for: " + apply . restart_required . join ( ", " ) ;
398+ statusDiv . className = "moltbot-status warning" ; // Yellow/Orange
399+ } else if ( apply . applied_now ?. length > 0 ) {
400+ msg += " Applied immediately (Hot Reload)." ;
401+ statusDiv . className = "moltbot-status ok" ;
402+ } else {
403+ // No changes or unknown
404+ statusDiv . className = "moltbot-status ok" ;
405+ }
406+ statusDiv . textContent = msg ;
386407 } else {
387408 const errorMsg = getAdminErrorMessage ( res . error , res . status ) ;
388409 statusDiv . textContent = `✗ ${ res . errors ?. join ( ", " ) || errorMsg } ` ;
@@ -396,7 +417,16 @@ export const settingsTab = {
396417 const testBtn = document . createElement ( "button" ) ;
397418 testBtn . className = "moltbot-btn moltbot-btn-secondary" ;
398419 testBtn . textContent = "Test Connection" ;
420+
421+ // R54: Debounced Test Action to prevent spam
422+ // We use a separate handler because we need to manage button state (disabled/enabled)
423+ // which debounce interferes with if not careful.
424+ // Better strategy: Disable button immediately on click, re-enable after completion.
425+ // Debounce is less critical here if we disable the button, but good for "auto-test on change" (future).
426+ // For now, implementing "Disable while testing" is the better guard than generic debounce for a button click.
399427 testBtn . onclick = async ( ) => {
428+ if ( testBtn . disabled ) return ;
429+
400430 const token = ( tokenInput . value || MoltbotSession . getAdminToken ( ) || "" ) . trim ( ) ;
401431 if ( token ) MoltbotSession . setAdminToken ( token ) ;
402432
@@ -408,22 +438,25 @@ export const settingsTab = {
408438 // selected in the UI, even if the user hasn't clicked Save yet. Otherwise, the backend
409439 // falls back to the effective config (often "openai") and produces confusing errors like:
410440 // "API key not configured for provider 'openai'" while the UI is set to Gemini.
411- const res = await moltbotApi . testLLM ( token , {
412- provider : providerSelect . value ,
413- model : modelInput . value ,
414- base_url : baseUrlInput . value ,
415- timeout_sec : parseInt ( timeoutInput . value ) || 120 ,
416- max_retries : parseInt ( retriesInput . value ) || 3 ,
417- } ) ;
418- if ( res . ok ) {
419- statusDiv . textContent = "✓ Success! " + ( res . response ? `"${ res . response } "` : "" ) ;
420- statusDiv . className = "moltbot-status ok" ;
421- } else {
422- const errorMsg = getAdminErrorMessage ( res . error , res . status ) ;
423- statusDiv . textContent = `✗ ${ errorMsg } ` ;
424- statusDiv . className = "moltbot-status error" ;
441+ try {
442+ const res = await moltbotApi . testLLM ( token , {
443+ provider : providerSelect . value ,
444+ model : modelInput . value ,
445+ base_url : baseUrlInput . value ,
446+ timeout_sec : parseInt ( timeoutInput . value ) || 120 ,
447+ max_retries : parseInt ( retriesInput . value ) || 3 ,
448+ } ) ;
449+ if ( res . ok ) {
450+ statusDiv . textContent = "✓ Success! " + ( res . response ? `"${ res . response } "` : "" ) ;
451+ statusDiv . className = "moltbot-status ok" ;
452+ } else {
453+ const errorMsg = getAdminErrorMessage ( res . error , res . status ) ;
454+ statusDiv . textContent = `✗ ${ errorMsg } ` ;
455+ statusDiv . className = "moltbot-status error" ;
456+ }
457+ } finally {
458+ testBtn . disabled = false ;
425459 }
426- testBtn . disabled = false ;
427460 } ;
428461 btnRow . appendChild ( testBtn ) ;
429462
0 commit comments