BoxOfColors Claude Sonnet 4.6 commited on
Commit
8ced419
Β·
1 Parent(s): 2496cac

Fix regen: CSS-hidden textboxes + remove _rtrig from outputs to fix Too many args

Browse files

- Replace visible=False textboxes with CSS-hidden (elem_classes=['wf-hidden-input'])
so Gradio 5 SSR renders them to the DOM (visible=False omits them entirely,
making getElementById return null and fireRegen silently fail)
- Remove regen trigger from regen handler outputs β€” having a .change() component
as its own output causes SSR argument count mismatch
- Drop 4th gr.update() from all regen yield statements to match 3-output list
- Add .wf-hidden-input CSS class: position absolute off-screen, opacity 0

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

Files changed (1) hide show
  1. app.py +52 -46
app.py CHANGED
@@ -1409,19 +1409,24 @@ 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
- # Hidden textbox: JS writes "<slot_id>|<seg_idx>" to trigger regen
 
 
 
 
1413
  regen_triggers.append(gr.Textbox(
1414
  value="",
1415
- visible=False,
1416
  elem_id=f"regen_trigger_{slot_id}",
1417
- label=f"regen_trigger_{slot_id}",
 
 
1418
  ))
1419
- # Hidden JSON textbox instead of gr.State β€” Gradio 5 SSR counts
1420
- # gr.Textbox correctly in endpoint outputs but not gr.State.
1421
  seg_states.append(gr.Textbox(
1422
  value="",
1423
- visible=False,
1424
- label=f"seg_state_{slot_id}",
 
1425
  ))
1426
  grps.append(g)
1427
  return grps, vids, waveforms, regen_triggers, seg_states
@@ -1487,6 +1492,18 @@ _SLOT_CSS = """
1487
  max-height: 60vh !important;
1488
  object-fit: contain;
1489
  }
 
 
 
 
 
 
 
 
 
 
 
 
1490
  """
1491
 
1492
  _GLOBAL_JS = """
@@ -1529,35 +1546,27 @@ _GLOBAL_JS = """
1529
  }
1530
 
1531
  function fireRegen(slot_id, idx) {
1532
- const hidden_id = 'regen_trigger_' + slot_id;
1533
- const el = document.getElementById(hidden_id);
1534
- if (!el) { console.warn('[fireRegen] element not found:', hidden_id); return; }
1535
  const input = el.querySelector('input, textarea');
1536
- if (!input) { console.warn('[fireRegen] no input inside', hidden_id); return; }
1537
-
1538
- // Gradio 5 / React: must use the native setter to bypass React's controlled-
1539
- // input tracking, otherwise dispatchEvent is swallowed. Also must clear first
1540
- // so a repeat regen of the same segment triggers a value *change*.
1541
- const nativeInputDesc = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')
1542
- || Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value');
1543
- function setAndFire(val) {
1544
- if (nativeInputDesc && nativeInputDesc.set) {
1545
- nativeInputDesc.set.call(input, val);
1546
- } else {
1547
- input.value = val;
1548
- }
1549
  input.dispatchEvent(new Event('input', {bubbles: true}));
1550
  input.dispatchEvent(new Event('change', {bubbles: true}));
1551
  }
1552
-
1553
- // Clear first so even a repeated click on the same segment fires a change
1554
- setAndFire('');
1555
  setTimeout(function() {
1556
- setAndFire(slot_id + '|' + idx);
1557
  console.log('[fireRegen] fired', slot_id + '|' + idx);
1558
  }, 50);
1559
 
1560
- // Update status label
1561
  const lbl = document.getElementById('wf_seglabel_' + slot_id);
1562
  if (lbl) lbl.textContent = 'Regenerating Seg ' + (idx + 1) + '...';
1563
  }
@@ -1662,11 +1671,11 @@ with gr.Blocks(title="Generate Audio for Video", css=_SLOT_CSS, js=_GLOBAL_JS) a
1662
  print(f"[regen TARO] trigger_val={trigger_val!r} state_json_len={len(state_json) if state_json else 0} video={video!r}")
1663
  if not trigger_val or not state_json:
