Spaces:
Running on Zero
Running on Zero
Upload 2 files
Browse files
README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
---
|
| 2 |
-
title: MiniMax-H3 · reference ·
|
| 3 |
emoji: 🎭
|
| 4 |
colorFrom: pink
|
| 5 |
colorTo: purple
|
|
@@ -7,7 +7,7 @@ sdk: gradio
|
|
| 7 |
sdk_version: 6.20.0
|
| 8 |
app_file: app.py
|
| 9 |
pinned: true
|
| 10 |
-
short_description:
|
| 11 |
suggested_hardware: zero-a10g
|
| 12 |
tags:
|
| 13 |
- not-for-all-audiences
|
|
@@ -20,10 +20,36 @@ tags:
|
|
| 20 |
- zerogpu
|
| 21 |
---
|
| 22 |
|
| 23 |
-
# MiniMax-H3 —
|
| 24 |
|
| 25 |
-
|
| 26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
This Space is the denoising half of the `ref2va` task: the 61.73 GiB `transformer_ref` partition and the two
|
| 29 |
autoencoders. The 62.14 GiB Qwen3-VL conditioner runs in
|
|
@@ -31,7 +57,6 @@ autoencoders. The 62.14 GiB Qwen3-VL conditioner runs in
|
|
| 31 |
the gradio API for every request — the same conditioner Space, and the same resident weights, that the keyframe half
|
| 32 |
[`minimax-h3`](https://huggingface.co/spaces/multimodalart/minimax-h3) uses.
|
| 33 |
|
| 34 |
-
## What this fork adds
|
| 35 |
|
| 36 |
**A structured prompt builder.** H3 was trained on the output of H3-Context-IR, a preprocessor that rewrites a plain
|
| 37 |
request into labelled sections, and MiniMax's own model card calls that structure *critical to the quality of the
|
|
|
|
| 1 |
---
|
| 2 |
+
title: MiniMax-H3 · reference · One button, photo to video
|
| 3 |
emoji: 🎭
|
| 4 |
colorFrom: pink
|
| 5 |
colorTo: purple
|
|
|
|
| 7 |
sdk_version: 6.20.0
|
| 8 |
app_file: app.py
|
| 9 |
pinned: true
|
| 10 |
+
short_description: One button — it writes the prompt and picks the loras
|
| 11 |
suggested_hardware: zero-a10g
|
| 12 |
tags:
|
| 13 |
- not-for-all-audiences
|
|
|
|
| 20 |
- zerogpu
|
| 21 |
---
|
| 22 |
|
| 23 |
+
# MiniMax-H3 — one button, or every dial
|
| 24 |
|
| 25 |
+
**Picture in, a few words, one press.** The description is written from your first reference picture, wrapped in the
|
| 26 |
+
labelled sections H3 was actually trained on, and the shared library is searched for loras that match it. Everything
|
| 27 |
+
this Space can do is still here — one tick at the top swaps the whole page over to it.
|
| 28 |
+
|
| 29 |
+
Underneath: joint video **and** soundtrack out of a single denoising pass, conditioned on an ordered list of image,
|
| 30 |
+
video and audio references, at **bfloat16 with no quantization anywhere**.
|
| 31 |
+
|
| 32 |
+
## 🟢 Simple — the button does the lot
|
| 33 |
+
|
| 34 |
+
One press, and:
|
| 35 |
+
|
| 36 |
+
* **The description is written from your first reference picture.** Not from your words alone — the picture is shown
|
| 37 |
+
to a chat Space of your choosing, so what comes back describes the subject and the setting actually in front of it.
|
| 38 |
+
* **The structure goes on around it.** The prose is wrapped in the labelled sections below, the part MiniMax call
|
| 39 |
+
*critical to the quality of the final output* — so the one thing that most changes the result is no longer
|
| 40 |
+
something you have to remember to press.
|
| 41 |
+
* **The loras are chosen.** The shared library is searched, the handful that match your words are shortlisted, and
|
| 42 |
+
the two that fit best go into the slots at their own strengths.
|
| 43 |
+
* **The trigger words go into the prompt.** An adapter whose token is missing does nothing at all, and that is the
|
| 44 |
+
commonest reason one seems to be ignored.
|
| 45 |
+
|
| 46 |
+
Whatever it picked is listed underneath with the rest of the shortlist. **Tick a different one and it swaps** — the
|
| 47 |
+
slots refill, the old trigger words come out and the new ones go in. Two at a time is the limit, because stacking
|
| 48 |
+
more only means each one shows less.
|
| 49 |
+
|
| 50 |
+
Leave the Space box empty and the button still builds the structured prompt out of your own words.
|
| 51 |
+
|
| 52 |
+
## 🔧 Everything — what this fork adds
|
| 53 |
|
| 54 |
This Space is the denoising half of the `ref2va` task: the 61.73 GiB `transformer_ref` partition and the two
|
| 55 |
autoencoders. The 62.14 GiB Qwen3-VL conditioner runs in
|
|
|
|
| 57 |
the gradio API for every request — the same conditioner Space, and the same resident weights, that the keyframe half
|
| 58 |
[`minimax-h3`](https://huggingface.co/spaces/multimodalart/minimax-h3) uses.
|
| 59 |
|
|
|
|
| 60 |
|
| 61 |
**A structured prompt builder.** H3 was trained on the output of H3-Context-IR, a preprocessor that rewrites a plain
|
| 62 |
request into labelled sections, and MiniMax's own model card calls that structure *critical to the quality of the
|
app.py
CHANGED
|
@@ -2525,9 +2525,340 @@ visit, so a saved path would come back as a dead file.
|
|
| 2525 |
|
| 2526 |
# gradio 6.0 takes `theme` and `css` on launch(), not here - passing them to the constructor
|
| 2527 |
# only earns a warning and the styling is dropped.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2528 |
with gr.Blocks(title="MiniMax-H3 - Custom lora + CivitAI, structured prompts, GPU cost, profiles, stitching") as demo:
|
| 2529 |
gr.HTML(HERO)
|
| 2530 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2531 |
with gr.Row(equal_height=False):
|
| 2532 |
with gr.Column(scale=5):
|
| 2533 |
|
|
@@ -2538,8 +2869,35 @@ with gr.Blocks(title="MiniMax-H3 - Custom lora + CivitAI, structured prompts, GP
|
|
| 2538 |
lines=3,
|
| 2539 |
value="The character walks through a neon-lit street in the rain, humming to themselves",
|
| 2540 |
)
|
| 2541 |
-
upsample = gr.Checkbox(label="✨ Upsample prompt", value=False)
|
| 2542 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2543 |
gr.Markdown(
|
| 2544 |
"H3 was trained on the output of a preprocessor that rewrites a request into labelled "
|
| 2545 |
"sections, and MiniMax call that structure *critical to the quality of the final output*. "
|
|
@@ -2566,7 +2924,8 @@ with gr.Blocks(title="MiniMax-H3 - Custom lora + CivitAI, structured prompts, GP
|
|
| 2566 |
ir_button = gr.Button("🎬 Build the structured prompt", variant="secondary",
|
| 2567 |
elem_id="ir-btn")
|
| 2568 |
|
| 2569 |
-
with gr.Accordion("💡 Quick tags — click to add", open=False
|
|
|
|
| 2570 |
with gr.Row(elem_classes="chip-row"):
|
| 2571 |
chip_buttons_a = [gr.Button(text, size="sm", variant="secondary") for text in CHIPS[:4]]
|
| 2572 |
with gr.Row(elem_classes="chip-row"):
|
|
@@ -2634,7 +2993,7 @@ with gr.Blocks(title="MiniMax-H3 - Custom lora + CivitAI, structured prompts, GP
|
|
| 2634 |
|
| 2635 |
# ---------------- the rest, in tabs ----------------
|
| 2636 |
with gr.Tabs():
|
| 2637 |
-
with gr.Tab(f"⭐ Custom lora ({LORA_SLOTS} slots)"):
|
| 2638 |
gr.Markdown(LORA_HELP)
|
| 2639 |
with gr.Accordion("🔍 Search CivitAI", open=False):
|
| 2640 |
gr.Markdown(
|
|
@@ -2700,17 +3059,19 @@ with gr.Blocks(title="MiniMax-H3 - Custom lora + CivitAI, structured prompts, GP
|
|
| 2700 |
duration = gr.Slider(
|
| 2701 |
label="Duration (s)", minimum=MIN_DURATION, maximum=MAX_UI_DURATION, step=1, value=5
|
| 2702 |
)
|
| 2703 |
-
steps = gr.Slider(label="Steps", minimum=MIN_STEPS, maximum=40, step=1,
|
| 2704 |
-
|
|
|
|
| 2705 |
seed = gr.Number(label="Seed", value=42, precision=0, scale=3)
|
| 2706 |
seed_dice = gr.Button("🎲 roll", variant="secondary", scale=1, elem_id="seed-dice")
|
| 2707 |
randomize_seed = gr.Checkbox(
|
| 2708 |
label="🎲 Randomize seed on every run",
|
| 2709 |
value=True,
|
|
|
|
| 2710 |
info="A new seed is drawn each time Generate is pressed, and lands in the box above.",
|
| 2711 |
)
|
| 2712 |
|
| 2713 |
-
with gr.Tab("💾 Profiles"):
|
| 2714 |
gr.Markdown(PROFILE_HELP)
|
| 2715 |
with gr.Row():
|
| 2716 |
profile_picker = gr.Dropdown(
|
|
@@ -2753,7 +3114,8 @@ with gr.Blocks(title="MiniMax-H3 - Custom lora + CivitAI, structured prompts, GP
|
|
| 2753 |
2, 8, value=3, step=1, label="How many clips in a row",
|
| 2754 |
info="3 clips of 5 s ≈ a 15 second video.",
|
| 2755 |
)
|
| 2756 |
-
with gr.Accordion("✍️ A prompt per clip (optional)", open=False
|
|
|
|
| 2757 |
scene_prompts = gr.Textbox(
|
| 2758 |
label="One line per clip", lines=8, max_lines=8,
|
| 2759 |
placeholder=("line 1 = clip 1, line 2 = clip 2, and so on\n"
|
|
@@ -2988,6 +3350,103 @@ with gr.Blocks(title="MiniMax-H3 - Custom lora + CivitAI, structured prompts, GP
|
|
| 2988 |
api_name=False,
|
| 2989 |
)
|
| 2990 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2991 |
lora_identify_btn.click(identify_loras, lora_references, lora_names, api_name=False)
|
| 2992 |
for _field in lora_references:
|
| 2993 |
_field.submit(identify_loras, lora_references, lora_names, api_name=False)
|
|
|
|
| 2525 |
|
| 2526 |
# gradio 6.0 takes `theme` and `css` on launch(), not here - passing them to the constructor
|
| 2527 |
# only earns a warning and the styling is dropped.
|
| 2528 |
+
# ---------------------------------------------------------------------------
|
| 2529 |
+
# Simple mode: a borrowed Space writes the prompt, the library is searched for loras
|
| 2530 |
+
# ---------------------------------------------------------------------------
|
| 2531 |
+
|
| 2532 |
+
# Set the Space variable EXPANDER_SPACE to point this somewhere else.
|
| 2533 |
+
REMOTE_SPACE = os.environ.get("EXPANDER_SPACE",
|
| 2534 |
+
"amisima/Qwen3.8-27B-Uncensored-Demo").strip()
|
| 2535 |
+
_REMOTE_CLIENTS = {}
|
| 2536 |
+
|
| 2537 |
+
|
| 2538 |
+
def _remote_reply_text(result):
|
| 2539 |
+
"""Pull the assistant's words out of whatever shape the Space hands back - a string,
|
| 2540 |
+
a message dict, a list of them, or history pairs. Thinking bubbles carry a metadata
|
| 2541 |
+
title and are dropped, the same way the Space itself drops them."""
|
| 2542 |
+
if result is None:
|
| 2543 |
+
return ""
|
| 2544 |
+
if isinstance(result, str):
|
| 2545 |
+
return result.strip()
|
| 2546 |
+
if isinstance(result, dict):
|
| 2547 |
+
if (result.get("metadata") or {}).get("title"):
|
| 2548 |
+
return ""
|
| 2549 |
+
return _remote_reply_text(result.get("content", result.get("text", "")))
|
| 2550 |
+
if isinstance(result, (list, tuple)):
|
| 2551 |
+
parts = [part for part in (_remote_reply_text(item) for item in result) if part]
|
| 2552 |
+
return "\n".join(parts[-2:]) if len(parts) > 2 else "\n".join(parts)
|
| 2553 |
+
return str(result).strip()
|
| 2554 |
+
|
| 2555 |
+
|
| 2556 |
+
def _make_remote_client(space_id):
|
| 2557 |
+
"""`Client(src, hf_token=...)` is what the documentation says and what the installed
|
| 2558 |
+
client may no longer accept - the argument has been spelled three ways across
|
| 2559 |
+
versions. Ask the signature which name it wants, fall back to sending the token as a
|
| 2560 |
+
header, and connect anonymously rather than not at all."""
|
| 2561 |
+
import inspect
|
| 2562 |
+
|
| 2563 |
+
from gradio_client import Client
|
| 2564 |
+
|
| 2565 |
+
token = os.environ.get("HF_TOKEN") or None
|
| 2566 |
+
if not token:
|
| 2567 |
+
return Client(space_id)
|
| 2568 |
+
try:
|
| 2569 |
+
accepted = list(inspect.signature(Client.__init__).parameters)
|
| 2570 |
+
except (TypeError, ValueError): # a C-level or wrapped __init__
|
| 2571 |
+
accepted = []
|
| 2572 |
+
for name in ("hf_token", "token", "auth_token", "api_key"):
|
| 2573 |
+
if name in accepted:
|
| 2574 |
+
try:
|
| 2575 |
+
return Client(space_id, **{name: token})
|
| 2576 |
+
except TypeError:
|
| 2577 |
+
break
|
| 2578 |
+
for extra in ({"headers": {"Authorization": f"Bearer {token}"}}, {}):
|
| 2579 |
+
try:
|
| 2580 |
+
return Client(space_id, **extra)
|
| 2581 |
+
except TypeError:
|
| 2582 |
+
continue
|
| 2583 |
+
return Client(space_id)
|
| 2584 |
+
|
| 2585 |
+
|
| 2586 |
+
def _remote_plan(client, payload, message):
|
| 2587 |
+
"""Ask the Space what it actually exposes, instead of guessing at `/chat`. Endpoint
|
| 2588 |
+
names change with the Gradio version and with how the interface was built, and the
|
| 2589 |
+
parameter list that comes back is the only honest description of the call."""
|
| 2590 |
+
info = None
|
| 2591 |
+
for kwargs in ({"return_format": "dict", "print_info": False}, {"return_format": "dict"}):
|
| 2592 |
+
try:
|
| 2593 |
+
info = client.view_api(**kwargs)
|
| 2594 |
+
break
|
| 2595 |
+
except Exception: # noqa: BLE001
|
| 2596 |
+
continue
|
| 2597 |
+
named = (info or {}).get("named_endpoints") or {} if isinstance(info, dict) else {}
|
| 2598 |
+
seen = list(named)
|
| 2599 |
+
print(f"[big-space] endpoints: {seen or 'none reported'}", flush=True)
|
| 2600 |
+
|
| 2601 |
+
def rank(name):
|
| 2602 |
+
low = name.lower()
|
| 2603 |
+
if "chat" in low:
|
| 2604 |
+
return 0
|
| 2605 |
+
if any(word in low for word in ("respond", "submit", "predict", "run", "generate")):
|
| 2606 |
+
return 1
|
| 2607 |
+
return 2
|
| 2608 |
+
|
| 2609 |
+
plan = []
|
| 2610 |
+
for name in sorted(named, key=lambda n: (rank(n), n)):
|
| 2611 |
+
params = (named[name] or {}).get("parameters") or []
|
| 2612 |
+
if not params:
|
| 2613 |
+
continue
|
| 2614 |
+
args = []
|
| 2615 |
+
for index, param in enumerate(params):
|
| 2616 |
+
if index == 0:
|
| 2617 |
+
component = str(param.get("component", "")).lower()
|
| 2618 |
+
python_type = str((param.get("python_type") or {}).get("type", "")).lower()
|
| 2619 |
+
# A multimodal box wants {"text": ..., "files": [...]}; a plain one a string.
|
| 2620 |
+
args.append(payload if ("multimodal" in component or "dict" in python_type)
|
| 2621 |
+
else message)
|
| 2622 |
+
elif param.get("parameter_has_default"):
|
| 2623 |
+
args.append(param.get("parameter_default"))
|
| 2624 |
+
else:
|
| 2625 |
+
args.append(param.get("example_input"))
|
| 2626 |
+
plan.append((tuple(args), {"api_name": name}))
|
| 2627 |
+
return plan, seen
|
| 2628 |
+
|
| 2629 |
+
|
| 2630 |
+
def _remote_ask(space_id, message, image_path=None, temperature=0.7):
|
| 2631 |
+
"""One turn on a Gradio chat Space. `gradio_client` ships with gradio itself, so
|
| 2632 |
+
there is nothing to add to requirements."""
|
| 2633 |
+
from gradio_client import handle_file
|
| 2634 |
+
from PIL import Image
|
| 2635 |
+
|
| 2636 |
+
client = _REMOTE_CLIENTS.get(space_id)
|
| 2637 |
+
if client is None:
|
| 2638 |
+
client = _make_remote_client(space_id)
|
| 2639 |
+
_REMOTE_CLIENTS[space_id] = client
|
| 2640 |
+
|
| 2641 |
+
files = []
|
| 2642 |
+
if image_path:
|
| 2643 |
+
try:
|
| 2644 |
+
picture = Image.open(image_path).convert("RGB")
|
| 2645 |
+
picture.thumbnail((1024, 1024))
|
| 2646 |
+
handle = tempfile.NamedTemporaryFile(suffix=".jpg", delete=False)
|
| 2647 |
+
picture.save(handle.name, format="JPEG", quality=88)
|
| 2648 |
+
files.append(handle_file(handle.name))
|
| 2649 |
+
except Exception: # noqa: BLE001
|
| 2650 |
+
files = []
|
| 2651 |
+
|
| 2652 |
+
payload = {"text": message, "files": files}
|
| 2653 |
+
plan, seen = _remote_plan(client, payload, message)
|
| 2654 |
+
attempts = tuple(plan) + (
|
| 2655 |
+
# Only reached when the Space reports nothing useful about itself: a multimodal
|
| 2656 |
+
# ChatInterface with the sampling knobs exposed, the same without them, then a
|
| 2657 |
+
# plain text-in text-out Space.
|
| 2658 |
+
((payload, "off", temperature, 0.95, 20), {"api_name": "/chat"}),
|
| 2659 |
+
((payload,), {"api_name": "/chat"}),
|
| 2660 |
+
((message,), {"api_name": "/chat"}),
|
| 2661 |
+
((payload, "off", temperature, 0.95, 20), {}),
|
| 2662 |
+
((message,), {}),
|
| 2663 |
+
)
|
| 2664 |
+
problems = []
|
| 2665 |
+
for args, kwargs in attempts:
|
| 2666 |
+
try:
|
| 2667 |
+
return _remote_reply_text(client.predict(*args, **kwargs))
|
| 2668 |
+
except Exception as error: # noqa: BLE001
|
| 2669 |
+
problems.append(f"{type(error).__name__}: {error}")
|
| 2670 |
+
print(f"[big-space] attempt failed - {problems[-1]}", flush=True)
|
| 2671 |
+
raise RuntimeError((" || ".join(problems[:2]) + f" || endpoints seen: {seen}")[:600])
|
| 2672 |
+
|
| 2673 |
+
|
| 2674 |
+
# The borrowed Space is a small model. Handing it the whole library and asking it to
|
| 2675 |
+
# choose is the one job that size of model does badly - it answers by position, repeats
|
| 2676 |
+
# itself and invents names that are not on the list. So the long list is cut down here,
|
| 2677 |
+
# by plain word matching, and the model is only ever asked to choose between a handful.
|
| 2678 |
+
|
| 2679 |
+
_PICK_NOISE = {
|
| 2680 |
+
"ltx", "ltxv", "wan", "wan22", "wan2", "i2v", "t2v", "lora", "loras", "video",
|
| 2681 |
+
"model", "safetensors", "merge", "rank", "version", "experimental", "alpha",
|
| 2682 |
+
"beta", "final", "test", "general", "suite", "helper", "enhancer", "motion",
|
| 2683 |
+
"nsfw", "sfw", "the", "and", "for", "with", "all", "one", "two", "pack",
|
| 2684 |
+
"high", "low", "only", "generic", "slider", "extreme", "ultimate", "booster",
|
| 2685 |
+
"minimax", "mmh3", "h3", "turbo", "step", "steps", "distill", "distilled",
|
| 2686 |
+
}
|
| 2687 |
+
_PROMPT_NOISE = {
|
| 2688 |
+
"the", "and", "with", "that", "this", "from", "into", "over", "under", "very",
|
| 2689 |
+
"while", "their", "there", "then", "them", "she", "her", "his", "him", "they",
|
| 2690 |
+
"are", "was", "were", "for", "not", "but", "you", "your", "its", "has", "have",
|
| 2691 |
+
"had", "one", "two", "all", "any", "out", "off", "been", "being", "more", "most",
|
| 2692 |
+
"some", "such", "than", "too", "just", "like", "also", "only", "own", "same",
|
| 2693 |
+
"each", "other", "how", "what", "when", "where", "which", "who", "will", "would",
|
| 2694 |
+
"can", "could", "should", "shot", "video", "clip", "camera", "scene", "frame",
|
| 2695 |
+
"light", "lighting", "photorealistic", "realistic", "detailed", "quality",
|
| 2696 |
+
"natural", "smooth", "consistent", "anatomy", "texture", "slowly", "gently",
|
| 2697 |
+
"towards", "toward", "keeps", "keeping", "looking", "looks", "moves", "moving",
|
| 2698 |
+
"sound", "audio", "music", "voice", "dialogue", "speaks", "saying", "picture",
|
| 2699 |
+
# Scenery and plain motion words. These turn up in lora titles as often as they
|
| 2700 |
+
# turn up in prompts, and matching on them is how "a man walking down a rainy
|
| 2701 |
+
# street" ends up wearing a lora about walking with no clothes on.
|
| 2702 |
+
"walk", "walks", "walking", "run", "runs", "running", "stand", "stands",
|
| 2703 |
+
"standing", "sit", "sits", "sitting", "slow", "fast", "quick", "turn", "turns",
|
| 2704 |
+
"turning", "move", "head", "hand", "hands", "body", "woman", "women", "girl",
|
| 2705 |
+
"girls", "man", "men", "guy", "lady", "hair", "face", "eyes", "mouth", "skin",
|
| 2706 |
+
"night", "morning", "street", "city", "room", "bed", "rain", "rainy", "water",
|
| 2707 |
+
"wind", "dress", "shirt", "clothes", "black", "white", "close", "wide", "front",
|
| 2708 |
+
"back", "side", "down", "smile", "smiling", "breathing", "leans", "leaning",
|
| 2709 |
+
"holds", "holding", "takes", "gives", "position", "movement", "style", "character",
|
| 2710 |
+
}
|
| 2711 |
+
|
| 2712 |
+
|
| 2713 |
+
def _pick_words(text, drop):
|
| 2714 |
+
out = set()
|
| 2715 |
+
for word in re.findall(r"[a-z0-9]+", str(text or "").lower()):
|
| 2716 |
+
if len(word) >= 4 and word not in drop and not word.isdigit():
|
| 2717 |
+
out.add(word)
|
| 2718 |
+
return out
|
| 2719 |
+
|
| 2720 |
+
|
| 2721 |
+
def _library_pool():
|
| 2722 |
+
"""Every usable library entry, nsfw and ordinary together."""
|
| 2723 |
+
try:
|
| 2724 |
+
data = lora_library.load_library()
|
| 2725 |
+
except Exception as error: # noqa: BLE001
|
| 2726 |
+
print(f"[simple] the library could not be read: {error}", flush=True)
|
| 2727 |
+
return []
|
| 2728 |
+
pool = []
|
| 2729 |
+
for section in ("nsfw", "normal"):
|
| 2730 |
+
for item in (data.get(section) or []):
|
| 2731 |
+
if isinstance(item, dict) and item.get("url") and item.get("name"):
|
| 2732 |
+
pool.append(item)
|
| 2733 |
+
return pool
|
| 2734 |
+
|
| 2735 |
+
|
| 2736 |
+
def _shortlist_loras(prompt_text, pool, limit=6):
|
| 2737 |
+
"""The few library entries whose name or trigger words are actually in the prompt."""
|
| 2738 |
+
asked = _pick_words(prompt_text, _PROMPT_NOISE)
|
| 2739 |
+
if not asked:
|
| 2740 |
+
return []
|
| 2741 |
+
scored = []
|
| 2742 |
+
for item in pool:
|
| 2743 |
+
hits = len(asked & _pick_words(item.get("name"), _PICK_NOISE))
|
| 2744 |
+
# A trigger word written out in the prompt is a much stronger signal than a
|
| 2745 |
+
# word that happens to appear in a title, so it counts double.
|
| 2746 |
+
for trigger in str(item.get("trigger") or "").split(","):
|
| 2747 |
+
trigger = trigger.strip().lower()
|
| 2748 |
+
if trigger and trigger in str(prompt_text or "").lower():
|
| 2749 |
+
hits += 2
|
| 2750 |
+
if hits:
|
| 2751 |
+
scored.append((hits, item))
|
| 2752 |
+
scored.sort(key=lambda pair: -pair[0])
|
| 2753 |
+
return [item for _score, item in scored[:limit]]
|
| 2754 |
+
|
| 2755 |
+
|
| 2756 |
+
def _pick_label(index, item):
|
| 2757 |
+
text = f"{index + 1}. {item.get('name', '')}"
|
| 2758 |
+
if not str(item.get("trigger") or "").strip():
|
| 2759 |
+
text += " \u00b7 no trigger words saved"
|
| 2760 |
+
return text[:200]
|
| 2761 |
+
|
| 2762 |
+
|
| 2763 |
+
def _ask_which_loras(space_id, prompt_text, shortlist):
|
| 2764 |
+
"""One small question: which of these few, by number. Nothing else is asked."""
|
| 2765 |
+
space = str(space_id or "").strip()
|
| 2766 |
+
if not space or not shortlist:
|
| 2767 |
+
return []
|
| 2768 |
+
lines = []
|
| 2769 |
+
for index, item in enumerate(shortlist, start=1):
|
| 2770 |
+
line = f"{index}. {item.get('name', '')}"
|
| 2771 |
+
trigger = str(item.get("trigger") or "").strip()
|
| 2772 |
+
if trigger:
|
| 2773 |
+
line += f" (words: {trigger})"
|
| 2774 |
+
lines.append(line)
|
| 2775 |
+
message = (
|
| 2776 |
+
"Below is a video prompt and a numbered list of lora files.\n\n"
|
| 2777 |
+
f"PROMPT: {prompt_text}\n\nLIST:\n" + "\n".join(lines) + "\n\n"
|
| 2778 |
+
"Reply with the numbers of at most two entries from the list that match what "
|
| 2779 |
+
"the prompt describes, separated by a comma. Reply with the word NONE if none "
|
| 2780 |
+
"of them match. Write nothing else - no explanation, no names, only numbers."
|
| 2781 |
+
)
|
| 2782 |
+
try:
|
| 2783 |
+
reply = _remote_ask(space, message)
|
| 2784 |
+
except Exception as error: # noqa: BLE001
|
| 2785 |
+
print(f"[simple] the Space did not answer the pick question: {error}", flush=True)
|
| 2786 |
+
return []
|
| 2787 |
+
if "none" in str(reply or "").lower() and not re.search(r"\d", str(reply or "")):
|
| 2788 |
+
return []
|
| 2789 |
+
picked = []
|
| 2790 |
+
for found in re.findall(r"\d+", str(reply or "")):
|
| 2791 |
+
index = int(found) - 1
|
| 2792 |
+
if 0 <= index < len(shortlist) and index not in picked:
|
| 2793 |
+
picked.append(index)
|
| 2794 |
+
return picked[:2]
|
| 2795 |
+
|
| 2796 |
+
|
| 2797 |
+
def _write_prompt(space_id, wanted, image_path):
|
| 2798 |
+
"""The description H3 wants, written from the reference picture. Plain prose only -
|
| 2799 |
+
the structured sections are put around it afterwards by the builder this Space
|
| 2800 |
+
already has, which is the part MiniMax call critical to the result."""
|
| 2801 |
+
space = str(space_id or "").strip()
|
| 2802 |
+
if not space:
|
| 2803 |
+
return "", "no Space in the box, so your own words were kept."
|
| 2804 |
+
message = (
|
| 2805 |
+
"Write a single paragraph of about 120 words describing a short video clip, for "
|
| 2806 |
+
"a video model. Describe what is in the picture and what moves: the subject, "
|
| 2807 |
+
"the setting, the action, the light. Present tense, plain prose, no headings, "
|
| 2808 |
+
"no lists, no camera jargon, no preamble - only the paragraph itself.\n\n"
|
| 2809 |
+
f"What is wanted: {wanted}"
|
| 2810 |
+
)
|
| 2811 |
+
try:
|
| 2812 |
+
reply = _remote_ask(space, message, image_path)
|
| 2813 |
+
except Exception as error: # noqa: BLE001
|
| 2814 |
+
return "", f"{space} did not answer, so your own words were kept: {str(error)[:200]}"
|
| 2815 |
+
written = " ".join(str(reply or "").split())
|
| 2816 |
+
if len(written) < 40:
|
| 2817 |
+
return "", f"{space} answered with almost nothing, so your own words were kept."
|
| 2818 |
+
return written, f"written by {space} ({len(written.split())} words)"
|
| 2819 |
+
|
| 2820 |
+
|
| 2821 |
+
def _add_triggers(prompt_text, items):
|
| 2822 |
+
"""A lora whose trigger word is missing from the prompt does nothing at all."""
|
| 2823 |
+
text = str(prompt_text or "")
|
| 2824 |
+
missing = []
|
| 2825 |
+
for item in items:
|
| 2826 |
+
# Only the first few. Some entries carry eight phrases and pasting all of them
|
| 2827 |
+
# in buries the sentence the model is meant to be following.
|
| 2828 |
+
for trigger in str(item.get("trigger") or "").split(",")[:3]:
|
| 2829 |
+
trigger = trigger.strip()
|
| 2830 |
+
if trigger and trigger.lower() not in text.lower() and trigger not in missing:
|
| 2831 |
+
missing.append(trigger)
|
| 2832 |
+
if not missing:
|
| 2833 |
+
return text
|
| 2834 |
+
return f"{text.rstrip().rstrip(',')}, {', '.join(missing)}".strip(" ,")
|
| 2835 |
+
|
| 2836 |
+
|
| 2837 |
+
def _retune_triggers(prompt_text, shortlist, picks):
|
| 2838 |
+
"""Swapping a lora has to take its words back out of the prompt as well."""
|
| 2839 |
+
text = str(prompt_text or "")
|
| 2840 |
+
kept = {str(item.get("url")) for item in picks}
|
| 2841 |
+
for item in shortlist or []:
|
| 2842 |
+
if str(item.get("url")) in kept:
|
| 2843 |
+
continue
|
| 2844 |
+
for trigger in str(item.get("trigger") or "").split(","):
|
| 2845 |
+
trigger = trigger.strip()
|
| 2846 |
+
if trigger:
|
| 2847 |
+
text = re.sub(r"(,\s*)?" + re.escape(trigger) + r"(?=\s*(,|$))",
|
| 2848 |
+
"", text, flags=re.I)
|
| 2849 |
+
text = re.sub(r"\s*,\s*,", ",", text).strip(" ,")
|
| 2850 |
+
return _add_triggers(text, picks)
|
| 2851 |
+
|
| 2852 |
+
|
| 2853 |
with gr.Blocks(title="MiniMax-H3 - Custom lora + CivitAI, structured prompts, GPU cost, profiles, stitching") as demo:
|
| 2854 |
gr.HTML(HERO)
|
| 2855 |
|
| 2856 |
+
ui_mode = gr.Radio(
|
| 2857 |
+
[("🟢 Simple — one button writes the prompt and picks the loras", "simple"),
|
| 2858 |
+
("🔧 Everything — every control this Space has", "pro")],
|
| 2859 |
+
value="simple", show_label=False,
|
| 2860 |
+
)
|
| 2861 |
+
|
| 2862 |
with gr.Row(equal_height=False):
|
| 2863 |
with gr.Column(scale=5):
|
| 2864 |
|
|
|
|
| 2869 |
lines=3,
|
| 2870 |
value="The character walks through a neon-lit street in the rain, humming to themselves",
|
| 2871 |
)
|
| 2872 |
+
upsample = gr.Checkbox(label="✨ Upsample prompt", value=False, visible=False)
|
| 2873 |
+
|
| 2874 |
+
with gr.Group(elem_classes="panel") as basic_panel:
|
| 2875 |
+
gr.Markdown(
|
| 2876 |
+
"**Picture in, a few words above, one press.** The description is "
|
| 2877 |
+
"written from your first reference picture, wrapped in the labelled "
|
| 2878 |
+
"sections H3 was trained on, and the library is searched for loras "
|
| 2879 |
+
"that match it. What it picks is listed underneath and can be "
|
| 2880 |
+
"changed with a tick."
|
| 2881 |
+
)
|
| 2882 |
+
basic_space = gr.Textbox(
|
| 2883 |
+
value=REMOTE_SPACE, label="🛰️ Big model Space", lines=1,
|
| 2884 |
+
placeholder="owner/space-name",
|
| 2885 |
+
info="A chat Space of your own, shown your first picture. Empty it "
|
| 2886 |
+
"and only the structured builder runs, on your own words.",
|
| 2887 |
+
)
|
| 2888 |
+
basic_btn = gr.Button("✨ Do all of it", variant="primary",
|
| 2889 |
+
elem_id="ir-btn")
|
| 2890 |
+
basic_status = gr.Markdown("Nothing done yet.")
|
| 2891 |
+
basic_pick = gr.CheckboxGroup(
|
| 2892 |
+
choices=[], value=[], visible=False,
|
| 2893 |
+
label="Loras it chose — tick another one to swap",
|
| 2894 |
+
info="Two at a time is the limit. Every tick refills the slots and "
|
| 2895 |
+
"puts the trigger words into the prompt.",
|
| 2896 |
+
)
|
| 2897 |
+
basic_shortlist = gr.State([])
|
| 2898 |
+
|
| 2899 |
+
with gr.Accordion("🎬 Structured prompt builder (what H3 was trained on)",
|
| 2900 |
+
open=False, visible=False) as pro_builder:
|
| 2901 |
gr.Markdown(
|
| 2902 |
"H3 was trained on the output of a preprocessor that rewrites a request into labelled "
|
| 2903 |
"sections, and MiniMax call that structure *critical to the quality of the final output*. "
|
|
|
|
| 2924 |
ir_button = gr.Button("🎬 Build the structured prompt", variant="secondary",
|
| 2925 |
elem_id="ir-btn")
|
| 2926 |
|
| 2927 |
+
with gr.Accordion("💡 Quick tags — click to add", open=False,
|
| 2928 |
+
visible=False) as pro_chips:
|
| 2929 |
with gr.Row(elem_classes="chip-row"):
|
| 2930 |
chip_buttons_a = [gr.Button(text, size="sm", variant="secondary") for text in CHIPS[:4]]
|
| 2931 |
with gr.Row(elem_classes="chip-row"):
|
|
|
|
| 2993 |
|
| 2994 |
# ---------------- the rest, in tabs ----------------
|
| 2995 |
with gr.Tabs():
|
| 2996 |
+
with gr.Tab(f"⭐ Custom lora ({LORA_SLOTS} slots)", visible=False) as pro_loratab:
|
| 2997 |
gr.Markdown(LORA_HELP)
|
| 2998 |
with gr.Accordion("🔍 Search CivitAI", open=False):
|
| 2999 |
gr.Markdown(
|
|
|
|
| 3059 |
duration = gr.Slider(
|
| 3060 |
label="Duration (s)", minimum=MIN_DURATION, maximum=MAX_UI_DURATION, step=1, value=5
|
| 3061 |
)
|
| 3062 |
+
steps = gr.Slider(label="Steps", minimum=MIN_STEPS, maximum=40, step=1,
|
| 3063 |
+
value=28, visible=False)
|
| 3064 |
+
with gr.Row(visible=False) as pro_seed:
|
| 3065 |
seed = gr.Number(label="Seed", value=42, precision=0, scale=3)
|
| 3066 |
seed_dice = gr.Button("🎲 roll", variant="secondary", scale=1, elem_id="seed-dice")
|
| 3067 |
randomize_seed = gr.Checkbox(
|
| 3068 |
label="🎲 Randomize seed on every run",
|
| 3069 |
value=True,
|
| 3070 |
+
visible=False,
|
| 3071 |
info="A new seed is drawn each time Generate is pressed, and lands in the box above.",
|
| 3072 |
)
|
| 3073 |
|
| 3074 |
+
with gr.Tab("💾 Profiles", visible=False) as pro_profiles:
|
| 3075 |
gr.Markdown(PROFILE_HELP)
|
| 3076 |
with gr.Row():
|
| 3077 |
profile_picker = gr.Dropdown(
|
|
|
|
| 3114 |
2, 8, value=3, step=1, label="How many clips in a row",
|
| 3115 |
info="3 clips of 5 s ≈ a 15 second video.",
|
| 3116 |
)
|
| 3117 |
+
with gr.Accordion("✍️ A prompt per clip (optional)", open=False,
|
| 3118 |
+
visible=False) as pro_perclip:
|
| 3119 |
scene_prompts = gr.Textbox(
|
| 3120 |
label="One line per clip", lines=8, max_lines=8,
|
| 3121 |
placeholder=("line 1 = clip 1, line 2 = clip 2, and so on\n"
|
|
|
|
| 3350 |
api_name=False,
|
| 3351 |
)
|
| 3352 |
|
| 3353 |
+
# ---------------------------------------------------------- simple mode
|
| 3354 |
+
|
| 3355 |
+
def _slot_updates(items):
|
| 3356 |
+
"""Fill the first slots with these entries and empty the rest of them.
|
| 3357 |
+
|
| 3358 |
+
Emptying matters - without it yesterday's pick stays loaded underneath today's.
|
| 3359 |
+
"""
|
| 3360 |
+
updates = []
|
| 3361 |
+
for index in range(LORA_SLOTS):
|
| 3362 |
+
item = items[index] if index < len(items) else None
|
| 3363 |
+
if item is None:
|
| 3364 |
+
updates.append(gr.update(value=""))
|
| 3365 |
+
updates.append(gr.update())
|
| 3366 |
+
else:
|
| 3367 |
+
updates.append(gr.update(value=str(item.get("url") or "")))
|
| 3368 |
+
updates.append(gr.update(value=float(item.get("strength") or DEFAULT_LORA_SCALE)))
|
| 3369 |
+
return updates
|
| 3370 |
+
|
| 3371 |
+
def _basic_note(picks, extra=""):
|
| 3372 |
+
if not picks:
|
| 3373 |
+
return "No lora added — the prompt is written, that is all." + extra
|
| 3374 |
+
names = ", ".join(str(item.get("name", ""))[:60] for item in picks)
|
| 3375 |
+
blind = [item for item in picks if not str(item.get("trigger") or "").strip()]
|
| 3376 |
+
if blind:
|
| 3377 |
+
extra += (" One of them has no trigger words saved, so it may do nothing; "
|
| 3378 |
+
"tick a different one if the clip ignores it.")
|
| 3379 |
+
return f"✅ **Loras in:** {names}.{extra}"
|
| 3380 |
+
|
| 3381 |
+
def _basic_run(wanted, space_id, first_image, shot, camera, sound, music,
|
| 3382 |
+
speaker, dialogue, references, progress=gr.Progress()):
|
| 3383 |
+
progress(0.1, desc="asking the Space (a sleeping one takes a minute to wake)")
|
| 3384 |
+
written, note = _write_prompt(space_id, wanted, first_image)
|
| 3385 |
+
plain = written or str(wanted or "")
|
| 3386 |
+
# The labelled sections go on last, around whatever prose we ended up with.
|
| 3387 |
+
built = build_ir_prompt(plain, shot, camera, sound, music, speaker, dialogue,
|
| 3388 |
+
references)
|
| 3389 |
+
progress(0.6, desc="searching the library")
|
| 3390 |
+
shortlist = _shortlist_loras(built, _library_pool())
|
| 3391 |
+
if not shortlist:
|
| 3392 |
+
return tuple(
|
| 3393 |
+
[gr.update(value=built), gr.update(choices=[], value=[], visible=False), [],
|
| 3394 |
+
f"✅ Prompt built — {note}. Nothing in the library matched these words."]
|
| 3395 |
+
+ _slot_updates([])
|
| 3396 |
+
)
|
| 3397 |
+
progress(0.8, desc="asking which loras fit")
|
| 3398 |
+
chosen = _ask_which_loras(space_id, built, shortlist) or [0]
|
| 3399 |
+
picks = [shortlist[index] for index in chosen]
|
| 3400 |
+
labels = [_pick_label(index, item) for index, item in enumerate(shortlist)]
|
| 3401 |
+
return tuple(
|
| 3402 |
+
[gr.update(value=_retune_triggers(built, shortlist, picks)),
|
| 3403 |
+
gr.update(choices=labels, value=[labels[i] for i in chosen], visible=True),
|
| 3404 |
+
shortlist,
|
| 3405 |
+
_basic_note(picks, f" Prompt {note}, picked out of {len(shortlist)} that matched.")]
|
| 3406 |
+
+ _slot_updates(picks)
|
| 3407 |
+
)
|
| 3408 |
+
|
| 3409 |
+
def _basic_swap(ticked, shortlist, prompt_text):
|
| 3410 |
+
labels = [_pick_label(index, item) for index, item in enumerate(shortlist or [])]
|
| 3411 |
+
wanted = [labels.index(label) for label in (ticked or []) if label in labels]
|
| 3412 |
+
extra = " Two at a time is the limit, so the rest were left out." if len(wanted) > 2 else ""
|
| 3413 |
+
picks = [shortlist[index] for index in wanted[:2]]
|
| 3414 |
+
return tuple(
|
| 3415 |
+
[gr.update(value=_retune_triggers(prompt_text, shortlist, picks)),
|
| 3416 |
+
_basic_note(picks, extra)]
|
| 3417 |
+
+ _slot_updates(picks)
|
| 3418 |
+
)
|
| 3419 |
+
|
| 3420 |
+
_BASIC_SLOTS = [field for pair in zip(lora_references, lora_scales) for field in pair]
|
| 3421 |
+
|
| 3422 |
+
basic_btn.click(
|
| 3423 |
+
_basic_run,
|
| 3424 |
+
[prompt, basic_space, images[0], ir_shot, ir_camera, ir_sound, ir_music,
|
| 3425 |
+
ir_speaker, ir_dialogue, ir_references],
|
| 3426 |
+
[prompt, basic_pick, basic_shortlist, basic_status] + _BASIC_SLOTS,
|
| 3427 |
+
api_name=False,
|
| 3428 |
+
)
|
| 3429 |
+
|
| 3430 |
+
# `.input` fires only when the person ticks something; older Gradio has only
|
| 3431 |
+
# `.change`, which also fires when the button above fills the box in. Either is
|
| 3432 |
+
# safe here - a second pass over the same ticks lands on the same answer.
|
| 3433 |
+
_tick_event = getattr(basic_pick, "input", None) or basic_pick.change
|
| 3434 |
+
_tick_event(
|
| 3435 |
+
_basic_swap,
|
| 3436 |
+
[basic_pick, basic_shortlist, prompt],
|
| 3437 |
+
[prompt, basic_status] + _BASIC_SLOTS,
|
| 3438 |
+
api_name=False,
|
| 3439 |
+
)
|
| 3440 |
+
|
| 3441 |
+
_PRO_ONLY = [pro_builder, pro_chips, upsample, pro_loratab, pro_profiles,
|
| 3442 |
+
steps, pro_seed, randomize_seed, pro_perclip]
|
| 3443 |
+
|
| 3444 |
+
def _switch_mode(mode):
|
| 3445 |
+
pro = str(mode) == "pro"
|
| 3446 |
+
return [gr.update(visible=pro) for _ in _PRO_ONLY] + [gr.update(visible=not pro)]
|
| 3447 |
+
|
| 3448 |
+
ui_mode.change(_switch_mode, [ui_mode], _PRO_ONLY + [basic_panel], api_name=False)
|
| 3449 |
+
|
| 3450 |
lora_identify_btn.click(identify_loras, lora_references, lora_names, api_name=False)
|
| 3451 |
for _field in lora_references:
|
| 3452 |
_field.submit(identify_loras, lora_references, lora_names, api_name=False)
|