BoxOfColors commited on
Commit
c45a944
·
1 Parent(s): d2864d0

Fix fireRegen: use correct prototype for textarea native setter

Browse files
Files changed (1) hide show
  1. app.py +87 -69
app.py CHANGED
@@ -1397,11 +1397,16 @@ def _make_output_slots(tab_prefix: str) -> tuple:
1397
  """Build MAX_SLOTS output groups for one tab.
1398
 
1399
  Each slot has: video, waveform HTML, hidden regen trigger textbox,
1400
- hidden JSON state textbox (replaces gr.State to fix Gradio 5 SSR
1401
- 'Too many arguments' caused by gr.State not counting in endpoint outputs).
1402
- Returns (grps, vids, waveforms, regen_triggers, seg_states).
 
 
 
 
 
1403
  """
1404
- grps, vids, waveforms, regen_triggers, seg_states = [], [], [], [], []
1405
  for i in range(MAX_SLOTS):
1406
  with gr.Group(visible=(i == 0)) as g:
1407
  slot_id = f"{tab_prefix}_{i}"
@@ -1409,11 +1414,7 @@ def _make_output_slots(tab_prefix: str) -> tuple:
1409
  waveforms.append(gr.HTML(
1410
  value="<p style='color:#888;font-size:12px'>Generate audio to see waveform.</p>",
1411
  ))
1412
- # Regen trigger: a Textbox that is CSS-hidden (NOT visible=False).
1413
- # Gradio 5 SSR omits visible=False components from the DOM entirely,
1414
- # so getElementById() returns null and JS can never fire the event.
1415
- # By keeping it visible=True but hiding with CSS (elem_classes),
1416
- # the input element exists in the DOM and JS can write to it.
1417
  regen_triggers.append(gr.Textbox(
1418
  value="",
1419
  elem_id=f"regen_trigger_{slot_id}",
@@ -1421,19 +1422,23 @@ def _make_output_slots(tab_prefix: str) -> tuple:
1421
  label="",
1422
  show_label=False,
1423
  ))
1424
- # State textbox: CSS-hidden, has elem_id so JS can READ current state
1425
- # and embed it in the trigger value (avoids having this component in
1426
- # both inputs AND outputs of the same .change() handler, which causes
1427
- # Gradio 5 SSR "Too many arguments" validation errors).
1428
  seg_states.append(gr.Textbox(
1429
  value="",
1430
- elem_id=f"seg_state_{slot_id}",
 
 
 
 
 
 
 
1431
  elem_classes=["wf-hidden-input"],
1432
  label="",
1433
  show_label=False,
1434
  ))
1435
  grps.append(g)
1436
- return grps, vids, waveforms, regen_triggers, seg_states
1437
 
1438
 
1439
  def _unpack_outputs(flat: list, n: int, tab_prefix: str) -> list:
