File size: 3,121 Bytes
d2794cb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
012085e
 
 
 
 
 
 
 
 
adf9d9b
d2794cb
 
012085e
 
 
fa8dc26
 
 
 
 
 
 
 
 
 
 
 
 
1afd1c1
 
 
fa8dc26
1afd1c1
012085e
 
 
 
d2794cb
 
 
 
012085e
d2794cb
 
 
 
012085e
d2794cb
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
"""Small Talk — Hugging Face Space entrypoint.

A Gradio app that wears no Gradio: we use `gradio.Server` (a FastAPI server with
Gradio's backend) purely as the host, then serve our own three.js meeting UI and
the /api control plane on it. Custom routes take priority over Gradio's, so the
visitor only ever sees the custom frontend — which also earns the hackathon's
"Off-Brand" badge.

LiveKit (the WebRTC SFU) is LiveKit Cloud; credentials come from Space secrets
(LIVEKIT_URL / LIVEKIT_API_KEY / LIVEKIT_API_SECRET). The browser connects to
LiveKit Cloud directly; this app just mints tokens and runs the Reachy
publishers that stream the pre-rendered voice clips into the room.
"""
import os
import pathlib

import gradio as gr
from fastapi.responses import FileResponse

from backend.server import attach

DIST = pathlib.Path(__file__).parent / "frontend" / "dist"

app = gr.Server()
attach(app)  # /api/* routes — registered before the static mounts below

# Serve the built single-page app. These subdirs don't collide with /api or
# Gradio's own routes, so a simple per-folder static mount is enough.
from fastapi.staticfiles import StaticFiles  # noqa: E402

class CachedStaticFiles(StaticFiles):
    """Hashed/immutable build artifacts: cache hard, forever."""

    def file_response(self, *args, **kwargs):
        resp = super().file_response(*args, **kwargs)
        resp.headers["Cache-Control"] = "public, max-age=31536000, immutable"
        return resp


for sub in ("assets", "robot-3d", "moves", "props"):
    d = DIST / sub
    if d.is_dir():
        cls = CachedStaticFiles if sub == "assets" else StaticFiles
        app.mount(f"/{sub}", cls(directory=str(d)), name=sub)

class RadioStaticFiles(StaticFiles):
    """Songs/art are immutable (cache hard); playlist.json must always revalidate
    so updated lyrics/timings are never served stale."""

    def file_response(self, *args, **kwargs):
        resp = super().file_response(*args, **kwargs)
        full_path = str(args[0]) if args else ""
        resp.headers["Cache-Control"] = (
            "no-cache, must-revalidate" if full_path.endswith(".json")
            else "public, max-age=604800")
        return resp


# Reachy FM: songs + album art + DJ Servo's prerecorded mic breaks
RADIO = pathlib.Path(__file__).parent / "radio"
if RADIO.is_dir():
    app.mount("/radio", RadioStaticFiles(directory=str(RADIO)), name="radio")


# index.html must NEVER be cached: each deploy replaces the hashed bundle it
# points at, so a stale cached page 404s on its own JS and the app goes blank.
_NO_CACHE = {"Cache-Control": "no-cache, must-revalidate"}


@app.get("/")
async def index():
    return FileResponse(DIST / "index.html", headers=_NO_CACHE)


@app.get("/favicon.ico")
async def favicon():
    return FileResponse(DIST / "index.html", headers=_NO_CACHE)  # SPA injects an emoji favicon


if __name__ == "__main__":
    # HF Spaces (sdk: gradio) runs this file; bind to the port it provides.
    port = int(os.environ.get("GRADIO_SERVER_PORT", os.environ.get("PORT", 7860)))
    app.launch(server_name="0.0.0.0", server_port=port)