""" Main entry point for Rubric AI. Runs both FastAPI backend and NiceGUI frontend on a single Uvicorn process. Designed for deployment on Hugging Face Spaces (port 7860). """ import re from starlette.middleware.base import BaseHTTPMiddleware from backend.app import create_app from backend.core.config import settings from frontend.ui import init_frontend from nicegui import ui # Create FastAPI application app = create_app() # Import API routes (registers @app decorators) import frontend.api_routes # noqa: F401 # Middleware to allow iframe embedding on HF Spaces class AllowIframeMiddleware(BaseHTTPMiddleware): async def dispatch(self, request, call_next): response = await call_next(request) # Remove X-Frame-Options to allow HF Spaces iframe embedding if "x-frame-options" in response.headers: del response.headers["x-frame-options"] # Remove or modify Content-Security-Policy frame-ancestors directive if "content-security-policy" in response.headers: csp = response.headers["content-security-policy"] # Remove frame-ancestors directive entirely to allow any iframe embedding csp = re.sub(r"frame-ancestors\s+[^;]+;?\s*", "", csp) if csp.strip(): response.headers["content-security-policy"] = csp else: del response.headers["content-security-policy"] return response app.add_middleware(AllowIframeMiddleware) # Initialize NiceGUI frontend routes BEFORE mounting init_frontend() # Mount NiceGUI onto FastAPI (modifies app in place) # This must happen at module level so Uvicorn can import 'main:app' ui.run_with( app, title="Rubric AI", favicon="favicon.png", storage_secret=settings.SUPABASE_ANON_KEY or "rubric-ai-secret", mount_path="/", reconnect_timeout=10, # Handle HF Spaces proxy timeouts ) if __name__ == "__main__": import uvicorn uvicorn.run( "main:app", host=settings.HOST, port=settings.PORT, reload=True, )