"""HF Spaces entrypoint for NIGHTWAVE (shipped via the Docker SDK). ================================================================================ HOW THIS IS SERVED (Docker SDK -> `uvicorn app:app`) -------------------------------------------------------------------------------- The Space runs `uvicorn app:app --host 0.0.0.0 --port 7860` (see ../space/Dockerfile and the README front-matter: `sdk: docker`, `app_port: 7860`). That makes the combined FastAPI ASGI `app` the single, deterministic server -- no guessing about which object an HF gradio-launcher would pick up. The module-level `app` is built FastAPI-FIRST and is ALWAYS functional: * GET / -> the raw skeuomorphic radio document (top-level, so mic + autoplay permissions work without extra iframe nesting) * POST /api/broadcast, /api/call, /api/seek -> same-origin proxy to Modal (browser never sees Modal creds; graceful canned fallback so the radio never goes silent) A minimal Gradio Blocks (the iframe-wrapped radio) is then mounted at /gradio so the Space is a bona-fide Gradio app (Off-Brand: a custom frontend past the default Gradio look). The mount is OPPORTUNISTIC: if gradio import/mount fails for any reason, the radio + /api/* still serve -- a gradio hiccup can never take the product down. This is the deliberate <24h ship-safety choice. ================================================================================ """ import logging import os from fastapi.responses import HTMLResponse from server import create_api, radio_iframe_html, read_radio_html _log = logging.getLogger("nightwave") # 1) FastAPI app with the same-origin proxy routes. This ALWAYS works (no gradio). app = create_api() # Serve the radio at the root as the RAW document (top-level mic/autoplay). @app.get("/", response_class=HTMLResponse) def root_radio() -> HTMLResponse: return HTMLResponse(read_radio_html()) # 2) Opportunistically mount a minimal Gradio Blocks at /gradio so the Space is a # genuine Gradio app. Never let a gradio problem break the product. demo = None try: import gradio as gr with gr.Blocks( title="NIGHTWAVE", theme=gr.themes.Base(primary_hue="indigo", neutral_hue="slate"), css="footer{display:none !important} .gradio-container{max-width:100% !important}", ) as demo: gr.HTML(radio_iframe_html()) app = gr.mount_gradio_app(app, demo, path="/gradio") _log.info("Gradio mounted at /gradio") except Exception as exc: # pragma: no cover - defensive: never break serving _log.warning("Gradio mount skipped (%r); serving radio + /api/* without it", exc) if __name__ == "__main__": # Runs locally too: with gradio absent (local Py3.9) the try/except above just # skips the mount and we still serve the radio + /api/* exactly as on HF. import uvicorn uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", "7860")))