Add Web Dashboard with multi-device control and callback hooks
Features: - Web Dashboard: FastAPI-based dashboard with Vue.js frontend - Multi-device support (ADB, HDC, iOS) - Real-time WebSocket updates for task progress - Device management with status tracking - Task queue with execution controls (start/stop/re-execute) - Detailed task information display (thinking, actions, completion messages) - Screenshot viewing per device - LAN deployment support with configurable CORS - Callback Hooks: Interrupt and modify task execution - step_callback: Called after each step with StepResult - before_action_callback: Called before executing action - Support for task interruption and dynamic task switching - Example scripts demonstrating callback usage - Configuration: Environment-based configuration - .env file support for all settings - .env.example template with documentation - Model API configuration (base URL, model name, API key) - Dashboard configuration (host, port, CORS, device type) - Phone agent configuration (delays, max steps, language) Technical improvements: - Fixed forward reference issue with StepResult - Added package exports for callback types and configs - Enhanced dependencies with FastAPI, WebSocket support - Thread-safe task execution with device locking - Async WebSocket broadcasting from sync thread pool Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
175
dashboard/main.py
Normal file
175
dashboard/main.py
Normal file
@@ -0,0 +1,175 @@
|
||||
"""
|
||||
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
|
||||
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)
|
||||
|
||||
|
||||
# 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")
|
||||
|
||||
|
||||
# Run script entry point
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
uvicorn.run(
|
||||
"dashboard.main:app",
|
||||
host=config.HOST,
|
||||
port=config.PORT,
|
||||
reload=config.DEBUG,
|
||||
)
|
||||
Reference in New Issue
Block a user