1664
  print(f"[regen TARO] early-exit: trigger_val empty={not trigger_val} state empty={not state_json}")
1665
- return gr.update(), gr.update(), gr.update(value=""), gr.update()
1666
  parts = trigger_val.split("|")
1667
  if len(parts) != 2 or parts[0] != _sid:
1668
  print(f"[regen TARO] early-exit: parts={parts} expected slot={_sid}")
1669
- return gr.update(), gr.update(), gr.update(value=""), gr.update()
1670
  seg_idx = int(parts[1])
1671
  print(f"[regen TARO] slot={_sid} seg_idx={seg_idx} β€” acquiring lock")
1672
  lock = _get_slot_lock(_sid)
@@ -1677,7 +1686,7 @@ with gr.Blocks(title="Generate Audio for Video", css=_SLOT_CSS, js=_GLOBAL_JS) a
1677
  state["segments"], seg_idx, _sid,
1678
  f"regen_trigger_{_sid}"
1679
  )
1680
- yield gr.update(), gr.update(value=pending_html), gr.update(value=state_json), gr.update()
1681
  print(f"[regen TARO] slot={_sid} seg_idx={seg_idx} β€” calling regen_taro_segment")
1682
  try:
1683
  vid, aud, new_meta_json, html = regen_taro_segment(
@@ -1688,14 +1697,13 @@ with gr.Blocks(title="Generate Audio for Video", css=_SLOT_CSS, js=_GLOBAL_JS) a
1688
  except Exception as _e:
1689
  print(f"[regen TARO] slot={_sid} seg_idx={seg_idx} β€” ERROR: {_e}")
1690
  raise
1691
- yield gr.update(value=vid), gr.update(value=html), gr.update(value=new_meta_json), gr.update(value="")
1692
  return _do
1693
  _rtrig.change(
1694
  fn=_make_taro_regen(_i, _slot_id),
1695
  inputs=[_rtrig, taro_video, taro_seed, taro_cfg, taro_steps,
1696
  taro_mode, taro_cf_dur, taro_cf_db, taro_slot_states[_i]],
1697
- outputs=[taro_slot_vids[_i], taro_slot_waves[_i],
1698
- taro_slot_states[_i], _rtrig],
1699
  )
1700
 
1701
  # ---------------------------------------------------------- #
@@ -1753,11 +1761,11 @@ with gr.Blocks(title="Generate Audio for Video", css=_SLOT_CSS, js=_GLOBAL_JS) a
1753
  print(f"[regen MMA] trigger_val={trigger_val!r} state_json_len={len(state_json) if state_json else 0} video={video!r}")
1754
  if not trigger_val or not state_json:
1755
  print(f"[regen MMA] early-exit: trigger_val empty={not trigger_val} state empty={not state_json}")
1756
- return gr.update(), gr.update(), gr.update(value=""), gr.update()
1757
  parts = trigger_val.split("|")
1758
  if len(parts) != 2 or parts[0] != _sid:
1759
  print(f"[regen MMA] early-exit: parts={parts} expected slot={_sid}")
1760
- return gr.update(), gr.update(), gr.update(value=""), gr.update()
1761
  seg_idx = int(parts[1])
1762
  print(f"[regen MMA] slot={_sid} seg_idx={seg_idx} β€” acquiring lock")
1763
  lock = _get_slot_lock(_sid)
@@ -1768,7 +1776,7 @@ with gr.Blocks(title="Generate Audio for Video", css=_SLOT_CSS, js=_GLOBAL_JS) a
1768
  state["segments"], seg_idx, _sid,
1769
  f"regen_trigger_{_sid}"
1770
  )
1771
- yield gr.update(), gr.update(value=pending_html), gr.update(value=state_json), gr.update()
1772
  print(f"[regen MMA] slot={_sid} seg_idx={seg_idx} β€” calling regen_mmaudio_segment")
1773
  try:
