feat: processing flow enhancement + responsive UI

Phase 2 - Processing flow:
- Multi-task monitoring: store supports concurrent task tracking
- Task retry: POST /api/tasks/{id}/retry re-runs failed tasks
- Dashboard multi-task cards with progress, error details, retry/dismiss
- Log panel expanded from 10 to 50 lines with "view all" link

Phase 3 - UI/UX:
- Mobile sidebar drawer (< 768px) with hamburger menu
- Layout responsive styles (768px, 480px breakpoints)
- Tasks/Logs pages responsive (stat cards, filters, columns)
- File views responsive (header wrap, button sizing)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-13 19:18:18 +08:00
parent 32af38fe2a
commit 7baf784a39
11 changed files with 585 additions and 101 deletions
+32 -5
View File
@@ -121,7 +121,34 @@ async def retry_task(
"""Retry a failed task by re-invoking its processing endpoint.
Only tasks with status ``failed`` may be retried.
For in-memory tasks with metadata, the original endpoint and request body
are used to faithfully reproduce the original call. For historical DB-only
tasks, the endpoint is looked up from ``_RETRY_ROUTE_MAP`` by task name.
"""
tm = request.state.task_manager
# --- Strategy 1: in-memory task with metadata ---
new_task = tm.retry_task(task_id)
if new_task is not None:
meta = new_task.metadata or {}
endpoint = meta.get("endpoint")
body = meta.get("body", {})
if endpoint:
base_url = f"http://{request.url.hostname}:{request.url.port}"
url = f"{base_url}{endpoint}"
auth_header = request.headers.get("authorization")
headers: dict[str, str] = {}
if auth_header:
headers["authorization"] = auth_header
async with httpx.AsyncClient() as client:
resp = await client.post(url, json=body, headers=headers)
return {"task_id": new_task.id, "status": "retried", "original_response": resp.json()}
# Metadata present but no endpoint — fall through to DB strategy
# (the new task was already created; caller can track it)
return {"task_id": new_task.id, "status": "retried"}
# --- Strategy 2: DB-only historical task (no in-memory record) ---
loop = asyncio.get_event_loop()
task = await loop.run_in_executor(
None, lambda: db_schema.query_task_by_id(task_id),
@@ -142,18 +169,18 @@ async def retry_task(
detail=f"未知的任务类型: {task_name}",
)
# Build the internal URL to the processing endpoint.
# Create a new in-memory task to track the retry.
new_task = tm.create_task(task_name)
base_url = f"http://{request.url.hostname}:{request.url.port}"
url = f"{base_url}{endpoint}"
# Forward the Authorization header so the processing endpoint can
# authenticate the request.
auth_header = request.headers.get("authorization")
headers: dict[str, str] = {}
headers = {}
if auth_header:
headers["authorization"] = auth_header
async with httpx.AsyncClient() as client:
resp = await client.post(url, headers=headers)
return resp.json()
return {"task_id": new_task.id, "status": "retried", "original_response": resp.json()}