BoxOfColors Claude Sonnet 4.6 commited on
Commit
c921113
·
1 Parent(s): 5c4d5d2

Fix regen: single-event trigger + generator-safe early-exit

Browse files

Two bugs causing regeneration to never reach Python:

1. clear-then-set fired TWO .change() events. The first (empty string)
caused the generator to hit a bare `return` — but Python generators
with a bare return raise StopIteration immediately, which Gradio 5
SSR interprets as an SSE stream error. This errored state blocked
the second (real value) call from completing.
Fix: remove the clear step entirely; use a timestamp suffix in the
trigger value so repeat clicks always produce a unique string and
React's change detection always fires.

2. Early-exit paths used `return tuple` inside a generator function.
In Python, any function containing `yield` is always a generator —
`return value` inside a generator is silently dropped (only
StopIteration is raised). Gradio 5 SSR then sees an empty stream.
Fix: change all early-exit paths to `yield ...; return` so Gradio
receives a proper final update before the stream closes.

Trigger format is now: "slot_id|seg_idx|timestamp|{stateJSON}"
All three handlers (TARO/MMA/HF) updated accordingly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Files changed (1) hide show
  1. app.py +29 -29
app.py CHANGED
@@ -1568,7 +1568,10 @@ _GLOBAL_JS = """
1568
  }
1569
 
1570
  // Use native setter to bypass React's controlled-input tracking.
1571
- // Clear to '' first so a repeat click on the same segment still fires .change().
 
 
 
1572
  const desc = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')
1573
  || Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value');
1574
  function setNative(val) {
@@ -1577,13 +1580,10 @@ _GLOBAL_JS = """
1577
  input.dispatchEvent(new Event('input', {bubbles: true}));
1578
  input.dispatchEvent(new Event('change', {bubbles: true}));
1579
  }
1580
- // Encode: "slot_id|seg_idx|{stateJSON}"
1581
- const triggerVal = slot_id + '|' + idx + '|' + stateJson;
1582
- setNative('');
1583
- setTimeout(function() {
1584
- setNative(triggerVal);
1585
- console.log('[fireRegen] fired trigger for', slot_id, 'seg', idx);
1586
- }, 50);
1587
 
1588
  const lbl = document.getElementById('wf_seglabel_' + slot_id);
1589
  if (lbl) lbl.textContent = 'Regenerating Seg ' + (idx + 1) + '...';
@@ -1689,14 +1689,14 @@ with gr.Blocks(title="Generate Audio for Video", css=_SLOT_CSS, js=_GLOBAL_JS) a
1689
  print(f"[regen TARO] trigger_val_len={len(trigger_val) if trigger_val else 0} video={video!r}")
1690
  if not trigger_val:
1691
  print(f"[regen TARO] early-exit: trigger_val empty")
1692
- return gr.update(), gr.update(), gr.update(value="")
1693
- # Trigger format: "slot_id|seg_idx|{stateJSON}"
1694
- parts = trigger_val.split("|", 2)
1695
- if len(parts) != 3 or parts[0] != _sid:
1696
- print(f"[regen TARO] early-exit: parts[0]={parts[0]!r} expected={_sid!r}")
1697
- return gr.update(), gr.update(), gr.update(value="")
1698
  seg_idx = int(parts[1])
1699
- state_json = parts[2]
1700
  print(f"[regen TARO] slot={_sid} seg_idx={seg_idx} state_json_len={len(state_json)}")
1701
  lock = _get_slot_lock(_sid)
1702
  with lock:
@@ -1781,14 +1781,14 @@ with gr.Blocks(title="Generate Audio for Video", css=_SLOT_CSS, js=_GLOBAL_JS) a
1781
  print(f"[regen MMA] trigger_val_len={len(trigger_val) if trigger_val else 0} video={video!r}")
1782
  if not trigger_val:
1783
  print(f"[regen MMA] early-exit: trigger_val empty")
1784
- return gr.update(), gr.update(), gr.update(value="")
1785
- # Trigger format: "slot_id|seg_idx|{stateJSON}"
1786
- parts = trigger_val.split("|", 2)
1787
- if len(parts) != 3 or parts[0] != _sid:
1788
- print(f"[regen MMA] early-exit: parts[0]={parts[0]!r} expected={_sid!r}")
1789
- return gr.update(), gr.update(), gr.update(value="")
1790
  seg_idx = int(parts[1])
1791
- state_json = parts[2]
1792
  print(f"[regen MMA] slot={_sid} seg_idx={seg_idx} state_json_len={len(state_json)}")
1793
  lock = _get_slot_lock(_sid)
1794
  with lock:
@@ -1874,14 +1874,14 @@ with gr.Blocks(title="Generate Audio for Video", css=_SLOT_CSS, js=_GLOBAL_JS) a
1874
  print(f"[regen HF] trigger_val_len={len(trigger_val) if trigger_val else 0} video={video!r}")
1875
  if not trigger_val:
