Spaces:
Running
Running
| """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")} | |
| 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)) | |
| 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)) | |
| def examples() -> str: | |
| return json.dumps(core.EXAMPLE_PROMPTS) | |
| def home(): | |
| return INDEX_HTML | |
| def canvas(): | |
| return CANVAS_HTML | |
| if __name__ == "__main__": | |
| server.launch(server_name="0.0.0.0", | |
| server_port=int(os.environ.get("PORT", "7860"))) | |