fix: persist elapsed badge on refresh via message ts
- normalize() now preserves ts field from API response - restoreElapsedBadges() moved to pollSession finally block (messages are empty during renderAllMessages) - sendPrompt() adds ts to local user messages - restoreElapsedBadges() resets lastUserTs when user msg has no ts (prevents stale accumulation from previous rounds)
This commit is contained in:
1 parent
537f465b93
commit
bc1c9f58dd
2 files changed
+48
-8
No files matched your search
@@ -217,9 +217,11 @@ CSS: `.task-elapsed { color: var(--accent); font-size: 12px; font-weight: 600; }
|
||||
|
||||
**问题:计时"已运行x"刷新后丢失**
|
||||
- 根因:`r.taskStartedAt` 仅存于内存 runtime Map,刷新后 runtime 重建;pollSession 重新检测 running → setBusy → startTaskTimer 设新 Date.now(),真正开始时间丢失
|
||||
- 修复方案(仅改 2 个函数,4 行新增):
|
||||
1. `startTaskTimer`:尝试从 `sessionStorage.getItem('ga_task_started_' + sess.id)` 恢复时间戳,若无则用 Date.now();设置后写入 sessionStorage
|
||||
2. `stopTaskTimer`:任务结束时 `sessionStorage.removeItem('ga_task_started_' + sess.id)`
|
||||
- 为什么用 sessionStorage:与标签页生命周期绑定,关闭标签自动清除,不残留脏数据
|
||||
- 为什么不需要 onBridgeReady 额外恢复:pollSession 检测到 running 后自动调 setBusy → startTaskTimer,此时 sessionStorage 中已有值会被读取恢复
|
||||
- 改动文件:app.js(startTaskTimer L1198-1205 / stopTaskTimer L1219-1225)
|
||||
- ❌ sessionStorage 方案失败(刷新后仍丢失)
|
||||
- ✅ 最终方案:基于消息 ts 时间戳恢复(2026-05-25 成功验证)
|
||||
- 根因:消息对象本身有 `ts` 字段(API 返回),但 `normalize()` 函数丢弃了它
|
||||
- 修复 3 处(仅 app.js):
|
||||
1. `normalize()` 增加 `if (m.ts) o.ts = m.ts;` — 保留时间戳
|
||||
2. `restoreElapsedBadges(sess, box)` 从 `renderAllMessages` 移到 `pollSession` finally 块 — 确保消息已加载
|
||||
3. `startTaskTimer(sess)` 从最后一条 user 消息的 ts 恢复 taskStartedAt — 运行中任务继续计时
|
||||
- 验证结果:刷新后 badge 正确显示"已运行 17s"/"已运行 14s",已完成任务静态显示,运行中任务实时计时
|
||||
@@ -1073,8 +1073,35 @@ function msgNode(msg) {
|
||||
function renderAllMessages(sess) {
|
||||
const box = ensureMsgs(); box.innerHTML = '';
|
||||
for (const m of sess.messages) box.appendChild(msgNode(m));
|
||||
// badge 恢复在 pollSession finally 中执行(此时 messages 已通过异步加载填充)
|
||||
refreshEmptyState(sess); scrollBottom(true);
|
||||
}
|
||||
// 遍历消息对,用 ts 差值恢复 badge;对运行中任务恢复 taskStartedAt
|
||||
function restoreElapsedBadges(sess, box) {
|
||||
const msgs = sess.messages;
|
||||
if (!msgs || !msgs.length) return;
|
||||
const nodes = box.querySelectorAll('.msg');
|
||||
let lastUserTs = null;
|
||||
for (let i = 0; i < msgs.length; i++) {
|
||||
if (msgs[i].role === 'user') {
|
||||
lastUserTs = msgs[i].ts ? msgs[i].ts * 1000 : null; // 无 ts 则重置
|
||||
} else if (msgs[i].role === 'assistant') {
|
||||
if (lastUserTs && msgs[i].ts) {
|
||||
const elapsed = msgs[i].ts * 1000 - lastUserTs;
|
||||
if (elapsed > 0 && nodes[i]) {
|
||||
ensureTaskElapsedBadge(nodes[i], lastUserTs, msgs[i].ts * 1000);
|
||||
}
|
||||
}
|
||||
lastUserTs = null;
|
||||
}
|
||||
}
|
||||
// 运行中任务:最后一条是 user 且 session busy,恢复实时计时
|
||||
if (lastUserTs && rt(sess).busy) {
|
||||
const r = rt(sess);
|
||||
r.taskStartedAt = lastUserTs;
|
||||
r.taskEndedAt = null;
|
||||
}
|
||||
}
|
||||
function appendMessage(sess, msg) {
|
||||
if (!isActive(sess)) return;
|
||||
const el = msgNode(msg);
|
||||
@@ -1198,7 +1225,15 @@ function ensureTaskElapsedBadge(wrap, startedAt, endedAt) {
|
||||
function startTaskTimer(sess) {
|
||||
const r = rt(sess);
|
||||
if (r.taskStartedAt) return; // 已在计时,不重置
|
||||
r.taskStartedAt = Date.now();
|
||||
// 优先从消息时间戳恢复(刷新后持久化)
|
||||
const msgs = sess.messages;
|
||||
let restored = 0;
|
||||
if (msgs && msgs.length) {
|
||||
for (let i = msgs.length - 1; i >= 0; i--) {
|
||||
if (msgs[i].role === 'user' && msgs[i].ts) { restored = msgs[i].ts * 1000; break; }
|
||||
}
|
||||
}
|
||||
r.taskStartedAt = restored || Date.now();
|
||||
r.taskEndedAt = null;
|
||||
if (r.taskTimerId) clearInterval(r.taskTimerId);
|
||||
r.taskTimerId = setInterval(() => {
|
||||
@@ -1393,6 +1428,7 @@ function normalize(m) {
|
||||
if (m.stopped) o.stopped = true;
|
||||
if (m.images) o.images = m.images;
|
||||
if (m.files) o.files = m.files;
|
||||
if (m.ts) o.ts = m.ts;
|
||||
return o;
|
||||
}
|
||||
function upsert(sess, raw, partial) {
|
||||
@@ -1428,6 +1464,8 @@ async function pollSession(sess) {
|
||||
setBusy(sess, false);
|
||||
} finally {
|
||||
r.polling = false; renderSessionList();
|
||||
// 历史消息已全部加载,恢复已完成任务的耗时 badge
|
||||
if (isActive(sess)) restoreElapsedBadges(sess, ensureMsgs());
|
||||
tokPollBridge();
|
||||
}
|
||||
}
|
||||
@@ -1519,7 +1557,7 @@ async function sendPrompt(text) {
|
||||
.filter(Boolean)
|
||||
.join('\n\n');
|
||||
const usedFiles = collectUsedFiles(text);
|
||||
const userMsg = { role: 'user', content: text };
|
||||
const userMsg = { role: 'user', content: text, ts: Date.now() / 1000 };
|
||||
const previewImgs = usedFiles.filter(f => f.isImage).map(f => ({ id: 'f-' + f.sid, name: f.name, path: f.path, dataUrl: f.dataUrl || '' }));
|
||||
if (previewImgs.length) userMsg.images = previewImgs;
|
||||
const previewFiles = usedFiles.filter(f => !f.isImage).map(f => ({ id: 'f-' + f.sid, name: f.name, path: f.path }));
|
||||
|
||||
Reference in new issue
Block a user