RobotTransportServer / server /launch_with_ui.py
blanchon's picture
Update
bf6e620
import logging
import os
from pathlib import Path
import uvicorn
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
# Import the existing API
from src.api import app as api_app
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
handlers=[logging.StreamHandler(), logging.FileHandler("server.log")],
)
logger = logging.getLogger(__name__)
# Create a new FastAPI app that combines API and frontend
app = FastAPI(
title="LeRobot Arena Transport Server with UI",
description="Combined API and web interface for LeRobot Arena transport server",
version="2.0.0",
)
# Add CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Add the health endpoint BEFORE other routes to avoid conflicts
@app.get("/health")
async def health_check():
return {
"status": "healthy",
"server_running": True,
"frontend_enabled": os.getenv("SERVE_FRONTEND", "false").lower() == "true",
"api_available": True,
}
# Mount the API under /api prefix
app.mount("/api", api_app)
# Check if we should serve frontend
serve_frontend = os.getenv("SERVE_FRONTEND", "false").lower() == "true"
if serve_frontend:
# Get the frontend static files path
frontend_path = Path(__file__).parent.parent / "static-frontend"
if frontend_path.exists():
logger.info(f"Serving frontend from: {frontend_path}")
# Mount static files (JS, CSS, etc.)
app.mount("/static", StaticFiles(directory=frontend_path), name="static")
# Serve the main app for all other routes (SPA)
@app.get("/{full_path:path}")
async def serve_frontend_app(full_path: str):
"""Serve the SvelteKit app for all non-API routes."""
# Don't serve frontend for API routes
if full_path.startswith("api/"):
return {"error": "API route not found"}
# Check if requesting a specific file
file_path = frontend_path / full_path
if file_path.is_file():
return FileResponse(file_path)
# For all other routes, serve the main index.html (SPA behavior)
index_path = frontend_path / "index.html"
if index_path.exists():
return FileResponse(index_path)
return {"error": "Frontend not available"}
else:
logger.warning(f"Frontend path not found: {frontend_path}")
@app.get("/")
async def no_frontend():
return {
"message": "LeRobot Arena Transport Server",
"frontend": "not available",
"api": "available at /api/",
"docs": "available at /api/docs",
}
else:
@app.get("/")
async def api_only():
return {
"message": "LeRobot Arena Transport Server - API Only",
"api": "available at /api/",
"docs": "available at /api/docs",
}
if __name__ == "__main__":
port = int(os.getenv("PORT", 7860))
host = os.getenv("HOST", "0.0.0.0")
logger.info("πŸ€– Starting LeRobot Arena Combined Server...")
logger.info(f" - API available at: http://{host}:{port}/api/")
logger.info(f" - API docs at: http://{host}:{port}/api/docs")
if serve_frontend:
logger.info(f" - Frontend available at: http://{host}:{port}/")
else:
logger.info(" - Frontend disabled (set SERVE_FRONTEND=true to enable)")
print(f"πŸ€– Starting LeRobot Arena Combined Server on {host}:{port}")
uvicorn.run(
"launch_with_ui:app",
host=host,
port=port,
reload=False,
log_level="info",
)