""" AutoGLM Dashboard - FastAPI Main Application. This is the main entry point for the web dashboard. Run with: uvicorn dashboard.main:app --host 0.0.0.0 --port 8080 --reload """ import os from contextlib import asynccontextmanager from datetime import datetime from pathlib import Path from dotenv import load_dotenv from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import FileResponse, JSONResponse from fastapi.staticfiles import StaticFiles from dashboard.api import devices_router, tasks_router, websocket_router, video_learning_router from dashboard.config import config from dashboard.dependencies import ( get_device_manager, get_task_executor, get_ws_manager, ) from dashboard.services.device_manager import DeviceManager from dashboard.services.task_executor import TaskExecutor from dashboard.services.websocket_manager import WebSocketManager # Load .env file load_dotenv() @asynccontextmanager async def lifespan(app: FastAPI): """Application lifespan manager. Handles startup and shutdown events. """ # Startup print("=" * 50) print("AutoGLM Dashboard Starting...") print("=" * 50) # Initialize services device_manager = get_device_manager() task_executor = get_task_executor() ws_manager = get_ws_manager() # Link services task_executor.set_ws_manager(ws_manager) # Scan for devices on startup print("Scanning for devices...") try: devices = await device_manager.refresh_devices() print(f"Found {len(devices)} device(s)") for device in devices: status = "connected" if device.is_connected else "disconnected" print(f" - {device.device_id} ({status})") except Exception as e: print(f"Error scanning devices: {e}") print("=" * 50) print(f"Dashboard running on http://{config.HOST}:{config.PORT}") print(f"API docs: http://{config.HOST}:{config.PORT}/docs") print("=" * 50) yield # Shutdown print("Shutting down dashboard...") # Create FastAPI app app = FastAPI( title="AutoGLM Dashboard", description="Web-based multi-device control interface for AutoGLM", version="0.1.0", lifespan=lifespan, ) # Configure CORS for LAN access app.add_middleware( CORSMiddleware, allow_origins=config.CORS_ORIGINS, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Exception handlers @app.exception_handler(Exception) async def global_exception_handler(request: Request, exc: Exception): """Global exception handler.""" return JSONResponse( status_code=500, content={"error": str(exc), "type": type(exc).__name__}, ) # Include routers app.include_router(devices_router, prefix="/api") app.include_router(tasks_router, prefix="/api") app.include_router(websocket_router) app.include_router(video_learning_router) # Health check @app.get("/health") async def health_check(): """Health check endpoint.""" device_manager = get_device_manager() task_executor = get_task_executor() ws_manager = get_ws_manager() devices = device_manager.list_all_devices() connected_devices = sum(1 for d in devices if d.is_connected) return { "status": "healthy", "timestamp": datetime.now().isoformat(), "devices": { "total": len(devices), "connected": connected_devices, }, "tasks": { "active": task_executor.get_active_task_count(), }, "websocket": { "connections": ws_manager.get_connection_count(), }, } # Root endpoint - serve dashboard or API info @app.get("/") async def root(): """Root endpoint - returns API info or serves dashboard.""" # Check if static files exist static_path = Path(__file__).parent / "static" / "index.html" if static_path.exists(): return FileResponse(static_path) # Return API info if dashboard not built return { "name": "AutoGLM Dashboard API", "version": "0.1.0", "docs": "/docs", "endpoints": { "devices": "/api/devices", "tasks": "/api/tasks", "websocket": "/ws", "health": "/health", }, } # Mount static files for dashboard (if exists) static_path = Path(__file__).parent / "static" if static_path.exists(): app.mount("/static", StaticFiles(directory=str(static_path)), name="static") # Mount static files for video learning screenshots video_learning_data_path = Path(config.VIDEO_LEARNING_OUTPUT_DIR) if video_learning_data_path.exists(): app.mount("/video-learning-data", StaticFiles(directory=str(video_learning_data_path)), name="video-learning-data") # Run script entry point if __name__ == "__main__": import uvicorn uvicorn.run( "dashboard.main:app", host=config.HOST, port=config.PORT, reload=config.DEBUG, )