pi-web-ui 0.2.1 → 0.2.2
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.
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { existsSync } from "node:fs";
|
|
13
13
|
import { join } from "node:path";
|
|
14
14
|
import { createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, getAgentDir, SessionManager, } from "@earendil-works/pi-coding-agent";
|
|
15
|
-
import { serializeMessage, serializeStreamingMessage } from "./serialize.js";
|
|
15
|
+
import { serializeMessage, serializeStreamingMessage, } from "./serialize.js";
|
|
16
16
|
import { loadCommands, saveCommandsFile, TerminalManager, } from "./terminals.js";
|
|
17
17
|
const SNAPSHOT_INTERVAL_MS = 60;
|
|
18
18
|
const WIDGET_REFRESH_MS = 2000;
|
|
@@ -286,7 +286,20 @@ export class ClientSession {
|
|
|
286
286
|
snapshotTimer = null;
|
|
287
287
|
sessionsTimer = null;
|
|
288
288
|
version = 0;
|
|
289
|
-
|
|
289
|
+
/**
|
|
290
|
+
* Stable per-message ids: assigned once per (role, timestamp) so snapshot ids
|
|
291
|
+
* don't change every 60ms — changing ids would remount the whole list in
|
|
292
|
+
* React (collapse open thinking blocks, reset scroll, jank on long chats).
|
|
293
|
+
*/
|
|
294
|
+
msgIds = new Map();
|
|
295
|
+
nextMsgId = 1;
|
|
296
|
+
/** Serialized UiMessage cache — object-reference-stable across snapshots, so
|
|
297
|
+
* the frontend's React.memo can skip unchanged messages entirely. */
|
|
298
|
+
uiMessageCache = new Map();
|
|
299
|
+
/** Reused when the message set didn't change, keeping state.messages
|
|
300
|
+
* reference-stable so the frontend can memoize derived maps. */
|
|
301
|
+
lastMessagesSig = "";
|
|
302
|
+
lastMessagesArray = [];
|
|
290
303
|
queueSteering = 0;
|
|
291
304
|
queueFollowUp = 0;
|
|
292
305
|
disposed = false;
|
|
@@ -434,6 +447,25 @@ export class ClientSession {
|
|
|
434
447
|
void this.pushSessions();
|
|
435
448
|
}, 800);
|
|
436
449
|
}
|
|
450
|
+
/** Serialize a persisted message with a STABLE id + cached object reference. */
|
|
451
|
+
serializeCached(m) {
|
|
452
|
+
const key = m.role === "toolResult"
|
|
453
|
+
? `t:${m.toolCallId}`
|
|
454
|
+
: `${m.role}:${m.timestamp}`;
|
|
455
|
+
let n = this.msgIds.get(key);
|
|
456
|
+
if (n === undefined) {
|
|
457
|
+
n = this.nextMsgId++;
|
|
458
|
+
this.msgIds.set(key, n);
|
|
459
|
+
}
|
|
460
|
+
const cacheKey = `${key}#${n}`;
|
|
461
|
+
const cached = this.uiMessageCache.get(cacheKey);
|
|
462
|
+
if (cached)
|
|
463
|
+
return cached;
|
|
464
|
+
const msg = serializeMessage(m, n);
|
|
465
|
+
if (msg)
|
|
466
|
+
this.uiMessageCache.set(cacheKey, msg);
|
|
467
|
+
return msg;
|
|
468
|
+
}
|
|
437
469
|
snapshot() {
|
|
438
470
|
const state = this.session.agent.state;
|
|
439
471
|
const model = state.model;
|
|
@@ -461,14 +493,22 @@ export class ClientSession {
|
|
|
461
493
|
catch {
|
|
462
494
|
// stats are best-effort
|
|
463
495
|
}
|
|
496
|
+
const rawMessages = state.messages
|
|
497
|
+
.map((m) => this.serializeCached(m))
|
|
498
|
+
.filter((m) => m !== null);
|
|
499
|
+
// Reuse the previous array when nothing changed: the element objects are
|
|
500
|
+
// cached (reference-stable) anyway, and a stable array reference lets the
|
|
501
|
+
// frontend memoize derived maps instead of rebuilding them every 60ms.
|
|
502
|
+
const sig = rawMessages.map((m) => m.id).join("\u0001");
|
|
503
|
+
const messages = sig === this.lastMessagesSig ? this.lastMessagesArray : rawMessages;
|
|
504
|
+
this.lastMessagesSig = sig;
|
|
505
|
+
this.lastMessagesArray = rawMessages;
|
|
464
506
|
return {
|
|
465
507
|
clientId: this.clientId,
|
|
466
508
|
cwd: this.cwd,
|
|
467
509
|
sessionId: this.session.sessionId,
|
|
468
510
|
sessionFile: this.session.sessionFile,
|
|
469
|
-
messages
|
|
470
|
-
.map((m) => serializeMessage(m, ++this.seq))
|
|
471
|
-
.filter((m) => m !== null),
|
|
511
|
+
messages,
|
|
472
512
|
// The in-progress assistant message lives in state.streamingMessage
|
|
473
513
|
// (the SDK only pushes it into state.messages at message_end). Surfacing
|
|
474
514
|
// it here is what makes thinking + text stream into the browser at
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-web-ui",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Web chat interface for the pi coding agent, powered by the pi SDK (@earendil-works/pi-coding-agent) — one-command run, Docker/systemd/launchd deployable",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|