From 13e3e50371e0a3d4b8f789a893815a9bf1a8d0a1 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Fri, 31 Jul 2026 15:02:13 +0800 Subject: [PATCH] fix(web): sort tasks by recent activity --- CHANGELOG.md | 4 ++++ PROGRESS.md | 3 ++- RUN.md | 2 +- core/__init__.py | 2 +- tests/test_web_common.py | 24 ++++++++++++++++++++++++ web/common.py | 6 +++--- web/routers/tasks.py | 2 +- web/static/dev.html | 4 ++-- web/static/js/chat.js | 2 +- 9 files changed, 39 insertions(+), 10 deletions(-) create mode 100644 tests/test_web_common.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 909dc99..b512de9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ > 所以不是每个版本号都有条目。条目格式 `## <版本> — <日期>`,新条目加在最上面。 > 工程口径的完整记录见 `PROGRESS.md` / git log。 +## 0.60.18 — 2026-07-31 + +- 左侧任务列表默认按最近更新时间排序,继续处理过的对话会自动回到列表前面;创建时间、名称和状态排序仍可手动选择。 + ## 0.60.17 — 2026-07-31 - 修复读取论文、PDF 等外部内容时,个别不可见字符可能导致工具结果保存失败并中断对话的问题。 diff --git a/PROGRESS.md b/PROGRESS.md index 32cb1fd..e70d46c 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -2,7 +2,7 @@ > 配合 `DESIGN.md`。本文件只记 phase 状态、决策偏差、文件量、下一步。每条 1-2 句:做了啥 + 关键判断;细节查 `git log` / `git diff` / `DESIGN §7.9`。 -最后更新:2026-07-31(消息 JSONB NUL 字符兼容,bump 0.60.17) +最后更新:2026-07-31(任务列表默认按最近更新时间排序,bump 0.60.18) --- @@ -23,6 +23,7 @@ ### 2026-07-31 +- **07-31 / 0.60.18 / 任务列表默认按最近更新时间排序**:普通任务列表的前后端默认排序统一由创建时间倒序改为更新时间倒序,最近继续处理的对话会回到顶部;显式 `ordering=-created_at` 等既有排序参数和全部下拉选项保持兼容。新增默认值、非法参数回退及创建时间兼容 3 项单元测试,Python/JavaScript 语法与 diff 格式检查通过;无 schema、migration 或依赖变化。 - **07-31 / 0.60.17 / 消息 JSONB NUL 字符兼容**:外部工具从论文、PDF 等来源提取的文本可能夹带真实 NUL,Python JSON 会编码为 PostgreSQL `jsonb` 无法转换成 text 的 `\u0000`,导致 tool message INSERT 回滚并中断 run。新增递归清洗并在 `Message.payload` ORM 写入边界统一守门,`Session.append` 同步清洗内存上下文,保留制表符、换行及其余 Unicode;无 schema、migration 或存量数据改动。相关 6 项单元测试、Python 编译及 diff 格式检查通过;未设置 `ZCBOT_TEST_DB_URL`,未连接数据库。 - **07-31 / 0.60.16 / platform 历史 task 稳定进入 + 新建任务名称可选**:普通 platform iframe 不再复用 localStorage 缓存 JWT 抢跑,始终等待父页面本次 `zcbot-token` 后再进入初始 task,消除旧 token 401 后界面永久停在“加载中”的时序;企微/App fragment 登录路径不变。`POST /v1/tasks` 省略/留空 name 且提供 working_dir 时自动以“新对话”占位并启用首条消息标题生成,旧 `auto_title` 语义继续兼容;快速新对话不再发送冗余字段,自定义创建统一为工作目录必填、任务名选填,并换用可搜索/可创建的目录组合框。27 项无 DB 测试、Python 编译、3 个 JavaScript 语法及 diff 格式检查通过;DB 路由用例已补,未配置 `ZCBOT_TEST_DB_URL` 时安全跳过。 - **07-31 / 0.60.15 / 嵌入初始 task 直接进入工作目录**:platform iframe、企业微信和 App 携带 `task_id` 首次进入时,由 `enterApp` 直接选择目标 task,跳过新对话草稿及 user_root 文件面板初始化,随后沿既有 `selectTask` 链路同步 `working_dir`;401 重签仍不重复定位,普通登录空态不变。JavaScript 语法、7 项静态前端测试及 diff 格式检查通过;无 API、schema、migration 或依赖变化。 diff --git a/RUN.md b/RUN.md index 91eaf2a..638a33e 100644 --- a/RUN.md +++ b/RUN.md @@ -265,7 +265,7 @@ curl --noproxy '*' -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8765/v1/ta | `POST /v1/auth/login_password` | dev SPA 邮箱密码;body `{email, password}` → `{token,...,email,...}`;邮箱不存在 / 密码错 / 未设密码统一 403 | 豁免 | | `POST /v1/auth/change_password` | dev SPA 顶栏「改密码」;body `{old_password, new_password}`(user_id 取自 JWT)→ `{ok:true}`;新密码 <6→400、旧密码错 / 无密码(platform_key 建的行)→403 | 必填 | | `POST /v1/tasks` | 创建 task,body `{name?, working_dir?, description?, skill?}`;省略/留空 `name` 时必须传 `working_dir`,后端以“新对话”占位并在首条消息后自动命名;显式 `name` 时仍可省略 `working_dir` 并沿用同名目录。旧字段 `auto_title` 仅作兼容保留,新调用方无需传 | 必填 | -| `GET /v1/tasks?page=&page_size=&status=&skill=&working_dir=&q=&ordering=` | 列任务,默认 `-created_at`;响应 `{page, page_size, count, results}`;`ordering` DRF 风格逗号分隔 `-field` 倒序,allowlist created_at/updated_at/name/status | 必填 | +| `GET /v1/tasks?page=&page_size=&status=&skill=&working_dir=&q=&ordering=` | 列任务,默认 `-updated_at`;响应 `{page, page_size, count, results}`;`ordering` DRF 风格逗号分隔 `-field` 倒序,allowlist created_at/updated_at/name/status | 必填 | | `GET /v1/tasks/{id}` | 单 task meta + `n_messages`;跨 user → 404 | 必填 | | `PATCH /v1/tasks/{id}` | `{status?,description?,name?,skill?}`;active 不让从 web 切回 | 必填 | | `DELETE /v1/tasks/{id}` | **软删**(204):置 `deleted_at`,从列表隐藏;messages/usage_events 及工作目录文件全部保留(留作语料 + 可恢复),不动任何磁盘文件;已软删幂等 204 | 必填 | diff --git a/core/__init__.py b/core/__init__.py index 82035d8..7e14266 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -1,3 +1,3 @@ # zcbot 版本号单一事实源:web/app.py 的 FastAPI version、/healthz 返回、前端展示都引这里。 # 改版本只动这一行。 -__version__ = "0.60.17" +__version__ = "0.60.18" diff --git a/tests/test_web_common.py b/tests/test_web_common.py new file mode 100644 index 0000000..6c3d431 --- /dev/null +++ b/tests/test_web_common.py @@ -0,0 +1,24 @@ +import unittest + +from web.common import parse_ordering + + +class TaskOrderingTests(unittest.TestCase): + def test_default_ordering_uses_latest_update_first(self): + ordering = parse_ordering(None) + + self.assertEqual([str(clause) for clause in ordering], ["tasks.updated_at DESC"]) + + def test_invalid_ordering_falls_back_to_latest_update_first(self): + ordering = parse_ordering("not_a_field") + + self.assertEqual([str(clause) for clause in ordering], ["tasks.updated_at DESC"]) + + def test_explicit_created_at_ordering_remains_supported(self): + ordering = parse_ordering("-created_at") + + self.assertEqual([str(clause) for clause in ordering], ["tasks.created_at DESC"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/web/common.py b/web/common.py index b50b0ff..cad4391 100644 --- a/web/common.py +++ b/web/common.py @@ -31,7 +31,7 @@ STATUS_WRITABLE = ("completed", "abandoned") # web 不让从 web 端切回 acti # 改由 /v1/channel_tasks 单独取、前端做成固定卡片。新增渠道在此追加即可。 CHANNEL_MIRROR_KINDS = ("wechat", "wecom") ORDER_FIELDS = ("created_at", "updated_at", "name", "status") -ORDER_DEFAULT = "-created_at" +ORDER_DEFAULT = "-updated_at" def norm_path(p: str) -> str: @@ -57,7 +57,7 @@ def outline_snippet(text: Optional[str], maxlen: int = 48) -> str: def parse_ordering(s: Optional[str]) -> list: """DRF 风格 `ordering` 解析:逗号分隔多字段,`-` 前缀代表 desc。 - allowlist 见 `ORDER_FIELDS`;非法字段静默丢弃。全部非法或空串 → `ORDER_DEFAULT`(`-created_at`)。 + allowlist 见 `ORDER_FIELDS`;非法字段静默丢弃。全部非法或空串 → `ORDER_DEFAULT`(`-updated_at`)。 返回 sqlalchemy `order_by` 列表(可直接 `*expand`)。 """ spec = (s or "").strip() or ORDER_DEFAULT @@ -75,7 +75,7 @@ def parse_ordering(s: Optional[str]) -> list: cols.append(col.asc() if asc else col.desc()) if not cols: # 用户传了全无效字段 → fallback 默认 - cols = [Task.created_at.desc()] + cols = [Task.updated_at.desc()] return cols diff --git a/web/routers/tasks.py b/web/routers/tasks.py index fcebec1..394ff3e 100644 --- a/web/routers/tasks.py +++ b/web/routers/tasks.py @@ -113,7 +113,7 @@ def register_task_routes(app, *, require_user) -> None: - `run_status` 逗号分隔,allowlist `idle/running/cancelling/error/cancelled`;非法值静默忽略 (dev SPA 拉同 wd 活跃 task 用,通常 `running,cancelling`) - `ordering` DRF 风格,逗号分隔,`-field` 倒序;allowlist `created_at/updated_at/name/status`; - 非法字段静默忽略;**默认 `-created_at`**(创建时间倒序) + 非法字段静默忽略;**默认 `-updated_at`**(更新时间倒序) 返回标准分页壳 `{page, page_size, count, results}` —— count 供前端算总页数。 """ # clamp + sanitize diff --git a/web/static/dev.html b/web/static/dev.html index a721722..f175b8e 100644 --- a/web/static/dev.html +++ b/web/static/dev.html @@ -1780,10 +1780,10 @@