1774
  vid, aud, new_meta_json, html = regen_mmaudio_segment(
@@ -1779,14 +1787,13 @@ with gr.Blocks(title="Generate Audio for Video", css=_SLOT_CSS, js=_GLOBAL_JS) a
1779
  except Exception as _e:
1780
  print(f"[regen MMA] slot={_sid} seg_idx={seg_idx} β€” ERROR: {_e}")
1781
  raise
1782
- yield gr.update(value=vid), gr.update(value=html), gr.update(value=new_meta_json), gr.update(value="")
1783
  return _do
1784
  _rtrig.change(
1785
  fn=_make_mma_regen(_i, _slot_id),
1786
  inputs=[_rtrig, mma_video, mma_prompt, mma_neg, mma_seed,
1787
  mma_cfg, mma_steps, mma_cf_dur, mma_cf_db, mma_slot_states[_i]],
1788
- outputs=[mma_slot_vids[_i], mma_slot_waves[_i],
1789
- mma_slot_states[_i], _rtrig],
1790
  )
1791
 
1792
  # ---------------------------------------------------------- #
@@ -1845,11 +1852,11 @@ with gr.Blocks(title="Generate Audio for Video", css=_SLOT_CSS, js=_GLOBAL_JS) a
1845
  print(f"[regen HF] trigger_val={trigger_val!r} state_json_len={len(state_json) if state_json else 0} video={video!r}")
1846
  if not trigger_val or not state_json:
1847
  print(f"[regen HF] early-exit: trigger_val empty={not trigger_val} state empty={not state_json}")
1848
- return gr.update(), gr.update(), gr.update(value=""), gr.update()
1849
  parts = trigger_val.split("|")
1850
  if len(parts) != 2 or parts[0] != _sid:
1851
  print(f"[regen HF] early-exit: parts={parts} expected slot={_sid}")
1852
- return gr.update(), gr.update(), gr.update(value=""), gr.update()
1853
  seg_idx = int(parts[1])
1854
  print(f"[regen HF] slot={_sid} seg_idx={seg_idx} β€” acquiring lock")
1855
  lock = _get_slot_lock(_sid)
@@ -1860,7 +1867,7 @@ with gr.Blocks(title="Generate Audio for Video", css=_SLOT_CSS, js=_GLOBAL_JS) a
1860
  state["segments"], seg_idx, _sid,
1861
  f"regen_trigger_{_sid}"
1862
  )
1863
- yield gr.update(), gr.update(value=pending_html), gr.update(value=state_json), gr.update()
1864
  print(f"[regen HF] slot={_sid} seg_idx={seg_idx} β€” calling regen_hunyuan_segment")
1865
  try:
1866
  vid, aud, new_meta_json, html = regen_hunyuan_segment(
@@ -1871,14 +1878,13 @@ with gr.Blocks(title="Generate Audio for Video", css=_SLOT_CSS, js=_GLOBAL_JS) a
1871
  except Exception as _e:
1872
  print(f"[regen HF] slot={_sid} seg_idx={seg_idx} β€” ERROR: {_e}")
1873
  raise
1874
- yield gr.update(value=vid), gr.update(value=html), gr.update(value=new_meta_json), gr.update(value="")
1875
  return _do
1876
  _rtrig.change(
1877
  fn=_make_hf_regen(_i, _slot_id),
1878
  inputs=[_rtrig, hf_video, hf_prompt, hf_neg, hf_seed,
1879
  hf_guidance, hf_steps, hf_size, hf_cf_dur, hf_cf_db, hf_slot_states[_i]],
1880
- outputs=[hf_slot_vids[_i], hf_slot_waves[_i],
1881
- hf_slot_states[_i], _rtrig],
1882
  )
1883
 
1884
  # ---- Cross-tab video sync ----
 
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}",
1420
+ elem_classes=["wf-hidden-input"],
1421
+ label="",
1422
+ show_label=False,
1423
  ))
1424
+ # State textbox: also CSS-hidden for same reason.
 
1425
  seg_states.append(gr.Textbox(
1426
  value="",
1427
+ elem_classes=["wf-hidden-input"],
1428
+ label="",
1429
+ show_label=False,
1430
  ))
1431
  grps.append(g)
1432
  return grps, vids, waveforms, regen_triggers, seg_states
 
1492
  max-height: 60vh !important;
1493
  object-fit: contain;
1494
  }
