mrfakename commited on
Commit
0ba18b7
·
1 Parent(s): b7100a9

Add Ref2VA storyboard studio and live TAE previews

Browse files
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
- PIPE, MANAGER, COND_PIPE = pipe, manager, cond_pipe
 
 
 
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
- if COND_PIPE is not None and prompt_embeds is None:
544
- COND_PIPE.text_encoder.to("cuda")
545
- if PLACEMENT == "lazy":
546
- PIPE.to("cuda")
547
- elif PLACEMENT == "pack":
548
- PIPE.vae.to("cuda")
549
- PIPE.audio_vae.to("cuda")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
550
 
551
  condition_seconds = None
552
  num_text_tokens = None
553
  condition_cache_hit = False
554
  if prompt_embeds is None:
555
- if COND_PIPE is None:
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 = COND_PIPE(
568
- prompt=prompt,
569
- image=image,
570
- last_image=last_image,
571
- height=int(height),
572
- width=int(width),
 
 
 
 
 
 
 
 
 
 
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
- activate_lora = getattr(PIPE.transformer, "activate_lora", None)
586
- clear_lora = getattr(PIPE.transformer, "clear_lora", None)
 
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(PIPE.transformer, "begin_request", None)
592
- end_request = getattr(PIPE.transformer, "end_request", None)
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
- state = PIPE(
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, ip_token=None, progress=gr.Progress(track_tqdm=True),
 
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-BTUFketj.js","assets/index-DoM51C4t.js","assets/index-CCfkPg0W.css"])))=>i.map(i=>d[i]);
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-DoM51C4t.js"></script>
11
- <link rel="stylesheet" crossorigin href="/studio-assets/assets/index-CCfkPg0W.css">
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 ready = values.prompt.trim().length > 0 && !running && !blockedReason;
 
 
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
- <KeyframeSettings values={values} update={update} />
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-6 bg-sunken px-6"
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: Number(values.image != null) + Number(values.lastImage != null),
 
 
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: Number(values.image != null) + Number(values.lastImage != null),
 
 
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
- path = hf_hub_download(repo_id=NVFP4_REPO, filename=NVFP4_FILE)
 
 
1197
  transformer = H3NVFP4Transformer()
1198
  transformer.load(path)
1199
- print(f"[h3-nvfp4] loaded {NVFP4_REPO}/{NVFP4_FILE}", flush=True)
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
- MiniMaxH3CoreDenoiseStep,
77
  MiniMaxH3AfterDenoiseStep,
78
  MiniMaxH3DecodeStep,
79
  ]
@@ -128,7 +180,7 @@ class MiniMaxH3Ref2VAGeneratorBlocks(SequentialPipelineBlocks):
128
  block_classes = [
129
  MiniMaxH3Ref2VASetupStep,
130
  MiniMaxH3Ref2VAReferenceEncoderStep,
131
- MiniMaxH3Ref2VACoreDenoiseStep,
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