@@ -1555,33 +1560,27 @@ _GLOBAL_JS = """
1555
  const input = el.querySelector('input, textarea');
1556
  if (!input) { console.warn('[fireRegen] no input inside regen_trigger:', slot_id); return; }
1557
 
1558
- // Read current seg state JSON from the state textbox so we can embed it
1559
- // in the trigger value. This avoids having seg_state in BOTH inputs AND
1560
- // outputs of the .change() handler, which causes Gradio 5 SSR to reject
1561
- // the call with "Too many arguments provided for the endpoint".
1562
- const stEl = document.getElementById('seg_state_' + slot_id);
1563
- const stInput = stEl ? stEl.querySelector('input, textarea') : null;
1564
- const stateJson = stInput ? stInput.value : '';
1565
- if (!stateJson) {
1566
- console.warn('[fireRegen] seg_state is empty for slot', slot_id, '— skipping regen');
1567
- return;
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) {
 
 
 
 
1578
  if (desc && desc.set) desc.set.call(input, val);
1579
  else input.value = 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
 
@@ -1644,7 +1643,8 @@ with gr.Blocks(title="Generate Audio for Video", css=_SLOT_CSS, js=_GLOBAL_JS) a
1644
  with gr.Column():
1645
  (taro_slot_grps, taro_slot_vids,
1646
  taro_slot_waves, taro_slot_rtrigs,
1647
- taro_slot_states) = _make_output_slots("taro")
 
1648
 
1649
  for trigger in [taro_video, taro_steps, taro_cf_dur]:
1650
  trigger.change(
@@ -1681,24 +1681,30 @@ with gr.Blocks(title="Generate Audio for Video", css=_SLOT_CSS, js=_GLOBAL_JS) a
1681
  outputs=taro_slot_grps,
1682
  ))
1683
 
 
 
 
 
1684
  # Per-slot regen trigger wiring for TARO
1685
  for _i, _rtrig in enumerate(taro_slot_rtrigs):
1686
  _slot_id = f"taro_{_i}"
1687
  print(f"[startup] registering regen handler for slot {_slot_id}")
1688
  def _make_taro_regen(_si, _sid):
1689
- def _do(trigger_val, video, seed, cfg, steps, mode, cf_dur, cf_db):
1690
- print(f"[regen TARO] trigger_val_len={len(trigger_val) if trigger_val else 0} video={video!r}")
1691
  if not trigger_val:
1692
  print(f"[regen TARO] early-exit: trigger_val empty")
1693
  yield gr.update(), gr.update(), gr.update(); return
1694
- # Trigger format: "slot_id|seg_idx|timestamp|{stateJSON}"
1695
- parts = trigger_val.split("|", 3)
1696
- if len(parts) != 4 or parts[0] != _sid:
1697
- print(f"[regen TARO] early-exit: parts={parts[:2]} expected slot={_sid!r}")
 
 
 
1698
  yield gr.update(), gr.update(), gr.update(); return
1699
- seg_idx = int(parts[1])
1700
- state_json = parts[3]
1701
- print(f"[regen TARO] slot={_sid} seg_idx={seg_idx} state_json_len={len(state_json)}")
1702
  lock = _get_slot_lock(_sid)
1703
  with lock:
1704
  print(f"[regen TARO] slot={_sid} seg_idx={seg_idx} — lock acquired, showing spinner")
@@ -1723,7 +1729,7 @@ with gr.Blocks(title="Generate Audio for Video", css=_SLOT_CSS, js=_GLOBAL_JS) a
1723
  _rtrig.change(
1724
  fn=_make_taro_regen(_i, _slot_id),
1725
  inputs=[_rtrig, taro_video, taro_seed, taro_cfg, taro_steps,
1726
- taro_mode, taro_cf_dur, taro_cf_db],
1727
  outputs=[taro_slot_vids[_i], taro_slot_waves[_i], taro_slot_states[_i]],
1728
  )
1729
 
@@ -1747,7 +1753,8 @@ with gr.Blocks(title="Generate Audio for Video", css=_SLOT_CSS, js=_GLOBAL_JS) a
1747
  with gr.Column():
1748
  (mma_slot_grps, mma_slot_vids,
1749
  mma_slot_waves, mma_slot_rtrigs,
1750
- mma_slot_states) = _make_output_slots("mma")
 
1751
 
1752
  mma_samples.change(
1753
  fn=_update_slot_visibility,
@@ -1775,22 +1782,27 @@ with gr.Blocks(title="Generate Audio for Video", css=_SLOT_CSS, js=_GLOBAL_JS) a
1775
  outputs=mma_slot_grps,
1776
  ))
1777
 
 
 
 
 
1778
  for _i, _rtrig in enumerate(mma_slot_rtrigs):
1779
  _slot_id = f"mma_{_i}"
1780
  def _make_mma_regen(_si, _sid):
1781
- def _do(trigger_val, video, prompt, neg, seed, cfg, steps, cf_dur, cf_db):
1782
- print(f"[regen MMA] trigger_val_len={len(trigger_val) if trigger_val else 0} video={video!r}")
1783
  if not trigger_val:
1784
  print(f"[regen MMA] early-exit: trigger_val empty")
1785
  yield gr.update(), gr.update(), gr.update(); return
1786
- # Trigger format: "slot_id|seg_idx|timestamp|{stateJSON}"
1787
- parts = trigger_val.split("|", 3)
1788
- if len(parts) != 4 or parts[0] != _sid:
1789
- print(f"[regen MMA] early-exit: parts={parts[:2]} expected slot={_sid!r}")
1790
  yield gr.update(), gr.update(), gr.update(); return
1791
- seg_idx = int(parts[1])
1792
- state_json = parts[3]
1793
- print(f"[regen MMA] slot={_sid} seg_idx={seg_idx} state_json_len={len(state_json)}")
 
 
 
1794
  lock = _get_slot_lock(_sid)
1795
  with lock:
1796
  print(f"[regen MMA] slot={_sid} seg_idx={seg_idx} — lock acquired, showing spinner")
@@ -1815,7 +1827,7 @@ with gr.Blocks(title="Generate Audio for Video", css=_SLOT_CSS, js=_GLOBAL_JS) a
1815
  _rtrig.change(
1816
  fn=_make_mma_regen(_i, _slot_id),
1817
  inputs=[_rtrig, mma_video, mma_prompt, mma_neg, mma_seed,
1818
- mma_cfg, mma_steps, mma_cf_dur, mma_cf_db],
1819
  outputs=[mma_slot_vids[_i], mma_slot_waves[_i], mma_slot_states[_i]],
1820
  )
1821
 
@@ -1840,7 +1852,8 @@ with gr.Blocks(title="Generate Audio for Video", css=_SLOT_CSS, js=_GLOBAL_JS) a
1840
  with gr.Column():
1841
  (hf_slot_grps, hf_slot_vids,
1842
  hf_slot_waves, hf_slot_rtrigs,
1843
- hf_slot_states) = _make_output_slots("hf")
 
1844
 
1845
  hf_samples.change(
1846
  fn=_update_slot_visibility,
@@ -1868,22 +1881,27 @@ with gr.Blocks(title="Generate Audio for Video", css=_SLOT_CSS, js=_GLOBAL_JS) a
1868
  outputs=hf_slot_grps,
1869
  ))
1870
 
 
 
 
 
1871
  for _i, _rtrig in enumerate(hf_slot_rtrigs):
1872
  _slot_id = f"hf_{_i}"
1873
  def _make_hf_regen(_si, _sid):
1874
- def _do(trigger_val, video, prompt, neg, seed, guidance, steps, size, cf_dur, cf_db):
1875
- print(f"[regen HF] trigger_val_len={len(trigger_val) if trigger_val else 0} video={video!r}")
1876
  if not trigger_val:
1877
  print(f"[regen HF] early-exit: trigger_val empty")
1878
  yield gr.update(), gr.update(), gr.update(); return
1879
- # Trigger format: "slot_id|seg_idx|timestamp|{stateJSON}"
1880
- parts = trigger_val.split("|", 3)
1881
- if len(parts) != 4 or parts[0] != _sid:
1882
- print(f"[regen HF] early-exit: parts={parts[:2]} expected slot={_sid!r}")
 
 
1883
  yield gr.update(), gr.update(), gr.update(); return
1884
- seg_idx = int(parts[1])
1885
- state_json = parts[3]
1886
- print(f"[regen HF] slot={_sid} seg_idx={seg_idx} state_json_len={len(state_json)}")
1887
  lock = _get_slot_lock(_sid)
1888
  with lock:
1889
  print(f"[regen HF] slot={_sid} seg_idx={seg_idx} — lock acquired, showing spinner")
@@ -1908,7 +1926,7 @@ with gr.Blocks(title="Generate Audio for Video", css=_SLOT_CSS, js=_GLOBAL_JS) a
1908
  _rtrig.change(
1909
  fn=_make_hf_regen(_i, _slot_id),
1910
  inputs=[_rtrig, hf_video, hf_prompt, hf_neg, hf_seed,
1911
- hf_guidance, hf_steps, hf_size, hf_cf_dur, hf_cf_db],
1912
  outputs=[hf_slot_vids[_i], hf_slot_waves[_i], hf_slot_states[_i]],
1913
  )
1914
 
 
1397
  """Build MAX_SLOTS output groups for one tab.
