BoxOfColors commited on
Commit
afa947b
·
1 Parent(s): 3bede42

Add client-side watchdog so a crashed generation can't wedge Generate

Browse files

The button-reset step (_btn_ready) only fires via Gradio's .then()
chain, which requires the preceding step's SSE stream to report SOME
terminal status. A dropped connection (container crash-restart, HTTP/2
protocol error — both seen in this app's logs) delivers no status at
all, so the chain stalls forever and Generate stays disabled with no
way to retry.

Arm a client-side timeout (10 min, well past any normal generation)
right after the button disables, that force-resets it if still stuck.
Token-gated per click so a stale watchdog can't clobber a newer,
still-in-flight generation.

Files changed (1) hide show
  1. app.py +43 -3
app.py CHANGED
@@ -2645,6 +2645,31 @@ _GLOBAL_JS = """
2645
  }
2646
  };
2647
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2648
  // ── ZeroGPU quota attribution ──
2649
  // HF Spaces run inside an iframe on huggingface.co. Gradio's own JS client
2650
  // gets ZeroGPU auth headers (x-zerogpu-token, x-zerogpu-uuid) by sending a
@@ -3073,7 +3098,7 @@ with gr.Blocks(title="Generate Audio for Video", css=_SLOT_CSS, js=_GLOBAL_JS) a
3073
  taro_cf_dur = gr.Slider(label="Crossfade Duration (s)", minimum=0, maximum=4, value=2, step=0.1, elem_id="taro_cf_dur")
3074
  taro_cf_db = gr.Textbox(label="Crossfade Boost (dB)", value="3", elem_id="taro_cf_db")
3075
  taro_samples = gr.Slider(label="Generations", minimum=1, maximum=MAX_SLOTS, value=1, step=1)
3076
- taro_btn = gr.Button("Generate", variant="primary")
3077
 
3078
  with gr.Column(scale=1):
3079
  (_, taro_slot_vids,
@@ -3119,6 +3144,11 @@ with gr.Blocks(title="Generate Audio for Video", css=_SLOT_CSS, js=_GLOBAL_JS) a
3119
  fn=_btn_generating,
3120
  inputs=None,
3121
  outputs=[taro_btn],
 
 
 
 
 
3122
  ).then(
3123
  fn=_run_taro,
3124
  inputs=[taro_video, taro_seed, taro_cfg, taro_steps, taro_mode,
@@ -3159,7 +3189,7 @@ with gr.Blocks(title="Generate Audio for Video", css=_SLOT_CSS, js=_GLOBAL_JS) a
3159
  mma_cf_dur = gr.Slider(label="Crossfade Duration (s)", minimum=0, maximum=4, value=2, step=0.1, elem_id="mma_cf_dur")
3160
  mma_cf_db = gr.Textbox(label="Crossfade Boost (dB)", value="3", elem_id="mma_cf_db")
3161
  mma_samples = gr.Slider(label="Generations", minimum=1, maximum=MAX_SLOTS, value=1, step=1)
3162
- mma_btn = gr.Button("Generate", variant="primary")
3163
 
3164
  with gr.Column(scale=1):
3165
  (_, mma_slot_vids,
@@ -3185,6 +3215,11 @@ with gr.Blocks(title="Generate Audio for Video", css=_SLOT_CSS, js=_GLOBAL_JS) a
3185
  fn=_btn_generating,
3186
  inputs=None,
3187
  outputs=[mma_btn],
 
 
 
 
 
3188
  ).then(
3189
  fn=_run_mmaudio,
3190
  inputs=[mma_video, mma_prompt, mma_neg, mma_seed,
@@ -3224,7 +3259,7 @@ with gr.Blocks(title="Generate Audio for Video", css=_SLOT_CSS, js=_GLOBAL_JS) a
3224
  hf_cf_dur = gr.Slider(label="Crossfade Duration (s)", minimum=0, maximum=4, value=2, step=0.1, elem_id="hf_cf_dur")
3225
  hf_cf_db = gr.Textbox(label="Crossfade Boost (dB)", value="3", elem_id="hf_cf_db")
3226
  hf_samples = gr.Slider(label="Generations", minimum=1, maximum=MAX_SLOTS, value=1, step=1)
3227
- hf_btn = gr.Button("Generate", variant="primary")
3228
 
3229
  with gr.Column(scale=1):
3230
  (_, hf_slot_vids,
@@ -3250,6 +3285,11 @@ with gr.Blocks(title="Generate Audio for Video", css=_SLOT_CSS, js=_GLOBAL_JS) a
3250
  fn=_btn_generating,
3251
  inputs=None,
3252
  outputs=[hf_btn],
 
 
 
 
 
3253
  ).then(
3254
  fn=_run_hunyuan,
3255
  inputs=[hf_video, hf_prompt, hf_neg, hf_seed,
 
2645
  }
2646
  };
2647
 
2648
+ // Force-reset a stuck "Generating…" button after a generous timeout.
2649
+ // The normal reset path is a .then() step chained after the (possibly
2650
+ // crash-prone) generate call — Gradio only fires .then() steps when the
2651
+ // preceding step's SSE stream reports SOME terminal status. A dropped
2652
+ // connection (container restart, HTTP/2 protocol error — both observed in
2653
+ // this app) never delivers one, so the chain stalls forever and the button
2654
+ // never re-enables. This watchdog is a client-side safety net that doesn't
2655
+ // depend on the chain completing at all. Token-gated so a stale watchdog
2656
+ // from an earlier click can't clobber a newer, still-in-flight generation.
2657
+ window._armGenerateWatchdog = function(btnId, timeoutMs) {
2658
+ const root = document.getElementById(btnId);
2659
+ if (!root) return;
2660
+ const btn = root.tagName === 'BUTTON' ? root : root.querySelector('button');
2661
+ if (!btn) return;
2662
+ btn._genToken = (btn._genToken || 0) + 1;
2663
+ const token = btn._genToken;
2664
+ setTimeout(function() {
2665
+ if (btn._genToken === token && btn.disabled) {
2666
+ btn.disabled = false;
2667
+ btn.textContent = 'Generate';
2668
+ console.log('[watchdog] ' + btnId + ' stuck disabled past ' + timeoutMs + 'ms — force re-enabled');
2669
+ }
2670
+ }, timeoutMs);
2671
+ };
2672
+
2673
  // ── ZeroGPU quota attribution ──
2674
  // HF Spaces run inside an iframe on huggingface.co. Gradio's own JS client
2675
  // gets ZeroGPU auth headers (x-zerogpu-token, x-zerogpu-uuid) by sending a
 
3098
  taro_cf_dur = gr.Slider(label="Crossfade Duration (s)", minimum=0, maximum=4, value=2, step=0.1, elem_id="taro_cf_dur")
3099
  taro_cf_db = gr.Textbox(label="Crossfade Boost (dB)", value="3", elem_id="taro_cf_db")
3100
  taro_samples = gr.Slider(label="Generations", minimum=1, maximum=MAX_SLOTS, value=1, step=1)
3101
+ taro_btn = gr.Button("Generate", variant="primary", elem_id="taro_btn")
3102
 
3103
  with gr.Column(scale=1):
3104
  (_, taro_slot_vids,
 
3144
  fn=_btn_generating,
3145
  inputs=None,
3146
  outputs=[taro_btn],
3147
+ ).then(
3148
+ fn=None,
3149
+ inputs=None,
3150
+ outputs=[],
3151
+ js="() => window._armGenerateWatchdog('taro_btn', 600000)",
3152
  ).then(
3153
  fn=_run_taro,
3154
  inputs=[taro_video, taro_seed, taro_cfg, taro_steps, taro_mode,
 
3189
  mma_cf_dur = gr.Slider(label="Crossfade Duration (s)", minimum=0, maximum=4, value=2, step=0.1, elem_id="mma_cf_dur")
3190
  mma_cf_db = gr.Textbox(label="Crossfade Boost (dB)", value="3", elem_id="mma_cf_db")
3191
  mma_samples = gr.Slider(label="Generations", minimum=1, maximum=MAX_SLOTS, value=1, step=1)
3192
+ mma_btn = gr.Button("Generate", variant="primary", elem_id="mma_btn")
3193
 
3194
  with gr.Column(scale=1):
3195
  (_, mma_slot_vids,
 
3215
  fn=_btn_generating,
3216
  inputs=None,
3217
  outputs=[mma_btn],
3218
+ ).then(
3219
+ fn=None,
3220
+ inputs=None,
3221
+ outputs=[],
3222
+ js="() => window._armGenerateWatchdog('mma_btn', 600000)",
3223
  ).then(
3224
  fn=_run_mmaudio,
3225
  inputs=[mma_video, mma_prompt, mma_neg, mma_seed,
 
3259
  hf_cf_dur = gr.Slider(label="Crossfade Duration (s)", minimum=0, maximum=4, value=2, step=0.1, elem_id="hf_cf_dur")
3260
  hf_cf_db = gr.Textbox(label="Crossfade Boost (dB)", value="3", elem_id="hf_cf_db")
3261
  hf_samples = gr.Slider(label="Generations", minimum=1, maximum=MAX_SLOTS, value=1, step=1)
3262
+ hf_btn = gr.Button("Generate", variant="primary", elem_id="hf_btn")
3263
 
3264
  with gr.Column(scale=1):
3265
  (_, hf_slot_vids,
 
3285
  fn=_btn_generating,
3286
  inputs=None,
3287
  outputs=[hf_btn],
3288
+ ).then(
3289
+ fn=None,
3290
+ inputs=None,
3291
+ outputs=[],
3292
+ js="() => window._armGenerateWatchdog('hf_btn', 600000)",
3293
  ).then(
3294
  fn=_run_hunyuan,
3295
  inputs=[hf_video, hf_prompt, hf_neg, hf_seed,