@manny-est/node-red-flowpilot 0.4.1 → 0.5.0

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.
@@ -0,0 +1,93 @@
1
+ // ---------------------------------------------------------------------
2
+ // Identifies this conversation for server-side transcript
3
+ // persistence (chats/<conversationId>.jsonl). Kept in sessionStorage so
4
+ // a page reload continues the same transcript; reset by clearChat()
5
+ // ("start a fresh conversation" gets a fresh transcript file too).
6
+ // ---------------------------------------------------------------------
7
+ function makeConversationId() {
8
+ if (window.crypto && typeof window.crypto.randomUUID === "function") {
9
+ return window.crypto.randomUUID();
10
+ }
11
+ return "fp-" + Date.now().toString(36) + "-" + Math.random().toString(36).slice(2);
12
+ }
13
+
14
+ function newConversationId() {
15
+ var id = makeConversationId();
16
+ try { sessionStorage.setItem("fp-conversation-id", id); } catch (e) { /* storage unavailable */ }
17
+ return id;
18
+ }
19
+
20
+ var conversationId = (function () {
21
+ try {
22
+ var existing = sessionStorage.getItem("fp-conversation-id");
23
+ if (existing) { return existing; }
24
+ } catch (e) { /* storage unavailable */ }
25
+ return newConversationId();
26
+ })();
27
+
28
+ // ---------------------------------------------------------------------
29
+ // Client-held conversation history. The backend is stateless —
30
+ // each request that should have continuity carries a capped slice of
31
+ // this array. Cleared by clearChat() ("start a fresh conversation").
32
+ // Entries are { role: "user" | "assistant", content: <string> }.
33
+ // ---------------------------------------------------------------------
34
+ var conversationHistory = [];
35
+
36
+ function pushHistory(role, content) {
37
+ if (!content) { return; }
38
+ conversationHistory.push({ role: role, content: String(content) });
39
+ }
40
+
41
+ // Every turn pushes its "user" entry before the request goes out (see
42
+ // send()'s comment), but a stopped/errored/empty turn never gets a
43
+ // matching assistant reply. Left alone, that dangling "user" entry sits
44
+ // at the end of conversationHistory and the NEXT turn's own "user" push
45
+ // lands right after it — two consecutive "user" entries with no
46
+ // assistant turn between them, corrupting the role-alternation shape of
47
+ // every request built from history from then on. Called from every
48
+ // failure exit (chat and generate/document/modify/build alike) to undo
49
+ // exactly that push. Safe even if called when nothing needs undoing: it
50
+ // only pops when the most recent entry is a "user" turn.
51
+ function popDanglingUserHistory() {
52
+ var last = conversationHistory[conversationHistory.length - 1];
53
+ if (last && last.role === "user") {
54
+ conversationHistory.pop();
55
+ }
56
+ }
57
+
58
+ function getHistoryMaxExchanges() {
59
+ var n = Number(currentSettings.historyMaxExchanges);
60
+ return (isFinite(n) && n >= 0) ? n : 10;
61
+ }
62
+
63
+ // Returns the history to send with a request, plus whether anything has
64
+ // ever been dropped. ONE place both /chat and the generate/modify/document
65
+ // send paths call, so the cap and truncation behaviour can't drift
66
+ // between them.
67
+ //
68
+ // B3: stepped (paged) truncation instead of a continuously-sliding
69
+ // window. A plain slice(-maxMessages) would drop the oldest exchange and
70
+ // append the newest on EVERY turn once the cap is reached — changing the
71
+ // history prefix sent to the provider every request and invalidating its
72
+ // prompt/KV cache for the (large, expensive) system prompt every time.
73
+ // Instead, conversationHistory grows untrimmed — and the sent history is
74
+ // a pure append, i.e. byte-stable except for new messages at the tail —
75
+ // up to 2x the cap, then drops the oldest half in one shot. Sent history
76
+ // size ranges between maxMessages and 2*maxMessages exchanges-worth of
77
+ // messages; "truncated" (and HISTORY_TRUNCATION_NOTICE) flips at each of
78
+ // those two points, not every turn.
79
+ function buildHistoryPayload() {
80
+ var maxMessages = getHistoryMaxExchanges() * 2;
81
+ // maxMessages === 0 means memory is off by design — that's not
82
+ // "truncation" and shouldn't trigger the omitted-messages notice (#10).
83
+ if (maxMessages === 0) {
84
+ return { messages: [], truncated: false };
85
+ }
86
+
87
+ if (conversationHistory.length > maxMessages * 2) {
88
+ conversationHistory = conversationHistory.slice(-maxMessages);
89
+ }
90
+
91
+ var truncated = conversationHistory.length > maxMessages;
92
+ return { messages: conversationHistory.slice(), truncated: truncated };
93
+ }