"""MiniMax-H3 Turbo LoRAs - a video+audio studio with a stackable LoRA rack. The UI is the whole of this module: request orchestration lives in `h3_engine`, the LoRA rack in `lora_stack`. No prompt is inspected, rewritten or refused anywhere in this Space - the only rejected request is an empty one, which the model itself does not accept. """ from __future__ import annotations import random import gradio as gr import h3_engine import lora_stack SLOTS = 5 MAX_SEED = 2**32 - 1 DEFAULT_PROMPT = ( "A lighthouse keeper climbs the spiral stairs at dawn, lantern swinging, waves booming against the rocks below" ) # --------------------------------------------------------------------------- # Handlers # --------------------------------------------------------------------------- def run( prompt, first_frame, last_frame, canvas, duration, steps, seed, randomize, *slot_values, request: gr.Request = None, ): """One Generate press. `slot_values` is `key, url, weight` per slot, in slot order.""" if not (prompt or "").strip(): raise gr.Error("MiniMax-H3 always takes a prompt, keyframes or not.") if randomize: seed = random.randint(0, MAX_SEED) slots = [tuple(slot_values[index * 3 : index * 3 + 3]) for index in range(SLOTS)] try: stack = lora_stack.build_stack(slots) except ValueError as error: raise gr.Error(str(error)) from error # The visitor's ZeroGPU token, so the conditioner's encode is booked against their quota and not this Space's # shared IP allowance. ip_token = request.headers.get("x-ip-token") if request is not None else None try: path, report, facts = h3_engine.generate( prompt, first_frame, last_frame, canvas, duration, steps, int(seed), stack, ip_token=ip_token, ) except gr.Error: raise except Exception as error: raise gr.Error(f"{type(error).__name__}: {error}") from error return path, str(facts["seed"]), facts["geometry"], facts["timing"], facts["loras"], report def frame_readout(duration): frames = h3_engine.snap_frames(duration) return ( f'
{frames} frames · ' f"{frames / h3_engine.FPS:.2f} s at {h3_engine.FPS} fps · " f"snapped to the VAE grid (17n+5)
" ) def step_hint(*keys): steps = lora_stack.suggested_steps(keys) if steps is None: return '
No turbo LoRA in the rack - the base checkpoint wants roughly 28 steps.
' return f'
Turbo LoRA in the rack - it was distilled for about {steps} steps.
' def slot_card(key): """The blurb under a slot: what the LoRA does, how to drive it, where it lives.""" about, hint, url = lora_stack.about(key) if not about: return "" link = f' model card ↗' if url else "" return f'
{about}{link}
{hint}
' def show_url(key): return gr.Textbox(visible=key == lora_stack.CUSTOM_CHOICE) def quick_add(preset): """A chip fills the first free slot, and does nothing when the preset is already racked.""" def handler(*keys): keys = list(keys) if preset in keys: return tuple(keys) free = keys.index(lora_stack.NONE_CHOICE) if lora_stack.NONE_CHOICE in keys else 0 keys[free] = preset return tuple(keys) return handler # --------------------------------------------------------------------------- # Theme + CSS # --------------------------------------------------------------------------- THEME = gr.themes.Base( primary_hue=gr.themes.colors.amber, secondary_hue=gr.themes.colors.teal, neutral_hue=gr.themes.colors.gray, radius_size=gr.themes.sizes.radius_sm, font=[gr.themes.GoogleFont("Space Grotesk"), "ui-sans-serif", "system-ui"], ) CSS = """ @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap'); * { font-family: 'Space Grotesk', system-ui, sans-serif !important; } code, .mono, .readout, .badge, .rack-index, .meta-tile textarea, #report textarea, input[type=number], .slot-weight .tab-like-container input { font-family: 'IBM Plex Mono', monospace !important; } /* ---- shell ---------------------------------------------------------- */ body, .dark { background: #07090c !important; } .gradio-container { background: radial-gradient(900px 520px at 12% -10%, rgba(255,180,84,.10) 0%, rgba(7,9,12,0) 60%), radial-gradient(900px 520px at 88% -6%, rgba(79,214,200,.10) 0%, rgba(7,9,12,0) 62%), #07090c !important; max-width: none !important; overflow: visible !important; color: #dfe5ea !important; } .app { max-width: 1420px !important; width: 100% !important; margin: 0 auto !important; padding: 0 22px 48px !important; } main.contain { padding: 0 !important; gap: 0 !important; } footer.svelte-zxu34v, gradio-app > .gradio-container > .main + footer { display: none !important; } ::selection { background: rgba(255,180,84,.30); } ::-webkit-scrollbar { width: 9px; height: 9px; } ::-webkit-scrollbar-track { background: #07090c; } ::-webkit-scrollbar-thumb { background: #1f262d; border-radius: 6px; border: 2px solid #07090c; } * { scrollbar-width: thin; scrollbar-color: #1f262d #07090c; } /* ---- top bar -------------------------------------------------------- */ .topbar { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 22px 0 18px; border-bottom: 1px solid #161b21; margin-bottom: 22px; flex-wrap: wrap; } .brand { display: flex; align-items: center; gap: 14px; } .brand-mark { width: 40px; height: 40px; border-radius: 10px; display: grid; place-items: center; background: linear-gradient(140deg, #ffb454, #ff7a45 55%, #4fd6c8); color: #0a0d11; font-weight: 700; font-size: 17px; letter-spacing: -.5px; } .brand h1 { margin: 0; font-size: 21px; font-weight: 700; letter-spacing: -.4px; color: #f2f5f7; } .brand p { margin: 2px 0 0; font-size: 12.5px; color: #7a848e; } .badge { font-size: 10.5px; letter-spacing: .10em; text-transform: uppercase; color: #ffb454; border: 1px solid rgba(255,180,84,.35); border-radius: 999px; padding: 3px 9px; margin-left: 10px; } .state { display: flex; align-items: center; gap: 9px; font-size: 12px; color: #99a3ad; background: #0d1116; border: 1px solid #1a2028; border-radius: 999px; padding: 7px 14px; max-width: 640px; } .state .dot { width: 7px; height: 7px; border-radius: 50%; background: #4fd6c8; flex: 0 0 auto; box-shadow: 0 0 0 3px rgba(79,214,200,.16); } .state.busy .dot { background: #ffb454; box-shadow: 0 0 0 3px rgba(255,180,84,.16); } .state.bad .dot { background: #ff6b6b; box-shadow: 0 0 0 3px rgba(255,107,107,.16); } .state code { color: #cfd6dd; background: none; padding: 0; font-size: 11.5px; } /* ---- columns -------------------------------------------------------- */ #main { gap: 20px !important; align-items: flex-start !important; } #console { gap: 16px !important; min-width: 0 !important; } #preview { position: sticky !important; top: 18px !important; align-self: flex-start !important; min-width: 0 !important; } #dock { position: sticky !important; bottom: 14px !important; z-index: 6; min-width: 0 !important; } @media (max-width: 960px) { #main { flex-direction: column !important; } #main > * { width: 100% !important; } #preview { position: static !important; } } /* ---- panels --------------------------------------------------------- */ .panel { background: #0b0e13 !important; border: 1px solid #1b2129 !important; border-radius: 14px !important; padding: 16px !important; gap: 12px !important; } .panel .block, .panel .form { background: transparent !important; border: none !important; box-shadow: none !important; padding: 0 !important; } .panel-head { display: flex; align-items: baseline; gap: 10px; margin-bottom: 2px; } .panel-head h2 { margin: 0; font-size: 11px; font-weight: 600; letter-spacing: .13em; text-transform: uppercase; color: #6d7883; } .panel-head span { font-size: 11.5px; color: #4e5761; } .panel.tabs { padding-bottom: 66px !important; } .panel.hero { border-color: #24303a !important; background: linear-gradient(180deg, #10151b 0%, #0c0f14 100%) !important; } .note { font-size: 11.5px; color: #5f6a75; margin: 0; } .hint { font-size: 11.5px; color: #6d7883; padding: 8px 10px; border-left: 2px solid #232b34; background: #0a0d11; border-radius: 0 8px 8px 0; } .hint.accent { border-left-color: #ffb454; color: #b9a389; } .readout { font-size: 11.5px; color: #7d8791; letter-spacing: .02em; } .readout span { color: #4fd6c8; font-weight: 600; } /* ---- inputs --------------------------------------------------------- */ textarea, input[type=text], input[type=number] { background: #090c10 !important; border: 1px solid #1c232b !important; color: #e6ebf0 !important; border-radius: 10px !important; } textarea:focus, input[type=text]:focus { border-color: #ffb454 !important; box-shadow: 0 0 0 3px rgba(255,180,84,.10) !important; } #prompt textarea { font-size: 14.5px !important; line-height: 1.55 !important; min-height: 96px !important; } span[data-testid="block-info"] { font-size: 11px !important; color: #6d7883 !important; letter-spacing: .06em !important; text-transform: uppercase !important; } .panel input[type=range] { accent-color: #ffb454 !important; } /* keyframe drop zones */ .frame-slot { background: #090c10 !important; border: 1px dashed #232b34 !important; border-radius: 12px !important; } .frame-slot:hover { border-color: #3a4650 !important; } /* canvas chips */ #canvas .wrap { gap: 6px !important; flex-wrap: wrap !important; } #canvas label { background: #0a0e12 !important; border: 1px solid #1b222a !important; border-radius: 8px !important; padding: 6px 10px !important; font-size: 11.5px !important; color: #939da7 !important; } #canvas label:has(input:checked) { border-color: #ffb454 !important; color: #ffce8a !important; background: rgba(255,180,84,.08) !important; } #canvas input { display: none !important; } /* tabs */ #tabs button { font-size: 12.5px !important; color: #7a848e !important; } #tabs button.selected { color: #ffce8a !important; } #tabs .tab-nav, #tabs .tab-container { border-bottom: 1px solid #171d24 !important; } /* ---- lora rack ------------------------------------------------------ */ .chips { gap: 6px !important; flex-wrap: wrap !important; } .chip { background: rgba(79,214,200,.08) !important; border: 1px solid rgba(79,214,200,.28) !important; color: #8fe3d8 !important; font-size: 11.5px !important; border-radius: 999px !important; padding: 5px 11px !important; flex: 0 1 auto !important; width: auto !important; min-width: 0 !important; } .chip:hover { background: rgba(79,214,200,.18) !important; color: #d5fbf5 !important; } .slot { background: #090c10 !important; border: 1px solid #171d24 !important; border-radius: 12px !important; padding: 12px !important; gap: 9px !important; } .slot:has(.slot-card) { border-color: #232b34 !important; } .rack-index { font-size: 10px; letter-spacing: .16em; color: #4e5761; text-transform: uppercase; } .slot .row { gap: 10px !important; align-items: center !important; } .slot-weight .min_value, .slot-weight .max_value { display: none !important; } .slot-card { font-size: 11.5px; color: #7d8791; line-height: 1.5; padding: 9px 11px; background: #0b0f14; border: 1px solid #161c23; border-radius: 9px; } .slot-card b { color: #b8c2cb; font-weight: 600; } .slot-card a { color: #4fd6c8; text-decoration: none; } .slot-url textarea { font-size: 11.5px !important; } /* ---- generate ------------------------------------------------------- */ #generate { background: linear-gradient(120deg, #ffb454, #ff8a4c) !important; color: #100b04 !important; border: none !important; font-weight: 700 !important; font-size: 15px !important; letter-spacing: .02em; padding: 15px !important; border-radius: 12px !important; box-shadow: 0 14px 34px -14px rgba(255,140,70,.75) !important; } #generate:hover { filter: brightness(1.06); transform: translateY(-1px); } /* ---- preview -------------------------------------------------------- */ #clip video { border-radius: 12px !important; background: #05070a !important; } #meta { gap: 8px !important; } .meta-tile textarea { background: #090c10 !important; border: 1px solid #171d24 !important; color: #cfd6dd !important; font-size: 11.5px !important; text-align: center !important; } .meta-tile span[data-testid="block-info"] { font-size: 9.5px !important; } #report textarea { background: #090c10 !important; border: 1px solid #171d24 !important; color: #8b959f !important; font-size: 11px !important; line-height: 1.6 !important; } .footer { border-top: 1px solid #161b21; margin-top: 26px; padding-top: 14px; font-size: 11.5px; color: #4e5761; display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap; } .footer span { color: #6d7883; } """ def topbar_html() -> str: """Title, badge and the engine's own status line - re-rendered whenever a page opens.""" if h3_engine.LOAD_ERROR: tone = "bad" elif not h3_engine.is_ready(): tone = "busy" else: tone = "" return ( '
' '
' '
H3
' "
" '

