Spaces:
Sleeping
Sleeping
File size: 3,442 Bytes
b89c27d b0b140b b89c27d b0b140b b89c27d b0b140b b89c27d b0b140b b89c27d 962ad43 b89c27d b0b140b 4a535ea b0b140b b89c27d b0b140b b89c27d b0b140b b89c27d b0b140b b89c27d b0b140b | 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 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | """FastAPI application for LandscapeForge.
Mounts:
- OpenEnv endpoints (`/reset`, `/step`, `/schema`, `/ws`, etc.) via create_app
- Frontend API helpers at `/api/*` (see `api_routes.py`)
- React SPA at `/` (built to `/app/env/frontend/dist` via Vite)
"""
from pathlib import Path
try:
from openenv.core.env_server.http_server import create_app
except Exception as e: # pragma: no cover
raise ImportError(
"openenv is required for the web interface. Install dependencies via 'uv sync'."
) from e
try:
from ..models import LandscapeforgeAction, LandscapeforgeObservation
from .landscapeforge_environment import LandscapeforgeEnvironment
from .api_routes import router as lf_api_router
except (ModuleNotFoundError, ImportError):
from models import LandscapeforgeAction, LandscapeforgeObservation # type: ignore
from server.landscapeforge_environment import LandscapeforgeEnvironment # type: ignore
from server.api_routes import router as lf_api_router # type: ignore
app = create_app(
LandscapeforgeEnvironment,
LandscapeforgeAction,
LandscapeforgeObservation,
env_name="landscapeforge",
# Single shared env for plain HTTP /reset + /step so the API playground
# (which doesn't carry session IDs) operates on consistent state.
# WebSocket clients still get fresh instances via SUPPORTS_CONCURRENT_SESSIONS.
max_concurrent_envs=1,
)
# Frontend-facing API (landscape, baseline_race, arena, llm_run)
app.include_router(lf_api_router)
# ---- React SPA serving ----
# The Dockerfile builds the frontend into `frontend/dist/`. Locate it relative
# to this file, and serve it under `/` with a SPA fallback to index.html.
try:
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
_here = Path(__file__).resolve().parent.parent
_dist = _here / "frontend" / "dist"
if _dist.is_dir():
# Assets (hashed css/js) at /assets, vite dist root items at /
app.mount("/assets", StaticFiles(directory=_dist / "assets"),
name="lf-assets")
@app.get("/")
def _index():
return FileResponse(_dist / "index.html")
# SPA fallback: any route the FastAPI router doesn't own, serve
# index.html so client-side routing (if any) works.
@app.get("/{path:path}")
def _spa_fallback(path: str):
# Don't swallow API/OpenEnv routes — FastAPI matches those first
# because they're registered before this catch-all.
candidate = _dist / path
if candidate.is_file():
return FileResponse(candidate)
return FileResponse(_dist / "index.html")
else:
import logging
logging.getLogger(__name__).warning(
"Frontend dist not found at %s — SPA routes disabled.", _dist
)
except Exception as _e:
import logging
logging.getLogger(__name__).warning("SPA mount failed: %s", _e)
def main():
"""Entry point for direct execution."""
import argparse
import os
import uvicorn
parser = argparse.ArgumentParser()
parser.add_argument("--port", type=int,
default=int(os.environ.get("PORT", 8000)))
parser.add_argument("--host", type=str, default="0.0.0.0")
args = parser.parse_args()
uvicorn.run(app, host=args.host, port=args.port)
if __name__ == "__main__":
main()
|