perf(web): 切 task 并发拉 meta+messages + 默认窗口 60→30 + bump 0.12.16

selectTask 里 meta 与 messages 原本串行 await,改 Promise.all 并发省一个 RTT;
MSG_PAGE 60→30 降首屏传输与 markdown/highlight 同步渲染量。切 task 慢非索引问题
((task_id, idx) 唯一索引已覆盖主查询),故只优化前端串行与窗口大小。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
caoqianming 2026-06-16 08:21:26 +08:00
parent ec27fcae3e
commit 82feecef06
3 changed files with 16 additions and 5 deletions

View File

@ -21,6 +21,11 @@
## 已完成关键能力 ## 已完成关键能力
### 2026-06-16 / 切 task 提速:meta+messages 并发拉 + 默认窗口降到 30
- 体感诊断:切 task 慢**不是索引问题**——`messages` 的 `UniqueConstraint(task_id, idx)` 在 PG 自带 `(task_id, idx)` 复合索引,主查询 `WHERE task_id=? ORDER BY idx`(app.py:1442)既走索引过滤又免排序;也不是"全量加载",前端早已尾部窗口分页。真正的低垂果实是 `selectTask` 里 meta 与 messages **串行 await**,以及首屏窗口偏大。
- `web/static/js/chat.js`:`selectTask` 把 `GET /v1/tasks/{id}`(meta)与 `loadMessages`(messages)改 `Promise.all` 并发(两者无依赖、落不同 DOM 区),省一个 RTT;`MSG_PAGE` 60→30,降首屏传输 + markdown/highlight 同步渲染量。bump 0.12.15 → 0.12.16。
### 2026-06-15 / plot_pub 吸收 nature-figure 投稿级复合图设计纪律 ### 2026-06-15 / plot_pub 吸收 nature-figure 投稿级复合图设计纪律
- 联网调研 `nature-figure` skill(MIT,github.com/Yuan1z0825/nature-skills):双层 manifest 路由 + Python/R 双后端 + 生物医学 gallery。判断不整包移植 —— 与已有 plot_pub 高度重叠、R/单细胞/在体内容跟建材院领域不沾边、多文件结构破坏 zcbot 单 SKILL.md 约定。 - 联网调研 `nature-figure` skill(MIT,github.com/Yuan1z0825/nature-skills):双层 manifest 路由 + Python/R 双后端 + 生物医学 gallery。判断不整包移植 —— 与已有 plot_pub 高度重叠、R/单细胞/在体内容跟建材院领域不沾边、多文件结构破坏 zcbot 单 SKILL.md 约定。

View File

@ -1,3 +1,3 @@
# zcbot 版本号单一事实源:web/app.py 的 FastAPI version、/healthz 返回、前端展示都引这里。 # zcbot 版本号单一事实源:web/app.py 的 FastAPI version、/healthz 返回、前端展示都引这里。
# 改版本只动这一行。 # 改版本只动这一行。
__version__ = "0.12.15" __version__ = "0.12.16"

View File

@ -241,10 +241,15 @@ export async function selectTask(tid) {
$("chat-stream").innerHTML = `<div class="empty">加载中…</div>`; $("chat-stream").innerHTML = `<div class="empty">加载中…</div>`;
renderTaskProgressDock([]); renderTaskProgressDock([]);
try { try {
const meta = await api("GET", "/v1/tasks/" + tid); // meta 与 messages 无依赖,并发拉省一个 RTT(切 task 体感更跟手)。
// loadMessages 内部读 state.taskId(上方已置),不依赖 meta;两者落在
// 不同 DOM 区(chat-meta / chat-stream),谁先返回先渲染,互不干扰。
const [meta] = await Promise.all([
api("GET", "/v1/tasks/" + tid),
loadMessages(),
]);
state.taskMeta = meta; state.taskMeta = meta;
renderChatMeta(); renderChatMeta();
await loadMessages();
if (meta.run_status === "running" || meta.run_status === "cancelling") { if (meta.run_status === "running" || meta.run_status === "cancelling") {
ensureRunningTaskSubscribed(tid, `/v1/tasks/${tid}/events`); ensureRunningTaskSubscribed(tid, `/v1/tasks/${tid}/events`);
} else { } else {
@ -425,8 +430,9 @@ async function onChangeModel(ev) {
} }
// 切 task 默认只拉最近一批(尾部窗口);更早的靠向上滚动按需补。 // 切 task 默认只拉最近一批(尾部窗口);更早的靠向上滚动按需补。
// 60 而非 50 留余量:system/task_progress 等被 render 跳过的行也算在窗口里。 // 30:首屏只需铺满一两屏可见消息,降低传输 + markdown/highlight 同步渲染量,
const MSG_PAGE = 60; // 切换更跟手;system/task_progress 等被 render 跳过的行也算在这窗口里,留了余量。
const MSG_PAGE = 30;
async function loadMessages() { async function loadMessages() {
const data = await api("GET", `/v1/tasks/${state.taskId}/messages?limit=${MSG_PAGE}`); const data = await api("GET", `/v1/tasks/${state.taskId}/messages?limit=${MSG_PAGE}`);