trikona / server.py
disssid's picture
chore: ready custom-UI (server.py) entry point for HF Space submission
7b0ad37
Raw
History Blame Contribute Delete
3.22 kB
"""Trikona custom-frontend server (gradio.Server) — parallel to app.py.
Serves a fully custom UI at / that calls the /generate and /regenerate API
endpoints via the Gradio JS client. The frontend renders shapes IN PLACE (no
iframe reload, no postMessage->hidden-button bridge), so the camera/orbit state
persists and there is no white flash between shapes.
Reuses app.py's generation core (codegen pipeline / mesh / raw) and its flags +
caches, so behavior matches the Blocks app. The Modal endpoint URLs stay
server-side (the browser only ever calls /generate and /regenerate).
Run: TRIKONA_CODEGEN=1 MODAL_ENDPOINT_URL=... python3 server.py
The original app.py (Blocks + iframe) is untouched and still works.
"""
import json
import os
from pathlib import Path
import gradio as gr
from fastapi.responses import HTMLResponse
import app as core # generation logic, flags (CODEGEN/MESH_ON), endpoint URLs, caches
HERE = Path(__file__).parent
CANVAS_HTML = (HERE / "canvas.html").read_text()
INDEX_HTML = (HERE / "web" / "index.html").read_text()
server = gr.Server(title="Trikona")
def _generate(prompt: str, params=None) -> dict:
"""Return {"contract": <json str>, "status": <tier>} reusing app.py's core."""
if not core.CODEGEN:
return {"contract": core.generate_shape(prompt, params=params), "status": "raw"}
from geometry import pipeline
mesh_fn = core._mesh_emit if (core.MESH_ON and core.MESH_URL) else None
result, meta = pipeline.build(core._model_emit, prompt, params=params, mesh_fn=mesh_fn)
if meta.get("spec") is not None:
core._SPEC_CACHE[prompt] = meta["spec"]
return {"contract": json.dumps(result), "status": meta.get("status", "ok")}
@server.api(name="generate")
def generate(prompt: str) -> str:
prompt = (prompt or "").strip()
if not prompt:
return json.dumps({"contract": json.dumps({"error": "empty_prompt"}), "status": "error"})
if len(prompt) > core.MAX_PROMPT_CHARS:
return json.dumps({"contract": json.dumps({"error": "prompt_too_long"}), "status": "error"})
return json.dumps(_generate(prompt))
@server.api(name="regenerate")
def regenerate(prompt: str, params: str) -> str:
"""Topological regen. params is a JSON object of param values (or empty)."""
prompt = (prompt or "").strip()
try:
p = json.loads(params) if params else None
except (json.JSONDecodeError, TypeError):
p = None
if core.CODEGEN:
spec = core._SPEC_CACHE.get(prompt)
if spec is not None:
try:
out = core._regen_modelfree(spec, p)
if out is not None:
return json.dumps({"contract": out, "status": "regen"})
except Exception:
pass
return json.dumps(_generate(prompt, params=p))
@server.api(name="examples")
def examples() -> str:
return json.dumps(core.EXAMPLE_PROMPTS)
@server.get("/", response_class=HTMLResponse)
def home():
return INDEX_HTML
@server.get("/canvas", response_class=HTMLResponse)
def canvas():
return CANVAS_HTML
if __name__ == "__main__":
server.launch(server_name="0.0.0.0",
server_port=int(os.environ.get("PORT", "7860")))