alexnasa commited on
Commit
ebc32fc
·
verified ·
1 Parent(s): 7e1323a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +323 -63
app.py CHANGED
@@ -570,19 +570,19 @@ class RadioAnimated(gr.HTML):
570
 
571
  class PromptBox(gr.HTML):
572
  """
573
- DeepSite-like prompt box (HTML textarea) that behaves like an input component.
574
- Outputs: the current text value (string)
575
  """
576
- def __init__(self, value="", placeholder="Describe the video with audio you want to generate...", **kwargs):
577
  uid = uuid.uuid4().hex[:8]
578
 
579
  html_template = f"""
580
- <div style="text-align:center; font-weight:600; margin-bottom:6px;">
581
- Prompt
582
- </div>
583
- <div class="ds-prompt" data-ds="{uid}">
584
- <textarea class="ds-textarea" rows="3"
585
- placeholder="{placeholder}"></textarea>
 
586
  </div>
587
  """
588
 
@@ -590,50 +590,65 @@ class PromptBox(gr.HTML):
590
  (() => {
591
  const textarea = element.querySelector(".ds-textarea");
592
  if (!textarea) return;
593
-
594
- // Auto-resize (optional, but nice)
595
  const autosize = () => {
596
  textarea.style.height = "0px";
597
  textarea.style.height = Math.min(textarea.scrollHeight, 240) + "px";
598
  };
599
-
600
- // Set initial value from props.value
601
  const setValue = (v, triggerChange=false) => {
602
  const val = (v ?? "");
603
  if (textarea.value !== val) textarea.value = val;
604
  autosize();
605
-
606
  props.value = textarea.value;
607
  if (triggerChange) trigger("change", props.value);
608
  };
609
-
610
  setValue(props.value, false);
611
-
612
- // Update Gradio value on input
613
  textarea.addEventListener("input", () => {
614
  autosize();
615
  props.value = textarea.value;
616
  trigger("change", props.value);
617
  });
618
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
619
  let last = props.value;
620
  const syncFromProps = () => {
621
  if (props.value !== last) {
622
  last = props.value;
623
- setValue(last, false); // don't re-trigger change loop
624
  }
625
  requestAnimationFrame(syncFromProps);
626
  };
627
  requestAnimationFrame(syncFromProps);
628
  })();
 
629
  """
630
 
631
- super().__init__(
632
- value=value,
633
- html_template=html_template,
634
- js_on_load=js_on_load,
635
- **kwargs
636
- )
637
 
638
  class CameraDropdown(gr.HTML):
639
  """
@@ -1010,7 +1025,7 @@ css = """
1010
 
1011
  /* Make the row behave nicely */
1012
  #controls-row {
1013
- display: flex;
1014
  align-items: center;
1015
  gap: 12px;
1016
  flex-wrap: nowrap; /* or wrap if you prefer on small screens */
@@ -1023,14 +1038,6 @@ css = """
1023
  min-width: 0 !important;
1024
  }
1025
 
1026
-
1027
- /* Same idea for your radio HTML blocks (optional but helps) */
1028
- #radioanimated_duration,
1029
- #radioanimated_duration > div,
1030
- #radioanimated_resolution,
1031
- #radioanimated_resolution > div {
1032
- width: fit-content !important;
1033
- }
1034
 
1035
  #col-container {
1036
  margin: 0 auto;
@@ -1210,35 +1217,88 @@ css += """
1210
  .ds-textarea{
1211
  width: 100%;
1212
  box-sizing: border-box;
1213
-
1214
  background: #2b2b2b;
1215
  color: rgba(255,255,255,0.9);
1216
-
1217
  border: 1px solid rgba(255,255,255,0.12);
1218
  border-radius: 14px;
1219
-
1220
  padding: 14px 16px;
1221
  outline: none;
1222
-
1223
  font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial;
1224
  font-size: 15px;
1225
  line-height: 1.35;
1226
-
1227
  resize: none;
1228
- height: 94px;
1229
- min-height: 94px;
1230
- max-height: 94px;
1231
  overflow-y: auto;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1232
  }
1233
 
1234
- .ds-textarea::placeholder{
1235
- color: rgba(255,255,255,0.55);
 
 
 
 
 
 
 
 
1236
  }
1237
 
1238
- .ds-textarea:focus{
1239
- border-color: rgba(255,255,255,0.22);
1240
- box-shadow: 0 0 0 3px rgba(255,255,255,0.06);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1241
  }
 
1242
  """