MiniMax H3 Turbo LoRAsvideo + stereo audio

' "

One packed sequence, 24 fps, sound generated with the picture - and a rack of stackable LoRAs.

" "
" "
" f'
{h3_engine.status()}
' "
" ) # --------------------------------------------------------------------------- # UI # --------------------------------------------------------------------------- with gr.Blocks(title="MiniMax H3 Turbo LoRAs") as demo: # The bar is a component, not static markup: the 77 GB load finishes after the page is built, and every visitor # gets the state as of the moment they opened it. topbar = gr.HTML(topbar_html()) with gr.Row(equal_height=False, elem_id="main"): # ---- console ----------------------------------------------------- with gr.Column(scale=105, min_width=340, elem_id="console"): with gr.Column(elem_classes="panel"): gr.HTML( '

Keyframes

optional
' '

First frame, last frame, or neither. Uploads are cover-cropped onto the ' "closest supported canvas.

" ) with gr.Row(): first_frame = gr.Image( type="filepath", label="First frame", height=176, elem_classes="frame-slot" ) last_frame = gr.Image(type="filepath", label="Last frame", height=176, elem_classes="frame-slot") with gr.Column(elem_classes="panel hero"): gr.HTML('

Prompt

picture and soundtrack
') prompt = gr.Textbox( show_label=False, lines=4, elem_id="prompt", value=DEFAULT_PROMPT, placeholder="Describe the shot - and what it sounds like. Dialogue in quotes is spoken.", ) with gr.Column(elem_classes="panel tabs"): with gr.Tabs(elem_id="tabs"): with gr.Tab("Shot"): gr.HTML('

