Files
Open-AutoGLM/dashboard/api/websocket.py
let5sne.win10 3552df23d6 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>
2026-01-09 02:20:06 +08:00

125 lines
4.3 KiB
Python

"""
WebSocket API endpoints for real-time updates.
"""
from fastapi import APIRouter, WebSocket, WebSocketDisconnect, Depends, Query
from dashboard.dependencies import get_ws_manager, get_device_manager
from dashboard.services.websocket_manager import WebSocketManager
from dashboard.services.device_manager import DeviceManager
router = APIRouter(prefix="/ws", tags=["websocket"])
@router.websocket("")
async def websocket_endpoint(
websocket: WebSocket,
client_id: str | None = Query(None),
ws_manager: WebSocketManager = Depends(get_ws_manager),
device_manager: DeviceManager = Depends(get_device_manager),
):
"""WebSocket endpoint for real-time updates.
This endpoint provides real-time updates for:
- Device connection/disconnection
- Task execution progress
- Screenshot updates
- Task completion
Query parameters:
client_id: Optional client ID (auto-generated if not provided)
Message types (client -> server):
- {"type": "subscribe", "device_id": "device_id"} - Subscribe to device updates
- {"type": "unsubscribe", "device_id": "device_id"} - Unsubscribe from device updates
- {"type": "ping"} - Ping server
Message types (server -> client):
- {"type": "device_update", "data": {...}} - Device status update
- {"type": "task_started", "data": {...}} - Task started
- {"type": "task_step", "data": {...}} - Task step update
- {"type": "task_completed", "data": {...}} - Task completed
- {"type": "task_failed", "data": {...}} - Task failed
- {"type": "task_stopped", "data": {...}} - Task stopped
- {"type": "screenshot", "data": {...}} - Screenshot update
- {"type": "error", "data": {...}} - Error occurred
- {"type": "pong"} - Pong response
"""
# Accept connection
client_id = await ws_manager.connect(websocket, client_id)
try:
# Send initial devices
devices = await device_manager.refresh_devices()
for device in devices:
await websocket.send_json({
"type": "device_update",
"data": {
"device_id": device.device_id,
"status": device.status.value,
"device_type": device.device_type.value,
"model": device.model,
"android_version": device.android_version,
"current_app": device.current_app,
"is_connected": device.is_connected,
}
})
# Message loop
while True:
data = await websocket.receive_json()
msg_type = data.get("type")
if msg_type == "subscribe":
# Subscribe to device updates
device_id = data.get("device_id", "*")
ws_manager.subscribe_to_device(client_id, device_id)
elif msg_type == "unsubscribe":
# Unsubscribe from device updates
device_id = data.get("device_id")
if device_id:
ws_manager.unsubscribe_from_device(client_id, device_id)
elif msg_type == "ping":
# Respond to ping
await websocket.send_json({"type": "pong"})
except WebSocketDisconnect:
ws_manager.disconnect(client_id)
except Exception:
ws_manager.disconnect(client_id)
@router.websocket("/device/{device_id}")
async def device_websocket(
device_id: str,
websocket: WebSocket,
ws_manager: WebSocketManager = Depends(get_ws_manager),
):
"""Device-specific WebSocket endpoint for real-time updates.
This endpoint provides real-time updates for a specific device.
Automatically subscribes to the device's updates.
Args:
device_id: Device identifier
"""
# Accept connection and auto-subscribe to device
client_id = await ws_manager.connect(websocket)
ws_manager.subscribe_to_device(client_id, device_id)
try:
# Keep connection alive
while True:
data = await websocket.receive_json()
# Handle client messages
if data.get("type") == "ping":
await websocket.send_json({"type": "pong"})
except WebSocketDisconnect:
ws_manager.disconnect(client_id)
except Exception:
ws_manager.disconnect(client_id)