@luckydraw/cumulus 0.27.19 → 0.27.21
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/cli/cumulus.js +0 -0
- package/dist/gateway/adapters/webchat.d.ts +3 -1
- package/dist/gateway/adapters/webchat.d.ts.map +1 -1
- package/dist/gateway/adapters/webchat.js +168 -4
- package/dist/gateway/adapters/webchat.js.map +1 -1
- package/dist/gateway/daemon.js +8 -0
- package/dist/gateway/daemon.js.map +1 -1
- package/dist/gateway/device-mcp.d.ts +21 -0
- package/dist/gateway/device-mcp.d.ts.map +1 -0
- package/dist/gateway/device-mcp.js +165 -0
- package/dist/gateway/device-mcp.js.map +1 -0
- package/dist/gateway/gateway-agents-mcp.js +1 -1
- package/dist/gateway/gateway-agents-mcp.js.map +1 -1
- package/dist/gateway/server.d.ts +9 -0
- package/dist/gateway/server.d.ts.map +1 -1
- package/dist/gateway/server.js +243 -6
- package/dist/gateway/server.js.map +1 -1
- package/dist/gateway/static/widget.js +861 -43
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/config.d.ts +8 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +61 -1
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/content-detector.d.ts.map +1 -1
- package/dist/lib/content-detector.js +9 -2
- package/dist/lib/content-detector.js.map +1 -1
- package/dist/lib/executor.d.ts +168 -0
- package/dist/lib/executor.d.ts.map +1 -0
- package/dist/lib/executor.js +284 -0
- package/dist/lib/executor.js.map +1 -0
- package/dist/lib/gateway.d.ts +14 -2
- package/dist/lib/gateway.d.ts.map +1 -1
- package/dist/lib/gateway.js +234 -103
- package/dist/lib/gateway.js.map +1 -1
- package/dist/lib/summaries.d.ts.map +1 -1
- package/dist/lib/summaries.js +9 -2
- package/dist/lib/summaries.js.map +1 -1
- package/dist/mcp/index.js +0 -0
- package/dist/mcp/tool-handler.d.ts.map +1 -1
- package/dist/mcp/tool-handler.js +10 -3
- package/dist/mcp/tool-handler.js.map +1 -1
- package/dist/tui/hooks/useClaudeProcess.d.ts.map +1 -1
- package/dist/tui/hooks/useClaudeProcess.js +18 -23
- package/dist/tui/hooks/useClaudeProcess.js.map +1 -1
- package/package.json +1 -1
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Cumulus Web Chat Widget — self-contained, no external dependencies.
|
|
3
3
|
* Embeddable via <script src="/widget.js" data-api-key="..."></script>
|
|
4
4
|
* or usable standalone at /chat.
|
|
5
|
+
* @version 0.27.15
|
|
5
6
|
*
|
|
6
7
|
* < 80KB unminified.
|
|
7
8
|
*/
|
|
@@ -154,6 +155,38 @@
|
|
|
154
155
|
' word-wrap: break-word;',
|
|
155
156
|
'}',
|
|
156
157
|
|
|
158
|
+
/* ── Timestamps ── */
|
|
159
|
+
'.cumulus-msg-time {',
|
|
160
|
+
' font-size: 0.75em; color: #555; margin-top: 0.3em; padding: 0 0.3em;',
|
|
161
|
+
'}',
|
|
162
|
+
'.cumulus-msg-row:has(.user) .cumulus-msg-time { align-self: flex-end; }',
|
|
163
|
+
'.cumulus-msg-row:has(.assistant) .cumulus-msg-time { align-self: flex-start; }',
|
|
164
|
+
|
|
165
|
+
/* ── Agent messages ── */
|
|
166
|
+
'.cumulus-msg.agent {',
|
|
167
|
+
' align-self: flex-start;',
|
|
168
|
+
' background: transparent;',
|
|
169
|
+
' color: #e0e0e0;',
|
|
170
|
+
' padding: 0.4em 0 0.4em 0.75em;',
|
|
171
|
+
' width: 100%;',
|
|
172
|
+
' word-wrap: break-word;',
|
|
173
|
+
' border-left: 2px solid var(--agent-color, #9966cc);',
|
|
174
|
+
'}',
|
|
175
|
+
'.cumulus-agent-header {',
|
|
176
|
+
' display: flex; align-items: center; gap: 0.4em;',
|
|
177
|
+
' margin-bottom: 0.3em; font-size: 0.82em;',
|
|
178
|
+
'}',
|
|
179
|
+
'.cumulus-agent-name {',
|
|
180
|
+
' font-family: "SF Mono", "Fira Code", Consolas, monospace;',
|
|
181
|
+
' font-weight: 600; color: var(--agent-color, #9966cc);',
|
|
182
|
+
'}',
|
|
183
|
+
'.cumulus-agent-name::before { content: "["; opacity: 0.5; }',
|
|
184
|
+
'.cumulus-agent-name::after { content: "]"; opacity: 0.5; }',
|
|
185
|
+
'.cumulus-agent-context {',
|
|
186
|
+
' font-size: 0.9em; color: #666;',
|
|
187
|
+
' font-family: "SF Mono", "Fira Code", Consolas, monospace;',
|
|
188
|
+
'}',
|
|
189
|
+
|
|
157
190
|
/* ── Markdown rendered inside assistant messages ── */
|
|
158
191
|
'.cumulus-msg.assistant p { margin: 0.4em 0; }',
|
|
159
192
|
'.cumulus-msg.assistant p:first-child { margin-top: 0; }',
|
|
@@ -685,6 +718,97 @@
|
|
|
685
718
|
' border-radius: 3px; font-family: inherit; white-space: nowrap;',
|
|
686
719
|
'}',
|
|
687
720
|
'.cumulus-panel-action-btn:hover { color: #ddd; border-color: #555; }',
|
|
721
|
+
'.cumulus-panel-action-btn.active { color: #e0e0e0; border-color: #0066cc; background: rgba(0,102,204,0.12); }',
|
|
722
|
+
|
|
723
|
+
/* ── Context menu (right-click on thread) ── */
|
|
724
|
+
'.cumulus-context-menu {',
|
|
725
|
+
' position: fixed; z-index: 300; background: #2a2a2a; border: 1px solid #444;',
|
|
726
|
+
' border-radius: 6px; padding: 4px 0; min-width: 140px;',
|
|
727
|
+
' box-shadow: 0 4px 12px rgba(0,0,0,0.5); font-size: 13px;',
|
|
728
|
+
'}',
|
|
729
|
+
'.cumulus-context-menu-item {',
|
|
730
|
+
' padding: 6px 14px; color: #ccc; cursor: pointer; display: block;',
|
|
731
|
+
' width: 100%; border: none; background: none; text-align: left;',
|
|
732
|
+
' font-family: inherit; font-size: inherit;',
|
|
733
|
+
'}',
|
|
734
|
+
'.cumulus-context-menu-item:hover { background: #3a3a3a; color: #fff; }',
|
|
735
|
+
'.cumulus-context-menu-item.danger { color: #ef4444; }',
|
|
736
|
+
'.cumulus-context-menu-item.danger:hover { background: rgba(239,68,68,0.15); color: #ff6b6b; }',
|
|
737
|
+
|
|
738
|
+
/* ── Unread dot (glowing) ── */
|
|
739
|
+
'.cumulus-thread-unread {',
|
|
740
|
+
' width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0;',
|
|
741
|
+
' background: #3b82f6;',
|
|
742
|
+
' box-shadow: 0 0 4px #3b82f6, 0 0 8px rgba(59,130,246,0.4);',
|
|
743
|
+
' animation: cumulus-unread-pulse 2s ease-in-out infinite;',
|
|
744
|
+
'}',
|
|
745
|
+
'@keyframes cumulus-unread-pulse {',
|
|
746
|
+
' 0%, 100% { box-shadow: 0 0 4px #3b82f6, 0 0 8px rgba(59,130,246,0.4); }',
|
|
747
|
+
' 50% { box-shadow: 0 0 6px #3b82f6, 0 0 14px rgba(59,130,246,0.6); }',
|
|
748
|
+
'}',
|
|
749
|
+
|
|
750
|
+
/* ── Message checkbox (selection mode) ── */
|
|
751
|
+
'.cumulus-msg-checkbox {',
|
|
752
|
+
' display: none; flex-shrink: 0; align-self: flex-start; margin-top: 0.4em;',
|
|
753
|
+
' width: 16px; height: 16px; cursor: pointer; accent-color: #0066cc;',
|
|
754
|
+
'}',
|
|
755
|
+
'.cumulus-selection-mode .cumulus-msg-checkbox { display: block; }',
|
|
756
|
+
'.cumulus-selection-mode .cumulus-msg-row {',
|
|
757
|
+
' flex-direction: row; align-items: flex-start; gap: 8px;',
|
|
758
|
+
'}',
|
|
759
|
+
'.cumulus-selection-mode .cumulus-msg-row > *:not(.cumulus-msg-checkbox) { flex: 1; min-width: 0; }',
|
|
760
|
+
|
|
761
|
+
/* ── Selection action bar (floats at bottom of messages area) ── */
|
|
762
|
+
'.cumulus-selection-bar {',
|
|
763
|
+
' display: flex; align-items: center; gap: 8px;',
|
|
764
|
+
' padding: 8px 12px;',
|
|
765
|
+
' background: #2d2d2d; border-top: 1px solid #3a3a3a;',
|
|
766
|
+
' flex-shrink: 0;',
|
|
767
|
+
'}',
|
|
768
|
+
'.cumulus-selection-count { font-size: 13px; color: #aaa; flex: 1; }',
|
|
769
|
+
'.cumulus-selection-select-all-btn {',
|
|
770
|
+
' background: none; border: 1px solid #555; color: #ccc;',
|
|
771
|
+
' padding: 4px 10px; border-radius: 4px; font-size: 12px;',
|
|
772
|
+
' cursor: pointer; font-family: inherit;',
|
|
773
|
+
'}',
|
|
774
|
+
'.cumulus-selection-select-all-btn:hover { border-color: #888; color: #e0e0e0; }',
|
|
775
|
+
'.cumulus-selection-delete-btn {',
|
|
776
|
+
' background: #8b2222; border: none; color: white;',
|
|
777
|
+
' padding: 4px 12px; border-radius: 4px; font-size: 12px;',
|
|
778
|
+
' cursor: pointer; font-family: inherit; font-weight: 600;',
|
|
779
|
+
'}',
|
|
780
|
+
'.cumulus-selection-delete-btn:hover:not(:disabled) { background: #a33; }',
|
|
781
|
+
'.cumulus-selection-delete-btn:disabled { background: #3a2a2a; color: #666; cursor: not-allowed; }',
|
|
782
|
+
|
|
783
|
+
/* ── Confirm dialog overlay ── */
|
|
784
|
+
'.cumulus-confirm-dialog {',
|
|
785
|
+
' position: absolute; top: 0; left: 0; right: 0; bottom: 0;',
|
|
786
|
+
' background: rgba(0,0,0,0.6); z-index: 20;',
|
|
787
|
+
' display: flex; align-items: center; justify-content: center;',
|
|
788
|
+
'}',
|
|
789
|
+
'.cumulus-confirm-dialog-box {',
|
|
790
|
+
' background: #2d2d2d; border: 1px solid #4a4a4a; border-radius: 8px;',
|
|
791
|
+
' padding: 1.5em; max-width: 320px; width: 90%;',
|
|
792
|
+
' display: flex; flex-direction: column; gap: 1em; text-align: center;',
|
|
793
|
+
' box-shadow: 0 8px 32px rgba(0,0,0,0.5);',
|
|
794
|
+
'}',
|
|
795
|
+
'.cumulus-confirm-dialog-title { font-weight: 600; font-size: 15px; color: #e0e0e0; }',
|
|
796
|
+
'.cumulus-confirm-dialog-text { font-size: 13px; color: #aaa; }',
|
|
797
|
+
'.cumulus-confirm-dialog-warn { font-size: 12px; color: #f59e0b; }',
|
|
798
|
+
'.cumulus-confirm-dialog-actions { display: flex; gap: 8px; justify-content: center; }',
|
|
799
|
+
'.cumulus-confirm-dialog-cancel {',
|
|
800
|
+
' padding: 0.4em 1.2em; border-radius: 4px; cursor: pointer;',
|
|
801
|
+
' font-size: 13px; border: 1px solid #3a3a3a; background: #2a2a2a; color: #ccc;',
|
|
802
|
+
' font-family: inherit;',
|
|
803
|
+
'}',
|
|
804
|
+
'.cumulus-confirm-dialog-cancel:hover { background: #3a3a3a; }',
|
|
805
|
+
'.cumulus-confirm-dialog-confirm {',
|
|
806
|
+
' padding: 0.4em 1.2em; border-radius: 4px; cursor: pointer;',
|
|
807
|
+
' font-size: 13px; border: none; background: #8b2222; color: white;',
|
|
808
|
+
' font-family: inherit; font-weight: 600;',
|
|
809
|
+
'}',
|
|
810
|
+
'.cumulus-confirm-dialog-confirm:hover { background: #a33; }',
|
|
811
|
+
'.cumulus-confirm-dialog-confirm:disabled { background: #3a2a2a; color: #666; cursor: not-allowed; }',
|
|
688
812
|
|
|
689
813
|
/* ── Overlay (covers the thread panel) ── */
|
|
690
814
|
'.cumulus-overlay {',
|
|
@@ -920,6 +1044,70 @@
|
|
|
920
1044
|
// else, then reinsert code blocks. This prevents markdown patterns inside
|
|
921
1045
|
// code from being processed.
|
|
922
1046
|
|
|
1047
|
+
// ── Agent message detection + timestamps ──────────────────────────────────
|
|
1048
|
+
var AGENT_MSG_RE = /^\[([^\]]+)\s*\u2192\s*([^\]]+)\]:\s*/; // [sender → recipient]:
|
|
1049
|
+
var AGENT_COLORS = ['#9966cc', '#2aa198', '#e69500', '#d33682', '#85c025', '#ff6b6b'];
|
|
1050
|
+
|
|
1051
|
+
function getAgentColor(name) {
|
|
1052
|
+
var hash = 0;
|
|
1053
|
+
for (var i = 0; i < name.length; i++) hash = ((hash << 5) - hash + name.charCodeAt(i)) | 0;
|
|
1054
|
+
return AGENT_COLORS[Math.abs(hash) % AGENT_COLORS.length];
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
function parseAgentMessage(content) {
|
|
1058
|
+
var match = content.match(AGENT_MSG_RE);
|
|
1059
|
+
if (!match) return null;
|
|
1060
|
+
var body = content
|
|
1061
|
+
.slice(match[0].length)
|
|
1062
|
+
.replace(/\n\n\(Reply using send_to_agent\([^)]*\)[^)]*\)\s*$/, '')
|
|
1063
|
+
.trim();
|
|
1064
|
+
// Also strip CC/broadcast reply hints
|
|
1065
|
+
body = body.replace(/\n\n\(You are CC'd on this message[^)]*\)\s*$/, '').trim();
|
|
1066
|
+
body = body.replace(/\n\n\(Review the above messages[^)]*\)\s*$/, '').trim();
|
|
1067
|
+
return { sender: match[1].trim(), recipient: match[2].trim(), body: body };
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
function formatTime(timestamp) {
|
|
1071
|
+
if (!timestamp) return null;
|
|
1072
|
+
var d = new Date(timestamp);
|
|
1073
|
+
return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
function appendTimestamp(parent, timestamp) {
|
|
1077
|
+
var time = formatTime(timestamp);
|
|
1078
|
+
if (time) {
|
|
1079
|
+
var timeEl = document.createElement('div');
|
|
1080
|
+
timeEl.className = 'cumulus-msg-time';
|
|
1081
|
+
timeEl.textContent = time;
|
|
1082
|
+
parent.appendChild(timeEl);
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
function buildAgentMsgEl(agent) {
|
|
1087
|
+
var el = document.createElement('div');
|
|
1088
|
+
el.className = 'cumulus-msg agent';
|
|
1089
|
+
el.style.setProperty('--agent-color', getAgentColor(agent.sender));
|
|
1090
|
+
|
|
1091
|
+
var header = document.createElement('div');
|
|
1092
|
+
header.className = 'cumulus-agent-header';
|
|
1093
|
+
var nameEl = document.createElement('span');
|
|
1094
|
+
nameEl.className = 'cumulus-agent-name';
|
|
1095
|
+
nameEl.textContent = agent.sender;
|
|
1096
|
+
header.appendChild(nameEl);
|
|
1097
|
+
var ctx = document.createElement('span');
|
|
1098
|
+
ctx.className = 'cumulus-agent-context';
|
|
1099
|
+
ctx.textContent = '\u2192 ' + agent.recipient;
|
|
1100
|
+
header.appendChild(ctx);
|
|
1101
|
+
el.appendChild(header);
|
|
1102
|
+
|
|
1103
|
+
var body = document.createElement('div');
|
|
1104
|
+
body.innerHTML = renderMarkdown(agent.body);
|
|
1105
|
+
el.appendChild(body);
|
|
1106
|
+
return el;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
// ── Markdown ──────────────────────────────────────────────────────────────
|
|
1110
|
+
|
|
923
1111
|
function renderMarkdown(text) {
|
|
924
1112
|
// Phase 1: Extract fenced code blocks — replace with unique tokens
|
|
925
1113
|
var codeBlocks = [];
|
|
@@ -1333,8 +1521,12 @@
|
|
|
1333
1521
|
var streamBuffer = '';
|
|
1334
1522
|
var connection = null;
|
|
1335
1523
|
var connectionStatus = 'disconnected';
|
|
1524
|
+
var embeddedStreamTimer = null;
|
|
1525
|
+
var EMBEDDED_STREAM_TIMEOUT = 120000;
|
|
1336
1526
|
var authenticated = false;
|
|
1337
1527
|
var pendingAttachments = [];
|
|
1528
|
+
var selectionMode = false;
|
|
1529
|
+
var selectedIds = {};
|
|
1338
1530
|
|
|
1339
1531
|
// ── Panel ──
|
|
1340
1532
|
var panel = document.createElement('div');
|
|
@@ -1351,6 +1543,9 @@
|
|
|
1351
1543
|
'<span class="cumulus-status-dot" data-testid="webchat-status-dot"></span>' +
|
|
1352
1544
|
'<span data-testid="webchat-status-text">Disconnected</span>' +
|
|
1353
1545
|
'</span>' +
|
|
1546
|
+
'<div class="cumulus-panel-actions">' +
|
|
1547
|
+
'<button class="cumulus-panel-action-btn" data-testid="webchat-select-btn" title="Select messages to delete">Select</button>' +
|
|
1548
|
+
'</div>' +
|
|
1354
1549
|
'<button class="cumulus-close-btn" data-testid="webchat-close">×</button>';
|
|
1355
1550
|
panel.appendChild(header);
|
|
1356
1551
|
|
|
@@ -1449,7 +1644,9 @@
|
|
|
1449
1644
|
|
|
1450
1645
|
// ── Rendering ──
|
|
1451
1646
|
function scrollToBottom() {
|
|
1452
|
-
|
|
1647
|
+
requestAnimationFrame(function () {
|
|
1648
|
+
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
1649
|
+
});
|
|
1453
1650
|
}
|
|
1454
1651
|
|
|
1455
1652
|
function isWideUserMessage(content) {
|
|
@@ -1518,20 +1715,51 @@
|
|
|
1518
1715
|
|
|
1519
1716
|
function renderMessages() {
|
|
1520
1717
|
messagesEl.innerHTML = '';
|
|
1718
|
+
// Apply selection mode class
|
|
1719
|
+
if (selectionMode) {
|
|
1720
|
+
messagesEl.classList.add('cumulus-selection-mode');
|
|
1721
|
+
} else {
|
|
1722
|
+
messagesEl.classList.remove('cumulus-selection-mode');
|
|
1723
|
+
}
|
|
1521
1724
|
if (messages.length === 0 && !streaming) {
|
|
1522
1725
|
messagesEl.innerHTML =
|
|
1523
1726
|
'<div class="cumulus-empty">Send a message to start chatting</div>';
|
|
1727
|
+
updateEmbeddedSelectionBar();
|
|
1524
1728
|
return;
|
|
1525
1729
|
}
|
|
1526
1730
|
for (var i = 0; i < messages.length; i++) {
|
|
1527
1731
|
var msg = messages[i];
|
|
1528
1732
|
var row = document.createElement('div');
|
|
1529
1733
|
row.className = 'cumulus-msg-row';
|
|
1734
|
+
// Add checkbox in selection mode
|
|
1735
|
+
if (selectionMode && msg.id) {
|
|
1736
|
+
var cb = document.createElement('input');
|
|
1737
|
+
cb.type = 'checkbox';
|
|
1738
|
+
cb.className = 'cumulus-msg-checkbox';
|
|
1739
|
+
cb.checked = !!selectedIds[msg.id];
|
|
1740
|
+
(function (msgId) {
|
|
1741
|
+
cb.addEventListener('change', function () {
|
|
1742
|
+
if (cb.checked) {
|
|
1743
|
+
selectedIds[msgId] = true;
|
|
1744
|
+
} else {
|
|
1745
|
+
delete selectedIds[msgId];
|
|
1746
|
+
}
|
|
1747
|
+
updateEmbeddedSelectionBar();
|
|
1748
|
+
});
|
|
1749
|
+
})(msg.id);
|
|
1750
|
+
row.appendChild(cb);
|
|
1751
|
+
}
|
|
1530
1752
|
if (msg.role === 'user') {
|
|
1531
|
-
|
|
1753
|
+
var agent = parseAgentMessage(msg.content);
|
|
1754
|
+
if (agent) {
|
|
1755
|
+
row.appendChild(buildAgentMsgEl(agent));
|
|
1756
|
+
} else {
|
|
1757
|
+
row.appendChild(buildUserMsgEl(msg));
|
|
1758
|
+
}
|
|
1532
1759
|
} else {
|
|
1533
1760
|
row.appendChild(buildAssistantMsgEl(msg.content, false));
|
|
1534
1761
|
}
|
|
1762
|
+
appendTimestamp(row, msg.timestamp);
|
|
1535
1763
|
messagesEl.appendChild(row);
|
|
1536
1764
|
}
|
|
1537
1765
|
if (streaming) {
|
|
@@ -1541,6 +1769,115 @@
|
|
|
1541
1769
|
messagesEl.appendChild(row);
|
|
1542
1770
|
}
|
|
1543
1771
|
scrollToBottom();
|
|
1772
|
+
updateEmbeddedSelectionBar();
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
function updateEmbeddedSelectionBar() {
|
|
1776
|
+
var existingBar = panel.querySelector('.cumulus-selection-bar');
|
|
1777
|
+
if (existingBar) existingBar.remove();
|
|
1778
|
+
if (!selectionMode) return;
|
|
1779
|
+
|
|
1780
|
+
var selectedCount = Object.keys(selectedIds).length;
|
|
1781
|
+
var selectableCount = messages.filter(function (m) {
|
|
1782
|
+
return !!m.id;
|
|
1783
|
+
}).length;
|
|
1784
|
+
|
|
1785
|
+
var bar = document.createElement('div');
|
|
1786
|
+
bar.className = 'cumulus-selection-bar';
|
|
1787
|
+
|
|
1788
|
+
var countEl = document.createElement('span');
|
|
1789
|
+
countEl.className = 'cumulus-selection-count';
|
|
1790
|
+
countEl.textContent = selectedCount + ' selected';
|
|
1791
|
+
bar.appendChild(countEl);
|
|
1792
|
+
|
|
1793
|
+
var selectAllBtn = document.createElement('button');
|
|
1794
|
+
selectAllBtn.className = 'cumulus-selection-select-all-btn';
|
|
1795
|
+
selectAllBtn.textContent =
|
|
1796
|
+
selectedCount === selectableCount && selectableCount > 0 ? 'Deselect All' : 'Select All';
|
|
1797
|
+
selectAllBtn.addEventListener('click', function () {
|
|
1798
|
+
if (selectedCount === selectableCount && selectableCount > 0) {
|
|
1799
|
+
selectedIds = {};
|
|
1800
|
+
} else {
|
|
1801
|
+
messages.forEach(function (m) {
|
|
1802
|
+
if (m.id) selectedIds[m.id] = true;
|
|
1803
|
+
});
|
|
1804
|
+
}
|
|
1805
|
+
renderMessages();
|
|
1806
|
+
});
|
|
1807
|
+
bar.appendChild(selectAllBtn);
|
|
1808
|
+
|
|
1809
|
+
var deleteBtn = document.createElement('button');
|
|
1810
|
+
deleteBtn.className = 'cumulus-selection-delete-btn';
|
|
1811
|
+
deleteBtn.textContent = 'Delete';
|
|
1812
|
+
deleteBtn.disabled = selectedCount === 0;
|
|
1813
|
+
deleteBtn.addEventListener('click', function () {
|
|
1814
|
+
if (selectedCount === 0) return;
|
|
1815
|
+
showEmbeddedDeleteMessagesConfirm(Object.keys(selectedIds));
|
|
1816
|
+
});
|
|
1817
|
+
bar.appendChild(deleteBtn);
|
|
1818
|
+
|
|
1819
|
+
var inputAreaEl = panel.querySelector('.cumulus-input-area');
|
|
1820
|
+
if (inputAreaEl) {
|
|
1821
|
+
panel.insertBefore(bar, inputAreaEl);
|
|
1822
|
+
} else {
|
|
1823
|
+
panel.appendChild(bar);
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
function showEmbeddedDeleteMessagesConfirm(ids) {
|
|
1828
|
+
var existing = panel.querySelector('.cumulus-confirm-dialog');
|
|
1829
|
+
if (existing) existing.remove();
|
|
1830
|
+
|
|
1831
|
+
var dialog = document.createElement('div');
|
|
1832
|
+
dialog.className = 'cumulus-confirm-dialog';
|
|
1833
|
+
|
|
1834
|
+
var box = document.createElement('div');
|
|
1835
|
+
box.className = 'cumulus-confirm-dialog-box';
|
|
1836
|
+
|
|
1837
|
+
var title = document.createElement('div');
|
|
1838
|
+
title.className = 'cumulus-confirm-dialog-title';
|
|
1839
|
+
title.textContent = 'Delete Messages';
|
|
1840
|
+
box.appendChild(title);
|
|
1841
|
+
|
|
1842
|
+
var text = document.createElement('div');
|
|
1843
|
+
text.className = 'cumulus-confirm-dialog-text';
|
|
1844
|
+
text.textContent =
|
|
1845
|
+
ids.length + ' message' + (ids.length === 1 ? '' : 's') + ' will be permanently deleted.';
|
|
1846
|
+
box.appendChild(text);
|
|
1847
|
+
|
|
1848
|
+
var warn = document.createElement('div');
|
|
1849
|
+
warn.className = 'cumulus-confirm-dialog-warn';
|
|
1850
|
+
warn.textContent = 'This cannot be undone.';
|
|
1851
|
+
box.appendChild(warn);
|
|
1852
|
+
|
|
1853
|
+
var actions = document.createElement('div');
|
|
1854
|
+
actions.className = 'cumulus-confirm-dialog-actions';
|
|
1855
|
+
|
|
1856
|
+
var cancelBtn = document.createElement('button');
|
|
1857
|
+
cancelBtn.className = 'cumulus-confirm-dialog-cancel';
|
|
1858
|
+
cancelBtn.textContent = 'Cancel';
|
|
1859
|
+
cancelBtn.addEventListener('click', function () {
|
|
1860
|
+
dialog.remove();
|
|
1861
|
+
});
|
|
1862
|
+
actions.appendChild(cancelBtn);
|
|
1863
|
+
|
|
1864
|
+
var confirmBtn = document.createElement('button');
|
|
1865
|
+
confirmBtn.className = 'cumulus-confirm-dialog-confirm';
|
|
1866
|
+
confirmBtn.setAttribute('data-testid', 'webchat-delete-messages-confirm');
|
|
1867
|
+
confirmBtn.textContent = 'Delete';
|
|
1868
|
+
confirmBtn.addEventListener('click', function () {
|
|
1869
|
+
confirmBtn.disabled = true;
|
|
1870
|
+
confirmBtn.textContent = 'Deleting\u2026';
|
|
1871
|
+
if (connection) {
|
|
1872
|
+
connection.send({ type: 'delete_messages', threadName: sessionId, messageIds: ids });
|
|
1873
|
+
}
|
|
1874
|
+
dialog.remove();
|
|
1875
|
+
});
|
|
1876
|
+
actions.appendChild(confirmBtn);
|
|
1877
|
+
|
|
1878
|
+
box.appendChild(actions);
|
|
1879
|
+
dialog.appendChild(box);
|
|
1880
|
+
panel.appendChild(dialog);
|
|
1544
1881
|
}
|
|
1545
1882
|
|
|
1546
1883
|
function updateSendBtn() {
|
|
@@ -1629,6 +1966,17 @@
|
|
|
1629
1966
|
function handleServerMessage(data) {
|
|
1630
1967
|
switch (data.type) {
|
|
1631
1968
|
case 'auth_ok':
|
|
1969
|
+
// Reset streaming state on reconnect
|
|
1970
|
+
if (streaming) {
|
|
1971
|
+
if (streamBuffer) {
|
|
1972
|
+
messages.push({ role: 'assistant', content: streamBuffer });
|
|
1973
|
+
streamBuffer = '';
|
|
1974
|
+
}
|
|
1975
|
+
streaming = false;
|
|
1976
|
+
stopRequested = false;
|
|
1977
|
+
if (embeddedStreamTimer) { clearTimeout(embeddedStreamTimer); embeddedStreamTimer = null; }
|
|
1978
|
+
updateSendBtn();
|
|
1979
|
+
}
|
|
1632
1980
|
showChatView();
|
|
1633
1981
|
break;
|
|
1634
1982
|
case 'auth_error':
|
|
@@ -1637,7 +1985,7 @@
|
|
|
1637
1985
|
case 'history':
|
|
1638
1986
|
if (data.messages && data.messages.length > 0) {
|
|
1639
1987
|
messages = data.messages.map(function (m) {
|
|
1640
|
-
return { role: m.role, content: m.content };
|
|
1988
|
+
return { id: m.id, role: m.role, content: m.content, timestamp: m.timestamp };
|
|
1641
1989
|
});
|
|
1642
1990
|
renderMessages();
|
|
1643
1991
|
}
|
|
@@ -1650,11 +1998,29 @@
|
|
|
1650
1998
|
updateSendBtn();
|
|
1651
1999
|
}
|
|
1652
2000
|
streamBuffer += data.text;
|
|
2001
|
+
// Reset streaming timeout on each token
|
|
2002
|
+
if (embeddedStreamTimer) clearTimeout(embeddedStreamTimer);
|
|
2003
|
+
embeddedStreamTimer = setTimeout(function () {
|
|
2004
|
+
if (streaming) {
|
|
2005
|
+
console.warn('[Cumulus] Streaming timeout — resetting');
|
|
2006
|
+
if (streamBuffer) {
|
|
2007
|
+
messages.push({ role: 'assistant', content: streamBuffer + '\n\n*(response may be incomplete)*' });
|
|
2008
|
+
}
|
|
2009
|
+
streaming = false;
|
|
2010
|
+
streamBuffer = '';
|
|
2011
|
+
updateSendBtn();
|
|
2012
|
+
renderMessages();
|
|
2013
|
+
// Reload history
|
|
2014
|
+
if (connection) connection.send({ type: 'history', threadName: sessionId, limit: 50 });
|
|
2015
|
+
}
|
|
2016
|
+
embeddedStreamTimer = null;
|
|
2017
|
+
}, EMBEDDED_STREAM_TIMEOUT);
|
|
1653
2018
|
renderMessages();
|
|
1654
2019
|
break;
|
|
1655
2020
|
case 'segment':
|
|
1656
2021
|
break;
|
|
1657
2022
|
case 'done':
|
|
2023
|
+
if (embeddedStreamTimer) { clearTimeout(embeddedStreamTimer); embeddedStreamTimer = null; }
|
|
1658
2024
|
if (stopRequested) {
|
|
1659
2025
|
stopRequested = false;
|
|
1660
2026
|
streaming = false;
|
|
@@ -1674,8 +2040,10 @@
|
|
|
1674
2040
|
break;
|
|
1675
2041
|
case 'interjected':
|
|
1676
2042
|
// Server confirms old stream was aborted. Partial already frozen client-side.
|
|
2043
|
+
if (embeddedStreamTimer) { clearTimeout(embeddedStreamTimer); embeddedStreamTimer = null; }
|
|
1677
2044
|
break;
|
|
1678
2045
|
case 'error':
|
|
2046
|
+
if (embeddedStreamTimer) { clearTimeout(embeddedStreamTimer); embeddedStreamTimer = null; }
|
|
1679
2047
|
stopRequested = false;
|
|
1680
2048
|
streaming = false;
|
|
1681
2049
|
if (streamBuffer) {
|
|
@@ -1693,6 +2061,30 @@
|
|
|
1693
2061
|
updateSendBtn();
|
|
1694
2062
|
renderMessages();
|
|
1695
2063
|
break;
|
|
2064
|
+
case 'new_messages':
|
|
2065
|
+
// Agent inject completed — append new messages and re-render
|
|
2066
|
+
if (data.messages && data.messages.length > 0) {
|
|
2067
|
+
for (var nm = 0; nm < data.messages.length; nm++) {
|
|
2068
|
+
messages.push({ role: data.messages[nm].role, content: data.messages[nm].content });
|
|
2069
|
+
}
|
|
2070
|
+
renderMessages();
|
|
2071
|
+
}
|
|
2072
|
+
break;
|
|
2073
|
+
case 'messages_deleted':
|
|
2074
|
+
// Server confirmed deletion — reload history and exit selection mode
|
|
2075
|
+
selectionMode = false;
|
|
2076
|
+
selectedIds = {};
|
|
2077
|
+
var embSelectBtn = panel.querySelector('[data-testid="webchat-select-btn"]');
|
|
2078
|
+
if (embSelectBtn) {
|
|
2079
|
+
embSelectBtn.textContent = 'Select';
|
|
2080
|
+
embSelectBtn.classList.remove('active');
|
|
2081
|
+
}
|
|
2082
|
+
messages = [];
|
|
2083
|
+
// Re-request history
|
|
2084
|
+
if (connection) {
|
|
2085
|
+
connection.send({ type: 'history', threadName: sessionId, limit: 200 });
|
|
2086
|
+
}
|
|
2087
|
+
break;
|
|
1696
2088
|
}
|
|
1697
2089
|
}
|
|
1698
2090
|
|
|
@@ -1808,6 +2200,22 @@
|
|
|
1808
2200
|
});
|
|
1809
2201
|
}
|
|
1810
2202
|
|
|
2203
|
+
// Select button for embedded mode
|
|
2204
|
+
var embSelectBtn = panel.querySelector('[data-testid="webchat-select-btn"]');
|
|
2205
|
+
if (embSelectBtn) {
|
|
2206
|
+
embSelectBtn.addEventListener('click', function () {
|
|
2207
|
+
selectionMode = !selectionMode;
|
|
2208
|
+
selectedIds = {};
|
|
2209
|
+
embSelectBtn.textContent = selectionMode ? 'Done' : 'Select';
|
|
2210
|
+
if (selectionMode) {
|
|
2211
|
+
embSelectBtn.classList.add('active');
|
|
2212
|
+
} else {
|
|
2213
|
+
embSelectBtn.classList.remove('active');
|
|
2214
|
+
}
|
|
2215
|
+
renderMessages();
|
|
2216
|
+
});
|
|
2217
|
+
}
|
|
2218
|
+
|
|
1811
2219
|
// Embedded mode: no auth panel handling needed (API key from attribute)
|
|
1812
2220
|
function startConnection() {
|
|
1813
2221
|
if (connection) {
|
|
@@ -1847,10 +2255,46 @@
|
|
|
1847
2255
|
|
|
1848
2256
|
// Thread registry: threadName -> { messages, streaming, streamBuffer, stopRequested, pendingAttachments }
|
|
1849
2257
|
var threadStates = {};
|
|
2258
|
+
var streamingTimeouts = {}; // threadName -> timeout ID
|
|
2259
|
+
var STREAMING_TIMEOUT_MS = 120000; // 2 minutes with no token = assume dead
|
|
2260
|
+
|
|
2261
|
+
function resetStreamingTimeout(threadName) {
|
|
2262
|
+
if (streamingTimeouts[threadName]) clearTimeout(streamingTimeouts[threadName]);
|
|
2263
|
+
streamingTimeouts[threadName] = setTimeout(function () {
|
|
2264
|
+
var s = threadStates[threadName];
|
|
2265
|
+
if (s && s.streaming) {
|
|
2266
|
+
console.warn('[CumulusChat] Streaming timeout for thread:', threadName);
|
|
2267
|
+
if (s.streamBuffer) {
|
|
2268
|
+
s.messages.push({ role: 'assistant', content: s.streamBuffer + '\n\n*(response may be incomplete — connection issue)*' });
|
|
2269
|
+
}
|
|
2270
|
+
s.streaming = false;
|
|
2271
|
+
s.streamBuffer = '';
|
|
2272
|
+
s.stopRequested = false;
|
|
2273
|
+
s.interjecting = false;
|
|
2274
|
+
// Reload history to get the real response if server completed
|
|
2275
|
+
s.historyLoaded = false;
|
|
2276
|
+
if (connection) {
|
|
2277
|
+
connection.send({ type: 'history', threadName: threadName, limit: 50 });
|
|
2278
|
+
}
|
|
2279
|
+
refreshThreadPanel(threadName);
|
|
2280
|
+
}
|
|
2281
|
+
delete streamingTimeouts[threadName];
|
|
2282
|
+
}, STREAMING_TIMEOUT_MS);
|
|
2283
|
+
}
|
|
2284
|
+
|
|
2285
|
+
function clearStreamingTimeout(threadName) {
|
|
2286
|
+
if (streamingTimeouts[threadName]) {
|
|
2287
|
+
clearTimeout(streamingTimeouts[threadName]);
|
|
2288
|
+
delete streamingTimeouts[threadName];
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
1850
2291
|
|
|
1851
2292
|
// Thread list from server: [{ name, messageCount, lastActivity }]
|
|
1852
2293
|
var allThreads = [];
|
|
1853
2294
|
|
|
2295
|
+
// Unread threads: threadName -> true
|
|
2296
|
+
var unreadThreads = {};
|
|
2297
|
+
|
|
1854
2298
|
// Currently visible panel names (ordered, max 3)
|
|
1855
2299
|
var visibleThreads = [];
|
|
1856
2300
|
|
|
@@ -1868,6 +2312,8 @@
|
|
|
1868
2312
|
interjecting: false,
|
|
1869
2313
|
pendingAttachments: [],
|
|
1870
2314
|
historyLoaded: false,
|
|
2315
|
+
selectionMode: false,
|
|
2316
|
+
selectedIds: {},
|
|
1871
2317
|
};
|
|
1872
2318
|
}
|
|
1873
2319
|
return threadStates[threadName];
|
|
@@ -2132,7 +2578,9 @@
|
|
|
2132
2578
|
item.setAttribute('data-thread-name', name);
|
|
2133
2579
|
|
|
2134
2580
|
var dot = document.createElement('span');
|
|
2135
|
-
dot.className =
|
|
2581
|
+
dot.className = unreadThreads[name]
|
|
2582
|
+
? 'cumulus-thread-unread'
|
|
2583
|
+
: 'cumulus-thread-dot ' + (isActive ? 'active' : 'inactive');
|
|
2136
2584
|
item.appendChild(dot);
|
|
2137
2585
|
|
|
2138
2586
|
var nameEl = document.createElement('span');
|
|
@@ -2155,6 +2603,13 @@
|
|
|
2155
2603
|
item.appendChild(countEl);
|
|
2156
2604
|
}
|
|
2157
2605
|
|
|
2606
|
+
// Right-click context menu
|
|
2607
|
+
item.addEventListener('contextmenu', function (e) {
|
|
2608
|
+
e.preventDefault();
|
|
2609
|
+
e.stopPropagation();
|
|
2610
|
+
showThreadContextMenu(name, e.clientX, e.clientY);
|
|
2611
|
+
});
|
|
2612
|
+
|
|
2158
2613
|
item.addEventListener('click', function (e) {
|
|
2159
2614
|
var multiSelect = e.metaKey || e.ctrlKey;
|
|
2160
2615
|
if (multiSelect) {
|
|
@@ -2167,8 +2622,123 @@
|
|
|
2167
2622
|
return item;
|
|
2168
2623
|
}
|
|
2169
2624
|
|
|
2625
|
+
// ── Thread context menu (right-click) ──
|
|
2626
|
+
function dismissContextMenu() {
|
|
2627
|
+
var existing = document.querySelector('.cumulus-context-menu');
|
|
2628
|
+
if (existing) existing.remove();
|
|
2629
|
+
document.removeEventListener('click', dismissContextMenu);
|
|
2630
|
+
document.removeEventListener('contextmenu', dismissContextMenu);
|
|
2631
|
+
}
|
|
2632
|
+
|
|
2633
|
+
function showThreadContextMenu(threadName, x, y) {
|
|
2634
|
+
dismissContextMenu();
|
|
2635
|
+
|
|
2636
|
+
var menu = document.createElement('div');
|
|
2637
|
+
menu.className = 'cumulus-context-menu';
|
|
2638
|
+
menu.style.left = x + 'px';
|
|
2639
|
+
menu.style.top = y + 'px';
|
|
2640
|
+
|
|
2641
|
+
var deleteItem = document.createElement('button');
|
|
2642
|
+
deleteItem.className = 'cumulus-context-menu-item danger';
|
|
2643
|
+
deleteItem.textContent = 'Delete thread';
|
|
2644
|
+
deleteItem.setAttribute('data-testid', 'webchat-thread-delete');
|
|
2645
|
+
deleteItem.addEventListener('click', function () {
|
|
2646
|
+
dismissContextMenu();
|
|
2647
|
+
showThreadDeleteConfirm(threadName);
|
|
2648
|
+
});
|
|
2649
|
+
menu.appendChild(deleteItem);
|
|
2650
|
+
|
|
2651
|
+
document.body.appendChild(menu);
|
|
2652
|
+
|
|
2653
|
+
// Clamp to viewport
|
|
2654
|
+
var rect = menu.getBoundingClientRect();
|
|
2655
|
+
if (rect.right > window.innerWidth)
|
|
2656
|
+
menu.style.left = window.innerWidth - rect.width - 4 + 'px';
|
|
2657
|
+
if (rect.bottom > window.innerHeight)
|
|
2658
|
+
menu.style.top = window.innerHeight - rect.height - 4 + 'px';
|
|
2659
|
+
|
|
2660
|
+
// Dismiss on next click or right-click anywhere
|
|
2661
|
+
setTimeout(function () {
|
|
2662
|
+
document.addEventListener('click', dismissContextMenu);
|
|
2663
|
+
document.addEventListener('contextmenu', dismissContextMenu);
|
|
2664
|
+
}, 0);
|
|
2665
|
+
}
|
|
2666
|
+
|
|
2667
|
+
// ── Thread delete confirmation ──
|
|
2668
|
+
function showThreadDeleteConfirm(threadName) {
|
|
2669
|
+
// Remove any existing confirm dialogs
|
|
2670
|
+
var existing = document.querySelector(
|
|
2671
|
+
'.cumulus-confirm-dialog[data-context="thread-delete"]'
|
|
2672
|
+
);
|
|
2673
|
+
if (existing) {
|
|
2674
|
+
existing.remove();
|
|
2675
|
+
return;
|
|
2676
|
+
}
|
|
2677
|
+
|
|
2678
|
+
// Build inline confirm dialog attached to the sidebar scroll area
|
|
2679
|
+
var dialog = document.createElement('div');
|
|
2680
|
+
dialog.className = 'cumulus-confirm-dialog';
|
|
2681
|
+
dialog.setAttribute('data-context', 'thread-delete');
|
|
2682
|
+
dialog.style.cssText =
|
|
2683
|
+
'position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.6); z-index: 200; display: flex; align-items: center; justify-content: center;';
|
|
2684
|
+
|
|
2685
|
+
var box = document.createElement('div');
|
|
2686
|
+
box.className = 'cumulus-confirm-dialog-box';
|
|
2687
|
+
|
|
2688
|
+
var title = document.createElement('div');
|
|
2689
|
+
title.className = 'cumulus-confirm-dialog-title';
|
|
2690
|
+
title.textContent = 'Delete Thread';
|
|
2691
|
+
box.appendChild(title);
|
|
2692
|
+
|
|
2693
|
+
var text = document.createElement('div');
|
|
2694
|
+
text.className = 'cumulus-confirm-dialog-text';
|
|
2695
|
+
text.textContent = 'Delete "' + threadName + '"?';
|
|
2696
|
+
box.appendChild(text);
|
|
2697
|
+
|
|
2698
|
+
var warn = document.createElement('div');
|
|
2699
|
+
warn.className = 'cumulus-confirm-dialog-warn';
|
|
2700
|
+
warn.textContent = 'All messages and history will be permanently removed.';
|
|
2701
|
+
box.appendChild(warn);
|
|
2702
|
+
|
|
2703
|
+
var actions = document.createElement('div');
|
|
2704
|
+
actions.className = 'cumulus-confirm-dialog-actions';
|
|
2705
|
+
|
|
2706
|
+
var cancelBtn = document.createElement('button');
|
|
2707
|
+
cancelBtn.className = 'cumulus-confirm-dialog-cancel';
|
|
2708
|
+
cancelBtn.textContent = 'Cancel';
|
|
2709
|
+
cancelBtn.addEventListener('click', function () {
|
|
2710
|
+
dialog.remove();
|
|
2711
|
+
});
|
|
2712
|
+
actions.appendChild(cancelBtn);
|
|
2713
|
+
|
|
2714
|
+
var confirmBtn = document.createElement('button');
|
|
2715
|
+
confirmBtn.className = 'cumulus-confirm-dialog-confirm';
|
|
2716
|
+
confirmBtn.setAttribute('data-testid', 'webchat-thread-delete-confirm');
|
|
2717
|
+
confirmBtn.textContent = 'Delete';
|
|
2718
|
+
confirmBtn.addEventListener('click', function () {
|
|
2719
|
+
confirmBtn.disabled = true;
|
|
2720
|
+
confirmBtn.textContent = 'Deleting\u2026';
|
|
2721
|
+
if (connection) {
|
|
2722
|
+
connection.send({ type: 'delete_thread', threadName: threadName });
|
|
2723
|
+
}
|
|
2724
|
+
dialog.remove();
|
|
2725
|
+
});
|
|
2726
|
+
actions.appendChild(confirmBtn);
|
|
2727
|
+
|
|
2728
|
+
box.appendChild(actions);
|
|
2729
|
+
dialog.appendChild(box);
|
|
2730
|
+
|
|
2731
|
+
// Dismiss on backdrop click
|
|
2732
|
+
dialog.addEventListener('click', function (e) {
|
|
2733
|
+
if (e.target === dialog) dialog.remove();
|
|
2734
|
+
});
|
|
2735
|
+
|
|
2736
|
+
document.body.appendChild(dialog);
|
|
2737
|
+
}
|
|
2738
|
+
|
|
2170
2739
|
// ── Panel management ──
|
|
2171
2740
|
function soloThread(name) {
|
|
2741
|
+
delete unreadThreads[name];
|
|
2172
2742
|
visibleThreads = [name];
|
|
2173
2743
|
renderSidebar();
|
|
2174
2744
|
renderContentArea();
|
|
@@ -2177,6 +2747,7 @@
|
|
|
2177
2747
|
}
|
|
2178
2748
|
|
|
2179
2749
|
function togglePanelThread(name) {
|
|
2750
|
+
delete unreadThreads[name];
|
|
2180
2751
|
// On mobile, always solo (no side-by-side)
|
|
2181
2752
|
if (isMobile()) {
|
|
2182
2753
|
soloThread(name);
|
|
@@ -2307,6 +2878,16 @@
|
|
|
2307
2878
|
});
|
|
2308
2879
|
actions.appendChild(revertBtn);
|
|
2309
2880
|
|
|
2881
|
+
var selectBtn = document.createElement('button');
|
|
2882
|
+
selectBtn.className = 'cumulus-panel-action-btn';
|
|
2883
|
+
selectBtn.setAttribute('data-testid', 'webchat-select-btn');
|
|
2884
|
+
selectBtn.setAttribute('title', 'Select messages to delete');
|
|
2885
|
+
selectBtn.textContent = 'Select';
|
|
2886
|
+
selectBtn.addEventListener('click', function () {
|
|
2887
|
+
toggleSelectionMode();
|
|
2888
|
+
});
|
|
2889
|
+
actions.appendChild(selectBtn);
|
|
2890
|
+
|
|
2310
2891
|
panelHeader.appendChild(actions);
|
|
2311
2892
|
|
|
2312
2893
|
var closeBtn = document.createElement('button');
|
|
@@ -2375,7 +2956,11 @@
|
|
|
2375
2956
|
|
|
2376
2957
|
// ── Panel-local render functions ──
|
|
2377
2958
|
function scrollToBottom() {
|
|
2378
|
-
|
|
2959
|
+
requestAnimationFrame(function () {
|
|
2960
|
+
requestAnimationFrame(function () {
|
|
2961
|
+
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
2962
|
+
});
|
|
2963
|
+
});
|
|
2379
2964
|
}
|
|
2380
2965
|
|
|
2381
2966
|
function isWideUserMessage(content) {
|
|
@@ -2444,20 +3029,51 @@
|
|
|
2444
3029
|
|
|
2445
3030
|
function renderPanelMessages() {
|
|
2446
3031
|
messagesEl.innerHTML = '';
|
|
3032
|
+
// Apply selection mode class to messages container
|
|
3033
|
+
if (state.selectionMode) {
|
|
3034
|
+
messagesEl.classList.add('cumulus-selection-mode');
|
|
3035
|
+
} else {
|
|
3036
|
+
messagesEl.classList.remove('cumulus-selection-mode');
|
|
3037
|
+
}
|
|
2447
3038
|
if (state.messages.length === 0 && !state.streaming) {
|
|
2448
3039
|
messagesEl.innerHTML =
|
|
2449
3040
|
'<div class="cumulus-empty">Send a message to start chatting</div>';
|
|
3041
|
+
updateSelectionBar();
|
|
2450
3042
|
return;
|
|
2451
3043
|
}
|
|
2452
3044
|
for (var i = 0; i < state.messages.length; i++) {
|
|
2453
3045
|
var msg = state.messages[i];
|
|
2454
3046
|
var row = document.createElement('div');
|
|
2455
3047
|
row.className = 'cumulus-msg-row';
|
|
3048
|
+
// Add checkbox if in selection mode and message has an id
|
|
3049
|
+
if (state.selectionMode && msg.id) {
|
|
3050
|
+
var cb = document.createElement('input');
|
|
3051
|
+
cb.type = 'checkbox';
|
|
3052
|
+
cb.className = 'cumulus-msg-checkbox';
|
|
3053
|
+
cb.checked = !!state.selectedIds[msg.id];
|
|
3054
|
+
(function (msgId) {
|
|
3055
|
+
cb.addEventListener('change', function () {
|
|
3056
|
+
if (cb.checked) {
|
|
3057
|
+
state.selectedIds[msgId] = true;
|
|
3058
|
+
} else {
|
|
3059
|
+
delete state.selectedIds[msgId];
|
|
3060
|
+
}
|
|
3061
|
+
updateSelectionBar();
|
|
3062
|
+
});
|
|
3063
|
+
})(msg.id);
|
|
3064
|
+
row.appendChild(cb);
|
|
3065
|
+
}
|
|
2456
3066
|
if (msg.role === 'user') {
|
|
2457
|
-
|
|
3067
|
+
var agent = parseAgentMessage(msg.content);
|
|
3068
|
+
if (agent) {
|
|
3069
|
+
row.appendChild(buildAgentMsgEl(agent));
|
|
3070
|
+
} else {
|
|
3071
|
+
row.appendChild(buildUserMsgEl(msg));
|
|
3072
|
+
}
|
|
2458
3073
|
} else {
|
|
2459
3074
|
row.appendChild(buildAssistantMsgEl(msg.content, false));
|
|
2460
3075
|
}
|
|
3076
|
+
appendTimestamp(row, msg.timestamp);
|
|
2461
3077
|
messagesEl.appendChild(row);
|
|
2462
3078
|
}
|
|
2463
3079
|
if (state.streaming) {
|
|
@@ -2467,6 +3083,132 @@
|
|
|
2467
3083
|
messagesEl.appendChild(row);
|
|
2468
3084
|
}
|
|
2469
3085
|
scrollToBottom();
|
|
3086
|
+
updateSelectionBar();
|
|
3087
|
+
}
|
|
3088
|
+
|
|
3089
|
+
function updateSelectionBar() {
|
|
3090
|
+
// Remove existing bar
|
|
3091
|
+
var existingBar = panel.querySelector('.cumulus-selection-bar');
|
|
3092
|
+
if (existingBar) existingBar.remove();
|
|
3093
|
+
if (!state.selectionMode) return;
|
|
3094
|
+
|
|
3095
|
+
var selectedCount = Object.keys(state.selectedIds).length;
|
|
3096
|
+
var selectableCount = state.messages.filter(function (m) {
|
|
3097
|
+
return !!m.id;
|
|
3098
|
+
}).length;
|
|
3099
|
+
|
|
3100
|
+
var bar = document.createElement('div');
|
|
3101
|
+
bar.className = 'cumulus-selection-bar';
|
|
3102
|
+
|
|
3103
|
+
var countEl = document.createElement('span');
|
|
3104
|
+
countEl.className = 'cumulus-selection-count';
|
|
3105
|
+
countEl.textContent = selectedCount + ' selected';
|
|
3106
|
+
bar.appendChild(countEl);
|
|
3107
|
+
|
|
3108
|
+
var selectAllBtn = document.createElement('button');
|
|
3109
|
+
selectAllBtn.className = 'cumulus-selection-select-all-btn';
|
|
3110
|
+
selectAllBtn.textContent =
|
|
3111
|
+
selectedCount === selectableCount && selectableCount > 0 ? 'Deselect All' : 'Select All';
|
|
3112
|
+
selectAllBtn.addEventListener('click', function () {
|
|
3113
|
+
if (selectedCount === selectableCount && selectableCount > 0) {
|
|
3114
|
+
// Deselect all
|
|
3115
|
+
state.selectedIds = {};
|
|
3116
|
+
} else {
|
|
3117
|
+
// Select all with IDs
|
|
3118
|
+
state.messages.forEach(function (m) {
|
|
3119
|
+
if (m.id) state.selectedIds[m.id] = true;
|
|
3120
|
+
});
|
|
3121
|
+
}
|
|
3122
|
+
renderPanelMessages();
|
|
3123
|
+
});
|
|
3124
|
+
bar.appendChild(selectAllBtn);
|
|
3125
|
+
|
|
3126
|
+
var deleteBtn = document.createElement('button');
|
|
3127
|
+
deleteBtn.className = 'cumulus-selection-delete-btn';
|
|
3128
|
+
deleteBtn.textContent = 'Delete';
|
|
3129
|
+
deleteBtn.disabled = selectedCount === 0;
|
|
3130
|
+
deleteBtn.addEventListener('click', function () {
|
|
3131
|
+
if (selectedCount === 0) return;
|
|
3132
|
+
showDeleteMessagesConfirm(threadName, panel, Object.keys(state.selectedIds));
|
|
3133
|
+
});
|
|
3134
|
+
bar.appendChild(deleteBtn);
|
|
3135
|
+
|
|
3136
|
+
// Insert before input area
|
|
3137
|
+
var inputAreaEl = panel.querySelector('.cumulus-input-area');
|
|
3138
|
+
if (inputAreaEl) {
|
|
3139
|
+
panel.insertBefore(bar, inputAreaEl);
|
|
3140
|
+
} else {
|
|
3141
|
+
panel.appendChild(bar);
|
|
3142
|
+
}
|
|
3143
|
+
}
|
|
3144
|
+
|
|
3145
|
+
function toggleSelectionMode() {
|
|
3146
|
+
state.selectionMode = !state.selectionMode;
|
|
3147
|
+
state.selectedIds = {};
|
|
3148
|
+
selectBtn.textContent = state.selectionMode ? 'Done' : 'Select';
|
|
3149
|
+
if (state.selectionMode) {
|
|
3150
|
+
selectBtn.classList.add('active');
|
|
3151
|
+
} else {
|
|
3152
|
+
selectBtn.classList.remove('active');
|
|
3153
|
+
}
|
|
3154
|
+
renderPanelMessages();
|
|
3155
|
+
}
|
|
3156
|
+
|
|
3157
|
+
function showDeleteMessagesConfirm(tName, panelEl, ids) {
|
|
3158
|
+
// Remove any existing confirm dialog
|
|
3159
|
+
var existing = panelEl.querySelector('.cumulus-confirm-dialog');
|
|
3160
|
+
if (existing) existing.remove();
|
|
3161
|
+
|
|
3162
|
+
var dialog = document.createElement('div');
|
|
3163
|
+
dialog.className = 'cumulus-confirm-dialog';
|
|
3164
|
+
|
|
3165
|
+
var box = document.createElement('div');
|
|
3166
|
+
box.className = 'cumulus-confirm-dialog-box';
|
|
3167
|
+
|
|
3168
|
+
var title = document.createElement('div');
|
|
3169
|
+
title.className = 'cumulus-confirm-dialog-title';
|
|
3170
|
+
title.textContent = 'Delete Messages';
|
|
3171
|
+
box.appendChild(title);
|
|
3172
|
+
|
|
3173
|
+
var text = document.createElement('div');
|
|
3174
|
+
text.className = 'cumulus-confirm-dialog-text';
|
|
3175
|
+
text.textContent =
|
|
3176
|
+
ids.length + ' message' + (ids.length === 1 ? '' : 's') + ' will be permanently deleted.';
|
|
3177
|
+
box.appendChild(text);
|
|
3178
|
+
|
|
3179
|
+
var warn = document.createElement('div');
|
|
3180
|
+
warn.className = 'cumulus-confirm-dialog-warn';
|
|
3181
|
+
warn.textContent = 'This cannot be undone.';
|
|
3182
|
+
box.appendChild(warn);
|
|
3183
|
+
|
|
3184
|
+
var actions = document.createElement('div');
|
|
3185
|
+
actions.className = 'cumulus-confirm-dialog-actions';
|
|
3186
|
+
|
|
3187
|
+
var cancelBtn = document.createElement('button');
|
|
3188
|
+
cancelBtn.className = 'cumulus-confirm-dialog-cancel';
|
|
3189
|
+
cancelBtn.textContent = 'Cancel';
|
|
3190
|
+
cancelBtn.addEventListener('click', function () {
|
|
3191
|
+
dialog.remove();
|
|
3192
|
+
});
|
|
3193
|
+
actions.appendChild(cancelBtn);
|
|
3194
|
+
|
|
3195
|
+
var confirmBtn = document.createElement('button');
|
|
3196
|
+
confirmBtn.className = 'cumulus-confirm-dialog-confirm';
|
|
3197
|
+
confirmBtn.setAttribute('data-testid', 'webchat-delete-messages-confirm');
|
|
3198
|
+
confirmBtn.textContent = 'Delete';
|
|
3199
|
+
confirmBtn.addEventListener('click', function () {
|
|
3200
|
+
confirmBtn.disabled = true;
|
|
3201
|
+
confirmBtn.textContent = 'Deleting\u2026';
|
|
3202
|
+
if (connection) {
|
|
3203
|
+
connection.send({ type: 'delete_messages', threadName: tName, messageIds: ids });
|
|
3204
|
+
}
|
|
3205
|
+
dialog.remove();
|
|
3206
|
+
});
|
|
3207
|
+
actions.appendChild(confirmBtn);
|
|
3208
|
+
|
|
3209
|
+
box.appendChild(actions);
|
|
3210
|
+
dialog.appendChild(box);
|
|
3211
|
+
panelEl.appendChild(dialog);
|
|
2470
3212
|
}
|
|
2471
3213
|
|
|
2472
3214
|
function updatePanelSendBtn() {
|
|
@@ -2557,6 +3299,9 @@
|
|
|
2557
3299
|
state._renderMessages = renderPanelMessages;
|
|
2558
3300
|
state._updateSendBtn = updatePanelSendBtn;
|
|
2559
3301
|
|
|
3302
|
+
// Initial render (messages may already be loaded from a previous view)
|
|
3303
|
+
renderPanelMessages();
|
|
3304
|
+
|
|
2560
3305
|
// ── Send message for this panel ──
|
|
2561
3306
|
function sendPanelMessage() {
|
|
2562
3307
|
var text = inputEl.value.trim();
|
|
@@ -2568,6 +3313,7 @@
|
|
|
2568
3313
|
if (!text && state.pendingAttachments.length === 0) {
|
|
2569
3314
|
state.stopRequested = true;
|
|
2570
3315
|
state.streaming = false;
|
|
3316
|
+
clearStreamingTimeout(threadName);
|
|
2571
3317
|
updatePanelSendBtn();
|
|
2572
3318
|
if (state.streamBuffer) {
|
|
2573
3319
|
state.messages.push({ role: 'assistant', content: state.streamBuffer });
|
|
@@ -2601,6 +3347,7 @@
|
|
|
2601
3347
|
state.stopRequested = false;
|
|
2602
3348
|
state.streamBuffer = '';
|
|
2603
3349
|
state.interjecting = false;
|
|
3350
|
+
resetStreamingTimeout(threadName);
|
|
2604
3351
|
updatePanelSendBtn();
|
|
2605
3352
|
renderPanelMessages();
|
|
2606
3353
|
|
|
@@ -2749,14 +3496,12 @@
|
|
|
2749
3496
|
var filePath = addInput.value.trim();
|
|
2750
3497
|
if (!filePath) return;
|
|
2751
3498
|
connection &&
|
|
2752
|
-
connection.send(
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
})
|
|
2759
|
-
);
|
|
3499
|
+
connection.send({
|
|
3500
|
+
type: 'include_add',
|
|
3501
|
+
threadName: threadName,
|
|
3502
|
+
filePath: filePath,
|
|
3503
|
+
scope: currentScope,
|
|
3504
|
+
});
|
|
2760
3505
|
addInput.value = '';
|
|
2761
3506
|
});
|
|
2762
3507
|
|
|
@@ -2779,8 +3524,7 @@
|
|
|
2779
3524
|
panel.appendChild(overlay);
|
|
2780
3525
|
|
|
2781
3526
|
// Request the list
|
|
2782
|
-
connection &&
|
|
2783
|
-
connection.send(JSON.stringify({ type: 'include_list', threadName: threadName }));
|
|
3527
|
+
connection && connection.send({ type: 'include_list', threadName: threadName });
|
|
2784
3528
|
}
|
|
2785
3529
|
|
|
2786
3530
|
function renderIncludeList(panel, threadName, files) {
|
|
@@ -2817,13 +3561,11 @@
|
|
|
2817
3561
|
removeBtn.setAttribute('title', 'Remove');
|
|
2818
3562
|
removeBtn.addEventListener('click', function () {
|
|
2819
3563
|
connection &&
|
|
2820
|
-
connection.send(
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
})
|
|
2826
|
-
);
|
|
3564
|
+
connection.send({
|
|
3565
|
+
type: 'include_remove',
|
|
3566
|
+
threadName: threadName,
|
|
3567
|
+
filePath: f.path,
|
|
3568
|
+
});
|
|
2827
3569
|
});
|
|
2828
3570
|
item.appendChild(removeBtn);
|
|
2829
3571
|
|
|
@@ -2868,8 +3610,7 @@
|
|
|
2868
3610
|
panel.appendChild(overlay);
|
|
2869
3611
|
|
|
2870
3612
|
// Request the turn list
|
|
2871
|
-
connection &&
|
|
2872
|
-
connection.send(JSON.stringify({ type: 'revert_list', threadName: threadName }));
|
|
3613
|
+
connection && connection.send({ type: 'revert_list', threadName: threadName });
|
|
2873
3614
|
}
|
|
2874
3615
|
|
|
2875
3616
|
function renderRevertList(panel, threadName, turns) {
|
|
@@ -2948,8 +3689,7 @@
|
|
|
2948
3689
|
cancelBtn.textContent = 'Cancel';
|
|
2949
3690
|
cancelBtn.addEventListener('click', function () {
|
|
2950
3691
|
// Re-request list
|
|
2951
|
-
connection &&
|
|
2952
|
-
connection.send(JSON.stringify({ type: 'revert_list', threadName: threadName }));
|
|
3692
|
+
connection && connection.send({ type: 'revert_list', threadName: threadName });
|
|
2953
3693
|
});
|
|
2954
3694
|
actions.appendChild(cancelBtn);
|
|
2955
3695
|
|
|
@@ -2961,13 +3701,11 @@
|
|
|
2961
3701
|
revertBtn.disabled = true;
|
|
2962
3702
|
revertBtn.textContent = 'Reverting\u2026';
|
|
2963
3703
|
connection &&
|
|
2964
|
-
connection.send(
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
})
|
|
2970
|
-
);
|
|
3704
|
+
connection.send({
|
|
3705
|
+
type: 'revert_execute',
|
|
3706
|
+
threadName: threadName,
|
|
3707
|
+
targetMessageId: turn.userMessageId,
|
|
3708
|
+
});
|
|
2971
3709
|
});
|
|
2972
3710
|
actions.appendChild(revertBtn);
|
|
2973
3711
|
|
|
@@ -3072,8 +3810,26 @@
|
|
|
3072
3810
|
|
|
3073
3811
|
// ── Message router ──
|
|
3074
3812
|
function handleServerMessage(data) {
|
|
3813
|
+
if (data.type !== 'token') {
|
|
3814
|
+
console.log('[CumulusChat] WS recv:', data.type, data.threadName || '', data);
|
|
3815
|
+
}
|
|
3075
3816
|
switch (data.type) {
|
|
3076
3817
|
case 'auth_ok':
|
|
3818
|
+
// Reset streaming state on reconnect — server may have finished while we were disconnected
|
|
3819
|
+
Object.keys(threadStates).forEach(function (tn) {
|
|
3820
|
+
var s = threadStates[tn];
|
|
3821
|
+
if (s.streaming) {
|
|
3822
|
+
if (s.streamBuffer) {
|
|
3823
|
+
s.messages.push({ role: 'assistant', content: s.streamBuffer });
|
|
3824
|
+
s.streamBuffer = '';
|
|
3825
|
+
}
|
|
3826
|
+
s.streaming = false;
|
|
3827
|
+
s.stopRequested = false;
|
|
3828
|
+
s.interjecting = false;
|
|
3829
|
+
}
|
|
3830
|
+
// Reload history from server to pick up any responses we missed
|
|
3831
|
+
s.historyLoaded = false;
|
|
3832
|
+
});
|
|
3077
3833
|
showAppView();
|
|
3078
3834
|
registerPushNotifications();
|
|
3079
3835
|
break;
|
|
@@ -3117,12 +3873,50 @@
|
|
|
3117
3873
|
}
|
|
3118
3874
|
break;
|
|
3119
3875
|
|
|
3876
|
+
case 'thread_deleted':
|
|
3877
|
+
// Server confirms thread deleted; remove from sidebar and panels
|
|
3878
|
+
if (data.threadName) {
|
|
3879
|
+
allThreads = allThreads.filter(function (t) {
|
|
3880
|
+
return t.name !== data.threadName;
|
|
3881
|
+
});
|
|
3882
|
+
delete threadStates[data.threadName];
|
|
3883
|
+
removeThreadFromView(data.threadName);
|
|
3884
|
+
renderSidebar();
|
|
3885
|
+
}
|
|
3886
|
+
break;
|
|
3887
|
+
|
|
3888
|
+
case 'messages_deleted':
|
|
3889
|
+
// Server confirms messages deleted; reload history for that thread
|
|
3890
|
+
if (data.threadName) {
|
|
3891
|
+
var dState = getThreadState(data.threadName);
|
|
3892
|
+
dState.messages = [];
|
|
3893
|
+
dState.historyLoaded = false;
|
|
3894
|
+
dState.selectionMode = false;
|
|
3895
|
+
dState.selectedIds = {};
|
|
3896
|
+
// Exit selection mode on visible panel
|
|
3897
|
+
var dPanel = document.querySelector('[data-thread-panel="' + data.threadName + '"]');
|
|
3898
|
+
if (dPanel) {
|
|
3899
|
+
var dMsgs = dPanel.querySelector('.cumulus-messages');
|
|
3900
|
+
if (dMsgs) dMsgs.classList.remove('cumulus-selection-mode');
|
|
3901
|
+
var dBar = dPanel.querySelector('.cumulus-selection-bar');
|
|
3902
|
+
if (dBar) dBar.remove();
|
|
3903
|
+
var dSelectBtn = dPanel.querySelector('[data-testid="webchat-select-btn"]');
|
|
3904
|
+
if (dSelectBtn) {
|
|
3905
|
+
dSelectBtn.textContent = 'Select';
|
|
3906
|
+
dSelectBtn.classList.remove('active');
|
|
3907
|
+
}
|
|
3908
|
+
}
|
|
3909
|
+
connection &&
|
|
3910
|
+
connection.send({ type: 'history', threadName: data.threadName, limit: 200 });
|
|
3911
|
+
}
|
|
3912
|
+
break;
|
|
3913
|
+
|
|
3120
3914
|
case 'history':
|
|
3121
3915
|
if (data.threadName) {
|
|
3122
3916
|
var state = getThreadState(data.threadName);
|
|
3123
3917
|
if (data.messages && data.messages.length > 0) {
|
|
3124
3918
|
state.messages = data.messages.map(function (m) {
|
|
3125
|
-
return { id: m.id, role: m.role, content: m.content };
|
|
3919
|
+
return { id: m.id, role: m.role, content: m.content, timestamp: m.timestamp };
|
|
3126
3920
|
});
|
|
3127
3921
|
}
|
|
3128
3922
|
refreshThreadPanel(data.threadName);
|
|
@@ -3138,6 +3932,8 @@
|
|
|
3138
3932
|
state.streamBuffer = '';
|
|
3139
3933
|
}
|
|
3140
3934
|
state.streamBuffer += data.text;
|
|
3935
|
+
// Reset streaming timeout on each token
|
|
3936
|
+
resetStreamingTimeout(data.threadName);
|
|
3141
3937
|
// Update lastActivity for this thread in allThreads
|
|
3142
3938
|
updateThreadActivity(data.threadName);
|
|
3143
3939
|
refreshThreadPanel(data.threadName);
|
|
@@ -3150,6 +3946,7 @@
|
|
|
3150
3946
|
|
|
3151
3947
|
case 'done':
|
|
3152
3948
|
if (data.threadName) {
|
|
3949
|
+
clearStreamingTimeout(data.threadName);
|
|
3153
3950
|
var state = getThreadState(data.threadName);
|
|
3154
3951
|
if (state.stopRequested) {
|
|
3155
3952
|
state.stopRequested = false;
|
|
@@ -3176,11 +3973,12 @@
|
|
|
3176
3973
|
case 'interjected':
|
|
3177
3974
|
// Server confirms the old stream was aborted due to interjection.
|
|
3178
3975
|
// The partial response was already frozen client-side in sendPanelMessage.
|
|
3179
|
-
|
|
3976
|
+
if (data.threadName) clearStreamingTimeout(data.threadName);
|
|
3180
3977
|
break;
|
|
3181
3978
|
|
|
3182
3979
|
case 'error':
|
|
3183
3980
|
if (data.threadName) {
|
|
3981
|
+
clearStreamingTimeout(data.threadName);
|
|
3184
3982
|
var state = getThreadState(data.threadName);
|
|
3185
3983
|
state.stopRequested = false;
|
|
3186
3984
|
state.streaming = false;
|
|
@@ -3201,6 +3999,22 @@
|
|
|
3201
3999
|
}
|
|
3202
4000
|
break;
|
|
3203
4001
|
|
|
4002
|
+
case 'new_messages':
|
|
4003
|
+
// Agent inject completed — append new messages to the thread and re-render
|
|
4004
|
+
if (data.threadName && data.messages && data.messages.length > 0) {
|
|
4005
|
+
var state = getThreadState(data.threadName);
|
|
4006
|
+
for (var nm = 0; nm < data.messages.length; nm++) {
|
|
4007
|
+
state.messages.push({
|
|
4008
|
+
id: data.messages[nm].id,
|
|
4009
|
+
role: data.messages[nm].role,
|
|
4010
|
+
content: data.messages[nm].content,
|
|
4011
|
+
});
|
|
4012
|
+
}
|
|
4013
|
+
updateThreadActivity(data.threadName);
|
|
4014
|
+
refreshThreadPanel(data.threadName);
|
|
4015
|
+
}
|
|
4016
|
+
break;
|
|
4017
|
+
|
|
3204
4018
|
// ── Include/Revert responses ──
|
|
3205
4019
|
case 'include_list':
|
|
3206
4020
|
if (data.threadName && data.files) {
|
|
@@ -3233,13 +4047,11 @@
|
|
|
3233
4047
|
tState.historyLoaded = false;
|
|
3234
4048
|
// Re-request history
|
|
3235
4049
|
connection &&
|
|
3236
|
-
connection.send(
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
})
|
|
3242
|
-
);
|
|
4050
|
+
connection.send({
|
|
4051
|
+
type: 'history',
|
|
4052
|
+
threadName: data.threadName,
|
|
4053
|
+
limit: 200,
|
|
4054
|
+
});
|
|
3243
4055
|
if (overlay) overlay.remove();
|
|
3244
4056
|
} else {
|
|
3245
4057
|
// Show error in overlay
|
|
@@ -3263,6 +4075,10 @@
|
|
|
3263
4075
|
break;
|
|
3264
4076
|
}
|
|
3265
4077
|
}
|
|
4078
|
+
// Mark unread if thread is not currently visible
|
|
4079
|
+
if (visibleThreads.indexOf(threadName) === -1) {
|
|
4080
|
+
unreadThreads[threadName] = true;
|
|
4081
|
+
}
|
|
3266
4082
|
// Re-render sidebar to reflect new sort order / move thread up
|
|
3267
4083
|
renderSidebar();
|
|
3268
4084
|
}
|
|
@@ -3415,5 +4231,7 @@
|
|
|
3415
4231
|
}
|
|
3416
4232
|
|
|
3417
4233
|
window.CumulusChat = widget;
|
|
4234
|
+
window.CumulusChat.version = '0.27.15';
|
|
4235
|
+
console.log('[CumulusChat] Widget loaded, version ' + window.CumulusChat.version);
|
|
3418
4236
|
}
|
|
3419
4237
|
})();
|