Spaces:
Running on Zero
Running on Zero
Commit ·
0ba18b7
1
Parent(s): b7100a9
Add Ref2VA storyboard studio and live TAE previews
Browse files- app.py +213 -31
- frontend/dist/assets/__vite-browser-external-Cgmn0awE-17s0Hkgg.js +1 -0
- frontend/dist/assets/__vite-browser-external-Cgmn0awE-BTUFketj.js +0 -1
- frontend/dist/assets/{browser-GbxqfQNb.js → browser-C_c7vNYP.js} +2 -2
- frontend/dist/assets/index-CCfkPg0W.css +0 -1
- frontend/dist/assets/index-DZbZsc56.css +1 -0
- frontend/dist/assets/index-DoM51C4t.js +0 -0
- frontend/dist/assets/index-Dxjs8B8O.js +0 -0
- frontend/dist/index.html +2 -2
- frontend/src/App.tsx +104 -6
- frontend/src/api.ts +27 -4
- frontend/src/components/AboutSheet.tsx +19 -0
- frontend/src/components/ComposeRail.tsx +27 -6
- frontend/src/components/ReferenceLibrary.tsx +108 -0
- frontend/src/components/StoryboardEditor.tsx +31 -0
- frontend/src/components/Viewer.tsx +9 -1
- frontend/src/lib/history.ts +5 -3
- frontend/src/lib/runtimeHistory.ts +6 -2
- frontend/src/lib/workflows.ts +7 -1
- frontend/src/types.ts +27 -1
- h3_nvfp4.py +5 -3
- h3_split_blocks.py +56 -4
- h3_tae.py +153 -0
- requirements.txt +2 -0
app.py
CHANGED
|
@@ -4,10 +4,12 @@ from __future__ import annotations
|
|
| 4 |
|
| 5 |
import hashlib
|
| 6 |
import ipaddress
|
|
|
|
| 7 |
import os
|
| 8 |
import re
|
| 9 |
import shutil
|
| 10 |
import socket
|
|
|
|
| 11 |
import tempfile
|
| 12 |
import time
|
| 13 |
import traceback
|
|
@@ -44,6 +46,8 @@ TURBO_4_LORA_FILE = "minimax_h3_fl2v_turbo_4step_v0.1.safetensors"
|
|
| 44 |
TURBO_4_LORA_SCALE = 8 / 128
|
| 45 |
TURBO_8_LORA_REPO = "larryvrh/MiniMax-H3-Turbo-Lora"
|
| 46 |
TURBO_8_LORA_FILE = "minimax_h3_turbo_4step_ema_ckpt850.safetensors"
|
|
|
|
|
|
|
| 47 |
LORA_MAX_BYTES = 2 * 1024**3
|
| 48 |
EGRID_COMMIT = "a7624b4c00626a8ae7e78860769389d706565190"
|
| 49 |
EGRID_SHA256 = "30eb3c2cc7fb6b470d9717ff840d359313ac27cd64b705e32da1baa10f72d6a8"
|
|
@@ -140,8 +144,10 @@ def lower_duration_floor(seconds: float = MIN_UI_DURATION) -> None:
|
|
| 140 |
|
| 141 |
|
| 142 |
PIPE = None
|
|
|
|
| 143 |
MANAGER = None
|
| 144 |
COND_PIPE = None
|
|
|
|
| 145 |
COND_ERROR: str | None = None
|
| 146 |
LOAD_ERROR: str | None = None
|
| 147 |
LOADED_IN: float | None = None
|
|
@@ -177,7 +183,8 @@ def status() -> str:
|
|
| 177 |
conditioner_status = f"remote `{CONDITIONER_SPACE}`" + (" (local fallback)" if COND_ERROR else "")
|
| 178 |
return (
|
| 179 |
f"Ready · **{engine_status}** · VAEs full precision · placement `{PLACEMENT}` · attention `{ATTENTION}` · "
|
| 180 |
-
f"loaded in {LOADED_IN:.0f}s · conditioner {conditioner_status}"
|
|
|
|
| 181 |
)
|
| 182 |
|
| 183 |
|
|
@@ -189,7 +196,7 @@ def load_models() -> str | None:
|
|
| 189 |
Both autoencoders carry `_keep_in_fp32_modules` over every module and stay float32: a bfloat16 audio VAE decodes
|
| 190 |
the soundtrack roughly 20 dB too quiet.
|
| 191 |
"""
|
| 192 |
-
global PIPE, MANAGER, COND_PIPE, COND_ERROR, LOAD_ERROR, LOADED_IN
|
| 193 |
|
| 194 |
if PIPE is not None or LOAD_ERROR is not None:
|
| 195 |
return LOAD_ERROR
|
|
@@ -199,7 +206,7 @@ def load_models() -> str | None:
|
|
| 199 |
import torch
|
| 200 |
from diffusers import ComponentsManager
|
| 201 |
|
| 202 |
-
from h3_split_blocks import MiniMaxH3GeneratorBlocks
|
| 203 |
|
| 204 |
lower_duration_floor()
|
| 205 |
manager = ComponentsManager()
|
|
@@ -209,6 +216,7 @@ def load_models() -> str | None:
|
|
| 209 |
# This is consumed by the endpoint's Gradio Progress tracker and forwarded across the ZeroGPU worker RPC,
|
| 210 |
# producing one real queue update per denoising step.
|
| 211 |
pipe.set_progress_bar_config(desc="Denoising")
|
|
|
|
| 212 |
if ENGINE == "nvfp4":
|
| 213 |
# Do not download the 61.7 GiB BF16 transformer. The schedulers and full-precision VAEs stay canonical;
|
| 214 |
# only the repeatedly executed DiT is replaced with the pruned Blackwell-native checkpoint.
|
|
@@ -219,6 +227,19 @@ def load_models() -> str | None:
|
|
| 219 |
from h3_nvfp4 import load_transformer
|
| 220 |
|
| 221 |
pipe.update_components(transformer=load_transformer())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 222 |
elif ENGINE == "bf16":
|
| 223 |
pipe.load_components(dtype=torch.bfloat16)
|
| 224 |
else:
|
|
@@ -243,10 +264,11 @@ def load_models() -> str | None:
|
|
| 243 |
_arm_decode_hooks(pipe)
|
| 244 |
|
| 245 |
cond_pipe = None
|
|
|
|
| 246 |
if CONDITIONER_MODE == "local":
|
| 247 |
try:
|
| 248 |
from h3_local_conditioner import load_local_conditioner
|
| 249 |
-
from h3_split_blocks import MiniMaxH3ConditionerBlocks
|
| 250 |
|
| 251 |
print("[cond] loading the local truncated NVFP4-AWQ conditioner ...", flush=True)
|
| 252 |
text_encoder, tokenizer, processor = load_local_conditioner()
|
|
@@ -256,6 +278,12 @@ def load_models() -> str | None:
|
|
| 256 |
tokenizer=tokenizer,
|
| 257 |
processor=processor,
|
| 258 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 259 |
except Exception as error:
|
| 260 |
traceback.print_exc()
|
| 261 |
COND_ERROR = f"{type(error).__name__}: {error}"
|
|
@@ -263,7 +291,10 @@ def load_models() -> str | None:
|
|
| 263 |
elif CONDITIONER_MODE != "remote":
|
| 264 |
raise ValueError(f"H3_CONDITIONER_MODE must be `local` or `remote`, got {CONDITIONER_MODE!r}")
|
| 265 |
|
| 266 |
-
|
|
|
|
|
|
|
|
|
|
| 267 |
LOADED_IN = time.time() - started
|
| 268 |
print(f"[gen] ready in {LOADED_IN:.0f}s", flush=True)
|
| 269 |
except Exception as error:
|
|
@@ -522,6 +553,15 @@ def get_duration(prompt, prompt_embeds, text_token_tags, image, last_image, heig
|
|
| 522 |
latent_frames = (num_frames - LATENTS_PER_CHUNK) // FRAMES_PER_CHUNK * LATENTS_PER_CHUNK + 2
|
| 523 |
patches = (height // 32) * (width // 32)
|
| 524 |
rows = latent_frames * patches + (int(image is not None) + int(last_image is not None)) * patches
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 525 |
denoise = steps * (_DUR_B * rows + _DUR_C * rows**2)
|
| 526 |
decode = _DECODE_BASE + _DECODE_PER_DEFAULT_CANVAS * (height * width * num_frames) / _DEFAULT_CANVAS_PIXELS
|
| 527 |
local_conditioning = 20 if prompt_embeds is None else 0
|
|
@@ -531,7 +571,7 @@ def get_duration(prompt, prompt_embeds, text_token_tags, image, last_image, heig
|
|
| 531 |
@spaces.GPU(duration=get_duration, size=GPU_SIZE)
|
| 532 |
def _generate(
|
| 533 |
prompt, prompt_embeds, text_token_tags, image, last_image, height, width, num_frames, steps, seed, acceleration,
|
| 534 |
-
lora_path, egrid_path, lora_strength, conditioning_cache_key,
|
| 535 |
):
|
| 536 |
"""The only thing on GPU time: local conditioning, packed denoising and the two decoders.
|
| 537 |
|
|
@@ -540,19 +580,46 @@ def _generate(
|
|
| 540 |
"""
|
| 541 |
import torch
|
| 542 |
|
| 543 |
-
|
| 544 |
-
|
| 545 |
-
if
|
| 546 |
-
|
| 547 |
-
|
| 548 |
-
|
| 549 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 550 |
|
| 551 |
condition_seconds = None
|
| 552 |
num_text_tokens = None
|
| 553 |
condition_cache_hit = False
|
| 554 |
if prompt_embeds is None:
|
| 555 |
-
if
|
| 556 |
raise RuntimeError(f"The local conditioner is unavailable: {COND_ERROR or 'disabled'}")
|
| 557 |
cached = CONDITION_CACHE.pop(conditioning_cache_key, None) if conditioning_cache_key else None
|
| 558 |
if cached is not None:
|
|
@@ -564,12 +631,22 @@ def _generate(
|
|
| 564 |
print(f"[cond] reused {num_text_tokens}-token embedding", flush=True)
|
| 565 |
else:
|
| 566 |
conditioned = time.time()
|
| 567 |
-
condition_state =
|
| 568 |
-
|
| 569 |
-
|
| 570 |
-
|
| 571 |
-
|
| 572 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 573 |
)
|
| 574 |
prompt_embeds = condition_state.get("prompt_embeds")
|
| 575 |
text_token_tags = condition_state.get("text_token_tags")
|
|
@@ -582,31 +659,35 @@ def _generate(
|
|
| 582 |
while len(CONDITION_CACHE) > CONDITION_CACHE_SIZE:
|
| 583 |
CONDITION_CACHE.popitem(last=False)
|
| 584 |
|
| 585 |
-
|
| 586 |
-
|
|
|
|
| 587 |
if lora_path and activate_lora is None:
|
| 588 |
raise RuntimeError("LoRAs are supported only by the NVFP4 engine.")
|
| 589 |
if activate_lora is not None:
|
| 590 |
activate_lora(lora_path, egrid_path, float(lora_strength))
|
| 591 |
-
begin_request = getattr(
|
| 592 |
-
end_request = getattr(
|
| 593 |
if begin_request is not None:
|
| 594 |
# MiniMaxH3Scheduler includes terminal sigma=0 in `num_inference_steps`, so N points execute N-1 forwards.
|
| 595 |
begin_request(max(1, int(steps) - 1), acceleration)
|
| 596 |
cache_stats = None
|
| 597 |
try:
|
| 598 |
with torch.inference_mode():
|
| 599 |
-
|
| 600 |
prompt_embeds=prompt_embeds.to("cuda", non_blocking=True),
|
| 601 |
text_token_tags=text_token_tags,
|
| 602 |
-
image=image,
|
| 603 |
-
last_image=last_image,
|
| 604 |
height=height,
|
| 605 |
width=width,
|
| 606 |
num_frames=num_frames,
|
| 607 |
num_inference_steps=int(steps),
|
| 608 |
generator=torch.Generator("cpu").manual_seed(int(seed)),
|
| 609 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 610 |
finally:
|
| 611 |
if end_request is not None:
|
| 612 |
cache_stats = end_request()
|
|
@@ -641,7 +722,8 @@ def _generate_with_hardware_retry(*args):
|
|
| 641 |
def generate(
|
| 642 |
prompt, image_path=None, last_image_path=None, canvas=DEFAULT_CANVAS, duration=5, steps=28, seed=42,
|
| 643 |
upsample=False, acceleration="Balanced", lora_preset="None", lora_repo="", lora_filename="",
|
| 644 |
-
lora_strength=1.0, generation_preset=CUSTOM_PRESET,
|
|
|
|
| 645 |
):
|
| 646 |
"""One request. The appended UI preset leaves older positional API parameters intact."""
|
| 647 |
if LOAD_ERROR:
|
|
@@ -667,14 +749,58 @@ def generate(
|
|
| 667 |
return value.get("path")
|
| 668 |
return getattr(value, "path", value)
|
| 669 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 670 |
image_path, last_image_path = input_path(image_path), input_path(last_image_path)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 671 |
if image_path:
|
| 672 |
image_path, canvas = _fit_keyframe(image_path, canvas)
|
| 673 |
if last_image_path:
|
| 674 |
last_image_path, canvas = _fit_keyframe(last_image_path, canvas)
|
| 675 |
|
| 676 |
requested_steps = int(steps)
|
| 677 |
-
displayed_steps = requested_steps
|
| 678 |
if lora_preset == "Turbo · 4 steps":
|
| 679 |
# The native scheduler includes its terminal zero in this count; 5 points produce 4 exact Euler evaluations.
|
| 680 |
steps, displayed_steps = 5, 4
|
|
@@ -712,7 +838,7 @@ def generate(
|
|
| 712 |
|
| 713 |
# Prompt rewriting needs the discarded LM head and decoder tail, so it intentionally retains the remote path.
|
| 714 |
# Normal generation—the default—keeps embeddings on this worker and never serializes them through another API.
|
| 715 |
-
if upsample or COND_PIPE is None:
|
| 716 |
progress(
|
| 717 |
0.0,
|
| 718 |
desc=f"Upsampling and conditioning on {CONDITIONER_SPACE} ..."
|
|
@@ -732,7 +858,7 @@ def generate(
|
|
| 732 |
|
| 733 |
progress(
|
| 734 |
0.1,
|
| 735 |
-
desc=("Local conditioning + " if prompt_embeds is None else "")
|
| 736 |
+ f"denoising {displayed_steps} steps at {width}x{height}, {num_frames} frames ...",
|
| 737 |
)
|
| 738 |
started = time.time()
|
|
@@ -750,6 +876,7 @@ def generate(
|
|
| 750 |
str(height),
|
| 751 |
_sha256(image_path) if image_path else "",
|
| 752 |
_sha256(last_image_path) if last_image_path else "",
|
|
|
|
| 753 |
]
|
| 754 |
).encode()
|
| 755 |
).hexdigest()
|
|
@@ -769,6 +896,7 @@ def generate(
|
|
| 769 |
egrid_path,
|
| 770 |
float(lora_strength) * adapter_scale,
|
| 771 |
conditioning_key,
|
|
|
|
| 772 |
)
|
| 773 |
finally:
|
| 774 |
LocalContext.progress.reset(progress_token)
|
|
@@ -892,6 +1020,7 @@ def generate_api(
|
|
| 892 |
lora_filename: str = "",
|
| 893 |
lora_strength: float = 1.0,
|
| 894 |
generation_preset: str = DEFAULT_PRESET,
|
|
|
|
| 895 |
request: Request = None,
|
| 896 |
) -> tuple[FileData, str, str]:
|
| 897 |
"""Queued generation endpoint used by both the React studio and ordinary gradio_client callers."""
|
|
@@ -911,10 +1040,54 @@ def generate_api(
|
|
| 911 |
lora_filename,
|
| 912 |
lora_strength,
|
| 913 |
generation_preset,
|
|
|
|
| 914 |
ip_token=ip_token,
|
| 915 |
)
|
| 916 |
|
| 917 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 918 |
@app.get("/status")
|
| 919 |
def studio_status():
|
| 920 |
return {"ready": PIPE is not None and LOAD_ERROR is None, "status": status()}
|
|
@@ -945,6 +1118,15 @@ def studio_config():
|
|
| 945 |
"default_preset": DEFAULT_PRESET,
|
| 946 |
"custom_preset": CUSTOM_PRESET,
|
| 947 |
"examples": EXAMPLES,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 948 |
}
|
| 949 |
|
| 950 |
|
|
|
|
| 4 |
|
| 5 |
import hashlib
|
| 6 |
import ipaddress
|
| 7 |
+
import mimetypes
|
| 8 |
import os
|
| 9 |
import re
|
| 10 |
import shutil
|
| 11 |
import socket
|
| 12 |
+
import subprocess
|
| 13 |
import tempfile
|
| 14 |
import time
|
| 15 |
import traceback
|
|
|
|
| 46 |
TURBO_4_LORA_SCALE = 8 / 128
|
| 47 |
TURBO_8_LORA_REPO = "larryvrh/MiniMax-H3-Turbo-Lora"
|
| 48 |
TURBO_8_LORA_FILE = "minimax_h3_turbo_4step_ema_ckpt850.safetensors"
|
| 49 |
+
REF2VA_REPO = os.environ.get("H3_REF2VA_REPO", "lilcheaty/MiniMax-H3-NVFP4")
|
| 50 |
+
REF2VA_FILE = os.environ.get("H3_REF2VA_FILE", "minimax_h3_ref2va_pruned_nvfp4.safetensors")
|
| 51 |
LORA_MAX_BYTES = 2 * 1024**3
|
| 52 |
EGRID_COMMIT = "a7624b4c00626a8ae7e78860769389d706565190"
|
| 53 |
EGRID_SHA256 = "30eb3c2cc7fb6b470d9717ff840d359313ac27cd64b705e32da1baa10f72d6a8"
|
|
|
|
| 144 |
|
| 145 |
|
| 146 |
PIPE = None
|
| 147 |
+
REF_PIPE = None
|
| 148 |
MANAGER = None
|
| 149 |
COND_PIPE = None
|
| 150 |
+
REF_COND_PIPE = None
|
| 151 |
COND_ERROR: str | None = None
|
| 152 |
LOAD_ERROR: str | None = None
|
| 153 |
LOADED_IN: float | None = None
|
|
|
|
| 183 |
conditioner_status = f"remote `{CONDITIONER_SPACE}`" + (" (local fallback)" if COND_ERROR else "")
|
| 184 |
return (
|
| 185 |
f"Ready · **{engine_status}** · VAEs full precision · placement `{PLACEMENT}` · attention `{ATTENTION}` · "
|
| 186 |
+
f"loaded in {LOADED_IN:.0f}s · conditioner {conditioner_status} · "
|
| 187 |
+
f"Ref2VA {'ready' if REF_PIPE is not None and REF_COND_PIPE is not None else 'unavailable'} · TAE live previews"
|
| 188 |
)
|
| 189 |
|
| 190 |
|
|
|
|
| 196 |
Both autoencoders carry `_keep_in_fp32_modules` over every module and stay float32: a bfloat16 audio VAE decodes
|
| 197 |
the soundtrack roughly 20 dB too quiet.
|
| 198 |
"""
|
| 199 |
+
global PIPE, REF_PIPE, MANAGER, COND_PIPE, REF_COND_PIPE, COND_ERROR, LOAD_ERROR, LOADED_IN
|
| 200 |
|
| 201 |
if PIPE is not None or LOAD_ERROR is not None:
|
| 202 |
return LOAD_ERROR
|
|
|
|
| 206 |
import torch
|
| 207 |
from diffusers import ComponentsManager
|
| 208 |
|
| 209 |
+
from h3_split_blocks import MiniMaxH3GeneratorBlocks, MiniMaxH3Ref2VAGeneratorBlocks
|
| 210 |
|
| 211 |
lower_duration_floor()
|
| 212 |
manager = ComponentsManager()
|
|
|
|
| 216 |
# This is consumed by the endpoint's Gradio Progress tracker and forwarded across the ZeroGPU worker RPC,
|
| 217 |
# producing one real queue update per denoising step.
|
| 218 |
pipe.set_progress_bar_config(desc="Denoising")
|
| 219 |
+
ref_pipe = None
|
| 220 |
if ENGINE == "nvfp4":
|
| 221 |
# Do not download the 61.7 GiB BF16 transformer. The schedulers and full-precision VAEs stay canonical;
|
| 222 |
# only the repeatedly executed DiT is replaced with the pruned Blackwell-native checkpoint.
|
|
|
|
| 227 |
from h3_nvfp4 import load_transformer
|
| 228 |
|
| 229 |
pipe.update_components(transformer=load_transformer())
|
| 230 |
+
ref_blocks = MiniMaxH3Ref2VAGeneratorBlocks()
|
| 231 |
+
ref_pipe = ref_blocks.init_pipeline(MODEL_REPO, components_manager=manager, collection="h3-ref")
|
| 232 |
+
shared = {
|
| 233 |
+
component.name: getattr(pipe, component.name)
|
| 234 |
+
for component in ref_blocks.expected_components
|
| 235 |
+
if component.name != "transformer_ref" and getattr(pipe, component.name, None) is not None
|
| 236 |
+
}
|
| 237 |
+
ref_pipe.update_components(
|
| 238 |
+
**shared,
|
| 239 |
+
transformer_ref=load_transformer(REF2VA_REPO, REF2VA_FILE),
|
| 240 |
+
)
|
| 241 |
+
ref_pipe.transformer_ref.set_attention_backend(ATTENTION)
|
| 242 |
+
ref_pipe.set_progress_bar_config(desc="Denoising")
|
| 243 |
elif ENGINE == "bf16":
|
| 244 |
pipe.load_components(dtype=torch.bfloat16)
|
| 245 |
else:
|
|
|
|
| 264 |
_arm_decode_hooks(pipe)
|
| 265 |
|
| 266 |
cond_pipe = None
|
| 267 |
+
ref_cond_pipe = None
|
| 268 |
if CONDITIONER_MODE == "local":
|
| 269 |
try:
|
| 270 |
from h3_local_conditioner import load_local_conditioner
|
| 271 |
+
from h3_split_blocks import MiniMaxH3ConditionerBlocks, MiniMaxH3Ref2VAConditionerBlocks
|
| 272 |
|
| 273 |
print("[cond] loading the local truncated NVFP4-AWQ conditioner ...", flush=True)
|
| 274 |
text_encoder, tokenizer, processor = load_local_conditioner()
|
|
|
|
| 278 |
tokenizer=tokenizer,
|
| 279 |
processor=processor,
|
| 280 |
)
|
| 281 |
+
ref_cond_pipe = MiniMaxH3Ref2VAConditionerBlocks().init_pipeline(MODEL_REPO)
|
| 282 |
+
ref_cond_pipe.update_components(
|
| 283 |
+
text_encoder=text_encoder,
|
| 284 |
+
tokenizer=tokenizer,
|
| 285 |
+
processor=processor,
|
| 286 |
+
)
|
| 287 |
except Exception as error:
|
| 288 |
traceback.print_exc()
|
| 289 |
COND_ERROR = f"{type(error).__name__}: {error}"
|
|
|
|
| 291 |
elif CONDITIONER_MODE != "remote":
|
| 292 |
raise ValueError(f"H3_CONDITIONER_MODE must be `local` or `remote`, got {CONDITIONER_MODE!r}")
|
| 293 |
|
| 294 |
+
from h3_tae import load_preview_model
|
| 295 |
+
|
| 296 |
+
load_preview_model(OUTPUT_DIR)
|
| 297 |
+
PIPE, REF_PIPE, MANAGER, COND_PIPE, REF_COND_PIPE = pipe, ref_pipe, manager, cond_pipe, ref_cond_pipe
|
| 298 |
LOADED_IN = time.time() - started
|
| 299 |
print(f"[gen] ready in {LOADED_IN:.0f}s", flush=True)
|
| 300 |
except Exception as error:
|
|
|
|
| 553 |
latent_frames = (num_frames - LATENTS_PER_CHUNK) // FRAMES_PER_CHUNK * LATENTS_PER_CHUNK + 2
|
| 554 |
patches = (height // 32) * (width // 32)
|
| 555 |
rows = latent_frames * patches + (int(image is not None) + int(last_image is not None)) * patches
|
| 556 |
+
reference_specs = a[-1] if a and isinstance(a[-1], (list, tuple)) else []
|
| 557 |
+
if reference_specs:
|
| 558 |
+
# Ref2VA packs every reference beside the generated rows. Images are encoded at a 2048px short edge; videos
|
| 559 |
+
# occupy approximately one target-video block; audio adds two 40 Hz streams. This is deliberately conservative
|
| 560 |
+
# because under-booking a ZeroGPU request kills it after all earlier work has already been paid for.
|
| 561 |
+
kinds = [kind for _, kind in reference_specs]
|
| 562 |
+
rows += kinds.count("image") * 4096
|
| 563 |
+
rows += kinds.count("video") * latent_frames * patches
|
| 564 |
+
rows += kinds.count("audio") * int(num_frames / FPS * 80)
|
| 565 |
denoise = steps * (_DUR_B * rows + _DUR_C * rows**2)
|
| 566 |
decode = _DECODE_BASE + _DECODE_PER_DEFAULT_CANVAS * (height * width * num_frames) / _DEFAULT_CANVAS_PIXELS
|
| 567 |
local_conditioning = 20 if prompt_embeds is None else 0
|
|
|
|
| 571 |
@spaces.GPU(duration=get_duration, size=GPU_SIZE)
|
| 572 |
def _generate(
|
| 573 |
prompt, prompt_embeds, text_token_tags, image, last_image, height, width, num_frames, steps, seed, acceleration,
|
| 574 |
+
lora_path, egrid_path, lora_strength, conditioning_cache_key, reference_specs,
|
| 575 |
):
|
| 576 |
"""The only thing on GPU time: local conditioning, packed denoising and the two decoders.
|
| 577 |
|
|
|
|
| 580 |
"""
|
| 581 |
import torch
|
| 582 |
|
| 583 |
+
use_ref2va = bool(reference_specs)
|
| 584 |
+
active_pipe = REF_PIPE if use_ref2va else PIPE
|
| 585 |
+
active_conditioner = REF_COND_PIPE if use_ref2va else COND_PIPE
|
| 586 |
+
if active_pipe is None:
|
| 587 |
+
raise RuntimeError("The Ref2VA engine is unavailable on this deployment.")
|
| 588 |
+
references = None
|
| 589 |
+
if use_ref2va:
|
| 590 |
+
from diffusers.modular_pipelines.minimax_h3 import (
|
| 591 |
+
MiniMaxH3AudioReference,
|
| 592 |
+
MiniMaxH3ImageReference,
|
| 593 |
+
MiniMaxH3VideoReference,
|
| 594 |
+
)
|
| 595 |
+
|
| 596 |
+
classes = {
|
| 597 |
+
"image": MiniMaxH3ImageReference,
|
| 598 |
+
"video": MiniMaxH3VideoReference,
|
| 599 |
+
"audio": MiniMaxH3AudioReference,
|
| 600 |
+
}
|
| 601 |
+
references = [classes[kind].from_file(path) for path, kind in reference_specs]
|
| 602 |
+
|
| 603 |
+
if active_conditioner is not None and prompt_embeds is None:
|
| 604 |
+
active_conditioner.text_encoder.to("cuda")
|
| 605 |
+
if PLACEMENT in ("lazy", "pack"):
|
| 606 |
+
# Only one 12.5 GB transformer needs to be active. Shared VAEs stay on the card across workflow switches.
|
| 607 |
+
inactive = PIPE if use_ref2va else REF_PIPE
|
| 608 |
+
inactive_transformer = (
|
| 609 |
+
getattr(inactive, "transformer", None) or getattr(inactive, "transformer_ref", None)
|
| 610 |
+
if inactive is not None
|
| 611 |
+
else None
|
| 612 |
+
)
|
| 613 |
+
inactive_device = getattr(inactive_transformer, "device", None)
|
| 614 |
+
if inactive_transformer is not None and inactive_device is not None and torch.device(inactive_device).type == "cuda":
|
| 615 |
+
inactive_transformer.to("cpu")
|
| 616 |
+
active_pipe.to("cuda")
|
| 617 |
|
| 618 |
condition_seconds = None
|
| 619 |
num_text_tokens = None
|
| 620 |
condition_cache_hit = False
|
| 621 |
if prompt_embeds is None:
|
| 622 |
+
if active_conditioner is None:
|
| 623 |
raise RuntimeError(f"The local conditioner is unavailable: {COND_ERROR or 'disabled'}")
|
| 624 |
cached = CONDITION_CACHE.pop(conditioning_cache_key, None) if conditioning_cache_key else None
|
| 625 |
if cached is not None:
|
|
|
|
| 631 |
print(f"[cond] reused {num_text_tokens}-token embedding", flush=True)
|
| 632 |
else:
|
| 633 |
conditioned = time.time()
|
| 634 |
+
condition_state = (
|
| 635 |
+
active_conditioner(
|
| 636 |
+
prompt=prompt,
|
| 637 |
+
references=references,
|
| 638 |
+
height=int(height),
|
| 639 |
+
width=int(width),
|
| 640 |
+
num_frames=int(num_frames),
|
| 641 |
+
)
|
| 642 |
+
if use_ref2va
|
| 643 |
+
else active_conditioner(
|
| 644 |
+
prompt=prompt,
|
| 645 |
+
image=image,
|
| 646 |
+
last_image=last_image,
|
| 647 |
+
height=int(height),
|
| 648 |
+
width=int(width),
|
| 649 |
+
)
|
| 650 |
)
|
| 651 |
prompt_embeds = condition_state.get("prompt_embeds")
|
| 652 |
text_token_tags = condition_state.get("text_token_tags")
|
|
|
|
| 659 |
while len(CONDITION_CACHE) > CONDITION_CACHE_SIZE:
|
| 660 |
CONDITION_CACHE.popitem(last=False)
|
| 661 |
|
| 662 |
+
active_transformer = active_pipe.transformer_ref if use_ref2va else active_pipe.transformer
|
| 663 |
+
activate_lora = getattr(active_transformer, "activate_lora", None)
|
| 664 |
+
clear_lora = getattr(active_transformer, "clear_lora", None)
|
| 665 |
if lora_path and activate_lora is None:
|
| 666 |
raise RuntimeError("LoRAs are supported only by the NVFP4 engine.")
|
| 667 |
if activate_lora is not None:
|
| 668 |
activate_lora(lora_path, egrid_path, float(lora_strength))
|
| 669 |
+
begin_request = getattr(active_transformer, "begin_request", None)
|
| 670 |
+
end_request = getattr(active_transformer, "end_request", None)
|
| 671 |
if begin_request is not None:
|
| 672 |
# MiniMaxH3Scheduler includes terminal sigma=0 in `num_inference_steps`, so N points execute N-1 forwards.
|
| 673 |
begin_request(max(1, int(steps) - 1), acceleration)
|
| 674 |
cache_stats = None
|
| 675 |
try:
|
| 676 |
with torch.inference_mode():
|
| 677 |
+
common = dict(
|
| 678 |
prompt_embeds=prompt_embeds.to("cuda", non_blocking=True),
|
| 679 |
text_token_tags=text_token_tags,
|
|
|
|
|
|
|
| 680 |
height=height,
|
| 681 |
width=width,
|
| 682 |
num_frames=num_frames,
|
| 683 |
num_inference_steps=int(steps),
|
| 684 |
generator=torch.Generator("cpu").manual_seed(int(seed)),
|
| 685 |
)
|
| 686 |
+
state = (
|
| 687 |
+
active_pipe(references=references, **common)
|
| 688 |
+
if use_ref2va
|
| 689 |
+
else active_pipe(image=image, last_image=last_image, **common)
|
| 690 |
+
)
|
| 691 |
finally:
|
| 692 |
if end_request is not None:
|
| 693 |
cache_stats = end_request()
|
|
|
|
| 722 |
def generate(
|
| 723 |
prompt, image_path=None, last_image_path=None, canvas=DEFAULT_CANVAS, duration=5, steps=28, seed=42,
|
| 724 |
upsample=False, acceleration="Balanced", lora_preset="None", lora_repo="", lora_filename="",
|
| 725 |
+
lora_strength=1.0, generation_preset=CUSTOM_PRESET, references=None, ip_token=None,
|
| 726 |
+
progress=gr.Progress(track_tqdm=True),
|
| 727 |
):
|
| 728 |
"""One request. The appended UI preset leaves older positional API parameters intact."""
|
| 729 |
if LOAD_ERROR:
|
|
|
|
| 749 |
return value.get("path")
|
| 750 |
return getattr(value, "path", value)
|
| 751 |
|
| 752 |
+
def reference_kind(value, path):
|
| 753 |
+
mime = ""
|
| 754 |
+
if isinstance(value, dict):
|
| 755 |
+
mime = value.get("mime_type") or ""
|
| 756 |
+
else:
|
| 757 |
+
mime = getattr(value, "mime_type", "") or ""
|
| 758 |
+
mime = mime or mimetypes.guess_type(str(path))[0] or ""
|
| 759 |
+
if mime.startswith("image/"):
|
| 760 |
+
return "image"
|
| 761 |
+
if mime.startswith("video/"):
|
| 762 |
+
return "video"
|
| 763 |
+
if mime.startswith("audio/"):
|
| 764 |
+
return "audio"
|
| 765 |
+
raise ValueError(f"Unsupported reference file {os.path.basename(str(path))!r}; use an image, video or audio file.")
|
| 766 |
+
|
| 767 |
image_path, last_image_path = input_path(image_path), input_path(last_image_path)
|
| 768 |
+
reference_specs = []
|
| 769 |
+
for reference in references or []:
|
| 770 |
+
path = input_path(reference)
|
| 771 |
+
if path:
|
| 772 |
+
reference_specs.append((path, reference_kind(reference, path)))
|
| 773 |
+
use_ref2va = bool(reference_specs)
|
| 774 |
+
if use_ref2va:
|
| 775 |
+
if image_path or last_image_path:
|
| 776 |
+
raise ValueError("Choose keyframes or omni references for one shot, not both.")
|
| 777 |
+
if len(reference_specs) > 12:
|
| 778 |
+
raise ValueError("MiniMax-H3 accepts at most 12 ordered references.")
|
| 779 |
+
counts = {kind: sum(spec_kind == kind for _, spec_kind in reference_specs) for kind in ("image", "video", "audio")}
|
| 780 |
+
for kind, limit in (("image", 9), ("video", 3), ("audio", 3)):
|
| 781 |
+
if counts[kind] > limit:
|
| 782 |
+
raise ValueError(f"MiniMax-H3 accepts at most {limit} {kind} references.")
|
| 783 |
+
if counts["audio"] and not (counts["image"] or counts["video"]):
|
| 784 |
+
raise ValueError("Audio references need at least one image or video reference.")
|
| 785 |
+
if float(duration) < 5:
|
| 786 |
+
raise ValueError("Ref2VA clips must be at least 5 seconds long.")
|
| 787 |
+
if upsample:
|
| 788 |
+
raise ValueError("Prompt enhancement is not yet available for Ref2VA; turn Enhance off.")
|
| 789 |
+
if REF_PIPE is None or REF_COND_PIPE is None:
|
| 790 |
+
raise RuntimeError("The local Ref2VA engine or conditioner is unavailable.")
|
| 791 |
+
# FL2VA Turbo adapters do not target the reference checkpoint. Ref2VA retains the quality schedule and the
|
| 792 |
+
# conservative cache engine until a validated Ref2VA distillation is available.
|
| 793 |
+
steps, displayed_steps = 28, 28
|
| 794 |
+
acceleration = "Balanced"
|
| 795 |
+
lora_preset = "None"
|
| 796 |
+
generation_preset = "Ref2VA 28-step — ordered references"
|
| 797 |
if image_path:
|
| 798 |
image_path, canvas = _fit_keyframe(image_path, canvas)
|
| 799 |
if last_image_path:
|
| 800 |
last_image_path, canvas = _fit_keyframe(last_image_path, canvas)
|
| 801 |
|
| 802 |
requested_steps = int(steps)
|
| 803 |
+
displayed_steps = locals().get("displayed_steps", requested_steps)
|
| 804 |
if lora_preset == "Turbo · 4 steps":
|
| 805 |
# The native scheduler includes its terminal zero in this count; 5 points produce 4 exact Euler evaluations.
|
| 806 |
steps, displayed_steps = 5, 4
|
|
|
|
| 838 |
|
| 839 |
# Prompt rewriting needs the discarded LM head and decoder tail, so it intentionally retains the remote path.
|
| 840 |
# Normal generation—the default—keeps embeddings on this worker and never serializes them through another API.
|
| 841 |
+
if upsample or (COND_PIPE is None and not use_ref2va):
|
| 842 |
progress(
|
| 843 |
0.0,
|
| 844 |
desc=f"Upsampling and conditioning on {CONDITIONER_SPACE} ..."
|
|
|
|
| 858 |
|
| 859 |
progress(
|
| 860 |
0.1,
|
| 861 |
+
desc=("Local Ref2VA conditioning + " if use_ref2va else "Local conditioning + " if prompt_embeds is None else "")
|
| 862 |
+ f"denoising {displayed_steps} steps at {width}x{height}, {num_frames} frames ...",
|
| 863 |
)
|
| 864 |
started = time.time()
|
|
|
|
| 876 |
str(height),
|
| 877 |
_sha256(image_path) if image_path else "",
|
| 878 |
_sha256(last_image_path) if last_image_path else "",
|
| 879 |
+
*(_sha256(path) for path, _ in reference_specs),
|
| 880 |
]
|
| 881 |
).encode()
|
| 882 |
).hexdigest()
|
|
|
|
| 896 |
egrid_path,
|
| 897 |
float(lora_strength) * adapter_scale,
|
| 898 |
conditioning_key,
|
| 899 |
+
reference_specs,
|
| 900 |
)
|
| 901 |
finally:
|
| 902 |
LocalContext.progress.reset(progress_token)
|
|
|
|
| 1020 |
lora_filename: str = "",
|
| 1021 |
lora_strength: float = 1.0,
|
| 1022 |
generation_preset: str = DEFAULT_PRESET,
|
| 1023 |
+
references: list[FileData] | None = None,
|
| 1024 |
request: Request = None,
|
| 1025 |
) -> tuple[FileData, str, str]:
|
| 1026 |
"""Queued generation endpoint used by both the React studio and ordinary gradio_client callers."""
|
|
|
|
| 1040 |
lora_filename,
|
| 1041 |
lora_strength,
|
| 1042 |
generation_preset,
|
| 1043 |
+
references,
|
| 1044 |
ip_token=ip_token,
|
| 1045 |
)
|
| 1046 |
|
| 1047 |
|
| 1048 |
+
@app.api(name="stitch")
|
| 1049 |
+
def stitch_api(clips: list[FileData]) -> FileData:
|
| 1050 |
+
"""Join completed storyboard shots without another GPU allocation.
|
| 1051 |
+
|
| 1052 |
+
Every H3 clip already has the same frame and audio rates. Re-encoding keeps the endpoint resilient to different
|
| 1053 |
+
canvas/pixel formats and trims one duplicated continuation frame from every shot after the first.
|
| 1054 |
+
"""
|
| 1055 |
+
paths = [input.path if hasattr(input, "path") else input.get("path") for input in clips or []]
|
| 1056 |
+
paths = [str(path) for path in paths if path]
|
| 1057 |
+
if not 2 <= len(paths) <= 12:
|
| 1058 |
+
raise ValueError("A storyboard must contain between 2 and 12 completed shots.")
|
| 1059 |
+
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
| 1060 |
+
output = os.path.join(OUTPUT_DIR, f"h3-story-{int(time.time() * 1000)}.mp4")
|
| 1061 |
+
command = ["ffmpeg", "-y", "-loglevel", "error"]
|
| 1062 |
+
for path in paths:
|
| 1063 |
+
command.extend(["-i", path])
|
| 1064 |
+
chains = []
|
| 1065 |
+
concat_inputs = []
|
| 1066 |
+
seam = 1 / FPS
|
| 1067 |
+
for index in range(len(paths)):
|
| 1068 |
+
trim = "" if index == 0 else f"trim=start={seam},"
|
| 1069 |
+
atrim = "" if index == 0 else f"atrim=start={seam},"
|
| 1070 |
+
chains.append(
|
| 1071 |
+
f"[{index}:v]{trim}setpts=PTS-STARTPTS,scale=trunc(iw/2)*2:trunc(ih/2)*2,"
|
| 1072 |
+
f"fps={FPS},format=yuv420p[v{index}]"
|
| 1073 |
+
)
|
| 1074 |
+
chains.append(f"[{index}:a]{atrim}asetpts=PTS-STARTPTS,aresample=48000[a{index}]")
|
| 1075 |
+
concat_inputs.append(f"[v{index}][a{index}]")
|
| 1076 |
+
chains.append(f"{''.join(concat_inputs)}concat=n={len(paths)}:v=1:a=1[v][a]")
|
| 1077 |
+
command.extend(
|
| 1078 |
+
[
|
| 1079 |
+
"-filter_complex", ";".join(chains), "-map", "[v]", "-map", "[a]",
|
| 1080 |
+
"-c:v", "libx264", "-preset", "veryfast", "-crf", "18", "-c:a", "aac", "-b:a", "192k",
|
| 1081 |
+
"-movflags", "+faststart", output,
|
| 1082 |
+
]
|
| 1083 |
+
)
|
| 1084 |
+
try:
|
| 1085 |
+
subprocess.run(command, check=True, capture_output=True, text=True, timeout=300)
|
| 1086 |
+
except subprocess.CalledProcessError as error:
|
| 1087 |
+
raise RuntimeError(f"Could not assemble the storyboard: {error.stderr[-800:]}") from error
|
| 1088 |
+
return FileData(path=output)
|
| 1089 |
+
|
| 1090 |
+
|
| 1091 |
@app.get("/status")
|
| 1092 |
def studio_status():
|
| 1093 |
return {"ready": PIPE is not None and LOAD_ERROR is None, "status": status()}
|
|
|
|
| 1118 |
"default_preset": DEFAULT_PRESET,
|
| 1119 |
"custom_preset": CUSTOM_PRESET,
|
| 1120 |
"examples": EXAMPLES,
|
| 1121 |
+
"ref2va": {
|
| 1122 |
+
"enabled": REF_PIPE is not None and REF_COND_PIPE is not None,
|
| 1123 |
+
"max_total": 12,
|
| 1124 |
+
"max_images": 9,
|
| 1125 |
+
"max_videos": 3,
|
| 1126 |
+
"max_audio": 3,
|
| 1127 |
+
"minimum_duration": 5,
|
| 1128 |
+
},
|
| 1129 |
+
"tae_previews": True,
|
| 1130 |
}
|
| 1131 |
|
| 1132 |
|
frontend/dist/assets/__vite-browser-external-Cgmn0awE-17s0Hkgg.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
import{t as r}from"./browser-C_c7vNYP.js";import"./index-Dxjs8B8O.js";var o=r(((e,t)=>{t.exports={}}));const m=o();export{m as default};
|
frontend/dist/assets/__vite-browser-external-Cgmn0awE-BTUFketj.js
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
import{t as r}from"./browser-GbxqfQNb.js";import"./index-DoM51C4t.js";var o=r(((e,t)=>{t.exports={}}));const m=o();export{m as default};
|
|
|
|
|
|
frontend/dist/assets/{browser-GbxqfQNb.js → browser-C_c7vNYP.js}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/__vite-browser-external-Cgmn0awE-
|
| 2 |
-
import{_ as fe}from"./index-DoM51C4t.js";var Qe=Object.create,De=Object.defineProperty,Xe=Object.getOwnPropertyDescriptor,et=Object.getOwnPropertyNames,tt=Object.getPrototypeOf,st=Object.prototype.hasOwnProperty,ps=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports),nt=(e,t,s,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(var i=et(t),r=0,a=i.length,o;r<a;r++)o=i[r],!st.call(e,o)&&o!==s&&De(e,o,{get:(l=>t[l]).bind(null,o),enumerable:!(n=Xe(t,o))||n.enumerable});return e},ge=(e,t,s)=>(s=e==null?{}:Qe(tt(e)),nt(De(s,"default",{value:e,enumerable:!0}),e)),it="host",Ae="queue/data",rt="queue/join",_e="upload",at="login",V="config",ot="info",ct="runtime",lt="sleeptime",pt="heartbeat",ut="component_server",dt="reset",ht="cancel",ft="app_id",Ce="This application is currently busy. Please try again. ",N="Connection errored out. ",T="Could not resolve app config. ",gt="Could not get space status. ",me="Could not get API info. ",G="Space metadata could not be loaded. ",_t="Invalid URL. A full URL path is required.",mt="Not authorized to access this space. ",qe="Invalid credentials. Could not login. ",wt="Could not access this app (received a 401 response). If it is a private Hugging Face Space, pass a valid Hugging Face token to the `token` option of `Client.connect`. You can generate a token at https://huggingface.co/settings/tokens.",yt=(e,t)=>`Space "${e}" could not be accessed (received a ${t} response from the Hugging Face API). Check that the Space name is spelled correctly and that the Space exists. If the Space is private, pass a valid Hugging Face token to the \`token\` option of \`Client.connect\`. You can generate a token at https://huggingface.co/settings/tokens.`,bt="No API information is available for this app. This can happen when the app's `/info` endpoint cannot be reached, or when the app is running a legacy version of Gradio that is not supported by this client. ",vt="This app appears to be running a legacy version of Gradio (3.x or earlier) that communicates over WebSockets, which is not supported by this version of @gradio/client. Please upgrade the app to a newer version of Gradio, or connect to it with @gradio/client version 0.x.",kt="File system access is only available in Node.js environments",Ne="Root URL not found in client config",$t="Error uploading file";async function we(e,t,s){try{return(await(await fetch(`https://huggingface.co/api/spaces/${e}/jwt`,{headers:{Authorization:`Bearer ${t}`,...s?{Cookie:s}:{}}})).json()).token||!1}catch{return!1}}function ze(e){e.hf_token&&!e.token&&(e.token=e.hf_token,console.warn("The `hf_token` option has been renamed to `token`. Support for `hf_token` will be removed in a future version of @gradio/client."))}function ye(e){let t={};return e.forEach(({api_name:s,id:n})=>{s&&(t[s]=n)}),t}function Et(e,t){let s=new URL(e,t),n=new URL(t);return s.hostname===n.hostname?(s.protocol=n.protocol,s.host=n.host,s.toString().replace(/\/$/,"")):e}async function xt(e,t=!0){let s=this.options.token?{Authorization:`Bearer ${this.options.token}`}:{};if(typeof window<"u"&&window.gradio_config&&location.origin!=="http://localhost:9876"){if(t&&window.gradio_config.current_page&&(e=e.substring(0,e.lastIndexOf("/"))),window.gradio_config.dev_mode||typeof window<"u"&&window?.BUILD_MODE==="dev"){let i=ee(e,this.deep_link?V+"?deep_link="+this.deep_link:V),r=await be(await this.fetch(i,{headers:s,credentials:this.options.credentials??"same-origin"}),!!this.options.auth);r.root=e||r.root,window.gradio_config={...r,current_page:window.gradio_config.current_page}}let n={...window.gradio_config};return n.root=Et(n.root,location.href),n}else if(e){let n=ee(e,this.deep_link?V+"?deep_link="+this.deep_link:V),i=await be(await this.fetch(n,{headers:s,credentials:this.options.credentials??"same-origin"}),!!this.options.auth);return i.root||=e,i}throw Error(T)}async function be(e,t){if(e?.status===401&&!t){let s=null;try{s=await e.json()}catch{throw Error(wt)}let n=s?.detail?.auth_message;throw Error(n||"Login credentials are required to access this space.")}else if(e?.status===401&&t)throw Error(qe);if(e?.status===200){let s=await e.json();return s.dependencies?.forEach((n,i)=>{n.id===void 0&&(n.id=i)}),s}else if(e?.status===401)throw Error(mt);throw Error(`${T}(received status ${e?.status} when fetching the app config)`)}async function St(){let{http_protocol:e,host:t}=await ne(this.app_reference,this.options.token);try{if(this.options.auth){let s=await Be(e,t,this.options.auth,this.fetch,this.options.token,this.options.credentials);s&&this.set_cookies(s)}}catch(s){throw Error(s.message)}}async function Be(e,t,s,n,i,r){let a=new FormData;a.append("username",s?.[0]),a.append("password",s?.[1]);let o={};i&&(o.Authorization=`Bearer ${i}`);let l=await n(`${e}//${t}/${at}`,{headers:o,method:"POST",body:a,credentials:r??"same-origin"});if(l.status===200)return l.headers.get("set-cookie");throw l.status===401?Error(qe):Error(G)}function X(e){if(e.startsWith("http")){let{protocol:t,host:s,pathname:n}=new URL(e);return{ws_protocol:t==="https:"?"wss":"ws",http_protocol:t,host:s+(n==="/"?"":n)}}return{ws_protocol:"wss",http_protocol:"https:",host:new URL(e).host}}var Le=e=>{let t=[];return e.split(/,(?=\s*[^\s=;]+=[^\s=;]+)/).forEach(s=>{let[n,i]=s.split(";")[0].split("=");n&&i&&t.push(`${n.trim()}=${i.trim()}`)}),t},se=/^[a-zA-Z0-9_\-\.]+\/[a-zA-Z0-9_\-\.]+$/,jt=/.*hf\.space\/{0,1}.*$/;async function ne(e,t){let s={};t&&(s.Authorization=`Bearer ${t}`);let n=e.trim().replace(/\/$/,"");if(se.test(n)){let i;try{i=await fetch(`https://huggingface.co/api/spaces/${n}/${it}`,{headers:s})}catch{throw Error(G)}if(i.status===401||i.status===404)throw Error(yt(n,i.status));let r;try{r=(await i.json()).host}catch{throw Error(G)}if(!r)throw Error(G);return{space_id:n,...X(r)}}if(jt.test(n)){let{ws_protocol:i,http_protocol:r,host:a}=X(n);return{space_id:a.split("/")[0].replace(".hf.space",""),ws_protocol:i,http_protocol:r,host:a}}return{space_id:!1,...X(n)}}var ee=(...e)=>{try{return e.reduce((t,s)=>(t=t.replace(/\/+$/,""),s=s.replace(/^\/+/,""),new URL(s,t+"/").toString()))}catch{throw Error(_t)}};function Ot(e,t,s){let n={named_endpoints:{},unnamed_endpoints:{}};return Object.keys(e).forEach(i=>{(i==="named_endpoints"||i==="unnamed_endpoints")&&(n[i]={},Object.entries(e[i]).forEach(([r,a])=>{let o=a?.parameters??[],l=a?.returns??[],c=t.dependencies.find(h=>h.api_name===r||h.api_name===r.replace("/",""))?.id||s[r.replace("/","")]||-1,u=c===-1?void 0:t.dependencies.find(h=>h.id==c),f=c===-1?{generator:!1,cancel:!1}:u?.types;if(u&&Array.isArray(u.inputs)&&u.inputs.length!==o.length){let h=u.inputs.map($=>t.components.find(w=>w.id===$)?.type);try{h.forEach(($,w)=>{$==="state"&&o.splice(w,0,{component:"state",example:null,parameter_default:null,parameter_has_default:!0,parameter_name:null,hidden:!0})})}catch($){console.error($)}}let v=(h,$,w,z)=>({...h,description:Tt(h?.type,w),type:Pt(h?.type,$,w,z)||""});n[i][r]={parameters:o.map(h=>v(h,h?.component,h?.serializer,"parameter")),returns:l.map(h=>v(h,h?.component,h?.serializer,"return")),type:f,...a?.oauth_token?{oauth_token:a.oauth_token}:{}}}))}),n}function Pt(e,t,s,n){if(t==="Api")return e.type;switch(e?.type){case"string":return"string";case"boolean":return"boolean";case"number":return"number"}if(s==="JSONSerializable"||s==="StringSerializable")return"any";if(s==="ListStringSerializable")return"string[]";if(t==="Image")return n==="parameter"?"Blob | File | Buffer":"string";if(s==="FileSerializable")return e?.type==="array"?n==="parameter"?"(Blob | File | Buffer)[]":"{ name: string; data: string; size?: number; is_file?: boolean; orig_name?: string}[]":n==="parameter"?"Blob | File | Buffer":"{ name: string; data: string; size?: number; is_file?: boolean; orig_name?: string}";if(s==="GallerySerializable")return n==="parameter"?"[(Blob | File | Buffer), (string | null)][]":"[{ name: string; data: string; size?: number; is_file?: boolean; orig_name?: string}, (string | null))][]"}function Tt(e,t){return t==="GallerySerializable"?"array of [file, label] tuples":t==="ListStringSerializable"?"array of strings":t==="FileSerializable"?"array of files or single file":e?.description}function ve(e,t){switch(e.msg){case"send_data":return{type:"data"};case"send_hash":return{type:"hash"};case"queue_full":return{type:"update",status:{queue:!0,message:Ce,stage:"error",code:e.code,success:e.success}};case"heartbeat":return{type:"heartbeat"};case"unexpected_error":return{type:"unexpected_error",status:{queue:!0,message:e.message,session_not_found:e.session_not_found,stage:"error",success:!1}};case"broken_connection":return{type:"broken_connection",status:{queue:!0,message:e.message,stage:"error",success:!1}};case"estimation":return{type:"update",status:{queue:!0,stage:t||"pending",code:e.code,size:e.queue_size,position:e.rank,eta:e.rank_eta,success:e.success}};case"progress":return{type:"update",status:{queue:!0,stage:"pending",code:e.code,progress_data:e.progress_data,success:e.success}};case"log":return{type:"log",data:e};case"process_generating":return{type:"generating",status:{queue:!0,message:e.success?null:e.output.error,stage:e.success?"generating":"error",code:e.code,progress_data:e.progress_data,eta:e.average_duration,changed_state_ids:e.success?e.output.changed_state_ids:void 0},data:e.success?e.output:null};case"process_streaming":return{type:"streaming",status:{queue:!0,message:e.output.error,stage:"streaming",time_limit:e.time_limit,code:e.code,progress_data:e.progress_data,changed_state_ids:e.output.changed_state_ids,eta:e.eta},data:e.output};case"process_completed":return"error"in e.output?{type:"update",status:{queue:!0,title:e.output.title??"Error",message:e.output.error??"An error occurred",visible:e.output.visible,duration:e.output.duration,stage:"error",code:e.code,success:e.success}}:{type:"complete",status:{queue:!0,message:e.success?void 0:e.output.error,stage:e.success?"complete":"error",code:e.code,progress_data:e.progress_data,changed_state_ids:e.success?e.output.changed_state_ids:void 0,used_cache:e.used_cache,cache_duration:e.cache_duration,avg_time:e.avg_time},data:e.success?e.output:null};case"process_starts":return{type:"update",status:{queue:!0,stage:"pending",code:e.code,size:e.rank,position:0,success:e.success,eta:e.eta},original_msg:"process_starts"}}return{type:"none",status:{stage:"error",queue:!0}}}var Dt=(e=[],t)=>{let s=t?t.parameters:[];if(Array.isArray(e))return t&&s.length>0&&e.length>s.length&&console.warn("Too many arguments provided for the endpoint."),e;let n=[],i=Object.keys(e);return s.forEach((r,a)=>{if(e.hasOwnProperty(r.parameter_name))n[a]=e[r.parameter_name];else if(r.parameter_has_default)n[a]=r.parameter_default;else throw Error(`No value provided for required parameter: ${r.parameter_name}`)}),i.forEach(r=>{if(!s.some(a=>a.parameter_name===r))throw Error(`Parameter \`${r}\` is not a valid keyword argument. Please refer to the API for usage.`)}),n.forEach((r,a)=>{if(r===void 0&&!s[a].parameter_has_default)throw Error(`No value provided for required parameter: ${s[a].parameter_name}`)}),n};async function At(){if(this.api_info)return this.api_info;let{token:e}=this.options,{config:t}=this,s={};if(e&&(s.Authorization=`Bearer ${e}`),t)try{let n,i;if(typeof window<"u"&&window.gradio_api_info)i=window.gradio_api_info;else{let r=ee(t.root,this.api_prefix,ot);if(n=await this.fetch(r,{headers:s,credentials:this.options.credentials??"same-origin"}),!n.ok)throw Error(N);i=await n.json()}return"api"in i&&(i=i.api),i.named_endpoints["/predict"]&&!i.unnamed_endpoints[0]&&(i.unnamed_endpoints[0]=i.named_endpoints["/predict"]),Ot(i,t,this.api_map)}catch(n){throw Error("Could not get API info. "+n.message)}}async function Ct(e,t,s){let n={};this?.options?.token&&(n.Authorization=`Bearer ${this.options.token}`);let i=1e3,r=[],a;for(let o=0;o<t.length;o+=i){let l=t.slice(o,o+i),c=new FormData;l.forEach(f=>{c.append("files",f)});try{let f=s?`${e}${this.api_prefix}/${_e}?upload_id=${s}`:`${e}${this.api_prefix}/${_e}`;a=await this.fetch(f,{method:"POST",body:c,headers:n,credentials:this.options.credentials??"same-origin"})}catch(f){throw Error(N+f.message)}if(!a.ok){let f=await a.text();return{error:`HTTP ${a.status}: ${f}`}}let u=await a.json();u&&r.push(...u)}return{files:r}}var ke={si:{radix:1e3,unit:["b","kb","Mb","Gb","Tb","Pb","Eb","Zb","Yb"]},iec:{radix:1024,unit:["b","Kib","Mib","Gib","Tib","Pib","Eib","Zib","Yib"]},jedec:{radix:1024,unit:["b","Kb","Mb","Gb","Tb","Pb","Eb","Zb","Yb"]}};function qt(e,t=1,s="jedec"){e=Math.abs(e);let{radix:n,unit:i}=ke[s]||ke.jedec,r=0;for(;e>=n;)e/=n,++r;return`${e.toFixed(t)} ${i[r]}`}async function Nt(e,t,s,n){let i=(Array.isArray(e)?e:[e]).map(a=>a.blob),r=i.filter(a=>a.size>(n??1/0));if(r.length)throw Error(`File(s) exceed the maximum allowed size of ${qt(n||1/0)}: ${r.map(a=>`"${a.name}"`).join(", ")}`);return await Promise.all(await this.upload_files(t,i,s).then(async a=>{if(a.error)throw Error(a.error);return a.files?a.files.map((o,l)=>new ie({...e[l],path:o,url:`${t}${this.api_prefix}/file=${o}`})):[]}))}var ie=class{path;url;orig_name;size;blob;is_stream;mime_type;alt_text;b64;meta={_type:"gradio.FileData"};constructor({path:e,url:t,orig_name:s,size:n,blob:i,is_stream:r,mime_type:a,alt_text:o,b64:l}){this.path=e,this.url=t,this.orig_name=s,this.size=n,this.blob=t?void 0:i,this.is_stream=r,this.mime_type=a,this.alt_text=o,this.b64=l}},Ie=class{type;command;meta;fileData;constructor(e,t){this.type="command",this.command=e,this.meta=t}},zt=typeof process<"u"&&process.versions&&process.versions.node;function $e(e,t,s){for(;s.length>1;){let i=s.shift();if(typeof i=="string"||typeof i=="number")e=e[i];else throw Error("Invalid key type")}let n=s.shift();if(typeof n=="string"||typeof n=="number")e[n]=t;else throw Error("Invalid key type")}async function te(e,t=void 0,s=[],n=!1,i=void 0){if(Array.isArray(e)){let r=[];return await Promise.all(e.map(async(a,o)=>{let l=s.slice();l.push(String(o));let c=await te(e[o],n?i?.parameters[o]?.component||void 0:t,l,!1,i);r=r.concat(c)})),r}else{if(globalThis.Buffer&&e instanceof globalThis.Buffer||e instanceof Blob)return[{path:s,blob:e instanceof Blob?e:new Blob([e]),type:t}];if(typeof e=="object"&&e){let r=[];for(let a of Object.keys(e)){let o=[...s,a],l=e[a];r=r.concat(await te(l,void 0,o,!1,i))}return r}}return[]}function Ee(e,t){let s=t?.dependencies?.find(n=>n.id==e)?.queue;return s==null?!t.enable_queue:!s}function Bt(e,t){return new Promise((s,n)=>{let i=new MessageChannel;i.port1.onmessage=(({data:r})=>{i.port1.close(),s(r)}),window.parent.postMessage(e,t,[i.port2])})}function us(e){if(typeof e=="string"){if(e.startsWith("http://")||e.startsWith("https://"))return{path:e,url:e,orig_name:e.split("/").pop()??"unknown",meta:{_type:"gradio.FileData"}};if(zt)return new Ie("upload_file",{path:e,name:e,orig_path:e})}else{if(typeof File<"u"&&e instanceof File)return e;if(globalThis.Buffer&&e instanceof globalThis.Buffer)return new Blob([e]);if(e instanceof Blob)return e}throw Error("Invalid input: must be a URL, File, Blob, or Buffer object.")}function Y(e,t,s,n,i=!1){if(n==="input"&&!i)throw Error("Invalid code path. Cannot skip state inputs for input.");if(n==="output"&&i)return e;let r=[],a=0,o=n==="input"?t.inputs:t.outputs;for(let l=0;l<o.length;l++){let c=o[l];if(s.find(u=>u.id===c)?.type==="state"){if(i)if(e.length===o.length){let u=e[a];r.push(u),a++}else r.push(null);else{a++;continue}continue}else{let u=e[a];r.push(u),a++}}return r}async function Lt(e,t,s){let n=this;await It(n,t);let i=await te(t,void 0,[],!0,s);return(await Promise.all(i.map(async({path:r,blob:a,type:o})=>{if(!a)return{path:r,type:o};let l=await n.upload_files(e,[a]);return{path:r,file_url:l.files&&l.files[0],type:o,name:typeof File<"u"&&a instanceof File?a?.name:void 0}}))).forEach(({path:r,file_url:a,type:o,name:l})=>{o==="Gallery"?$e(t,a,r):a&&$e(t,new ie({path:a,orig_name:l}),r)}),t}async function It(e,t){if(!(e.config?.root||e.config?.root_url))throw Error(Ne);await Ue(e,t)}async function Ue(e,t,s=[]){for(let n in t)t[n]instanceof Ie?await Ut(e,t,n):typeof t[n]=="object"&&t[n]!==null&&await Ue(e,t[n],[...s,n])}async function Ut(e,t,s){let n=t[s],i=e.config?.root||e.config?.root_url;if(!i)throw Error(Ne);try{let r,a;if(typeof process<"u"&&process.versions&&process.versions.node){let u=await fe(()=>import("./__vite-browser-external-Cgmn0awE-BTUFketj.js"),__vite__mapDeps([0,1,2])).then(f=>ge(f.default,1));a=(await fe(()=>import("./__vite-browser-external-Cgmn0awE-BTUFketj.js"),__vite__mapDeps([0,1,2])).then(f=>ge(f.default,1))).resolve(process.cwd(),n.meta.path),r=await u.readFile(a)}else throw Error(kt);let o=new Blob([r],{type:"application/octet-stream"}),l=await e.upload_files(i,[o]),c=l.files&&l.files[0];c&&(t[s]=new ie({path:c,orig_name:n.meta.name||""}))}catch(r){console.error($t,r)}}async function Ft(e,t,s){let n={"Content-Type":"application/json"};this.options.token&&(n.Authorization=`Bearer ${this.options.token}`);try{var i=await this.fetch(e,{method:"POST",body:JSON.stringify(t),headers:{...n,...s},credentials:this.options.credentials??"same-origin"})}catch{return[{error:N},500]}let r,a;try{r=await i.json(),a=i.status}catch(o){r={error:`Could not parse server response: ${o}`},a=500}return[r,a]}async function Rt(e,t={}){let s=!1,n=!1;if(!this.config)throw Error("Could not resolve app config");if(typeof e=="number")this.config.dependencies.find(a=>a.id==e);else{let a=e.replace(/^\//,"");this.config.dependencies.find(o=>o.id==this.api_map[a])}let i=this.submit(e,t,null,null,!0),r;for await(let a of i){if(a.type==="data"&&(s=!0,r=a,n))return r;if(a.type==="status"){if(a.stage==="error"){let{message:o,...l}=a,c=Error((typeof o=="string"?o:o&&JSON.stringify(o))||"An unknown error occurred while making a prediction.");throw Object.assign(c,l),c}if(a.stage==="complete"&&(n=!0,s))return r}}return r}async function M(e,t,s){let n=t==="subdomain"?`https://huggingface.co/api/spaces/by-subdomain/${e}`:`https://huggingface.co/api/spaces/${e}`,i,r;try{if(i=await fetch(n),r=i.status,r!==200)throw Error();i=await i.json()}catch{s({status:"error",load_status:"error",message:gt,detail:"NOT_FOUND"});return}if(!i||r!==200)return;let{runtime:{stage:a},id:o}=i;switch(a){case"STOPPED":case"SLEEPING":s({status:"sleeping",load_status:"pending",message:"Space is asleep. Waking it up...",detail:a}),setTimeout(()=>{M(e,t,s)},1e3);break;case"PAUSED":s({status:"paused",load_status:"error",message:"This space has been paused by the author. If you would like to try this demo, consider duplicating the space.",detail:a,discussions_enabled:await xe(o)});break;case"RUNNING":case"RUNNING_BUILDING":s({status:"running",load_status:"complete",message:"Space is running.",detail:a});break;case"BUILDING":s({status:"building",load_status:"pending",message:"Space is building...",detail:a}),setTimeout(()=>{M(e,t,s)},1e3);break;case"APP_STARTING":s({status:"starting",load_status:"pending",message:"Space is starting...",detail:a}),setTimeout(()=>{M(e,t,s)},1e3);break;default:s({status:"space_error",load_status:"error",message:"This space is experiencing an issue.",detail:a,discussions_enabled:await xe(o)});break}}var Fe=async(e,t)=>{let s=0;return new Promise(n=>{M(e,se.test(e)?"space_name":"subdomain",i=>{t(i),i.status==="running"||i.status==="error"||i.status==="paused"||i.status==="space_error"?n():(i.status==="sleeping"||i.status==="building")&&(s<12?(s++,setTimeout(()=>{Fe(e,t).then(n)},5e3)):n())})})},Gt=/^(?=[^]*\b[dD]iscussions{0,1}\b)(?=[^]*\b[dD]isabled\b)[^]*$/;async function xe(e){try{let t=await fetch(`https://huggingface.co/api/spaces/${e}/discussions`,{method:"HEAD"}),s=t.headers.get("x-error-message");return!(!t.ok||s&&Gt.test(s))}catch{return!1}}async function Mt(e,t){let s={};t&&(s.Authorization=`Bearer ${t}`);try{let n=await fetch(`https://huggingface.co/api/spaces/${e}/${ct}`,{headers:s});if(n.status!==200)throw Error("Space hardware could not be obtained.");let{hardware:i}=await n.json();return i.current}catch(n){throw Error(n.message)}}async function Ht(e,t,s){let n={};s&&(n.Authorization=`Bearer ${s}`);let i={seconds:t};try{let r=await fetch(`https://huggingface.co/api/spaces/${e}/${lt}`,{method:"POST",headers:{"Content-Type":"application/json",...n},body:JSON.stringify(i)});if(r.status!==200)throw Error("Could not set sleep timeout on duplicated Space. Please visit *ADD HF LINK TO SETTINGS* to set a timeout manually to reduce billing charges.");return await r.json()}catch(r){throw Error(r.message)}}var Se=["cpu-basic","cpu-upgrade","cpu-xl","t4-small","t4-medium","a10g-small","a10g-large","a10g-largex2","a10g-largex4","a100-large","zero-a10g","h100","h100x8"];async function Jt(e,t){ze(t);let{token:s,private:n,hardware:i,timeout:r,auth:a}=t;if(i&&!Se.includes(i))throw Error(`Invalid hardware type provided. Valid types are: ${Se.map(w=>`"${w}"`).join(",")}.`);let{http_protocol:o,host:l}=await ne(e,s),c=null;if(a){let w=await Be(o,l,a,fetch,void 0,t.credentials);w&&(c=Le(w))}let u={Authorization:`Bearer ${s}`,"Content-Type":"application/json",...c?{Cookie:c.join("; ")}:{}},f=(await(await fetch("https://huggingface.co/api/whoami-v2",{headers:u})).json()).name,v=e.split("/")[1],h={repository:`${f}/${v}`};n&&(h.private=!0);let $;try{i||($=await Mt(e,s))}catch(w){throw Error(G+w.message)}h.hardware=i||$||"cpu-basic";try{let w=await fetch(`https://huggingface.co/api/spaces/${e}/duplicate`,{method:"POST",headers:u,body:JSON.stringify(h)});if(w.status===409)try{return await Te.connect(`${f}/${v}`,t)}catch(H){throw console.error("Failed to connect Client instance:",H),H}else if(w.status!==200)throw Error(w.statusText);let z=await w.json();return await Ht(`${f}/${v}`,r||300,s),await Te.connect(Wt(z.url),t)}catch(w){throw Error(w)}}function Wt(e){let t=e.match(/https:\/\/huggingface.co\/spaces\/([^/]+\/[^/]+)/);if(t)return t[1]}var je="supports-zerogpu-headers",Oe=!1;function Vt(){return typeof window<"u"&&typeof document<"u"&&typeof window.addEventListener=="function"}function Re(e){return e.includes(".dev.")?`https://moon-${e.split(".")[1]}.dev.spaces.huggingface.tech`:e.endsWith(".hf.space")?"https://huggingface.co":null}function Yt(){if(!Vt()||Oe)return;window.addEventListener("message",t=>{t.data===je&&(window.supports_zerogpu_headers=!0)}),Oe=!0;let e=Re(window.location.hostname);e&&window.parent!==window&&window.parent.postMessage(je,e)}var Zt=class extends TransformStream{#e="";constructor(e={allowCR:!1}){super({transform:(t,s)=>{for(t=this.#e+t;;){let n=t.indexOf(`
|
| 3 |
`),i=e.allowCR?t.indexOf("\r"):-1;if(i!==-1&&i!==t.length-1&&(n===-1||n-1>i)){s.enqueue(t.slice(0,i)),t=t.slice(i+1);continue}if(n===-1)break;let r=t[n-1]==="\r"?n-1:n;s.enqueue(t.slice(0,r)),t=t.slice(n+1)}this.#e=t},flush:t=>{if(this.#e==="")return;let s=e.allowCR&&this.#e.endsWith("\r")?this.#e.slice(0,-1):this.#e;t.enqueue(s)}})}};function Kt(e){let t=new TextDecoderStream,s=new Zt({allowCR:!0});return e.pipeThrough(t).pipeThrough(s)}function Qt(e){let t=/[:]\s*/.exec(e),s=t&&t.index;if(s)return[e.substring(0,s),e.substring(s+t[0].length)]}function Pe(e,t,s){e.get(t)||e.set(t,s)}async function*Xt(e,t){if(!e.body)return;let s=Kt(e.body),n,i=s.getReader(),r;for(;;){if(t&&t.aborted)return i.cancel();if(n=await i.read(),n.done)return;if(!n.value){r&&(yield r),r=void 0;continue}let[a,o]=Qt(n.value)||[];a==="data"?(r||={},r[a]=r[a]?r[a]+`
|
| 4 |
`+o:o):a==="event"?(r||={},r[a]=o):a==="id"?(r||={},r[a]=String(+o)===o?+o:o):a==="retry"&&(r||={},r[a]=+o||void 0)}}async function es(e,t){let s=new Request(e,t);Pe(s.headers,"Accept","text/event-stream"),Pe(s.headers,"Content-Type","application/json");let n=await fetch(s);if(!n.ok)throw n;return Xt(n,s.signal)}async function ts(){let{event_callbacks:e,unclosed_events:t,pending_stream_messages:s,stream_status:n,config:i,jwt:r}=this,a=this;if(!i)throw Error("Could not resolve app config");n.open=!0;let o=null,l=new URLSearchParams({session_hash:this.session_hash}).toString(),c=new URL(`${i.root}${this.api_prefix}/${Ae}?${l}`);if(r&&c.searchParams.set("__sign",r),o=this.stream(c),!o){console.warn("Cannot connect to SSE endpoint: "+c.toString());return}o.onmessage=async function(u){let f=JSON.parse(u.data);if(f.msg==="close_stream"){re(n,a.abort_controller);return}let v=f.event_id;if(!v)await Promise.all(Object.keys(e).map(h=>e[h](f)));else if(e[v]&&i){f.msg==="process_completed"&&["sse","sse_v1","sse_v2","sse_v2.1","sse_v3"].includes(i.protocol)&&t.delete(v);let h=e[v];typeof window<"u"&&typeof document<"u"&&document.visibilityState!=="hidden"?setTimeout(h,0,f):h(f)}else s[v]||(s[v]=[]),s[v].push(f)},o.onerror=async function(u){console.error(u),await Promise.all(Object.keys(e).map(f=>e[f]({msg:"broken_connection",message:N})))}}function re(e,t){e&&(e.open=!1,t?.abort())}function ss(e,t,s){e[t]?s.data.forEach((n,i)=>{let r=ns(i<e[t].length?e[t][i]:null,n);e[t][i]=r,s.data[i]=r}):(e[t]=[],s.data.forEach((n,i)=>{e[t][i]=n}))}function ns(e,t){return t.forEach(([s,n,i])=>{e=is(e,n,s,i)}),e}function is(e,t,s,n){if(t.length===0){if(s==="replace")return n;if(s==="append")return e+n;throw Error(`Unsupported action: ${s}`)}let i=e;for(let a=0;a<t.length-1;a++)i=i[t[a]];let r=t[t.length-1];switch(s){case"replace":i[r]=n;break;case"append":i[r]+=n;break;case"add":Array.isArray(i)?i.splice(Number(r),0,n):i[r]=n;break;case"delete":Array.isArray(i)?i.splice(Number(r),1):delete i[r];break;default:throw Error(`Unknown action: ${s}`)}return e}function rs(e,t={}){let s={close:()=>{console.warn("Method not implemented.")},onerror:null,onmessage:null,onopen:null,readyState:0,url:e.toString(),withCredentials:!1,CONNECTING:0,OPEN:1,CLOSED:2,addEventListener:()=>{throw Error("Method not implemented.")},dispatchEvent:()=>{throw Error("Method not implemented.")},removeEventListener:()=>{throw Error("Method not implemented.")}};return es(e,t).then(async n=>{s.readyState=s.OPEN;try{for await(let i of n)s.onmessage&&s.onmessage(i);s.readyState=s.CLOSED}catch(i){s.onerror&&s.onerror(i),s.readyState=s.CLOSED}}).catch(n=>{console.error(n),s.onerror&&s.onerror(n),s.readyState=s.CLOSED}),s}function as(e,t={},s,n,i,r){try{let m=function(p){(i||He[p.type])&&Ye(p)},E=function(){for(pe=!0;R.length>0;)R.shift()({value:void 0,done:!0})},ue=function(p){R.length>0?R.shift()(p):Q.push(p)},Ve=function(p){ue(os(p)),E()},Ye=function(p){ue({value:p,done:!1})},de=function(){return Q.length>0?Promise.resolve(Q.shift()):pe?Promise.resolve({value:void 0,done:!0}):new Promise(p=>R.push(p))},{token:a}=this.options,{fetch:o,app_reference:l,config:c,session_hash:u,api_info:f,api_map:v,stream_status:h,pending_stream_messages:$,pending_diff_streams:w,event_callbacks:z,unclosed_events:H,post_data:Z,options:B,api_prefix:D}=this,ae=r||{"x-gradio-user":"api"},Ge=this;if(!f)throw Error(bt);if(!c)throw Error("Could not resolve app config");let{fn_index:d,endpoint_info:K,dependency:I}=cs(f,e,v,c),Me=Dt(t,K),L,A=c.protocol??"ws";if(A==="ws")throw Error(vt);let U="",g=typeof e=="number"?"/predict":e,J,k=null,C=!1,oe={},F=typeof window<"u"&&typeof document<"u"?new URLSearchParams(window.location.search).toString():"",He=B?.events?.reduce((p,P)=>(p[P]=!0,p),{})||{};async function Je(){let p={},P={};p={event_id:k},P={event_id:k,session_hash:u,fn_index:d};try{if(!c)throw Error("Could not resolve app config");"event_id"in P&&await o(`${c.root}${D}/${ht}`,{headers:{"Content-Type":"application/json"},method:"POST",body:JSON.stringify(P)}),await o(`${c.root}${D}/${dt}`,{headers:{"Content-Type":"application/json"},method:"POST",body:JSON.stringify(p)})}catch{console.warn("The `/reset` endpoint could not be called. Subsequent endpoint results may be unreliable.")}}let We=async p=>{await this._resolve_heartbeat(p)};async function ce(p){if(!c)return;let P=p.render_id;c.components=[...c.components.filter(_=>_.props.rendered_in!==P),...p.components],c.dependencies=[...c.dependencies.filter(_=>_.rendered_in!==P),...p.dependencies];let y=c.components.some(_=>_.type==="state"),q=c.dependencies.some(_=>_.targets.some(b=>b[1]==="unload"));c.connect_heartbeat=y||q,await We(c),m({type:"render",data:p,endpoint:g,fn_index:d})}let le=this.handle_blob(c.root,Me,K).then(async p=>{if(J={data:Y(p,I,c.components,"input",!0)||[],event_data:s,fn_index:d,trigger_id:n,...B.oauth_token&&K?.oauth_token?{oauth_token:B.oauth_token}:{}},Ee(d,c))m({type:"status",endpoint:g,stage:"pending",queue:!1,fn_index:d,time:new Date}),Z(`${c.root}${D}/run${g.startsWith("/")?g:`/${g}`}${F?"?"+F:""}`,{...J,session_hash:u},ae).then(async([y,q])=>{let _=y.data;if(q==200)m({type:"data",endpoint:g,fn_index:d,data:Y(_,I,c.components,"output",B.with_null_state),time:new Date,event_data:s,trigger_id:n}),y.render_config&&await ce(y.render_config),m({type:"status",endpoint:g,fn_index:d,stage:"complete",eta:y.average_duration,queue:!1,time:new Date});else{let b=y?.error===N;m({type:"status",stage:"error",endpoint:g,fn_index:d,message:y.error,broken:b,queue:!1,time:new Date})}}).catch(y=>{m({type:"status",stage:"error",message:y.message,endpoint:g,fn_index:d,queue:!1,time:new Date})});else if(A=="sse"){m({type:"status",stage:"pending",queue:!0,endpoint:g,fn_index:d,time:new Date});var P=new URLSearchParams({fn_index:d.toString(),session_hash:u}).toString();let y=new URL(`${c.root}${D}/${Ae}?${F?F+"&":""}${P}`);if(this.jwt&&y.searchParams.set("__sign",this.jwt),L=this.stream(y),!L)return Promise.reject(Error("Cannot connect to SSE endpoint: "+y.toString()));L.onmessage=async function(q){let{type:_,status:b,data:x}=ve(JSON.parse(q.data),oe[d]);if(_==="update"&&b&&!C)m({type:"status",endpoint:g,fn_index:d,time:new Date,...b}),b.stage==="error"&&(L?.close(),E());else if(_==="data"){let[W,S]=await Z(`${c.root}${D}/queue/data`,{...J,session_hash:u,event_id:k});S!==200&&(m({type:"status",stage:"error",message:N,queue:!0,endpoint:g,fn_index:d,time:new Date}),L?.close(),E())}else _==="complete"?C=b:_==="log"?m({type:"log",title:x.title,log:x.log,level:x.level,endpoint:g,duration:x.duration,visible:x.visible,fn_index:d}):(_==="generating"||_==="streaming")&&m({type:"status",time:new Date,...b,stage:b?.stage,queue:!0,endpoint:g,fn_index:d});x&&(m({type:"data",time:new Date,data:Y(x.data,I,c.components,"output",B.with_null_state),endpoint:g,fn_index:d,event_data:s,trigger_id:n}),C&&(m({type:"status",time:new Date,...C,stage:b?.stage,queue:!0,endpoint:g,fn_index:d}),L?.close(),E()))}}else if(A=="sse_v1"||A=="sse_v2"||A=="sse_v2.1"||A=="sse_v3"){m({type:"status",stage:"pending",queue:!0,endpoint:g,fn_index:d,time:new Date});let y="";typeof window<"u"&&typeof document<"u"&&(y=window?.location?.hostname);let q=Re(y);return(typeof window<"u"&&typeof document<"u"&&window.parent!=window&&q&&window.supports_zerogpu_headers?Bt("zerogpu-headers",q):Promise.resolve(null)).then(_=>{let b={...ae,..._||{}};return Z(`${c.root}${D}/${rt}?${F}`,{...J,session_hash:u},b)}).then(async([_,b])=>{if(_.event_id&&(U=_.event_id),b===503)m({type:"status",stage:"error",message:Ce,queue:!0,endpoint:g,fn_index:d,time:new Date,visible:!0}),E();else if(b===422)m({type:"status",stage:"error",message:_.detail,queue:!0,endpoint:g,fn_index:d,code:"validation_error",time:new Date,visible:!0}),E();else if(b!==200){let x=_?.error===N;m({type:"status",stage:"error",broken:x,message:x?N:_.detail||_.error,queue:!0,endpoint:g,fn_index:d,time:new Date,visible:!0}),E()}else{k=_.event_id,U=k;let x=async function(W){try{let{type:S,status:j,data:O,original_msg:Ze}=ve(W,oe[d]);if(S=="heartbeat")return;if(S==="update"&&j&&!C)m({type:"status",endpoint:g,fn_index:d,time:new Date,original_msg:Ze,...j});else if(S==="complete")C=j;else if(S=="unexpected_error"||S=="broken_connection"){console.error("Unexpected error",j?.message);let Ke=S==="broken_connection";m({type:"status",stage:"error",message:j?.message||"An Unexpected Error Occurred!",queue:!0,endpoint:g,broken:Ke,session_not_found:j?.session_not_found,fn_index:d,time:new Date})}else if(S==="log"){m({type:"log",title:O.title,log:O.log,level:O.level,endpoint:g,duration:O.duration,visible:O.visible,fn_index:d});return}else(S==="generating"||S==="streaming")&&(m({type:"status",time:new Date,...j,stage:j?.stage,queue:!0,endpoint:g,fn_index:d}),O&&I.connection!=="stream"&&["sse_v2","sse_v2.1","sse_v3"].includes(A)&&ss(w,k,O));O&&(m({type:"data",time:new Date,data:Y(O.data,I,c.components,"output",B.with_null_state),endpoint:g,fn_index:d}),O.render_config&&await ce(O.render_config),C&&(m({type:"status",time:new Date,...C,stage:j?.stage,queue:!0,endpoint:g,fn_index:d}),E())),(j?.stage==="complete"||j?.stage==="error")&&(z[k]&&delete z[k],k in w&&delete w[k],E())}catch(S){console.error("Unexpected client exception",S),m({type:"status",stage:"error",message:"An Unexpected Error Occurred!",queue:!0,endpoint:g,fn_index:d,time:new Date}),["sse_v2","sse_v2.1","sse_v3"].includes(A)&&(re(h,Ge.abort_controller),h.open=!1,E())}};k in $&&($[k].forEach(W=>x(W)),delete $[k]),z[k]=x,H.add(k),h.open||await this.open_stream()}})}});le.catch(p=>{m({type:"status",stage:"error",message:p instanceof Error?p.message:String(p),queue:!Ee(d,c),endpoint:g,fn_index:d,time:new Date}),E()});let pe=!1,Q=[],R=[],he={[Symbol.asyncIterator]:()=>he,next:de,throw:async p=>(Ve(p),de()),return:async()=>(E(),{value:void 0,done:!0}),cancel:Je,send_chunk:p=>{this.post_data(`${c.root}${D}/stream/${U}`,{...p,session_hash:this.session_hash})},close_stream:()=>{this.post_data(`${c.root}${D}/stream/${U}/close`,{}),E()},event_id:()=>U,wait_for_id:async()=>(await le,k)};return he}catch(a){throw console.error("Submit function encountered an error:",a),a}}function os(e){return{then:(t,s)=>s(e)}}function cs(e,t,s,n){let i,r,a;if(typeof t=="number")i=t,r=e.unnamed_endpoints[i],a=n.dependencies.find(o=>o.id==t);else{let o=t.replace(/^\//,"");i=s[o],r=e.named_endpoints[t.trim()]??e.named_endpoints[`/${o}`],a=n.dependencies.find(l=>l.id==s[o])}if(typeof i!="number"||!a){let o=n.dependencies.filter(l=>l.api_name).map(l=>`"/${l.api_name}"`).join(", ");throw Error(`No endpoint matching ${JSON.stringify(t)} was found. `+(o?`Valid named endpoints are: ${o}. `:"This app exposes no named endpoints. ")+"An fn_index (number) of an existing dependency can also be used.")}return{fn_index:i,endpoint_info:r,dependency:a}}var Te=class{app_reference;options;deep_link=null;config;api_prefix="";api_info;api_map={};session_hash=Math.random().toString(36).substring(2);jwt=!1;last_status={};cookies=null;stream_status={open:!1};closed=!1;pending_stream_messages={};pending_diff_streams={};event_callbacks={};unclosed_events=new Set;heartbeat_event=null;abort_controller=null;stream_instance=null;current_payload;get_url_config(e=null){if(!this.config)throw Error(T);e===null&&(e=window.location.href);let t=r=>r.replace(/^\/+|\/+$/g,""),s=t(new URL(this.config.root).pathname),n=t(new URL(e).pathname),i;return i=n.startsWith(s)?t(n.substring(s.length)):"",this.get_page_config(i)}get_page_config(e){if(!this.config)throw Error(T);let t=this.config;return e in t.page||(e=""),{...t,current_page:e,layout:t.page[e].layout,components:t.components.filter(s=>t.page[e].components.includes(s.id)),dependencies:this.config.dependencies.filter(s=>t.page[e].dependencies.includes(s.id))}}fetch(e,t){let s=new Headers(t?.headers||{});return this&&this.cookies&&s.append("Cookie",this.cookies),this&&this.options.headers&&new Headers(this.options.headers).forEach((n,i)=>{s.append(i,n)}),fetch(e,{...t,headers:s})}stream(e){let t=new Headers;return this&&this.cookies&&t.append("Cookie",this.cookies),this&&this.options.headers&&new Headers(this.options.headers).forEach((s,n)=>{t.append(n,s)}),this&&this.options.token&&t.append("Authorization",`Bearer ${this.options.token}`),this.abort_controller=new AbortController,this.stream_instance=rs(e.toString(),{credentials:this.options.credentials??"same-origin",headers:t,signal:this.abort_controller.signal}),this.stream_instance}view_api;upload_files;upload;handle_blob;post_data;submit;predict;open_stream;resolve_config;resolve_cookies;constructor(e,t={events:["data"]}){this.app_reference=e,this.deep_link=t.query_params?.deep_link||null,t.events||=["data"],ze(t),this.options=t,this.current_payload={},t.cookies&&(this.cookies=t.cookies),this.view_api=At.bind(this),this.upload_files=Ct.bind(this),this.handle_blob=Lt.bind(this),this.post_data=Ft.bind(this),this.submit=as.bind(this),this.predict=Rt.bind(this),this.open_stream=ts.bind(this),this.resolve_config=xt.bind(this),this.resolve_cookies=St.bind(this),this.upload=Nt.bind(this),this.fetch=this.fetch.bind(this),this.handle_space_success=this.handle_space_success.bind(this),this.stream=this.stream.bind(this)}async init(){Yt(),this.options.auth&&await this.resolve_cookies(),await this._resolve_config().then(e=>e?.config&&this._resolve_heartbeat(e.config));try{this.api_info=await this.view_api()}catch(e){console.error(e.message)}this.api_map=ye(this.config?.dependencies||[])}async _resolve_heartbeat(e){if(e&&(this.config=e,this.api_prefix=e.api_prefix||"",this.config&&this.config.connect_heartbeat&&this.config.space_id&&this.options.token&&(this.jwt=await we(this.config.space_id,this.options.token,this.cookies))),e.space_id&&this.options.token&&(this.jwt=await we(e.space_id,this.options.token)),this.config&&this.config.connect_heartbeat){let t=new URL(`${this.config.root}${this.api_prefix}/${pt}/${this.session_hash}`);this.jwt&&t.searchParams.set("__sign",this.jwt),this.heartbeat_event||=this.stream(t)}}static async connect(e,t={events:["data"]}){let s=new this(e,t);return t.session_hash&&(s.session_hash=t.session_hash),await s.init(),s}async reconnect(){let e=new URL(`${this.config.root}${this.api_prefix}/${ft}`),t;try{let s=await this.fetch(e);if(!s.ok)throw Error();t=(await s.json()).app_id}catch{return"broken"}return t===this.config.app_id?"connected":"changed"}close(){this.closed=!0,re(this.stream_status,this.abort_controller)}async refresh(){if(!this.config)throw Error(T);let e=await this.resolve_config(this.config.root,!1);if(!e)throw Error(T);this.config=e,this.api_prefix=e.api_prefix||"",this.api_map=ye(e.dependencies||[]);try{this.api_info=await this.view_api()}catch(t){console.error(me+t.message)}return this.get_url_config()}set_current_payload(e){this.current_payload=e}static async duplicate(e,t={events:["data"]}){return Jt(e,t)}async _resolve_config(){let{http_protocol:e,host:t,space_id:s}=await ne(this.app_reference,this.options.token),{status_callback:n}=this.options;s&&n&&await Fe(s,n);let i;try{let r=`${e}//${t}`;if(i=await this.resolve_config(r),!i)throw Error(T);return this.config_success(i)}catch(r){if(s&&n)M(s,se.test(s)?"space_name":"subdomain",this.handle_space_success);else throw n&&n({status:"error",message:"Could not load this space.",load_status:"error",detail:"NOT_FOUND"}),r instanceof Error?r:Error(String(r))}}async config_success(e){if(this.config=e,this.api_prefix=e.api_prefix||"",this.config.auth_required)return this.prepare_return_obj();try{this.api_info=await this.view_api()}catch(t){console.error(me+t.message)}return this.prepare_return_obj()}async handle_space_success(e){if(!this)throw Error(T);let{status_callback:t}=this.options;if(t&&t(e),e.status==="running")try{if(this.config=await this._resolve_config(),this.api_prefix=this?.config?.api_prefix||"",!this.config)throw Error(T);return await this.config_success(this.config)}catch(s){throw t&&t({status:"error",message:"Could not load this space.",load_status:"error",detail:"NOT_FOUND"}),s}}async component_server(e,t,s){if(!this.config)throw Error(T);let n={},{token:i}=this.options,{session_hash:r}=this;i&&(n.Authorization=`Bearer ${this.options.token}`);let a,o=this.config.components.find(c=>c.id===e);a=o?.props?.root_url?o.props.root_url:this.config.root;let l;if(typeof s=="object"&&s&&"binary"in s){let c=s;l=new FormData;for(let u in c.data)u!=="binary"&&l.append(u,c.data[u]);l.set("component_id",e.toString()),l.set("fn_name",t),l.set("session_hash",r)}else l=JSON.stringify({data:s,component_id:e,fn_name:t,session_hash:r}),n["Content-Type"]="application/json";i&&(n.Authorization=`Bearer ${i}`);try{let c=await this.fetch(`${a}${this.api_prefix}/${ut}/`,{method:"POST",body:l,headers:n,credentials:this.options.credentials??"same-origin"});if(!c.ok)throw Error("Could not connect to component server: "+c.statusText);return await c.json()}catch(c){console.warn(c)}}set_cookies(e){this.cookies=Le(e).join("; ")}prepare_return_obj(){return{config:this.config,predict:this.predict,submit:this.submit,view_api:this.view_api,component_server:this.component_server}}};export{Te as Client,ie as FileData,us as handle_file,Rt as predict,as as submit,ps as t,Nt as upload,Ct as upload_files};
|
|
|
|
| 1 |
+
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/__vite-browser-external-Cgmn0awE-17s0Hkgg.js","assets/index-Dxjs8B8O.js","assets/index-DZbZsc56.css"])))=>i.map(i=>d[i]);
|
| 2 |
+
import{_ as fe}from"./index-Dxjs8B8O.js";var Qe=Object.create,De=Object.defineProperty,Xe=Object.getOwnPropertyDescriptor,et=Object.getOwnPropertyNames,tt=Object.getPrototypeOf,st=Object.prototype.hasOwnProperty,ps=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports),nt=(e,t,s,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(var i=et(t),r=0,a=i.length,o;r<a;r++)o=i[r],!st.call(e,o)&&o!==s&&De(e,o,{get:(l=>t[l]).bind(null,o),enumerable:!(n=Xe(t,o))||n.enumerable});return e},ge=(e,t,s)=>(s=e==null?{}:Qe(tt(e)),nt(De(s,"default",{value:e,enumerable:!0}),e)),it="host",Ae="queue/data",rt="queue/join",_e="upload",at="login",V="config",ot="info",ct="runtime",lt="sleeptime",pt="heartbeat",ut="component_server",dt="reset",ht="cancel",ft="app_id",Ce="This application is currently busy. Please try again. ",N="Connection errored out. ",T="Could not resolve app config. ",gt="Could not get space status. ",me="Could not get API info. ",G="Space metadata could not be loaded. ",_t="Invalid URL. A full URL path is required.",mt="Not authorized to access this space. ",qe="Invalid credentials. Could not login. ",wt="Could not access this app (received a 401 response). If it is a private Hugging Face Space, pass a valid Hugging Face token to the `token` option of `Client.connect`. You can generate a token at https://huggingface.co/settings/tokens.",yt=(e,t)=>`Space "${e}" could not be accessed (received a ${t} response from the Hugging Face API). Check that the Space name is spelled correctly and that the Space exists. If the Space is private, pass a valid Hugging Face token to the \`token\` option of \`Client.connect\`. You can generate a token at https://huggingface.co/settings/tokens.`,bt="No API information is available for this app. This can happen when the app's `/info` endpoint cannot be reached, or when the app is running a legacy version of Gradio that is not supported by this client. ",vt="This app appears to be running a legacy version of Gradio (3.x or earlier) that communicates over WebSockets, which is not supported by this version of @gradio/client. Please upgrade the app to a newer version of Gradio, or connect to it with @gradio/client version 0.x.",kt="File system access is only available in Node.js environments",Ne="Root URL not found in client config",$t="Error uploading file";async function we(e,t,s){try{return(await(await fetch(`https://huggingface.co/api/spaces/${e}/jwt`,{headers:{Authorization:`Bearer ${t}`,...s?{Cookie:s}:{}}})).json()).token||!1}catch{return!1}}function ze(e){e.hf_token&&!e.token&&(e.token=e.hf_token,console.warn("The `hf_token` option has been renamed to `token`. Support for `hf_token` will be removed in a future version of @gradio/client."))}function ye(e){let t={};return e.forEach(({api_name:s,id:n})=>{s&&(t[s]=n)}),t}function Et(e,t){let s=new URL(e,t),n=new URL(t);return s.hostname===n.hostname?(s.protocol=n.protocol,s.host=n.host,s.toString().replace(/\/$/,"")):e}async function xt(e,t=!0){let s=this.options.token?{Authorization:`Bearer ${this.options.token}`}:{};if(typeof window<"u"&&window.gradio_config&&location.origin!=="http://localhost:9876"){if(t&&window.gradio_config.current_page&&(e=e.substring(0,e.lastIndexOf("/"))),window.gradio_config.dev_mode||typeof window<"u"&&window?.BUILD_MODE==="dev"){let i=ee(e,this.deep_link?V+"?deep_link="+this.deep_link:V),r=await be(await this.fetch(i,{headers:s,credentials:this.options.credentials??"same-origin"}),!!this.options.auth);r.root=e||r.root,window.gradio_config={...r,current_page:window.gradio_config.current_page}}let n={...window.gradio_config};return n.root=Et(n.root,location.href),n}else if(e){let n=ee(e,this.deep_link?V+"?deep_link="+this.deep_link:V),i=await be(await this.fetch(n,{headers:s,credentials:this.options.credentials??"same-origin"}),!!this.options.auth);return i.root||=e,i}throw Error(T)}async function be(e,t){if(e?.status===401&&!t){let s=null;try{s=await e.json()}catch{throw Error(wt)}let n=s?.detail?.auth_message;throw Error(n||"Login credentials are required to access this space.")}else if(e?.status===401&&t)throw Error(qe);if(e?.status===200){let s=await e.json();return s.dependencies?.forEach((n,i)=>{n.id===void 0&&(n.id=i)}),s}else if(e?.status===401)throw Error(mt);throw Error(`${T}(received status ${e?.status} when fetching the app config)`)}async function St(){let{http_protocol:e,host:t}=await ne(this.app_reference,this.options.token);try{if(this.options.auth){let s=await Be(e,t,this.options.auth,this.fetch,this.options.token,this.options.credentials);s&&this.set_cookies(s)}}catch(s){throw Error(s.message)}}async function Be(e,t,s,n,i,r){let a=new FormData;a.append("username",s?.[0]),a.append("password",s?.[1]);let o={};i&&(o.Authorization=`Bearer ${i}`);let l=await n(`${e}//${t}/${at}`,{headers:o,method:"POST",body:a,credentials:r??"same-origin"});if(l.status===200)return l.headers.get("set-cookie");throw l.status===401?Error(qe):Error(G)}function X(e){if(e.startsWith("http")){let{protocol:t,host:s,pathname:n}=new URL(e);return{ws_protocol:t==="https:"?"wss":"ws",http_protocol:t,host:s+(n==="/"?"":n)}}return{ws_protocol:"wss",http_protocol:"https:",host:new URL(e).host}}var Le=e=>{let t=[];return e.split(/,(?=\s*[^\s=;]+=[^\s=;]+)/).forEach(s=>{let[n,i]=s.split(";")[0].split("=");n&&i&&t.push(`${n.trim()}=${i.trim()}`)}),t},se=/^[a-zA-Z0-9_\-\.]+\/[a-zA-Z0-9_\-\.]+$/,jt=/.*hf\.space\/{0,1}.*$/;async function ne(e,t){let s={};t&&(s.Authorization=`Bearer ${t}`);let n=e.trim().replace(/\/$/,"");if(se.test(n)){let i;try{i=await fetch(`https://huggingface.co/api/spaces/${n}/${it}`,{headers:s})}catch{throw Error(G)}if(i.status===401||i.status===404)throw Error(yt(n,i.status));let r;try{r=(await i.json()).host}catch{throw Error(G)}if(!r)throw Error(G);return{space_id:n,...X(r)}}if(jt.test(n)){let{ws_protocol:i,http_protocol:r,host:a}=X(n);return{space_id:a.split("/")[0].replace(".hf.space",""),ws_protocol:i,http_protocol:r,host:a}}return{space_id:!1,...X(n)}}var ee=(...e)=>{try{return e.reduce((t,s)=>(t=t.replace(/\/+$/,""),s=s.replace(/^\/+/,""),new URL(s,t+"/").toString()))}catch{throw Error(_t)}};function Ot(e,t,s){let n={named_endpoints:{},unnamed_endpoints:{}};return Object.keys(e).forEach(i=>{(i==="named_endpoints"||i==="unnamed_endpoints")&&(n[i]={},Object.entries(e[i]).forEach(([r,a])=>{let o=a?.parameters??[],l=a?.returns??[],c=t.dependencies.find(h=>h.api_name===r||h.api_name===r.replace("/",""))?.id||s[r.replace("/","")]||-1,u=c===-1?void 0:t.dependencies.find(h=>h.id==c),f=c===-1?{generator:!1,cancel:!1}:u?.types;if(u&&Array.isArray(u.inputs)&&u.inputs.length!==o.length){let h=u.inputs.map($=>t.components.find(w=>w.id===$)?.type);try{h.forEach(($,w)=>{$==="state"&&o.splice(w,0,{component:"state",example:null,parameter_default:null,parameter_has_default:!0,parameter_name:null,hidden:!0})})}catch($){console.error($)}}let v=(h,$,w,z)=>({...h,description:Tt(h?.type,w),type:Pt(h?.type,$,w,z)||""});n[i][r]={parameters:o.map(h=>v(h,h?.component,h?.serializer,"parameter")),returns:l.map(h=>v(h,h?.component,h?.serializer,"return")),type:f,...a?.oauth_token?{oauth_token:a.oauth_token}:{}}}))}),n}function Pt(e,t,s,n){if(t==="Api")return e.type;switch(e?.type){case"string":return"string";case"boolean":return"boolean";case"number":return"number"}if(s==="JSONSerializable"||s==="StringSerializable")return"any";if(s==="ListStringSerializable")return"string[]";if(t==="Image")return n==="parameter"?"Blob | File | Buffer":"string";if(s==="FileSerializable")return e?.type==="array"?n==="parameter"?"(Blob | File | Buffer)[]":"{ name: string; data: string; size?: number; is_file?: boolean; orig_name?: string}[]":n==="parameter"?"Blob | File | Buffer":"{ name: string; data: string; size?: number; is_file?: boolean; orig_name?: string}";if(s==="GallerySerializable")return n==="parameter"?"[(Blob | File | Buffer), (string | null)][]":"[{ name: string; data: string; size?: number; is_file?: boolean; orig_name?: string}, (string | null))][]"}function Tt(e,t){return t==="GallerySerializable"?"array of [file, label] tuples":t==="ListStringSerializable"?"array of strings":t==="FileSerializable"?"array of files or single file":e?.description}function ve(e,t){switch(e.msg){case"send_data":return{type:"data"};case"send_hash":return{type:"hash"};case"queue_full":return{type:"update",status:{queue:!0,message:Ce,stage:"error",code:e.code,success:e.success}};case"heartbeat":return{type:"heartbeat"};case"unexpected_error":return{type:"unexpected_error",status:{queue:!0,message:e.message,session_not_found:e.session_not_found,stage:"error",success:!1}};case"broken_connection":return{type:"broken_connection",status:{queue:!0,message:e.message,stage:"error",success:!1}};case"estimation":return{type:"update",status:{queue:!0,stage:t||"pending",code:e.code,size:e.queue_size,position:e.rank,eta:e.rank_eta,success:e.success}};case"progress":return{type:"update",status:{queue:!0,stage:"pending",code:e.code,progress_data:e.progress_data,success:e.success}};case"log":return{type:"log",data:e};case"process_generating":return{type:"generating",status:{queue:!0,message:e.success?null:e.output.error,stage:e.success?"generating":"error",code:e.code,progress_data:e.progress_data,eta:e.average_duration,changed_state_ids:e.success?e.output.changed_state_ids:void 0},data:e.success?e.output:null};case"process_streaming":return{type:"streaming",status:{queue:!0,message:e.output.error,stage:"streaming",time_limit:e.time_limit,code:e.code,progress_data:e.progress_data,changed_state_ids:e.output.changed_state_ids,eta:e.eta},data:e.output};case"process_completed":return"error"in e.output?{type:"update",status:{queue:!0,title:e.output.title??"Error",message:e.output.error??"An error occurred",visible:e.output.visible,duration:e.output.duration,stage:"error",code:e.code,success:e.success}}:{type:"complete",status:{queue:!0,message:e.success?void 0:e.output.error,stage:e.success?"complete":"error",code:e.code,progress_data:e.progress_data,changed_state_ids:e.success?e.output.changed_state_ids:void 0,used_cache:e.used_cache,cache_duration:e.cache_duration,avg_time:e.avg_time},data:e.success?e.output:null};case"process_starts":return{type:"update",status:{queue:!0,stage:"pending",code:e.code,size:e.rank,position:0,success:e.success,eta:e.eta},original_msg:"process_starts"}}return{type:"none",status:{stage:"error",queue:!0}}}var Dt=(e=[],t)=>{let s=t?t.parameters:[];if(Array.isArray(e))return t&&s.length>0&&e.length>s.length&&console.warn("Too many arguments provided for the endpoint."),e;let n=[],i=Object.keys(e);return s.forEach((r,a)=>{if(e.hasOwnProperty(r.parameter_name))n[a]=e[r.parameter_name];else if(r.parameter_has_default)n[a]=r.parameter_default;else throw Error(`No value provided for required parameter: ${r.parameter_name}`)}),i.forEach(r=>{if(!s.some(a=>a.parameter_name===r))throw Error(`Parameter \`${r}\` is not a valid keyword argument. Please refer to the API for usage.`)}),n.forEach((r,a)=>{if(r===void 0&&!s[a].parameter_has_default)throw Error(`No value provided for required parameter: ${s[a].parameter_name}`)}),n};async function At(){if(this.api_info)return this.api_info;let{token:e}=this.options,{config:t}=this,s={};if(e&&(s.Authorization=`Bearer ${e}`),t)try{let n,i;if(typeof window<"u"&&window.gradio_api_info)i=window.gradio_api_info;else{let r=ee(t.root,this.api_prefix,ot);if(n=await this.fetch(r,{headers:s,credentials:this.options.credentials??"same-origin"}),!n.ok)throw Error(N);i=await n.json()}return"api"in i&&(i=i.api),i.named_endpoints["/predict"]&&!i.unnamed_endpoints[0]&&(i.unnamed_endpoints[0]=i.named_endpoints["/predict"]),Ot(i,t,this.api_map)}catch(n){throw Error("Could not get API info. "+n.message)}}async function Ct(e,t,s){let n={};this?.options?.token&&(n.Authorization=`Bearer ${this.options.token}`);let i=1e3,r=[],a;for(let o=0;o<t.length;o+=i){let l=t.slice(o,o+i),c=new FormData;l.forEach(f=>{c.append("files",f)});try{let f=s?`${e}${this.api_prefix}/${_e}?upload_id=${s}`:`${e}${this.api_prefix}/${_e}`;a=await this.fetch(f,{method:"POST",body:c,headers:n,credentials:this.options.credentials??"same-origin"})}catch(f){throw Error(N+f.message)}if(!a.ok){let f=await a.text();return{error:`HTTP ${a.status}: ${f}`}}let u=await a.json();u&&r.push(...u)}return{files:r}}var ke={si:{radix:1e3,unit:["b","kb","Mb","Gb","Tb","Pb","Eb","Zb","Yb"]},iec:{radix:1024,unit:["b","Kib","Mib","Gib","Tib","Pib","Eib","Zib","Yib"]},jedec:{radix:1024,unit:["b","Kb","Mb","Gb","Tb","Pb","Eb","Zb","Yb"]}};function qt(e,t=1,s="jedec"){e=Math.abs(e);let{radix:n,unit:i}=ke[s]||ke.jedec,r=0;for(;e>=n;)e/=n,++r;return`${e.toFixed(t)} ${i[r]}`}async function Nt(e,t,s,n){let i=(Array.isArray(e)?e:[e]).map(a=>a.blob),r=i.filter(a=>a.size>(n??1/0));if(r.length)throw Error(`File(s) exceed the maximum allowed size of ${qt(n||1/0)}: ${r.map(a=>`"${a.name}"`).join(", ")}`);return await Promise.all(await this.upload_files(t,i,s).then(async a=>{if(a.error)throw Error(a.error);return a.files?a.files.map((o,l)=>new ie({...e[l],path:o,url:`${t}${this.api_prefix}/file=${o}`})):[]}))}var ie=class{path;url;orig_name;size;blob;is_stream;mime_type;alt_text;b64;meta={_type:"gradio.FileData"};constructor({path:e,url:t,orig_name:s,size:n,blob:i,is_stream:r,mime_type:a,alt_text:o,b64:l}){this.path=e,this.url=t,this.orig_name=s,this.size=n,this.blob=t?void 0:i,this.is_stream=r,this.mime_type=a,this.alt_text=o,this.b64=l}},Ie=class{type;command;meta;fileData;constructor(e,t){this.type="command",this.command=e,this.meta=t}},zt=typeof process<"u"&&process.versions&&process.versions.node;function $e(e,t,s){for(;s.length>1;){let i=s.shift();if(typeof i=="string"||typeof i=="number")e=e[i];else throw Error("Invalid key type")}let n=s.shift();if(typeof n=="string"||typeof n=="number")e[n]=t;else throw Error("Invalid key type")}async function te(e,t=void 0,s=[],n=!1,i=void 0){if(Array.isArray(e)){let r=[];return await Promise.all(e.map(async(a,o)=>{let l=s.slice();l.push(String(o));let c=await te(e[o],n?i?.parameters[o]?.component||void 0:t,l,!1,i);r=r.concat(c)})),r}else{if(globalThis.Buffer&&e instanceof globalThis.Buffer||e instanceof Blob)return[{path:s,blob:e instanceof Blob?e:new Blob([e]),type:t}];if(typeof e=="object"&&e){let r=[];for(let a of Object.keys(e)){let o=[...s,a],l=e[a];r=r.concat(await te(l,void 0,o,!1,i))}return r}}return[]}function Ee(e,t){let s=t?.dependencies?.find(n=>n.id==e)?.queue;return s==null?!t.enable_queue:!s}function Bt(e,t){return new Promise((s,n)=>{let i=new MessageChannel;i.port1.onmessage=(({data:r})=>{i.port1.close(),s(r)}),window.parent.postMessage(e,t,[i.port2])})}function us(e){if(typeof e=="string"){if(e.startsWith("http://")||e.startsWith("https://"))return{path:e,url:e,orig_name:e.split("/").pop()??"unknown",meta:{_type:"gradio.FileData"}};if(zt)return new Ie("upload_file",{path:e,name:e,orig_path:e})}else{if(typeof File<"u"&&e instanceof File)return e;if(globalThis.Buffer&&e instanceof globalThis.Buffer)return new Blob([e]);if(e instanceof Blob)return e}throw Error("Invalid input: must be a URL, File, Blob, or Buffer object.")}function Y(e,t,s,n,i=!1){if(n==="input"&&!i)throw Error("Invalid code path. Cannot skip state inputs for input.");if(n==="output"&&i)return e;let r=[],a=0,o=n==="input"?t.inputs:t.outputs;for(let l=0;l<o.length;l++){let c=o[l];if(s.find(u=>u.id===c)?.type==="state"){if(i)if(e.length===o.length){let u=e[a];r.push(u),a++}else r.push(null);else{a++;continue}continue}else{let u=e[a];r.push(u),a++}}return r}async function Lt(e,t,s){let n=this;await It(n,t);let i=await te(t,void 0,[],!0,s);return(await Promise.all(i.map(async({path:r,blob:a,type:o})=>{if(!a)return{path:r,type:o};let l=await n.upload_files(e,[a]);return{path:r,file_url:l.files&&l.files[0],type:o,name:typeof File<"u"&&a instanceof File?a?.name:void 0}}))).forEach(({path:r,file_url:a,type:o,name:l})=>{o==="Gallery"?$e(t,a,r):a&&$e(t,new ie({path:a,orig_name:l}),r)}),t}async function It(e,t){if(!(e.config?.root||e.config?.root_url))throw Error(Ne);await Ue(e,t)}async function Ue(e,t,s=[]){for(let n in t)t[n]instanceof Ie?await Ut(e,t,n):typeof t[n]=="object"&&t[n]!==null&&await Ue(e,t[n],[...s,n])}async function Ut(e,t,s){let n=t[s],i=e.config?.root||e.config?.root_url;if(!i)throw Error(Ne);try{let r,a;if(typeof process<"u"&&process.versions&&process.versions.node){let u=await fe(()=>import("./__vite-browser-external-Cgmn0awE-17s0Hkgg.js"),__vite__mapDeps([0,1,2])).then(f=>ge(f.default,1));a=(await fe(()=>import("./__vite-browser-external-Cgmn0awE-17s0Hkgg.js"),__vite__mapDeps([0,1,2])).then(f=>ge(f.default,1))).resolve(process.cwd(),n.meta.path),r=await u.readFile(a)}else throw Error(kt);let o=new Blob([r],{type:"application/octet-stream"}),l=await e.upload_files(i,[o]),c=l.files&&l.files[0];c&&(t[s]=new ie({path:c,orig_name:n.meta.name||""}))}catch(r){console.error($t,r)}}async function Ft(e,t,s){let n={"Content-Type":"application/json"};this.options.token&&(n.Authorization=`Bearer ${this.options.token}`);try{var i=await this.fetch(e,{method:"POST",body:JSON.stringify(t),headers:{...n,...s},credentials:this.options.credentials??"same-origin"})}catch{return[{error:N},500]}let r,a;try{r=await i.json(),a=i.status}catch(o){r={error:`Could not parse server response: ${o}`},a=500}return[r,a]}async function Rt(e,t={}){let s=!1,n=!1;if(!this.config)throw Error("Could not resolve app config");if(typeof e=="number")this.config.dependencies.find(a=>a.id==e);else{let a=e.replace(/^\//,"");this.config.dependencies.find(o=>o.id==this.api_map[a])}let i=this.submit(e,t,null,null,!0),r;for await(let a of i){if(a.type==="data"&&(s=!0,r=a,n))return r;if(a.type==="status"){if(a.stage==="error"){let{message:o,...l}=a,c=Error((typeof o=="string"?o:o&&JSON.stringify(o))||"An unknown error occurred while making a prediction.");throw Object.assign(c,l),c}if(a.stage==="complete"&&(n=!0,s))return r}}return r}async function M(e,t,s){let n=t==="subdomain"?`https://huggingface.co/api/spaces/by-subdomain/${e}`:`https://huggingface.co/api/spaces/${e}`,i,r;try{if(i=await fetch(n),r=i.status,r!==200)throw Error();i=await i.json()}catch{s({status:"error",load_status:"error",message:gt,detail:"NOT_FOUND"});return}if(!i||r!==200)return;let{runtime:{stage:a},id:o}=i;switch(a){case"STOPPED":case"SLEEPING":s({status:"sleeping",load_status:"pending",message:"Space is asleep. Waking it up...",detail:a}),setTimeout(()=>{M(e,t,s)},1e3);break;case"PAUSED":s({status:"paused",load_status:"error",message:"This space has been paused by the author. If you would like to try this demo, consider duplicating the space.",detail:a,discussions_enabled:await xe(o)});break;case"RUNNING":case"RUNNING_BUILDING":s({status:"running",load_status:"complete",message:"Space is running.",detail:a});break;case"BUILDING":s({status:"building",load_status:"pending",message:"Space is building...",detail:a}),setTimeout(()=>{M(e,t,s)},1e3);break;case"APP_STARTING":s({status:"starting",load_status:"pending",message:"Space is starting...",detail:a}),setTimeout(()=>{M(e,t,s)},1e3);break;default:s({status:"space_error",load_status:"error",message:"This space is experiencing an issue.",detail:a,discussions_enabled:await xe(o)});break}}var Fe=async(e,t)=>{let s=0;return new Promise(n=>{M(e,se.test(e)?"space_name":"subdomain",i=>{t(i),i.status==="running"||i.status==="error"||i.status==="paused"||i.status==="space_error"?n():(i.status==="sleeping"||i.status==="building")&&(s<12?(s++,setTimeout(()=>{Fe(e,t).then(n)},5e3)):n())})})},Gt=/^(?=[^]*\b[dD]iscussions{0,1}\b)(?=[^]*\b[dD]isabled\b)[^]*$/;async function xe(e){try{let t=await fetch(`https://huggingface.co/api/spaces/${e}/discussions`,{method:"HEAD"}),s=t.headers.get("x-error-message");return!(!t.ok||s&&Gt.test(s))}catch{return!1}}async function Mt(e,t){let s={};t&&(s.Authorization=`Bearer ${t}`);try{let n=await fetch(`https://huggingface.co/api/spaces/${e}/${ct}`,{headers:s});if(n.status!==200)throw Error("Space hardware could not be obtained.");let{hardware:i}=await n.json();return i.current}catch(n){throw Error(n.message)}}async function Ht(e,t,s){let n={};s&&(n.Authorization=`Bearer ${s}`);let i={seconds:t};try{let r=await fetch(`https://huggingface.co/api/spaces/${e}/${lt}`,{method:"POST",headers:{"Content-Type":"application/json",...n},body:JSON.stringify(i)});if(r.status!==200)throw Error("Could not set sleep timeout on duplicated Space. Please visit *ADD HF LINK TO SETTINGS* to set a timeout manually to reduce billing charges.");return await r.json()}catch(r){throw Error(r.message)}}var Se=["cpu-basic","cpu-upgrade","cpu-xl","t4-small","t4-medium","a10g-small","a10g-large","a10g-largex2","a10g-largex4","a100-large","zero-a10g","h100","h100x8"];async function Jt(e,t){ze(t);let{token:s,private:n,hardware:i,timeout:r,auth:a}=t;if(i&&!Se.includes(i))throw Error(`Invalid hardware type provided. Valid types are: ${Se.map(w=>`"${w}"`).join(",")}.`);let{http_protocol:o,host:l}=await ne(e,s),c=null;if(a){let w=await Be(o,l,a,fetch,void 0,t.credentials);w&&(c=Le(w))}let u={Authorization:`Bearer ${s}`,"Content-Type":"application/json",...c?{Cookie:c.join("; ")}:{}},f=(await(await fetch("https://huggingface.co/api/whoami-v2",{headers:u})).json()).name,v=e.split("/")[1],h={repository:`${f}/${v}`};n&&(h.private=!0);let $;try{i||($=await Mt(e,s))}catch(w){throw Error(G+w.message)}h.hardware=i||$||"cpu-basic";try{let w=await fetch(`https://huggingface.co/api/spaces/${e}/duplicate`,{method:"POST",headers:u,body:JSON.stringify(h)});if(w.status===409)try{return await Te.connect(`${f}/${v}`,t)}catch(H){throw console.error("Failed to connect Client instance:",H),H}else if(w.status!==200)throw Error(w.statusText);let z=await w.json();return await Ht(`${f}/${v}`,r||300,s),await Te.connect(Wt(z.url),t)}catch(w){throw Error(w)}}function Wt(e){let t=e.match(/https:\/\/huggingface.co\/spaces\/([^/]+\/[^/]+)/);if(t)return t[1]}var je="supports-zerogpu-headers",Oe=!1;function Vt(){return typeof window<"u"&&typeof document<"u"&&typeof window.addEventListener=="function"}function Re(e){return e.includes(".dev.")?`https://moon-${e.split(".")[1]}.dev.spaces.huggingface.tech`:e.endsWith(".hf.space")?"https://huggingface.co":null}function Yt(){if(!Vt()||Oe)return;window.addEventListener("message",t=>{t.data===je&&(window.supports_zerogpu_headers=!0)}),Oe=!0;let e=Re(window.location.hostname);e&&window.parent!==window&&window.parent.postMessage(je,e)}var Zt=class extends TransformStream{#e="";constructor(e={allowCR:!1}){super({transform:(t,s)=>{for(t=this.#e+t;;){let n=t.indexOf(`
|
| 3 |
`),i=e.allowCR?t.indexOf("\r"):-1;if(i!==-1&&i!==t.length-1&&(n===-1||n-1>i)){s.enqueue(t.slice(0,i)),t=t.slice(i+1);continue}if(n===-1)break;let r=t[n-1]==="\r"?n-1:n;s.enqueue(t.slice(0,r)),t=t.slice(n+1)}this.#e=t},flush:t=>{if(this.#e==="")return;let s=e.allowCR&&this.#e.endsWith("\r")?this.#e.slice(0,-1):this.#e;t.enqueue(s)}})}};function Kt(e){let t=new TextDecoderStream,s=new Zt({allowCR:!0});return e.pipeThrough(t).pipeThrough(s)}function Qt(e){let t=/[:]\s*/.exec(e),s=t&&t.index;if(s)return[e.substring(0,s),e.substring(s+t[0].length)]}function Pe(e,t,s){e.get(t)||e.set(t,s)}async function*Xt(e,t){if(!e.body)return;let s=Kt(e.body),n,i=s.getReader(),r;for(;;){if(t&&t.aborted)return i.cancel();if(n=await i.read(),n.done)return;if(!n.value){r&&(yield r),r=void 0;continue}let[a,o]=Qt(n.value)||[];a==="data"?(r||={},r[a]=r[a]?r[a]+`
|
| 4 |
`+o:o):a==="event"?(r||={},r[a]=o):a==="id"?(r||={},r[a]=String(+o)===o?+o:o):a==="retry"&&(r||={},r[a]=+o||void 0)}}async function es(e,t){let s=new Request(e,t);Pe(s.headers,"Accept","text/event-stream"),Pe(s.headers,"Content-Type","application/json");let n=await fetch(s);if(!n.ok)throw n;return Xt(n,s.signal)}async function ts(){let{event_callbacks:e,unclosed_events:t,pending_stream_messages:s,stream_status:n,config:i,jwt:r}=this,a=this;if(!i)throw Error("Could not resolve app config");n.open=!0;let o=null,l=new URLSearchParams({session_hash:this.session_hash}).toString(),c=new URL(`${i.root}${this.api_prefix}/${Ae}?${l}`);if(r&&c.searchParams.set("__sign",r),o=this.stream(c),!o){console.warn("Cannot connect to SSE endpoint: "+c.toString());return}o.onmessage=async function(u){let f=JSON.parse(u.data);if(f.msg==="close_stream"){re(n,a.abort_controller);return}let v=f.event_id;if(!v)await Promise.all(Object.keys(e).map(h=>e[h](f)));else if(e[v]&&i){f.msg==="process_completed"&&["sse","sse_v1","sse_v2","sse_v2.1","sse_v3"].includes(i.protocol)&&t.delete(v);let h=e[v];typeof window<"u"&&typeof document<"u"&&document.visibilityState!=="hidden"?setTimeout(h,0,f):h(f)}else s[v]||(s[v]=[]),s[v].push(f)},o.onerror=async function(u){console.error(u),await Promise.all(Object.keys(e).map(f=>e[f]({msg:"broken_connection",message:N})))}}function re(e,t){e&&(e.open=!1,t?.abort())}function ss(e,t,s){e[t]?s.data.forEach((n,i)=>{let r=ns(i<e[t].length?e[t][i]:null,n);e[t][i]=r,s.data[i]=r}):(e[t]=[],s.data.forEach((n,i)=>{e[t][i]=n}))}function ns(e,t){return t.forEach(([s,n,i])=>{e=is(e,n,s,i)}),e}function is(e,t,s,n){if(t.length===0){if(s==="replace")return n;if(s==="append")return e+n;throw Error(`Unsupported action: ${s}`)}let i=e;for(let a=0;a<t.length-1;a++)i=i[t[a]];let r=t[t.length-1];switch(s){case"replace":i[r]=n;break;case"append":i[r]+=n;break;case"add":Array.isArray(i)?i.splice(Number(r),0,n):i[r]=n;break;case"delete":Array.isArray(i)?i.splice(Number(r),1):delete i[r];break;default:throw Error(`Unknown action: ${s}`)}return e}function rs(e,t={}){let s={close:()=>{console.warn("Method not implemented.")},onerror:null,onmessage:null,onopen:null,readyState:0,url:e.toString(),withCredentials:!1,CONNECTING:0,OPEN:1,CLOSED:2,addEventListener:()=>{throw Error("Method not implemented.")},dispatchEvent:()=>{throw Error("Method not implemented.")},removeEventListener:()=>{throw Error("Method not implemented.")}};return es(e,t).then(async n=>{s.readyState=s.OPEN;try{for await(let i of n)s.onmessage&&s.onmessage(i);s.readyState=s.CLOSED}catch(i){s.onerror&&s.onerror(i),s.readyState=s.CLOSED}}).catch(n=>{console.error(n),s.onerror&&s.onerror(n),s.readyState=s.CLOSED}),s}function as(e,t={},s,n,i,r){try{let m=function(p){(i||He[p.type])&&Ye(p)},E=function(){for(pe=!0;R.length>0;)R.shift()({value:void 0,done:!0})},ue=function(p){R.length>0?R.shift()(p):Q.push(p)},Ve=function(p){ue(os(p)),E()},Ye=function(p){ue({value:p,done:!1})},de=function(){return Q.length>0?Promise.resolve(Q.shift()):pe?Promise.resolve({value:void 0,done:!0}):new Promise(p=>R.push(p))},{token:a}=this.options,{fetch:o,app_reference:l,config:c,session_hash:u,api_info:f,api_map:v,stream_status:h,pending_stream_messages:$,pending_diff_streams:w,event_callbacks:z,unclosed_events:H,post_data:Z,options:B,api_prefix:D}=this,ae=r||{"x-gradio-user":"api"},Ge=this;if(!f)throw Error(bt);if(!c)throw Error("Could not resolve app config");let{fn_index:d,endpoint_info:K,dependency:I}=cs(f,e,v,c),Me=Dt(t,K),L,A=c.protocol??"ws";if(A==="ws")throw Error(vt);let U="",g=typeof e=="number"?"/predict":e,J,k=null,C=!1,oe={},F=typeof window<"u"&&typeof document<"u"?new URLSearchParams(window.location.search).toString():"",He=B?.events?.reduce((p,P)=>(p[P]=!0,p),{})||{};async function Je(){let p={},P={};p={event_id:k},P={event_id:k,session_hash:u,fn_index:d};try{if(!c)throw Error("Could not resolve app config");"event_id"in P&&await o(`${c.root}${D}/${ht}`,{headers:{"Content-Type":"application/json"},method:"POST",body:JSON.stringify(P)}),await o(`${c.root}${D}/${dt}`,{headers:{"Content-Type":"application/json"},method:"POST",body:JSON.stringify(p)})}catch{console.warn("The `/reset` endpoint could not be called. Subsequent endpoint results may be unreliable.")}}let We=async p=>{await this._resolve_heartbeat(p)};async function ce(p){if(!c)return;let P=p.render_id;c.components=[...c.components.filter(_=>_.props.rendered_in!==P),...p.components],c.dependencies=[...c.dependencies.filter(_=>_.rendered_in!==P),...p.dependencies];let y=c.components.some(_=>_.type==="state"),q=c.dependencies.some(_=>_.targets.some(b=>b[1]==="unload"));c.connect_heartbeat=y||q,await We(c),m({type:"render",data:p,endpoint:g,fn_index:d})}let le=this.handle_blob(c.root,Me,K).then(async p=>{if(J={data:Y(p,I,c.components,"input",!0)||[],event_data:s,fn_index:d,trigger_id:n,...B.oauth_token&&K?.oauth_token?{oauth_token:B.oauth_token}:{}},Ee(d,c))m({type:"status",endpoint:g,stage:"pending",queue:!1,fn_index:d,time:new Date}),Z(`${c.root}${D}/run${g.startsWith("/")?g:`/${g}`}${F?"?"+F:""}`,{...J,session_hash:u},ae).then(async([y,q])=>{let _=y.data;if(q==200)m({type:"data",endpoint:g,fn_index:d,data:Y(_,I,c.components,"output",B.with_null_state),time:new Date,event_data:s,trigger_id:n}),y.render_config&&await ce(y.render_config),m({type:"status",endpoint:g,fn_index:d,stage:"complete",eta:y.average_duration,queue:!1,time:new Date});else{let b=y?.error===N;m({type:"status",stage:"error",endpoint:g,fn_index:d,message:y.error,broken:b,queue:!1,time:new Date})}}).catch(y=>{m({type:"status",stage:"error",message:y.message,endpoint:g,fn_index:d,queue:!1,time:new Date})});else if(A=="sse"){m({type:"status",stage:"pending",queue:!0,endpoint:g,fn_index:d,time:new Date});var P=new URLSearchParams({fn_index:d.toString(),session_hash:u}).toString();let y=new URL(`${c.root}${D}/${Ae}?${F?F+"&":""}${P}`);if(this.jwt&&y.searchParams.set("__sign",this.jwt),L=this.stream(y),!L)return Promise.reject(Error("Cannot connect to SSE endpoint: "+y.toString()));L.onmessage=async function(q){let{type:_,status:b,data:x}=ve(JSON.parse(q.data),oe[d]);if(_==="update"&&b&&!C)m({type:"status",endpoint:g,fn_index:d,time:new Date,...b}),b.stage==="error"&&(L?.close(),E());else if(_==="data"){let[W,S]=await Z(`${c.root}${D}/queue/data`,{...J,session_hash:u,event_id:k});S!==200&&(m({type:"status",stage:"error",message:N,queue:!0,endpoint:g,fn_index:d,time:new Date}),L?.close(),E())}else _==="complete"?C=b:_==="log"?m({type:"log",title:x.title,log:x.log,level:x.level,endpoint:g,duration:x.duration,visible:x.visible,fn_index:d}):(_==="generating"||_==="streaming")&&m({type:"status",time:new Date,...b,stage:b?.stage,queue:!0,endpoint:g,fn_index:d});x&&(m({type:"data",time:new Date,data:Y(x.data,I,c.components,"output",B.with_null_state),endpoint:g,fn_index:d,event_data:s,trigger_id:n}),C&&(m({type:"status",time:new Date,...C,stage:b?.stage,queue:!0,endpoint:g,fn_index:d}),L?.close(),E()))}}else if(A=="sse_v1"||A=="sse_v2"||A=="sse_v2.1"||A=="sse_v3"){m({type:"status",stage:"pending",queue:!0,endpoint:g,fn_index:d,time:new Date});let y="";typeof window<"u"&&typeof document<"u"&&(y=window?.location?.hostname);let q=Re(y);return(typeof window<"u"&&typeof document<"u"&&window.parent!=window&&q&&window.supports_zerogpu_headers?Bt("zerogpu-headers",q):Promise.resolve(null)).then(_=>{let b={...ae,..._||{}};return Z(`${c.root}${D}/${rt}?${F}`,{...J,session_hash:u},b)}).then(async([_,b])=>{if(_.event_id&&(U=_.event_id),b===503)m({type:"status",stage:"error",message:Ce,queue:!0,endpoint:g,fn_index:d,time:new Date,visible:!0}),E();else if(b===422)m({type:"status",stage:"error",message:_.detail,queue:!0,endpoint:g,fn_index:d,code:"validation_error",time:new Date,visible:!0}),E();else if(b!==200){let x=_?.error===N;m({type:"status",stage:"error",broken:x,message:x?N:_.detail||_.error,queue:!0,endpoint:g,fn_index:d,time:new Date,visible:!0}),E()}else{k=_.event_id,U=k;let x=async function(W){try{let{type:S,status:j,data:O,original_msg:Ze}=ve(W,oe[d]);if(S=="heartbeat")return;if(S==="update"&&j&&!C)m({type:"status",endpoint:g,fn_index:d,time:new Date,original_msg:Ze,...j});else if(S==="complete")C=j;else if(S=="unexpected_error"||S=="broken_connection"){console.error("Unexpected error",j?.message);let Ke=S==="broken_connection";m({type:"status",stage:"error",message:j?.message||"An Unexpected Error Occurred!",queue:!0,endpoint:g,broken:Ke,session_not_found:j?.session_not_found,fn_index:d,time:new Date})}else if(S==="log"){m({type:"log",title:O.title,log:O.log,level:O.level,endpoint:g,duration:O.duration,visible:O.visible,fn_index:d});return}else(S==="generating"||S==="streaming")&&(m({type:"status",time:new Date,...j,stage:j?.stage,queue:!0,endpoint:g,fn_index:d}),O&&I.connection!=="stream"&&["sse_v2","sse_v2.1","sse_v3"].includes(A)&&ss(w,k,O));O&&(m({type:"data",time:new Date,data:Y(O.data,I,c.components,"output",B.with_null_state),endpoint:g,fn_index:d}),O.render_config&&await ce(O.render_config),C&&(m({type:"status",time:new Date,...C,stage:j?.stage,queue:!0,endpoint:g,fn_index:d}),E())),(j?.stage==="complete"||j?.stage==="error")&&(z[k]&&delete z[k],k in w&&delete w[k],E())}catch(S){console.error("Unexpected client exception",S),m({type:"status",stage:"error",message:"An Unexpected Error Occurred!",queue:!0,endpoint:g,fn_index:d,time:new Date}),["sse_v2","sse_v2.1","sse_v3"].includes(A)&&(re(h,Ge.abort_controller),h.open=!1,E())}};k in $&&($[k].forEach(W=>x(W)),delete $[k]),z[k]=x,H.add(k),h.open||await this.open_stream()}})}});le.catch(p=>{m({type:"status",stage:"error",message:p instanceof Error?p.message:String(p),queue:!Ee(d,c),endpoint:g,fn_index:d,time:new Date}),E()});let pe=!1,Q=[],R=[],he={[Symbol.asyncIterator]:()=>he,next:de,throw:async p=>(Ve(p),de()),return:async()=>(E(),{value:void 0,done:!0}),cancel:Je,send_chunk:p=>{this.post_data(`${c.root}${D}/stream/${U}`,{...p,session_hash:this.session_hash})},close_stream:()=>{this.post_data(`${c.root}${D}/stream/${U}/close`,{}),E()},event_id:()=>U,wait_for_id:async()=>(await le,k)};return he}catch(a){throw console.error("Submit function encountered an error:",a),a}}function os(e){return{then:(t,s)=>s(e)}}function cs(e,t,s,n){let i,r,a;if(typeof t=="number")i=t,r=e.unnamed_endpoints[i],a=n.dependencies.find(o=>o.id==t);else{let o=t.replace(/^\//,"");i=s[o],r=e.named_endpoints[t.trim()]??e.named_endpoints[`/${o}`],a=n.dependencies.find(l=>l.id==s[o])}if(typeof i!="number"||!a){let o=n.dependencies.filter(l=>l.api_name).map(l=>`"/${l.api_name}"`).join(", ");throw Error(`No endpoint matching ${JSON.stringify(t)} was found. `+(o?`Valid named endpoints are: ${o}. `:"This app exposes no named endpoints. ")+"An fn_index (number) of an existing dependency can also be used.")}return{fn_index:i,endpoint_info:r,dependency:a}}var Te=class{app_reference;options;deep_link=null;config;api_prefix="";api_info;api_map={};session_hash=Math.random().toString(36).substring(2);jwt=!1;last_status={};cookies=null;stream_status={open:!1};closed=!1;pending_stream_messages={};pending_diff_streams={};event_callbacks={};unclosed_events=new Set;heartbeat_event=null;abort_controller=null;stream_instance=null;current_payload;get_url_config(e=null){if(!this.config)throw Error(T);e===null&&(e=window.location.href);let t=r=>r.replace(/^\/+|\/+$/g,""),s=t(new URL(this.config.root).pathname),n=t(new URL(e).pathname),i;return i=n.startsWith(s)?t(n.substring(s.length)):"",this.get_page_config(i)}get_page_config(e){if(!this.config)throw Error(T);let t=this.config;return e in t.page||(e=""),{...t,current_page:e,layout:t.page[e].layout,components:t.components.filter(s=>t.page[e].components.includes(s.id)),dependencies:this.config.dependencies.filter(s=>t.page[e].dependencies.includes(s.id))}}fetch(e,t){let s=new Headers(t?.headers||{});return this&&this.cookies&&s.append("Cookie",this.cookies),this&&this.options.headers&&new Headers(this.options.headers).forEach((n,i)=>{s.append(i,n)}),fetch(e,{...t,headers:s})}stream(e){let t=new Headers;return this&&this.cookies&&t.append("Cookie",this.cookies),this&&this.options.headers&&new Headers(this.options.headers).forEach((s,n)=>{t.append(n,s)}),this&&this.options.token&&t.append("Authorization",`Bearer ${this.options.token}`),this.abort_controller=new AbortController,this.stream_instance=rs(e.toString(),{credentials:this.options.credentials??"same-origin",headers:t,signal:this.abort_controller.signal}),this.stream_instance}view_api;upload_files;upload;handle_blob;post_data;submit;predict;open_stream;resolve_config;resolve_cookies;constructor(e,t={events:["data"]}){this.app_reference=e,this.deep_link=t.query_params?.deep_link||null,t.events||=["data"],ze(t),this.options=t,this.current_payload={},t.cookies&&(this.cookies=t.cookies),this.view_api=At.bind(this),this.upload_files=Ct.bind(this),this.handle_blob=Lt.bind(this),this.post_data=Ft.bind(this),this.submit=as.bind(this),this.predict=Rt.bind(this),this.open_stream=ts.bind(this),this.resolve_config=xt.bind(this),this.resolve_cookies=St.bind(this),this.upload=Nt.bind(this),this.fetch=this.fetch.bind(this),this.handle_space_success=this.handle_space_success.bind(this),this.stream=this.stream.bind(this)}async init(){Yt(),this.options.auth&&await this.resolve_cookies(),await this._resolve_config().then(e=>e?.config&&this._resolve_heartbeat(e.config));try{this.api_info=await this.view_api()}catch(e){console.error(e.message)}this.api_map=ye(this.config?.dependencies||[])}async _resolve_heartbeat(e){if(e&&(this.config=e,this.api_prefix=e.api_prefix||"",this.config&&this.config.connect_heartbeat&&this.config.space_id&&this.options.token&&(this.jwt=await we(this.config.space_id,this.options.token,this.cookies))),e.space_id&&this.options.token&&(this.jwt=await we(e.space_id,this.options.token)),this.config&&this.config.connect_heartbeat){let t=new URL(`${this.config.root}${this.api_prefix}/${pt}/${this.session_hash}`);this.jwt&&t.searchParams.set("__sign",this.jwt),this.heartbeat_event||=this.stream(t)}}static async connect(e,t={events:["data"]}){let s=new this(e,t);return t.session_hash&&(s.session_hash=t.session_hash),await s.init(),s}async reconnect(){let e=new URL(`${this.config.root}${this.api_prefix}/${ft}`),t;try{let s=await this.fetch(e);if(!s.ok)throw Error();t=(await s.json()).app_id}catch{return"broken"}return t===this.config.app_id?"connected":"changed"}close(){this.closed=!0,re(this.stream_status,this.abort_controller)}async refresh(){if(!this.config)throw Error(T);let e=await this.resolve_config(this.config.root,!1);if(!e)throw Error(T);this.config=e,this.api_prefix=e.api_prefix||"",this.api_map=ye(e.dependencies||[]);try{this.api_info=await this.view_api()}catch(t){console.error(me+t.message)}return this.get_url_config()}set_current_payload(e){this.current_payload=e}static async duplicate(e,t={events:["data"]}){return Jt(e,t)}async _resolve_config(){let{http_protocol:e,host:t,space_id:s}=await ne(this.app_reference,this.options.token),{status_callback:n}=this.options;s&&n&&await Fe(s,n);let i;try{let r=`${e}//${t}`;if(i=await this.resolve_config(r),!i)throw Error(T);return this.config_success(i)}catch(r){if(s&&n)M(s,se.test(s)?"space_name":"subdomain",this.handle_space_success);else throw n&&n({status:"error",message:"Could not load this space.",load_status:"error",detail:"NOT_FOUND"}),r instanceof Error?r:Error(String(r))}}async config_success(e){if(this.config=e,this.api_prefix=e.api_prefix||"",this.config.auth_required)return this.prepare_return_obj();try{this.api_info=await this.view_api()}catch(t){console.error(me+t.message)}return this.prepare_return_obj()}async handle_space_success(e){if(!this)throw Error(T);let{status_callback:t}=this.options;if(t&&t(e),e.status==="running")try{if(this.config=await this._resolve_config(),this.api_prefix=this?.config?.api_prefix||"",!this.config)throw Error(T);return await this.config_success(this.config)}catch(s){throw t&&t({status:"error",message:"Could not load this space.",load_status:"error",detail:"NOT_FOUND"}),s}}async component_server(e,t,s){if(!this.config)throw Error(T);let n={},{token:i}=this.options,{session_hash:r}=this;i&&(n.Authorization=`Bearer ${this.options.token}`);let a,o=this.config.components.find(c=>c.id===e);a=o?.props?.root_url?o.props.root_url:this.config.root;let l;if(typeof s=="object"&&s&&"binary"in s){let c=s;l=new FormData;for(let u in c.data)u!=="binary"&&l.append(u,c.data[u]);l.set("component_id",e.toString()),l.set("fn_name",t),l.set("session_hash",r)}else l=JSON.stringify({data:s,component_id:e,fn_name:t,session_hash:r}),n["Content-Type"]="application/json";i&&(n.Authorization=`Bearer ${i}`);try{let c=await this.fetch(`${a}${this.api_prefix}/${ut}/`,{method:"POST",body:l,headers:n,credentials:this.options.credentials??"same-origin"});if(!c.ok)throw Error("Could not connect to component server: "+c.statusText);return await c.json()}catch(c){console.warn(c)}}set_cookies(e){this.cookies=Le(e).join("; ")}prepare_return_obj(){return{config:this.config,predict:this.predict,submit:this.submit,view_api:this.view_api,component_server:this.component_server}}};export{Te as Client,ie as FileData,us as handle_file,Rt as predict,as as submit,ps as t,Nt as upload,Ct as upload_files};
|
frontend/dist/assets/index-CCfkPg0W.css
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-content:""}}}@layer theme{:root,:host{--font-sans:"Inter",ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;--font-mono:ui-monospace,"SF Mono","JetBrains Mono",Menlo,monospace;--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--container-sm:24rem;--container-md:28rem;--container-lg:32rem;--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--leading-tight:1.25;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--ease-in:cubic-bezier(.4,0,1,1);--ease-out:cubic-bezier(0,0,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite;--blur-sm:8px;--blur-md:12px;--aspect-video:16/9;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-canvas:oklch(14.8% .008 265);--color-sunken:oklch(11.8% .008 265);--color-surface:oklch(18.8% .009 265);--color-raised:oklch(23.2% .011 265);--color-line:oklch(28.8% .012 265);--color-line-strong:oklch(40% .016 265);--color-ink:oklch(97% .003 265);--color-muted:oklch(70.5% .014 265);--color-faint:oklch(54.5% .014 265);--color-accent:oklch(77% .168 57);--color-accent-soft:oklch(83% .13 62);--color-accent-ink:oklch(19% .045 57);--color-ok:oklch(77% .15 158);--color-warn:oklch(82% .15 88);--color-bad:oklch(70% .19 24)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}html{color-scheme:dark;background:var(--color-sunken)}body{background:var(--color-canvas);min-height:100vh;color:var(--color-ink);font-family:var(--font-sans);font-feature-settings:"cv05" 1,"ss01" 1,"tnum" 0;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;margin:0}#root{min-height:100vh}:focus-visible{outline:2px solid var(--color-accent);outline-offset:2px}::selection{background:#ff963a59}@supports (color:color-mix(in lab,red,red)){::selection{background:color-mix(in oklch,var(--color-accent)35%,transparent)}}}@layer components;@layer utilities{.pointer-events-none{pointer-events:none}.collapse{visibility:collapse}.visible{visibility:visible}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.inset-0{inset:calc(var(--spacing)*0)}.inset-y-0{inset-block:calc(var(--spacing)*0)}.top-0{top:calc(var(--spacing)*0)}.top-1\.5{top:calc(var(--spacing)*1.5)}.top-1\/2{top:50%}.right-1\.5{right:calc(var(--spacing)*1.5)}.bottom-0{bottom:calc(var(--spacing)*0)}.bottom-1\.5{bottom:calc(var(--spacing)*1.5)}.left-0{left:calc(var(--spacing)*0)}.left-1\.5{left:calc(var(--spacing)*1.5)}.z-20{z-index:20}.z-30{z-index:30}.z-40{z-index:40}.z-50{z-index:50}.z-60,.z-\[60\]{z-index:60}.mx-auto{margin-inline:auto}.mt-0\.5{margin-top:calc(var(--spacing)*.5)}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-1\.5{margin-top:calc(var(--spacing)*1.5)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-2\.5{margin-top:calc(var(--spacing)*2.5)}.mt-3{margin-top:calc(var(--spacing)*3)}.mt-3\.5{margin-top:calc(var(--spacing)*3.5)}.mt-4{margin-top:calc(var(--spacing)*4)}.mt-px{margin-top:1px}.-mr-12{margin-right:calc(var(--spacing)*-12)}.mr-auto{margin-right:auto}.-mb-12{margin-bottom:calc(var(--spacing)*-12)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-3{margin-bottom:calc(var(--spacing)*3)}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-flex{display:inline-flex}.table{display:table}.aspect-video{aspect-ratio:var(--aspect-video)}.size-1{width:calc(var(--spacing)*1);height:calc(var(--spacing)*1)}.size-1\.5{width:calc(var(--spacing)*1.5);height:calc(var(--spacing)*1.5)}.size-3{width:calc(var(--spacing)*3);height:calc(var(--spacing)*3)}.size-3\.5{width:calc(var(--spacing)*3.5);height:calc(var(--spacing)*3.5)}.size-4{width:calc(var(--spacing)*4);height:calc(var(--spacing)*4)}.size-6{width:calc(var(--spacing)*6);height:calc(var(--spacing)*6)}.size-7{width:calc(var(--spacing)*7);height:calc(var(--spacing)*7)}.size-8{width:calc(var(--spacing)*8);height:calc(var(--spacing)*8)}.size-9{width:calc(var(--spacing)*9);height:calc(var(--spacing)*9)}.size-full{width:100%;height:100%}.h-1{height:calc(var(--spacing)*1)}.h-1\.5{height:calc(var(--spacing)*1.5)}.h-5{height:calc(var(--spacing)*5)}.h-7{height:calc(var(--spacing)*7)}.h-8{height:calc(var(--spacing)*8)}.h-9{height:calc(var(--spacing)*9)}.h-10{height:calc(var(--spacing)*10)}.h-13{height:calc(var(--spacing)*13)}.h-36{height:calc(var(--spacing)*36)}.h-\[46vh\]{height:46vh}.h-\[var\(--collapsible-panel-height\)\]{height:var(--collapsible-panel-height)}.h-full{height:100%}.max-h-64{max-height:calc(var(--spacing)*64)}.max-h-\[88vh\]{max-height:88vh}.max-h-\[calc\(85dvh\+3rem\)\]{max-height:calc(85dvh + 3rem)}.max-h-\[min\(var\(--available-height\)\,34rem\)\]{max-height:min(var(--available-height),34rem)}.min-h-0{min-height:calc(var(--spacing)*0)}.min-h-5{min-height:calc(var(--spacing)*5)}.min-h-8{min-height:calc(var(--spacing)*8)}.min-h-\[88px\]{min-height:88px}.min-h-\[220px\]{min-height:220px}.min-h-dvh{min-height:100dvh}.w-1\/2{width:50%}.w-1\/3{width:33.3333%}.w-8{width:calc(var(--spacing)*8)}.w-9{width:calc(var(--spacing)*9)}.w-10{width:calc(var(--spacing)*10)}.w-\[22rem\]{width:22rem}.w-\[24rem\]{width:24rem}.w-\[calc\(100\%\+3rem\)\]{width:calc(100% + 3rem)}.w-full{width:100%}.max-w-lg{max-width:var(--container-lg)}.max-w-md{max-width:var(--container-md)}.max-w-sm{max-width:var(--container-sm)}.max-w-xs{max-width:var(--container-xs)}.min-w-0{min-width:calc(var(--spacing)*0)}.min-w-\[3px\]{min-width:3px}.flex-1{flex:1}.shrink-0{flex-shrink:0}.grow{flex-grow:1}.origin-\[var\(--transform-origin\)\]{transform-origin:var(--transform-origin)}.-translate-x-1\/2{--tw-translate-x: -50% ;translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-y-1\/2{--tw-translate-y: -50% ;translate:var(--tw-translate-x)var(--tw-translate-y)}.\[transform\:translateX\(var\(--drawer-swipe-movement-x\)\)\]{transform:translate(var(--drawer-swipe-movement-x))}.\[transform\:translateY\(var\(--drawer-swipe-movement-y\)\)\]{transform:translateY(var(--drawer-swipe-movement-y))}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.animate-pulse{animation:var(--animate-pulse)}.animate-spin{animation:var(--animate-spin)}.cursor-pointer{cursor:pointer}.touch-auto{touch-action:auto}.touch-none{touch-action:none}.resize{resize:both}.resize-none{resize:none}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-\[repeat\(auto-fill\,minmax\(11rem\,1fr\)\)\]{grid-template-columns:repeat(auto-fill,minmax(11rem,1fr))}.flex-col{flex-direction:column}.flex-row{flex-direction:row}.flex-wrap{flex-wrap:wrap}.place-items-center{place-items:center}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.items-stretch{align-items:stretch}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-1{gap:calc(var(--spacing)*1)}.gap-1\.5{gap:calc(var(--spacing)*1.5)}.gap-2{gap:calc(var(--spacing)*2)}.gap-2\.5{gap:calc(var(--spacing)*2.5)}.gap-3{gap:calc(var(--spacing)*3)}.gap-3\.5{gap:calc(var(--spacing)*3.5)}.gap-4{gap:calc(var(--spacing)*4)}.gap-6{gap:calc(var(--spacing)*6)}.gap-7{gap:calc(var(--spacing)*7)}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-line>:not(:last-child)){border-color:var(--color-line)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.overscroll-contain{overscroll-behavior:contain}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-\[3px\]{border-radius:3px}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-xl{border-radius:var(--radius-xl)}.rounded-t-2xl{border-top-left-radius:var(--radius-2xl);border-top-right-radius:var(--radius-2xl)}.rounded-t-xl{border-top-left-radius:var(--radius-xl);border-top-right-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-accent{border-color:var(--color-accent)}.border-line{border-color:var(--color-line)}.border-line-strong{border-color:var(--color-line-strong)}.bg-accent{background-color:var(--color-accent)}.bg-accent-ink\/50{background-color:#220e0080}@supports (color:color-mix(in lab,red,red)){.bg-accent-ink\/50{background-color:color-mix(in oklab,var(--color-accent-ink)50%,transparent)}}.bg-accent\/8{background-color:#ff963a14}@supports (color:color-mix(in lab,red,red)){.bg-accent\/8{background-color:color-mix(in oklab,var(--color-accent)8%,transparent)}}.bg-accent\/10{background-color:#ff963a1a}@supports (color:color-mix(in lab,red,red)){.bg-accent\/10{background-color:color-mix(in oklab,var(--color-accent)10%,transparent)}}.bg-accent\/12{background-color:#ff963a1f}@supports (color:color-mix(in lab,red,red)){.bg-accent\/12{background-color:color-mix(in oklab,var(--color-accent)12%,transparent)}}.bg-accent\/15{background-color:#ff963a26}@supports (color:color-mix(in lab,red,red)){.bg-accent\/15{background-color:color-mix(in oklab,var(--color-accent)15%,transparent)}}.bg-accent\/\[0\.045\]{background-color:#ff963a0b}@supports (color:color-mix(in lab,red,red)){.bg-accent\/\[0\.045\]{background-color:color-mix(in oklab,var(--color-accent)4.5%,transparent)}}.bg-bad{background-color:var(--color-bad)}.bg-bad\/12{background-color:#ff63621f}@supports (color:color-mix(in lab,red,red)){.bg-bad\/12{background-color:color-mix(in oklab,var(--color-bad)12%,transparent)}}.bg-black{background-color:var(--color-black)}.bg-black\/65{background-color:#000000a6}@supports (color:color-mix(in lab,red,red)){.bg-black\/65{background-color:color-mix(in oklab,var(--color-black)65%,transparent)}}.bg-black\/70{background-color:#000000b3}@supports (color:color-mix(in lab,red,red)){.bg-black\/70{background-color:color-mix(in oklab,var(--color-black)70%,transparent)}}.bg-black\/75{background-color:#000000bf}@supports (color:color-mix(in lab,red,red)){.bg-black\/75{background-color:color-mix(in oklab,var(--color-black)75%,transparent)}}.bg-canvas{background-color:var(--color-canvas)}.bg-canvas\/85{background-color:#090b0ed9}@supports (color:color-mix(in lab,red,red)){.bg-canvas\/85{background-color:color-mix(in oklab,var(--color-canvas)85%,transparent)}}.bg-canvas\/92{background-color:#090b0eeb}@supports (color:color-mix(in lab,red,red)){.bg-canvas\/92{background-color:color-mix(in oklab,var(--color-canvas)92%,transparent)}}.bg-ink{background-color:var(--color-ink)}.bg-line{background-color:var(--color-line)}.bg-line-strong{background-color:var(--color-line-strong)}.bg-ok{background-color:var(--color-ok)}.bg-ok\/12{background-color:#4cd18f1f}@supports (color:color-mix(in lab,red,red)){.bg-ok\/12{background-color:color-mix(in oklab,var(--color-ok)12%,transparent)}}.bg-raised{background-color:var(--color-raised)}.bg-sunken{background-color:var(--color-sunken)}.bg-surface{background-color:var(--color-surface)}.bg-transparent{background-color:#0000}.bg-warn{background-color:var(--color-warn)}.bg-linear-to-b{--tw-gradient-position:to bottom}@supports (background-image:linear-gradient(in lab,red,red)){.bg-linear-to-b{--tw-gradient-position:to bottom in oklab}}.bg-linear-to-b{background-image:linear-gradient(var(--tw-gradient-stops))}.from-accent-soft{--tw-gradient-from:var(--color-accent-soft);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-accent{--tw-gradient-to:var(--color-accent);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.object-contain{object-fit:contain}.object-cover{object-fit:cover}.p-1{padding:calc(var(--spacing)*1)}.p-3{padding:calc(var(--spacing)*3)}.p-4{padding:calc(var(--spacing)*4)}.p-\[2px\]{padding:2px}.px-0{padding-inline:calc(var(--spacing)*0)}.px-1{padding-inline:calc(var(--spacing)*1)}.px-1\.5{padding-inline:calc(var(--spacing)*1.5)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-2\.5{padding-inline:calc(var(--spacing)*2.5)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-5{padding-inline:calc(var(--spacing)*5)}.px-6{padding-inline:calc(var(--spacing)*6)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1{padding-block:calc(var(--spacing)*1)}.py-1\.5{padding-block:calc(var(--spacing)*1.5)}.py-2{padding-block:calc(var(--spacing)*2)}.py-2\.5{padding-block:calc(var(--spacing)*2.5)}.py-3{padding-block:calc(var(--spacing)*3)}.py-4{padding-block:calc(var(--spacing)*4)}.py-5{padding-block:calc(var(--spacing)*5)}.pt-2\.5{padding-top:calc(var(--spacing)*2.5)}.pt-3{padding-top:calc(var(--spacing)*3)}.pr-1{padding-right:calc(var(--spacing)*1)}.pr-12{padding-right:calc(var(--spacing)*12)}.pb-1{padding-bottom:calc(var(--spacing)*1)}.pb-\[calc\(1rem\+env\(safe-area-inset-bottom\,0px\)\+3rem\)\]{padding-bottom:calc(4rem + env(safe-area-inset-bottom,0px))}.text-center{text-align:center}.text-left{text-align:left}.font-mono{font-family:var(--font-mono)}.text-\[10\.5px\]{font-size:10.5px}.text-\[10px\]{font-size:10px}.text-\[11\.5px\]{font-size:11.5px}.text-\[11px\]{font-size:11px}.text-\[12\.5px\]{font-size:12.5px}.text-\[12px\]{font-size:12px}.text-\[13\.5px\]{font-size:13.5px}.text-\[13px\]{font-size:13px}.text-\[14px\]{font-size:14px}.text-\[15px\]{font-size:15px}.leading-3{--tw-leading:calc(var(--spacing)*3);line-height:calc(var(--spacing)*3)}.leading-4{--tw-leading:calc(var(--spacing)*4);line-height:calc(var(--spacing)*4)}.leading-\[1\.5\]{--tw-leading:1.5;line-height:1.5}.leading-\[1\.6\]{--tw-leading:1.6;line-height:1.6}.leading-\[1\.15\]{--tw-leading:1.15;line-height:1.15}.leading-\[1\.45\]{--tw-leading:1.45;line-height:1.45}.leading-\[1\.55\]{--tw-leading:1.55;line-height:1.55}.leading-\[1\.65\]{--tw-leading:1.65;line-height:1.65}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-\[0\.1em\]{--tw-tracking:.1em;letter-spacing:.1em}.tracking-\[0\.06em\]{--tw-tracking:.06em;letter-spacing:.06em}.tracking-\[0\.08em\]{--tw-tracking:.08em;letter-spacing:.08em}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.break-words{overflow-wrap:break-word}.whitespace-nowrap{white-space:nowrap}.text-accent{color:var(--color-accent)}.text-accent-ink{color:var(--color-accent-ink)}.text-bad{color:var(--color-bad)}.text-faint{color:var(--color-faint)}.text-ink{color:var(--color-ink)}.text-muted{color:var(--color-muted)}.text-ok{color:var(--color-ok)}.text-warn{color:var(--color-warn)}.uppercase{text-transform:uppercase}.underline{text-decoration-line:underline}.underline-offset-2{text-underline-offset:2px}.opacity-40{opacity:.4}.opacity-\[calc\(0\.65\*\(1-var\(--drawer-swipe-progress\)\)\)\]{opacity:calc(.65*(1 - var(--drawer-swipe-progress)))}.shadow-\[-24px_0_60px_-15px_rgba\(0\,0\,0\,0\.75\)\]{--tw-shadow:-24px 0 60px -15px var(--tw-shadow-color,#000000bf);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[0_-20px_60px_-15px_rgba\(0\,0\,0\,0\.7\)\]{--tw-shadow:0 -20px 60px -15px var(--tw-shadow-color,#000000b3);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[0_1px_2px_rgb\(0_0_0\/0\.5\)\]{--tw-shadow:0 1px 2px var(--tw-shadow-color,#00000080);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[0_1px_3px_rgb\(0_0_0\/0\.6\)\]{--tw-shadow:0 1px 3px var(--tw-shadow-color,#0009);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[0_8px_24px_-8px_rgb\(0_0_0\/0\.8\)\]{--tw-shadow:0 8px 24px -8px var(--tw-shadow-color,#000c);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[0_24px_60px_-15px_rgba\(0\,0\,0\,0\.75\)\]{--tw-shadow:0 24px 60px -15px var(--tw-shadow-color,#000000bf);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[inset_0_1px_0_rgb\(255_255_255\/0\.06\)\,0_1px_2px_rgb\(0_0_0\/0\.3\)\]{--tw-shadow:inset 0 1px 0 var(--tw-shadow-color,#ffffff0f),0 1px 2px var(--tw-shadow-color,#0000004d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[inset_0_1px_0_rgb\(255_255_255\/0\.06\)\]{--tw-shadow:inset 0 1px 0 var(--tw-shadow-color,#ffffff0f);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[inset_0_1px_0_rgb\(255_255_255\/0\.07\)\,0_1px_2px_rgb\(0_0_0\/0\.35\)\]{--tw-shadow:inset 0 1px 0 var(--tw-shadow-color,#ffffff12),0 1px 2px var(--tw-shadow-color,#00000059);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[inset_0_1px_0_rgb\(255_255_255\/0\.28\)\,0_1px_2px_rgb\(0_0_0\/0\.45\)\]{--tw-shadow:inset 0 1px 0 var(--tw-shadow-color,#ffffff47),0 1px 2px var(--tw-shadow-color,#00000073);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-1{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-2{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-accent{--tw-ring-color:var(--color-accent)}.ring-accent\/35{--tw-ring-color:#ff963a59}@supports (color:color-mix(in lab,red,red)){.ring-accent\/35{--tw-ring-color:color-mix(in oklab,var(--color-accent)35%,transparent)}}.ring-accent\/50{--tw-ring-color:#ff963a80}@supports (color:color-mix(in lab,red,red)){.ring-accent\/50{--tw-ring-color:color-mix(in oklab,var(--color-accent)50%,transparent)}}.ring-bad\/35{--tw-ring-color:#ff636259}@supports (color:color-mix(in lab,red,red)){.ring-bad\/35{--tw-ring-color:color-mix(in oklab,var(--color-bad)35%,transparent)}}.ring-line{--tw-ring-color:var(--color-line)}.ring-ok\/30{--tw-ring-color:#4cd18f4d}@supports (color:color-mix(in lab,red,red)){.ring-ok\/30{--tw-ring-color:color-mix(in oklab,var(--color-ok)30%,transparent)}}.ring-transparent{--tw-ring-color:transparent}.ring-white\/5{--tw-ring-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.ring-white\/5{--tw-ring-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.drop-shadow{--tw-drop-shadow-size:drop-shadow(0 1px 2px var(--tw-drop-shadow-color,#0000001a))drop-shadow(0 1px 1px var(--tw-drop-shadow-color,#0000000f));--tw-drop-shadow:drop-shadow(0 1px 2px #0000001a)drop-shadow(0 1px 1px #0000000f);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.backdrop-blur{--tw-backdrop-blur:blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.backdrop-blur-\[2px\]{--tw-backdrop-blur:blur(2px);-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.backdrop-blur-md{--tw-backdrop-blur:blur(var(--blur-md));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.backdrop-blur-sm{--tw-backdrop-blur:blur(var(--blur-sm));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[background-color\,color\,filter\,box-shadow\]{transition-property:background-color,color,filter,box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[box-shadow\,background-color\]{transition-property:box-shadow,background-color;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[color\,opacity\]{transition-property:color,opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[height\]{transition-property:height;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[opacity\,transform\]{transition-property:opacity,transform;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[width\]{transition-property:width;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-shadow{transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-100{--tw-duration:.1s;transition-duration:.1s}.duration-150{--tw-duration:.15s;transition-duration:.15s}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-500{--tw-duration:.5s;transition-duration:.5s}.duration-\[450ms\]{--tw-duration:.45s;transition-duration:.45s}.ease-\[cubic-bezier\(0\.32\,0\.72\,0\,1\)\]{--tw-ease:cubic-bezier(.32,.72,0,1);transition-timing-function:cubic-bezier(.32,.72,0,1)}.ease-in{--tw-ease:var(--ease-in);transition-timing-function:var(--ease-in)}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}.\[--halo\:color-mix\(in_oklch\,var\(--color-accent\)_28\%\,transparent\)\]{--halo:#ff963a47}@supports (color:color-mix(in lab,red,red)){.\[--halo\:color-mix\(in_oklch\,var\(--color-accent\)_28\%\,transparent\)\]{--halo:color-mix(in oklch,var(--color-accent)28%,transparent)}}.\[transition\:inset-inline-start_200ms_ease-out\,box-shadow_150ms_ease-out\,scale_150ms_ease-out\]{transition:inset-inline-start .2s ease-out,box-shadow .15s ease-out,scale .15s ease-out}.ring-inset{--tw-ring-inset:inset}@media(hover:hover){.group-hover\:shadow-\[0_1px_3px_rgb\(0_0_0\/0\.6\)\,0_0_0_5px_var\(--halo\)\]:is(:where(.group):hover *){--tw-shadow:0 1px 3px var(--tw-shadow-color,#0009),0 0 0 5px var(--tw-shadow-color,var(--halo));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}.group-data-dragging\:scale-105:is(:where(.group)[data-dragging] *){--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x)var(--tw-scale-y)}.group-data-dragging\:shadow-\[0_1px_3px_rgb\(0_0_0\/0\.6\)\,0_0_0_7px_var\(--halo\)\]:is(:where(.group)[data-dragging] *){--tw-shadow:0 1px 3px var(--tw-shadow-color,#0009),0 0 0 7px var(--tw-shadow-color,var(--halo));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.group-data-dragging\:duration-0:is(:where(.group)[data-dragging] *){--tw-duration:0s;transition-duration:0s}.group-data-dragging\:\[transition\:box-shadow_150ms_ease-out\,scale_150ms_ease-out\]:is(:where(.group)[data-dragging] *){transition:box-shadow .15s ease-out,scale .15s ease-out}.group-data-panel-open\:rotate-180:is(:where(.group)[data-panel-open] *){rotate:180deg}.placeholder\:text-faint::placeholder{color:var(--color-faint)}.before\:absolute:before{content:var(--tw-content);position:absolute}.before\:inset-y-0:before{content:var(--tw-content);inset-block:calc(var(--spacing)*0)}.before\:left-0:before{content:var(--tw-content);left:calc(var(--spacing)*0)}.before\:w-0\.5:before{content:var(--tw-content);width:calc(var(--spacing)*.5)}.before\:bg-accent\/60:before{content:var(--tw-content);background-color:#ff963a99}@supports (color:color-mix(in lab,red,red)){.before\:bg-accent\/60:before{background-color:color-mix(in oklab,var(--color-accent)60%,transparent)}}.last\:border-0:last-child{border-style:var(--tw-border-style);border-width:0}.focus-within\:ring-accent:focus-within{--tw-ring-color:var(--color-accent)}@media(hover:hover){.hover\:bg-accent\/12:hover{background-color:#ff963a1f}@supports (color:color-mix(in lab,red,red)){.hover\:bg-accent\/12:hover{background-color:color-mix(in oklab,var(--color-accent)12%,transparent)}}.hover\:bg-bad\/20:hover{background-color:#ff636233}@supports (color:color-mix(in lab,red,red)){.hover\:bg-bad\/20:hover{background-color:color-mix(in oklab,var(--color-bad)20%,transparent)}}.hover\:bg-black\/85:hover{background-color:#000000d9}@supports (color:color-mix(in lab,red,red)){.hover\:bg-black\/85:hover{background-color:color-mix(in oklab,var(--color-black)85%,transparent)}}.hover\:bg-line:hover{background-color:var(--color-line)}.hover\:bg-raised:hover{background-color:var(--color-raised)}.hover\:bg-raised\/50:hover{background-color:#1b1d2380}@supports (color:color-mix(in lab,red,red)){.hover\:bg-raised\/50:hover{background-color:color-mix(in oklab,var(--color-raised)50%,transparent)}}.hover\:bg-raised\/60:hover{background-color:#1b1d2399}@supports (color:color-mix(in lab,red,red)){.hover\:bg-raised\/60:hover{background-color:color-mix(in oklab,var(--color-raised)60%,transparent)}}.hover\:text-accent:hover{color:var(--color-accent)}.hover\:text-bad:hover{color:var(--color-bad)}.hover\:text-ink:hover{color:var(--color-ink)}.hover\:ring-line-strong:hover{--tw-ring-color:var(--color-line-strong)}.hover\:brightness-\[1\.06\]:hover{--tw-brightness:brightness(1.06);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}}.focus\:bg-canvas:focus{background-color:var(--color-canvas)}.focus\:ring-accent:focus{--tw-ring-color:var(--color-accent)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.focus-visible\:opacity-100:focus-visible{opacity:1}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:bg-raised:disabled{background-color:var(--color-raised)}.disabled\:bg-none:disabled{background-image:none}.disabled\:text-faint:disabled{color:var(--color-faint)}.disabled\:opacity-80:disabled{opacity:.8}.disabled\:shadow-none:disabled{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.disabled\:ring-line:disabled{--tw-ring-color:var(--color-line)}.data-checked\:ml-auto[data-checked]{margin-left:auto}.data-checked\:bg-accent[data-checked]{background-color:var(--color-accent)}.data-checked\:bg-accent-ink[data-checked]{background-color:var(--color-accent-ink)}.data-disabled\:cursor-not-allowed[data-disabled]{cursor:not-allowed}.data-disabled\:text-faint\/50[data-disabled]{color:#6c707980}@supports (color:color-mix(in lab,red,red)){.data-disabled\:text-faint\/50[data-disabled]{color:color-mix(in oklab,var(--color-faint)50%,transparent)}}.data-disabled\:opacity-40[data-disabled]{opacity:.4}@media(hover:hover){.data-disabled\:hover\:bg-transparent[data-disabled]:hover{background-color:#0000}}.data-ending-style\:h-0[data-ending-style]{height:calc(var(--spacing)*0)}.data-ending-style\:scale-95[data-ending-style]{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y)}.data-ending-style\:\[transform\:translateX\(calc\(100\%-3rem\+2px\)\)\][data-ending-style]{transform:translate(calc(100% - 3rem + 2px))}.data-ending-style\:\[transform\:translateY\(calc\(100\%-3rem\+2px\)\)\][data-ending-style]{transform:translateY(calc(100% - 3rem + 2px))}.data-ending-style\:opacity-0[data-ending-style]{opacity:0}.data-ending-style\:duration-\[calc\(var\(--drawer-swipe-strength\)\*400ms\)\][data-ending-style]{--tw-duration:calc(var(--drawer-swipe-strength)*.4s);transition-duration:calc(var(--drawer-swipe-strength)*.4s)}.data-pressed\:bg-accent\/12[data-pressed]{background-color:#ff963a1f}@supports (color:color-mix(in lab,red,red)){.data-pressed\:bg-accent\/12[data-pressed]{background-color:color-mix(in oklab,var(--color-accent)12%,transparent)}}.data-pressed\:bg-bad\/12[data-pressed]{background-color:#ff63621f}@supports (color:color-mix(in lab,red,red)){.data-pressed\:bg-bad\/12[data-pressed]{background-color:color-mix(in oklab,var(--color-bad)12%,transparent)}}.data-pressed\:bg-line[data-pressed]{background-color:var(--color-line)}.data-pressed\:bg-raised[data-pressed]{background-color:var(--color-raised)}.data-pressed\:bg-surface[data-pressed]{background-color:var(--color-surface)}.data-pressed\:text-accent[data-pressed]{color:var(--color-accent)}.data-pressed\:ring-accent\/45[data-pressed]{--tw-ring-color:#ff963a73}@supports (color:color-mix(in lab,red,red)){.data-pressed\:ring-accent\/45[data-pressed]{--tw-ring-color:color-mix(in oklab,var(--color-accent)45%,transparent)}}.data-pressed\:brightness-\[0\.96\][data-pressed]{--tw-brightness:brightness(.96);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.data-starting-style\:h-0[data-starting-style]{height:calc(var(--spacing)*0)}.data-starting-style\:scale-95[data-starting-style]{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y)}.data-starting-style\:\[transform\:translateX\(calc\(100\%-3rem\+2px\)\)\][data-starting-style]{transform:translate(calc(100% - 3rem + 2px))}.data-starting-style\:\[transform\:translateY\(calc\(100\%-3rem\+2px\)\)\][data-starting-style]{transform:translateY(calc(100% - 3rem + 2px))}.data-starting-style\:opacity-0[data-starting-style]{opacity:0}.data-swiping\:duration-0[data-swiping]{--tw-duration:0s;transition-duration:0s}.data-swiping\:select-none[data-swiping]{-webkit-user-select:none;user-select:none}@media(min-width:40rem){.sm\:inline{display:inline}.sm\:w-\[calc\(36rem\+3rem\)\]{width:39rem}.sm\:max-w-\[calc\(100vw-2rem\+3rem\)\]{max-width:calc(100vw + 1rem)}.sm\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.sm\:opacity-0{opacity:0}@media(hover:hover){.sm\:group-hover\/tile\:opacity-100:is(:where(.group\/tile):hover *){opacity:1}}}@media(min-width:64rem){.lg\:h-auto{height:auto}.lg\:h-dvh{height:100dvh}.lg\:h-full{height:100%}.lg\:min-h-0{min-height:calc(var(--spacing)*0)}.lg\:flex-1{flex:1}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-\[minmax\(0\,22rem\)_minmax\(0\,1fr\)\]{grid-template-columns:minmax(0,22rem) minmax(0,1fr)}.lg\:overflow-hidden{overflow:hidden}.lg\:overflow-y-auto{overflow-y:auto}.lg\:overscroll-contain{overscroll-behavior:contain}.lg\:border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.lg\:p-5{padding:calc(var(--spacing)*5)}}@media(min-width:80rem){.xl\:grid-cols-\[minmax\(0\,24rem\)_minmax\(0\,1fr\)\]{grid-template-columns:minmax(0,24rem) minmax(0,1fr)}}.\[\&_svg\]\:size-4 svg{width:calc(var(--spacing)*4);height:calc(var(--spacing)*4)}.\[\&_svg\]\:shrink-0 svg{flex-shrink:0}.tabular{font-variant-numeric:tabular-nums}.scrollbar-slim{scrollbar-width:thin;scrollbar-color:var(--color-line-strong)transparent}.scrollbar-slim::-webkit-scrollbar{width:10px;height:10px}.scrollbar-slim::-webkit-scrollbar-thumb{background-clip:content-box;background-color:var(--color-line-strong);border:3px solid #0000;border-radius:999px}}@keyframes sweep{0%{transform:translate(-100%)}to{transform:translate(320%)}}.sweep{animation:1.5s cubic-bezier(.5,0,.5,1) infinite sweep}@media(prefers-reduced-motion:reduce){*,:before,:after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}.sweep{opacity:.5;width:100%!important;animation:none!important}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"<length-percentage>";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"<length-percentage>";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"<length-percentage>";inherits:false;initial-value:100%}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@property --tw-content{syntax:"*";inherits:false;initial-value:""}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse{50%{opacity:.5}}
|
|
|
|
|
|
frontend/dist/assets/index-DZbZsc56.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-content:""}}}@layer theme{:root,:host{--font-sans:"Inter",ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;--font-mono:ui-monospace,"SF Mono","JetBrains Mono",Menlo,monospace;--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--container-sm:24rem;--container-md:28rem;--container-lg:32rem;--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--leading-tight:1.25;--leading-relaxed:1.625;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--ease-in:cubic-bezier(.4,0,1,1);--ease-out:cubic-bezier(0,0,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite;--blur-sm:8px;--blur-md:12px;--aspect-video:16/9;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-canvas:oklch(14.8% .008 265);--color-sunken:oklch(11.8% .008 265);--color-surface:oklch(18.8% .009 265);--color-raised:oklch(23.2% .011 265);--color-line:oklch(28.8% .012 265);--color-line-strong:oklch(40% .016 265);--color-ink:oklch(97% .003 265);--color-muted:oklch(70.5% .014 265);--color-faint:oklch(54.5% .014 265);--color-accent:oklch(77% .168 57);--color-accent-soft:oklch(83% .13 62);--color-accent-ink:oklch(19% .045 57);--color-ok:oklch(77% .15 158);--color-warn:oklch(82% .15 88);--color-bad:oklch(70% .19 24)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}html{color-scheme:dark;background:var(--color-sunken)}body{background:var(--color-canvas);min-height:100vh;color:var(--color-ink);font-family:var(--font-sans);font-feature-settings:"cv05" 1,"ss01" 1,"tnum" 0;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;margin:0}#root{min-height:100vh}:focus-visible{outline:2px solid var(--color-accent);outline-offset:2px}::selection{background:#ff963a59}@supports (color:color-mix(in lab,red,red)){::selection{background:color-mix(in oklch,var(--color-accent)35%,transparent)}}}@layer components;@layer utilities{.pointer-events-none{pointer-events:none}.collapse{visibility:collapse}.visible{visibility:visible}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.inset-0{inset:calc(var(--spacing)*0)}.inset-y-0{inset-block:calc(var(--spacing)*0)}.top-0{top:calc(var(--spacing)*0)}.top-1\.5{top:calc(var(--spacing)*1.5)}.top-1\/2{top:50%}.right-1\.5{right:calc(var(--spacing)*1.5)}.bottom-0{bottom:calc(var(--spacing)*0)}.bottom-1\.5{bottom:calc(var(--spacing)*1.5)}.bottom-2{bottom:calc(var(--spacing)*2)}.left-0{left:calc(var(--spacing)*0)}.left-1\.5{left:calc(var(--spacing)*1.5)}.left-2{left:calc(var(--spacing)*2)}.z-20{z-index:20}.z-30{z-index:30}.z-40{z-index:40}.z-50{z-index:50}.z-60,.z-\[60\]{z-index:60}.mx-auto{margin-inline:auto}.mt-0\.5{margin-top:calc(var(--spacing)*.5)}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-1\.5{margin-top:calc(var(--spacing)*1.5)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-2\.5{margin-top:calc(var(--spacing)*2.5)}.mt-3{margin-top:calc(var(--spacing)*3)}.mt-3\.5{margin-top:calc(var(--spacing)*3.5)}.mt-4{margin-top:calc(var(--spacing)*4)}.mt-px{margin-top:1px}.-mr-12{margin-right:calc(var(--spacing)*-12)}.mr-auto{margin-right:auto}.-mb-12{margin-bottom:calc(var(--spacing)*-12)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-3{margin-bottom:calc(var(--spacing)*3)}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-flex{display:inline-flex}.table{display:table}.aspect-video{aspect-ratio:var(--aspect-video)}.size-1{width:calc(var(--spacing)*1);height:calc(var(--spacing)*1)}.size-1\.5{width:calc(var(--spacing)*1.5);height:calc(var(--spacing)*1.5)}.size-3{width:calc(var(--spacing)*3);height:calc(var(--spacing)*3)}.size-3\.5{width:calc(var(--spacing)*3.5);height:calc(var(--spacing)*3.5)}.size-4{width:calc(var(--spacing)*4);height:calc(var(--spacing)*4)}.size-5{width:calc(var(--spacing)*5);height:calc(var(--spacing)*5)}.size-6{width:calc(var(--spacing)*6);height:calc(var(--spacing)*6)}.size-7{width:calc(var(--spacing)*7);height:calc(var(--spacing)*7)}.size-8{width:calc(var(--spacing)*8);height:calc(var(--spacing)*8)}.size-9{width:calc(var(--spacing)*9);height:calc(var(--spacing)*9)}.size-full{width:100%;height:100%}.h-1{height:calc(var(--spacing)*1)}.h-1\.5{height:calc(var(--spacing)*1.5)}.h-5{height:calc(var(--spacing)*5)}.h-7{height:calc(var(--spacing)*7)}.h-8{height:calc(var(--spacing)*8)}.h-9{height:calc(var(--spacing)*9)}.h-10{height:calc(var(--spacing)*10)}.h-13{height:calc(var(--spacing)*13)}.h-36{height:calc(var(--spacing)*36)}.h-\[46vh\]{height:46vh}.h-\[var\(--collapsible-panel-height\)\]{height:var(--collapsible-panel-height)}.h-full{height:100%}.max-h-64{max-height:calc(var(--spacing)*64)}.max-h-\[88vh\]{max-height:88vh}.max-h-\[calc\(85dvh\+3rem\)\]{max-height:calc(85dvh + 3rem)}.max-h-\[min\(var\(--available-height\)\,34rem\)\]{max-height:min(var(--available-height),34rem)}.min-h-0{min-height:calc(var(--spacing)*0)}.min-h-5{min-height:calc(var(--spacing)*5)}.min-h-8{min-height:calc(var(--spacing)*8)}.min-h-\[88px\]{min-height:88px}.min-h-\[220px\]{min-height:220px}.min-h-dvh{min-height:100dvh}.w-1\/2{width:50%}.w-1\/3{width:33.3333%}.w-8{width:calc(var(--spacing)*8)}.w-9{width:calc(var(--spacing)*9)}.w-10{width:calc(var(--spacing)*10)}.w-36{width:calc(var(--spacing)*36)}.w-\[22rem\]{width:22rem}.w-\[24rem\]{width:24rem}.w-\[calc\(100\%\+3rem\)\]{width:calc(100% + 3rem)}.w-full{width:100%}.max-w-lg{max-width:var(--container-lg)}.max-w-md{max-width:var(--container-md)}.max-w-sm{max-width:var(--container-sm)}.max-w-xs{max-width:var(--container-xs)}.min-w-0{min-width:calc(var(--spacing)*0)}.min-w-\[3px\]{min-width:3px}.flex-1{flex:1}.shrink-0{flex-shrink:0}.grow{flex-grow:1}.origin-\[var\(--transform-origin\)\]{transform-origin:var(--transform-origin)}.-translate-x-1\/2{--tw-translate-x: -50% ;translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-y-1\/2{--tw-translate-y: -50% ;translate:var(--tw-translate-x)var(--tw-translate-y)}.\[transform\:translateX\(var\(--drawer-swipe-movement-x\)\)\]{transform:translate(var(--drawer-swipe-movement-x))}.\[transform\:translateY\(var\(--drawer-swipe-movement-y\)\)\]{transform:translateY(var(--drawer-swipe-movement-y))}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.animate-pulse{animation:var(--animate-pulse)}.animate-spin{animation:var(--animate-spin)}.cursor-pointer{cursor:pointer}.touch-auto{touch-action:auto}.touch-none{touch-action:none}.resize{resize:both}.resize-none{resize:none}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-\[minmax\(0\,1fr\)\]{grid-template-columns:minmax(0,1fr)}.grid-cols-\[repeat\(auto-fill\,minmax\(11rem\,1fr\)\)\]{grid-template-columns:repeat(auto-fill,minmax(11rem,1fr))}.flex-col{flex-direction:column}.flex-row{flex-direction:row}.flex-wrap{flex-wrap:wrap}.place-items-center{place-items:center}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.items-stretch{align-items:stretch}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-1{gap:calc(var(--spacing)*1)}.gap-1\.5{gap:calc(var(--spacing)*1.5)}.gap-2{gap:calc(var(--spacing)*2)}.gap-2\.5{gap:calc(var(--spacing)*2.5)}.gap-3{gap:calc(var(--spacing)*3)}.gap-3\.5{gap:calc(var(--spacing)*3.5)}.gap-4{gap:calc(var(--spacing)*4)}.gap-5{gap:calc(var(--spacing)*5)}.gap-7{gap:calc(var(--spacing)*7)}:where(.space-y-1\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1.5)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1.5)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*2.5)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*2.5)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*3)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*3)*calc(1 - var(--tw-space-y-reverse)))}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-line>:not(:last-child)){border-color:var(--color-line)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.overscroll-contain{overscroll-behavior:contain}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-\[3px\]{border-radius:3px}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-xl{border-radius:var(--radius-xl)}.rounded-t-2xl{border-top-left-radius:var(--radius-2xl);border-top-right-radius:var(--radius-2xl)}.rounded-t-xl{border-top-left-radius:var(--radius-xl);border-top-right-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-accent{border-color:var(--color-accent)}.border-line{border-color:var(--color-line)}.border-line-strong{border-color:var(--color-line-strong)}.bg-accent{background-color:var(--color-accent)}.bg-accent-ink\/50{background-color:#220e0080}@supports (color:color-mix(in lab,red,red)){.bg-accent-ink\/50{background-color:color-mix(in oklab,var(--color-accent-ink)50%,transparent)}}.bg-accent\/8{background-color:#ff963a14}@supports (color:color-mix(in lab,red,red)){.bg-accent\/8{background-color:color-mix(in oklab,var(--color-accent)8%,transparent)}}.bg-accent\/10{background-color:#ff963a1a}@supports (color:color-mix(in lab,red,red)){.bg-accent\/10{background-color:color-mix(in oklab,var(--color-accent)10%,transparent)}}.bg-accent\/12{background-color:#ff963a1f}@supports (color:color-mix(in lab,red,red)){.bg-accent\/12{background-color:color-mix(in oklab,var(--color-accent)12%,transparent)}}.bg-accent\/15{background-color:#ff963a26}@supports (color:color-mix(in lab,red,red)){.bg-accent\/15{background-color:color-mix(in oklab,var(--color-accent)15%,transparent)}}.bg-accent\/\[0\.045\]{background-color:#ff963a0b}@supports (color:color-mix(in lab,red,red)){.bg-accent\/\[0\.045\]{background-color:color-mix(in oklab,var(--color-accent)4.5%,transparent)}}.bg-bad{background-color:var(--color-bad)}.bg-bad\/12{background-color:#ff63621f}@supports (color:color-mix(in lab,red,red)){.bg-bad\/12{background-color:color-mix(in oklab,var(--color-bad)12%,transparent)}}.bg-black{background-color:var(--color-black)}.bg-black\/65{background-color:#000000a6}@supports (color:color-mix(in lab,red,red)){.bg-black\/65{background-color:color-mix(in oklab,var(--color-black)65%,transparent)}}.bg-black\/70{background-color:#000000b3}@supports (color:color-mix(in lab,red,red)){.bg-black\/70{background-color:color-mix(in oklab,var(--color-black)70%,transparent)}}.bg-black\/75{background-color:#000000bf}@supports (color:color-mix(in lab,red,red)){.bg-black\/75{background-color:color-mix(in oklab,var(--color-black)75%,transparent)}}.bg-canvas{background-color:var(--color-canvas)}.bg-canvas\/85{background-color:#090b0ed9}@supports (color:color-mix(in lab,red,red)){.bg-canvas\/85{background-color:color-mix(in oklab,var(--color-canvas)85%,transparent)}}.bg-canvas\/92{background-color:#090b0eeb}@supports (color:color-mix(in lab,red,red)){.bg-canvas\/92{background-color:color-mix(in oklab,var(--color-canvas)92%,transparent)}}.bg-ink{background-color:var(--color-ink)}.bg-line{background-color:var(--color-line)}.bg-line-strong{background-color:var(--color-line-strong)}.bg-ok{background-color:var(--color-ok)}.bg-ok\/12{background-color:#4cd18f1f}@supports (color:color-mix(in lab,red,red)){.bg-ok\/12{background-color:color-mix(in oklab,var(--color-ok)12%,transparent)}}.bg-raised{background-color:var(--color-raised)}.bg-sunken{background-color:var(--color-sunken)}.bg-surface{background-color:var(--color-surface)}.bg-transparent{background-color:#0000}.bg-warn{background-color:var(--color-warn)}.bg-linear-to-b{--tw-gradient-position:to bottom}@supports (background-image:linear-gradient(in lab,red,red)){.bg-linear-to-b{--tw-gradient-position:to bottom in oklab}}.bg-linear-to-b{background-image:linear-gradient(var(--tw-gradient-stops))}.from-accent-soft{--tw-gradient-from:var(--color-accent-soft);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-accent{--tw-gradient-to:var(--color-accent);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.object-contain{object-fit:contain}.object-cover{object-fit:cover}.p-1{padding:calc(var(--spacing)*1)}.p-2{padding:calc(var(--spacing)*2)}.p-2\.5{padding:calc(var(--spacing)*2.5)}.p-3{padding:calc(var(--spacing)*3)}.p-4{padding:calc(var(--spacing)*4)}.p-\[2px\]{padding:2px}.px-0{padding-inline:calc(var(--spacing)*0)}.px-1{padding-inline:calc(var(--spacing)*1)}.px-1\.5{padding-inline:calc(var(--spacing)*1.5)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-2\.5{padding-inline:calc(var(--spacing)*2.5)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-5{padding-inline:calc(var(--spacing)*5)}.px-6{padding-inline:calc(var(--spacing)*6)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1{padding-block:calc(var(--spacing)*1)}.py-1\.5{padding-block:calc(var(--spacing)*1.5)}.py-2{padding-block:calc(var(--spacing)*2)}.py-2\.5{padding-block:calc(var(--spacing)*2.5)}.py-3{padding-block:calc(var(--spacing)*3)}.py-4{padding-block:calc(var(--spacing)*4)}.py-5{padding-block:calc(var(--spacing)*5)}.pt-2\.5{padding-top:calc(var(--spacing)*2.5)}.pt-3{padding-top:calc(var(--spacing)*3)}.pr-1{padding-right:calc(var(--spacing)*1)}.pr-12{padding-right:calc(var(--spacing)*12)}.pb-1{padding-bottom:calc(var(--spacing)*1)}.pb-3{padding-bottom:calc(var(--spacing)*3)}.pb-\[calc\(1rem\+env\(safe-area-inset-bottom\,0px\)\+3rem\)\]{padding-bottom:calc(4rem + env(safe-area-inset-bottom,0px))}.text-center{text-align:center}.text-left{text-align:left}.font-mono{font-family:var(--font-mono)}.text-\[10\.5px\]{font-size:10.5px}.text-\[10px\]{font-size:10px}.text-\[11\.5px\]{font-size:11.5px}.text-\[11px\]{font-size:11px}.text-\[12\.5px\]{font-size:12.5px}.text-\[12px\]{font-size:12px}.text-\[13\.5px\]{font-size:13.5px}.text-\[13px\]{font-size:13px}.text-\[14px\]{font-size:14px}.text-\[15px\]{font-size:15px}.leading-3{--tw-leading:calc(var(--spacing)*3);line-height:calc(var(--spacing)*3)}.leading-4{--tw-leading:calc(var(--spacing)*4);line-height:calc(var(--spacing)*4)}.leading-\[1\.5\]{--tw-leading:1.5;line-height:1.5}.leading-\[1\.6\]{--tw-leading:1.6;line-height:1.6}.leading-\[1\.15\]{--tw-leading:1.15;line-height:1.15}.leading-\[1\.45\]{--tw-leading:1.45;line-height:1.45}.leading-\[1\.55\]{--tw-leading:1.55;line-height:1.55}.leading-\[1\.65\]{--tw-leading:1.65;line-height:1.65}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-\[\.1em\],.tracking-\[0\.1em\]{--tw-tracking:.1em;letter-spacing:.1em}.tracking-\[0\.06em\]{--tw-tracking:.06em;letter-spacing:.06em}.tracking-\[0\.08em\]{--tw-tracking:.08em;letter-spacing:.08em}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.break-words{overflow-wrap:break-word}.whitespace-nowrap{white-space:nowrap}.text-accent{color:var(--color-accent)}.text-accent-ink{color:var(--color-accent-ink)}.text-bad{color:var(--color-bad)}.text-faint{color:var(--color-faint)}.text-ink{color:var(--color-ink)}.text-muted{color:var(--color-muted)}.text-ok{color:var(--color-ok)}.text-warn{color:var(--color-warn)}.text-white{color:var(--color-white)}.capitalize{text-transform:capitalize}.uppercase{text-transform:uppercase}.underline{text-decoration-line:underline}.underline-offset-2{text-underline-offset:2px}.opacity-40{opacity:.4}.opacity-\[calc\(0\.65\*\(1-var\(--drawer-swipe-progress\)\)\)\]{opacity:calc(.65*(1 - var(--drawer-swipe-progress)))}.shadow-\[-24px_0_60px_-15px_rgba\(0\,0\,0\,0\.75\)\]{--tw-shadow:-24px 0 60px -15px var(--tw-shadow-color,#000000bf);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[0_-20px_60px_-15px_rgba\(0\,0\,0\,0\.7\)\]{--tw-shadow:0 -20px 60px -15px var(--tw-shadow-color,#000000b3);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[0_1px_2px_rgb\(0_0_0\/0\.5\)\]{--tw-shadow:0 1px 2px var(--tw-shadow-color,#00000080);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[0_1px_3px_rgb\(0_0_0\/0\.6\)\]{--tw-shadow:0 1px 3px var(--tw-shadow-color,#0009);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[0_8px_24px_-8px_rgb\(0_0_0\/0\.8\)\]{--tw-shadow:0 8px 24px -8px var(--tw-shadow-color,#000c);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[0_24px_60px_-15px_rgba\(0\,0\,0\,0\.75\)\]{--tw-shadow:0 24px 60px -15px var(--tw-shadow-color,#000000bf);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[inset_0_1px_0_rgb\(255_255_255\/0\.06\)\,0_1px_2px_rgb\(0_0_0\/0\.3\)\]{--tw-shadow:inset 0 1px 0 var(--tw-shadow-color,#ffffff0f),0 1px 2px var(--tw-shadow-color,#0000004d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[inset_0_1px_0_rgb\(255_255_255\/0\.06\)\]{--tw-shadow:inset 0 1px 0 var(--tw-shadow-color,#ffffff0f);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[inset_0_1px_0_rgb\(255_255_255\/0\.07\)\,0_1px_2px_rgb\(0_0_0\/0\.35\)\]{--tw-shadow:inset 0 1px 0 var(--tw-shadow-color,#ffffff12),0 1px 2px var(--tw-shadow-color,#00000059);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[inset_0_1px_0_rgb\(255_255_255\/0\.28\)\,0_1px_2px_rgb\(0_0_0\/0\.45\)\]{--tw-shadow:inset 0 1px 0 var(--tw-shadow-color,#ffffff47),0 1px 2px var(--tw-shadow-color,#00000073);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-1{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-2{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-accent{--tw-ring-color:var(--color-accent)}.ring-accent\/35{--tw-ring-color:#ff963a59}@supports (color:color-mix(in lab,red,red)){.ring-accent\/35{--tw-ring-color:color-mix(in oklab,var(--color-accent)35%,transparent)}}.ring-accent\/50{--tw-ring-color:#ff963a80}@supports (color:color-mix(in lab,red,red)){.ring-accent\/50{--tw-ring-color:color-mix(in oklab,var(--color-accent)50%,transparent)}}.ring-bad\/35{--tw-ring-color:#ff636259}@supports (color:color-mix(in lab,red,red)){.ring-bad\/35{--tw-ring-color:color-mix(in oklab,var(--color-bad)35%,transparent)}}.ring-line{--tw-ring-color:var(--color-line)}.ring-ok\/30{--tw-ring-color:#4cd18f4d}@supports (color:color-mix(in lab,red,red)){.ring-ok\/30{--tw-ring-color:color-mix(in oklab,var(--color-ok)30%,transparent)}}.ring-transparent{--tw-ring-color:transparent}.ring-white\/5{--tw-ring-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.ring-white\/5{--tw-ring-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.drop-shadow{--tw-drop-shadow-size:drop-shadow(0 1px 2px var(--tw-drop-shadow-color,#0000001a))drop-shadow(0 1px 1px var(--tw-drop-shadow-color,#0000000f));--tw-drop-shadow:drop-shadow(0 1px 2px #0000001a)drop-shadow(0 1px 1px #0000000f);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.backdrop-blur{--tw-backdrop-blur:blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.backdrop-blur-\[2px\]{--tw-backdrop-blur:blur(2px);-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.backdrop-blur-md{--tw-backdrop-blur:blur(var(--blur-md));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.backdrop-blur-sm{--tw-backdrop-blur:blur(var(--blur-sm));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[background-color\,color\,filter\,box-shadow\]{transition-property:background-color,color,filter,box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[box-shadow\,background-color\]{transition-property:box-shadow,background-color;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[color\,opacity\]{transition-property:color,opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[height\]{transition-property:height;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[opacity\,transform\]{transition-property:opacity,transform;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[width\]{transition-property:width;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-shadow{transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-100{--tw-duration:.1s;transition-duration:.1s}.duration-150{--tw-duration:.15s;transition-duration:.15s}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-500{--tw-duration:.5s;transition-duration:.5s}.duration-\[450ms\]{--tw-duration:.45s;transition-duration:.45s}.ease-\[cubic-bezier\(0\.32\,0\.72\,0\,1\)\]{--tw-ease:cubic-bezier(.32,.72,0,1);transition-timing-function:cubic-bezier(.32,.72,0,1)}.ease-in{--tw-ease:var(--ease-in);transition-timing-function:var(--ease-in)}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}.\[--halo\:color-mix\(in_oklch\,var\(--color-accent\)_28\%\,transparent\)\]{--halo:#ff963a47}@supports (color:color-mix(in lab,red,red)){.\[--halo\:color-mix\(in_oklch\,var\(--color-accent\)_28\%\,transparent\)\]{--halo:color-mix(in oklch,var(--color-accent)28%,transparent)}}.\[transition\:inset-inline-start_200ms_ease-out\,box-shadow_150ms_ease-out\,scale_150ms_ease-out\]{transition:inset-inline-start .2s ease-out,box-shadow .15s ease-out,scale .15s ease-out}.ring-inset{--tw-ring-inset:inset}@media(hover:hover){.group-hover\:shadow-\[0_1px_3px_rgb\(0_0_0\/0\.6\)\,0_0_0_5px_var\(--halo\)\]:is(:where(.group):hover *){--tw-shadow:0 1px 3px var(--tw-shadow-color,#0009),0 0 0 5px var(--tw-shadow-color,var(--halo));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}.group-data-dragging\:scale-105:is(:where(.group)[data-dragging] *){--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x)var(--tw-scale-y)}.group-data-dragging\:shadow-\[0_1px_3px_rgb\(0_0_0\/0\.6\)\,0_0_0_7px_var\(--halo\)\]:is(:where(.group)[data-dragging] *){--tw-shadow:0 1px 3px var(--tw-shadow-color,#0009),0 0 0 7px var(--tw-shadow-color,var(--halo));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.group-data-dragging\:duration-0:is(:where(.group)[data-dragging] *){--tw-duration:0s;transition-duration:0s}.group-data-dragging\:\[transition\:box-shadow_150ms_ease-out\,scale_150ms_ease-out\]:is(:where(.group)[data-dragging] *){transition:box-shadow .15s ease-out,scale .15s ease-out}.group-data-panel-open\:rotate-180:is(:where(.group)[data-panel-open] *){rotate:180deg}.placeholder\:text-faint::placeholder{color:var(--color-faint)}.before\:absolute:before{content:var(--tw-content);position:absolute}.before\:inset-y-0:before{content:var(--tw-content);inset-block:calc(var(--spacing)*0)}.before\:left-0:before{content:var(--tw-content);left:calc(var(--spacing)*0)}.before\:w-0\.5:before{content:var(--tw-content);width:calc(var(--spacing)*.5)}.before\:bg-accent\/60:before{content:var(--tw-content);background-color:#ff963a99}@supports (color:color-mix(in lab,red,red)){.before\:bg-accent\/60:before{background-color:color-mix(in oklab,var(--color-accent)60%,transparent)}}.last\:border-0:last-child{border-style:var(--tw-border-style);border-width:0}.focus-within\:ring-accent:focus-within{--tw-ring-color:var(--color-accent)}@media(hover:hover){.hover\:bg-accent\/12:hover{background-color:#ff963a1f}@supports (color:color-mix(in lab,red,red)){.hover\:bg-accent\/12:hover{background-color:color-mix(in oklab,var(--color-accent)12%,transparent)}}.hover\:bg-bad\/20:hover{background-color:#ff636233}@supports (color:color-mix(in lab,red,red)){.hover\:bg-bad\/20:hover{background-color:color-mix(in oklab,var(--color-bad)20%,transparent)}}.hover\:bg-black\/85:hover{background-color:#000000d9}@supports (color:color-mix(in lab,red,red)){.hover\:bg-black\/85:hover{background-color:color-mix(in oklab,var(--color-black)85%,transparent)}}.hover\:bg-line:hover{background-color:var(--color-line)}.hover\:bg-raised:hover{background-color:var(--color-raised)}.hover\:bg-raised\/50:hover{background-color:#1b1d2380}@supports (color:color-mix(in lab,red,red)){.hover\:bg-raised\/50:hover{background-color:color-mix(in oklab,var(--color-raised)50%,transparent)}}.hover\:bg-raised\/60:hover{background-color:#1b1d2399}@supports (color:color-mix(in lab,red,red)){.hover\:bg-raised\/60:hover{background-color:color-mix(in oklab,var(--color-raised)60%,transparent)}}.hover\:text-accent:hover{color:var(--color-accent)}.hover\:text-bad:hover{color:var(--color-bad)}.hover\:text-ink:hover{color:var(--color-ink)}.hover\:ring-line-strong:hover{--tw-ring-color:var(--color-line-strong)}.hover\:brightness-\[1\.06\]:hover{--tw-brightness:brightness(1.06);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}}.focus\:bg-canvas:focus{background-color:var(--color-canvas)}.focus\:ring-accent:focus{--tw-ring-color:var(--color-accent)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.focus-visible\:opacity-100:focus-visible{opacity:1}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:bg-raised:disabled{background-color:var(--color-raised)}.disabled\:bg-none:disabled{background-image:none}.disabled\:text-faint:disabled{color:var(--color-faint)}.disabled\:opacity-20:disabled{opacity:.2}.disabled\:opacity-25:disabled{opacity:.25}.disabled\:opacity-80:disabled{opacity:.8}.disabled\:shadow-none:disabled{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.disabled\:ring-line:disabled{--tw-ring-color:var(--color-line)}.data-checked\:ml-auto[data-checked]{margin-left:auto}.data-checked\:bg-accent[data-checked]{background-color:var(--color-accent)}.data-checked\:bg-accent-ink[data-checked]{background-color:var(--color-accent-ink)}.data-disabled\:cursor-not-allowed[data-disabled]{cursor:not-allowed}.data-disabled\:text-faint\/50[data-disabled]{color:#6c707980}@supports (color:color-mix(in lab,red,red)){.data-disabled\:text-faint\/50[data-disabled]{color:color-mix(in oklab,var(--color-faint)50%,transparent)}}.data-disabled\:opacity-40[data-disabled]{opacity:.4}@media(hover:hover){.data-disabled\:hover\:bg-transparent[data-disabled]:hover{background-color:#0000}}.data-ending-style\:h-0[data-ending-style]{height:calc(var(--spacing)*0)}.data-ending-style\:scale-95[data-ending-style]{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y)}.data-ending-style\:\[transform\:translateX\(calc\(100\%-3rem\+2px\)\)\][data-ending-style]{transform:translate(calc(100% - 3rem + 2px))}.data-ending-style\:\[transform\:translateY\(calc\(100\%-3rem\+2px\)\)\][data-ending-style]{transform:translateY(calc(100% - 3rem + 2px))}.data-ending-style\:opacity-0[data-ending-style]{opacity:0}.data-ending-style\:duration-\[calc\(var\(--drawer-swipe-strength\)\*400ms\)\][data-ending-style]{--tw-duration:calc(var(--drawer-swipe-strength)*.4s);transition-duration:calc(var(--drawer-swipe-strength)*.4s)}.data-pressed\:bg-accent\/12[data-pressed]{background-color:#ff963a1f}@supports (color:color-mix(in lab,red,red)){.data-pressed\:bg-accent\/12[data-pressed]{background-color:color-mix(in oklab,var(--color-accent)12%,transparent)}}.data-pressed\:bg-bad\/12[data-pressed]{background-color:#ff63621f}@supports (color:color-mix(in lab,red,red)){.data-pressed\:bg-bad\/12[data-pressed]{background-color:color-mix(in oklab,var(--color-bad)12%,transparent)}}.data-pressed\:bg-line[data-pressed]{background-color:var(--color-line)}.data-pressed\:bg-raised[data-pressed]{background-color:var(--color-raised)}.data-pressed\:bg-surface[data-pressed]{background-color:var(--color-surface)}.data-pressed\:text-accent[data-pressed]{color:var(--color-accent)}.data-pressed\:ring-accent\/45[data-pressed]{--tw-ring-color:#ff963a73}@supports (color:color-mix(in lab,red,red)){.data-pressed\:ring-accent\/45[data-pressed]{--tw-ring-color:color-mix(in oklab,var(--color-accent)45%,transparent)}}.data-pressed\:brightness-\[0\.96\][data-pressed]{--tw-brightness:brightness(.96);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.data-starting-style\:h-0[data-starting-style]{height:calc(var(--spacing)*0)}.data-starting-style\:scale-95[data-starting-style]{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y)}.data-starting-style\:\[transform\:translateX\(calc\(100\%-3rem\+2px\)\)\][data-starting-style]{transform:translate(calc(100% - 3rem + 2px))}.data-starting-style\:\[transform\:translateY\(calc\(100\%-3rem\+2px\)\)\][data-starting-style]{transform:translateY(calc(100% - 3rem + 2px))}.data-starting-style\:opacity-0[data-starting-style]{opacity:0}.data-swiping\:duration-0[data-swiping]{--tw-duration:0s;transition-duration:0s}.data-swiping\:select-none[data-swiping]{-webkit-user-select:none;user-select:none}@media(min-width:40rem){.sm\:inline{display:inline}.sm\:w-\[calc\(36rem\+3rem\)\]{width:39rem}.sm\:max-w-\[calc\(100vw-2rem\+3rem\)\]{max-width:calc(100vw + 1rem)}.sm\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.sm\:opacity-0{opacity:0}@media(hover:hover){.sm\:group-hover\/tile\:opacity-100:is(:where(.group\/tile):hover *){opacity:1}}}@media(min-width:64rem){.lg\:h-auto{height:auto}.lg\:h-dvh{height:100dvh}.lg\:h-full{height:100%}.lg\:min-h-0{min-height:calc(var(--spacing)*0)}.lg\:flex-1{flex:1}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-\[minmax\(0\,22rem\)_minmax\(0\,1fr\)\]{grid-template-columns:minmax(0,22rem) minmax(0,1fr)}.lg\:overflow-hidden{overflow:hidden}.lg\:overflow-y-auto{overflow-y:auto}.lg\:overscroll-contain{overscroll-behavior:contain}.lg\:border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.lg\:p-5{padding:calc(var(--spacing)*5)}}@media(min-width:80rem){.xl\:grid-cols-\[minmax\(0\,24rem\)_minmax\(0\,1fr\)\]{grid-template-columns:minmax(0,24rem) minmax(0,1fr)}}.\[\&_svg\]\:size-4 svg{width:calc(var(--spacing)*4);height:calc(var(--spacing)*4)}.\[\&_svg\]\:shrink-0 svg{flex-shrink:0}.\[\&\>svg\]\:size-4>svg{width:calc(var(--spacing)*4);height:calc(var(--spacing)*4)}.tabular{font-variant-numeric:tabular-nums}.scrollbar-slim{scrollbar-width:thin;scrollbar-color:var(--color-line-strong)transparent}.scrollbar-slim::-webkit-scrollbar{width:10px;height:10px}.scrollbar-slim::-webkit-scrollbar-thumb{background-clip:content-box;background-color:var(--color-line-strong);border:3px solid #0000;border-radius:999px}}@keyframes sweep{0%{transform:translate(-100%)}to{transform:translate(320%)}}.sweep{animation:1.5s cubic-bezier(.5,0,.5,1) infinite sweep}@media(prefers-reduced-motion:reduce){*,:before,:after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}.sweep{opacity:.5;width:100%!important;animation:none!important}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"<length-percentage>";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"<length-percentage>";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"<length-percentage>";inherits:false;initial-value:100%}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@property --tw-content{syntax:"*";inherits:false;initial-value:""}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse{50%{opacity:.5}}
|
frontend/dist/assets/index-DoM51C4t.js
DELETED
|
The diff for this file is too large to render.
See raw diff
|
|
|
frontend/dist/assets/index-Dxjs8B8O.js
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
frontend/dist/index.html
CHANGED
|
@@ -7,8 +7,8 @@
|
|
| 7 |
<meta name="theme-color" content="#0b0c0f" />
|
| 8 |
<meta name="description" content="Generate video with a synchronized soundtrack from a single prompt, on MiniMax-H3." />
|
| 9 |
<title>MiniMax-H3 Ultra Fast</title>
|
| 10 |
-
<script type="module" crossorigin src="/studio-assets/assets/index-
|
| 11 |
-
<link rel="stylesheet" crossorigin href="/studio-assets/assets/index-
|
| 12 |
</head>
|
| 13 |
<body>
|
| 14 |
<div id="root"></div>
|
|
|
|
| 7 |
<meta name="theme-color" content="#0b0c0f" />
|
| 8 |
<meta name="description" content="Generate video with a synchronized soundtrack from a single prompt, on MiniMax-H3." />
|
| 9 |
<title>MiniMax-H3 Ultra Fast</title>
|
| 10 |
+
<script type="module" crossorigin src="/studio-assets/assets/index-Dxjs8B8O.js"></script>
|
| 11 |
+
<link rel="stylesheet" crossorigin href="/studio-assets/assets/index-DZbZsc56.css">
|
| 12 |
</head>
|
| 13 |
<body>
|
| 14 |
<div id="root"></div>
|
frontend/src/App.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
| 2 |
-
import { fetchModelStatus, fetchStudioConfig, runGeneration } from "./api";
|
| 3 |
import { AboutSheet } from "./components/AboutSheet";
|
| 4 |
import { ComposeRail } from "./components/ComposeRail";
|
| 5 |
import { Header } from "./components/Header";
|
|
@@ -11,17 +11,32 @@ import { deleteHistoryItem, restoreHistory, saveHistoryItem } from "./lib/histor
|
|
| 11 |
import { estimateRuntime, loadRuntimeSamples, rememberRuntime, runtimeSampleFor } from "./lib/runtimeHistory";
|
| 12 |
import { findCanvas, snapFrames, FPS } from "./lib/studio";
|
| 13 |
import { draftValues, finalFrameFile, finalValues, recipeFrom, remixValues, valuesFromHistory } from "./lib/workflows";
|
| 14 |
-
import type { GenerationValues, HistoryItem, ModelStatus, RunProgress, StudioConfig } from "./types";
|
| 15 |
import { FALLBACK_CONFIG } from "./types";
|
| 16 |
|
| 17 |
const IDLE: RunProgress = { stage: "idle", label: "Ready", progress: null };
|
| 18 |
const STATUS_POLL_MS = 15_000;
|
| 19 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
function initialValues(config: StudioConfig): GenerationValues {
|
| 21 |
return {
|
| 22 |
prompt: "",
|
| 23 |
image: null,
|
| 24 |
lastImage: null,
|
|
|
|
|
|
|
| 25 |
canvas: config.default_canvas,
|
| 26 |
duration: config.duration.default,
|
| 27 |
seed: 42,
|
|
@@ -50,6 +65,9 @@ export default function App() {
|
|
| 50 |
const [error, setError] = useState<string | null>(null);
|
| 51 |
const [usageOpen, setUsageOpen] = useState(false);
|
| 52 |
const [aboutOpen, setAboutOpen] = useState(false);
|
|
|
|
|
|
|
|
|
|
| 53 |
const viewerRef = useRef<HTMLDivElement>(null);
|
| 54 |
|
| 55 |
const running =
|
|
@@ -122,6 +140,9 @@ export default function App() {
|
|
| 122 |
values.loraStrength,
|
| 123 |
]);
|
| 124 |
|
|
|
|
|
|
|
|
|
|
| 125 |
const update = useCallback(
|
| 126 |
<K extends keyof GenerationValues>(key: K, value: GenerationValues[K]) =>
|
| 127 |
setValues((current) => ({ ...current, [key]: value })),
|
|
@@ -154,6 +175,7 @@ export default function App() {
|
|
| 154 |
recipe: recipeFrom(requestValues),
|
| 155 |
sourceImage: requestValues.image,
|
| 156 |
sourceLastImage: requestValues.lastImage,
|
|
|
|
| 157 |
};
|
| 158 |
setHistory((current) => [item, ...current]);
|
| 159 |
// The UI can show the result immediately; durable browser storage continues without delaying completion.
|
|
@@ -168,6 +190,67 @@ export default function App() {
|
|
| 168 |
}
|
| 169 |
}
|
| 170 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 171 |
function remix(item: HistoryItem) {
|
| 172 |
setValues(remixValues(item, config));
|
| 173 |
setError(null);
|
|
@@ -209,15 +292,19 @@ export default function App() {
|
|
| 209 |
<div className="flex min-h-dvh flex-col lg:h-dvh lg:overflow-hidden">
|
| 210 |
<Header model={model} onOpenUsage={() => setUsageOpen(true)} onOpenAbout={() => setAboutOpen(true)} />
|
| 211 |
|
| 212 |
-
<main className="grid min-h-0 flex-1 lg:grid-cols-[minmax(0,22rem)_minmax(0,1fr)] xl:grid-cols-[minmax(0,24rem)_minmax(0,1fr)]">
|
| 213 |
-
<div className="min-h-0 border-line lg:border-r">
|
| 214 |
<ComposeRail
|
| 215 |
config={config}
|
| 216 |
values={values}
|
| 217 |
update={update}
|
| 218 |
onApplyExample={applyExample}
|
| 219 |
-
onGenerate={() => void generate()}
|
| 220 |
-
onGenerateDraft={() => void generate(draftValues(values, config))}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 221 |
running={running}
|
| 222 |
blockedReason={blockedReason}
|
| 223 |
runtimeEstimate={runtimeEstimate}
|
|
@@ -242,6 +329,17 @@ export default function App() {
|
|
| 242 |
onContinue={(item) => void continueFrom(item)}
|
| 243 |
onRemix={remix}
|
| 244 |
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 245 |
<History
|
| 246 |
items={history}
|
| 247 |
selectedId={selectedId}
|
|
|
|
| 1 |
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
| 2 |
+
import { fetchModelStatus, fetchStudioConfig, runGeneration, stitchVideos } from "./api";
|
| 3 |
import { AboutSheet } from "./components/AboutSheet";
|
| 4 |
import { ComposeRail } from "./components/ComposeRail";
|
| 5 |
import { Header } from "./components/Header";
|
|
|
|
| 11 |
import { estimateRuntime, loadRuntimeSamples, rememberRuntime, runtimeSampleFor } from "./lib/runtimeHistory";
|
| 12 |
import { findCanvas, snapFrames, FPS } from "./lib/studio";
|
| 13 |
import { draftValues, finalFrameFile, finalValues, recipeFrom, remixValues, valuesFromHistory } from "./lib/workflows";
|
| 14 |
+
import type { GenerationValues, HistoryItem, ModelStatus, RunProgress, StoryboardShot, StudioConfig, StudioMode } from "./types";
|
| 15 |
import { FALLBACK_CONFIG } from "./types";
|
| 16 |
|
| 17 |
const IDLE: RunProgress = { stage: "idle", label: "Ready", progress: null };
|
| 18 |
const STATUS_POLL_MS = 15_000;
|
| 19 |
|
| 20 |
+
function storedMode(): StudioMode {
|
| 21 |
+
try { return localStorage.getItem("h3-studio-mode") === "storyboard" ? "storyboard" : "single"; }
|
| 22 |
+
catch { return "single"; }
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
function storedShots(): StoryboardShot[] {
|
| 26 |
+
const fresh = () => [{ id: crypto.randomUUID(), prompt: "" }, { id: crypto.randomUUID(), prompt: "" }];
|
| 27 |
+
try {
|
| 28 |
+
const parsed = JSON.parse(localStorage.getItem("h3-storyboard") || "null");
|
| 29 |
+
return Array.isArray(parsed) && parsed.length >= 2 ? parsed : fresh();
|
| 30 |
+
} catch { return fresh(); }
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
function initialValues(config: StudioConfig): GenerationValues {
|
| 34 |
return {
|
| 35 |
prompt: "",
|
| 36 |
image: null,
|
| 37 |
lastImage: null,
|
| 38 |
+
referenceMode: "keyframes",
|
| 39 |
+
references: [],
|
| 40 |
canvas: config.default_canvas,
|
| 41 |
duration: config.duration.default,
|
| 42 |
seed: 42,
|
|
|
|
| 65 |
const [error, setError] = useState<string | null>(null);
|
| 66 |
const [usageOpen, setUsageOpen] = useState(false);
|
| 67 |
const [aboutOpen, setAboutOpen] = useState(false);
|
| 68 |
+
const [mode, setMode] = useState<StudioMode>(storedMode);
|
| 69 |
+
const [shots, setShots] = useState<StoryboardShot[]>(storedShots);
|
| 70 |
+
const [storyClips, setStoryClips] = useState<HistoryItem[]>([]);
|
| 71 |
const viewerRef = useRef<HTMLDivElement>(null);
|
| 72 |
|
| 73 |
const running =
|
|
|
|
| 140 |
values.loraStrength,
|
| 141 |
]);
|
| 142 |
|
| 143 |
+
useEffect(() => { try { localStorage.setItem("h3-studio-mode", mode); } catch { /* optional */ } }, [mode]);
|
| 144 |
+
useEffect(() => { try { localStorage.setItem("h3-storyboard", JSON.stringify(shots)); } catch { /* optional */ } }, [shots]);
|
| 145 |
+
|
| 146 |
const update = useCallback(
|
| 147 |
<K extends keyof GenerationValues>(key: K, value: GenerationValues[K]) =>
|
| 148 |
setValues((current) => ({ ...current, [key]: value })),
|
|
|
|
| 175 |
recipe: recipeFrom(requestValues),
|
| 176 |
sourceImage: requestValues.image,
|
| 177 |
sourceLastImage: requestValues.lastImage,
|
| 178 |
+
sourceReferences: requestValues.references.map((reference) => ({ name: reference.file.name, type: reference.file.type, blob: reference.file })),
|
| 179 |
};
|
| 180 |
setHistory((current) => [item, ...current]);
|
| 181 |
// The UI can show the result immediately; durable browser storage continues without delaying completion.
|
|
|
|
| 190 |
}
|
| 191 |
}
|
| 192 |
|
| 193 |
+
async function generateStoryboard(draft: boolean) {
|
| 194 |
+
const authored = shots.filter((shot) => shot.prompt.trim());
|
| 195 |
+
if (running || authored.length < 2) return;
|
| 196 |
+
setError(null); setStoryClips([]);
|
| 197 |
+
viewerRef.current?.scrollIntoView({ behavior: "smooth", block: "start" });
|
| 198 |
+
const completed: HistoryItem[] = [];
|
| 199 |
+
let continuation: File | null = null;
|
| 200 |
+
try {
|
| 201 |
+
for (let index = 0; index < authored.length; index++) {
|
| 202 |
+
let requestValues: GenerationValues = {
|
| 203 |
+
...values,
|
| 204 |
+
prompt: authored[index].prompt,
|
| 205 |
+
seed: values.seed + index,
|
| 206 |
+
image: index === 0 ? values.image : continuation,
|
| 207 |
+
lastImage: index === authored.length - 1 ? values.lastImage : null,
|
| 208 |
+
};
|
| 209 |
+
if (draft) requestValues = draftValues(requestValues, config);
|
| 210 |
+
if (requestValues.referenceMode === "omni" && continuation) {
|
| 211 |
+
requestValues = {
|
| 212 |
+
...requestValues, image: null, lastImage: null,
|
| 213 |
+
references: [{ id: `continuity-${index}`, file: continuation, kind: "image" }, ...requestValues.references],
|
| 214 |
+
};
|
| 215 |
+
}
|
| 216 |
+
const estimate = estimateRuntime(runtimeSamples, config, requestValues);
|
| 217 |
+
const result = await runGeneration(requestValues, (next) => setProgress({
|
| 218 |
+
...next,
|
| 219 |
+
label: `Shot ${index + 1}/${authored.length} · ${next.label}`,
|
| 220 |
+
progress: next.progress == null ? index / authored.length : (index + next.progress) / authored.length,
|
| 221 |
+
}), estimate);
|
| 222 |
+
const item: HistoryItem = {
|
| 223 |
+
...result,
|
| 224 |
+
id: crypto.randomUUID(), createdAt: Date.now() + index,
|
| 225 |
+
prompt: requestValues.prompt, canvas: findCanvas(config, requestValues.canvas),
|
| 226 |
+
seconds: snapFrames(requestValues.duration) / FPS, seed: requestValues.seed,
|
| 227 |
+
preset: `Shot ${index + 1} · ${requestValues.preset}`, recipe: recipeFrom(requestValues),
|
| 228 |
+
sourceImage: requestValues.image, sourceLastImage: requestValues.lastImage,
|
| 229 |
+
sourceReferences: requestValues.references.map((r) => ({ name: r.file.name, type: r.file.type, blob: r.file })),
|
| 230 |
+
};
|
| 231 |
+
completed.push(item); setStoryClips([...completed]);
|
| 232 |
+
setHistory((current) => [item, ...current]); void saveHistoryItem(item);
|
| 233 |
+
continuation = await finalFrameFile(result.url);
|
| 234 |
+
}
|
| 235 |
+
setProgress({ stage: "generating", phase: "finalizing", label: "Assembling storyboard", progress: .98 });
|
| 236 |
+
const url = await stitchVideos(completed.map((item) => item.url));
|
| 237 |
+
const film: HistoryItem = {
|
| 238 |
+
...completed[0], id: crypto.randomUUID(), createdAt: Date.now(), url,
|
| 239 |
+
prompt: `Storyboard · ${authored.map((shot) => shot.prompt).join(" / ")}`,
|
| 240 |
+
seconds: completed.reduce((sum, item) => sum + item.seconds, 0),
|
| 241 |
+
preset: `Storyboard · ${draft ? "Draft" : "Final"}`,
|
| 242 |
+
report: `${completed.length} connected shots · ${completed.reduce((sum, item) => sum + item.seconds, 0).toFixed(1)} s · CPU assembled`,
|
| 243 |
+
runtimeSeconds: completed.reduce((sum, item) => sum + item.runtimeSeconds, 0),
|
| 244 |
+
};
|
| 245 |
+
setHistory((current) => [film, ...current]); setSelectedId(film.id); void saveHistoryItem(film);
|
| 246 |
+
setProgress({ stage: "complete", label: "Storyboard complete", progress: 1, exact: true });
|
| 247 |
+
} catch (caught) {
|
| 248 |
+
const message = caught instanceof Error ? caught.message : "Storyboard generation failed.";
|
| 249 |
+
setError(completed.length ? `${message} Your ${completed.length} completed shot${completed.length === 1 ? " is" : "s are"} saved below.` : message);
|
| 250 |
+
setProgress({ stage: "error", label: message, progress: null });
|
| 251 |
+
}
|
| 252 |
+
}
|
| 253 |
+
|
| 254 |
function remix(item: HistoryItem) {
|
| 255 |
setValues(remixValues(item, config));
|
| 256 |
setError(null);
|
|
|
|
| 292 |
<div className="flex min-h-dvh flex-col lg:h-dvh lg:overflow-hidden">
|
| 293 |
<Header model={model} onOpenUsage={() => setUsageOpen(true)} onOpenAbout={() => setAboutOpen(true)} />
|
| 294 |
|
| 295 |
+
<main className="grid min-h-0 min-w-0 flex-1 grid-cols-[minmax(0,1fr)] lg:grid-cols-[minmax(0,22rem)_minmax(0,1fr)] xl:grid-cols-[minmax(0,24rem)_minmax(0,1fr)]">
|
| 296 |
+
<div className="min-h-0 min-w-0 border-line lg:border-r">
|
| 297 |
<ComposeRail
|
| 298 |
config={config}
|
| 299 |
values={values}
|
| 300 |
update={update}
|
| 301 |
onApplyExample={applyExample}
|
| 302 |
+
onGenerate={() => mode === "storyboard" ? void generateStoryboard(false) : void generate()}
|
| 303 |
+
onGenerateDraft={() => mode === "storyboard" ? void generateStoryboard(true) : void generate(draftValues(values, config))}
|
| 304 |
+
mode={mode}
|
| 305 |
+
onModeChange={setMode}
|
| 306 |
+
shots={shots}
|
| 307 |
+
onShotsChange={setShots}
|
| 308 |
running={running}
|
| 309 |
blockedReason={blockedReason}
|
| 310 |
runtimeEstimate={runtimeEstimate}
|
|
|
|
| 329 |
onContinue={(item) => void continueFrom(item)}
|
| 330 |
onRemix={remix}
|
| 331 |
/>
|
| 332 |
+
{mode === "storyboard" && storyClips.length > 0 && (
|
| 333 |
+
<section className="mt-3 shrink-0 rounded-xl bg-sunken p-3 ring-1 ring-inset ring-line">
|
| 334 |
+
<p className="mb-2 text-[10.5px] font-semibold uppercase tracking-[.1em] text-faint">Storyboard timeline</p>
|
| 335 |
+
<div className="flex gap-2 overflow-x-auto pb-1">
|
| 336 |
+
{storyClips.map((clip, index) => <button key={clip.id} onClick={() => setSelectedId(clip.id)} className="w-36 shrink-0 overflow-hidden rounded-lg bg-black ring-1 ring-line">
|
| 337 |
+
<video src={`${clip.url}#t=.1`} muted preload="metadata" className="aspect-video w-full object-cover" />
|
| 338 |
+
<span className="block truncate px-2 py-1.5 text-left text-[10.5px] text-muted">{index + 1}. {clip.prompt}</span>
|
| 339 |
+
</button>)}
|
| 340 |
+
</div>
|
| 341 |
+
</section>
|
| 342 |
+
)}
|
| 343 |
<History
|
| 344 |
items={history}
|
| 345 |
selectedId={selectedId}
|
frontend/src/api.ts
CHANGED
|
@@ -71,10 +71,13 @@ function statusLabel(message: StatusMessage, startedAt: number, previous: RunPro
|
|
| 71 |
}
|
| 72 |
// Gradio 6 emits explicit gr.Progress packets with stage="pending" even after execution starts.
|
| 73 |
if (message.stage === "generating" || message.stage === "streaming" || isProgressPacket) {
|
|
|
|
|
|
|
|
|
|
| 74 |
const trackedStep = progressItem?.index != null && progressItem.length != null && progressItem.length > 0;
|
| 75 |
const rawFraction = progressItem?.progress ?? (trackedStep ? progressItem.index! / progressItem.length! : null);
|
| 76 |
const gpuInitialization = progressItem?.desc === "ZeroGPU init";
|
| 77 |
-
const denoising = trackedStep && !gpuInitialization;
|
| 78 |
const gpuPreflight = !trackedStep && progressItem?.desc?.toLowerCase().includes("denoising") === true;
|
| 79 |
let phase: RunProgress["phase"] = denoising
|
| 80 |
? "denoising"
|
|
@@ -106,7 +109,7 @@ function statusLabel(message: StatusMessage, startedAt: number, previous: RunPro
|
|
| 106 |
return {
|
| 107 |
stage: "generating",
|
| 108 |
label: denoising
|
| 109 |
-
? progressItem?.desc || "Denoising video and audio"
|
| 110 |
: phase === "finalizing"
|
| 111 |
? "Finalizing video and audio"
|
| 112 |
: phase === "gpu"
|
|
@@ -121,6 +124,7 @@ function statusLabel(message: StatusMessage, startedAt: number, previous: RunPro
|
|
| 121 |
unit: progressItem?.unit ?? undefined,
|
| 122 |
exact,
|
| 123 |
phase,
|
|
|
|
| 124 |
};
|
| 125 |
}
|
| 126 |
if (message.stage === "complete") {
|
|
@@ -172,8 +176,8 @@ export async function runGeneration(
|
|
| 172 |
const client = await getClient();
|
| 173 |
const submission = client.submit("/generate", {
|
| 174 |
prompt: values.prompt,
|
| 175 |
-
image_path: values.image ? handle_file(values.image) : null,
|
| 176 |
-
last_image_path: values.lastImage ? handle_file(values.lastImage) : null,
|
| 177 |
canvas: values.canvas,
|
| 178 |
duration: values.duration,
|
| 179 |
steps: values.steps,
|
|
@@ -185,6 +189,8 @@ export async function runGeneration(
|
|
| 185 |
lora_filename: values.loraFilename,
|
| 186 |
lora_strength: values.loraStrength,
|
| 187 |
generation_preset: values.preset,
|
|
|
|
|
|
|
| 188 |
});
|
| 189 |
|
| 190 |
let result: unknown[] | null = null;
|
|
@@ -232,3 +238,20 @@ export async function runGeneration(
|
|
| 232 |
runtimeSeconds: (Date.now() - (runtimeStartedAt ?? startedAt)) / 1000,
|
| 233 |
};
|
| 234 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
}
|
| 72 |
// Gradio 6 emits explicit gr.Progress packets with stage="pending" even after execution starts.
|
| 73 |
if (message.stage === "generating" || message.stage === "streaming" || isProgressPacket) {
|
| 74 |
+
const previewParts = progressItem?.desc?.startsWith("TAE_PREVIEW|")
|
| 75 |
+
? progressItem.desc.split("|", 3)
|
| 76 |
+
: null;
|
| 77 |
const trackedStep = progressItem?.index != null && progressItem.length != null && progressItem.length > 0;
|
| 78 |
const rawFraction = progressItem?.progress ?? (trackedStep ? progressItem.index! / progressItem.length! : null);
|
| 79 |
const gpuInitialization = progressItem?.desc === "ZeroGPU init";
|
| 80 |
+
const denoising = (trackedStep && !gpuInitialization) || Boolean(previewParts);
|
| 81 |
const gpuPreflight = !trackedStep && progressItem?.desc?.toLowerCase().includes("denoising") === true;
|
| 82 |
let phase: RunProgress["phase"] = denoising
|
| 83 |
? "denoising"
|
|
|
|
| 109 |
return {
|
| 110 |
stage: "generating",
|
| 111 |
label: denoising
|
| 112 |
+
? previewParts?.[2] || progressItem?.desc || "Denoising video and audio"
|
| 113 |
: phase === "finalizing"
|
| 114 |
? "Finalizing video and audio"
|
| 115 |
: phase === "gpu"
|
|
|
|
| 124 |
unit: progressItem?.unit ?? undefined,
|
| 125 |
exact,
|
| 126 |
phase,
|
| 127 |
+
previewUrl: previewParts?.[1] || previous.previewUrl,
|
| 128 |
};
|
| 129 |
}
|
| 130 |
if (message.stage === "complete") {
|
|
|
|
| 176 |
const client = await getClient();
|
| 177 |
const submission = client.submit("/generate", {
|
| 178 |
prompt: values.prompt,
|
| 179 |
+
image_path: values.referenceMode === "keyframes" && values.image ? handle_file(values.image) : null,
|
| 180 |
+
last_image_path: values.referenceMode === "keyframes" && values.lastImage ? handle_file(values.lastImage) : null,
|
| 181 |
canvas: values.canvas,
|
| 182 |
duration: values.duration,
|
| 183 |
steps: values.steps,
|
|
|
|
| 189 |
lora_filename: values.loraFilename,
|
| 190 |
lora_strength: values.loraStrength,
|
| 191 |
generation_preset: values.preset,
|
| 192 |
+
references:
|
| 193 |
+
values.referenceMode === "omni" ? values.references.map((reference) => handle_file(reference.file)) : [],
|
| 194 |
});
|
| 195 |
|
| 196 |
let result: unknown[] | null = null;
|
|
|
|
| 238 |
runtimeSeconds: (Date.now() - (runtimeStartedAt ?? startedAt)) / 1000,
|
| 239 |
};
|
| 240 |
}
|
| 241 |
+
|
| 242 |
+
/** Assemble generated storyboard shots on the CPU endpoint, so editing never books another GPU. */
|
| 243 |
+
export async function stitchVideos(urls: string[]): Promise<string> {
|
| 244 |
+
const { handle_file } = await import("@gradio/client");
|
| 245 |
+
const files = await Promise.all(
|
| 246 |
+
urls.map(async (url, index) => {
|
| 247 |
+
const response = await fetch(url);
|
| 248 |
+
if (!response.ok) throw new Error(`Could not read storyboard shot ${index + 1}.`);
|
| 249 |
+
return new File([await response.blob()], `shot-${index + 1}.mp4`, { type: "video/mp4" });
|
| 250 |
+
}),
|
| 251 |
+
);
|
| 252 |
+
const client = await getClient();
|
| 253 |
+
const result = await client.predict("/stitch", { clips: files.map(handle_file) });
|
| 254 |
+
const data = unwrapServerOutput(Array.isArray(result.data) ? result.data : [result.data]);
|
| 255 |
+
if (!data.length) throw new Error("The storyboard assembler returned no video.");
|
| 256 |
+
return outputUrl(data[0] as FilePayload | string);
|
| 257 |
+
}
|
frontend/src/components/AboutSheet.tsx
CHANGED
|
@@ -11,6 +11,7 @@ const LINKS = [
|
|
| 11 |
["NVFP4 checkpoint", "https://huggingface.co/lilcheaty/MiniMax-H3-NVFP4"],
|
| 12 |
["4-step Turbo LoRA", "https://huggingface.co/lightx2v/Minimax-h3-Turbo"],
|
| 13 |
["Sana / Sol-Engine", "https://github.com/NVlabs/Sana/tree/sol-engine/models/minimax_h3/optimized"],
|
|
|
|
| 14 |
];
|
| 15 |
|
| 16 |
/**
|
|
@@ -104,6 +105,24 @@ export function AboutSheet({ open, onClose }: { open: boolean; onClose: () => vo
|
|
| 104 |
</P>
|
| 105 |
</Part>
|
| 106 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
<Part title="Things that will surprise you">
|
| 108 |
<P>
|
| 109 |
<Term>Lengths snap.</Term> The video VAE decodes 17n + 5 frames at 24 fps and nothing else, so a request for
|
|
|
|
| 11 |
["NVFP4 checkpoint", "https://huggingface.co/lilcheaty/MiniMax-H3-NVFP4"],
|
| 12 |
["4-step Turbo LoRA", "https://huggingface.co/lightx2v/Minimax-h3-Turbo"],
|
| 13 |
["Sana / Sol-Engine", "https://github.com/NVlabs/Sana/tree/sol-engine/models/minimax_h3/optimized"],
|
| 14 |
+
["H3 TAE previews", "https://huggingface.co/Kijai/MiniMax-H3-TAE"],
|
| 15 |
];
|
| 16 |
|
| 17 |
/**
|
|
|
|
| 105 |
</P>
|
| 106 |
</Part>
|
| 107 |
|
| 108 |
+
<Part title="Creative workflows">
|
| 109 |
+
<P>
|
| 110 |
+
<Term>Reference studio</Term> runs MiniMax-H3’s Ref2VA checkpoint with up to 12 ordered references: nine
|
| 111 |
+
images, three videos and three audio files within that total. Reference order is preserved through both
|
| 112 |
+
the local Qwen conditioner and the joint video/audio denoiser. It stays on the full 28-step quality path.
|
| 113 |
+
</P>
|
| 114 |
+
<P>
|
| 115 |
+
<Term>Storyboard</Term> renders shots in order, captures each finished clip’s last frame locally, and uses
|
| 116 |
+
it to anchor the next shot. The clips are joined on CPU with a one-frame seam trim, so assembly does not
|
| 117 |
+
consume another ZeroGPU allocation. Shot recipes and source references remain in browser history.
|
| 118 |
+
</P>
|
| 119 |
+
<P>
|
| 120 |
+
<Term>TAE live preview</Term> decodes a tiny animated approximation at denoising milestones. It adds a
|
| 121 |
+
little work but gives useful visual feedback before the full video and audio VAEs finish; exported frames
|
| 122 |
+
always come from the original full-precision VAE.
|
| 123 |
+
</P>
|
| 124 |
+
</Part>
|
| 125 |
+
|
| 126 |
<Part title="Things that will surprise you">
|
| 127 |
<P>
|
| 128 |
<Term>Lengths snap.</Term> The video VAE decodes 17n + 5 frames at 24 fps and nothing else, so a request for
|
frontend/src/components/ComposeRail.tsx
CHANGED
|
@@ -5,14 +5,16 @@ import { AnimatePresence, motion } from "framer-motion";
|
|
| 5 |
import { cx } from "../lib/cx";
|
| 6 |
import { FADE, POP } from "../lib/motion";
|
| 7 |
import { findPreset, formatBudget } from "../lib/studio";
|
| 8 |
-
import type { GenerationValues, RuntimeEstimate, StudioConfig } from "../types";
|
| 9 |
import { Button } from "../ui/Button";
|
| 10 |
import { Section } from "../ui/Section";
|
|
|
|
|
|
|
|
|
|
| 11 |
import {
|
| 12 |
budgetFor,
|
| 13 |
fasterOption,
|
| 14 |
FormatSettings,
|
| 15 |
-
KeyframeSettings,
|
| 16 |
LengthSettings,
|
| 17 |
SeedSettings,
|
| 18 |
SpeedSettings,
|
|
@@ -25,6 +27,10 @@ type Props = {
|
|
| 25 |
onApplyExample: (prompt: string, canvas: string) => void;
|
| 26 |
onGenerate: () => void;
|
| 27 |
onGenerateDraft: () => void;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
running: boolean;
|
| 29 |
/** Non-null when generating is impossible right now — shown in place of the GPU estimate. */
|
| 30 |
blockedReason: string | null;
|
|
@@ -50,6 +56,10 @@ export function ComposeRail({
|
|
| 50 |
onApplyExample,
|
| 51 |
onGenerate,
|
| 52 |
onGenerateDraft,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
running,
|
| 54 |
blockedReason,
|
| 55 |
runtimeEstimate,
|
|
@@ -59,7 +69,9 @@ export function ComposeRail({
|
|
| 59 |
const steps = preset.custom ? values.steps : preset.steps;
|
| 60 |
const budget = budgetFor(config, values, steps);
|
| 61 |
const faster = fasterOption(config, values);
|
| 62 |
-
const
|
|
|
|
|
|
|
| 63 |
|
| 64 |
// Grow the prompt with its content instead of scrolling inside a fixed box, up to a point where the rail takes over.
|
| 65 |
useEffect(() => {
|
|
@@ -72,6 +84,12 @@ export function ComposeRail({
|
|
| 72 |
return (
|
| 73 |
<div className="flex min-h-0 flex-col lg:h-full">
|
| 74 |
<div className="scrollbar-slim divide-y divide-line lg:min-h-0 lg:flex-1 lg:overflow-y-auto lg:overscroll-contain">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
<Section
|
| 76 |
title="Speed"
|
| 77 |
tone="accent"
|
|
@@ -103,6 +121,8 @@ export function ComposeRail({
|
|
| 103 |
<SpeedSettings config={config} values={values} update={update} runtimeEstimate={runtimeEstimate} />
|
| 104 |
</Section>
|
| 105 |
|
|
|
|
|
|
|
| 106 |
<Section
|
| 107 |
title="Prompt"
|
| 108 |
action={
|
|
@@ -163,9 +183,10 @@ export function ComposeRail({
|
|
| 163 |
))}
|
| 164 |
</div>
|
| 165 |
</Section>
|
|
|
|
| 166 |
|
| 167 |
<Section title="References" defaultOpen>
|
| 168 |
-
<
|
| 169 |
</Section>
|
| 170 |
|
| 171 |
<Section title="Format" defaultOpen={false}>
|
|
@@ -202,13 +223,13 @@ export function ComposeRail({
|
|
| 202 |
</AnimatePresence>
|
| 203 |
</div>
|
| 204 |
|
| 205 |
-
<Button variant="outline" size="lg" disabled={!ready} onClick={onGenerateDraft}>
|
| 206 |
{running ? <Loader2 className="animate-spin" /> : <Clapperboard />}
|
| 207 |
Draft
|
| 208 |
</Button>
|
| 209 |
<Button variant="primary" size="lg" disabled={!ready} onClick={onGenerate}>
|
| 210 |
{running ? <Loader2 className="animate-spin" /> : <Zap fill="currentColor" />}
|
| 211 |
-
{running ? "Generating…" : "Generate"}
|
| 212 |
</Button>
|
| 213 |
</div>
|
| 214 |
</div>
|
|
|
|
| 5 |
import { cx } from "../lib/cx";
|
| 6 |
import { FADE, POP } from "../lib/motion";
|
| 7 |
import { findPreset, formatBudget } from "../lib/studio";
|
| 8 |
+
import type { GenerationValues, RuntimeEstimate, StoryboardShot, StudioConfig, StudioMode } from "../types";
|
| 9 |
import { Button } from "../ui/Button";
|
| 10 |
import { Section } from "../ui/Section";
|
| 11 |
+
import { Segmented } from "../ui/Segmented";
|
| 12 |
+
import { ReferenceLibrary } from "./ReferenceLibrary";
|
| 13 |
+
import { StoryboardEditor } from "./StoryboardEditor";
|
| 14 |
import {
|
| 15 |
budgetFor,
|
| 16 |
fasterOption,
|
| 17 |
FormatSettings,
|
|
|
|
| 18 |
LengthSettings,
|
| 19 |
SeedSettings,
|
| 20 |
SpeedSettings,
|
|
|
|
| 27 |
onApplyExample: (prompt: string, canvas: string) => void;
|
| 28 |
onGenerate: () => void;
|
| 29 |
onGenerateDraft: () => void;
|
| 30 |
+
mode: StudioMode;
|
| 31 |
+
onModeChange: (mode: StudioMode) => void;
|
| 32 |
+
shots: StoryboardShot[];
|
| 33 |
+
onShotsChange: (shots: StoryboardShot[]) => void;
|
| 34 |
running: boolean;
|
| 35 |
/** Non-null when generating is impossible right now — shown in place of the GPU estimate. */
|
| 36 |
blockedReason: string | null;
|
|
|
|
| 56 |
onApplyExample,
|
| 57 |
onGenerate,
|
| 58 |
onGenerateDraft,
|
| 59 |
+
mode,
|
| 60 |
+
onModeChange,
|
| 61 |
+
shots,
|
| 62 |
+
onShotsChange,
|
| 63 |
running,
|
| 64 |
blockedReason,
|
| 65 |
runtimeEstimate,
|
|
|
|
| 69 |
const steps = preset.custom ? values.steps : preset.steps;
|
| 70 |
const budget = budgetFor(config, values, steps);
|
| 71 |
const faster = fasterOption(config, values);
|
| 72 |
+
const hasPrompt = mode === "storyboard" ? shots.length >= 2 && shots.every((shot) => shot.prompt.trim()) : values.prompt.trim().length > 0;
|
| 73 |
+
const validReferences = values.referenceMode !== "omni" || (values.references.some((r) => r.kind !== "audio") && values.references.length > 0);
|
| 74 |
+
const ready = hasPrompt && validReferences && !running && !blockedReason;
|
| 75 |
|
| 76 |
// Grow the prompt with its content instead of scrolling inside a fixed box, up to a point where the rail takes over.
|
| 77 |
useEffect(() => {
|
|
|
|
| 84 |
return (
|
| 85 |
<div className="flex min-h-0 flex-col lg:h-full">
|
| 86 |
<div className="scrollbar-slim divide-y divide-line lg:min-h-0 lg:flex-1 lg:overflow-y-auto lg:overscroll-contain">
|
| 87 |
+
<div className="p-4 pb-3">
|
| 88 |
+
<Segmented ariaLabel="Creation mode" value={mode} onChange={onModeChange} options={[
|
| 89 |
+
{ value: "single", label: "Single shot" },
|
| 90 |
+
{ value: "storyboard", label: "Storyboard", hint: `${shots.length} shots` },
|
| 91 |
+
]} />
|
| 92 |
+
</div>
|
| 93 |
<Section
|
| 94 |
title="Speed"
|
| 95 |
tone="accent"
|
|
|
|
| 121 |
<SpeedSettings config={config} values={values} update={update} runtimeEstimate={runtimeEstimate} />
|
| 122 |
</Section>
|
| 123 |
|
| 124 |
+
{mode === "storyboard" ? <Section title="Storyboard" defaultOpen><StoryboardEditor shots={shots} onChange={onShotsChange} /></Section> : <>
|
| 125 |
+
|
| 126 |
<Section
|
| 127 |
title="Prompt"
|
| 128 |
action={
|
|
|
|
| 183 |
))}
|
| 184 |
</div>
|
| 185 |
</Section>
|
| 186 |
+
</>}
|
| 187 |
|
| 188 |
<Section title="References" defaultOpen>
|
| 189 |
+
<ReferenceLibrary config={config} values={values} update={update} />
|
| 190 |
</Section>
|
| 191 |
|
| 192 |
<Section title="Format" defaultOpen={false}>
|
|
|
|
| 223 |
</AnimatePresence>
|
| 224 |
</div>
|
| 225 |
|
| 226 |
+
<Button variant="outline" size="lg" disabled={!ready || values.referenceMode === "omni"} onClick={onGenerateDraft} title={values.referenceMode === "omni" ? "Ref2VA uses the full quality schedule" : undefined}>
|
| 227 |
{running ? <Loader2 className="animate-spin" /> : <Clapperboard />}
|
| 228 |
Draft
|
| 229 |
</Button>
|
| 230 |
<Button variant="primary" size="lg" disabled={!ready} onClick={onGenerate}>
|
| 231 |
{running ? <Loader2 className="animate-spin" /> : <Zap fill="currentColor" />}
|
| 232 |
+
{running ? "Generating…" : mode === "storyboard" ? `Render ${shots.length} shots` : "Generate"}
|
| 233 |
</Button>
|
| 234 |
</div>
|
| 235 |
</div>
|
frontend/src/components/ReferenceLibrary.tsx
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useEffect, useMemo, useRef } from "react";
|
| 2 |
+
import { ArrowDown, ArrowUp, FileAudio, Film, ImageIcon, Plus, Trash2 } from "lucide-react";
|
| 3 |
+
import type { GenerationValues, ReferenceAsset, ReferenceKind, StudioConfig } from "../types";
|
| 4 |
+
import { Button } from "../ui/Button";
|
| 5 |
+
import { Segmented } from "../ui/Segmented";
|
| 6 |
+
import { KeyframeSettings } from "./settings";
|
| 7 |
+
|
| 8 |
+
type Props = {
|
| 9 |
+
config: StudioConfig;
|
| 10 |
+
values: GenerationValues;
|
| 11 |
+
update: <K extends keyof GenerationValues>(key: K, value: GenerationValues[K]) => void;
|
| 12 |
+
};
|
| 13 |
+
|
| 14 |
+
function kindOf(file: File): ReferenceKind | null {
|
| 15 |
+
if (file.type.startsWith("image/")) return "image";
|
| 16 |
+
if (file.type.startsWith("video/")) return "video";
|
| 17 |
+
if (file.type.startsWith("audio/")) return "audio";
|
| 18 |
+
return null;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
function icon(kind: ReferenceKind) {
|
| 22 |
+
return kind === "image" ? <ImageIcon /> : kind === "video" ? <Film /> : <FileAudio />;
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
function ReferenceThumb({ asset }: { asset: ReferenceAsset }) {
|
| 26 |
+
const url = useMemo(() => URL.createObjectURL(asset.file), [asset.file]);
|
| 27 |
+
useEffect(() => () => URL.revokeObjectURL(url), [url]);
|
| 28 |
+
if (asset.kind === "image") return <img src={url} alt="" className="size-full object-cover" />;
|
| 29 |
+
if (asset.kind === "video") return <video src={`${url}#t=.1`} muted preload="metadata" className="size-full object-cover" />;
|
| 30 |
+
return icon(asset.kind);
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
export function ReferenceLibrary({ config, values, update }: Props) {
|
| 34 |
+
const input = useRef<HTMLInputElement>(null);
|
| 35 |
+
const counts = useMemo(
|
| 36 |
+
() => Object.fromEntries(["image", "video", "audio"].map((kind) => [kind, values.references.filter((r) => r.kind === kind).length])),
|
| 37 |
+
[values.references],
|
| 38 |
+
);
|
| 39 |
+
const limits = config.ref2va ?? { enabled: true, max_total: 12, max_images: 9, max_videos: 3, max_audio: 3, minimum_duration: 5 };
|
| 40 |
+
|
| 41 |
+
function add(files: FileList | null) {
|
| 42 |
+
const next = [...values.references];
|
| 43 |
+
for (const file of Array.from(files ?? [])) {
|
| 44 |
+
const kind = kindOf(file);
|
| 45 |
+
if (!kind || next.length >= limits.max_total) continue;
|
| 46 |
+
const limit = kind === "image" ? limits.max_images : kind === "video" ? limits.max_videos : limits.max_audio;
|
| 47 |
+
if (next.filter((asset) => asset.kind === kind).length >= limit) continue;
|
| 48 |
+
next.push({ id: crypto.randomUUID(), file, kind });
|
| 49 |
+
}
|
| 50 |
+
update("references", next);
|
| 51 |
+
if (values.duration < limits.minimum_duration) update("duration", limits.minimum_duration);
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
function move(index: number, delta: number) {
|
| 55 |
+
const target = index + delta;
|
| 56 |
+
if (target < 0 || target >= values.references.length) return;
|
| 57 |
+
const next = [...values.references];
|
| 58 |
+
[next[index], next[target]] = [next[target], next[index]];
|
| 59 |
+
update("references", next);
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
return (
|
| 63 |
+
<div>
|
| 64 |
+
<Segmented
|
| 65 |
+
ariaLabel="Reference workflow"
|
| 66 |
+
value={values.referenceMode}
|
| 67 |
+
onChange={(value) => update("referenceMode", value as GenerationValues["referenceMode"])}
|
| 68 |
+
options={[
|
| 69 |
+
{ value: "keyframes", label: "Keyframes" },
|
| 70 |
+
{ value: "omni", label: "Reference studio" },
|
| 71 |
+
]}
|
| 72 |
+
/>
|
| 73 |
+
{values.referenceMode === "keyframes" ? (
|
| 74 |
+
<div className="mt-3"><KeyframeSettings values={values} update={update} /></div>
|
| 75 |
+
) : (
|
| 76 |
+
<div className="mt-3 space-y-3">
|
| 77 |
+
<p className="text-[11.5px] leading-relaxed text-muted">
|
| 78 |
+
Ordered image, video and audio references guide identity, motion, look and voice. Ref2VA uses the full 28-step quality path.
|
| 79 |
+
</p>
|
| 80 |
+
<div className="flex flex-wrap gap-1.5 text-[10.5px] text-faint">
|
| 81 |
+
<span>{counts.image}/{limits.max_images} images</span><span>·</span>
|
| 82 |
+
<span>{counts.video}/{limits.max_videos} videos</span><span>·</span>
|
| 83 |
+
<span>{counts.audio}/{limits.max_audio} audio</span>
|
| 84 |
+
</div>
|
| 85 |
+
<div className="space-y-1.5">
|
| 86 |
+
{values.references.map((asset, index) => (
|
| 87 |
+
<div key={asset.id} className="flex items-center gap-2 rounded-xl bg-sunken p-2 ring-1 ring-inset ring-line">
|
| 88 |
+
<span className="grid size-9 shrink-0 place-items-center overflow-hidden rounded-lg bg-raised text-accent [&>svg]:size-4"><ReferenceThumb asset={asset} /></span>
|
| 89 |
+
<div className="min-w-0 flex-1">
|
| 90 |
+
<p className="truncate text-[12px] font-medium text-ink">{index + 1}. {asset.file.name}</p>
|
| 91 |
+
<p className="text-[10.5px] capitalize text-faint">{asset.kind} reference</p>
|
| 92 |
+
</div>
|
| 93 |
+
<button aria-label="Move up" disabled={!index} onClick={() => move(index, -1)} className="text-muted disabled:opacity-25"><ArrowUp className="size-3.5" /></button>
|
| 94 |
+
<button aria-label="Move down" disabled={index === values.references.length - 1} onClick={() => move(index, 1)} className="text-muted disabled:opacity-25"><ArrowDown className="size-3.5" /></button>
|
| 95 |
+
<button aria-label="Remove" onClick={() => update("references", values.references.filter((item) => item.id !== asset.id))} className="text-muted hover:text-bad"><Trash2 className="size-3.5" /></button>
|
| 96 |
+
</div>
|
| 97 |
+
))}
|
| 98 |
+
</div>
|
| 99 |
+
<input ref={input} hidden multiple type="file" accept="image/*,video/*,audio/*" onChange={(event) => add(event.target.files)} />
|
| 100 |
+
<Button variant="outline" size="sm" onClick={() => input.current?.click()} disabled={values.references.length >= limits.max_total} className="w-full">
|
| 101 |
+
<Plus /> Add references
|
| 102 |
+
</Button>
|
| 103 |
+
{counts.audio > 0 && counts.image + counts.video === 0 && <p className="text-[11px] text-warn">Add an image or video alongside audio.</p>}
|
| 104 |
+
</div>
|
| 105 |
+
)}
|
| 106 |
+
</div>
|
| 107 |
+
);
|
| 108 |
+
}
|
frontend/src/components/StoryboardEditor.tsx
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { ArrowDown, ArrowUp, Copy, Plus, Trash2 } from "lucide-react";
|
| 2 |
+
import type { StoryboardShot } from "../types";
|
| 3 |
+
import { Button } from "../ui/Button";
|
| 4 |
+
|
| 5 |
+
export function StoryboardEditor({ shots, onChange }: { shots: StoryboardShot[]; onChange: (shots: StoryboardShot[]) => void }) {
|
| 6 |
+
const patch = (id: string, prompt: string) => onChange(shots.map((shot) => shot.id === id ? { ...shot, prompt } : shot));
|
| 7 |
+
const move = (index: number, delta: number) => {
|
| 8 |
+
const target = index + delta;
|
| 9 |
+
if (target < 0 || target >= shots.length) return;
|
| 10 |
+
const next = [...shots]; [next[index], next[target]] = [next[target], next[index]]; onChange(next);
|
| 11 |
+
};
|
| 12 |
+
return (
|
| 13 |
+
<div className="space-y-2.5">
|
| 14 |
+
{shots.map((shot, index) => (
|
| 15 |
+
<article key={shot.id} className="rounded-xl bg-sunken p-2.5 ring-1 ring-inset ring-line">
|
| 16 |
+
<div className="mb-2 flex items-center gap-2">
|
| 17 |
+
<span className="grid size-5 place-items-center rounded-md bg-accent/15 text-[10px] font-semibold text-accent">{index + 1}</span>
|
| 18 |
+
<span className="text-[11px] font-medium text-muted">Shot {index + 1}</span><span className="flex-1" />
|
| 19 |
+
<button disabled={!index} onClick={() => move(index, -1)} className="text-muted disabled:opacity-20"><ArrowUp className="size-3.5" /></button>
|
| 20 |
+
<button disabled={index === shots.length - 1} onClick={() => move(index, 1)} className="text-muted disabled:opacity-20"><ArrowDown className="size-3.5" /></button>
|
| 21 |
+
<button onClick={() => onChange([...shots.slice(0, index + 1), { ...shot, id: crypto.randomUUID() }, ...shots.slice(index + 1)])} className="text-muted hover:text-ink"><Copy className="size-3.5" /></button>
|
| 22 |
+
<button disabled={shots.length <= 2} onClick={() => onChange(shots.filter((item) => item.id !== shot.id))} className="text-muted hover:text-bad disabled:opacity-20"><Trash2 className="size-3.5" /></button>
|
| 23 |
+
</div>
|
| 24 |
+
<textarea value={shot.prompt} onChange={(event) => patch(shot.id, event.target.value)} rows={3} placeholder="Action, camera, dialogue and sound for this shot…" className="w-full resize-none rounded-lg bg-canvas p-2.5 text-[12.5px] leading-relaxed text-ink ring-1 ring-inset ring-line placeholder:text-faint focus:ring-accent focus:outline-none" />
|
| 25 |
+
</article>
|
| 26 |
+
))}
|
| 27 |
+
<Button variant="outline" size="sm" onClick={() => onChange([...shots, { id: crypto.randomUUID(), prompt: "" }])} disabled={shots.length >= 8} className="w-full"><Plus /> Add shot</Button>
|
| 28 |
+
<p className="text-[10.5px] leading-relaxed text-faint">Each shot continues from the previous final frame, then the editor joins them into one film.</p>
|
| 29 |
+
</div>
|
| 30 |
+
);
|
| 31 |
+
}
|
frontend/src/components/Viewer.tsx
CHANGED
|
@@ -186,8 +186,16 @@ function RunState({ progress }: { progress: RunProgress }) {
|
|
| 186 |
return (
|
| 187 |
<section
|
| 188 |
aria-label="Generation progress"
|
| 189 |
-
className="flex size-full flex-col items-center justify-center gap-
|
| 190 |
>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 191 |
<div className="w-full max-w-sm">
|
| 192 |
{/* A finished phase fills left-to-right rather than switching colour, so the row reads as a track being
|
| 193 |
covered — which is what the five phases actually are. */}
|
|
|
|
| 186 |
return (
|
| 187 |
<section
|
| 188 |
aria-label="Generation progress"
|
| 189 |
+
className="flex size-full flex-col items-center justify-center gap-5 bg-sunken px-6"
|
| 190 |
>
|
| 191 |
+
{progress.previewUrl && (
|
| 192 |
+
<div className="relative w-full max-w-sm overflow-hidden rounded-xl bg-black ring-1 ring-accent/35">
|
| 193 |
+
<img src={progress.previewUrl} alt="Approximate live generation preview" className="aspect-video w-full object-contain" />
|
| 194 |
+
<span className="absolute bottom-2 left-2 rounded-md bg-black/75 px-2 py-1 text-[10px] font-medium text-white backdrop-blur-sm">
|
| 195 |
+
Live TAE preview · final uses full VAE
|
| 196 |
+
</span>
|
| 197 |
+
</div>
|
| 198 |
+
)}
|
| 199 |
<div className="w-full max-w-sm">
|
| 200 |
{/* A finished phase fills left-to-right rather than switching colour, so the row reads as a track being
|
| 201 |
covered — which is what the five phases actually are. */}
|
frontend/src/lib/history.ts
CHANGED
|
@@ -70,8 +70,9 @@ async function deleteIds(database: IDBDatabase, ids: string[]): Promise<void> {
|
|
| 70 |
await transactionDone(transaction);
|
| 71 |
}
|
| 72 |
|
| 73 |
-
function clipBytes(clip: Pick<StoredClip, "video" | "sourceImage" | "sourceLastImage">): number {
|
| 74 |
-
return clip.video.size + (clip.sourceImage?.size ?? 0) + (clip.sourceLastImage?.size ?? 0)
|
|
|
|
| 75 |
}
|
| 76 |
|
| 77 |
/** Save the actual MP4, pruning before the write so one large clip cannot grow storage without bound. */
|
|
@@ -84,7 +85,8 @@ export async function saveHistoryItem(item: HistoryItem): Promise<void> {
|
|
| 84 |
const database = await openHistory();
|
| 85 |
try {
|
| 86 |
const existing = (await storedClips(database)).filter((clip) => clip.id !== item.id);
|
| 87 |
-
let bytes = video.size + (item.sourceImage?.size ?? 0) + (item.sourceLastImage?.size ?? 0)
|
|
|
|
| 88 |
const keep: StoredClip[] = [];
|
| 89 |
for (const clip of existing) {
|
| 90 |
if (keep.length + 1 >= MAX_CLIPS || bytes + clipBytes(clip) > MAX_BYTES) continue;
|
|
|
|
| 70 |
await transactionDone(transaction);
|
| 71 |
}
|
| 72 |
|
| 73 |
+
function clipBytes(clip: Pick<StoredClip, "video" | "sourceImage" | "sourceLastImage" | "sourceReferences">): number {
|
| 74 |
+
return clip.video.size + (clip.sourceImage?.size ?? 0) + (clip.sourceLastImage?.size ?? 0)
|
| 75 |
+
+ (clip.sourceReferences ?? []).reduce((sum, reference) => sum + reference.blob.size, 0);
|
| 76 |
}
|
| 77 |
|
| 78 |
/** Save the actual MP4, pruning before the write so one large clip cannot grow storage without bound. */
|
|
|
|
| 85 |
const database = await openHistory();
|
| 86 |
try {
|
| 87 |
const existing = (await storedClips(database)).filter((clip) => clip.id !== item.id);
|
| 88 |
+
let bytes = video.size + (item.sourceImage?.size ?? 0) + (item.sourceLastImage?.size ?? 0)
|
| 89 |
+
+ (item.sourceReferences ?? []).reduce((sum, reference) => sum + reference.blob.size, 0);
|
| 90 |
const keep: StoredClip[] = [];
|
| 91 |
for (const clip of existing) {
|
| 92 |
if (keep.length + 1 >= MAX_CLIPS || bytes + clipBytes(clip) > MAX_BYTES) continue;
|
frontend/src/lib/runtimeHistory.ts
CHANGED
|
@@ -70,7 +70,9 @@ export function runtimeSampleFor(
|
|
| 70 |
height: canvas.height,
|
| 71 |
frames: snapFrames(values.duration),
|
| 72 |
steps,
|
| 73 |
-
keyframes:
|
|
|
|
|
|
|
| 74 |
upsample: values.upsample,
|
| 75 |
seconds,
|
| 76 |
createdAt: Date.now(),
|
|
@@ -115,7 +117,9 @@ export function estimateRuntime(
|
|
| 115 |
height: canvas.height,
|
| 116 |
frames: snapFrames(values.duration),
|
| 117 |
steps,
|
| 118 |
-
keyframes:
|
|
|
|
|
|
|
| 119 |
};
|
| 120 |
const targetWork = work(target);
|
| 121 |
const matches = samples
|
|
|
|
| 70 |
height: canvas.height,
|
| 71 |
frames: snapFrames(values.duration),
|
| 72 |
steps,
|
| 73 |
+
keyframes: values.referenceMode === "omni"
|
| 74 |
+
? values.references.reduce((sum, reference) => sum + (reference.kind === "video" ? 5 : 1), 0)
|
| 75 |
+
: Number(values.image != null) + Number(values.lastImage != null),
|
| 76 |
upsample: values.upsample,
|
| 77 |
seconds,
|
| 78 |
createdAt: Date.now(),
|
|
|
|
| 117 |
height: canvas.height,
|
| 118 |
frames: snapFrames(values.duration),
|
| 119 |
steps,
|
| 120 |
+
keyframes: values.referenceMode === "omni"
|
| 121 |
+
? values.references.reduce((sum, reference) => sum + (reference.kind === "video" ? 5 : 1), 0)
|
| 122 |
+
: Number(values.image != null) + Number(values.lastImage != null),
|
| 123 |
};
|
| 124 |
const targetWork = work(target);
|
| 125 |
const matches = samples
|
frontend/src/lib/workflows.ts
CHANGED
|
@@ -11,7 +11,7 @@ function fileFromBlob(blob: Blob | null | undefined, name: string): File | null
|
|
| 11 |
|
| 12 |
/** Store every authored setting so a clip is a reusable project, not merely an MP4. */
|
| 13 |
export function recipeFrom(values: GenerationValues): GenerationRecipe {
|
| 14 |
-
const { image: _image, lastImage: _lastImage, ...recipe } = values;
|
| 15 |
return recipe;
|
| 16 |
}
|
| 17 |
|
|
@@ -29,6 +29,7 @@ function legacyRecipe(item: HistoryItem, config: StudioConfig): GenerationRecipe
|
|
| 29 |
loraRepo: "",
|
| 30 |
loraFilename: "",
|
| 31 |
loraStrength: 1,
|
|
|
|
| 32 |
};
|
| 33 |
}
|
| 34 |
|
|
@@ -39,6 +40,11 @@ export function valuesFromHistory(item: HistoryItem, config: StudioConfig): Gene
|
|
| 39 |
canvas: findCanvas(config, recipe.canvas).label,
|
| 40 |
image: fileFromBlob(item.sourceImage, "first-frame.png"),
|
| 41 |
lastImage: fileFromBlob(item.sourceLastImage, "last-frame.png"),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
};
|
| 43 |
}
|
| 44 |
|
|
|
|
| 11 |
|
| 12 |
/** Store every authored setting so a clip is a reusable project, not merely an MP4. */
|
| 13 |
export function recipeFrom(values: GenerationValues): GenerationRecipe {
|
| 14 |
+
const { image: _image, lastImage: _lastImage, references: _references, ...recipe } = values;
|
| 15 |
return recipe;
|
| 16 |
}
|
| 17 |
|
|
|
|
| 29 |
loraRepo: "",
|
| 30 |
loraFilename: "",
|
| 31 |
loraStrength: 1,
|
| 32 |
+
referenceMode: "keyframes",
|
| 33 |
};
|
| 34 |
}
|
| 35 |
|
|
|
|
| 40 |
canvas: findCanvas(config, recipe.canvas).label,
|
| 41 |
image: fileFromBlob(item.sourceImage, "first-frame.png"),
|
| 42 |
lastImage: fileFromBlob(item.sourceLastImage, "last-frame.png"),
|
| 43 |
+
references: (item.sourceReferences ?? []).map((reference, index) => ({
|
| 44 |
+
id: `${item.id}-ref-${index}`,
|
| 45 |
+
file: new File([reference.blob], reference.name, { type: reference.type }),
|
| 46 |
+
kind: reference.type.startsWith("video/") ? "video" : reference.type.startsWith("audio/") ? "audio" : "image",
|
| 47 |
+
})),
|
| 48 |
};
|
| 49 |
}
|
| 50 |
|
frontend/src/types.ts
CHANGED
|
@@ -29,15 +29,37 @@ export type StudioConfig = {
|
|
| 29 |
default_preset: string;
|
| 30 |
custom_preset: string;
|
| 31 |
examples: PromptExample[];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
};
|
| 33 |
|
| 34 |
export type Acceleration = "Balanced" | "Ultra Fast" | "Exact";
|
| 35 |
export type LoraPreset = "None" | "Turbo · 4 steps" | "Turbo · 8 steps" | "Custom";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
|
| 37 |
export type GenerationValues = {
|
| 38 |
prompt: string;
|
| 39 |
image: File | null;
|
| 40 |
lastImage: File | null;
|
|
|
|
|
|
|
| 41 |
canvas: string;
|
| 42 |
duration: number;
|
| 43 |
seed: number;
|
|
@@ -80,9 +102,11 @@ export type HistoryItem = GeneratedVideo & {
|
|
| 80 |
/** Original FL2VA anchors, cloned by IndexedDB so Draft -> Final remains faithful after a reload. */
|
| 81 |
sourceImage?: Blob | null;
|
| 82 |
sourceLastImage?: Blob | null;
|
|
|
|
|
|
|
| 83 |
};
|
| 84 |
|
| 85 |
-
export type GenerationRecipe = Omit<GenerationValues, "image" | "lastImage">;
|
| 86 |
|
| 87 |
export type RunPhase = "queue" | "conditioning" | "gpu" | "denoising" | "finalizing";
|
| 88 |
|
|
@@ -101,6 +125,8 @@ export type RunProgress = {
|
|
| 101 |
unit?: string;
|
| 102 |
exact?: boolean;
|
| 103 |
phase?: RunPhase;
|
|
|
|
|
|
|
| 104 |
};
|
| 105 |
|
| 106 |
export type ModelStatus = {
|
|
|
|
| 29 |
default_preset: string;
|
| 30 |
custom_preset: string;
|
| 31 |
examples: PromptExample[];
|
| 32 |
+
ref2va?: {
|
| 33 |
+
enabled: boolean;
|
| 34 |
+
max_total: number;
|
| 35 |
+
max_images: number;
|
| 36 |
+
max_videos: number;
|
| 37 |
+
max_audio: number;
|
| 38 |
+
minimum_duration: number;
|
| 39 |
+
};
|
| 40 |
+
tae_previews?: boolean;
|
| 41 |
};
|
| 42 |
|
| 43 |
export type Acceleration = "Balanced" | "Ultra Fast" | "Exact";
|
| 44 |
export type LoraPreset = "None" | "Turbo · 4 steps" | "Turbo · 8 steps" | "Custom";
|
| 45 |
+
export type ReferenceKind = "image" | "video" | "audio";
|
| 46 |
+
export type ReferenceMode = "keyframes" | "omni";
|
| 47 |
+
export type StudioMode = "single" | "storyboard";
|
| 48 |
+
|
| 49 |
+
export type ReferenceAsset = {
|
| 50 |
+
id: string;
|
| 51 |
+
file: File;
|
| 52 |
+
kind: ReferenceKind;
|
| 53 |
+
};
|
| 54 |
+
|
| 55 |
+
export type StoryboardShot = { id: string; prompt: string };
|
| 56 |
|
| 57 |
export type GenerationValues = {
|
| 58 |
prompt: string;
|
| 59 |
image: File | null;
|
| 60 |
lastImage: File | null;
|
| 61 |
+
referenceMode: ReferenceMode;
|
| 62 |
+
references: ReferenceAsset[];
|
| 63 |
canvas: string;
|
| 64 |
duration: number;
|
| 65 |
seed: number;
|
|
|
|
| 102 |
/** Original FL2VA anchors, cloned by IndexedDB so Draft -> Final remains faithful after a reload. */
|
| 103 |
sourceImage?: Blob | null;
|
| 104 |
sourceLastImage?: Blob | null;
|
| 105 |
+
/** Ordered Ref2VA media, retained locally; never leaves the browser except for a requested generation. */
|
| 106 |
+
sourceReferences?: { name: string; type: string; blob: Blob }[];
|
| 107 |
};
|
| 108 |
|
| 109 |
+
export type GenerationRecipe = Omit<GenerationValues, "image" | "lastImage" | "references">;
|
| 110 |
|
| 111 |
export type RunPhase = "queue" | "conditioning" | "gpu" | "denoising" | "finalizing";
|
| 112 |
|
|
|
|
| 125 |
unit?: string;
|
| 126 |
exact?: boolean;
|
| 127 |
phase?: RunPhase;
|
| 128 |
+
/** Approximate low-resolution TAE animation emitted while the full decoder is still pending. */
|
| 129 |
+
previewUrl?: string;
|
| 130 |
};
|
| 131 |
|
| 132 |
export type ModelStatus = {
|
h3_nvfp4.py
CHANGED
|
@@ -1188,15 +1188,17 @@ class H3NVFP4Transformer(nn.Module):
|
|
| 1188 |
return MiniMaxH3TransformerOutput(sample=video_output, audio_sample=audio_output)
|
| 1189 |
|
| 1190 |
|
| 1191 |
-
def load_transformer() -> H3NVFP4Transformer:
|
| 1192 |
if torch.version.cuda is None or int(torch.version.cuda.split(".")[0]) < 13:
|
| 1193 |
raise RuntimeError("NVFP4 requires the CUDA 13 PyTorch build.")
|
| 1194 |
from huggingface_hub import hf_hub_download
|
| 1195 |
|
| 1196 |
-
|
|
|
|
|
|
|
| 1197 |
transformer = H3NVFP4Transformer()
|
| 1198 |
transformer.load(path)
|
| 1199 |
-
print(f"[h3-nvfp4] loaded {
|
| 1200 |
return transformer
|
| 1201 |
|
| 1202 |
|
|
|
|
| 1188 |
return MiniMaxH3TransformerOutput(sample=video_output, audio_sample=audio_output)
|
| 1189 |
|
| 1190 |
|
| 1191 |
+
def load_transformer(repo_id: str | None = None, filename: str | None = None) -> H3NVFP4Transformer:
|
| 1192 |
if torch.version.cuda is None or int(torch.version.cuda.split(".")[0]) < 13:
|
| 1193 |
raise RuntimeError("NVFP4 requires the CUDA 13 PyTorch build.")
|
| 1194 |
from huggingface_hub import hf_hub_download
|
| 1195 |
|
| 1196 |
+
repo_id = repo_id or NVFP4_REPO
|
| 1197 |
+
filename = filename or NVFP4_FILE
|
| 1198 |
+
path = hf_hub_download(repo_id=repo_id, filename=filename)
|
| 1199 |
transformer = H3NVFP4Transformer()
|
| 1200 |
transformer.load(path)
|
| 1201 |
+
print(f"[h3-nvfp4] loaded {repo_id}/{filename}", flush=True)
|
| 1202 |
return transformer
|
| 1203 |
|
| 1204 |
|
h3_split_blocks.py
CHANGED
|
@@ -13,7 +13,14 @@ frame count is aligned to `17 * n + 5` before the call, since that arithmetic li
|
|
| 13 |
"""
|
| 14 |
|
| 15 |
from diffusers.modular_pipelines.minimax_h3.before_encoder import MiniMaxH3Ref2VASetupStep
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
from diffusers.modular_pipelines.minimax_h3.decoders import MiniMaxH3AfterDenoiseStep
|
|
|
|
| 17 |
from diffusers.modular_pipelines.minimax_h3.encoders import (
|
| 18 |
MiniMaxH3Ref2VAReferenceEncoderStep,
|
| 19 |
MiniMaxH3Ref2VATextEncoderStep,
|
|
@@ -22,15 +29,60 @@ from diffusers.modular_pipelines.minimax_h3.encoders import (
|
|
| 22 |
from diffusers.modular_pipelines.minimax_h3.modular_blocks_minimax_h3 import (
|
| 23 |
MiniMaxH3AutoKeyframeVaeEncoderStep,
|
| 24 |
MiniMaxH3AutoResizeStep,
|
| 25 |
-
MiniMaxH3CoreDenoiseStep,
|
| 26 |
MiniMaxH3DecodeStep,
|
| 27 |
-
MiniMaxH3Ref2VACoreDenoiseStep,
|
| 28 |
_generation_outputs,
|
| 29 |
)
|
| 30 |
from diffusers.modular_pipelines.modular_pipeline import SequentialPipelineBlocks
|
| 31 |
from diffusers.modular_pipelines.modular_pipeline_utils import OutputParam
|
| 32 |
|
| 33 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
def _wire_outputs(num_frames: bool = True) -> list[OutputParam]:
|
| 35 |
"""The wire format of the split. `num_frames` is declared by the `ref2va` half alone, whose setup resolves one."""
|
| 36 |
return [
|
|
@@ -73,7 +125,7 @@ class MiniMaxH3GeneratorBlocks(SequentialPipelineBlocks):
|
|
| 73 |
block_classes = [
|
| 74 |
MiniMaxH3AutoResizeStep,
|
| 75 |
MiniMaxH3AutoKeyframeVaeEncoderStep,
|
| 76 |
-
|
| 77 |
MiniMaxH3AfterDenoiseStep,
|
| 78 |
MiniMaxH3DecodeStep,
|
| 79 |
]
|
|
@@ -128,7 +180,7 @@ class MiniMaxH3Ref2VAGeneratorBlocks(SequentialPipelineBlocks):
|
|
| 128 |
block_classes = [
|
| 129 |
MiniMaxH3Ref2VASetupStep,
|
| 130 |
MiniMaxH3Ref2VAReferenceEncoderStep,
|
| 131 |
-
|
| 132 |
MiniMaxH3AfterDenoiseStep,
|
| 133 |
MiniMaxH3DecodeStep,
|
| 134 |
]
|
|
|
|
| 13 |
"""
|
| 14 |
|
| 15 |
from diffusers.modular_pipelines.minimax_h3.before_encoder import MiniMaxH3Ref2VASetupStep
|
| 16 |
+
from diffusers.modular_pipelines.minimax_h3.before_denoise import (
|
| 17 |
+
MiniMaxH3PrepareLatentsStep,
|
| 18 |
+
MiniMaxH3PrepareLayoutStep,
|
| 19 |
+
MiniMaxH3Ref2VAPrepareLayoutStep,
|
| 20 |
+
MiniMaxH3SetTimestepsStep,
|
| 21 |
+
)
|
| 22 |
from diffusers.modular_pipelines.minimax_h3.decoders import MiniMaxH3AfterDenoiseStep
|
| 23 |
+
from diffusers.modular_pipelines.minimax_h3.denoise import MiniMaxH3DenoiseStep, MiniMaxH3Ref2VADenoiseStep
|
| 24 |
from diffusers.modular_pipelines.minimax_h3.encoders import (
|
| 25 |
MiniMaxH3Ref2VAReferenceEncoderStep,
|
| 26 |
MiniMaxH3Ref2VATextEncoderStep,
|
|
|
|
| 29 |
from diffusers.modular_pipelines.minimax_h3.modular_blocks_minimax_h3 import (
|
| 30 |
MiniMaxH3AutoKeyframeVaeEncoderStep,
|
| 31 |
MiniMaxH3AutoResizeStep,
|
|
|
|
| 32 |
MiniMaxH3DecodeStep,
|
|
|
|
| 33 |
_generation_outputs,
|
| 34 |
)
|
| 35 |
from diffusers.modular_pipelines.modular_pipeline import SequentialPipelineBlocks
|
| 36 |
from diffusers.modular_pipelines.modular_pipeline_utils import OutputParam
|
| 37 |
|
| 38 |
|
| 39 |
+
class _PreviewLoopMixin:
|
| 40 |
+
"""The pinned Diffusers loop plus four non-authoritative TAE preview emissions."""
|
| 41 |
+
|
| 42 |
+
def __call__(self, components, state):
|
| 43 |
+
from h3_tae import maybe_emit_preview
|
| 44 |
+
|
| 45 |
+
block_state = self.get_block_state(state)
|
| 46 |
+
total = len(block_state.timesteps)
|
| 47 |
+
with self.progress_bar(total=total) as progress_bar:
|
| 48 |
+
for index, timestep in enumerate(block_state.timesteps):
|
| 49 |
+
components, block_state = self.loop_step(components, block_state, i=index, t=timestep)
|
| 50 |
+
maybe_emit_preview(components, block_state, index, total)
|
| 51 |
+
progress_bar.update()
|
| 52 |
+
self.set_block_state(state, block_state)
|
| 53 |
+
return components, state
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
class MiniMaxH3PreviewDenoiseStep(_PreviewLoopMixin, MiniMaxH3DenoiseStep):
|
| 57 |
+
pass
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
class MiniMaxH3Ref2VAPreviewDenoiseStep(_PreviewLoopMixin, MiniMaxH3Ref2VADenoiseStep):
|
| 61 |
+
pass
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
class MiniMaxH3PreviewCoreDenoiseStep(SequentialPipelineBlocks):
|
| 65 |
+
model_name = "minimax-h3"
|
| 66 |
+
block_classes = [
|
| 67 |
+
MiniMaxH3PrepareLayoutStep,
|
| 68 |
+
MiniMaxH3PrepareLatentsStep,
|
| 69 |
+
MiniMaxH3SetTimestepsStep,
|
| 70 |
+
MiniMaxH3PreviewDenoiseStep,
|
| 71 |
+
]
|
| 72 |
+
block_names = ["prepare_layout", "prepare_latents", "set_timesteps", "denoise"]
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
class MiniMaxH3Ref2VAPreviewCoreDenoiseStep(SequentialPipelineBlocks):
|
| 76 |
+
model_name = "minimax-h3"
|
| 77 |
+
block_classes = [
|
| 78 |
+
MiniMaxH3Ref2VAPrepareLayoutStep,
|
| 79 |
+
MiniMaxH3PrepareLatentsStep,
|
| 80 |
+
MiniMaxH3SetTimestepsStep,
|
| 81 |
+
MiniMaxH3Ref2VAPreviewDenoiseStep,
|
| 82 |
+
]
|
| 83 |
+
block_names = ["prepare_layout", "prepare_latents", "set_timesteps", "denoise"]
|
| 84 |
+
|
| 85 |
+
|
| 86 |
def _wire_outputs(num_frames: bool = True) -> list[OutputParam]:
|
| 87 |
"""The wire format of the split. `num_frames` is declared by the `ref2va` half alone, whose setup resolves one."""
|
| 88 |
return [
|
|
|
|
| 125 |
block_classes = [
|
| 126 |
MiniMaxH3AutoResizeStep,
|
| 127 |
MiniMaxH3AutoKeyframeVaeEncoderStep,
|
| 128 |
+
MiniMaxH3PreviewCoreDenoiseStep,
|
| 129 |
MiniMaxH3AfterDenoiseStep,
|
| 130 |
MiniMaxH3DecodeStep,
|
| 131 |
]
|
|
|
|
| 180 |
block_classes = [
|
| 181 |
MiniMaxH3Ref2VASetupStep,
|
| 182 |
MiniMaxH3Ref2VAReferenceEncoderStep,
|
| 183 |
+
MiniMaxH3Ref2VAPreviewCoreDenoiseStep,
|
| 184 |
MiniMaxH3AfterDenoiseStep,
|
| 185 |
MiniMaxH3DecodeStep,
|
| 186 |
]
|
h3_tae.py
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Tiny MiniMax-H3 latent previews streamed through Gradio progress packets.
|
| 2 |
+
|
| 3 |
+
The 2D decoder architecture follows ComfyUI's MIT-licensed TAESD blocks and Kijai's H3 checkpoint layout. It is
|
| 4 |
+
preview-only: final frames still come exclusively from MiniMax-H3's full video VAE.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from __future__ import annotations
|
| 8 |
+
|
| 9 |
+
import os
|
| 10 |
+
import time
|
| 11 |
+
from urllib.parse import quote
|
| 12 |
+
|
| 13 |
+
import torch
|
| 14 |
+
import torch.nn as nn
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
TAE_REPO = "Kijai/MiniMax-H3-TAE"
|
| 18 |
+
TAE_FILE = "vae_approx/taeh3.safetensors"
|
| 19 |
+
PREVIEW_MAX_EDGE = 384
|
| 20 |
+
PREVIEW_POINTS = 4
|
| 21 |
+
|
| 22 |
+
_DECODER = None
|
| 23 |
+
_OUTPUT_DIR = None
|
| 24 |
+
_FAILED = False
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def _conv(n_in: int, n_out: int, **kwargs):
|
| 28 |
+
return nn.Conv2d(n_in, n_out, 3, padding=1, **kwargs)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class _Clamp(nn.Module):
|
| 32 |
+
def forward(self, value):
|
| 33 |
+
return torch.tanh(value / 3) * 3
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
class _Block(nn.Module):
|
| 37 |
+
def __init__(self, n_in: int, n_out: int):
|
| 38 |
+
super().__init__()
|
| 39 |
+
self.conv = nn.Sequential(
|
| 40 |
+
_conv(n_in, n_out), nn.ReLU(), _conv(n_out, n_out), nn.ReLU(), _conv(n_out, n_out)
|
| 41 |
+
)
|
| 42 |
+
self.skip = nn.Conv2d(n_in, n_out, 1, bias=False) if n_in != n_out else nn.Identity()
|
| 43 |
+
self.fuse = nn.ReLU()
|
| 44 |
+
|
| 45 |
+
def forward(self, value):
|
| 46 |
+
return self.fuse(self.conv(value) + self.skip(value))
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def _build_decoder(state):
|
| 50 |
+
by_index = {}
|
| 51 |
+
for key, value in state.items():
|
| 52 |
+
head, _, tail = key.partition(".")
|
| 53 |
+
by_index.setdefault(int(head), {})[tail] = value
|
| 54 |
+
modules = []
|
| 55 |
+
for index in range(max(by_index) + 1):
|
| 56 |
+
entry = by_index.get(index)
|
| 57 |
+
if entry is None:
|
| 58 |
+
modules.append(_Clamp() if index == 0 else nn.ReLU() if index == 2 else nn.Upsample(scale_factor=2))
|
| 59 |
+
elif "conv.0.weight" in entry:
|
| 60 |
+
weight = entry["conv.0.weight"]
|
| 61 |
+
modules.append(_Block(weight.shape[1], weight.shape[0]))
|
| 62 |
+
elif "weight" in entry:
|
| 63 |
+
weight = entry["weight"]
|
| 64 |
+
modules.append(_conv(weight.shape[1], weight.shape[0], bias="bias" in entry))
|
| 65 |
+
else:
|
| 66 |
+
raise ValueError(f"Unrecognized H3 TAE module {index}: {sorted(entry)}")
|
| 67 |
+
decoder = nn.Sequential(*modules)
|
| 68 |
+
decoder.load_state_dict(state)
|
| 69 |
+
return decoder.eval()
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def load_preview_model(output_dir: str):
|
| 73 |
+
"""Load the 9.8 MB decoder on CPU at startup; CUDA placement happens only inside a booked request."""
|
| 74 |
+
global _DECODER, _OUTPUT_DIR, _FAILED
|
| 75 |
+
if _DECODER is not None or _FAILED:
|
| 76 |
+
return
|
| 77 |
+
try:
|
| 78 |
+
from huggingface_hub import hf_hub_download
|
| 79 |
+
from safetensors.torch import load_file
|
| 80 |
+
|
| 81 |
+
path = hf_hub_download(TAE_REPO, TAE_FILE)
|
| 82 |
+
_DECODER = _build_decoder(load_file(path, device="cpu"))
|
| 83 |
+
_OUTPUT_DIR = os.path.join(output_dir, "previews")
|
| 84 |
+
os.makedirs(_OUTPUT_DIR, exist_ok=True)
|
| 85 |
+
print(f"[tae] loaded {TAE_REPO}/{TAE_FILE}", flush=True)
|
| 86 |
+
except Exception as error:
|
| 87 |
+
_FAILED = True
|
| 88 |
+
print(f"[tae] disabled ({type(error).__name__}: {error})", flush=True)
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def _unpatchify(components, state):
|
| 92 |
+
patch_t, patch_h, patch_w = components.patch_size
|
| 93 |
+
channels = components.vae_latent_channels
|
| 94 |
+
rows = state.latents[state.num_condition_video_rows :]
|
| 95 |
+
rows = rows.reshape(
|
| 96 |
+
-1,
|
| 97 |
+
state.num_latent_frames // patch_t,
|
| 98 |
+
state.latent_height // patch_h,
|
| 99 |
+
state.latent_width // patch_w,
|
| 100 |
+
channels,
|
| 101 |
+
patch_t,
|
| 102 |
+
patch_h,
|
| 103 |
+
patch_w,
|
| 104 |
+
)
|
| 105 |
+
rows = rows.permute(0, 4, 1, 5, 2, 6, 3, 7)
|
| 106 |
+
return rows.reshape(-1, channels, state.num_latent_frames, state.latent_height, state.latent_width)
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
@torch.inference_mode()
|
| 110 |
+
def maybe_emit_preview(components, state, step: int, total: int) -> None:
|
| 111 |
+
"""Decode three representative latent frames at four milestones and publish a tiny animated WebP."""
|
| 112 |
+
if _DECODER is None or _OUTPUT_DIR is None or total < 2:
|
| 113 |
+
return
|
| 114 |
+
milestones = {max(0, round((total - 1) * fraction)) for fraction in (0.12, 0.38, 0.66, 0.9)}
|
| 115 |
+
if step not in milestones:
|
| 116 |
+
return
|
| 117 |
+
try:
|
| 118 |
+
from gradio.context import LocalContext
|
| 119 |
+
from PIL import Image
|
| 120 |
+
|
| 121 |
+
progress = LocalContext.progress.get()
|
| 122 |
+
if progress is None:
|
| 123 |
+
return
|
| 124 |
+
latents = _unpatchify(components, state)
|
| 125 |
+
picks = torch.linspace(0, latents.shape[2] - 1, min(3, latents.shape[2])).round().long().tolist()
|
| 126 |
+
decoder = _DECODER.to(device=latents.device, dtype=torch.bfloat16)
|
| 127 |
+
frames = []
|
| 128 |
+
for index in picks:
|
| 129 |
+
rgb = decoder(latents[:1, :, index].to(torch.bfloat16))[0].float().clamp(0, 1)
|
| 130 |
+
array = rgb.mul(255).to(torch.uint8).movedim(0, -1).cpu().numpy()
|
| 131 |
+
image = Image.fromarray(array)
|
| 132 |
+
image.thumbnail((PREVIEW_MAX_EDGE, PREVIEW_MAX_EDGE), Image.Resampling.LANCZOS)
|
| 133 |
+
frames.append(image)
|
| 134 |
+
name = f"tae-{os.getpid()}-{int(time.time() * 1000)}.webp"
|
| 135 |
+
path = os.path.join(_OUTPUT_DIR, name)
|
| 136 |
+
frames[0].save(
|
| 137 |
+
path,
|
| 138 |
+
format="WEBP",
|
| 139 |
+
save_all=len(frames) > 1,
|
| 140 |
+
append_images=frames[1:],
|
| 141 |
+
duration=420,
|
| 142 |
+
loop=0,
|
| 143 |
+
quality=72,
|
| 144 |
+
method=3,
|
| 145 |
+
)
|
| 146 |
+
url = "/gradio_api/file=" + quote(path, safe="")
|
| 147 |
+
progress((step + 1) / total, desc=f"TAE_PREVIEW|{url}|Preview {step + 1}/{total}")
|
| 148 |
+
except Exception as error:
|
| 149 |
+
print(f"[tae] preview skipped ({type(error).__name__}: {error})", flush=True)
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
def status() -> str:
|
| 153 |
+
return "TAE live previews" if _DECODER is not None else "TAE unavailable"
|
requirements.txt
CHANGED
|
@@ -9,6 +9,8 @@
|
|
| 9 |
diffusers @ git+https://github.com/huggingface/diffusers.git@665f578278365ea4a3318cb8c9b66ce6c01204b9
|
| 10 |
torch==2.11.0
|
| 11 |
torchvision==0.26.0
|
|
|
|
|
|
|
| 12 |
# The Qwen3-VL processor decides the vision patch count, so a different minor changes the conditioning.
|
| 13 |
transformers==5.8.0
|
| 14 |
accelerate==1.14.0
|
|
|
|
| 9 |
diffusers @ git+https://github.com/huggingface/diffusers.git@665f578278365ea4a3318cb8c9b66ce6c01204b9
|
| 10 |
torch==2.11.0
|
| 11 |
torchvision==0.26.0
|
| 12 |
+
# Ref2VA preserves each audio/video reference's native sample rate, then resamples it to the audio VAE rate.
|
| 13 |
+
torchaudio==2.11.0
|
| 14 |
# The Qwen3-VL processor decides the vision patch count, so a different minor changes the conditioning.
|
| 15 |
transformers==5.8.0
|
| 16 |
accelerate==1.14.0
|