1398
 
1399
  Each slot has: video, waveform HTML, hidden regen trigger textbox,
1400
+ and TWO state textboxes:
1401
+ - seg_states (write): written by main gen + regen; also an output
1402
+ - seg_state_reads (read): mirrors seg_states via .change() relay;
1403
+ used as input-only for regen handlers so that
1404
+ no component ever appears in BOTH inputs AND
1405
+ outputs of the same event (which causes Gradio
1406
+ 5 "Too many arguments" even with SSR disabled).
1407
+ Returns (grps, vids, waveforms, regen_triggers, seg_states, seg_state_reads).
1408
  """
1409
+ grps, vids, waveforms, regen_triggers, seg_states, seg_state_reads = [], [], [], [], [], []
1410
  for i in range(MAX_SLOTS):
1411
  with gr.Group(visible=(i == 0)) as g:
1412
  slot_id = f"{tab_prefix}_{i}"
 
1414
  waveforms.append(gr.HTML(
1415
  value="<p style='color:#888;font-size:12px'>Generate audio to see waveform.</p>",
1416
  ))
1417
+ # Regen trigger: CSS-hidden so JS can find and write to it.
 
 
 
 
1418
  regen_triggers.append(gr.Textbox(
1419
  value="",
1420
  elem_id=f"regen_trigger_{slot_id}",
 
1422
  label="",
1423
  show_label=False,
1424
  ))
1425
+ # Write-only state: updated by main gen and regen outputs.
 
 
 
1426
  seg_states.append(gr.Textbox(
1427
  value="",
1428
+ elem_classes=["wf-hidden-input"],
1429
+ label="",
1430
+ show_label=False,
1431
+ ))
1432
+ # Read-only mirror: fed into regen handler inputs only.
1433
+ # Stays in sync via a .change() relay wired after slot creation.
1434
+ seg_state_reads.append(gr.Textbox(
1435
+ value="",
1436
  elem_classes=["wf-hidden-input"],
1437
  label="",
1438
  show_label=False,
1439
  ))
1440
  grps.append(g)
1441
+ return grps, vids, waveforms, regen_triggers, seg_states, seg_state_reads
1442
 
1443
 
1444
  def _unpack_outputs(flat: list, n: int, tab_prefix: str) -> list:
 
1560
  const input = el.querySelector('input, textarea');
1561
  if (!input) { console.warn('[fireRegen] no input inside regen_trigger:', slot_id); return; }
1562
 
1563
+ // Use native setter to bypass Svelte's controlled-input tracking.
1564
+ // Timestamp suffix ensures repeat clicks on the same segment always
1565
+ // produce a new value so Svelte's change detection always fires.
1566
+ // State JSON is passed via a separate Gradio input (seg_state_read),
1567
+ // not embedded in the trigger string — Gradio's own state is reliable,
1568
+ // whereas reading the DOM input.value returns '' for Svelte-controlled inputs.
1569
+ // IMPORTANT: Gradio 5 renders Textbox as <textarea>, NOT <input>.
1570
+ // Must use HTMLTextAreaElement.prototype setter — using HTMLInputElement.prototype
1571
+ // on a textarea causes "TypeError: Illegal invocation" and silently aborts.
 
 
 
 
 
 
 
 
 
 
1572
  function setNative(val) {
1573
+ const proto = input.tagName === 'TEXTAREA'
1574
+ ? HTMLTextAreaElement.prototype
1575
+ : HTMLInputElement.prototype;
1576
+ const desc = Object.getOwnPropertyDescriptor(proto, 'value');
1577
  if (desc && desc.set) desc.set.call(input, val);
1578
  else input.value = val;
1579
  input.dispatchEvent(new Event('input', {bubbles: true}));
1580
  input.dispatchEvent(new Event('change', {bubbles: true}));
1581
  }
1582
+ // Encode: "slot_id|seg_idx|timestamp"
1583
+ const triggerVal = slot_id + '|' + idx + '|' + Date.now();
1584
  setNative(triggerVal);
1585
  console.log('[fireRegen] fired trigger for', slot_id, 'seg', idx);
1586
 
 
1643
  with gr.Column():
1644
  (taro_slot_grps, taro_slot_vids,
1645
  taro_slot_waves, taro_slot_rtrigs,
1646
+ taro_slot_states,
1647
+ taro_slot_state_reads) = _make_output_slots("taro")
1648
 
1649
  for trigger in [taro_video, taro_steps, taro_cf_dur]:
1650
  trigger.change(
 
1681
  outputs=taro_slot_grps,
1682
  ))
1683
 
1684
+ # Relay: keep seg_state_reads in sync with seg_states (write→read mirror)
1685
+ for _st, _str in zip(taro_slot_states, taro_slot_state_reads):
1686
+ _st.change(fn=lambda v: v, inputs=[_st], outputs=[_str])
1687
+
1688
  # Per-slot regen trigger wiring for TARO
1689
  for _i, _rtrig in enumerate(taro_slot_rtrigs):
1690
  _slot_id = f"taro_{_i}"
1691
  print(f"[startup] registering regen handler for slot {_slot_id}")
1692
  def _make_taro_regen(_si, _sid):
1693
+ def _do(trigger_val, video, seed, cfg, steps, mode, cf_dur, cf_db, state_json):
1694
+ print(f"[regen TARO] trigger_val={trigger_val!r} state_json_len={len(state_json) if state_json else 0}")
1695
  if not trigger_val:
1696
  print(f"[regen TARO] early-exit: trigger_val empty")
1697
  yield gr.update(), gr.update(), gr.update(); return
1698
+ if not state_json:
1699
+ print(f"[regen TARO] early-exit: state_json empty")
1700
+ yield gr.update(), gr.update(), gr.update(); return
1701
+ # Trigger format: "slot_id|seg_idx|timestamp"
1702
+ parts = trigger_val.split("|", 2)
1703
+ if len(parts) < 2 or parts[0] != _sid:
1704
+ print(f"[regen TARO] early-exit: parts[0]={parts[0]!r} expected={_sid!r}")
1705
  yield gr.update(), gr.update(), gr.update(); return
1706
+ seg_idx = int(parts[1])
1707
+ print(f"[regen TARO] slot={_sid} seg_idx={seg_idx} — acquiring lock")
 
1708
  lock = _get_slot_lock(_sid)
1709
  with lock:
1710
  print(f"[regen TARO] slot={_sid} seg_idx={seg_idx} — lock acquired, showing spinner")
 
1729
  _rtrig.change(
1730
  fn=_make_taro_regen(_i, _slot_id),
1731
  inputs=[_rtrig, taro_video, taro_seed, taro_cfg, taro_steps,
1732
+ taro_mode, taro_cf_dur, taro_cf_db, taro_slot_state_reads[_i]],
1733
  outputs=[taro_slot_vids[_i], taro_slot_waves[_i], taro_slot_states[_i]],
1734
  )
1735
 
 
1753
  with gr.Column():
1754
  (mma_slot_grps, mma_slot_vids,
1755
  mma_slot_waves, mma_slot_rtrigs,
1756
+ mma_slot_states,
1757
+ mma_slot_state_reads) = _make_output_slots("mma")
1758
 
1759
  mma_samples.change(
1760
  fn=_update_slot_visibility,
 
1782
  outputs=mma_slot_grps,
1783
  ))
1784
 
1785
+ # Relay: keep mma_slot_state_reads in sync with mma_slot_states
1786
+ for _st, _str in zip(mma_slot_states, mma_slot_state_reads):
1787
+ _st.change(fn=lambda v: v, inputs=[_st], outputs=[_str])
1788
+
1789
  for _i, _rtrig in enumerate(mma_slot_rtrigs):
1790
  _slot_id = f"mma_{_i}"
1791
  def _make_mma_regen(_si, _sid):
1792
+ def _do(trigger_val, video, prompt, neg, seed, cfg, steps, cf_dur, cf_db, state_json):
1793
+ print(f"[regen MMA] trigger_val={trigger_val!r} state_json_len={len(state_json) if state_json else 0}")
1794
  if not trigger_val:
1795
  print(f"[regen MMA] early-exit: trigger_val empty")
1796
  yield gr.update(), gr.update(), gr.update(); return
1797
+ if not state_json:
1798
+ print(f"[regen MMA] early-exit: state_json empty")
 
 
1799
  yield gr.update(), gr.update(), gr.update(); return
1800
+ parts = trigger_val.split("|", 2)
1801
+ if len(parts) < 2 or parts[0] != _sid:
1802
+ print(f"[regen MMA] early-exit: parts[0]={parts[0]!r} expected={_sid!r}")
1803
+ yield gr.update(), gr.update(), gr.update(); return
1804
+ seg_idx = int(parts[1])
1805
+ print(f"[regen MMA] slot={_sid} seg_idx={seg_idx} — acquiring lock")
1806
  lock = _get_slot_lock(_sid)
1807
  with lock:
1808
  print(f"[regen MMA] slot={_sid} seg_idx={seg_idx} — lock acquired, showing spinner")
 
1827
  _rtrig.change(
1828
  fn=_make_mma_regen(_i, _slot_id),
1829
  inputs=[_rtrig, mma_video, mma_prompt, mma_neg, mma_seed,
1830
+ mma_cfg, mma_steps, mma_cf_dur, mma_cf_db, mma_slot_state_reads[_i]],
1831
  outputs=[mma_slot_vids[_i], mma_slot_waves[_i], mma_slot_states[_i]],
1832
  )
1833
 
 
1852
  with gr.Column():
1853
  (hf_slot_grps, hf_slot_vids,
1854
  hf_slot_waves, hf_slot_rtrigs,
1855
+ hf_slot_states,
1856
+ hf_slot_state_reads) = _make_output_slots("hf")
1857
 
1858
  hf_samples.change(
1859
  fn=_update_slot_visibility,
 
1881
  outputs=hf_slot_grps,
1882
  ))
1883
 
1884
+ # Relay: keep hf_slot_state_reads in sync with hf_slot_states
1885
+ for _st, _str in zip(hf_slot_states, hf_slot_state_reads):
1886
+ _st.change(fn=lambda v: v, inputs=[_st], outputs=[_str])
1887
+
1888
  for _i, _rtrig in enumerate(hf_slot_rtrigs):
1889
  _slot_id = f"hf_{_i}"
1890
  def _make_hf_regen(_si, _sid):
1891
+ def _do(trigger_val, video, prompt, neg, seed, guidance, steps, size, cf_dur, cf_db, state_json):
1892
+ print(f"[regen HF] trigger_val={trigger_val!r} state_json_len={len(state_json) if state_json else 0}")
1893
  if not trigger_val:
1894
  print(f"[regen HF] early-exit: trigger_val empty")
1895
  yield gr.update(), gr.update(), gr.update(); return
1896
+ if not state_json:
1897
+ print(f"[regen HF] early-exit: state_json empty")
1898
+ yield gr.update(), gr.update(), gr.update(); return
1899
+ parts = trigger_val.split("|", 2)
1900
+ if len(parts) < 2 or parts[0] != _sid:
1901
+ print(f"[regen HF] early-exit: parts[0]={parts[0]!r} expected={_sid!r}")
1902
  yield gr.update(), gr.update(), gr.update(); return
1903
+ seg_idx = int(parts[1])
1904
+ print(f"[regen HF] slot={_sid} seg_idx={seg_idx} — acquiring lock")
 
1905
  lock = _get_slot_lock(_sid)
1906
  with lock:
1907
  print(f"[regen HF] slot={_sid} seg_idx={seg_idx} — lock acquired, showing spinner")
 
1926
  _rtrig.change(
1927
  fn=_make_hf_regen(_i, _slot_id),
1928
  inputs=[_rtrig, hf_video, hf_prompt, hf_neg, hf_seed,
1929
+ hf_guidance, hf_steps, hf_size, hf_cf_dur, hf_cf_db, hf_slot_state_reads[_i]],
1930
  outputs=[hf_slot_vids[_i], hf_slot_waves[_i], hf_slot_states[_i]],
1931
  )
1932