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>
157 lines
3.9 KiB
Python
157 lines
3.9 KiB
Python
"""
|
|
Task management API endpoints.
|
|
"""
|
|
|
|
from typing import List
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
|
|
from dashboard.config import config
|
|
from dashboard.dependencies import get_task_executor
|
|
from dashboard.models.task import TaskCreateRequest, TaskSchema, TaskStatus
|
|
from dashboard.services.task_executor import TaskExecutor
|
|
|
|
router = APIRouter(prefix="/tasks", tags=["tasks"])
|
|
|
|
|
|
@router.post("/execute", response_model=dict)
|
|
async def execute_task(
|
|
request: TaskCreateRequest,
|
|
executor: TaskExecutor = Depends(get_task_executor),
|
|
):
|
|
"""Execute task on device.
|
|
|
|
Args:
|
|
request: Task creation request
|
|
|
|
Returns:
|
|
Task ID
|
|
"""
|
|
# Fill in model config from environment if using defaults
|
|
if request.base_url == "http://localhost:8000/v1":
|
|
request.base_url = config.MODEL_BASE_URL
|
|
if request.model_name == "autoglm-phone-9b":
|
|
request.model_name = config.MODEL_NAME
|
|
if request.api_key == "EMPTY":
|
|
request.api_key = config.MODEL_API_KEY
|
|
|
|
task_id = await executor.execute_task(request)
|
|
|
|
return {"task_id": task_id, "message": "Task started"}
|
|
|
|
|
|
@router.post("/{task_id}/stop")
|
|
async def stop_task(
|
|
task_id: str,
|
|
executor: TaskExecutor = Depends(get_task_executor),
|
|
):
|
|
"""Stop running task.
|
|
|
|
Args:
|
|
task_id: Task identifier
|
|
|
|
Returns:
|
|
Stop confirmation
|
|
"""
|
|
await executor.stop_task(task_id)
|
|
|
|
return {"message": f"Task {task_id} stop requested"}
|
|
|
|
|
|
@router.get("", response_model=List[TaskSchema])
|
|
async def list_tasks(
|
|
executor: TaskExecutor = Depends(get_task_executor),
|
|
):
|
|
"""List all tasks (active and recent).
|
|
|
|
Returns:
|
|
List of task schemas
|
|
"""
|
|
return await executor.list_tasks()
|
|
|
|
|
|
@router.get("/{task_id}", response_model=TaskSchema)
|
|
async def get_task_status(
|
|
task_id: str,
|
|
executor: TaskExecutor = Depends(get_task_executor),
|
|
):
|
|
"""Get task status.
|
|
|
|
Args:
|
|
task_id: Task identifier
|
|
|
|
Returns:
|
|
Task schema
|
|
|
|
Raises:
|
|
HTTPException: If task not found
|
|
"""
|
|
task = await executor.get_task_status(task_id)
|
|
|
|
if task is None:
|
|
raise HTTPException(status_code=404, detail=f"Task {task_id} not found")
|
|
|
|
return task
|
|
|
|
|
|
@router.get("/{task_id}/screenshot")
|
|
async def get_task_screenshot(
|
|
task_id: str,
|
|
executor: TaskExecutor = Depends(get_task_executor),
|
|
):
|
|
"""Get latest screenshot from task execution.
|
|
|
|
Args:
|
|
task_id: Task identifier
|
|
|
|
Returns:
|
|
Screenshot data (base64 encoded)
|
|
|
|
Raises:
|
|
HTTPException: If task not found or device unavailable
|
|
"""
|
|
task = await executor.get_task_status(task_id)
|
|
|
|
if task is None:
|
|
raise HTTPException(status_code=404, detail=f"Task {task_id} not found")
|
|
|
|
# Get screenshot from device manager
|
|
from dashboard.dependencies import get_device_manager
|
|
|
|
device_manager = get_device_manager()
|
|
screenshot = await device_manager.get_screenshot(task.device_id)
|
|
|
|
if screenshot is None:
|
|
raise HTTPException(
|
|
status_code=500, detail="Failed to capture screenshot from device"
|
|
)
|
|
|
|
return {"task_id": task_id, "device_id": task.device_id, "screenshot": screenshot}
|
|
|
|
|
|
@router.get("/stats/summary")
|
|
async def get_task_stats(
|
|
executor: TaskExecutor = Depends(get_task_executor),
|
|
):
|
|
"""Get task execution statistics.
|
|
|
|
Returns:
|
|
Task statistics summary
|
|
"""
|
|
tasks = await executor.list_tasks()
|
|
|
|
total = len(tasks)
|
|
running = sum(1 for t in tasks if t.status == TaskStatus.RUNNING)
|
|
completed = sum(1 for t in tasks if t.status == TaskStatus.COMPLETED)
|
|
failed = sum(1 for t in tasks if t.status == TaskStatus.FAILED)
|
|
stopped = sum(1 for t in tasks if t.status == TaskStatus.STOPPED)
|
|
|
|
return {
|
|
"total": total,
|
|
"running": running,
|
|
"completed": completed,
|
|
"failed": failed,
|
|
"stopped": stopped,
|
|
"active_count": executor.get_active_task_count(),
|
|
}
|