1243
 
1244
  css += """
@@ -1418,6 +1478,172 @@ css += """
1418
  .cd-label{
1419
  flex: 1;
1420
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1421
  """
1422
 
1423
 
@@ -1454,11 +1680,45 @@ with gr.Blocks(title="LTX-2 Video Distilled 🎥🔈") as demo:
1454
  with gr.Column(elem_id="step-column"):
1455
 
1456
  input_image = gr.Image(
1457
- label="Input Image (Optional)",
1458
- type="filepath", # <-- was "pil"
1459
- height=512
1460
  )
1461
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1462
  prompt_ui = PromptBox(
1463
  value="Make this image come alive with cinematic motion, smooth animation",
1464
  elem_id="prompt_ui",
@@ -1498,11 +1758,11 @@ with gr.Blocks(title="LTX-2 Video Distilled 🎥🔈") as demo:
1498
 
1499
  with gr.Row(elem_id="controls-row"):
1500
 
1501
- radioanimated_duration = CameraDropdown(
1502
  choices=["3s", "5s", "10s", "15s"],
1503
  value="5s",
1504
  title="Clip Duration",
1505
- elem_id="radioanimated_duration"
1506
  )
1507
 
1508
  duration = gr.Slider(
@@ -1527,7 +1787,7 @@ with gr.Blocks(title="LTX-2 Video Distilled 🎥🔈") as demo:
1527
  </svg>"""
1528
 
1529
 
1530
- radioanimated_resolution = CameraDropdown(
1531
  choices=[
1532
  {"label": "16:9", "value": "16:9", "icon": ICON_16_9},
1533
  {"label": "1:1", "value": "1:1", "icon": ICON_1_1},
@@ -1535,18 +1795,18 @@ with gr.Blocks(title="LTX-2 Video Distilled 🎥🔈") as demo:
1535
  ],
1536
  value="16:9",
1537
  title="Resolution",
1538
- elem_id="radioanimated_resolution"
1539
  )
1540
 
1541
 
1542
  width = gr.Number(label="Width", value=DEFAULT_1_STAGE_WIDTH, precision=0, visible=False)
1543
  height = gr.Number(label="Height", value=DEFAULT_1_STAGE_HEIGHT, precision=0, visible=False)
1544
 
1545
- camera_lora_ui = CameraDropdown(
1546
  choices=[name for name, _ in RUNTIME_LORA_CHOICES],
1547
  value="No LoRA",
1548
  title="Camera LoRA",
1549
- elem_id="camera_lora_ui",
1550
  )
1551
 
1552
  # Hidden real dropdown (backend value)
@@ -1559,22 +1819,22 @@ with gr.Blocks(title="LTX-2 Video Distilled 🎥🔈") as demo:
1559
 
1560
  generate_btn = gr.Button("🤩 Generate Video", variant="primary", elem_classes="button-gradient")
1561
 
1562
- camera_lora_ui.change(
1563
  fn=lambda x: x,
1564
- inputs=camera_lora_ui,
1565
  outputs=camera_lora,
1566
  api_visibility="private"
1567
  )
1568
 
1569
- radioanimated_duration.change(
1570
  fn=apply_duration,
1571
- inputs=radioanimated_duration,
1572
  outputs=[duration],
1573
  api_visibility="private"
1574
  )
1575
- radioanimated_resolution.change(
1576
  fn=apply_resolution,
1577
- inputs=radioanimated_resolution,
1578
  outputs=[width, height],
1579
  api_visibility="private"
1580
  )
@@ -1645,7 +1905,7 @@ with gr.Blocks(title="LTX-2 Video Distilled 🎥🔈") as demo:
1645
 
1646
  ],
1647
  fn=generate_video_example,
1648
- inputs=[input_image, prompt_ui, camera_lora_ui, radioanimated_resolution, audio_input],
1649
  outputs = [output_video],
1650
  label="I2V Examples",
1651
  cache_examples=True,
 
570
 