Canvas

the label the model generates on
') canvas = gr.Radio( choices=list(h3_engine.CANVASES), value=h3_engine.DEFAULT_CANVAS, show_label=False, elem_id="canvas", ) duration = gr.Slider( h3_engine.MIN_UI_DURATION, h3_engine.MAX_UI_DURATION, value=5, step=0.5, label="Duration (seconds)", ) frames_readout = gr.HTML(frame_readout(5)) with gr.Tab("Sampling"): steps = gr.Slider(1, 30, value=6, step=1, label="Steps") steps_hint = gr.HTML(step_hint()) with gr.Row(): seed = gr.Number(value=42, precision=0, label="Seed", interactive=False) randomize = gr.Checkbox(value=True, label="Random seed") gr.HTML( '

The checkpoint is guidance-distilled: no negative prompt, no CFG ' "scale, one forward pass per step.

" ) with gr.Tab("LoRA rack"): gr.HTML('

Quick add

fills the first free slot
') with gr.Row(elem_classes="chips"): chips = [ (gr.Button(key, size="sm", elem_classes="chip", min_width=40), key) for key in lora_stack.CATALOG ] slot_keys, slot_urls, slot_weights, slot_cards = [], [], [], [] for index in range(SLOTS): with gr.Column(elem_classes="slot"): gr.HTML(f'
Slot {index + 1}
') with gr.Row(): key = gr.Dropdown( choices=lora_stack.CHOICES, value=lora_stack.NONE_CHOICE, show_label=False, scale=3, ) weight = gr.Slider( 0.0, 2.0, value=1.0, step=0.05, label="Weight", scale=2, min_width=112, elem_classes="slot-weight", ) url = gr.Textbox( visible=False, show_label=False, lines=2, elem_classes="slot-url", placeholder=( "HF: user/repo/file.safetensors · CivitAI: " "civitai.com/models/X?modelVersionId=Y · https://…/lora.safetensors" ), ) card = gr.HTML("") slot_keys.append(key) slot_urls.append(url) slot_weights.append(weight) slot_cards.append(card) gr.HTML( '

