| |
| """Reusable Gradio components shared across modes.""" |
|
|
| from __future__ import annotations |
|
|
| from dataclasses import dataclass |
|
|
| import gradio as gr |
|
|
|
|
| def preset_bar(label: str = "Preset") -> gr.Radio: |
| """Fast / Balanced / Quality radio. Use as a single component.""" |
| return gr.Radio( |
| choices=["Fast", "Balanced", "Quality"], |
| value="Balanced", |
| label=label, |
| container=True, |
| info="Fast: distilled 8 steps · Balanced: two-stage 30+4 · Quality: HQ res_2s sampler", |
| ) |
|
|
|
|
| def status_banner() -> gr.HTML: |
| """Status banner: stage chips + progress + memory.""" |
| return gr.HTML( |
| value=_render_idle(), |
| elem_classes=["status-banner"], |
| ) |
|
|
|
|
| def _render_idle() -> str: |
| return ( |
| '<div class="status-card status-idle">' |
| '<div class="status-row"><span class="status-dot"></span>' |
| '<span class="status-label">Idle</span></div></div>' |
| ) |
|
|
|
|
| def render_status( |
| stage_index: int, |
| stage_label: str, |
| step: int, |
| total_steps: int, |
| elapsed_s: float, |
| eta_s: float, |
| memory_text: str = "", |
| ) -> str: |
| """Render a status banner HTML string for the current event.""" |
| pct = 0 if total_steps <= 0 else int(100 * step / total_steps) |
| return ( |
| f'<div class="status-card">' |
| f' <div class="status-row">' |
| f' <span class="status-stage">Stage {stage_index} · {stage_label}</span>' |
| f' <span class="status-meta">Step {step}/{total_steps} · ' |
| f" {_fmt_secs(elapsed_s)} elapsed · ~{_fmt_secs(eta_s)} remaining</span>" |
| f" </div>" |
| f' <div class="status-bar"><div class="status-fill" style="width:{pct}%"></div></div>' |
| f' <div class="status-mem">{memory_text}</div>' |
| f"</div>" |
| ) |
|
|
|
|
| def _fmt_secs(secs: float) -> str: |
| secs = int(max(0, secs)) |
| if secs < 60: |
| return f"{secs}s" |
| return f"{secs // 60}m {secs % 60}s" |
|
|
|
|
| CAMERA_LORAS: list[str] = [ |
| "none", |
| "static", |
| "dolly-in", |
| "dolly-out", |
| "dolly-left", |
| "dolly-right", |
| "jib-up", |
| "jib-down", |
| ] |
|
|
| IC_LORAS_BY_MODE: dict[str, list[str]] = { |
| "t2v": [], |
| "a2v": [], |
| "i2v": ["union", "pose-control"], |
| "lipsync": ["pose-control"], |
| "keyframe": ["union"], |
| "style": ["motion-track", "union"], |
| } |
|
|
|
|
| @dataclass |
| class LoRAComponents: |
| camera_lora: gr.Dropdown |
| camera_strength: gr.Slider |
| detailer_on: gr.Checkbox |
| detailer_strength: gr.Slider |
| ic_lora: gr.Dropdown | None |
| ic_strength: gr.Slider | None |
| pose_on: gr.Checkbox | None |
|
|
|
|
| def lora_chrome(mode: str) -> LoRAComponents: |
| """Categorized LoRA controls for a given mode (camera + detailer + IC + pose). |
| |
| Only LoRAs relevant to the mode are surfaced. Distilled LoRA is auto-applied |
| by the workflow when the Fast preset is chosen — not exposed here. |
| """ |
| with gr.Group(): |
| gr.Markdown("**📷 Camera Movement**") |
| camera_lora = gr.Dropdown( |
| choices=CAMERA_LORAS, |
| value="none", |
| label="Camera", |
| info="Mutually exclusive — pick one camera direction or none.", |
| ) |
| camera_strength = gr.Slider( |
| minimum=0.0, |
| maximum=1.5, |
| value=0.8, |
| step=0.05, |
| label="Camera strength", |
| visible=True, |
| ) |
|
|
| with gr.Group(): |
| gr.Markdown("**✨ Detailer**") |
| detailer_on = gr.Checkbox(label="Apply IC-LoRA-Detailer", value=False) |
| detailer_strength = gr.Slider( |
| minimum=0.0, |
| maximum=1.0, |
| value=0.5, |
| step=0.05, |
| label="Detailer strength", |
| ) |
|
|
| ic_lora = ic_strength = pose_on = None |
| ic_options = IC_LORAS_BY_MODE.get(mode, []) |
| if ic_options: |
| with gr.Group(): |
| gr.Markdown("**🎯 Image Conditioning**") |
| ic_lora = gr.Dropdown( |
| choices=["none"] + ic_options, |
| value=ic_options[0] if ic_options else "none", |
| label="IC-LoRA", |
| ) |
| ic_strength = gr.Slider( |
| minimum=0.0, |
| maximum=1.0, |
| value=0.5, |
| step=0.05, |
| label="IC strength", |
| ) |
|
|
| if mode in ("i2v", "lipsync"): |
| with gr.Group(): |
| gr.Markdown("**🚶 Pose Control**") |
| pose_on = gr.Checkbox(label="Apply IC-LoRA-Pose-Control", value=False) |
|
|
| return LoRAComponents( |
| camera_lora=camera_lora, |
| camera_strength=camera_strength, |
| detailer_on=detailer_on, |
| detailer_strength=detailer_strength, |
| ic_lora=ic_lora, |
| ic_strength=ic_strength, |
| pose_on=pose_on, |
| ) |
|
|