571
  class PromptBox(gr.HTML):
572
  """
573
+ Prompt textarea with an internal footer slot (.ds-footer) where we can inject dropdowns.
 
574
  """
575
+ def __init__(self, value="", placeholder="Describe what you want...", **kwargs):
576
  uid = uuid.uuid4().hex[:8]
577
 
578
  html_template = f"""
579
+ <div class="ds-card" data-ds="{uid}">
580
+ <div class="ds-top">
581
+ <textarea class="ds-textarea" rows="3" placeholder="{placeholder}"></textarea>
582
+
583
+ <!-- footer slot -->
584
+ <div class="ds-footer" aria-label="prompt-footer"></div>
585
+ </div>
586
  </div>
587
  """
588
 
 
590
  (() => {
591
  const textarea = element.querySelector(".ds-textarea");
592
  if (!textarea) return;
593
+
 
594
  const autosize = () => {
595
  textarea.style.height = "0px";
596
  textarea.style.height = Math.min(textarea.scrollHeight, 240) + "px";
597
  };
598
+
 
599
  const setValue = (v, triggerChange=false) => {
600
  const val = (v ?? "");
601
  if (textarea.value !== val) textarea.value = val;
602
  autosize();
 
603
  props.value = textarea.value;
604
  if (triggerChange) trigger("change", props.value);
605
  };
606
+
607
  setValue(props.value, false);
608
+
 
609
  textarea.addEventListener("input", () => {
610
  autosize();
611
  props.value = textarea.value;
612
  trigger("change", props.value);
613
  });
614
+
615
+ // ✅ Focus-on-load (robust)
616
+ const shouldAutoFocus = () => {
617
+ // don’t steal focus if user already clicked/typed somewhere
618
+ const ae = document.activeElement;
619
+ if (ae && ae !== document.body && ae !== document.documentElement) return false;
620
+ // don’t auto-focus on small screens (optional; avoids mobile keyboard pop)
621
+ if (window.matchMedia && window.matchMedia("(max-width: 768px)").matches) return false;
622
+ return true;
623
+ };
624
+
625
+ const focusWithRetry = (tries = 30) => {
626
+ if (!shouldAutoFocus()) return;
627
+ // only focus if still not focused
628
+ if (document.activeElement !== textarea) textarea.focus({ preventScroll: true });
629
+ if (document.activeElement === textarea) return;
630
+ if (tries > 0) requestAnimationFrame(() => focusWithRetry(tries - 1));
631
+ };
632
+
633
+ // wait a tick so Gradio/layout settles
634
+ requestAnimationFrame(() => focusWithRetry());
635
+
636
+ // keep your sync loop
637
  let last = props.value;
638
  const syncFromProps = () => {
639
  if (props.value !== last) {
640
  last = props.value;
641
+ setValue(last, false);
642
  }
643
  requestAnimationFrame(syncFromProps);
644
  };
645
  requestAnimationFrame(syncFromProps);
646
  })();
647
+
648
  """
649
 
650
+ super().__init__(value=value, html_template=html_template, js_on_load=js_on_load, **kwargs)
651
+
 
 
 
 
652
 
653
  class CameraDropdown(gr.HTML):
654
  """
 
1025
 
1026
  /* Make the row behave nicely */
1027
  #controls-row {
1028
+ display: none !important;
1029
  align-items: center;
1030
  gap: 12px;
1031
  flex-wrap: nowrap; /* or wrap if you prefer on small screens */
 
1038
  min-width: 0 !important;
1039
  }
1040
 
 
 
 
 
 
 
 
 
1041
 
1042
  #col-container {
1043
  margin: 0 auto;
 
1217
  .ds-textarea{
1218
  width: 100%;
1219
  box-sizing: border-box;
 
1220
  background: #2b2b2b;
1221
  color: rgba(255,255,255,0.9);
 
1222
  border: 1px solid rgba(255,255,255,0.12);
1223
  border-radius: 14px;
 
1224
  padding: 14px 16px;
1225
  outline: none;
 
1226
  font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial;
1227
  font-size: 15px;
1228
  line-height: 1.35;
 
1229
  resize: none;
1230
+ min-height: 210px;
1231
+ max-height: 210px;
 
1232
  overflow-y: auto;
1233
+
1234
+ /* IMPORTANT: space for the footer controls */
1235
+ padding-bottom: 72px;
1236
+ }
1237
+
1238
+
1239
+ .ds-card{
1240
+ width: 100%;
1241
+ max-width: 720px;
1242
+ margin: 0 auto;
1243
+ }
1244
+ .ds-top{
1245
+ position: relative;
1246
+ }
1247
+
1248
+ /* Make room for footer inside textarea */
1249
+ .ds-textarea{
1250
+ padding-bottom: 72px;
1251
  }
1252
 
1253
+ /* Footer positioning */
1254
+ .ds-footer{
1255
+ position: absolute;
1256
+ right: 12px;
1257
+ bottom: 10px;
1258
+ display: flex;
1259
+ gap: 8px;
1260
+ align-items: center;
1261
+ justify-content: flex-end;
1262
+ z-index: 3;
1263
  }
1264
 
1265
+ /* Smaller pill buttons inside footer */
1266
+ .ds-footer .cd-trigger{
1267
+ min-height: 32px;
1268
+ padding: 6px 10px;
1269
+ font-size: 12px;
1270
+ gap: 6px;
1271
+ border-radius: 9999px;
1272
+ }
1273
+ .ds-footer .cd-trigger-icon,
1274
+ .ds-footer .cd-icn{
1275
+ width: 14px;
1276
+ height: 14px;
1277
+ }
1278
+ .ds-footer .cd-trigger-icon svg,
1279
+ .ds-footer .cd-icn svg{
1280
+ width: 14px;
1281
+ height: 14px;
1282
+ }
1283
+ .ds-footer .cd-caret{
1284
+ font-size: 11px;
1285
+ }
1286
+
1287
+ /* Bottom safe area bar (optional but looks nicer) */
1288
+ .ds-top::after{
1289
+ content: "";
1290
+ position: absolute;
1291
+ left: 1px;
1292
+ right: 1px;
1293
+ bottom: 1px;
1294
+ height: 56px;
1295
+ background: #2b2b2b;
1296
+ border-bottom-left-radius: 13px;
1297
+ border-bottom-right-radius: 13px;
1298
+ pointer-events: none;
1299
+ z-index: 2;
1300
  }
1301
+
1302
  """
1303
 
1304
  css += """
 
1478
  .cd-label{
1479
  flex: 1;
1480
  }
1481
+
1482
+ /* =========================
1483
+ FIX: prompt border + scrollbar bleed
1484
+ ========================= */
1485
+
1486
+ /* Put the border + background on the wrapper, not the textarea */
1487
+ .ds-top{
1488
+ position: relative;
1489
+ background: #2b2b2b;
1490
+ border: 1px solid rgba(255,255,255,0.12);
1491
+ border-radius: 14px;
1492
+ overflow: hidden; /* ensures the footer bar is clipped to rounded corners */
1493
+ }
1494
+
1495
+ /* Make textarea "transparent" so wrapper owns the border/background */
1496
+ .ds-textarea{
1497
+ background: transparent !important;
1498
+ border: none !important;
1499
+ border-radius: 0 !important; /* wrapper handles radius */
1500
+ outline: none;
1501
+
1502
+ /* keep your spacing */
1503
+ padding: 14px 16px;
1504
+ padding-bottom: 72px; /* room for footer */
1505
+ width: 100%;
1506
+ box-sizing: border-box;
1507
+
1508
+ /* keep scroll behavior */
1509
+ overflow-y: auto;
1510
+
1511
+ /* prevent scrollbar bleed by hiding native scrollbar */
1512
+ scrollbar-width: none; /* Firefox */
1513
+ }
1514
+ .ds-textarea::-webkit-scrollbar{ /* Chrome/Safari */
1515
+ width: 0;
1516
+ height: 0;
1517
+ }
1518
+
1519
+ /* Safe-area bar: now it matches perfectly because it's inside the same bordered wrapper */
1520
+ .ds-top::after{
1521
+ content: "";
1522
+ position: absolute;
1523
+ left: 0;
1524
+ right: 0;
1525
+ bottom: 0;
1526
+ height: 56px;
1527
+ background: #2b2b2b;
1528
+ pointer-events: none;
1529
+ z-index: 2;
1530
+ }
1531
+
1532
+ /* Footer above the bar */
1533
+ .ds-footer{
1534
+ position: absolute;
1535
+ right: 12px;
1536
+ bottom: 10px;
1537
+ display: flex;
1538
+ gap: 8px;
1539
+ align-items: center;
1540
+ justify-content: flex-end;
1541
+ z-index: 3;
1542
+ }
1543
+
1544
+ /* Ensure textarea content sits below overlays */
1545
+ .ds-textarea{
1546
+ position: relative;
1547
+ z-index: 1;
1548
+ }
1549
+
1550
+ /* ===== FIX dropdown menu being clipped/behind ===== */
1551
+
1552
+ /* Let the dropdown menu escape the prompt wrapper */
1553
+ .ds-top{
1554
+ overflow: visible !important; /* IMPORTANT: do not clip the menu */
1555
+ }
1556
+
1557
+ /* Keep the rounded "safe area" look without clipping the menu */
1558
+ .ds-top::after{
1559
+ left: 0 !important;
1560
+ right: 0 !important;
1561
+ bottom: 0 !important;
1562
+ border-bottom-left-radius: 14px !important;
1563
+ border-bottom-right-radius: 14px !important;
1564
+ }
1565
+
1566
+ /* Ensure the footer stays above the safe-area bar */
1567
+ .ds-footer{
1568
+ z-index: 20 !important;
1569
+ }
1570
+
1571
+ /* Make sure the opened menu is above EVERYTHING */
1572
+ .ds-footer .cd-menu{
1573
+ z-index: 999999 !important;
1574
+ }
1575
+
1576
+ /* Sometimes Gradio/columns/cards create stacking contexts;
1577
+ force the whole prompt card above nearby panels */
1578
+ .ds-card{
1579
+ position: relative;
1580
+ z-index: 50;
1581
+ }
1582
+
1583
+ /* --- Fix focus highlight shape (make it match rounded container) --- */
1584
+
1585
+ /* Kill any theme focus ring on the textarea itself */
1586
+ .ds-textarea:focus,
1587
+ .ds-textarea:focus-visible{
1588
+ outline: none !important;
1589
+ box-shadow: none !important;
1590
+ }
1591
+
1592
+ /* Optional: if some themes apply it even when not focused */
1593
+ .ds-textarea{
1594
+ outline: none !important;
1595
+ }
1596
+
1597
+ /* Apply the focus ring to the rounded wrapper instead */
1598
+ .ds-top:focus-within{
1599
+ border-color: rgba(255,255,255,0.22) !important;
1600
+ box-shadow: 0 0 0 3px rgba(255,255,255,0.06) !important;
1601
+ border-radius: 14px !important;
1602
+ }
1603
+
1604
+ /* If you see any tiny square corners, ensure the wrapper clips its own shadow properly */
1605
+ .ds-top{
1606
+ border-radius: 14px !important;
1607
+ }
1608
+
1609
+ /* =========================
1610
+ CameraDropdown: force readable menu text in BOTH themes
1611
+ ========================= */
1612
+
1613
+ /* Menu surface */
1614
+ .cd-menu{
1615
+ background: #2b2b2b !important;
1616
+ border: 1px solid rgba(255,255,255,0.14) !important;
1617
+ }
1618
+
1619
+ /* Title */
1620
+ .cd-title{
1621
+ color: rgba(255,255,255,0.55) !important;
1622
+ }
1623
+
1624
+ /* Items + all descendants (fixes spans / inherited theme colors) */
1625
+ .cd-item,
1626
+ .cd-item *{
1627
+ color: rgba(255,255,255,0.92) !important;
1628
+ }
1629
+
1630
+ /* Hover state */
1631
+ .cd-item:hover{
1632
+ background: rgba(255,255,255,0.10) !important;
1633
+ }
1634
+
1635
+ /* Checkmark */
1636
+ .cd-item::after{
1637
+ color: rgba(255,255,255,0.92) !important;
1638
+ }
1639
+
1640
+ /* (Optional) make sure the trigger stays readable too */
1641
+ .cd-trigger,
1642
+ .cd-trigger *{
1643
+ color: rgba(255,255,255,0.75) !important;
1644
+ }
1645
+
1646
+
1647
  """
1648
 
1649
 
 
1680
  with gr.Column(elem_id="step-column"):
1681
 
1682
  input_image = gr.Image(
1683
+ label="First Frame (Optional)",
1684
+ type="filepath",
1685
+ height=256
1686
  )
1687
 
1688
+ relocate = gr.HTML(
1689
+ value="",
1690
+ html_template="<div></div>",
1691
+ js_on_load=r"""
1692
+ (() => {
1693
+ function moveIntoFooter() {
1694
+ const promptRoot = document.querySelector("#prompt_ui");
1695
+ if (!promptRoot) return false;
1696
+
1697
+ const footer = promptRoot.querySelector(".ds-footer");
1698
+ if (!footer) return false;
1699
+
1700
+ const dur = document.querySelector("#duration_ui .cd-wrap");
1701
+ const res = document.querySelector("#resolution_ui .cd-wrap");
1702
+ const cam = document.querySelector("#camera_ui .cd-wrap");
1703
+
1704
+ if (!dur || !res || !cam) return false;
1705
+
1706
+ footer.appendChild(dur);
1707
+ footer.appendChild(res);
1708
+ footer.appendChild(cam);
1709
+
1710
+ return true;
1711
+ }
1712
+
1713
+ const tick = () => {
1714
+ if (!moveIntoFooter()) requestAnimationFrame(tick);
1715
+ };
1716
+ requestAnimationFrame(tick);
1717
+ })();
1718
+ """
1719
+ )
1720
+
1721
+
1722
  prompt_ui = PromptBox(
1723
  value="Make this image come alive with cinematic motion, smooth animation",
1724
  elem_id="prompt_ui",
 
1758
 
1759
  with gr.Row(elem_id="controls-row"):
1760
 
1761
+ duration_ui = CameraDropdown(
1762
  choices=["3s", "5s", "10s", "15s"],
1763
  value="5s",
1764
  title="Clip Duration",
1765
+ elem_id="duration_ui"
1766
  )
1767
 
1768
  duration = gr.Slider(
 
1787
  </svg>"""