1495
+ /* Regen trigger and state textboxes are CSS-hidden, NOT visible=False.
1496
+ Gradio 5 SSR omits visible=False components from the DOM entirely,
1497
+ so JS can never find them. CSS-hidden components are always in the DOM. */
1498
+ .wf-hidden-input {
1499
+ position: absolute !important;
1500
+ left: -9999px !important;
1501
+ width: 1px !important;
1502
+ height: 1px !important;
1503
+ overflow: hidden !important;
1504
+ pointer-events: none !important;
1505
+ opacity: 0 !important;
1506
+ }
1507
  """
1508
 
1509
  _GLOBAL_JS = """
 
1546
  }
1547
 
1548
  function fireRegen(slot_id, idx) {
1549
+ const el = document.getElementById('regen_trigger_' + slot_id);
1550
+ if (!el) { console.warn('[fireRegen] element not found: regen_trigger_' + slot_id); return; }
 
1551
  const input = el.querySelector('input, textarea');
1552
+ if (!input) { console.warn('[fireRegen] no input inside regen_trigger_' + slot_id); return; }
1553
+
1554
+ // Use native setter to bypass React's controlled-input tracking.
1555
+ // Clear to '' first so a repeat click on the same segment still fires .change().
1556
+ const desc = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')
1557
+ || Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value');
1558
+ function setNative(val) {
1559
+ if (desc && desc.set) desc.set.call(input, val);
1560
+ else input.value = val;
 
 
 
 
1561
  input.dispatchEvent(new Event('input', {bubbles: true}));
1562
  input.dispatchEvent(new Event('change', {bubbles: true}));
1563
  }
1564
+ setNative('');
 
 
1565
  setTimeout(function() {
1566
+ setNative(slot_id + '|' + idx);
1567
  console.log('[fireRegen] fired', slot_id + '|' + idx);
1568
  }, 50);
1569
 
 
1570
  const lbl = document.getElementById('wf_seglabel_' + slot_id);
1571
  if (lbl) lbl.textContent = 'Regenerating Seg ' + (idx + 1) + '...';
1572
  }
 
1671
  print(f"[regen TARO] trigger_val={trigger_val!r} state_json_len={len(state_json) if state_json else 0} video={video!r}")
1672
  if not trigger_val or not state_json:
1673
  print(f"[regen TARO] early-exit: trigger_val empty={not trigger_val} state empty={not state_json}")
1674
+ return gr.update(), gr.update(), gr.update(value="")
1675
  parts = trigger_val.split("|")
1676
  if len(parts) != 2 or parts[0] != _sid:
1677
  print(f"[regen TARO] early-exit: parts={parts} expected slot={_sid}")
1678
+ return gr.update(), gr.update(), gr.update(value="")
1679
  seg_idx = int(parts[1])
1680
  print(f"[regen TARO] slot={_sid} seg_idx={seg_idx} β€” acquiring lock")
1681
  lock = _get_slot_lock(_sid)
 
1686
  state["segments"], seg_idx, _sid,
1687
  f"regen_trigger_{_sid}"
1688
  )
1689
+ yield gr.update(), gr.update(value=pending_html), gr.update(value=state_json)
1690
  print(f"[regen TARO] slot={_sid} seg_idx={seg_idx} β€” calling regen_taro_segment")
1691
  try:
1692
  vid, aud, new_meta_json, html = regen_taro_segment(
 
1697
  except Exception as _e:
1698
  print(f"[regen TARO] slot={_sid} seg_idx={seg_idx} β€” ERROR: {_e}")
1699
  raise
1700
+ yield gr.update(value=vid), gr.update(value=html), gr.update(value=new_meta_json)
1701
  return _do
1702
  _rtrig.change(
1703
  fn=_make_taro_regen(_i, _slot_id),
1704
  inputs=[_rtrig, taro_video, taro_seed, taro_cfg, taro_steps,
1705
  taro_mode, taro_cf_dur, taro_cf_db, taro_slot_states[_i]],
1706
+ outputs=[taro_slot_vids[_i], taro_slot_waves[_i], taro_slot_states[_i]],
 
1707
  )
1708
 
1709
  # ---------------------------------------------------------- #
 
1761
  print(f"[regen MMA] trigger_val={trigger_val!r} state_json_len={len(state_json) if state_json else 0} video={video!r}")
1762
  if not trigger_val or not state_json:
1763
  print(f"[regen MMA] early-exit: trigger_val empty={not trigger_val} state empty={not state_json}")
1764
+ return gr.update(), gr.update(), gr.update(value="")
1765
  parts = trigger_val.split("|")
1766
  if len(parts) != 2 or parts[0] != _sid:
1767
  print(f"[regen MMA] early-exit: parts={parts} expected slot={_sid}")
1768
+ return gr.update(), gr.update(), gr.update(value="")
1769
  seg_idx = int(parts[1])
1770
  print(f"[regen MMA] slot={_sid} seg_idx={seg_idx} β€” acquiring lock")
1771
  lock = _get_slot_lock(_sid)
 
1776
  state["segments"], seg_idx, _sid,
1777
  f"regen_trigger_{_sid}"
1778
  )