1876
  print(f"[regen HF] early-exit: trigger_val empty")
1877
- return gr.update(), gr.update(), gr.update(value="")
1878
- # Trigger format: "slot_id|seg_idx|{stateJSON}"
1879
- parts = trigger_val.split("|", 2)
1880
- if len(parts) != 3 or parts[0] != _sid:
1881
- print(f"[regen HF] early-exit: parts[0]={parts[0]!r} expected={_sid!r}")
1882
- return gr.update(), gr.update(), gr.update(value="")
1883
  seg_idx = int(parts[1])
1884
- state_json = parts[2]
1885
  print(f"[regen HF] slot={_sid} seg_idx={seg_idx} state_json_len={len(state_json)}")
1886
  lock = _get_slot_lock(_sid)
1887
  with lock:
 
1568
  }
1569
 
1570
  // Use native setter to bypass React's controlled-input tracking.
1571
+ // We do NOT clear to '' first that would fire a spurious .change() event
1572
+ // which (as a generator function) causes an SSE stream error that blocks
1573
+ // the real call. Instead we use a timestamp suffix to ensure uniqueness
1574
+ // so repeat clicks on the same segment always look like a new value.
1575
  const desc = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')
1576
  || Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value');
1577
  function setNative(val) {
 
1580
  input.dispatchEvent(new Event('input', {bubbles: true}));
1581
  input.dispatchEvent(new Event('change', {bubbles: true}));
1582
  }
1583
+ // Encode: "slot_id|seg_idx|ts|{stateJSON}" (ts = timestamp for uniqueness)
1584
+ const triggerVal = slot_id + '|' + idx + '|' + Date.now() + '|' + stateJson;
1585
+ setNative(triggerVal);
1586
+ console.log('[fireRegen] fired trigger for', slot_id, 'seg', idx);
 
 
 
1587
 
1588
  const lbl = document.getElementById('wf_seglabel_' + slot_id);
1589
  if (lbl) lbl.textContent = 'Regenerating Seg ' + (idx + 1) + '...';
 
1689
  print(f"[regen TARO] trigger_val_len={len(trigger_val) if trigger_val else 0} video={video!r}")
1690
  if not trigger_val:
1691
  print(f"[regen TARO] early-exit: trigger_val empty")
1692
+ yield gr.update(), gr.update(), gr.update(); return
1693
+ # Trigger format: "slot_id|seg_idx|timestamp|{stateJSON}"
1694
+ parts = trigger_val.split("|", 3)
1695
+ if len(parts) != 4 or parts[0] != _sid:
1696
+ print(f"[regen TARO] early-exit: parts={parts[:2]} expected slot={_sid!r}")
1697
+ yield gr.update(), gr.update(), gr.update(); return
1698
  seg_idx = int(parts[1])
1699
+ state_json = parts[3]
1700
  print(f"[regen TARO] slot={_sid} seg_idx={seg_idx} state_json_len={len(state_json)}")
1701
  lock = _get_slot_lock(_sid)
1702
  with lock:
 
1781
  print(f"[regen MMA] trigger_val_len={len(trigger_val) if trigger_val else 0} video={video!r}")
1782
  if not trigger_val:
1783
  print(f"[regen MMA] early-exit: trigger_val empty")
1784
+ yield gr.update(), gr.update(), gr.update(); return
1785
+ # Trigger format: "slot_id|seg_idx|timestamp|{stateJSON}"
1786
+ parts = trigger_val.split("|", 3)
1787
+ if len(parts) != 4 or parts[0] != _sid:
1788
+ print(f"[regen MMA] early-exit: parts={parts[:2]} expected slot={_sid!r}")
1789
+ yield gr.update(), gr.update(), gr.update(); return
1790
  seg_idx = int(parts[1])
1791
+ state_json = parts[3]
1792
  print(f"[regen MMA] slot={_sid} seg_idx={seg_idx} state_json_len={len(state_json)}")
1793
  lock = _get_slot_lock(_sid)
1794
  with lock:
 
1874
  print(f"[regen HF] trigger_val_len={len(trigger_val) if trigger_val else 0} video={video!r}")
1875
  if not trigger_val:
1876
  print(f"[regen HF] early-exit: trigger_val empty")
1877
+ yield gr.update(), gr.update(), gr.update(); return
1878
+ # Trigger format: "slot_id|seg_idx|timestamp|{stateJSON}"
1879
+ parts = trigger_val.split("|", 3)
1880
+ if len(parts) != 4 or parts[0] != _sid:
1881
+ print(f"[regen HF] early-exit: parts={parts[:2]} expected slot={_sid!r}")
1882
+ yield gr.update(), gr.update(), gr.update(); return
1883
  seg_idx = int(parts[1])
1884
+ state_json = parts[3]
1885
  print(f"[regen HF] slot={_sid} seg_idx={seg_idx} state_json_len={len(state_json)}")
1886
  lock = _get_slot_lock(_sid)
1887
  with lock: