dsh-neotui 0.1.19 → 0.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/panels.js +31 -4
- package/src/views.js +15 -3
package/package.json
CHANGED
package/src/panels.js
CHANGED
|
@@ -560,6 +560,9 @@ export class TrajectoryPanel extends Widget {
|
|
|
560
560
|
this.flashUntil = 0;
|
|
561
561
|
this.loadPromise = null; // dedupes concurrent load(currentSession)
|
|
562
562
|
this.loadTarget = null;
|
|
563
|
+
this.liveTickAt = 0; // ⏱ live timer re-render throttle
|
|
564
|
+
this.tailFetchAt = 0; // tail-window auto-refresh throttle
|
|
565
|
+
this.refreshing = false;
|
|
563
566
|
this.winSeqLo = null; // visible window = first-event SEQ range; null = follow the tail
|
|
564
567
|
this.winSeqHi = null;
|
|
565
568
|
// LEFT click toggles a step's 详细/简略 expansion (the ▸/▾ triangle).
|
|
@@ -977,13 +980,37 @@ export class TrajectoryPanel extends Widget {
|
|
|
977
980
|
screen.text(this.x + 2, this.y + 1, "加载轨迹…", { fg: K.FAINT });
|
|
978
981
|
return;
|
|
979
982
|
}
|
|
980
|
-
// the live step's ⏱ timer re-renders once per second while the turn runs
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
983
|
+
// the live step's ⏱ timer re-renders once per second while the turn runs,
|
|
984
|
+
// and the tail window refreshes periodically so a NEW turn's step (and
|
|
985
|
+
// its timer) appears without pressing r
|
|
986
|
+
if (this.app.chat?.running && this.winSeqLo == null) {
|
|
987
|
+
if (Date.now() - (this.liveTickAt ?? 0) > 1000) {
|
|
988
|
+
this.liveTickAt = Date.now();
|
|
989
|
+
this.buildLines();
|
|
990
|
+
}
|
|
991
|
+
if (!this.refreshing && Date.now() - (this.tailFetchAt ?? 0) > 4000) {
|
|
992
|
+
this.tailFetchAt = Date.now();
|
|
993
|
+
this.#refreshTail();
|
|
994
|
+
}
|
|
984
995
|
}
|
|
985
996
|
this.view.render(screen);
|
|
986
997
|
}
|
|
998
|
+
/** Re-fetch the tail window while following the live turn. */
|
|
999
|
+
async #refreshTail() {
|
|
1000
|
+
if (!this.sessionId) return;
|
|
1001
|
+
this.refreshing = true;
|
|
1002
|
+
try {
|
|
1003
|
+
const h = await this.app.api.call("session.history", { sessionId: this.sessionId, maxMessages: 20 });
|
|
1004
|
+
this.allEvents = h.events;
|
|
1005
|
+
this.minSeq = h.events[0]?.event?.seq ?? this.minSeq;
|
|
1006
|
+
this.hasMore = h.hasMore;
|
|
1007
|
+
this.stats = h.projections?.values?.sessionStats ?? this.stats;
|
|
1008
|
+
this.build();
|
|
1009
|
+
this.buildLines();
|
|
1010
|
+
this.app.redraw();
|
|
1011
|
+
} catch { /* next tick retries */ }
|
|
1012
|
+
this.refreshing = false;
|
|
1013
|
+
}
|
|
987
1014
|
onMouse(ev) {
|
|
988
1015
|
// RIGHT click on a step: context menu (expand/collapse · jump · detail).
|
|
989
1016
|
if (ev.kind === "press" && ev.button === 2) {
|
package/src/views.js
CHANGED
|
@@ -130,8 +130,11 @@ function applyEvent(nodes, event, view, log, state = null) {
|
|
|
130
130
|
const text = partsToText(d.content ?? d.message?.content);
|
|
131
131
|
const images = partsToImages(d.content ?? d.message?.content);
|
|
132
132
|
const id = d.id ?? null;
|
|
133
|
-
|
|
134
|
-
|
|
133
|
+
// the turn timer starts HERE: the user message carries the turn start
|
|
134
|
+
// so the 🕐 ticker shows even before the first assistant chunk lands
|
|
135
|
+
const turnStartAt = st.turnStart ?? event.time ?? Date.now();
|
|
136
|
+
if (text !== null) nodes.push({ kind: "user", text, images, id, step: st.step, turnStartAt });
|
|
137
|
+
else if (images) nodes.push({ kind: "user", text: "", images, id, step: st.step, turnStartAt });
|
|
135
138
|
break;
|
|
136
139
|
}
|
|
137
140
|
case "assistant/message": {
|
|
@@ -1201,7 +1204,9 @@ export class ChatView extends Widget {
|
|
|
1201
1204
|
const ckey = `${realIdx}|${w}|${expKey}|${blockKeys}|${this.thinkMode}|${this.bashMode}|${node.streaming ? "s" : "f"}|${themeName()}|${node.step ?? "-"}|${userPrefix()}|${node.turnMs ?? "-"}`;
|
|
1202
1205
|
// Streaming nodes re-render every frame: their text grows without any
|
|
1203
1206
|
// change to the cache key, so caching them freezes the live think/tool/text.
|
|
1204
|
-
|
|
1207
|
+
// The LAST node re-renders too while a turn runs — its ticking 🕐 timer
|
|
1208
|
+
// must not be baked into a cached entry.
|
|
1209
|
+
const hit = (node.streaming || (this.running && realIdx === this.nodes.length - 1)) ? undefined : this.cache.get(ckey);
|
|
1205
1210
|
if (hit) {
|
|
1206
1211
|
for (const [rs, re, bg] of hit.cards ?? []) {
|
|
1207
1212
|
this.cardRanges.push([lines.length + rs, lines.length + re, bg]);
|
|
@@ -1263,6 +1268,13 @@ export class ChatView extends Widget {
|
|
|
1263
1268
|
markImg(realIdx, ii);
|
|
1264
1269
|
}
|
|
1265
1270
|
}
|
|
1271
|
+
// the turn timer starts at the QUESTION: while the reply is still
|
|
1272
|
+
// in the request/queue phase (no assistant node yet), the ticker
|
|
1273
|
+
// lives under the user message — web-style deep-dive from t=0.
|
|
1274
|
+
if (this.running && realIdx === this.nodes.length - 1 && node.turnStartAt != null) {
|
|
1275
|
+
lines.push([{ t: ` 🕐 本轮进行中…已经过 ${fmtDuration(Date.now() - node.turnStartAt)}`, fg: T.WARN, bold: true }]);
|
|
1276
|
+
mark(realIdx);
|
|
1277
|
+
}
|
|
1266
1278
|
sep();
|
|
1267
1279
|
break;
|
|
1268
1280
|
}
|