dsh-neotui 0.0.5 → 0.0.7
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/views.js +21 -25
- package/src/widgets.js +0 -2
package/package.json
CHANGED
package/src/views.js
CHANGED
|
@@ -111,12 +111,12 @@ function diffText(oldText, newText, width) {
|
|
|
111
111
|
|
|
112
112
|
// ---- Chat node model ----
|
|
113
113
|
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
/** Apply ONE event onto a mutable nodes array (shared by full-window
|
|
115
|
+
* derivation and the incremental mux merge — the two paths must agree). */
|
|
116
|
+
function applyEvent(nodes, event, view, log) {
|
|
116
117
|
const cur = () => nodes[nodes.length - 1];
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
switch (event.type) {
|
|
118
|
+
const d = event.data ?? {};
|
|
119
|
+
switch (event.type) {
|
|
120
120
|
case "user/message": {
|
|
121
121
|
const text = partsToText(d.content ?? d.message?.content);
|
|
122
122
|
const images = partsToImages(d.content ?? d.message?.content);
|
|
@@ -238,7 +238,12 @@ function nodeForEvents(events, log) {
|
|
|
238
238
|
}
|
|
239
239
|
}
|
|
240
240
|
}
|
|
241
|
-
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** Re-derive the whole node list from a complete event window (open/poll). */
|
|
244
|
+
function nodeForEvents(events, log) {
|
|
245
|
+
const nodes = [];
|
|
246
|
+
for (const { event, view } of events) applyEvent(nodes, event, view, log);
|
|
242
247
|
return nodes;
|
|
243
248
|
}
|
|
244
249
|
|
|
@@ -541,7 +546,7 @@ export class ChatView extends Widget {
|
|
|
541
546
|
this.input = new Input({
|
|
542
547
|
x: this.x, y: this.y + this.h - 2, w: this.w, h: 1,
|
|
543
548
|
multi: true, maxLines: 6,
|
|
544
|
-
bg: T.
|
|
549
|
+
bg: T.PANEL,
|
|
545
550
|
placeholder: "输入消息…(Ctrl+J 换行,Enter 发送)",
|
|
546
551
|
onEnter: (v) => this.send(v),
|
|
547
552
|
onChange: () => this.inputChanged(),
|
|
@@ -557,23 +562,13 @@ export class ChatView extends Widget {
|
|
|
557
562
|
}
|
|
558
563
|
|
|
559
564
|
/** Queue a rebuild; flushed on the next frame render (throttles streaming). */
|
|
560
|
-
/** Merge freshly arrived events (mux frames
|
|
565
|
+
/** Merge freshly arrived events (mux frames) into the tail INCREMENTALLY —
|
|
566
|
+
* each event mutates this.nodes directly via the same applyEvent the
|
|
567
|
+
* full-window re-derivation uses, so a lone reasoning-delta appends to the
|
|
568
|
+
* existing block instead of wiping it (the old "shows then deleted" bug). */
|
|
561
569
|
mergeEvents(entries) {
|
|
562
|
-
const
|
|
563
|
-
|
|
564
|
-
const last = nodes[nodes.length - 1];
|
|
565
|
-
const mine = this.nodes[this.nodes.length - 1];
|
|
566
|
-
// Only merge into the ACTIVE streaming node; a newly started turn is pushed
|
|
567
|
-
// as its own node (merging into a finalized turn would overwrite it).
|
|
568
|
-
if (last.kind === "assistant" && mine && mine.kind === "assistant" && mine.streaming) {
|
|
569
|
-
mine.blocks = last.blocks;
|
|
570
|
-
mine.images = last.images ?? mine.images;
|
|
571
|
-
mine.id = last.id ?? mine.id;
|
|
572
|
-
mine.streaming = last.streaming;
|
|
573
|
-
mine.finalized = false;
|
|
574
|
-
} else {
|
|
575
|
-
this.nodes.push(...nodes);
|
|
576
|
-
this.expanded.add(this.nodes.length - 1);
|
|
570
|
+
for (const { event, view } of entries) {
|
|
571
|
+
applyEvent(this.nodes, event, view, this.app.log);
|
|
577
572
|
}
|
|
578
573
|
this.running = this.nodes.some((n) => n.kind === "assistant" && n.streaming);
|
|
579
574
|
this.queueRebuild();
|
|
@@ -737,6 +732,9 @@ export class ChatView extends Widget {
|
|
|
737
732
|
this.nodes = [{ kind: "system", text: `加载失败: ${e.message}` }];
|
|
738
733
|
}
|
|
739
734
|
this.#rebuild();
|
|
735
|
+
// Jump to the LIVE tail (the newest content) on open — the view would
|
|
736
|
+
// otherwise sit at the top showing the oldest turns.
|
|
737
|
+
this.view.scrollY = this.view.maxScroll();
|
|
740
738
|
}
|
|
741
739
|
|
|
742
740
|
async loadOlder() {
|
|
@@ -1094,7 +1092,6 @@ export class ChatView extends Widget {
|
|
|
1094
1092
|
const isBlank = this.app.sessions.find((s) => s.sessionId === this.sessionId)?.blank ?? false;
|
|
1095
1093
|
if (this.nodes.length === 0 && isBlank) {
|
|
1096
1094
|
this.#renderWelcome(screen);
|
|
1097
|
-
screen.hline(this.x, this.x + this.w - 1, this.input.y - 1, "─", { fg: T.BORDER2 });
|
|
1098
1095
|
this.input.render(screen);
|
|
1099
1096
|
return;
|
|
1100
1097
|
}
|
|
@@ -1120,7 +1117,6 @@ export class ChatView extends Widget {
|
|
|
1120
1117
|
}
|
|
1121
1118
|
}
|
|
1122
1119
|
this.#renderTodos(screen);
|
|
1123
|
-
screen.hline(this.x, this.x + this.w - 1, this.input.y - 1, "─", { fg: T.BORDER2 });
|
|
1124
1120
|
this.input.render(screen);
|
|
1125
1121
|
}
|
|
1126
1122
|
|
package/src/widgets.js
CHANGED
|
@@ -336,7 +336,6 @@ export class Input extends Widget {
|
|
|
336
336
|
if (!this.multi) {
|
|
337
337
|
// single-line: horizontal scroll (search/rename/picker inputs)
|
|
338
338
|
screen.fillRect(this.x, this.y, this.x + this.w - 1, this.y, " ", { bg: this.bg });
|
|
339
|
-
screen.hline(this.x, this.x + this.w - 1, this.y, "─", { fg: this.border, bg: this.bg });
|
|
340
339
|
const promptW = strWidth(this.prompt);
|
|
341
340
|
const inner = Math.max(0, this.w - promptW - 2);
|
|
342
341
|
screen.text(this.x, this.y, this.prompt, { fg: T.ACCENT, bg: this.bg });
|
|
@@ -358,7 +357,6 @@ export class Input extends Widget {
|
|
|
358
357
|
const rows = this.#visualRows();
|
|
359
358
|
const h = Math.min(this.maxLines, rows.length);
|
|
360
359
|
screen.fillRect(this.x, this.y, this.x + this.w - 1, this.y + h - 1, " ", { bg: this.bg });
|
|
361
|
-
screen.hline(this.x, this.x + this.w - 1, this.y, "─", { fg: this.border, bg: this.bg });
|
|
362
360
|
if (this.value === "" && this.placeholder) {
|
|
363
361
|
screen.text(this.x, this.y, this.prompt, { fg: T.ACCENT, bg: this.bg });
|
|
364
362
|
screen.text(this.x + strWidth(this.prompt), this.y, truncate(this.placeholder, this.w - strWidth(this.prompt) - 2), { fg: T.FAINT, bg: this.bg });
|