feat: add prompt-first batches and pausable task guidance

This commit is contained in:
chenmingxuan committed 2026-09-23 22:59:48 +08:00
1 parent cec9898e4c
commit 057f0c3050
44 files changed
+1080 -191

No files matched your search

+18 -5
View File
@@ -146,8 +146,13 @@ def report(session_id: str):
@router.get("/reports/{session_id}/{fmt}")
async def download_report(session_id: str, fmt: Literal["docx", "xlsx"]):
async def download_report(
session_id: str, fmt: Literal["docx", "xlsx", "screenshots", "video", "all"]
):
try:
if fmt in {"screenshots", "video", "all"}:
path = await asyncio.to_thread(report_export.export_session_media, session_id, fmt)
return FileResponse(path, filename=f"任务{session_id[:8]}-{fmt}.zip")
paths = await asyncio.to_thread(report_export.export_session, session_id, [fmt])
return FileResponse(paths[0], filename=f"任务报告-{session_id[:8]}.{fmt}")
except ValueError as exc:
@@ -167,21 +172,29 @@ def history(
query = """WITH history AS (
SELECT s.session_id, s.initial_goal, s.start_time, s.end_time,
COALESCE(j.status,s.status) status, COALESCE(json_extract(j.payload,'$.case.name'),s.initial_goal) name,
COALESCE(json_extract(j.payload,'$.case.tags'),'') tags
COALESCE(json_extract(j.payload,'$.case.tags'),'') tags,
COALESCE(json_extract(j.payload,'$.case.case_id'),'') case_id,
COALESCE(json_extract(j.payload,'$.case.category'),'') category,
COALESCE(json_extract(j.payload,'$.case.subcategory'),'') subcategory,
COALESCE(json_extract(j.payload,'$.case.preconditions'),'') preconditions
FROM sessions s LEFT JOIN automation_jobs j ON s.session_id=j.session_id
UNION ALL
SELECT j.session_id,json_extract(j.payload,'$.case.goal'),
COALESCE(json_extract(j.payload,'$.started_at'),json_extract(j.payload,'$.created_at')),
json_extract(j.payload,'$.ended_at'),j.status,json_extract(j.payload,'$.case.name'),
COALESCE(json_extract(j.payload,'$.case.tags'),'')
COALESCE(json_extract(j.payload,'$.case.tags'),''),
COALESCE(json_extract(j.payload,'$.case.case_id'),''),
COALESCE(json_extract(j.payload,'$.case.category'),''),
COALESCE(json_extract(j.payload,'$.case.subcategory'),''),
COALESCE(json_extract(j.payload,'$.case.preconditions'),'')
FROM automation_jobs j WHERE NOT EXISTS (SELECT 1 FROM sessions s WHERE s.session_id=j.session_id)
) """
where = " WHERE status IN ('completed','success','failed','cancelled','interrupted','skipped')"
args = []
if q:
where += " AND (name LIKE ? ESCAPE '\\' OR initial_goal LIKE ? ESCAPE '\\' OR session_id LIKE ? ESCAPE '\\' OR tags LIKE ? ESCAPE '\\')"
where += " AND (name LIKE ? ESCAPE '\\' OR initial_goal LIKE ? ESCAPE '\\' OR session_id LIKE ? ESCAPE '\\' OR tags LIKE ? ESCAPE '\\' OR case_id LIKE ? ESCAPE '\\' OR category LIKE ? ESCAPE '\\' OR subcategory LIKE ? ESCAPE '\\' OR preconditions LIKE ? ESCAPE '\\')"
escaped = "%" + q.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_") + "%"
args.extend([escaped] * 4)
args.extend([escaped] * 8)
for condition, value in [
("status = ?", status or None),
("start_time >= ?", start),
+61
View File
@@ -18,8 +18,16 @@ import json
from typing import Any
from fastapi import APIRouter, HTTPException, Request
from fastapi.responses import StreamingResponse
from pydantic import BaseModel, Field
from artemis.core.diagnostics import readiness_engine
from artemis.runtime import DeviceExecutionLock, device_pool
from artemis.runtime.task_control import (
add_instruction,
control_dir,
read_state,
request_pause,
request_resume,
)
try:
from admin_console.core.state import state
@@ -42,6 +50,48 @@ except ImportError:
router = APIRouter(tags=["tasks"])
class GuidanceRequest(BaseModel):
instruction: str = Field(min_length=1, max_length=10000)
def _live_task(session_id: str) -> dict:
from apps.admin_console.services.automation_store import identifier
identifier(session_id)
run = state.active_runs.get(session_id)
if not run or run["process"].returncode is not None:
raise HTTPException(409, "任务未在执行,无法暂停或指导")
return run
@router.post("/api/tasks/{session_id}/pause")
def pause_running_task(session_id: str):
_live_task(session_id)
request_pause(session_id)
return {"session_id": session_id, "status": "pausing"}
@router.post("/api/tasks/{session_id}/guide")
def guide_running_task(session_id: str, value: GuidanceRequest):
_live_task(session_id)
add_instruction(session_id, value.instruction, source="web")
if (
read_state(session_id).get("status") == "task_paused"
or (control_dir(session_id) / "pause.request").exists()
):
request_resume(session_id)
return {"session_id": session_id, "status": "guidance_submitted"}
@router.post("/api/tasks/{session_id}/continue")
def continue_running_task(session_id: str):
_live_task(session_id)
if read_state(session_id).get("status") != "task_paused":
raise HTTPException(409, "任务尚未暂停")
request_resume(session_id)
return {"session_id": session_id, "status": "resuming"}
@router.get("/api/tasks/presets")
async def get_task_presets(
category: str = "recommended",
@@ -285,6 +335,17 @@ async def get_status():
}
for owner in active_owners.values()
]
for sid, run in state.active_runs.items():
if run.get("parked"):
active_tasks.append(
{
"session_id": sid,
"device_id": run.get("device_id"),
"goal": run.get("goal"),
"pid": getattr(run.get("process"), "pid", None),
"status": read_state(sid).get("status") or "task_paused",
}
)
running_sid = (
(global_owner.session_id if global_owner else None)