1788
 
1789
 
1790
+ resolution_ui = CameraDropdown(
1791
  choices=[
1792
  {"label": "16:9", "value": "16:9", "icon": ICON_16_9},
1793
  {"label": "1:1", "value": "1:1", "icon": ICON_1_1},
 
1795
  ],
1796
  value="16:9",
1797
  title="Resolution",
1798
+ elem_id="resolution_ui"
1799
  )
1800
 
1801
 
1802
  width = gr.Number(label="Width", value=DEFAULT_1_STAGE_WIDTH, precision=0, visible=False)
1803
  height = gr.Number(label="Height", value=DEFAULT_1_STAGE_HEIGHT, precision=0, visible=False)
1804
 
1805
+ camera_ui = CameraDropdown(
1806
  choices=[name for name, _ in RUNTIME_LORA_CHOICES],
1807
  value="No LoRA",
1808
  title="Camera LoRA",
1809
+ elem_id="camera_ui",
1810
  )
1811
 
1812
  # Hidden real dropdown (backend value)
 
1819
 
1820
  generate_btn = gr.Button("🤩 Generate Video", variant="primary", elem_classes="button-gradient")
1821
 
1822
+ camera_ui.change(
1823
  fn=lambda x: x,
1824
+ inputs=camera_ui,
1825
  outputs=camera_lora,
1826
  api_visibility="private"
1827
  )
1828
 
1829
+ duration_ui.change(
1830
  fn=apply_duration,
1831
+ inputs=duration_ui,
1832
  outputs=[duration],
1833
  api_visibility="private"
1834
  )
1835
+ resolution_ui.change(
1836
  fn=apply_resolution,
1837
+ inputs=resolution_ui,
1838
  outputs=[width, height],
1839
  api_visibility="private"
1840
  )
 
1905
 
1906
  ],
1907
  fn=generate_video_example,
1908
+ inputs=[input_image, prompt_ui, camera_ui, resolution_ui, audio_input],
1909
  outputs = [output_video],
1910
  label="I2V Examples",
1911
  cache_examples=True,