Slots stack: every filled one is loaded as its own adapter and ' "activated with its weight. Weights change for free - nothing is baked into the " "checkpoint.

" ) with gr.Column(elem_id="dock"): generate = gr.Button("Generate clip", variant="primary", elem_id="generate") # ---- preview ----------------------------------------------------- with gr.Column(scale=110, min_width=340, elem_id="preview"): with gr.Column(elem_classes="panel"): gr.HTML('

Clip

mp4 · 24 fps · stereo 32 kHz
') clip = gr.Video(show_label=False, height=380, elem_id="clip", interactive=False) with gr.Row(elem_id="meta"): used_seed = gr.Textbox(label="Seed", value="—", interactive=False, elem_classes="meta-tile") geometry = gr.Textbox(label="Geometry", value="—", interactive=False, elem_classes="meta-tile") with gr.Row(elem_id="meta"): timing = gr.Textbox(label="Timing", value="—", interactive=False, elem_classes="meta-tile") stack_used = gr.Textbox(label="Stack", value="—", interactive=False, elem_classes="meta-tile") report = gr.Textbox(label="Report", value="", lines=4, interactive=False, elem_id="report") gr.HTML( '" ) # ---- events ---------------------------------------------------------- for chip, preset in chips: chip.click(fn=quick_add(preset), inputs=slot_keys, outputs=slot_keys) for key, url, weight, card in zip(slot_keys, slot_urls, slot_weights, slot_cards): key.change(fn=show_url, inputs=[key], outputs=[url]) key.change(fn=slot_card, inputs=[key], outputs=[card]) key.change(fn=lora_stack.default_weight, inputs=[key], outputs=[weight]) key.change(fn=step_hint, inputs=slot_keys, outputs=[steps_hint]) duration.change(fn=frame_readout, inputs=[duration], outputs=[frames_readout]) randomize.change(fn=lambda on: gr.Number(interactive=not on), inputs=[randomize], outputs=[seed]) demo.load(fn=topbar_html, outputs=[topbar]) generate.click( fn=run, inputs=[ prompt, first_frame, last_frame, canvas, duration, steps, seed, randomize, *[widget for slot in zip(slot_keys, slot_urls, slot_weights) for widget in slot], ], outputs=[clip, used_seed, geometry, timing, stack_used, report], ) if __name__ == "__main__": h3_engine.load_engine() # allowed_paths: the file route only serves whitelisted directories, and the clips live outside the repo. # Gradio 6 takes the theme and the stylesheet at launch, not on the Blocks constructor. demo.launch(theme=THEME, css=CSS, show_error=True, allowed_paths=[h3_engine.OUTPUT_DIR])