""" ui.app — Router Gradio. Lee vm.screen y monta la pantalla activa con @gr.render. """ from __future__ import annotations from pathlib import Path import gradio as gr from ui.assets import ASSETS_DIR, music_tag, voice_tag, MUSIC_VOLUME from ui.state import ViewModel from ui.screens import SCREENS, title from ui.tts import warmup as _tts_warmup from game.llm import warmup as _llm_warmup CSS = (Path(__file__).resolve().parent / "style.css").read_text(encoding="utf-8") # Forzar SIEMPRE modo oscuro: Gradio lo controla con el query param ?__theme=dark. # Va en el (no en js=): un """.replace("__VOL__", str(MUSIC_VOLUME)) # Global: vale para `python app.py` y para `gradio app.py` (que ignora el # allowed_paths de launch porque no ejecuta el bloque __main__). gr.set_static_paths([str(ASSETS_DIR)]) # Warmup: arranca los contenedores de Modal (voz + LLM) al lanzar la app, en hilos, # para que la 1ª voz/legislación del juego salga caliente (~2s) y no espere el cold # start (~60s). Fire-and-forget; si los endpoints no responden, no pasa nada. _tts_warmup() _llm_warmup() with gr.Blocks(title="Lesky's Shop", css=CSS, head=HEAD) as demo: # Gradio deep-copia el valor por defecto del gr.State por sesión. app = gr.State(ViewModel()) @gr.render(inputs=[app]) def router(vm: ViewModel): gr.HTML(music_tag(vm.screen), elem_classes="music-host") # música de fondo (oculto) gr.HTML(voice_tag(vm.voice_url, vm.voice_seq), elem_classes="music-host") # voz enemigo gr.HTML(f'
') # corta voz al cambiar if vm.voice_loading: # avisa: avanzar bloqueado mientras genera gr.HTML('
🔊 Generating voice…
') SCREENS.get(vm.screen, title.build)(app, vm) if __name__ == "__main__": demo.launch(allowed_paths=[str(ASSETS_DIR)])