from fastapi import FastAPI, Request, WebSocket, WebSocketDisconnect from fastapi.staticfiles import StaticFiles from fastapi.responses import HTMLResponse, JSONResponse from app.core.storage import ensure_storage_dirs from app.core.config import Settings # Page renderers from app.pages.landing import render_landing from app.pages.phone import render_phone_page from app.pages.gbridge import render_gbridge_page from app.pages.windsurf import render_windsurf_page from app.pages.receipts import render_receipts_page from app.pages.chat import render_chat_page # API routes from app.api.routes_sessions import router as sessions_router from app.api.routes_jobs import router as jobs_router from app.api.routes_receipts import router as receipts_router from app.api.routes_gbridge import router as gbridge_router from app.api.routes_windsurf import router as windsurf_router from app.api.routes_health import router as health_router from app.api.routes_chat import router as chat_router from app.api.routes_mesh import router as mesh_router # Phone relay from app.phone.relay import ( connect_worker_websocket, connect_client_websocket, disconnect_worker, disconnect_client, handle_worker_message, handle_client_message, handle_mac_node_message, ) from app.mac_relay import connect_mac_node, disconnect_mac_node from app.phone.sessions import verify_worker_join_token # gbridge router from app.gbridge.router import route_capability_call app = FastAPI(title="GBridge PhoneGPU", version="0.1.0") # Mount static assets app.mount("/static", StaticFiles(directory="app/static", html=True), name="static") # Include API routes app.include_router(sessions_router) app.include_router(jobs_router) app.include_router(receipts_router) app.include_router(gbridge_router) app.include_router(windsurf_router) app.include_router(health_router) app.include_router(chat_router) app.include_router(mesh_router) @app.on_event("startup") async def startup_event(): ensure_storage_dirs() @app.on_event("shutdown") async def shutdown_event(): pass # Page routes @app.get("/", response_class=HTMLResponse) async def root(): return render_landing() @app.get("/phone", response_class=HTMLResponse) async def phone_page(request: Request): return render_phone_page(request) @app.get("/gbridge", response_class=HTMLResponse) async def gbridge_page(request: Request): return render_gbridge_page(request) @app.get("/windsurf", response_class=HTMLResponse) async def windsurf_page(request: Request): return render_windsurf_page(request) @app.get("/chat", response_class=HTMLResponse) async def chat_page(request: Request): return render_chat_page(request) @app.get("/receipts", response_class=HTMLResponse) async def receipts_page(request: Request): return render_receipts_page(request) @app.get("/dashboard", response_class=HTMLResponse) async def dashboard_page(): with open("app/static/dashboard.html", "r") as f: return f.read() @app.get("/worker") async def worker_page(request: Request, session_id: str, token: str): if not verify_worker_join_token(session_id, token): return HTMLResponse(content="

Invalid token

", status_code=403) # Serve the iPhone worker PWA with open("app/static/worker/worker.html", "r") as f: return HTMLResponse(content=f.read(), status_code=200) # gbridge capability endpoint — the core primitive @app.post("/cap/{capability_name}") async def capability_endpoint(capability_name: str, request: Request): body = await request.json() session_id = body.get("session_id") payload = body.get("payload", {}) result = route_capability_call(capability_name, payload, session_id) return result # WebSocket routes @app.websocket("/ws/gbridge/worker/{session_id}/{worker_id}") async def worker_websocket(ws: WebSocket, session_id: str, worker_id: str): await ws.accept() await connect_worker_websocket(session_id, worker_id, ws) try: while True: data = await ws.receive_json() await handle_worker_message(session_id, worker_id, data) except WebSocketDisconnect: await disconnect_worker(session_id, worker_id) @app.websocket("/ws/gbridge/client/{session_id}") async def client_websocket(ws: WebSocket, session_id: str): await ws.accept() await connect_client_websocket(session_id, ws) try: while True: data = await ws.receive_json() await handle_client_message(session_id, data) except WebSocketDisconnect: await disconnect_client(session_id) @app.websocket("/ws/mac/{session_id}/{node_id}") async def mac_websocket(ws: WebSocket, session_id: str, node_id: str): await ws.accept() await connect_mac_node(node_id, ws) try: while True: data = await ws.receive_json() await handle_mac_node_message(node_id, data) except WebSocketDisconnect: await disconnect_mac_node(node_id)