@luckydraw/cumulus 0.27.40 → 0.28.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.
- package/dist/gateway/adapters/webchat.d.ts +5 -7
- package/dist/gateway/adapters/webchat.d.ts.map +1 -1
- package/dist/gateway/adapters/webchat.js +204 -57
- package/dist/gateway/adapters/webchat.js.map +1 -1
- package/dist/gateway/config.d.ts +48 -0
- package/dist/gateway/config.d.ts.map +1 -1
- package/dist/gateway/config.js +90 -0
- package/dist/gateway/config.js.map +1 -1
- package/dist/gateway/daemon.d.ts.map +1 -1
- package/dist/gateway/daemon.js +268 -27
- package/dist/gateway/daemon.js.map +1 -1
- package/dist/gateway/gateway-agents-mcp.js +570 -31
- package/dist/gateway/gateway-agents-mcp.js.map +1 -1
- package/dist/gateway/hooks.d.ts +101 -0
- package/dist/gateway/hooks.d.ts.map +1 -0
- package/dist/gateway/hooks.js +200 -0
- package/dist/gateway/hooks.js.map +1 -0
- package/dist/gateway/scheduler.d.ts +50 -0
- package/dist/gateway/scheduler.d.ts.map +1 -0
- package/dist/gateway/scheduler.js +263 -0
- package/dist/gateway/scheduler.js.map +1 -0
- package/dist/gateway/server.d.ts +16 -1
- package/dist/gateway/server.d.ts.map +1 -1
- package/dist/gateway/server.js +451 -22
- package/dist/gateway/server.js.map +1 -1
- package/dist/gateway/static/blex.min.js +74 -17
- package/dist/gateway/static/chat.html +1 -4
- package/dist/gateway/static/dashboard.html +151 -0
- package/dist/gateway/static/dashboard.js +229 -0
- package/dist/gateway/static/widget.js +656 -460
- package/dist/index.d.ts +13 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -3
- package/dist/index.js.map +1 -1
- package/dist/lib/agentic-loop.d.ts +124 -0
- package/dist/lib/agentic-loop.d.ts.map +1 -0
- package/dist/lib/agentic-loop.js +338 -0
- package/dist/lib/agentic-loop.js.map +1 -0
- package/dist/lib/config.d.ts +28 -15
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +47 -41
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/gateway.d.ts +34 -3
- package/dist/lib/gateway.d.ts.map +1 -1
- package/dist/lib/gateway.js +584 -37
- package/dist/lib/gateway.js.map +1 -1
- package/dist/lib/huggingface-provider.d.ts +26 -0
- package/dist/lib/huggingface-provider.d.ts.map +1 -0
- package/dist/lib/huggingface-provider.js +338 -0
- package/dist/lib/huggingface-provider.js.map +1 -0
- package/dist/lib/provider.d.ts +71 -0
- package/dist/lib/provider.d.ts.map +1 -0
- package/dist/lib/provider.js +90 -0
- package/dist/lib/provider.js.map +1 -0
- package/dist/lib/tools/ask-user-question.d.ts +14 -0
- package/dist/lib/tools/ask-user-question.d.ts.map +1 -0
- package/dist/lib/tools/ask-user-question.js +74 -0
- package/dist/lib/tools/ask-user-question.js.map +1 -0
- package/dist/lib/tools/bash.d.ts +6 -0
- package/dist/lib/tools/bash.d.ts.map +1 -0
- package/dist/lib/tools/bash.js +85 -0
- package/dist/lib/tools/bash.js.map +1 -0
- package/dist/lib/tools/edit.d.ts +6 -0
- package/dist/lib/tools/edit.d.ts.map +1 -0
- package/dist/lib/tools/edit.js +79 -0
- package/dist/lib/tools/edit.js.map +1 -0
- package/dist/lib/tools/glob.d.ts +6 -0
- package/dist/lib/tools/glob.d.ts.map +1 -0
- package/dist/lib/tools/glob.js +91 -0
- package/dist/lib/tools/glob.js.map +1 -0
- package/dist/lib/tools/grep.d.ts +6 -0
- package/dist/lib/tools/grep.d.ts.map +1 -0
- package/dist/lib/tools/grep.js +110 -0
- package/dist/lib/tools/grep.js.map +1 -0
- package/dist/lib/tools/index.d.ts +29 -0
- package/dist/lib/tools/index.d.ts.map +1 -0
- package/dist/lib/tools/index.js +52 -0
- package/dist/lib/tools/index.js.map +1 -0
- package/dist/lib/tools/read.d.ts +6 -0
- package/dist/lib/tools/read.d.ts.map +1 -0
- package/dist/lib/tools/read.js +80 -0
- package/dist/lib/tools/read.js.map +1 -0
- package/dist/lib/tools/tool-search.d.ts +30 -0
- package/dist/lib/tools/tool-search.d.ts.map +1 -0
- package/dist/lib/tools/tool-search.js +108 -0
- package/dist/lib/tools/tool-search.js.map +1 -0
- package/dist/lib/tools/web-fetch.d.ts +9 -0
- package/dist/lib/tools/web-fetch.d.ts.map +1 -0
- package/dist/lib/tools/web-fetch.js +114 -0
- package/dist/lib/tools/web-fetch.js.map +1 -0
- package/dist/lib/tools/web-search.d.ts +12 -0
- package/dist/lib/tools/web-search.d.ts.map +1 -0
- package/dist/lib/tools/web-search.js +89 -0
- package/dist/lib/tools/web-search.js.map +1 -0
- package/dist/lib/tools/write.d.ts +6 -0
- package/dist/lib/tools/write.d.ts.map +1 -0
- package/dist/lib/tools/write.js +44 -0
- package/dist/lib/tools/write.js.map +1 -0
- package/package.json +3 -2
|
@@ -44,14 +44,7 @@
|
|
|
44
44
|
|
|
45
45
|
// ─── Styles ─────────────────────────────────────────────────────────────────
|
|
46
46
|
var STYLES = [
|
|
47
|
-
|
|
48
|
-
'.cumulus-widget *:not(.blex-block-container *), .cumulus-standalone-root *:not(.blex-block-container *) { box-sizing: border-box; }',
|
|
49
|
-
/* Zero-specificity reset: any class selector (widget or blex) overrides this naturally */
|
|
50
|
-
':where(.cumulus-widget, .cumulus-standalone-root) :where(*:not(.blex-block-container *)) { margin: 0; padding: 0; }',
|
|
51
|
-
/* Blex container: isolate from widget styles, let blex own its internals */
|
|
52
|
-
'.blex-block-container { isolation: isolate; }',
|
|
53
|
-
/* Restore browser defaults for blex internals that the widget reset may have affected */
|
|
54
|
-
'.blex-block-container * { box-sizing: content-box; }',
|
|
47
|
+
'.cumulus-widget * { box-sizing: border-box; margin: 0; padding: 0; }',
|
|
55
48
|
|
|
56
49
|
'.cumulus-widget {',
|
|
57
50
|
' font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;',
|
|
@@ -102,11 +95,15 @@
|
|
|
102
95
|
' flex-shrink: 0;',
|
|
103
96
|
'}',
|
|
104
97
|
'.cumulus-header-title { font-weight: 600; font-size: 15px; color: #e0e0e0; }',
|
|
105
|
-
'.cumulus-
|
|
106
|
-
'.cumulus-status-dot { width: 8px; height: 8px; border-radius: 50%; background: #555; }',
|
|
98
|
+
'.cumulus-status-dot { width: 8px; height: 8px; border-radius: 50%; background: #555; transition: background 0.3s; }',
|
|
107
99
|
'.cumulus-status-dot.connected { background: #22c55e; }',
|
|
108
|
-
'.cumulus-status-dot.connecting { background: #f59e0b; }',
|
|
100
|
+
'.cumulus-status-dot.connecting { background: #f59e0b; animation: cumulus-pulse 1s ease-in-out infinite; }',
|
|
109
101
|
'.cumulus-status-dot.disconnected { background: #ef4444; }',
|
|
102
|
+
'.cumulus-status-dot.stale { background: #f59e0b; }',
|
|
103
|
+
'@keyframes cumulus-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }',
|
|
104
|
+
'.cumulus-header-status { font-size: 11px; display: flex; align-items: center; gap: 6px; color: #aaa; cursor: pointer; user-select: none; }',
|
|
105
|
+
'.cumulus-header-status:hover { color: #e0e0e0; }',
|
|
106
|
+
'.cumulus-header-status:active { opacity: 0.7; }',
|
|
110
107
|
'.cumulus-close-btn { background: none; border: none; color: #888; cursor: pointer; font-size: 20px; padding: 2px 4px; line-height: 1; }',
|
|
111
108
|
'.cumulus-close-btn:hover { color: #e0e0e0; }',
|
|
112
109
|
|
|
@@ -124,8 +121,8 @@
|
|
|
124
121
|
/* ── Message inner container (max-width centering) ── */
|
|
125
122
|
'.cumulus-msg-row {',
|
|
126
123
|
' display: flex; flex-direction: column;',
|
|
127
|
-
' width: 100%;',
|
|
128
|
-
'
|
|
124
|
+
' max-width: 78ch; width: 100%;',
|
|
125
|
+
' margin: 0 auto;',
|
|
129
126
|
'}',
|
|
130
127
|
|
|
131
128
|
/* ── User messages ── */
|
|
@@ -140,7 +137,21 @@
|
|
|
140
137
|
' word-wrap: break-word;',
|
|
141
138
|
' white-space: pre-wrap;',
|
|
142
139
|
'}',
|
|
143
|
-
'.cumulus-msg.user.wide { max-width:
|
|
140
|
+
'.cumulus-msg.user.wide { max-width: 100%; }',
|
|
141
|
+
|
|
142
|
+
/* ── System messages (e.g. "Gateway restarted") ── */
|
|
143
|
+
'.cumulus-msg.system {',
|
|
144
|
+
' align-self: center;',
|
|
145
|
+
' background: transparent;',
|
|
146
|
+
' color: #8a9aaa;',
|
|
147
|
+
' border: 1px dashed #3a4a5a;',
|
|
148
|
+
' border-radius: 0.5em;',
|
|
149
|
+
' padding: 0.35em 0.8em;',
|
|
150
|
+
' max-width: 90%;',
|
|
151
|
+
' font-size: 0.82em;',
|
|
152
|
+
' text-align: center;',
|
|
153
|
+
' font-style: italic;',
|
|
154
|
+
'}',
|
|
144
155
|
|
|
145
156
|
/* ── User message attachments ── */
|
|
146
157
|
'.cumulus-msg-attachments { display: flex; flex-wrap: wrap; gap: 0.4em; margin-top: 0.4em; }',
|
|
@@ -166,7 +177,6 @@
|
|
|
166
177
|
'.cumulus-msg-time {',
|
|
167
178
|
' font-size: 0.75em; color: #555; margin-top: 0.3em; padding: 0 0.3em;',
|
|
168
179
|
'}',
|
|
169
|
-
'.cumulus-msg-row:has(.user) { max-width: 78ch; }',
|
|
170
180
|
'.cumulus-msg-row:has(.user) .cumulus-msg-time { align-self: flex-end; }',
|
|
171
181
|
'.cumulus-msg-row:has(.assistant) .cumulus-msg-time { align-self: flex-start; }',
|
|
172
182
|
|
|
@@ -200,26 +210,42 @@
|
|
|
200
210
|
'.cumulus-msg.assistant p:first-child { margin-top: 0; }',
|
|
201
211
|
'.cumulus-msg.assistant p:last-child { margin-bottom: 0; }',
|
|
202
212
|
|
|
203
|
-
'.cumulus-msg a { color: #4da6ff; text-decoration: none; }',
|
|
204
|
-
'.cumulus-msg a:hover { text-decoration: underline; }',
|
|
205
|
-
'.cumulus-msg strong { font-weight: 600; }',
|
|
206
|
-
'.cumulus-msg em { font-style: italic; }',
|
|
207
|
-
'.cumulus-msg del { text-decoration: line-through; color: #888; }',
|
|
208
|
-
|
|
209
|
-
/* Constrain text inside assistant messages, but let blex blocks go full width */
|
|
210
|
-
'.cumulus-msg.assistant > * { max-width: 78ch; }',
|
|
211
|
-
'.cumulus-msg.assistant > .blex-block-container { max-width: none; width: 100%; }',
|
|
212
|
-
|
|
213
213
|
/* Blex block containers */
|
|
214
214
|
'.blex-block-container {',
|
|
215
215
|
' margin: 0.6em 0;',
|
|
216
216
|
' border-radius: 8px;',
|
|
217
|
-
' overflow:
|
|
217
|
+
' overflow: hidden;',
|
|
218
218
|
' min-height: 2em;',
|
|
219
219
|
'}',
|
|
220
220
|
'.blex-block-container[data-blex-ready="true"] {',
|
|
221
221
|
' min-height: auto;',
|
|
222
222
|
'}',
|
|
223
|
+
/* Blex CSS isolation — revert widget resets inside blex blocks */
|
|
224
|
+
'.blex-block-container * { margin: revert; padding: revert; line-height: revert; list-style: revert; border-spacing: revert; }',
|
|
225
|
+
'.blex-block-container { isolation: isolate; }',
|
|
226
|
+
|
|
227
|
+
/* Chip tray for deferred blex interactions */
|
|
228
|
+
'.cumulus-chip-tray {',
|
|
229
|
+
' display: flex; flex-direction: row; flex-wrap: wrap;',
|
|
230
|
+
' gap: 0.35em; max-width: 78ch; align-self: center; width: 100%;',
|
|
231
|
+
' padding-bottom: 0.15em;',
|
|
232
|
+
'}',
|
|
233
|
+
'.cumulus-interaction-chip {',
|
|
234
|
+
' display: inline-flex; align-items: center; gap: 0.3em;',
|
|
235
|
+
' background: #2a3a4a; color: #d4e8f8; border: 1px solid #3a5060;',
|
|
236
|
+
' border-radius: 1em; padding: 0.15em 0.6em; font-size: 0.82em;',
|
|
237
|
+
' cursor: default; max-width: 32ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;',
|
|
238
|
+
'}',
|
|
239
|
+
'.cumulus-interaction-chip .chip-dismiss {',
|
|
240
|
+
' cursor: pointer; opacity: 0.6; font-size: 0.9em; margin-left: 0.2em;',
|
|
241
|
+
'}',
|
|
242
|
+
'.cumulus-interaction-chip .chip-dismiss:hover { opacity: 1; }',
|
|
243
|
+
|
|
244
|
+
'.cumulus-msg a { color: #4da6ff; text-decoration: none; }',
|
|
245
|
+
'.cumulus-msg a:hover { text-decoration: underline; }',
|
|
246
|
+
'.cumulus-msg strong { font-weight: 600; }',
|
|
247
|
+
'.cumulus-msg em { font-style: italic; }',
|
|
248
|
+
'.cumulus-msg del { text-decoration: line-through; color: #888; }',
|
|
223
249
|
|
|
224
250
|
/* Headings */
|
|
225
251
|
'.cumulus-msg h1,.cumulus-msg h2,.cumulus-msg h3,.cumulus-msg h4,.cumulus-msg h5,.cumulus-msg h6 {',
|
|
@@ -365,6 +391,12 @@
|
|
|
365
391
|
' text-align: center; padding: 20px;',
|
|
366
392
|
'}',
|
|
367
393
|
|
|
394
|
+
/* ── Load older messages indicator ── */
|
|
395
|
+
'.cumulus-load-older {',
|
|
396
|
+
' text-align: center; padding: 10px; color: #666;',
|
|
397
|
+
' font-size: 12px; font-style: italic;',
|
|
398
|
+
'}',
|
|
399
|
+
|
|
368
400
|
/* ── Input area ── */
|
|
369
401
|
'.cumulus-input-area {',
|
|
370
402
|
' display: flex; flex-direction: column;',
|
|
@@ -379,8 +411,8 @@
|
|
|
379
411
|
'.cumulus-attach-strip {',
|
|
380
412
|
' display: flex; flex-direction: row;',
|
|
381
413
|
' gap: 0.5em; overflow-x: auto;',
|
|
382
|
-
' max-width: 78ch; width: 100%;',
|
|
383
|
-
' padding-bottom: 0.1em;
|
|
414
|
+
' max-width: 78ch; align-self: center; width: 100%;',
|
|
415
|
+
' padding-bottom: 0.1em;',
|
|
384
416
|
'}',
|
|
385
417
|
'.cumulus-attach-strip::-webkit-scrollbar { height: 4px; }',
|
|
386
418
|
'.cumulus-attach-strip::-webkit-scrollbar-thumb { background: #444; border-radius: 2px; }',
|
|
@@ -416,37 +448,11 @@
|
|
|
416
448
|
'}',
|
|
417
449
|
'.cumulus-attach-chip-remove:hover { color: #ff6666; }',
|
|
418
450
|
|
|
419
|
-
/* Interaction chip tray (blex deferred interactions) */
|
|
420
|
-
'.cumulus-chip-tray {',
|
|
421
|
-
' display: flex; flex-direction: row; flex-wrap: wrap;',
|
|
422
|
-
' gap: 0.35em; max-width: 78ch; width: 100%;',
|
|
423
|
-
' padding-bottom: 0.15em; padding-left: 1.5em;',
|
|
424
|
-
'}',
|
|
425
|
-
'.cumulus-chip-tray:empty { display: none; }',
|
|
426
|
-
'.cumulus-interaction-chip {',
|
|
427
|
-
' display: inline-flex; align-items: center; gap: 0.3em;',
|
|
428
|
-
' background: #2a3a4a; border: 1px solid #3a5a7a;',
|
|
429
|
-
' border-radius: 1em; padding: 0.2em 0.55em;',
|
|
430
|
-
' font-size: 0.8em; color: #bcd; cursor: default;',
|
|
431
|
-
' max-width: 20em; overflow: hidden; white-space: nowrap;',
|
|
432
|
-
'}',
|
|
433
|
-
'.cumulus-interaction-chip-icon { flex-shrink: 0; }',
|
|
434
|
-
'.cumulus-interaction-chip-label { overflow: hidden; text-overflow: ellipsis; }',
|
|
435
|
-
'.cumulus-interaction-chip-dismiss {',
|
|
436
|
-
' flex-shrink: 0; background: none; border: none;',
|
|
437
|
-
' color: #789; font-size: 0.9em; cursor: pointer;',
|
|
438
|
-
' padding: 0 0.1em; line-height: 1;',
|
|
439
|
-
'}',
|
|
440
|
-
'.cumulus-interaction-chip-dismiss:hover { color: #ff6666; }',
|
|
441
|
-
'@keyframes chipFlash { 0% { background: #3a5a7a; } 100% { background: #2a3a4a; } }',
|
|
442
|
-
'.cumulus-interaction-chip--immediate { animation: chipFlash 0.15s ease-out; }',
|
|
443
|
-
|
|
444
451
|
/* Input row (attach btn + textarea + send btn) */
|
|
445
452
|
'.cumulus-input-row {',
|
|
446
453
|
' display: flex; flex-direction: row;',
|
|
447
454
|
' gap: 0.5em; align-items: flex-end;',
|
|
448
|
-
' max-width: 78ch; width: 100%;',
|
|
449
|
-
' padding-left: 1.5em;',
|
|
455
|
+
' max-width: 78ch; align-self: center; width: 100%;',
|
|
450
456
|
'}',
|
|
451
457
|
'.cumulus-attach-btn {',
|
|
452
458
|
' width: 2.7em; height: 2.7em; flex-shrink: 0;',
|
|
@@ -719,8 +725,9 @@
|
|
|
719
725
|
'.cumulus-thread-panel:first-child { border-left: none; }',
|
|
720
726
|
'.cumulus-thread-panel-header {',
|
|
721
727
|
' display: flex; align-items: center; justify-content: space-between;',
|
|
722
|
-
'
|
|
723
|
-
'
|
|
728
|
+
' flex-wrap: wrap; gap: 4px;',
|
|
729
|
+
' padding: 4px 0.85em;',
|
|
730
|
+
' min-height: 40px;',
|
|
724
731
|
' background: #2d2d2d;',
|
|
725
732
|
' border-bottom: 1px solid #3a3a3a;',
|
|
726
733
|
' flex-shrink: 0;',
|
|
@@ -767,6 +774,16 @@
|
|
|
767
774
|
' border-radius: 3px; font-family: inherit; white-space: nowrap;',
|
|
768
775
|
'}',
|
|
769
776
|
'.cumulus-panel-action-btn:hover { color: #ddd; border-color: #555; }',
|
|
777
|
+
/* ── Model selector dropdown ── */
|
|
778
|
+
'.cumulus-model-select {',
|
|
779
|
+
' background: #2a2a2a; color: #aaa; border: 1px solid #444;',
|
|
780
|
+
' border-radius: 4px; padding: 2px 6px; font-size: 11px;',
|
|
781
|
+
' font-family: inherit; cursor: pointer; max-width: 140px;',
|
|
782
|
+
' outline: none; flex-shrink: 0;',
|
|
783
|
+
'}',
|
|
784
|
+
'.cumulus-model-select:hover { border-color: #666; color: #ddd; }',
|
|
785
|
+
'.cumulus-model-select:focus { border-color: #0066cc; }',
|
|
786
|
+
'.cumulus-model-select option { background: #2a2a2a; color: #ccc; }',
|
|
770
787
|
'.cumulus-panel-action-btn.active { color: #e0e0e0; border-color: #0066cc; background: rgba(0,102,204,0.12); }',
|
|
771
788
|
|
|
772
789
|
/* ── Context menu (right-click on thread) ── */
|
|
@@ -1041,8 +1058,8 @@
|
|
|
1041
1058
|
' .cumulus-thread-panel { border-left: none; border-top: 1px solid #333; }',
|
|
1042
1059
|
' .cumulus-thread-panel:first-child { border-top: none; }',
|
|
1043
1060
|
|
|
1044
|
-
/* Full-width message content
|
|
1045
|
-
' .cumulus-msg
|
|
1061
|
+
/* Full-width message content */
|
|
1062
|
+
' .cumulus-msg-row { max-width: 100%; }',
|
|
1046
1063
|
' .cumulus-input-row { max-width: 100%; }',
|
|
1047
1064
|
' .cumulus-attach-strip { max-width: 100%; }',
|
|
1048
1065
|
|
|
@@ -1051,12 +1068,10 @@
|
|
|
1051
1068
|
' .cumulus-input-area { padding: 0.5em 0.65em 0.6em; }',
|
|
1052
1069
|
|
|
1053
1070
|
/* User bubble can be wider on mobile */
|
|
1054
|
-
' .cumulus-msg-row:has(.user) { max-width: 100%; }',
|
|
1055
1071
|
' .cumulus-msg.user { max-width: 85%; }',
|
|
1056
1072
|
|
|
1057
1073
|
/* Tables scroll horizontally */
|
|
1058
|
-
' .cumulus-msg table
|
|
1059
|
-
' .blex-block-container table { display: table; overflow-x: visible; white-space: normal; }',
|
|
1074
|
+
' .cumulus-msg table { display: block; overflow-x: auto; white-space: nowrap; }',
|
|
1060
1075
|
|
|
1061
1076
|
/* Code blocks: ensure horizontal scroll works */
|
|
1062
1077
|
' .code-block-wrapper pre { font-size: 0.85em; }',
|
|
@@ -1121,12 +1136,7 @@
|
|
|
1121
1136
|
function formatTime(timestamp) {
|
|
1122
1137
|
if (!timestamp) return null;
|
|
1123
1138
|
var d = new Date(timestamp);
|
|
1124
|
-
|
|
1125
|
-
var mm = String(d.getMinutes()).padStart(2, '0');
|
|
1126
|
-
var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
|
|
1127
|
-
var mon = months[d.getMonth()];
|
|
1128
|
-
var dd = String(d.getDate()).padStart(2, '0');
|
|
1129
|
-
return hh + ':' + mm + ' ' + mon + '/' + dd;
|
|
1139
|
+
return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
1130
1140
|
}
|
|
1131
1141
|
|
|
1132
1142
|
function appendTimestamp(parent, timestamp) {
|
|
@@ -1159,198 +1169,183 @@
|
|
|
1159
1169
|
var body = document.createElement('div');
|
|
1160
1170
|
var mdResult = renderMarkdown(agent.body);
|
|
1161
1171
|
body.innerHTML = mdResult.html;
|
|
1162
|
-
renderBlexBlocks(body, mdResult.blexBlocks, null);
|
|
1163
1172
|
el.appendChild(body);
|
|
1173
|
+
renderBlexBlocks(body, mdResult.blexBlocks, false);
|
|
1164
1174
|
return el;
|
|
1165
1175
|
}
|
|
1166
1176
|
|
|
1167
|
-
// ──
|
|
1177
|
+
// ── Blex Integration ─────────────────────────────────────────────────────
|
|
1168
1178
|
|
|
1169
|
-
//
|
|
1170
|
-
|
|
1171
|
-
var blexHandles = new WeakMap();
|
|
1179
|
+
// WeakMap: DOM element -> BlockHandle[] for lifecycle management
|
|
1180
|
+
var blexHandles = typeof WeakMap !== 'undefined' ? new WeakMap() : null;
|
|
1172
1181
|
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
* Returns { text: processedText, blocks: [{type, json, id}] }
|
|
1176
|
-
* Fences: ~~~blex:TYPE\n{json}\n~~~ or ```blex:TYPE\n{json}\n```
|
|
1177
|
-
*/
|
|
1182
|
+
// Extract ~~~blex:TYPE\n{json}\n~~~ fences from text
|
|
1183
|
+
// Returns { text: string (with placeholders), blocks: Array<{type, json, idx}> }
|
|
1178
1184
|
function extractBlexBlocks(text) {
|
|
1179
1185
|
var blocks = [];
|
|
1180
1186
|
var BLEX_PREFIX = '\x00BLEX_';
|
|
1181
|
-
var
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
return BLEX_PREFIX + idx + '\x00';
|
|
1188
|
-
}
|
|
1189
|
-
);
|
|
1190
|
-
return { text: processed, blocks: blocks };
|
|
1187
|
+
var replaced = text.replace(/~~~blex:(\w[\w-]*)\n([\s\S]*?)\n~~~/g, function (_, type, json) {
|
|
1188
|
+
var idx = blocks.length;
|
|
1189
|
+
blocks.push({ type: type, json: json.trim(), idx: idx });
|
|
1190
|
+
return BLEX_PREFIX + idx + '\x00';
|
|
1191
|
+
});
|
|
1192
|
+
return { text: replaced, blocks: blocks };
|
|
1191
1193
|
}
|
|
1192
1194
|
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
var placeholders =
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
var
|
|
1204
|
-
if (
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
var data;
|
|
1195
|
+
// Render blex blocks into placeholder divs within a message element
|
|
1196
|
+
function renderBlexBlocks(el, blexBlocks, isStreaming) {
|
|
1197
|
+
if (!blexBlocks || blexBlocks.length === 0) return;
|
|
1198
|
+
if (typeof Blex === 'undefined') {
|
|
1199
|
+
console.warn('[blex] Blex library not loaded — blex.min.js may have failed to fetch');
|
|
1200
|
+
return;
|
|
1201
|
+
}
|
|
1202
|
+
var placeholders = el.querySelectorAll('.blex-block-container');
|
|
1203
|
+
|
|
1204
|
+
placeholders.forEach(function (container) {
|
|
1205
|
+
var idx = parseInt(container.getAttribute('data-blex-idx'), 10);
|
|
1206
|
+
if (isNaN(idx) || idx >= blexBlocks.length) return;
|
|
1207
|
+
var block = blexBlocks[idx];
|
|
1208
|
+
|
|
1208
1209
|
try {
|
|
1209
|
-
data = JSON.parse(block.json);
|
|
1210
|
-
|
|
1211
|
-
// Claude sometimes outputs real newlines inside JSON string values
|
|
1212
|
-
// instead of \n escape sequences. Fix by escaping newlines within strings.
|
|
1213
|
-
try {
|
|
1214
|
-
var fixed = block.json.replace(
|
|
1215
|
-
/"(?:[^"\\]|\\.)*"/g,
|
|
1216
|
-
function (match) {
|
|
1217
|
-
return match
|
|
1218
|
-
.replace(/\n/g, '\\n')
|
|
1219
|
-
.replace(/\r/g, '\\r')
|
|
1220
|
-
.replace(/\t/g, '\\t');
|
|
1221
|
-
}
|
|
1222
|
-
);
|
|
1223
|
-
data = JSON.parse(fixed);
|
|
1224
|
-
} catch (e) {
|
|
1225
|
-
// Show parse error in a fallback
|
|
1226
|
-
ph.innerHTML =
|
|
1227
|
-
'<pre style="color:#f44;font-size:0.85em;">blex:' +
|
|
1228
|
-
escapeHtml(block.type) +
|
|
1229
|
-
' — invalid JSON: ' +
|
|
1230
|
-
escapeHtml(e.message) +
|
|
1231
|
-
'</pre>';
|
|
1232
|
-
return;
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
1210
|
+
var data = JSON.parse(block.json);
|
|
1211
|
+
var blockObj = { type: block.type, id: block.type + '-' + idx, data: data };
|
|
1235
1212
|
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
inputEl.value =
|
|
1249
|
-
(inputEl.value ? inputEl.value + '\n' : '') + interaction.serialized;
|
|
1250
|
-
inputEl.focus();
|
|
1251
|
-
inputEl.dispatchEvent(new Event('input', { bubbles: true }));
|
|
1252
|
-
return;
|
|
1213
|
+
if (isStreaming) {
|
|
1214
|
+
// During streaming, show placeholder skeleton
|
|
1215
|
+
Blex.renderPlaceholder(block.type, container);
|
|
1216
|
+
} else {
|
|
1217
|
+
// Final render — full interactive block
|
|
1218
|
+
Blex.renderBlock(blockObj, container)
|
|
1219
|
+
.then(function (handle) {
|
|
1220
|
+
// Store handle immediately (inside async callback, not after sync loop)
|
|
1221
|
+
if (blexHandles) {
|
|
1222
|
+
var existing = blexHandles.get(el) || [];
|
|
1223
|
+
existing.push(handle);
|
|
1224
|
+
blexHandles.set(el, existing);
|
|
1253
1225
|
}
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
var domIdx = 0;
|
|
1265
|
-
for (var cj = 0; cj < ci; cj++) domIdx++;
|
|
1266
|
-
existingChipEl = chips[domIdx] || null;
|
|
1267
|
-
break;
|
|
1226
|
+
if (handle && handle.onInteraction) {
|
|
1227
|
+
// Wire interaction handler
|
|
1228
|
+
handle.onInteraction(function (interaction) {
|
|
1229
|
+
// Find the panel — try el.closest first, fall back to document query
|
|
1230
|
+
// (el may be detached from DOM if renderPanelMessages rebuilt the message list)
|
|
1231
|
+
var panel = el.closest('.cumulus-thread-panel') || el.closest('.cumulus-panel');
|
|
1232
|
+
if (!panel) {
|
|
1233
|
+
panel =
|
|
1234
|
+
document.querySelector('.cumulus-thread-panel') ||
|
|
1235
|
+
document.querySelector('.cumulus-panel');
|
|
1268
1236
|
}
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
}
|
|
1288
|
-
var labelSpan = document.createElement('span');
|
|
1289
|
-
labelSpan.className = 'cumulus-interaction-chip-label';
|
|
1290
|
-
labelSpan.textContent = interaction.summary || interaction.serialized;
|
|
1291
|
-
labelSpan.setAttribute('title', interaction.serialized);
|
|
1292
|
-
chip.appendChild(labelSpan);
|
|
1293
|
-
if (interaction.revertable) {
|
|
1294
|
-
var dismiss = document.createElement('button');
|
|
1295
|
-
dismiss.className = 'cumulus-interaction-chip-dismiss';
|
|
1296
|
-
dismiss.textContent = '\u00d7';
|
|
1297
|
-
dismiss.setAttribute('title', 'Remove');
|
|
1298
|
-
dismiss.addEventListener('click', function () {
|
|
1299
|
-
if (handle.revertInteraction) {
|
|
1300
|
-
handle.revertInteraction(interaction);
|
|
1237
|
+
var inputEl = panel
|
|
1238
|
+
? panel.querySelector('[data-testid="webchat-input"]')
|
|
1239
|
+
: document.querySelector('[data-testid="webchat-input"]');
|
|
1240
|
+
|
|
1241
|
+
if (inputEl && interaction.serialized) {
|
|
1242
|
+
if (interaction.immediate) {
|
|
1243
|
+
// Immediate: inject into textarea and auto-send
|
|
1244
|
+
inputEl.value = interaction.serialized;
|
|
1245
|
+
inputEl.dispatchEvent(new Event('input', { bubbles: true }));
|
|
1246
|
+
// Find and click the send button
|
|
1247
|
+
var sendBtn = panel ? panel.querySelector('[data-testid*="send"]') : null;
|
|
1248
|
+
if (sendBtn) {
|
|
1249
|
+
sendBtn.click();
|
|
1250
|
+
}
|
|
1251
|
+
} else {
|
|
1252
|
+
// Deferred: add to chip tray
|
|
1253
|
+
addInteractionChip(panel, interaction, handle);
|
|
1254
|
+
}
|
|
1301
1255
|
}
|
|
1302
|
-
var idx = chipCtx.pending.indexOf(interaction);
|
|
1303
|
-
if (idx !== -1) chipCtx.pending.splice(idx, 1);
|
|
1304
|
-
chip.parentNode && chip.parentNode.removeChild(chip);
|
|
1305
|
-
inputEl.dispatchEvent(new Event('input', { bubbles: true }));
|
|
1306
1256
|
});
|
|
1307
|
-
chip.appendChild(dismiss);
|
|
1308
|
-
}
|
|
1309
|
-
chipCtx.tray.appendChild(chip);
|
|
1310
|
-
chipCtx.pending.push(interaction);
|
|
1311
|
-
inputEl.dispatchEvent(new Event('input', { bubbles: true }));
|
|
1312
|
-
// Immediate interactions: auto-send after a brief flash
|
|
1313
|
-
if (interaction.immediate) {
|
|
1314
|
-
chip.style.transition = 'opacity 0.15s';
|
|
1315
|
-
setTimeout(function () {
|
|
1316
|
-
// Find and click the send button to trigger sendPanelMessage
|
|
1317
|
-
var panel = chipCtx.tray.closest('.cumulus-thread-panel, .cumulus-panel, .cumulus-widget');
|
|
1318
|
-
var sendButton = panel && panel.querySelector('[data-testid="webchat-send"]');
|
|
1319
|
-
if (sendButton) sendButton.click();
|
|
1320
|
-
}, 150);
|
|
1321
1257
|
}
|
|
1258
|
+
})
|
|
1259
|
+
.catch(function (err) {
|
|
1260
|
+
container.innerHTML =
|
|
1261
|
+
'<pre style="color:#f66;font-size:0.85em;padding:0.5em;">Blex error: ' +
|
|
1262
|
+
(err.message || err) +
|
|
1263
|
+
'</pre>';
|
|
1322
1264
|
});
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
.
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
escapeHtml(err.message) +
|
|
1331
|
-
'</pre>';
|
|
1332
|
-
});
|
|
1265
|
+
}
|
|
1266
|
+
} catch (e) {
|
|
1267
|
+
container.innerHTML =
|
|
1268
|
+
'<pre style="color:#f66;font-size:0.85em;padding:0.5em;">Invalid blex JSON: ' +
|
|
1269
|
+
e.message +
|
|
1270
|
+
'</pre>';
|
|
1271
|
+
}
|
|
1333
1272
|
});
|
|
1334
|
-
|
|
1273
|
+
|
|
1274
|
+
// Note: handles are stored inside the async .then() callback above,
|
|
1275
|
+
// not here — by this point the promises haven't resolved yet.
|
|
1335
1276
|
}
|
|
1336
1277
|
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
var handles = blexHandles.get(container);
|
|
1278
|
+
// Destroy blex block handles for a message element
|
|
1279
|
+
function destroyBlexBlocks(el) {
|
|
1280
|
+
if (!blexHandles) return;
|
|
1281
|
+
var handles = blexHandles.get(el);
|
|
1342
1282
|
if (handles) {
|
|
1343
1283
|
handles.forEach(function (h) {
|
|
1344
|
-
|
|
1284
|
+
try {
|
|
1285
|
+
h.destroy();
|
|
1286
|
+
} catch (e) {}
|
|
1287
|
+
});
|
|
1288
|
+
blexHandles.delete(el);
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
// Add a deferred interaction chip to the chip tray
|
|
1293
|
+
function addInteractionChip(panel, interaction, handle) {
|
|
1294
|
+
if (!panel) return;
|
|
1295
|
+
var tray = panel.querySelector('.cumulus-chip-tray');
|
|
1296
|
+
if (!tray) return;
|
|
1297
|
+
|
|
1298
|
+
var chip = document.createElement('span');
|
|
1299
|
+
chip.className = 'cumulus-interaction-chip';
|
|
1300
|
+
chip.setAttribute('data-serialized', interaction.serialized || '');
|
|
1301
|
+
|
|
1302
|
+
var label = document.createElement('span');
|
|
1303
|
+
label.textContent =
|
|
1304
|
+
(interaction.icon || '\u2022') +
|
|
1305
|
+
' ' +
|
|
1306
|
+
(interaction.summary || interaction.serialized || '').substring(0, 40);
|
|
1307
|
+
chip.appendChild(label);
|
|
1308
|
+
|
|
1309
|
+
if (interaction.revertable) {
|
|
1310
|
+
var dismiss = document.createElement('span');
|
|
1311
|
+
dismiss.className = 'chip-dismiss';
|
|
1312
|
+
dismiss.textContent = '\u00d7';
|
|
1313
|
+
dismiss.addEventListener('click', function () {
|
|
1314
|
+
if (handle && handle.revertInteraction) {
|
|
1315
|
+
try {
|
|
1316
|
+
handle.revertInteraction(interaction);
|
|
1317
|
+
} catch (e) {}
|
|
1318
|
+
}
|
|
1319
|
+
chip.remove();
|
|
1345
1320
|
});
|
|
1346
|
-
|
|
1321
|
+
chip.appendChild(dismiss);
|
|
1347
1322
|
}
|
|
1323
|
+
|
|
1324
|
+
tray.appendChild(chip);
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
// Collect and clear chips from a panel's chip tray
|
|
1328
|
+
function collectAndClearChips(panel) {
|
|
1329
|
+
if (!panel) return '';
|
|
1330
|
+
var tray = panel.querySelector('.cumulus-chip-tray');
|
|
1331
|
+
if (!tray) return '';
|
|
1332
|
+
var chips = tray.querySelectorAll('.cumulus-interaction-chip');
|
|
1333
|
+
var texts = [];
|
|
1334
|
+
chips.forEach(function (chip) {
|
|
1335
|
+
var s = chip.getAttribute('data-serialized');
|
|
1336
|
+
if (s) texts.push(s);
|
|
1337
|
+
});
|
|
1338
|
+
tray.innerHTML = '';
|
|
1339
|
+
return texts.join('\n');
|
|
1348
1340
|
}
|
|
1349
1341
|
|
|
1342
|
+
// ── Markdown ──────────────────────────────────────────────────────────────
|
|
1343
|
+
|
|
1350
1344
|
function renderMarkdown(text) {
|
|
1351
|
-
// Phase 0.5: Extract blex
|
|
1345
|
+
// Phase 0.5: Extract blex fences BEFORE code blocks
|
|
1352
1346
|
var blexResult = extractBlexBlocks(text);
|
|
1353
1347
|
text = blexResult.text;
|
|
1348
|
+
var blexBlocks = blexResult.blocks;
|
|
1354
1349
|
|
|
1355
1350
|
// Phase 1: Extract fenced code blocks — replace with unique tokens
|
|
1356
1351
|
var codeBlocks = [];
|
|
@@ -1365,8 +1360,10 @@
|
|
|
1365
1360
|
});
|
|
1366
1361
|
|
|
1367
1362
|
// Phase 2: Escape HTML in the non-code portions
|
|
1368
|
-
// We need to escape line by line between tokens
|
|
1369
|
-
|
|
1363
|
+
// We need to escape line by line between tokens
|
|
1364
|
+
// Split by BOTH code block and blex tokens — escapeHtml uses DOM textContent
|
|
1365
|
+
// which strips null bytes (\x00), destroying our placeholder tokens
|
|
1366
|
+
var parts = processed.split(/(\x00(?:CODEBLOCK|BLEX)_\d+\x00)/);
|
|
1370
1367
|
var escapedParts = parts.map(function (part) {
|
|
1371
1368
|
if (/^\x00(?:CODEBLOCK|BLEX)_\d+\x00$/.test(part)) return part; // keep token as-is
|
|
1372
1369
|
return escapeHtml(part);
|
|
@@ -1425,19 +1422,11 @@
|
|
|
1425
1422
|
});
|
|
1426
1423
|
|
|
1427
1424
|
// Phase 7.5: Reinsert blex block placeholders
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
'<div class="blex-block-container" data-blex-idx="' +
|
|
1432
|
-
idx +
|
|
1433
|
-
'" data-testid="blex-block-' +
|
|
1434
|
-
idx +
|
|
1435
|
-
'"></div>'
|
|
1436
|
-
);
|
|
1437
|
-
});
|
|
1438
|
-
}
|
|
1425
|
+
html = html.replace(/\x00BLEX_(\d+)\x00/g, function (_, idx) {
|
|
1426
|
+
return '<div class="blex-block-container" data-blex-idx="' + idx + '"></div>';
|
|
1427
|
+
});
|
|
1439
1428
|
|
|
1440
|
-
return { html: html, blexBlocks:
|
|
1429
|
+
return { html: html, blexBlocks: blexBlocks };
|
|
1441
1430
|
}
|
|
1442
1431
|
|
|
1443
1432
|
function buildCodeBlock(lang, escapedCode, tokenId) {
|
|
@@ -1679,10 +1668,14 @@
|
|
|
1679
1668
|
var reconnectTimer = null;
|
|
1680
1669
|
var reconnectDelay = 1000;
|
|
1681
1670
|
var destroyed = false;
|
|
1671
|
+
var lastPongTime = 0;
|
|
1672
|
+
var staleCheckTimer = null;
|
|
1673
|
+
var currentStatus = 'disconnected';
|
|
1682
1674
|
|
|
1683
1675
|
function connect() {
|
|
1684
1676
|
if (destroyed) return;
|
|
1685
1677
|
onStatus('connecting');
|
|
1678
|
+
currentStatus = 'connecting';
|
|
1686
1679
|
ws = new WebSocket(wsUrl);
|
|
1687
1680
|
|
|
1688
1681
|
ws.onopen = function () {
|
|
@@ -1690,15 +1683,24 @@
|
|
|
1690
1683
|
ws.close();
|
|
1691
1684
|
return;
|
|
1692
1685
|
}
|
|
1686
|
+
currentStatus = 'connected';
|
|
1693
1687
|
onStatus('connected');
|
|
1694
1688
|
reconnectDelay = 1000;
|
|
1689
|
+
lastPongTime = Date.now();
|
|
1695
1690
|
ws.send(JSON.stringify({ type: 'auth', apiKey: apiKey }));
|
|
1696
1691
|
if (!skipHistory) {
|
|
1697
1692
|
ws.send(JSON.stringify({ type: 'history', threadName: sessionId, limit: 200 }));
|
|
1698
1693
|
}
|
|
1694
|
+
startStaleCheck();
|
|
1699
1695
|
};
|
|
1700
1696
|
|
|
1701
1697
|
ws.onmessage = function (event) {
|
|
1698
|
+
// Any message from server proves the connection is alive
|
|
1699
|
+
lastPongTime = Date.now();
|
|
1700
|
+
if (currentStatus === 'stale') {
|
|
1701
|
+
currentStatus = 'connected';
|
|
1702
|
+
onStatus('connected');
|
|
1703
|
+
}
|
|
1702
1704
|
try {
|
|
1703
1705
|
var data = JSON.parse(event.data);
|
|
1704
1706
|
onMessage(data);
|
|
@@ -1709,7 +1711,9 @@
|
|
|
1709
1711
|
|
|
1710
1712
|
ws.onclose = function () {
|
|
1711
1713
|
if (destroyed) return;
|
|
1714
|
+
currentStatus = 'disconnected';
|
|
1712
1715
|
onStatus('disconnected');
|
|
1716
|
+
stopStaleCheck();
|
|
1713
1717
|
scheduleReconnect();
|
|
1714
1718
|
};
|
|
1715
1719
|
|
|
@@ -1718,6 +1722,26 @@
|
|
|
1718
1722
|
};
|
|
1719
1723
|
}
|
|
1720
1724
|
|
|
1725
|
+
function startStaleCheck() {
|
|
1726
|
+
stopStaleCheck();
|
|
1727
|
+
staleCheckTimer = setInterval(function () {
|
|
1728
|
+
if (ws && ws.readyState === WebSocket.OPEN && Date.now() - lastPongTime > 60000) {
|
|
1729
|
+
// No message from server in 60s — connection may be half-open
|
|
1730
|
+
if (currentStatus !== 'stale') {
|
|
1731
|
+
currentStatus = 'stale';
|
|
1732
|
+
onStatus('stale');
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
}, 15000);
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
function stopStaleCheck() {
|
|
1739
|
+
if (staleCheckTimer) {
|
|
1740
|
+
clearInterval(staleCheckTimer);
|
|
1741
|
+
staleCheckTimer = null;
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1721
1745
|
function scheduleReconnect() {
|
|
1722
1746
|
if (reconnectTimer || destroyed) return;
|
|
1723
1747
|
reconnectTimer = setTimeout(function () {
|
|
@@ -1733,8 +1757,26 @@
|
|
|
1733
1757
|
}
|
|
1734
1758
|
}
|
|
1735
1759
|
|
|
1760
|
+
function reconnect() {
|
|
1761
|
+
// Force reconnect — close current connection and reconnect immediately
|
|
1762
|
+
console.log('[CumulusChat] Manual reconnect triggered');
|
|
1763
|
+
stopStaleCheck();
|
|
1764
|
+
if (reconnectTimer) {
|
|
1765
|
+
clearTimeout(reconnectTimer);
|
|
1766
|
+
reconnectTimer = null;
|
|
1767
|
+
}
|
|
1768
|
+
if (ws) {
|
|
1769
|
+
ws.onclose = null;
|
|
1770
|
+
ws.close();
|
|
1771
|
+
ws = null;
|
|
1772
|
+
}
|
|
1773
|
+
reconnectDelay = 1000;
|
|
1774
|
+
connect();
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1736
1777
|
function close() {
|
|
1737
1778
|
destroyed = true;
|
|
1779
|
+
stopStaleCheck();
|
|
1738
1780
|
if (reconnectTimer) {
|
|
1739
1781
|
clearTimeout(reconnectTimer);
|
|
1740
1782
|
reconnectTimer = null;
|
|
@@ -1746,8 +1788,12 @@
|
|
|
1746
1788
|
}
|
|
1747
1789
|
}
|
|
1748
1790
|
|
|
1791
|
+
function getStatus() {
|
|
1792
|
+
return currentStatus;
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1749
1795
|
connect();
|
|
1750
|
-
return { send: send, close: close };
|
|
1796
|
+
return { send: send, close: close, reconnect: reconnect, getStatus: getStatus };
|
|
1751
1797
|
}
|
|
1752
1798
|
|
|
1753
1799
|
// ─── Widget ──────────────────────────────────────────────────────────────────
|
|
@@ -1836,12 +1882,6 @@
|
|
|
1836
1882
|
attachStrip.style.display = 'none';
|
|
1837
1883
|
inputArea.appendChild(attachStrip);
|
|
1838
1884
|
|
|
1839
|
-
var chipTray = document.createElement('div');
|
|
1840
|
-
chipTray.className = 'cumulus-chip-tray';
|
|
1841
|
-
chipTray.setAttribute('data-testid', 'webchat-chip-tray');
|
|
1842
|
-
inputArea.appendChild(chipTray);
|
|
1843
|
-
var pendingChips = [];
|
|
1844
|
-
|
|
1845
1885
|
var inputRow = document.createElement('div');
|
|
1846
1886
|
inputRow.className = 'cumulus-input-row';
|
|
1847
1887
|
|
|
@@ -1917,6 +1957,18 @@
|
|
|
1917
1957
|
return lineCount > 3;
|
|
1918
1958
|
}
|
|
1919
1959
|
|
|
1960
|
+
function isSystemMessage(content) {
|
|
1961
|
+
return typeof content === 'string' && content.startsWith('[System]');
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
function buildSystemMsgEl(content) {
|
|
1965
|
+
var el = document.createElement('div');
|
|
1966
|
+
el.className = 'cumulus-msg system';
|
|
1967
|
+
el.setAttribute('data-testid', 'webchat-system-msg');
|
|
1968
|
+
el.textContent = content.replace(/^\[System\]\s*/, '');
|
|
1969
|
+
return el;
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1920
1972
|
function buildUserMsgEl(msg) {
|
|
1921
1973
|
var el = document.createElement('div');
|
|
1922
1974
|
el.className = 'cumulus-msg user' + (isWideUserMessage(msg.content) ? ' wide' : '');
|
|
@@ -1955,34 +2007,9 @@
|
|
|
1955
2007
|
if (isStreaming) {
|
|
1956
2008
|
el.innerHTML += '<span class="cumulus-cursor"></span>';
|
|
1957
2009
|
}
|
|
1958
|
-
// Render blex blocks
|
|
1959
|
-
if (
|
|
1960
|
-
|
|
1961
|
-
// During streaming: show placeholders
|
|
1962
|
-
el.querySelectorAll('[data-blex-idx]').forEach(function (ph) {
|
|
1963
|
-
var idx = parseInt(ph.getAttribute('data-blex-idx'), 10);
|
|
1964
|
-
var block = mdResult.blexBlocks[idx];
|
|
1965
|
-
if (block) window.Blex.renderPlaceholder(block.type, ph);
|
|
1966
|
-
});
|
|
1967
|
-
} else if (!isStreaming) {
|
|
1968
|
-
// Final render: full blex blocks with interaction wiring
|
|
1969
|
-
// Defer rendering until the element is in the DOM (el.closest needs a parent)
|
|
1970
|
-
var _blexBlocks = mdResult.blexBlocks;
|
|
1971
|
-
requestAnimationFrame(function () {
|
|
1972
|
-
var panelEl = el.closest('.cumulus-panel, .cumulus-widget, .cumulus-thread-panel');
|
|
1973
|
-
var blexInputEl = panelEl
|
|
1974
|
-
? panelEl.querySelector('.cumulus-input, .cumulus-standalone-input')
|
|
1975
|
-
: null;
|
|
1976
|
-
var blexChipTray = panelEl
|
|
1977
|
-
? panelEl.querySelector('.cumulus-chip-tray')
|
|
1978
|
-
: null;
|
|
1979
|
-
var blexChipCtx = blexChipTray
|
|
1980
|
-
? { tray: blexChipTray, pending: blexChipTray._pendingChips || (blexChipTray._pendingChips = []) }
|
|
1981
|
-
: null;
|
|
1982
|
-
console.log('[blex-render] panelEl:', !!panelEl, 'inputEl:', !!blexInputEl, 'chipTray:', !!blexChipTray, 'chipCtx:', !!blexChipCtx, 'blocks:', _blexBlocks.length);
|
|
1983
|
-
renderBlexBlocks(el, _blexBlocks, blexInputEl, blexChipCtx);
|
|
1984
|
-
});
|
|
1985
|
-
}
|
|
2010
|
+
// Render blex blocks — skip during streaming to avoid destroy/recreate churn
|
|
2011
|
+
if (!isStreaming) {
|
|
2012
|
+
renderBlexBlocks(el, mdResult.blexBlocks, false);
|
|
1986
2013
|
}
|
|
1987
2014
|
el.querySelectorAll('.code-block-copy-btn').forEach(function (btn) {
|
|
1988
2015
|
btn.addEventListener('click', function () {
|
|
@@ -2005,15 +2032,30 @@
|
|
|
2005
2032
|
return el;
|
|
2006
2033
|
}
|
|
2007
2034
|
|
|
2008
|
-
|
|
2035
|
+
// Incremental streaming update — only touch the streaming element
|
|
2036
|
+
function updateStreamOnly() {
|
|
2037
|
+
var streamEl = messagesEl.querySelector('[data-testid="webchat-streaming"]');
|
|
2038
|
+
if (streamEl) {
|
|
2039
|
+
var parentRow = streamEl.closest('.cumulus-msg-row') || streamEl.parentElement;
|
|
2040
|
+
if (parentRow) {
|
|
2041
|
+
var newEl = buildAssistantMsgEl(streamBuffer, true);
|
|
2042
|
+
parentRow.replaceChild(newEl, streamEl);
|
|
2043
|
+
}
|
|
2044
|
+
} else if (streaming) {
|
|
2045
|
+
var row = document.createElement('div');
|
|
2046
|
+
row.className = 'cumulus-msg-row';
|
|
2047
|
+
row.appendChild(buildAssistantMsgEl(streamBuffer, true));
|
|
2048
|
+
messagesEl.appendChild(row);
|
|
2049
|
+
}
|
|
2050
|
+
scrollToBottom();
|
|
2051
|
+
}
|
|
2009
2052
|
|
|
2010
2053
|
function renderMessages() {
|
|
2011
|
-
// Destroy
|
|
2054
|
+
// Destroy blex blocks before clearing DOM
|
|
2012
2055
|
messagesEl.querySelectorAll('.cumulus-msg.assistant').forEach(function (m) {
|
|
2013
2056
|
destroyBlexBlocks(m);
|
|
2014
2057
|
});
|
|
2015
2058
|
messagesEl.innerHTML = '';
|
|
2016
|
-
_embeddedStreamingRow = null;
|
|
2017
2059
|
// Apply selection mode class
|
|
2018
2060
|
if (selectionMode) {
|
|
2019
2061
|
messagesEl.classList.add('cumulus-selection-mode');
|
|
@@ -2048,7 +2090,9 @@
|
|
|
2048
2090
|
})(msg.id);
|
|
2049
2091
|
row.appendChild(cb);
|
|
2050
2092
|
}
|
|
2051
|
-
if (msg.role === 'user') {
|
|
2093
|
+
if (msg.role === 'user' && isSystemMessage(msg.content)) {
|
|
2094
|
+
row.appendChild(buildSystemMsgEl(msg.content));
|
|
2095
|
+
} else if (msg.role === 'user') {
|
|
2052
2096
|
var agent = parseAgentMessage(msg.content);
|
|
2053
2097
|
if (agent) {
|
|
2054
2098
|
row.appendChild(buildAgentMsgEl(agent));
|
|
@@ -2066,41 +2110,11 @@
|
|
|
2066
2110
|
row.className = 'cumulus-msg-row';
|
|
2067
2111
|
row.appendChild(buildAssistantMsgEl(streamBuffer, true));
|
|
2068
2112
|
messagesEl.appendChild(row);
|
|
2069
|
-
_embeddedStreamingRow = row;
|
|
2070
2113
|
}
|
|
2071
2114
|
scrollToBottom();
|
|
2072
2115
|
updateEmbeddedSelectionBar();
|
|
2073
2116
|
}
|
|
2074
2117
|
|
|
2075
|
-
// Incremental streaming update — only touches the streaming element
|
|
2076
|
-
function updateEmbeddedStreamingOnly() {
|
|
2077
|
-
if (!streaming) {
|
|
2078
|
-
_embeddedStreamingRow = null;
|
|
2079
|
-
renderMessages();
|
|
2080
|
-
return;
|
|
2081
|
-
}
|
|
2082
|
-
if (!_embeddedStreamingRow) {
|
|
2083
|
-
renderMessages();
|
|
2084
|
-
return;
|
|
2085
|
-
}
|
|
2086
|
-
var el = _embeddedStreamingRow.querySelector('.cumulus-msg.assistant');
|
|
2087
|
-
if (el && streamBuffer) {
|
|
2088
|
-
var mdResult = renderMarkdown(streamBuffer);
|
|
2089
|
-
el.innerHTML = mdResult.html + '<span class="cumulus-cursor"></span>';
|
|
2090
|
-
if (mdResult.blexBlocks.length > 0 && typeof window.Blex !== 'undefined') {
|
|
2091
|
-
el.querySelectorAll('[data-blex-idx]').forEach(function (ph) {
|
|
2092
|
-
var idx = parseInt(ph.getAttribute('data-blex-idx'), 10);
|
|
2093
|
-
var block = mdResult.blexBlocks[idx];
|
|
2094
|
-
if (block) window.Blex.renderPlaceholder(block.type, ph);
|
|
2095
|
-
});
|
|
2096
|
-
}
|
|
2097
|
-
} else if (el && !streamBuffer) {
|
|
2098
|
-
el.innerHTML =
|
|
2099
|
-
'<span class="cumulus-typing-dots"><span></span><span></span><span></span></span>';
|
|
2100
|
-
}
|
|
2101
|
-
scrollToBottom();
|
|
2102
|
-
}
|
|
2103
|
-
|
|
2104
2118
|
function updateEmbeddedSelectionBar() {
|
|
2105
2119
|
var existingBar = panel.querySelector('.cumulus-selection-bar');
|
|
2106
2120
|
if (existingBar) existingBar.remove();
|
|
@@ -2226,7 +2240,25 @@
|
|
|
2226
2240
|
var dot = panel.querySelector('.cumulus-status-dot');
|
|
2227
2241
|
var text = panel.querySelector('[data-testid="webchat-status-text"]');
|
|
2228
2242
|
if (dot) dot.className = 'cumulus-status-dot ' + status;
|
|
2229
|
-
if (text)
|
|
2243
|
+
if (text) {
|
|
2244
|
+
var label =
|
|
2245
|
+
status === 'stale'
|
|
2246
|
+
? 'Stale — tap to reconnect'
|
|
2247
|
+
: status === 'disconnected'
|
|
2248
|
+
? 'Disconnected — tap to reconnect'
|
|
2249
|
+
: status.charAt(0).toUpperCase() + status.slice(1);
|
|
2250
|
+
text.textContent = label;
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
|
|
2254
|
+
// ── Click status to force reconnect ──
|
|
2255
|
+
var statusEl = panel.querySelector('.cumulus-header-status');
|
|
2256
|
+
if (statusEl) {
|
|
2257
|
+
statusEl.addEventListener('click', function () {
|
|
2258
|
+
if (connection && connectionStatus !== 'connecting') {
|
|
2259
|
+
connection.reconnect();
|
|
2260
|
+
}
|
|
2261
|
+
});
|
|
2230
2262
|
}
|
|
2231
2263
|
|
|
2232
2264
|
function renderAttachStrip() {
|
|
@@ -2316,22 +2348,9 @@
|
|
|
2316
2348
|
break;
|
|
2317
2349
|
case 'history':
|
|
2318
2350
|
if (data.messages && data.messages.length > 0) {
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
var ehm = data.messages[ehi];
|
|
2323
|
-
if (ehm.streaming) {
|
|
2324
|
-
embStreamingMsg = ehm;
|
|
2325
|
-
} else {
|
|
2326
|
-
embCompleted.push({ id: ehm.id, role: ehm.role, content: ehm.content, timestamp: ehm.timestamp });
|
|
2327
|
-
}
|
|
2328
|
-
}
|
|
2329
|
-
messages = embCompleted;
|
|
2330
|
-
if (embStreamingMsg && data.streaming) {
|
|
2331
|
-
streaming = true;
|
|
2332
|
-
streamBuffer = embStreamingMsg.content || '';
|
|
2333
|
-
updateSendBtn();
|
|
2334
|
-
}
|
|
2351
|
+
messages = data.messages.map(function (m) {
|
|
2352
|
+
return { id: m.id, role: m.role, content: m.content, timestamp: m.timestamp };
|
|
2353
|
+
});
|
|
2335
2354
|
renderMessages();
|
|
2336
2355
|
}
|
|
2337
2356
|
break;
|
|
@@ -2360,12 +2379,11 @@
|
|
|
2360
2379
|
renderMessages();
|
|
2361
2380
|
// Reload history
|
|
2362
2381
|
if (connection)
|
|
2363
|
-
connection.send({ type: 'history', threadName: sessionId, limit:
|
|
2382
|
+
connection.send({ type: 'history', threadName: sessionId, limit: 50 });
|
|
2364
2383
|
}
|
|
2365
2384
|
embeddedStreamTimer = null;
|
|
2366
2385
|
}, EMBEDDED_STREAM_TIMEOUT);
|
|
2367
|
-
|
|
2368
|
-
updateEmbeddedStreamingOnly();
|
|
2386
|
+
updateStreamOnly();
|
|
2369
2387
|
break;
|
|
2370
2388
|
case 'segment':
|
|
2371
2389
|
break;
|
|
@@ -2448,17 +2466,7 @@
|
|
|
2448
2466
|
}
|
|
2449
2467
|
|
|
2450
2468
|
function sendMessage() {
|
|
2451
|
-
|
|
2452
|
-
var chipTrayEl = panel.querySelector('.cumulus-chip-tray');
|
|
2453
|
-
var chipPending = chipTrayEl && chipTrayEl._pendingChips ? chipTrayEl._pendingChips : [];
|
|
2454
|
-
var chipPrefix = '';
|
|
2455
|
-
if (chipPending.length > 0) {
|
|
2456
|
-
chipPrefix = chipPending.map(function (c) { return c.serialized; }).join('\n');
|
|
2457
|
-
chipPending.length = 0;
|
|
2458
|
-
if (chipTrayEl) chipTrayEl.innerHTML = '';
|
|
2459
|
-
}
|
|
2460
|
-
var rawText = input.value.trim();
|
|
2461
|
-
var text = chipPrefix ? (chipPrefix + (rawText ? '\n' + rawText : '')) : rawText;
|
|
2469
|
+
var text = input.value.trim();
|
|
2462
2470
|
|
|
2463
2471
|
// Interjection: user sends while streaming
|
|
2464
2472
|
if (streaming) {
|
|
@@ -2646,7 +2654,7 @@
|
|
|
2646
2654
|
// Reload history to get the real response if server completed
|
|
2647
2655
|
s.historyLoaded = false;
|
|
2648
2656
|
if (connection) {
|
|
2649
|
-
connection.send({ type: 'history', threadName: threadName, limit:
|
|
2657
|
+
connection.send({ type: 'history', threadName: threadName, limit: 50 });
|
|
2650
2658
|
}
|
|
2651
2659
|
refreshThreadPanel(threadName);
|
|
2652
2660
|
}
|
|
@@ -2686,6 +2694,12 @@
|
|
|
2686
2694
|
historyLoaded: false,
|
|
2687
2695
|
selectionMode: false,
|
|
2688
2696
|
selectedIds: {},
|
|
2697
|
+
draft: '', // persists unsent textarea text across thread switches
|
|
2698
|
+
// Pagination state
|
|
2699
|
+
loadingOlder: false,
|
|
2700
|
+
hasOlderMessages: false,
|
|
2701
|
+
oldestOffset: -1, // -1 = unknown, set on first history response
|
|
2702
|
+
model: 'claude', // default model
|
|
2689
2703
|
};
|
|
2690
2704
|
}
|
|
2691
2705
|
return threadStates[threadName];
|
|
@@ -2821,6 +2835,20 @@
|
|
|
2821
2835
|
});
|
|
2822
2836
|
}
|
|
2823
2837
|
|
|
2838
|
+
// ── Click status indicator to force reconnect ──
|
|
2839
|
+
var appStatusEl = topbar.querySelector('.cumulus-header-status');
|
|
2840
|
+
if (appStatusEl) {
|
|
2841
|
+
appStatusEl.addEventListener('click', function () {
|
|
2842
|
+
if (connection && connectionStatus !== 'connecting') {
|
|
2843
|
+
connection.reconnect();
|
|
2844
|
+
// Force history reload for all loaded threads
|
|
2845
|
+
Object.keys(threadStates).forEach(function (tn) {
|
|
2846
|
+
threadStates[tn].historyLoaded = false;
|
|
2847
|
+
});
|
|
2848
|
+
}
|
|
2849
|
+
});
|
|
2850
|
+
}
|
|
2851
|
+
|
|
2824
2852
|
standaloneRoot.appendChild(authWrapper);
|
|
2825
2853
|
standaloneRoot.appendChild(appLayout);
|
|
2826
2854
|
container.appendChild(standaloneRoot);
|
|
@@ -2828,16 +2856,27 @@
|
|
|
2828
2856
|
// ── Status update (updates both auth-phase and app-phase dots) ──
|
|
2829
2857
|
function updateStatus(status) {
|
|
2830
2858
|
connectionStatus = status;
|
|
2859
|
+
var label =
|
|
2860
|
+
status === 'stale'
|
|
2861
|
+
? 'Stale — tap to reconnect'
|
|
2862
|
+
: status === 'disconnected'
|
|
2863
|
+
? 'Disconnected — tap to reconnect'
|
|
2864
|
+
: status.charAt(0).toUpperCase() + status.slice(1);
|
|
2831
2865
|
// Auth phase dot
|
|
2832
2866
|
var dot = authWrapper.querySelector('[data-testid="webchat-status-dot"]');
|
|
2833
2867
|
var text = authWrapper.querySelector('[data-testid="webchat-status-text"]');
|
|
2834
2868
|
if (dot) dot.className = 'cumulus-status-dot ' + status;
|
|
2835
|
-
if (text) text.textContent =
|
|
2869
|
+
if (text) text.textContent = label;
|
|
2836
2870
|
// App phase dot
|
|
2837
2871
|
var dotApp = appLayout.querySelector('[data-testid="webchat-status-dot-app"]');
|
|
2838
2872
|
var textApp = appLayout.querySelector('[data-testid="webchat-status-text-app"]');
|
|
2839
2873
|
if (dotApp) dotApp.className = 'cumulus-status-dot ' + status;
|
|
2840
|
-
if (textApp) textApp.textContent =
|
|
2874
|
+
if (textApp) textApp.textContent = label;
|
|
2875
|
+
// Update per-thread panel status dots
|
|
2876
|
+
var threadDots = appLayout.querySelectorAll('[data-thread-status-dot]');
|
|
2877
|
+
threadDots.forEach(function (d) {
|
|
2878
|
+
d.className = 'cumulus-status-dot ' + status;
|
|
2879
|
+
});
|
|
2841
2880
|
}
|
|
2842
2881
|
|
|
2843
2882
|
// ── View switching ──
|
|
@@ -3154,6 +3193,7 @@
|
|
|
3154
3193
|
var state = threadStates[name];
|
|
3155
3194
|
if (state) {
|
|
3156
3195
|
state._renderMessages = null;
|
|
3196
|
+
state._updateStreamOnly = null;
|
|
3157
3197
|
state._updateSendBtn = null;
|
|
3158
3198
|
}
|
|
3159
3199
|
renderSidebar();
|
|
@@ -3176,6 +3216,7 @@
|
|
|
3176
3216
|
var _cs = threadStates[visibleThreads[_ci]];
|
|
3177
3217
|
if (_cs) {
|
|
3178
3218
|
_cs._renderMessages = null;
|
|
3219
|
+
_cs._updateStreamOnly = null;
|
|
3179
3220
|
_cs._updateSendBtn = null;
|
|
3180
3221
|
}
|
|
3181
3222
|
}
|
|
@@ -3226,6 +3267,99 @@
|
|
|
3226
3267
|
|
|
3227
3268
|
panelHeader.appendChild(titleLeft);
|
|
3228
3269
|
|
|
3270
|
+
// Model selector dropdown
|
|
3271
|
+
var modelSelect = document.createElement('select');
|
|
3272
|
+
modelSelect.className = 'cumulus-model-select';
|
|
3273
|
+
modelSelect.setAttribute('data-testid', 'webchat-model-select');
|
|
3274
|
+
modelSelect.setAttribute('data-thread-model', threadName);
|
|
3275
|
+
// Default option
|
|
3276
|
+
var defaultOpt = document.createElement('option');
|
|
3277
|
+
defaultOpt.value = 'claude';
|
|
3278
|
+
defaultOpt.textContent = 'Claude (CLI)';
|
|
3279
|
+
modelSelect.appendChild(defaultOpt);
|
|
3280
|
+
// Load models from API + thread's saved model
|
|
3281
|
+
(function loadModels() {
|
|
3282
|
+
var xhr = new XMLHttpRequest();
|
|
3283
|
+
xhr.open('GET', '/api/models');
|
|
3284
|
+
xhr.setRequestHeader('X-API-Key', activeApiKey);
|
|
3285
|
+
xhr.onload = function () {
|
|
3286
|
+
if (xhr.status === 200) {
|
|
3287
|
+
try {
|
|
3288
|
+
var resp = JSON.parse(xhr.responseText);
|
|
3289
|
+
modelSelect.innerHTML = '';
|
|
3290
|
+
(resp.models || []).forEach(function (m) {
|
|
3291
|
+
var opt = document.createElement('option');
|
|
3292
|
+
opt.value = m.id;
|
|
3293
|
+
opt.textContent = m.label || m.id;
|
|
3294
|
+
modelSelect.appendChild(opt);
|
|
3295
|
+
});
|
|
3296
|
+
// Find the default model from the API response (global config default)
|
|
3297
|
+
var defaultModel = resp.models.find(function (m) {
|
|
3298
|
+
return m.default;
|
|
3299
|
+
});
|
|
3300
|
+
var defaultModelId = defaultModel
|
|
3301
|
+
? defaultModel.id
|
|
3302
|
+
: resp.models[0] && resp.models[0].id;
|
|
3303
|
+
|
|
3304
|
+
// Set initial dropdown to the global default (for new threads)
|
|
3305
|
+
if (defaultModelId) {
|
|
3306
|
+
modelSelect.value = defaultModelId;
|
|
3307
|
+
var ts = getThreadState(threadName);
|
|
3308
|
+
ts.model = defaultModelId;
|
|
3309
|
+
}
|
|
3310
|
+
|
|
3311
|
+
// Fetch the thread's saved config to override the default
|
|
3312
|
+
var cfgXhr = new XMLHttpRequest();
|
|
3313
|
+
cfgXhr.open('GET', '/api/thread/' + encodeURIComponent(threadName) + '/config');
|
|
3314
|
+
cfgXhr.setRequestHeader('X-API-Key', activeApiKey);
|
|
3315
|
+
cfgXhr.onload = function () {
|
|
3316
|
+
if (cfgXhr.status === 200) {
|
|
3317
|
+
try {
|
|
3318
|
+
var cfg = JSON.parse(cfgXhr.responseText);
|
|
3319
|
+
// Use thread's saved model, or fall back to global default
|
|
3320
|
+
var selectedModel = cfg.model || defaultModelId;
|
|
3321
|
+
if (selectedModel) {
|
|
3322
|
+
// Check if this model exists in the dropdown options
|
|
3323
|
+
var optionExists = false;
|
|
3324
|
+
for (var i = 0; i < modelSelect.options.length; i++) {
|
|
3325
|
+
if (modelSelect.options[i].value === selectedModel) {
|
|
3326
|
+
optionExists = true;
|
|
3327
|
+
break;
|
|
3328
|
+
}
|
|
3329
|
+
}
|
|
3330
|
+
// Only set if the model exists in the dropdown
|
|
3331
|
+
if (optionExists) {
|
|
3332
|
+
modelSelect.value = selectedModel;
|
|
3333
|
+
var ts2 = getThreadState(threadName);
|
|
3334
|
+
ts2.model = selectedModel;
|
|
3335
|
+
}
|
|
3336
|
+
}
|
|
3337
|
+
} catch (e) {
|
|
3338
|
+
/* ignore */
|
|
3339
|
+
}
|
|
3340
|
+
}
|
|
3341
|
+
};
|
|
3342
|
+
cfgXhr.send();
|
|
3343
|
+
} catch (e) {
|
|
3344
|
+
/* ignore parse errors */
|
|
3345
|
+
}
|
|
3346
|
+
}
|
|
3347
|
+
};
|
|
3348
|
+
xhr.send();
|
|
3349
|
+
})();
|
|
3350
|
+
modelSelect.addEventListener('change', function () {
|
|
3351
|
+
var newModel = modelSelect.value;
|
|
3352
|
+
var ts = getThreadState(threadName);
|
|
3353
|
+
ts.model = newModel;
|
|
3354
|
+
// Persist to thread config via API
|
|
3355
|
+
var xhr = new XMLHttpRequest();
|
|
3356
|
+
xhr.open('PUT', '/api/thread/' + encodeURIComponent(threadName) + '/config');
|
|
3357
|
+
xhr.setRequestHeader('Content-Type', 'application/json');
|
|
3358
|
+
xhr.setRequestHeader('X-API-Key', activeApiKey);
|
|
3359
|
+
xhr.send(JSON.stringify({ model: newModel }));
|
|
3360
|
+
});
|
|
3361
|
+
panelHeader.appendChild(modelSelect);
|
|
3362
|
+
|
|
3229
3363
|
// Action buttons (include + revert)
|
|
3230
3364
|
var actions = document.createElement('div');
|
|
3231
3365
|
actions.className = 'cumulus-panel-actions';
|
|
@@ -3280,10 +3414,46 @@
|
|
|
3280
3414
|
messagesEl.setAttribute('data-thread-messages', threadName);
|
|
3281
3415
|
panel.appendChild(messagesEl);
|
|
3282
3416
|
|
|
3417
|
+
// Scroll-to-top pagination: load older messages
|
|
3418
|
+
messagesEl.addEventListener('scroll', function () {
|
|
3419
|
+
if (
|
|
3420
|
+
messagesEl.scrollTop < 100 &&
|
|
3421
|
+
state.hasOlderMessages &&
|
|
3422
|
+
!state.loadingOlder &&
|
|
3423
|
+
state.oldestOffset > 0 &&
|
|
3424
|
+
connection
|
|
3425
|
+
) {
|
|
3426
|
+
state.loadingOlder = true;
|
|
3427
|
+
// Show loading indicator at top
|
|
3428
|
+
var loader = messagesEl.querySelector('.cumulus-load-older');
|
|
3429
|
+
if (!loader) {
|
|
3430
|
+
loader = document.createElement('div');
|
|
3431
|
+
loader.className = 'cumulus-load-older';
|
|
3432
|
+
loader.setAttribute('data-testid', 'webchat-load-older');
|
|
3433
|
+
loader.textContent = 'Loading older messages\u2026';
|
|
3434
|
+
messagesEl.insertBefore(loader, messagesEl.firstChild);
|
|
3435
|
+
}
|
|
3436
|
+
// Request older messages: go back 50 from the oldest we have
|
|
3437
|
+
var requestOffset = Math.max(0, state.oldestOffset - 50);
|
|
3438
|
+
var requestLimit = state.oldestOffset - requestOffset;
|
|
3439
|
+
connection.send({
|
|
3440
|
+
type: 'history',
|
|
3441
|
+
threadName: threadName,
|
|
3442
|
+
limit: requestLimit,
|
|
3443
|
+
offset: requestOffset,
|
|
3444
|
+
});
|
|
3445
|
+
}
|
|
3446
|
+
});
|
|
3447
|
+
|
|
3283
3448
|
// Input area
|
|
3284
3449
|
var inputArea = document.createElement('div');
|
|
3285
3450
|
inputArea.className = 'cumulus-input-area';
|
|
3286
3451
|
|
|
3452
|
+
// Chip tray for deferred blex interactions
|
|
3453
|
+
var chipTray = document.createElement('div');
|
|
3454
|
+
chipTray.className = 'cumulus-chip-tray';
|
|
3455
|
+
inputArea.appendChild(chipTray);
|
|
3456
|
+
|
|
3287
3457
|
// Attachment strip
|
|
3288
3458
|
var attachStrip = document.createElement('div');
|
|
3289
3459
|
attachStrip.className = 'cumulus-attach-strip';
|
|
@@ -3291,12 +3461,6 @@
|
|
|
3291
3461
|
attachStrip.style.display = 'none';
|
|
3292
3462
|
inputArea.appendChild(attachStrip);
|
|
3293
3463
|
|
|
3294
|
-
// Interaction chip tray (blex deferred interactions)
|
|
3295
|
-
var chipTray = document.createElement('div');
|
|
3296
|
-
chipTray.className = 'cumulus-chip-tray';
|
|
3297
|
-
chipTray.setAttribute('data-testid', 'webchat-chip-tray');
|
|
3298
|
-
inputArea.appendChild(chipTray);
|
|
3299
|
-
|
|
3300
3464
|
// Input row
|
|
3301
3465
|
var inputRow = document.createElement('div');
|
|
3302
3466
|
inputRow.className = 'cumulus-input-row';
|
|
@@ -3319,6 +3483,14 @@
|
|
|
3319
3483
|
inputEl.setAttribute('data-testid', 'webchat-input');
|
|
3320
3484
|
inputEl.placeholder = 'Message ' + threadName + '\u2026';
|
|
3321
3485
|
inputEl.rows = 1;
|
|
3486
|
+
// Restore draft from previous session on this thread
|
|
3487
|
+
if (state.draft) {
|
|
3488
|
+
inputEl.value = state.draft;
|
|
3489
|
+
requestAnimationFrame(function () {
|
|
3490
|
+
inputEl.style.height = 'auto';
|
|
3491
|
+
inputEl.style.height = Math.min(inputEl.scrollHeight, 120) + 'px';
|
|
3492
|
+
});
|
|
3493
|
+
}
|
|
3322
3494
|
|
|
3323
3495
|
var sendBtn = document.createElement('button');
|
|
3324
3496
|
sendBtn.className = 'cumulus-send-btn';
|
|
@@ -3347,6 +3519,18 @@
|
|
|
3347
3519
|
return lineCount > 3;
|
|
3348
3520
|
}
|
|
3349
3521
|
|
|
3522
|
+
function isSystemMessage(content) {
|
|
3523
|
+
return typeof content === 'string' && content.startsWith('[System]');
|
|
3524
|
+
}
|
|
3525
|
+
|
|
3526
|
+
function buildSystemMsgEl(content) {
|
|
3527
|
+
var el = document.createElement('div');
|
|
3528
|
+
el.className = 'cumulus-msg system';
|
|
3529
|
+
el.setAttribute('data-testid', 'webchat-system-msg');
|
|
3530
|
+
el.textContent = content.replace(/^\[System\]\s*/, '');
|
|
3531
|
+
return el;
|
|
3532
|
+
}
|
|
3533
|
+
|
|
3350
3534
|
function buildUserMsgEl(msg) {
|
|
3351
3535
|
var el = document.createElement('div');
|
|
3352
3536
|
el.className = 'cumulus-msg user' + (isWideUserMessage(msg.content) ? ' wide' : '');
|
|
@@ -3385,34 +3569,9 @@
|
|
|
3385
3569
|
if (isStreaming) {
|
|
3386
3570
|
el.innerHTML += '<span class="cumulus-cursor"></span>';
|
|
3387
3571
|
}
|
|
3388
|
-
// Render blex blocks
|
|
3389
|
-
if (
|
|
3390
|
-
|
|
3391
|
-
// During streaming: show placeholders
|
|
3392
|
-
el.querySelectorAll('[data-blex-idx]').forEach(function (ph) {
|
|
3393
|
-
var idx = parseInt(ph.getAttribute('data-blex-idx'), 10);
|
|
3394
|
-
var block = mdResult.blexBlocks[idx];
|
|
3395
|
-
if (block) window.Blex.renderPlaceholder(block.type, ph);
|
|
3396
|
-
});
|
|
3397
|
-
} else if (!isStreaming) {
|
|
3398
|
-
// Final render: full blex blocks with interaction wiring
|
|
3399
|
-
// Defer rendering until the element is in the DOM (el.closest needs a parent)
|
|
3400
|
-
var _blexBlocks = mdResult.blexBlocks;
|
|
3401
|
-
requestAnimationFrame(function () {
|
|
3402
|
-
var panelEl = el.closest('.cumulus-panel, .cumulus-widget, .cumulus-thread-panel');
|
|
3403
|
-
var blexInputEl = panelEl
|
|
3404
|
-
? panelEl.querySelector('.cumulus-input, .cumulus-standalone-input')
|
|
3405
|
-
: null;
|
|
3406
|
-
var blexChipTray = panelEl
|
|
3407
|
-
? panelEl.querySelector('.cumulus-chip-tray')
|
|
3408
|
-
: null;
|
|
3409
|
-
var blexChipCtx = blexChipTray
|
|
3410
|
-
? { tray: blexChipTray, pending: blexChipTray._pendingChips || (blexChipTray._pendingChips = []) }
|
|
3411
|
-
: null;
|
|
3412
|
-
console.log('[blex-render] panelEl:', !!panelEl, 'inputEl:', !!blexInputEl, 'chipTray:', !!blexChipTray, 'chipCtx:', !!blexChipCtx, 'blocks:', _blexBlocks.length);
|
|
3413
|
-
renderBlexBlocks(el, _blexBlocks, blexInputEl, blexChipCtx);
|
|
3414
|
-
});
|
|
3415
|
-
}
|
|
3572
|
+
// Render blex blocks — skip during streaming to avoid destroy/recreate churn
|
|
3573
|
+
if (!isStreaming) {
|
|
3574
|
+
renderBlexBlocks(el, mdResult.blexBlocks, false);
|
|
3416
3575
|
}
|
|
3417
3576
|
el.querySelectorAll('.code-block-copy-btn').forEach(function (btn) {
|
|
3418
3577
|
btn.addEventListener('click', function () {
|
|
@@ -3436,7 +3595,7 @@
|
|
|
3436
3595
|
}
|
|
3437
3596
|
|
|
3438
3597
|
function renderPanelMessages() {
|
|
3439
|
-
// Destroy existing blex
|
|
3598
|
+
// Destroy existing blex blocks before clearing DOM
|
|
3440
3599
|
messagesEl.querySelectorAll('.cumulus-msg.assistant').forEach(function (m) {
|
|
3441
3600
|
destroyBlexBlocks(m);
|
|
3442
3601
|
});
|
|
@@ -3453,6 +3612,16 @@
|
|
|
3453
3612
|
updateSelectionBar();
|
|
3454
3613
|
return;
|
|
3455
3614
|
}
|
|
3615
|
+
// Show "load older" indicator if more messages are available
|
|
3616
|
+
if (state.hasOlderMessages) {
|
|
3617
|
+
var loader = document.createElement('div');
|
|
3618
|
+
loader.className = 'cumulus-load-older';
|
|
3619
|
+
loader.setAttribute('data-testid', 'webchat-load-older');
|
|
3620
|
+
loader.textContent = state.loadingOlder
|
|
3621
|
+
? 'Loading older messages\u2026'
|
|
3622
|
+
: '\u2191 Scroll up for older messages';
|
|
3623
|
+
messagesEl.appendChild(loader);
|
|
3624
|
+
}
|
|
3456
3625
|
for (var i = 0; i < state.messages.length; i++) {
|
|
3457
3626
|
var msg = state.messages[i];
|
|
3458
3627
|
var row = document.createElement('div');
|
|
@@ -3475,7 +3644,9 @@
|
|
|
3475
3644
|
})(msg.id);
|
|
3476
3645
|
row.appendChild(cb);
|
|
3477
3646
|
}
|
|
3478
|
-
if (msg.role === 'user') {
|
|
3647
|
+
if (msg.role === 'user' && isSystemMessage(msg.content)) {
|
|
3648
|
+
row.appendChild(buildSystemMsgEl(msg.content));
|
|
3649
|
+
} else if (msg.role === 'user') {
|
|
3479
3650
|
var agent = parseAgentMessage(msg.content);
|
|
3480
3651
|
if (agent) {
|
|
3481
3652
|
row.appendChild(buildAgentMsgEl(agent));
|
|
@@ -3708,51 +3879,29 @@
|
|
|
3708
3879
|
}
|
|
3709
3880
|
|
|
3710
3881
|
// ── Incremental streaming update ──
|
|
3711
|
-
// Only
|
|
3712
|
-
|
|
3713
|
-
function
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
}
|
|
3721
|
-
if (!_streamingRow) {
|
|
3722
|
-
// First token: do a full render to set up all messages + streaming row
|
|
3723
|
-
renderPanelMessages();
|
|
3724
|
-
// Grab reference to the streaming row (last row in messagesEl)
|
|
3725
|
-
var rows = messagesEl.querySelectorAll('.cumulus-msg-row');
|
|
3726
|
-
if (rows.length > 0) {
|
|
3727
|
-
var lastRow = rows[rows.length - 1];
|
|
3728
|
-
var streamingEl = lastRow.querySelector('[data-testid="webchat-streaming"]');
|
|
3729
|
-
if (streamingEl) _streamingRow = lastRow;
|
|
3730
|
-
}
|
|
3731
|
-
return;
|
|
3732
|
-
}
|
|
3733
|
-
// Subsequent tokens: only update the streaming row's content
|
|
3734
|
-
var el = _streamingRow.querySelector('.cumulus-msg.assistant');
|
|
3735
|
-
if (el && state.streamBuffer) {
|
|
3736
|
-
var mdResult = renderMarkdown(state.streamBuffer);
|
|
3737
|
-
el.innerHTML = mdResult.html + '<span class="cumulus-cursor"></span>';
|
|
3738
|
-
// Show blex placeholders during streaming
|
|
3739
|
-
if (mdResult.blexBlocks.length > 0 && typeof window.Blex !== 'undefined') {
|
|
3740
|
-
el.querySelectorAll('[data-blex-idx]').forEach(function (ph) {
|
|
3741
|
-
var idx = parseInt(ph.getAttribute('data-blex-idx'), 10);
|
|
3742
|
-
var block = mdResult.blexBlocks[idx];
|
|
3743
|
-
if (block) window.Blex.renderPlaceholder(block.type, ph);
|
|
3744
|
-
});
|
|
3882
|
+
// Only update the streaming row instead of rebuilding all messages.
|
|
3883
|
+
// This prevents blex blocks in completed messages from being destroyed/recreated on every token.
|
|
3884
|
+
function updateStreamOnly() {
|
|
3885
|
+
var streamRow = messagesEl.querySelector('[data-testid="webchat-streaming"]');
|
|
3886
|
+
if (streamRow) {
|
|
3887
|
+
var parentRow = streamRow.closest('.cumulus-msg-row') || streamRow.parentElement;
|
|
3888
|
+
if (parentRow) {
|
|
3889
|
+
var newEl = buildAssistantMsgEl(state.streamBuffer, true);
|
|
3890
|
+
parentRow.replaceChild(newEl, streamRow);
|
|
3745
3891
|
}
|
|
3746
|
-
} else if (
|
|
3747
|
-
|
|
3748
|
-
|
|
3892
|
+
} else if (state.streaming) {
|
|
3893
|
+
// No streaming element yet — append one
|
|
3894
|
+
var row = document.createElement('div');
|
|
3895
|
+
row.className = 'cumulus-msg-row';
|
|
3896
|
+
row.appendChild(buildAssistantMsgEl(state.streamBuffer, true));
|
|
3897
|
+
messagesEl.appendChild(row);
|
|
3749
3898
|
}
|
|
3750
3899
|
scrollToBottom();
|
|
3751
3900
|
}
|
|
3752
3901
|
|
|
3753
3902
|
// Store references on the state so the router can call re-render
|
|
3754
3903
|
state._renderMessages = renderPanelMessages;
|
|
3755
|
-
state.
|
|
3904
|
+
state._updateStreamOnly = updateStreamOnly;
|
|
3756
3905
|
state._updateSendBtn = updatePanelSendBtn;
|
|
3757
3906
|
|
|
3758
3907
|
// Initial render (messages may already be loaded from a previous view)
|
|
@@ -3760,16 +3909,9 @@
|
|
|
3760
3909
|
|
|
3761
3910
|
// ── Send message for this panel ──
|
|
3762
3911
|
function sendPanelMessage() {
|
|
3763
|
-
// Prepend deferred interaction chips to the message
|
|
3764
|
-
var
|
|
3765
|
-
var
|
|
3766
|
-
if (chipPending.length > 0) {
|
|
3767
|
-
chipPrefix = chipPending.map(function (c) { return c.serialized; }).join('\n');
|
|
3768
|
-
chipPending.length = 0;
|
|
3769
|
-
chipTray.innerHTML = '';
|
|
3770
|
-
}
|
|
3771
|
-
var rawText = inputEl.value.trim();
|
|
3772
|
-
var text = chipPrefix ? (chipPrefix + (rawText ? '\n' + rawText : '')) : rawText;
|
|
3912
|
+
// Prepend any deferred blex interaction chips to the message
|
|
3913
|
+
var chipText = collectAndClearChips(panel);
|
|
3914
|
+
var text = (chipText ? chipText + '\n' : '') + inputEl.value.trim();
|
|
3773
3915
|
|
|
3774
3916
|
// ── Interjection: user sends while streaming ──
|
|
3775
3917
|
// Freeze partial response, send the new message — server aborts the old stream
|
|
@@ -3806,6 +3948,7 @@
|
|
|
3806
3948
|
state.messages.push({ role: 'user', content: displayText, attachments: attachSnapshot });
|
|
3807
3949
|
inputEl.value = '';
|
|
3808
3950
|
inputEl.style.height = 'auto';
|
|
3951
|
+
state.draft = '';
|
|
3809
3952
|
state.pendingAttachments = [];
|
|
3810
3953
|
renderPanelAttachStrip();
|
|
3811
3954
|
state.streaming = true;
|
|
@@ -3846,6 +3989,8 @@
|
|
|
3846
3989
|
inputEl.addEventListener('input', function () {
|
|
3847
3990
|
inputEl.style.height = 'auto';
|
|
3848
3991
|
inputEl.style.height = Math.min(inputEl.scrollHeight, 120) + 'px';
|
|
3992
|
+
// Persist draft per thread
|
|
3993
|
+
state.draft = inputEl.value;
|
|
3849
3994
|
// Update send button label (Send vs Stop) based on input content
|
|
3850
3995
|
updatePanelSendBtn();
|
|
3851
3996
|
});
|
|
@@ -3887,15 +4032,15 @@
|
|
|
3887
4032
|
|
|
3888
4033
|
// ── Re-render a visible thread panel's messages in-place ──
|
|
3889
4034
|
// Called by the message router after state mutation
|
|
3890
|
-
function refreshThreadPanel(threadName,
|
|
4035
|
+
function refreshThreadPanel(threadName, streamingOnly) {
|
|
3891
4036
|
var state = threadStates[threadName];
|
|
3892
4037
|
if (!state) return;
|
|
3893
4038
|
|
|
3894
|
-
// During streaming
|
|
3895
|
-
|
|
3896
|
-
|
|
4039
|
+
// During streaming, only update the streaming element (not all messages)
|
|
4040
|
+
// This prevents blex blocks in completed messages from being destroyed on every token
|
|
4041
|
+
if (streamingOnly && state._updateStreamOnly) {
|
|
4042
|
+
state._updateStreamOnly();
|
|
3897
4043
|
} else if (state._renderMessages) {
|
|
3898
|
-
// Full rebuild (done, new message, history load, etc.)
|
|
3899
4044
|
state._renderMessages();
|
|
3900
4045
|
}
|
|
3901
4046
|
if (state._updateSendBtn) {
|
|
@@ -4183,12 +4328,11 @@
|
|
|
4183
4328
|
|
|
4184
4329
|
function formatTime(ts) {
|
|
4185
4330
|
var d = new Date(typeof ts === 'number' ? ts : Date.parse(ts));
|
|
4186
|
-
var
|
|
4187
|
-
var
|
|
4188
|
-
var
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
return hh + ':' + mm + ' ' + mon + '/' + dd;
|
|
4331
|
+
var h = d.getHours();
|
|
4332
|
+
var m = d.getMinutes();
|
|
4333
|
+
var ampm = h >= 12 ? 'PM' : 'AM';
|
|
4334
|
+
h = h % 12 || 12;
|
|
4335
|
+
return h + ':' + (m < 10 ? '0' : '') + m + ' ' + ampm;
|
|
4192
4336
|
}
|
|
4193
4337
|
|
|
4194
4338
|
// ── Push notification subscription ──
|
|
@@ -4383,29 +4527,81 @@
|
|
|
4383
4527
|
case 'history':
|
|
4384
4528
|
if (data.threadName) {
|
|
4385
4529
|
var state = getThreadState(data.threadName);
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4530
|
+
var total = data.total || 0;
|
|
4531
|
+
var responseOffset = typeof data.offset === 'number' ? data.offset : -1;
|
|
4532
|
+
var isPagination =
|
|
4533
|
+
state.loadingOlder && responseOffset >= 0 && responseOffset < state.oldestOffset;
|
|
4534
|
+
|
|
4535
|
+
if (isPagination) {
|
|
4536
|
+
// Prepend older messages to the existing array
|
|
4537
|
+
state.loadingOlder = false;
|
|
4538
|
+
if (data.messages && data.messages.length > 0) {
|
|
4539
|
+
var olderMsgs = data.messages.map(function (m) {
|
|
4540
|
+
return { id: m.id, role: m.role, content: m.content, timestamp: m.timestamp };
|
|
4541
|
+
});
|
|
4542
|
+
state.messages = olderMsgs.concat(state.messages);
|
|
4543
|
+
state.oldestOffset = responseOffset;
|
|
4544
|
+
state.hasOlderMessages = responseOffset > 0;
|
|
4545
|
+
|
|
4546
|
+
// Preserve scroll position: save current height, render, adjust
|
|
4547
|
+
var panel = document.querySelector('[data-thread-panel="' + data.threadName + '"]');
|
|
4548
|
+
var msgEl =
|
|
4549
|
+
panel && panel.querySelector('[data-thread-messages="' + data.threadName + '"]');
|
|
4550
|
+
if (msgEl) {
|
|
4551
|
+
var prevHeight = msgEl.scrollHeight;
|
|
4552
|
+
var prevTop = msgEl.scrollTop;
|
|
4553
|
+
refreshThreadPanel(data.threadName);
|
|
4554
|
+
// After render, adjust scroll so the same content stays in view
|
|
4555
|
+
requestAnimationFrame(function () {
|
|
4556
|
+
var newHeight = msgEl.scrollHeight;
|
|
4557
|
+
msgEl.scrollTop = prevTop + (newHeight - prevHeight);
|
|
4558
|
+
});
|
|
4559
|
+
} else {
|
|
4560
|
+
refreshThreadPanel(data.threadName);
|
|
4561
|
+
}
|
|
4562
|
+
} else {
|
|
4563
|
+
// No more messages — remove loader
|
|
4564
|
+
state.hasOlderMessages = false;
|
|
4565
|
+
var panel = document.querySelector('[data-thread-panel="' + data.threadName + '"]');
|
|
4566
|
+
var msgEl =
|
|
4567
|
+
panel && panel.querySelector('[data-thread-messages="' + data.threadName + '"]');
|
|
4568
|
+
if (msgEl) {
|
|
4569
|
+
var loader = msgEl.querySelector('.cumulus-load-older');
|
|
4570
|
+
if (loader) loader.remove();
|
|
4571
|
+
}
|
|
4572
|
+
}
|
|
4573
|
+
} else {
|
|
4574
|
+
// Initial load or full refresh — replace all messages
|
|
4575
|
+
if (data.messages && data.messages.length > 0) {
|
|
4576
|
+
var lastMsg = data.messages[data.messages.length - 1];
|
|
4577
|
+
if (lastMsg && lastMsg.streaming) {
|
|
4578
|
+
var nonStreamingMsgs = data.messages.slice(0, -1);
|
|
4579
|
+
state.messages = nonStreamingMsgs.map(function (m) {
|
|
4580
|
+
return { id: m.id, role: m.role, content: m.content, timestamp: m.timestamp };
|
|
4581
|
+
});
|
|
4582
|
+
state.streaming = true;
|
|
4583
|
+
state.streamBuffer = lastMsg.content;
|
|
4584
|
+
state.stopRequested = false;
|
|
4585
|
+
resetStreamingTimeout(data.threadName);
|
|
4394
4586
|
} else {
|
|
4395
|
-
|
|
4587
|
+
state.messages = data.messages.map(function (m) {
|
|
4588
|
+
return { id: m.id, role: m.role, content: m.content, timestamp: m.timestamp };
|
|
4589
|
+
});
|
|
4396
4590
|
}
|
|
4397
4591
|
}
|
|
4398
|
-
state
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4592
|
+
// Set pagination state from response
|
|
4593
|
+
if (responseOffset >= 0) {
|
|
4594
|
+
state.oldestOffset = responseOffset;
|
|
4595
|
+
state.hasOlderMessages = responseOffset > 0;
|
|
4596
|
+
} else if (total > 0 && data.messages) {
|
|
4597
|
+
// Fallback: estimate from total and message count
|
|
4598
|
+
state.oldestOffset = Math.max(0, total - data.messages.length);
|
|
4599
|
+
state.hasOlderMessages = state.oldestOffset > 0;
|
|
4405
4600
|
}
|
|
4601
|
+
state.historyLoaded = true;
|
|
4602
|
+
state.loadingOlder = false;
|
|
4603
|
+
refreshThreadPanel(data.threadName);
|
|
4406
4604
|
}
|
|
4407
|
-
state.historyLoaded = true;
|
|
4408
|
-
refreshThreadPanel(data.threadName);
|
|
4409
4605
|
}
|
|
4410
4606
|
break;
|
|
4411
4607
|
|
|
@@ -4422,7 +4618,7 @@
|
|
|
4422
4618
|
resetStreamingTimeout(data.threadName);
|
|
4423
4619
|
// Update lastActivity for this thread in allThreads
|
|
4424
4620
|
updateThreadActivity(data.threadName);
|
|
4425
|
-
// Incremental update: only touch the streaming element, preserve blex blocks
|
|
4621
|
+
// Incremental update: only touch the streaming element, preserve blex blocks in completed messages
|
|
4426
4622
|
refreshThreadPanel(data.threadName, true);
|
|
4427
4623
|
}
|
|
4428
4624
|
break;
|