console: 任务列表服务端分页 + 控制台分页面签
任务列表分页(服务端 + 前端): - GET /tasks 新增 limit(1..100,非法回 400)、offset、 order=desc、status/executor/q 服务端过滤;回 total(过滤后 计数)与 status_counts(全项目统计,不受过滤与分页影响, 数据卡片不再把当前页当成全项目);无 limit 时旧行为不变 (全量、正序,协调子任务与旧调用不受影响)。 - 前端每页 20 条、最新在前,上一页/下一页 + 页码;筛选与 搜索改服务端过滤(搜索防抖 300ms),切筛选/切项目/派发 后回第 1 页,10 秒刷新与刷新按钮停在当前页;新测试 test_task_list_pagination.py 5 项。 分页面签: - 顶栏锚点导航改成 5 个页签:工作台(派发 + 任务列表 + 对话 + 详情)、项目(项目 + 成员)、环境与 Workers、 技能与自动化(技能 + 自动化 + 分诊)、管理与 Key; 数据卡片仍在页签上方常驻。选中任务存在 App 层 store 里,切页不丢,回来仍是同一任务、10 秒刷新续同步; 页签同步到 location.hash(#tab-workbench 等),刷新与 分享链接落在同一页签;窄屏工作台仍保持派发在前的顺序。 - 工作台页签仍是左(任务)右(派发/对话/详情)两栏, 沿用等高内部滚动规则;其它页签单列堆叠。
This commit is contained in:
1 parent
9a11ef27b8
commit
e5b6c45c83
11 files changed
+443
-122
No files matched your search
+56
-4
@@ -4495,6 +4495,17 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler):
|
||||
self._send_json(201, task)
|
||||
|
||||
def _list_tasks(self, user_id, query):
|
||||
# GET /tasks: server-side pagination + filtering.
|
||||
# - No limit param: old behaviour, all rows in created_at
|
||||
# order (coord children and existing callers rely on it).
|
||||
# - limit param: limit 1..100, offset >= 0 (invalid -> 400),
|
||||
# order=desc (newest first, the list's display order),
|
||||
# status/executor exact match, q case-insensitive
|
||||
# substring over id + prompt (same rule the frontend
|
||||
# used to apply client-side). total is the filtered
|
||||
# count before slicing, status_counts is over all
|
||||
# project rows (so the data card's breakdown is not
|
||||
# skewed by the current filter or page).
|
||||
project_ids = query.get("project_id")
|
||||
project_id = project_ids[0] if project_ids else None
|
||||
if not project_id:
|
||||
@@ -4508,6 +4519,22 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler):
|
||||
parent_ids = query.get("parent_task_id")
|
||||
if parent_ids:
|
||||
parent_task_id = parent_ids[0]
|
||||
limit = None
|
||||
offset = 0
|
||||
if query.get("limit"):
|
||||
try:
|
||||
limit = int(query["limit"][0])
|
||||
offset = int(query["offset"][0]) if query.get("offset") else 0
|
||||
except (TypeError, ValueError):
|
||||
self._send_json(400, {"error": "limit/offset must be integers"})
|
||||
return
|
||||
if limit < 1 or limit > 100 or offset < 0:
|
||||
self._send_json(400, {"error": "limit must be 1..100, offset >= 0"})
|
||||
return
|
||||
order_desc = bool(query.get("order") and query["order"][0] == "desc")
|
||||
status_f = query["status"][0] if query.get("status") else None
|
||||
executor_f = query["executor"][0] if query.get("executor") else None
|
||||
q_f = query["q"][0].lower() if query.get("q") else None
|
||||
conn = self._db()
|
||||
try:
|
||||
if parent_task_id:
|
||||
@@ -4523,13 +4550,38 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler):
|
||||
" ORDER BY created_at, id",
|
||||
(project_id,),
|
||||
).fetchall()
|
||||
amap = _attachments_map(conn, [r["id"] for r in rows])
|
||||
tmap = _task_tokens_map(conn, [r["id"] for r in rows])
|
||||
status_counts = {}
|
||||
for r in rows:
|
||||
s = r["status"]
|
||||
status_counts[s] = status_counts.get(s, 0) + 1
|
||||
if parent_task_id:
|
||||
filtered = rows
|
||||
else:
|
||||
filtered = []
|
||||
for r in rows:
|
||||
d = _task_to_dict(r, [], 0)
|
||||
if status_f and d["status"] != status_f:
|
||||
continue
|
||||
if executor_f and (d["executor"] or "") != executor_f:
|
||||
continue
|
||||
if q_f and q_f not in ((d["id"] or "") + " " + (d["prompt"] or "")).lower():
|
||||
continue
|
||||
filtered.append(r)
|
||||
total = len(filtered)
|
||||
if order_desc:
|
||||
filtered = list(reversed(filtered))
|
||||
page_rows = filtered[offset:offset + limit] if limit is not None else filtered
|
||||
amap = _attachments_map(conn, [r["id"] for r in page_rows])
|
||||
tmap = _task_tokens_map(conn, [r["id"] for r in page_rows])
|
||||
finally:
|
||||
conn.close()
|
||||
self._send_json(200, {"tasks": [
|
||||
out = {"tasks": [
|
||||
_task_to_dict(r, amap[r["id"]], tmap.get(r["id"], 0))
|
||||
for r in rows]})
|
||||
for r in page_rows], "total": total, "status_counts": status_counts}
|
||||
if limit is not None:
|
||||
out["limit"] = limit
|
||||
out["offset"] = offset
|
||||
self._send_json(200, out)
|
||||
|
||||
def _fetch_task(self, task_id):
|
||||
conn = self._db()
|
||||
|
||||
Reference in new issue
Block a user