@@ -197,17 +197,24 @@ document.addEventListener('DOMContentLoaded', () => {
197197 document . getElementById ( 'seed-input' ) . value = Math . floor ( Math . random ( ) * 1000000000 ) ;
198198 } ) ;
199199
200- // Fetch Available Models
201- async function loadModels ( ) {
200+ // Fetch Available Models with Automatic Retry
201+ async function loadModels ( retries = 5 ) {
202202 try {
203203 const res = await fetch ( '/api/models' ) ;
204204 const data = await res . json ( ) ;
205205
206- // Checkpoint Select
207206 const ckptSelect = document . getElementById ( 'ckpt-select' ) ;
208- ckptSelect . innerHTML = '' ;
207+ if ( ! ckptSelect ) return ;
208+
209+ const availableCkpts = ( data . comfy_checkpoints && data . comfy_checkpoints . length > 0 ) ? data . comfy_checkpoints : ( data . checkpoints || [ ] ) ;
209210
210- const availableCkpts = data . comfy_checkpoints . length > 0 ? data . comfy_checkpoints : data . checkpoints ;
211+ if ( availableCkpts . length === 0 && retries > 0 ) {
212+ console . log ( "[Studio UI] Models API returned empty, retrying in 1.5s..." ) ;
213+ setTimeout ( ( ) => loadModels ( retries - 1 ) , 1500 ) ;
214+ return ;
215+ }
216+
217+ ckptSelect . innerHTML = '' ;
211218 availableCkpts . forEach ( ckpt => {
212219 const opt = document . createElement ( 'option' ) ;
213220 opt . value = ckpt ;
@@ -217,7 +224,7 @@ document.addEventListener('DOMContentLoaded', () => {
217224 }
218225 ckptSelect . appendChild ( opt ) ;
219226 } ) ;
220- // Fallback if not selected yet
227+
221228 if ( ! ckptSelect . value && ckptSelect . options . length > 0 ) {
222229 ckptSelect . selectedIndex = 0 ;
223230 }
@@ -253,44 +260,74 @@ document.addEventListener('DOMContentLoaded', () => {
253260 const triggerBadges = document . getElementById ( 'trigger-badges' ) ;
254261 const promptInput = document . getElementById ( 'prompt-input' ) ;
255262
256- loraSelect . innerHTML = '<option value="None">None (Disabled)</option>' ;
257- data . loras . forEach ( lora => {
258- const opt = document . createElement ( 'option' ) ;
259- opt . value = lora ;
260- opt . textContent = lora ;
261- loraSelect . appendChild ( opt ) ;
262- } ) ;
263+ if ( loraSelect ) {
264+ loraSelect . innerHTML = '<option value="None">None (Disabled)</option>' ;
265+ ( data . loras || [ ] ) . forEach ( lora => {
266+ const opt = document . createElement ( 'option' ) ;
267+ opt . value = lora ;
268+ opt . textContent = lora ;
269+ loraSelect . appendChild ( opt ) ;
270+ } ) ;
263271
264- function updateTriggerBadges ( ) {
265- const selectedLora = loraSelect . value ;
266- triggerBadges . innerHTML = '' ;
267- if ( selectedLora && selectedLora !== 'None' && loraTriggersMap [ selectedLora ] ) {
268- triggersContainer . classList . remove ( 'hidden' ) ;
269- const tags = loraTriggersMap [ selectedLora ] ;
270- tags . forEach ( tag => {
271- const chip = document . createElement ( 'span' ) ;
272- chip . className = 'trigger-chip' ;
273- chip . innerHTML = `<i class="fa-solid fa-plus"></i> ${ tag } ` ;
274- chip . title = "Click to add to prompt" ;
275- chip . addEventListener ( 'click' , ( ) => {
276- if ( ! promptInput . value . includes ( tag ) ) {
277- promptInput . value += ( promptInput . value . trim ( ) ? ', ' : '' ) + tag ;
278- }
272+ function updateTriggerBadges ( ) {
273+ if ( ! triggersContainer || ! triggerBadges ) return ;
274+ const selectedLora = loraSelect . value ;
275+ const tags = loraTriggersMap [ selectedLora ] || [ ] ;
276+ triggerBadges . innerHTML = '' ;
277+ if ( tags . length > 0 ) {
278+ triggersContainer . classList . remove ( 'hidden' ) ;
279+ tags . forEach ( tag => {
280+ const chip = document . createElement ( 'span' ) ;
281+ chip . className = 'trigger-chip' ;
282+ chip . innerHTML = `<i class="fa-solid fa-plus"></i> ${ tag } ` ;
283+ chip . title = "Click to add to prompt" ;
284+ chip . addEventListener ( 'click' , ( ) => {
285+ if ( ! promptInput . value . includes ( tag ) ) {
286+ promptInput . value += ( promptInput . value . trim ( ) ? ', ' : '' ) + tag ;
287+ }
288+ } ) ;
289+ triggerBadges . appendChild ( chip ) ;
279290 } ) ;
280- triggerBadges . appendChild ( chip ) ;
281- } ) ;
282- } else {
283- triggersContainer . classList . add ( 'hidden' ) ;
291+ } else {
292+ triggersContainer . classList . add ( 'hidden' ) ;
293+ }
284294 }
285- }
286295
287- loraSelect . addEventListener ( 'change' , updateTriggerBadges ) ;
296+ loraSelect . addEventListener ( 'change' , updateTriggerBadges ) ;
297+ }
288298 } catch ( e ) {
289299 console . error ( "Failed to load models:" , e ) ;
300+ if ( retries > 0 ) {
301+ setTimeout ( ( ) => loadModels ( retries - 1 ) , 2000 ) ;
302+ }
290303 }
291304 }
292305 loadModels ( ) ;
293306
307+ // Inference Engine Radio UI Sync
308+ const engineRadios = document . querySelectorAll ( 'input[name="inference-engine"]' ) ;
309+ const modelSelectContainer = document . getElementById ( 'model-select-container' ) ;
310+ const ckptSelect = document . getElementById ( 'ckpt-select' ) ;
311+
312+ function syncEngineUI ( ) {
313+ const selected = document . querySelector ( 'input[name="inference-engine"]:checked' ) ?. value || 'local' ;
314+ if ( selected === 'zerogpu' ) {
315+ if ( ckptSelect ) ckptSelect . disabled = true ;
316+ if ( modelSelectContainer ) {
317+ modelSelectContainer . style . opacity = '0.5' ;
318+ modelSelectContainer . title = "HF ZeroGPU Cloud executes remotely via serverless FLUX.1-dev H100" ;
319+ }
320+ } else {
321+ if ( ckptSelect ) ckptSelect . disabled = false ;
322+ if ( modelSelectContainer ) {
323+ modelSelectContainer . style . opacity = '1.0' ;
324+ modelSelectContainer . title = "" ;
325+ }
326+ }
327+ }
328+ engineRadios . forEach ( r => r . addEventListener ( 'change' , syncEngineUI ) ) ;
329+ syncEngineUI ( ) ;
330+
294331 // WebSocket Connection to ComfyUI
295332 const clientId = 'studio-' + Math . random ( ) . toString ( 36 ) . substring ( 2 , 9 ) ;
296333 let ws ;
0 commit comments