1779
+ yield gr.update(), gr.update(value=pending_html), gr.update(value=state_json)
1780
  print(f"[regen MMA] slot={_sid} seg_idx={seg_idx} β€” calling regen_mmaudio_segment")
1781
  try:
1782
  vid, aud, new_meta_json, html = regen_mmaudio_segment(
 
1787
  except Exception as _e:
1788
  print(f"[regen MMA] slot={_sid} seg_idx={seg_idx} β€” ERROR: {_e}")
1789
  raise
1790
+ yield gr.update(value=vid), gr.update(value=html), gr.update(value=new_meta_json)
1791
  return _do
1792
  _rtrig.change(
1793
  fn=_make_mma_regen(_i, _slot_id),
1794
  inputs=[_rtrig, mma_video, mma_prompt, mma_neg, mma_seed,
1795
  mma_cfg, mma_steps, mma_cf_dur, mma_cf_db, mma_slot_states[_i]],
1796
+ outputs=[mma_slot_vids[_i], mma_slot_waves[_i], mma_slot_states[_i]],
 
1797
  )
1798
 
1799
  # ---------------------------------------------------------- #
 
1852
  print(f"[regen HF] trigger_val={trigger_val!r} state_json_len={len(state_json) if state_json else 0} video={video!r}")
1853
  if not trigger_val or not state_json:
1854
  print(f"[regen HF] early-exit: trigger_val empty={not trigger_val} state empty={not state_json}")
1855
+ return gr.update(), gr.update(), gr.update(value="")
1856
  parts = trigger_val.split("|")
1857
  if len(parts) != 2 or parts[0] != _sid:
1858
  print(f"[regen HF] early-exit: parts={parts} expected slot={_sid}")
1859
+ return gr.update(), gr.update(), gr.update(value="")
1860
  seg_idx = int(parts[1])
1861
  print(f"[regen HF] slot={_sid} seg_idx={seg_idx} β€” acquiring lock")
1862
  lock = _get_slot_lock(_sid)
 
1867
  state["segments"], seg_idx, _sid,
1868
  f"regen_trigger_{_sid}"
1869
  )
1870
+ yield gr.update(), gr.update(value=pending_html), gr.update(value=state_json)
1871
  print(f"[regen HF] slot={_sid} seg_idx={seg_idx} β€” calling regen_hunyuan_segment")
1872
  try:
1873
  vid, aud, new_meta_json, html = regen_hunyuan_segment(
 
1878
  except Exception as _e:
1879
  print(f"[regen HF] slot={_sid} seg_idx={seg_idx} β€” ERROR: {_e}")
1880
  raise
1881
+ yield gr.update(value=vid), gr.update(value=html), gr.update(value=new_meta_json)
1882
  return _do
1883
  _rtrig.change(
1884
  fn=_make_hf_regen(_i, _slot_id),
1885
  inputs=[_rtrig, hf_video, hf_prompt, hf_neg, hf_seed,
1886
  hf_guidance, hf_steps, hf_size, hf_cf_dur, hf_cf_db, hf_slot_states[_i]],
1887
+ outputs=[hf_slot_vids[_i], hf_slot_waves[_i], hf_slot_states[_i]],
 
1888
  )
1889
 
1890
  # ---- Cross-tab video sync ----