@luckydraw/cumulus 0.27.19 → 0.27.23
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/blex-chart.min.js +23 -0
- package/dist/gateway/static/blex.min.js +105 -0
- package/dist/gateway/static/chat.html +1 -0
- package/dist/gateway/static/widget.js +1055 -46
- 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 +3 -2
|
@@ -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; }',
|
|
@@ -165,6 +198,17 @@
|
|
|
165
198
|
'.cumulus-msg em { font-style: italic; }',
|
|
166
199
|
'.cumulus-msg del { text-decoration: line-through; color: #888; }',
|
|
167
200
|
|
|
201
|
+
/* Blex block containers */
|
|
202
|
+
'.blex-block-container {',
|
|
203
|
+
' margin: 0.6em 0;',
|
|
204
|
+
' border-radius: 8px;',
|
|
205
|
+
' overflow: hidden;',
|
|
206
|
+
' min-height: 2em;',
|
|
207
|
+
'}',
|
|
208
|
+
'.blex-block-container[data-blex-ready="true"] {',
|
|
209
|
+
' min-height: auto;',
|
|
210
|
+
'}',
|
|
211
|
+
|
|
168
212
|
/* Headings */
|
|
169
213
|
'.cumulus-msg h1,.cumulus-msg h2,.cumulus-msg h3,.cumulus-msg h4,.cumulus-msg h5,.cumulus-msg h6 {',
|
|
170
214
|
' font-weight: 600; margin: 0.7em 0 0.3em; line-height: 1.3; color: #e8e8e8;',
|
|
@@ -685,6 +729,97 @@
|
|
|
685
729
|
' border-radius: 3px; font-family: inherit; white-space: nowrap;',
|
|
686
730
|
'}',
|
|
687
731
|
'.cumulus-panel-action-btn:hover { color: #ddd; border-color: #555; }',
|
|
732
|
+
'.cumulus-panel-action-btn.active { color: #e0e0e0; border-color: #0066cc; background: rgba(0,102,204,0.12); }',
|
|
733
|
+
|
|
734
|
+
/* ── Context menu (right-click on thread) ── */
|
|
735
|
+
'.cumulus-context-menu {',
|
|
736
|
+
' position: fixed; z-index: 300; background: #2a2a2a; border: 1px solid #444;',
|
|
737
|
+
' border-radius: 6px; padding: 4px 0; min-width: 140px;',
|
|
738
|
+
' box-shadow: 0 4px 12px rgba(0,0,0,0.5); font-size: 13px;',
|
|
739
|
+
'}',
|
|
740
|
+
'.cumulus-context-menu-item {',
|
|
741
|
+
' padding: 6px 14px; color: #ccc; cursor: pointer; display: block;',
|
|
742
|
+
' width: 100%; border: none; background: none; text-align: left;',
|
|
743
|
+
' font-family: inherit; font-size: inherit;',
|
|
744
|
+
'}',
|
|
745
|
+
'.cumulus-context-menu-item:hover { background: #3a3a3a; color: #fff; }',
|
|
746
|
+
'.cumulus-context-menu-item.danger { color: #ef4444; }',
|
|
747
|
+
'.cumulus-context-menu-item.danger:hover { background: rgba(239,68,68,0.15); color: #ff6b6b; }',
|
|
748
|
+
|
|
749
|
+
/* ── Unread dot (glowing) ── */
|
|
750
|
+
'.cumulus-thread-unread {',
|
|
751
|
+
' width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0;',
|
|
752
|
+
' background: #3b82f6;',
|
|
753
|
+
' box-shadow: 0 0 4px #3b82f6, 0 0 8px rgba(59,130,246,0.4);',
|
|
754
|
+
' animation: cumulus-unread-pulse 2s ease-in-out infinite;',
|
|
755
|
+
'}',
|
|
756
|
+
'@keyframes cumulus-unread-pulse {',
|
|
757
|
+
' 0%, 100% { box-shadow: 0 0 4px #3b82f6, 0 0 8px rgba(59,130,246,0.4); }',
|
|
758
|
+
' 50% { box-shadow: 0 0 6px #3b82f6, 0 0 14px rgba(59,130,246,0.6); }',
|
|
759
|
+
'}',
|
|
760
|
+
|
|
761
|
+
/* ── Message checkbox (selection mode) ── */
|
|
762
|
+
'.cumulus-msg-checkbox {',
|
|
763
|
+
' display: none; flex-shrink: 0; align-self: flex-start; margin-top: 0.4em;',
|
|
764
|
+
' width: 16px; height: 16px; cursor: pointer; accent-color: #0066cc;',
|
|
765
|
+
'}',
|
|
766
|
+
'.cumulus-selection-mode .cumulus-msg-checkbox { display: block; }',
|
|
767
|
+
'.cumulus-selection-mode .cumulus-msg-row {',
|
|
768
|
+
' flex-direction: row; align-items: flex-start; gap: 8px;',
|
|
769
|
+
'}',
|
|
770
|
+
'.cumulus-selection-mode .cumulus-msg-row > *:not(.cumulus-msg-checkbox) { flex: 1; min-width: 0; }',
|
|
771
|
+
|
|
772
|
+
/* ── Selection action bar (floats at bottom of messages area) ── */
|
|
773
|
+
'.cumulus-selection-bar {',
|
|
774
|
+
' display: flex; align-items: center; gap: 8px;',
|
|
775
|
+
' padding: 8px 12px;',
|
|
776
|
+
' background: #2d2d2d; border-top: 1px solid #3a3a3a;',
|
|
777
|
+
' flex-shrink: 0;',
|
|
778
|
+
'}',
|
|
779
|
+
'.cumulus-selection-count { font-size: 13px; color: #aaa; flex: 1; }',
|
|
780
|
+
'.cumulus-selection-select-all-btn {',
|
|
781
|
+
' background: none; border: 1px solid #555; color: #ccc;',
|
|
782
|
+
' padding: 4px 10px; border-radius: 4px; font-size: 12px;',
|
|
783
|
+
' cursor: pointer; font-family: inherit;',
|
|
784
|
+
'}',
|
|
785
|
+
'.cumulus-selection-select-all-btn:hover { border-color: #888; color: #e0e0e0; }',
|
|
786
|
+
'.cumulus-selection-delete-btn {',
|
|
787
|
+
' background: #8b2222; border: none; color: white;',
|
|
788
|
+
' padding: 4px 12px; border-radius: 4px; font-size: 12px;',
|
|
789
|
+
' cursor: pointer; font-family: inherit; font-weight: 600;',
|
|
790
|
+
'}',
|
|
791
|
+
'.cumulus-selection-delete-btn:hover:not(:disabled) { background: #a33; }',
|
|
792
|
+
'.cumulus-selection-delete-btn:disabled { background: #3a2a2a; color: #666; cursor: not-allowed; }',
|
|
793
|
+
|
|
794
|
+
/* ── Confirm dialog overlay ── */
|
|
795
|
+
'.cumulus-confirm-dialog {',
|
|
796
|
+
' position: absolute; top: 0; left: 0; right: 0; bottom: 0;',
|
|
797
|
+
' background: rgba(0,0,0,0.6); z-index: 20;',
|
|
798
|
+
' display: flex; align-items: center; justify-content: center;',
|
|
799
|
+
'}',
|
|
800
|
+
'.cumulus-confirm-dialog-box {',
|
|
801
|
+
' background: #2d2d2d; border: 1px solid #4a4a4a; border-radius: 8px;',
|
|
802
|
+
' padding: 1.5em; max-width: 320px; width: 90%;',
|
|
803
|
+
' display: flex; flex-direction: column; gap: 1em; text-align: center;',
|
|
804
|
+
' box-shadow: 0 8px 32px rgba(0,0,0,0.5);',
|
|
805
|
+
'}',
|
|
806
|
+
'.cumulus-confirm-dialog-title { font-weight: 600; font-size: 15px; color: #e0e0e0; }',
|
|
807
|
+
'.cumulus-confirm-dialog-text { font-size: 13px; color: #aaa; }',
|
|
808
|
+
'.cumulus-confirm-dialog-warn { font-size: 12px; color: #f59e0b; }',
|
|
809
|
+
'.cumulus-confirm-dialog-actions { display: flex; gap: 8px; justify-content: center; }',
|
|
810
|
+
'.cumulus-confirm-dialog-cancel {',
|
|
811
|
+
' padding: 0.4em 1.2em; border-radius: 4px; cursor: pointer;',
|
|
812
|
+
' font-size: 13px; border: 1px solid #3a3a3a; background: #2a2a2a; color: #ccc;',
|
|
813
|
+
' font-family: inherit;',
|
|
814
|
+
'}',
|
|
815
|
+
'.cumulus-confirm-dialog-cancel:hover { background: #3a3a3a; }',
|
|
816
|
+
'.cumulus-confirm-dialog-confirm {',
|
|
817
|
+
' padding: 0.4em 1.2em; border-radius: 4px; cursor: pointer;',
|
|
818
|
+
' font-size: 13px; border: none; background: #8b2222; color: white;',
|
|
819
|
+
' font-family: inherit; font-weight: 600;',
|
|
820
|
+
'}',
|
|
821
|
+
'.cumulus-confirm-dialog-confirm:hover { background: #a33; }',
|
|
822
|
+
'.cumulus-confirm-dialog-confirm:disabled { background: #3a2a2a; color: #666; cursor: not-allowed; }',
|
|
688
823
|
|
|
689
824
|
/* ── Overlay (covers the thread panel) ── */
|
|
690
825
|
'.cumulus-overlay {',
|
|
@@ -920,7 +1055,173 @@
|
|
|
920
1055
|
// else, then reinsert code blocks. This prevents markdown patterns inside
|
|
921
1056
|
// code from being processed.
|
|
922
1057
|
|
|
1058
|
+
// ── Agent message detection + timestamps ──────────────────────────────────
|
|
1059
|
+
var AGENT_MSG_RE = /^\[([^\]]+)\s*\u2192\s*([^\]]+)\]:\s*/; // [sender → recipient]:
|
|
1060
|
+
var AGENT_COLORS = ['#9966cc', '#2aa198', '#e69500', '#d33682', '#85c025', '#ff6b6b'];
|
|
1061
|
+
|
|
1062
|
+
function getAgentColor(name) {
|
|
1063
|
+
var hash = 0;
|
|
1064
|
+
for (var i = 0; i < name.length; i++) hash = ((hash << 5) - hash + name.charCodeAt(i)) | 0;
|
|
1065
|
+
return AGENT_COLORS[Math.abs(hash) % AGENT_COLORS.length];
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
function parseAgentMessage(content) {
|
|
1069
|
+
var match = content.match(AGENT_MSG_RE);
|
|
1070
|
+
if (!match) return null;
|
|
1071
|
+
var body = content
|
|
1072
|
+
.slice(match[0].length)
|
|
1073
|
+
.replace(/\n\n\(Reply using send_to_agent\([^)]*\)[^)]*\)\s*$/, '')
|
|
1074
|
+
.trim();
|
|
1075
|
+
// Also strip CC/broadcast reply hints
|
|
1076
|
+
body = body.replace(/\n\n\(You are CC'd on this message[^)]*\)\s*$/, '').trim();
|
|
1077
|
+
body = body.replace(/\n\n\(Review the above messages[^)]*\)\s*$/, '').trim();
|
|
1078
|
+
return { sender: match[1].trim(), recipient: match[2].trim(), body: body };
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
function formatTime(timestamp) {
|
|
1082
|
+
if (!timestamp) return null;
|
|
1083
|
+
var d = new Date(timestamp);
|
|
1084
|
+
return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
function appendTimestamp(parent, timestamp) {
|
|
1088
|
+
var time = formatTime(timestamp);
|
|
1089
|
+
if (time) {
|
|
1090
|
+
var timeEl = document.createElement('div');
|
|
1091
|
+
timeEl.className = 'cumulus-msg-time';
|
|
1092
|
+
timeEl.textContent = time;
|
|
1093
|
+
parent.appendChild(timeEl);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
function buildAgentMsgEl(agent) {
|
|
1098
|
+
var el = document.createElement('div');
|
|
1099
|
+
el.className = 'cumulus-msg agent';
|
|
1100
|
+
el.style.setProperty('--agent-color', getAgentColor(agent.sender));
|
|
1101
|
+
|
|
1102
|
+
var header = document.createElement('div');
|
|
1103
|
+
header.className = 'cumulus-agent-header';
|
|
1104
|
+
var nameEl = document.createElement('span');
|
|
1105
|
+
nameEl.className = 'cumulus-agent-name';
|
|
1106
|
+
nameEl.textContent = agent.sender;
|
|
1107
|
+
header.appendChild(nameEl);
|
|
1108
|
+
var ctx = document.createElement('span');
|
|
1109
|
+
ctx.className = 'cumulus-agent-context';
|
|
1110
|
+
ctx.textContent = '\u2192 ' + agent.recipient;
|
|
1111
|
+
header.appendChild(ctx);
|
|
1112
|
+
el.appendChild(header);
|
|
1113
|
+
|
|
1114
|
+
var body = document.createElement('div');
|
|
1115
|
+
var mdResult = renderMarkdown(agent.body);
|
|
1116
|
+
body.innerHTML = mdResult.html;
|
|
1117
|
+
renderBlexBlocks(body, mdResult.blexBlocks, null);
|
|
1118
|
+
el.appendChild(body);
|
|
1119
|
+
return el;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
// ── Markdown ──────────────────────────────────────────────────────────────
|
|
1123
|
+
|
|
1124
|
+
// ─── Blex block handle tracking ─────────────────────────────────────────────
|
|
1125
|
+
// Map: msgEl → Array<BlockHandle> — for cleanup on message delete/revert
|
|
1126
|
+
var blexHandles = new WeakMap();
|
|
1127
|
+
|
|
1128
|
+
/**
|
|
1129
|
+
* Parse blex fenced blocks from markdown text.
|
|
1130
|
+
* Returns { text: processedText, blocks: [{type, json, id}] }
|
|
1131
|
+
* Fences: ~~~blex:TYPE\n{json}\n~~~ or ```blex:TYPE\n{json}\n```
|
|
1132
|
+
*/
|
|
1133
|
+
function extractBlexBlocks(text) {
|
|
1134
|
+
var blocks = [];
|
|
1135
|
+
var BLEX_PREFIX = '\x00BLEX_';
|
|
1136
|
+
var processed = text.replace(
|
|
1137
|
+
/(?:~~~|```)blex:(\w[\w-]*)\n([\s\S]*?)(?:~~~|```)/g,
|
|
1138
|
+
function (_, type, json) {
|
|
1139
|
+
var idx = blocks.length;
|
|
1140
|
+
var id = 'blex-' + idx + '-' + Date.now();
|
|
1141
|
+
blocks.push({ type: type, json: json.trim(), id: id });
|
|
1142
|
+
return BLEX_PREFIX + idx + '\x00';
|
|
1143
|
+
}
|
|
1144
|
+
);
|
|
1145
|
+
return { text: processed, blocks: blocks };
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
/**
|
|
1149
|
+
* After innerHTML is set, find blex placeholder divs and render blocks.
|
|
1150
|
+
* Returns array of BlockHandles for lifecycle management.
|
|
1151
|
+
*/
|
|
1152
|
+
function renderBlexBlocks(container, blocks, inputEl) {
|
|
1153
|
+
if (!blocks || !blocks.length || typeof window.Blex === 'undefined') return;
|
|
1154
|
+
var handles = [];
|
|
1155
|
+
var placeholders = container.querySelectorAll('[data-blex-idx]');
|
|
1156
|
+
placeholders.forEach(function (ph) {
|
|
1157
|
+
var idx = parseInt(ph.getAttribute('data-blex-idx'), 10);
|
|
1158
|
+
var block = blocks[idx];
|
|
1159
|
+
if (!block) return;
|
|
1160
|
+
|
|
1161
|
+
// Try to parse JSON
|
|
1162
|
+
var data;
|
|
1163
|
+
try {
|
|
1164
|
+
data = JSON.parse(block.json);
|
|
1165
|
+
} catch (e) {
|
|
1166
|
+
// Show parse error in a fallback
|
|
1167
|
+
ph.innerHTML =
|
|
1168
|
+
'<pre style="color:#f44;font-size:0.85em;">blex:' +
|
|
1169
|
+
escapeHtml(block.type) +
|
|
1170
|
+
' — invalid JSON: ' +
|
|
1171
|
+
escapeHtml(e.message) +
|
|
1172
|
+
'</pre>';
|
|
1173
|
+
return;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
var contentBlock = { type: block.type, id: block.id, data: data };
|
|
1177
|
+
|
|
1178
|
+
// Render the block
|
|
1179
|
+
window.Blex.renderBlock(contentBlock, ph)
|
|
1180
|
+
.then(function (handle) {
|
|
1181
|
+
handles.push(handle);
|
|
1182
|
+
// Wire interaction → inject into input
|
|
1183
|
+
if (handle && handle.onInteraction && inputEl) {
|
|
1184
|
+
handle.onInteraction(function (interaction) {
|
|
1185
|
+
if (interaction && interaction.serialized) {
|
|
1186
|
+
inputEl.value =
|
|
1187
|
+
(inputEl.value ? inputEl.value + '\n' : '') + interaction.serialized;
|
|
1188
|
+
inputEl.focus();
|
|
1189
|
+
// Trigger input event so send button updates
|
|
1190
|
+
inputEl.dispatchEvent(new Event('input', { bubbles: true }));
|
|
1191
|
+
}
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
})
|
|
1195
|
+
.catch(function (err) {
|
|
1196
|
+
ph.innerHTML =
|
|
1197
|
+
'<pre style="color:#f44;font-size:0.85em;">blex:' +
|
|
1198
|
+
escapeHtml(block.type) +
|
|
1199
|
+
' render error: ' +
|
|
1200
|
+
escapeHtml(err.message) +
|
|
1201
|
+
'</pre>';
|
|
1202
|
+
});
|
|
1203
|
+
});
|
|
1204
|
+
blexHandles.set(container, handles);
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
/**
|
|
1208
|
+
* Destroy all blex block handles for a message element.
|
|
1209
|
+
*/
|
|
1210
|
+
function destroyBlexBlocks(container) {
|
|
1211
|
+
var handles = blexHandles.get(container);
|
|
1212
|
+
if (handles) {
|
|
1213
|
+
handles.forEach(function (h) {
|
|
1214
|
+
if (h && h.destroy) h.destroy();
|
|
1215
|
+
});
|
|
1216
|
+
blexHandles.delete(container);
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
|
|
923
1220
|
function renderMarkdown(text) {
|
|
1221
|
+
// Phase 0.5: Extract blex fenced blocks — replace with unique tokens
|
|
1222
|
+
var blexResult = extractBlexBlocks(text);
|
|
1223
|
+
text = blexResult.text;
|
|
1224
|
+
|
|
924
1225
|
// Phase 1: Extract fenced code blocks — replace with unique tokens
|
|
925
1226
|
var codeBlocks = [];
|
|
926
1227
|
var TOKEN_PREFIX = '\x00CODEBLOCK_';
|
|
@@ -993,7 +1294,20 @@
|
|
|
993
1294
|
return codeBlocks[parseInt(idx, 10)];
|
|
994
1295
|
});
|
|
995
1296
|
|
|
996
|
-
|
|
1297
|
+
// Phase 7.5: Reinsert blex block placeholders
|
|
1298
|
+
if (blexResult.blocks.length > 0) {
|
|
1299
|
+
html = html.replace(/\x00BLEX_(\d+)\x00/g, function (_, idx) {
|
|
1300
|
+
return (
|
|
1301
|
+
'<div class="blex-block-container" data-blex-idx="' +
|
|
1302
|
+
idx +
|
|
1303
|
+
'" data-testid="blex-block-' +
|
|
1304
|
+
idx +
|
|
1305
|
+
'"></div>'
|
|
1306
|
+
);
|
|
1307
|
+
});
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
return { html: html, blexBlocks: blexResult.blocks };
|
|
997
1311
|
}
|
|
998
1312
|
|
|
999
1313
|
function buildCodeBlock(lang, escapedCode, tokenId) {
|
|
@@ -1333,8 +1647,12 @@
|
|
|
1333
1647
|
var streamBuffer = '';
|
|
1334
1648
|
var connection = null;
|
|
1335
1649
|
var connectionStatus = 'disconnected';
|
|
1650
|
+
var embeddedStreamTimer = null;
|
|
1651
|
+
var EMBEDDED_STREAM_TIMEOUT = 120000;
|
|
1336
1652
|
var authenticated = false;
|
|
1337
1653
|
var pendingAttachments = [];
|
|
1654
|
+
var selectionMode = false;
|
|
1655
|
+
var selectedIds = {};
|
|
1338
1656
|
|
|
1339
1657
|
// ── Panel ──
|
|
1340
1658
|
var panel = document.createElement('div');
|
|
@@ -1351,6 +1669,9 @@
|
|
|
1351
1669
|
'<span class="cumulus-status-dot" data-testid="webchat-status-dot"></span>' +
|
|
1352
1670
|
'<span data-testid="webchat-status-text">Disconnected</span>' +
|
|
1353
1671
|
'</span>' +
|
|
1672
|
+
'<div class="cumulus-panel-actions">' +
|
|
1673
|
+
'<button class="cumulus-panel-action-btn" data-testid="webchat-select-btn" title="Select messages to delete">Select</button>' +
|
|
1674
|
+
'</div>' +
|
|
1354
1675
|
'<button class="cumulus-close-btn" data-testid="webchat-close">×</button>';
|
|
1355
1676
|
panel.appendChild(header);
|
|
1356
1677
|
|
|
@@ -1449,7 +1770,9 @@
|
|
|
1449
1770
|
|
|
1450
1771
|
// ── Rendering ──
|
|
1451
1772
|
function scrollToBottom() {
|
|
1452
|
-
|
|
1773
|
+
requestAnimationFrame(function () {
|
|
1774
|
+
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
1775
|
+
});
|
|
1453
1776
|
}
|
|
1454
1777
|
|
|
1455
1778
|
function isWideUserMessage(content) {
|
|
@@ -1491,10 +1814,29 @@
|
|
|
1491
1814
|
el.className = 'cumulus-msg assistant';
|
|
1492
1815
|
if (isStreaming) el.setAttribute('data-testid', 'webchat-streaming');
|
|
1493
1816
|
if (content) {
|
|
1494
|
-
|
|
1817
|
+
var mdResult = renderMarkdown(content);
|
|
1818
|
+
el.innerHTML = mdResult.html;
|
|
1495
1819
|
if (isStreaming) {
|
|
1496
1820
|
el.innerHTML += '<span class="cumulus-cursor"></span>';
|
|
1497
1821
|
}
|
|
1822
|
+
// Render blex blocks
|
|
1823
|
+
if (mdResult.blexBlocks.length > 0) {
|
|
1824
|
+
if (isStreaming && typeof window.Blex !== 'undefined') {
|
|
1825
|
+
// During streaming: show placeholders
|
|
1826
|
+
el.querySelectorAll('[data-blex-idx]').forEach(function (ph) {
|
|
1827
|
+
var idx = parseInt(ph.getAttribute('data-blex-idx'), 10);
|
|
1828
|
+
var block = mdResult.blexBlocks[idx];
|
|
1829
|
+
if (block) window.Blex.renderPlaceholder(block.type, ph);
|
|
1830
|
+
});
|
|
1831
|
+
} else if (!isStreaming) {
|
|
1832
|
+
// Final render: full blex blocks with interaction wiring
|
|
1833
|
+
var inputEl = el.closest('.cumulus-panel, .cumulus-widget');
|
|
1834
|
+
var blexInputEl = inputEl
|
|
1835
|
+
? inputEl.querySelector('.cumulus-input, .cumulus-standalone-input')
|
|
1836
|
+
: null;
|
|
1837
|
+
renderBlexBlocks(el, mdResult.blexBlocks, blexInputEl);
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1498
1840
|
el.querySelectorAll('.code-block-copy-btn').forEach(function (btn) {
|
|
1499
1841
|
btn.addEventListener('click', function () {
|
|
1500
1842
|
var targetId = btn.getAttribute('data-copy-target');
|
|
@@ -1517,21 +1859,56 @@
|
|
|
1517
1859
|
}
|
|
1518
1860
|
|
|
1519
1861
|
function renderMessages() {
|
|
1862
|
+
// Destroy existing blex block handles before clearing DOM
|
|
1863
|
+
messagesEl.querySelectorAll('.cumulus-msg.assistant').forEach(function (m) {
|
|
1864
|
+
destroyBlexBlocks(m);
|
|
1865
|
+
});
|
|
1520
1866
|
messagesEl.innerHTML = '';
|
|
1867
|
+
// Apply selection mode class
|
|
1868
|
+
if (selectionMode) {
|
|
1869
|
+
messagesEl.classList.add('cumulus-selection-mode');
|
|
1870
|
+
} else {
|
|
1871
|
+
messagesEl.classList.remove('cumulus-selection-mode');
|
|
1872
|
+
}
|
|
1521
1873
|
if (messages.length === 0 && !streaming) {
|
|
1522
1874
|
messagesEl.innerHTML =
|
|
1523
1875
|
'<div class="cumulus-empty">Send a message to start chatting</div>';
|
|
1876
|
+
updateEmbeddedSelectionBar();
|
|
1524
1877
|
return;
|
|
1525
1878
|
}
|
|
1526
1879
|
for (var i = 0; i < messages.length; i++) {
|
|
1527
1880
|
var msg = messages[i];
|
|
1528
1881
|
var row = document.createElement('div');
|
|
1529
1882
|
row.className = 'cumulus-msg-row';
|
|
1883
|
+
// Add checkbox in selection mode
|
|
1884
|
+
if (selectionMode && msg.id) {
|
|
1885
|
+
var cb = document.createElement('input');
|
|
1886
|
+
cb.type = 'checkbox';
|
|
1887
|
+
cb.className = 'cumulus-msg-checkbox';
|
|
1888
|
+
cb.checked = !!selectedIds[msg.id];
|
|
1889
|
+
(function (msgId) {
|
|
1890
|
+
cb.addEventListener('change', function () {
|
|
1891
|
+
if (cb.checked) {
|
|
1892
|
+
selectedIds[msgId] = true;
|
|
1893
|
+
} else {
|
|
1894
|
+
delete selectedIds[msgId];
|
|
1895
|
+
}
|
|
1896
|
+
updateEmbeddedSelectionBar();
|
|
1897
|
+
});
|
|
1898
|
+
})(msg.id);
|
|
1899
|
+
row.appendChild(cb);
|
|
1900
|
+
}
|
|
1530
1901
|
if (msg.role === 'user') {
|
|
1531
|
-
|
|
1902
|
+
var agent = parseAgentMessage(msg.content);
|
|
1903
|
+
if (agent) {
|
|
1904
|
+
row.appendChild(buildAgentMsgEl(agent));
|
|
1905
|
+
} else {
|
|
1906
|
+
row.appendChild(buildUserMsgEl(msg));
|
|
1907
|
+
}
|
|
1532
1908
|
} else {
|
|
1533
1909
|
row.appendChild(buildAssistantMsgEl(msg.content, false));
|
|
1534
1910
|
}
|
|
1911
|
+
appendTimestamp(row, msg.timestamp);
|
|
1535
1912
|
messagesEl.appendChild(row);
|
|
1536
1913
|
}
|
|
1537
1914
|
if (streaming) {
|
|
@@ -1541,6 +1918,115 @@
|
|
|
1541
1918
|
messagesEl.appendChild(row);
|
|
1542
1919
|
}
|
|
1543
1920
|
scrollToBottom();
|
|
1921
|
+
updateEmbeddedSelectionBar();
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
function updateEmbeddedSelectionBar() {
|
|
1925
|
+
var existingBar = panel.querySelector('.cumulus-selection-bar');
|
|
1926
|
+
if (existingBar) existingBar.remove();
|
|
1927
|
+
if (!selectionMode) return;
|
|
1928
|
+
|
|
1929
|
+
var selectedCount = Object.keys(selectedIds).length;
|
|
1930
|
+
var selectableCount = messages.filter(function (m) {
|
|
1931
|
+
return !!m.id;
|
|
1932
|
+
}).length;
|
|
1933
|
+
|
|
1934
|
+
var bar = document.createElement('div');
|
|
1935
|
+
bar.className = 'cumulus-selection-bar';
|
|
1936
|
+
|
|
1937
|
+
var countEl = document.createElement('span');
|
|
1938
|
+
countEl.className = 'cumulus-selection-count';
|
|
1939
|
+
countEl.textContent = selectedCount + ' selected';
|
|
1940
|
+
bar.appendChild(countEl);
|
|
1941
|
+
|
|
1942
|
+
var selectAllBtn = document.createElement('button');
|
|
1943
|
+
selectAllBtn.className = 'cumulus-selection-select-all-btn';
|
|
1944
|
+
selectAllBtn.textContent =
|
|
1945
|
+
selectedCount === selectableCount && selectableCount > 0 ? 'Deselect All' : 'Select All';
|
|
1946
|
+
selectAllBtn.addEventListener('click', function () {
|
|
1947
|
+
if (selectedCount === selectableCount && selectableCount > 0) {
|
|
1948
|
+
selectedIds = {};
|
|
1949
|
+
} else {
|
|
1950
|
+
messages.forEach(function (m) {
|
|
1951
|
+
if (m.id) selectedIds[m.id] = true;
|
|
1952
|
+
});
|
|
1953
|
+
}
|
|
1954
|
+
renderMessages();
|
|
1955
|
+
});
|
|
1956
|
+
bar.appendChild(selectAllBtn);
|
|
1957
|
+
|
|
1958
|
+
var deleteBtn = document.createElement('button');
|
|
1959
|
+
deleteBtn.className = 'cumulus-selection-delete-btn';
|
|
1960
|
+
deleteBtn.textContent = 'Delete';
|
|
1961
|
+
deleteBtn.disabled = selectedCount === 0;
|
|
1962
|
+
deleteBtn.addEventListener('click', function () {
|
|
1963
|
+
if (selectedCount === 0) return;
|
|
1964
|
+
showEmbeddedDeleteMessagesConfirm(Object.keys(selectedIds));
|
|
1965
|
+
});
|
|
1966
|
+
bar.appendChild(deleteBtn);
|
|
1967
|
+
|
|
1968
|
+
var inputAreaEl = panel.querySelector('.cumulus-input-area');
|
|
1969
|
+
if (inputAreaEl) {
|
|
1970
|
+
panel.insertBefore(bar, inputAreaEl);
|
|
1971
|
+
} else {
|
|
1972
|
+
panel.appendChild(bar);
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1976
|
+
function showEmbeddedDeleteMessagesConfirm(ids) {
|
|
1977
|
+
var existing = panel.querySelector('.cumulus-confirm-dialog');
|
|
1978
|
+
if (existing) existing.remove();
|
|
1979
|
+
|
|
1980
|
+
var dialog = document.createElement('div');
|
|
1981
|
+
dialog.className = 'cumulus-confirm-dialog';
|
|
1982
|
+
|
|
1983
|
+
var box = document.createElement('div');
|
|
1984
|
+
box.className = 'cumulus-confirm-dialog-box';
|
|
1985
|
+
|
|
1986
|
+
var title = document.createElement('div');
|
|
1987
|
+
title.className = 'cumulus-confirm-dialog-title';
|
|
1988
|
+
title.textContent = 'Delete Messages';
|
|
1989
|
+
box.appendChild(title);
|
|
1990
|
+
|
|
1991
|
+
var text = document.createElement('div');
|
|
1992
|
+
text.className = 'cumulus-confirm-dialog-text';
|
|
1993
|
+
text.textContent =
|
|
1994
|
+
ids.length + ' message' + (ids.length === 1 ? '' : 's') + ' will be permanently deleted.';
|
|
1995
|
+
box.appendChild(text);
|
|
1996
|
+
|
|
1997
|
+
var warn = document.createElement('div');
|
|
1998
|
+
warn.className = 'cumulus-confirm-dialog-warn';
|
|
1999
|
+
warn.textContent = 'This cannot be undone.';
|
|
2000
|
+
box.appendChild(warn);
|
|
2001
|
+
|
|
2002
|
+
var actions = document.createElement('div');
|
|
2003
|
+
actions.className = 'cumulus-confirm-dialog-actions';
|
|
2004
|
+
|
|
2005
|
+
var cancelBtn = document.createElement('button');
|
|
2006
|
+
cancelBtn.className = 'cumulus-confirm-dialog-cancel';
|
|
2007
|
+
cancelBtn.textContent = 'Cancel';
|
|
2008
|
+
cancelBtn.addEventListener('click', function () {
|
|
2009
|
+
dialog.remove();
|
|
2010
|
+
});
|
|
2011
|
+
actions.appendChild(cancelBtn);
|
|
2012
|
+
|
|
2013
|
+
var confirmBtn = document.createElement('button');
|
|
2014
|
+
confirmBtn.className = 'cumulus-confirm-dialog-confirm';
|
|
2015
|
+
confirmBtn.setAttribute('data-testid', 'webchat-delete-messages-confirm');
|
|
2016
|
+
confirmBtn.textContent = 'Delete';
|
|
2017
|
+
confirmBtn.addEventListener('click', function () {
|
|
2018
|
+
confirmBtn.disabled = true;
|
|
2019
|
+
confirmBtn.textContent = 'Deleting\u2026';
|
|
2020
|
+
if (connection) {
|
|
2021
|
+
connection.send({ type: 'delete_messages', threadName: sessionId, messageIds: ids });
|
|
2022
|
+
}
|
|
2023
|
+
dialog.remove();
|
|
2024
|
+
});
|
|
2025
|
+
actions.appendChild(confirmBtn);
|
|
2026
|
+
|
|
2027
|
+
box.appendChild(actions);
|
|
2028
|
+
dialog.appendChild(box);
|
|
2029
|
+
panel.appendChild(dialog);
|
|
1544
2030
|
}
|
|
1545
2031
|
|
|
1546
2032
|
function updateSendBtn() {
|
|
@@ -1629,6 +2115,20 @@
|
|
|
1629
2115
|
function handleServerMessage(data) {
|
|
1630
2116
|
switch (data.type) {
|
|
1631
2117
|
case 'auth_ok':
|
|
2118
|
+
// Reset streaming state on reconnect
|
|
2119
|
+
if (streaming) {
|
|
2120
|
+
if (streamBuffer) {
|
|
2121
|
+
messages.push({ role: 'assistant', content: streamBuffer });
|
|
2122
|
+
streamBuffer = '';
|
|
2123
|
+
}
|
|
2124
|
+
streaming = false;
|
|
2125
|
+
stopRequested = false;
|
|
2126
|
+
if (embeddedStreamTimer) {
|
|
2127
|
+
clearTimeout(embeddedStreamTimer);
|
|
2128
|
+
embeddedStreamTimer = null;
|
|
2129
|
+
}
|
|
2130
|
+
updateSendBtn();
|
|
2131
|
+
}
|
|
1632
2132
|
showChatView();
|
|
1633
2133
|
break;
|
|
1634
2134
|
case 'auth_error':
|
|
@@ -1637,7 +2137,7 @@
|
|
|
1637
2137
|
case 'history':
|
|
1638
2138
|
if (data.messages && data.messages.length > 0) {
|
|
1639
2139
|
messages = data.messages.map(function (m) {
|
|
1640
|
-
return { role: m.role, content: m.content };
|
|
2140
|
+
return { id: m.id, role: m.role, content: m.content, timestamp: m.timestamp };
|
|
1641
2141
|
});
|
|
1642
2142
|
renderMessages();
|
|
1643
2143
|
}
|
|
@@ -1650,11 +2150,36 @@
|
|
|
1650
2150
|
updateSendBtn();
|
|
1651
2151
|
}
|
|
1652
2152
|
streamBuffer += data.text;
|
|
2153
|
+
// Reset streaming timeout on each token
|
|
2154
|
+
if (embeddedStreamTimer) clearTimeout(embeddedStreamTimer);
|
|
2155
|
+
embeddedStreamTimer = setTimeout(function () {
|
|
2156
|
+
if (streaming) {
|
|
2157
|
+
console.warn('[Cumulus] Streaming timeout — resetting');
|
|
2158
|
+
if (streamBuffer) {
|
|
2159
|
+
messages.push({
|
|
2160
|
+
role: 'assistant',
|
|
2161
|
+
content: streamBuffer + '\n\n*(response may be incomplete)*',
|
|
2162
|
+
});
|
|
2163
|
+
}
|
|
2164
|
+
streaming = false;
|
|
2165
|
+
streamBuffer = '';
|
|
2166
|
+
updateSendBtn();
|
|
2167
|
+
renderMessages();
|
|
2168
|
+
// Reload history
|
|
2169
|
+
if (connection)
|
|
2170
|
+
connection.send({ type: 'history', threadName: sessionId, limit: 50 });
|
|
2171
|
+
}
|
|
2172
|
+
embeddedStreamTimer = null;
|
|
2173
|
+
}, EMBEDDED_STREAM_TIMEOUT);
|
|
1653
2174
|
renderMessages();
|
|
1654
2175
|
break;
|
|
1655
2176
|
case 'segment':
|
|
1656
2177
|
break;
|
|
1657
2178
|
case 'done':
|
|
2179
|
+
if (embeddedStreamTimer) {
|
|
2180
|
+
clearTimeout(embeddedStreamTimer);
|
|
2181
|
+
embeddedStreamTimer = null;
|
|
2182
|
+
}
|
|
1658
2183
|
if (stopRequested) {
|
|
1659
2184
|
stopRequested = false;
|
|
1660
2185
|
streaming = false;
|
|
@@ -1674,8 +2199,16 @@
|
|
|
1674
2199
|
break;
|
|
1675
2200
|
case 'interjected':
|
|
1676
2201
|
// Server confirms old stream was aborted. Partial already frozen client-side.
|
|
2202
|
+
if (embeddedStreamTimer) {
|
|
2203
|
+
clearTimeout(embeddedStreamTimer);
|
|
2204
|
+
embeddedStreamTimer = null;
|
|
2205
|
+
}
|
|
1677
2206
|
break;
|
|
1678
2207
|
case 'error':
|
|
2208
|
+
if (embeddedStreamTimer) {
|
|
2209
|
+
clearTimeout(embeddedStreamTimer);
|
|
2210
|
+
embeddedStreamTimer = null;
|
|
2211
|
+
}
|
|
1679
2212
|
stopRequested = false;
|
|
1680
2213
|
streaming = false;
|
|
1681
2214
|
if (streamBuffer) {
|
|
@@ -1693,6 +2226,30 @@
|
|
|
1693
2226
|
updateSendBtn();
|
|
1694
2227
|
renderMessages();
|
|
1695
2228
|
break;
|
|
2229
|
+
case 'new_messages':
|
|
2230
|
+
// Agent inject completed — append new messages and re-render
|
|
2231
|
+
if (data.messages && data.messages.length > 0) {
|
|
2232
|
+
for (var nm = 0; nm < data.messages.length; nm++) {
|
|
2233
|
+
messages.push({ role: data.messages[nm].role, content: data.messages[nm].content });
|
|
2234
|
+
}
|
|
2235
|
+
renderMessages();
|
|
2236
|
+
}
|
|
2237
|
+
break;
|
|
2238
|
+
case 'messages_deleted':
|
|
2239
|
+
// Server confirmed deletion — reload history and exit selection mode
|
|
2240
|
+
selectionMode = false;
|
|
2241
|
+
selectedIds = {};
|
|
2242
|
+
var embSelectBtn = panel.querySelector('[data-testid="webchat-select-btn"]');
|
|
2243
|
+
if (embSelectBtn) {
|
|
2244
|
+
embSelectBtn.textContent = 'Select';
|
|
2245
|
+
embSelectBtn.classList.remove('active');
|
|
2246
|
+
}
|
|
2247
|
+
messages = [];
|
|
2248
|
+
// Re-request history
|
|
2249
|
+
if (connection) {
|
|
2250
|
+
connection.send({ type: 'history', threadName: sessionId, limit: 200 });
|
|
2251
|
+
}
|
|
2252
|
+
break;
|
|
1696
2253
|
}
|
|
1697
2254
|
}
|
|
1698
2255
|
|
|
@@ -1808,6 +2365,22 @@
|
|
|
1808
2365
|
});
|
|
1809
2366
|
}
|
|
1810
2367
|
|
|
2368
|
+
// Select button for embedded mode
|
|
2369
|
+
var embSelectBtn = panel.querySelector('[data-testid="webchat-select-btn"]');
|
|
2370
|
+
if (embSelectBtn) {
|
|
2371
|
+
embSelectBtn.addEventListener('click', function () {
|
|
2372
|
+
selectionMode = !selectionMode;
|
|
2373
|
+
selectedIds = {};
|
|
2374
|
+
embSelectBtn.textContent = selectionMode ? 'Done' : 'Select';
|
|
2375
|
+
if (selectionMode) {
|
|
2376
|
+
embSelectBtn.classList.add('active');
|
|
2377
|
+
} else {
|
|
2378
|
+
embSelectBtn.classList.remove('active');
|
|
2379
|
+
}
|
|
2380
|
+
renderMessages();
|
|
2381
|
+
});
|
|
2382
|
+
}
|
|
2383
|
+
|
|
1811
2384
|
// Embedded mode: no auth panel handling needed (API key from attribute)
|
|
1812
2385
|
function startConnection() {
|
|
1813
2386
|
if (connection) {
|
|
@@ -1847,10 +2420,49 @@
|
|
|
1847
2420
|
|
|
1848
2421
|
// Thread registry: threadName -> { messages, streaming, streamBuffer, stopRequested, pendingAttachments }
|
|
1849
2422
|
var threadStates = {};
|
|
2423
|
+
var streamingTimeouts = {}; // threadName -> timeout ID
|
|
2424
|
+
var STREAMING_TIMEOUT_MS = 120000; // 2 minutes with no token = assume dead
|
|
2425
|
+
|
|
2426
|
+
function resetStreamingTimeout(threadName) {
|
|
2427
|
+
if (streamingTimeouts[threadName]) clearTimeout(streamingTimeouts[threadName]);
|
|
2428
|
+
streamingTimeouts[threadName] = setTimeout(function () {
|
|
2429
|
+
var s = threadStates[threadName];
|
|
2430
|
+
if (s && s.streaming) {
|
|
2431
|
+
console.warn('[CumulusChat] Streaming timeout for thread:', threadName);
|
|
2432
|
+
if (s.streamBuffer) {
|
|
2433
|
+
s.messages.push({
|
|
2434
|
+
role: 'assistant',
|
|
2435
|
+
content: s.streamBuffer + '\n\n*(response may be incomplete — connection issue)*',
|
|
2436
|
+
});
|
|
2437
|
+
}
|
|
2438
|
+
s.streaming = false;
|
|
2439
|
+
s.streamBuffer = '';
|
|
2440
|
+
s.stopRequested = false;
|
|
2441
|
+
s.interjecting = false;
|
|
2442
|
+
// Reload history to get the real response if server completed
|
|
2443
|
+
s.historyLoaded = false;
|
|
2444
|
+
if (connection) {
|
|
2445
|
+
connection.send({ type: 'history', threadName: threadName, limit: 50 });
|
|
2446
|
+
}
|
|
2447
|
+
refreshThreadPanel(threadName);
|
|
2448
|
+
}
|
|
2449
|
+
delete streamingTimeouts[threadName];
|
|
2450
|
+
}, STREAMING_TIMEOUT_MS);
|
|
2451
|
+
}
|
|
2452
|
+
|
|
2453
|
+
function clearStreamingTimeout(threadName) {
|
|
2454
|
+
if (streamingTimeouts[threadName]) {
|
|
2455
|
+
clearTimeout(streamingTimeouts[threadName]);
|
|
2456
|
+
delete streamingTimeouts[threadName];
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
1850
2459
|
|
|
1851
2460
|
// Thread list from server: [{ name, messageCount, lastActivity }]
|
|
1852
2461
|
var allThreads = [];
|
|
1853
2462
|
|
|
2463
|
+
// Unread threads: threadName -> true
|
|
2464
|
+
var unreadThreads = {};
|
|
2465
|
+
|
|
1854
2466
|
// Currently visible panel names (ordered, max 3)
|
|
1855
2467
|
var visibleThreads = [];
|
|
1856
2468
|
|
|
@@ -1868,6 +2480,8 @@
|
|
|
1868
2480
|
interjecting: false,
|
|
1869
2481
|
pendingAttachments: [],
|
|
1870
2482
|
historyLoaded: false,
|
|
2483
|
+
selectionMode: false,
|
|
2484
|
+
selectedIds: {},
|
|
1871
2485
|
};
|
|
1872
2486
|
}
|
|
1873
2487
|
return threadStates[threadName];
|
|
@@ -2132,7 +2746,9 @@
|
|
|
2132
2746
|
item.setAttribute('data-thread-name', name);
|
|
2133
2747
|
|
|
2134
2748
|
var dot = document.createElement('span');
|
|
2135
|
-
dot.className =
|
|
2749
|
+
dot.className = unreadThreads[name]
|
|
2750
|
+
? 'cumulus-thread-unread'
|
|
2751
|
+
: 'cumulus-thread-dot ' + (isActive ? 'active' : 'inactive');
|
|
2136
2752
|
item.appendChild(dot);
|
|
2137
2753
|
|
|
2138
2754
|
var nameEl = document.createElement('span');
|
|
@@ -2155,6 +2771,13 @@
|
|
|
2155
2771
|
item.appendChild(countEl);
|
|
2156
2772
|
}
|
|
2157
2773
|
|
|
2774
|
+
// Right-click context menu
|
|
2775
|
+
item.addEventListener('contextmenu', function (e) {
|
|
2776
|
+
e.preventDefault();
|
|
2777
|
+
e.stopPropagation();
|
|
2778
|
+
showThreadContextMenu(name, e.clientX, e.clientY);
|
|
2779
|
+
});
|
|
2780
|
+
|
|
2158
2781
|
item.addEventListener('click', function (e) {
|
|
2159
2782
|
var multiSelect = e.metaKey || e.ctrlKey;
|
|
2160
2783
|
if (multiSelect) {
|
|
@@ -2167,8 +2790,123 @@
|
|
|
2167
2790
|
return item;
|
|
2168
2791
|
}
|
|
2169
2792
|
|
|
2793
|
+
// ── Thread context menu (right-click) ──
|
|
2794
|
+
function dismissContextMenu() {
|
|
2795
|
+
var existing = document.querySelector('.cumulus-context-menu');
|
|
2796
|
+
if (existing) existing.remove();
|
|
2797
|
+
document.removeEventListener('click', dismissContextMenu);
|
|
2798
|
+
document.removeEventListener('contextmenu', dismissContextMenu);
|
|
2799
|
+
}
|
|
2800
|
+
|
|
2801
|
+
function showThreadContextMenu(threadName, x, y) {
|
|
2802
|
+
dismissContextMenu();
|
|
2803
|
+
|
|
2804
|
+
var menu = document.createElement('div');
|
|
2805
|
+
menu.className = 'cumulus-context-menu';
|
|
2806
|
+
menu.style.left = x + 'px';
|
|
2807
|
+
menu.style.top = y + 'px';
|
|
2808
|
+
|
|
2809
|
+
var deleteItem = document.createElement('button');
|
|
2810
|
+
deleteItem.className = 'cumulus-context-menu-item danger';
|
|
2811
|
+
deleteItem.textContent = 'Delete thread';
|
|
2812
|
+
deleteItem.setAttribute('data-testid', 'webchat-thread-delete');
|
|
2813
|
+
deleteItem.addEventListener('click', function () {
|
|
2814
|
+
dismissContextMenu();
|
|
2815
|
+
showThreadDeleteConfirm(threadName);
|
|
2816
|
+
});
|
|
2817
|
+
menu.appendChild(deleteItem);
|
|
2818
|
+
|
|
2819
|
+
document.body.appendChild(menu);
|
|
2820
|
+
|
|
2821
|
+
// Clamp to viewport
|
|
2822
|
+
var rect = menu.getBoundingClientRect();
|
|
2823
|
+
if (rect.right > window.innerWidth)
|
|
2824
|
+
menu.style.left = window.innerWidth - rect.width - 4 + 'px';
|
|
2825
|
+
if (rect.bottom > window.innerHeight)
|
|
2826
|
+
menu.style.top = window.innerHeight - rect.height - 4 + 'px';
|
|
2827
|
+
|
|
2828
|
+
// Dismiss on next click or right-click anywhere
|
|
2829
|
+
setTimeout(function () {
|
|
2830
|
+
document.addEventListener('click', dismissContextMenu);
|
|
2831
|
+
document.addEventListener('contextmenu', dismissContextMenu);
|
|
2832
|
+
}, 0);
|
|
2833
|
+
}
|
|
2834
|
+
|
|
2835
|
+
// ── Thread delete confirmation ──
|
|
2836
|
+
function showThreadDeleteConfirm(threadName) {
|
|
2837
|
+
// Remove any existing confirm dialogs
|
|
2838
|
+
var existing = document.querySelector(
|
|
2839
|
+
'.cumulus-confirm-dialog[data-context="thread-delete"]'
|
|
2840
|
+
);
|
|
2841
|
+
if (existing) {
|
|
2842
|
+
existing.remove();
|
|
2843
|
+
return;
|
|
2844
|
+
}
|
|
2845
|
+
|
|
2846
|
+
// Build inline confirm dialog attached to the sidebar scroll area
|
|
2847
|
+
var dialog = document.createElement('div');
|
|
2848
|
+
dialog.className = 'cumulus-confirm-dialog';
|
|
2849
|
+
dialog.setAttribute('data-context', 'thread-delete');
|
|
2850
|
+
dialog.style.cssText =
|
|
2851
|
+
'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;';
|
|
2852
|
+
|
|
2853
|
+
var box = document.createElement('div');
|
|
2854
|
+
box.className = 'cumulus-confirm-dialog-box';
|
|
2855
|
+
|
|
2856
|
+
var title = document.createElement('div');
|
|
2857
|
+
title.className = 'cumulus-confirm-dialog-title';
|
|
2858
|
+
title.textContent = 'Delete Thread';
|
|
2859
|
+
box.appendChild(title);
|
|
2860
|
+
|
|
2861
|
+
var text = document.createElement('div');
|
|
2862
|
+
text.className = 'cumulus-confirm-dialog-text';
|
|
2863
|
+
text.textContent = 'Delete "' + threadName + '"?';
|
|
2864
|
+
box.appendChild(text);
|
|
2865
|
+
|
|
2866
|
+
var warn = document.createElement('div');
|
|
2867
|
+
warn.className = 'cumulus-confirm-dialog-warn';
|
|
2868
|
+
warn.textContent = 'All messages and history will be permanently removed.';
|
|
2869
|
+
box.appendChild(warn);
|
|
2870
|
+
|
|
2871
|
+
var actions = document.createElement('div');
|
|
2872
|
+
actions.className = 'cumulus-confirm-dialog-actions';
|
|
2873
|
+
|
|
2874
|
+
var cancelBtn = document.createElement('button');
|
|
2875
|
+
cancelBtn.className = 'cumulus-confirm-dialog-cancel';
|
|
2876
|
+
cancelBtn.textContent = 'Cancel';
|
|
2877
|
+
cancelBtn.addEventListener('click', function () {
|
|
2878
|
+
dialog.remove();
|
|
2879
|
+
});
|
|
2880
|
+
actions.appendChild(cancelBtn);
|
|
2881
|
+
|
|
2882
|
+
var confirmBtn = document.createElement('button');
|
|
2883
|
+
confirmBtn.className = 'cumulus-confirm-dialog-confirm';
|
|
2884
|
+
confirmBtn.setAttribute('data-testid', 'webchat-thread-delete-confirm');
|
|
2885
|
+
confirmBtn.textContent = 'Delete';
|
|
2886
|
+
confirmBtn.addEventListener('click', function () {
|
|
2887
|
+
confirmBtn.disabled = true;
|
|
2888
|
+
confirmBtn.textContent = 'Deleting\u2026';
|
|
2889
|
+
if (connection) {
|
|
2890
|
+
connection.send({ type: 'delete_thread', threadName: threadName });
|
|
2891
|
+
}
|
|
2892
|
+
dialog.remove();
|
|
2893
|
+
});
|
|
2894
|
+
actions.appendChild(confirmBtn);
|
|
2895
|
+
|
|
2896
|
+
box.appendChild(actions);
|
|
2897
|
+
dialog.appendChild(box);
|
|
2898
|
+
|
|
2899
|
+
// Dismiss on backdrop click
|
|
2900
|
+
dialog.addEventListener('click', function (e) {
|
|
2901
|
+
if (e.target === dialog) dialog.remove();
|
|
2902
|
+
});
|
|
2903
|
+
|
|
2904
|
+
document.body.appendChild(dialog);
|
|
2905
|
+
}
|
|
2906
|
+
|
|
2170
2907
|
// ── Panel management ──
|
|
2171
2908
|
function soloThread(name) {
|
|
2909
|
+
delete unreadThreads[name];
|
|
2172
2910
|
visibleThreads = [name];
|
|
2173
2911
|
renderSidebar();
|
|
2174
2912
|
renderContentArea();
|
|
@@ -2177,6 +2915,7 @@
|
|
|
2177
2915
|
}
|
|
2178
2916
|
|
|
2179
2917
|
function togglePanelThread(name) {
|
|
2918
|
+
delete unreadThreads[name];
|
|
2180
2919
|
// On mobile, always solo (no side-by-side)
|
|
2181
2920
|
if (isMobile()) {
|
|
2182
2921
|
soloThread(name);
|
|
@@ -2307,6 +3046,16 @@
|
|
|
2307
3046
|
});
|
|
2308
3047
|
actions.appendChild(revertBtn);
|
|
2309
3048
|
|
|
3049
|
+
var selectBtn = document.createElement('button');
|
|
3050
|
+
selectBtn.className = 'cumulus-panel-action-btn';
|
|
3051
|
+
selectBtn.setAttribute('data-testid', 'webchat-select-btn');
|
|
3052
|
+
selectBtn.setAttribute('title', 'Select messages to delete');
|
|
3053
|
+
selectBtn.textContent = 'Select';
|
|
3054
|
+
selectBtn.addEventListener('click', function () {
|
|
3055
|
+
toggleSelectionMode();
|
|
3056
|
+
});
|
|
3057
|
+
actions.appendChild(selectBtn);
|
|
3058
|
+
|
|
2310
3059
|
panelHeader.appendChild(actions);
|
|
2311
3060
|
|
|
2312
3061
|
var closeBtn = document.createElement('button');
|
|
@@ -2375,7 +3124,11 @@
|
|
|
2375
3124
|
|
|
2376
3125
|
// ── Panel-local render functions ──
|
|
2377
3126
|
function scrollToBottom() {
|
|
2378
|
-
|
|
3127
|
+
requestAnimationFrame(function () {
|
|
3128
|
+
requestAnimationFrame(function () {
|
|
3129
|
+
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
3130
|
+
});
|
|
3131
|
+
});
|
|
2379
3132
|
}
|
|
2380
3133
|
|
|
2381
3134
|
function isWideUserMessage(content) {
|
|
@@ -2417,10 +3170,29 @@
|
|
|
2417
3170
|
el.className = 'cumulus-msg assistant';
|
|
2418
3171
|
if (isStreaming) el.setAttribute('data-testid', 'webchat-streaming');
|
|
2419
3172
|
if (content) {
|
|
2420
|
-
|
|
3173
|
+
var mdResult = renderMarkdown(content);
|
|
3174
|
+
el.innerHTML = mdResult.html;
|
|
2421
3175
|
if (isStreaming) {
|
|
2422
3176
|
el.innerHTML += '<span class="cumulus-cursor"></span>';
|
|
2423
3177
|
}
|
|
3178
|
+
// Render blex blocks
|
|
3179
|
+
if (mdResult.blexBlocks.length > 0) {
|
|
3180
|
+
if (isStreaming && typeof window.Blex !== 'undefined') {
|
|
3181
|
+
// During streaming: show placeholders
|
|
3182
|
+
el.querySelectorAll('[data-blex-idx]').forEach(function (ph) {
|
|
3183
|
+
var idx = parseInt(ph.getAttribute('data-blex-idx'), 10);
|
|
3184
|
+
var block = mdResult.blexBlocks[idx];
|
|
3185
|
+
if (block) window.Blex.renderPlaceholder(block.type, ph);
|
|
3186
|
+
});
|
|
3187
|
+
} else if (!isStreaming) {
|
|
3188
|
+
// Final render: full blex blocks with interaction wiring
|
|
3189
|
+
var inputEl = el.closest('.cumulus-panel, .cumulus-widget');
|
|
3190
|
+
var blexInputEl = inputEl
|
|
3191
|
+
? inputEl.querySelector('.cumulus-input, .cumulus-standalone-input')
|
|
3192
|
+
: null;
|
|
3193
|
+
renderBlexBlocks(el, mdResult.blexBlocks, blexInputEl);
|
|
3194
|
+
}
|
|
3195
|
+
}
|
|
2424
3196
|
el.querySelectorAll('.code-block-copy-btn').forEach(function (btn) {
|
|
2425
3197
|
btn.addEventListener('click', function () {
|
|
2426
3198
|
var targetId = btn.getAttribute('data-copy-target');
|
|
@@ -2443,21 +3215,56 @@
|
|
|
2443
3215
|
}
|
|
2444
3216
|
|
|
2445
3217
|
function renderPanelMessages() {
|
|
3218
|
+
// Destroy existing blex block handles before clearing DOM
|
|
3219
|
+
messagesEl.querySelectorAll('.cumulus-msg.assistant').forEach(function (m) {
|
|
3220
|
+
destroyBlexBlocks(m);
|
|
3221
|
+
});
|
|
2446
3222
|
messagesEl.innerHTML = '';
|
|
3223
|
+
// Apply selection mode class to messages container
|
|
3224
|
+
if (state.selectionMode) {
|
|
3225
|
+
messagesEl.classList.add('cumulus-selection-mode');
|
|
3226
|
+
} else {
|
|
3227
|
+
messagesEl.classList.remove('cumulus-selection-mode');
|
|
3228
|
+
}
|
|
2447
3229
|
if (state.messages.length === 0 && !state.streaming) {
|
|
2448
3230
|
messagesEl.innerHTML =
|
|
2449
3231
|
'<div class="cumulus-empty">Send a message to start chatting</div>';
|
|
3232
|
+
updateSelectionBar();
|
|
2450
3233
|
return;
|
|
2451
3234
|
}
|
|
2452
3235
|
for (var i = 0; i < state.messages.length; i++) {
|
|
2453
3236
|
var msg = state.messages[i];
|
|
2454
3237
|
var row = document.createElement('div');
|
|
2455
3238
|
row.className = 'cumulus-msg-row';
|
|
3239
|
+
// Add checkbox if in selection mode and message has an id
|
|
3240
|
+
if (state.selectionMode && msg.id) {
|
|
3241
|
+
var cb = document.createElement('input');
|
|
3242
|
+
cb.type = 'checkbox';
|
|
3243
|
+
cb.className = 'cumulus-msg-checkbox';
|
|
3244
|
+
cb.checked = !!state.selectedIds[msg.id];
|
|
3245
|
+
(function (msgId) {
|
|
3246
|
+
cb.addEventListener('change', function () {
|
|
3247
|
+
if (cb.checked) {
|
|
3248
|
+
state.selectedIds[msgId] = true;
|
|
3249
|
+
} else {
|
|
3250
|
+
delete state.selectedIds[msgId];
|
|
3251
|
+
}
|
|
3252
|
+
updateSelectionBar();
|
|
3253
|
+
});
|
|
3254
|
+
})(msg.id);
|
|
3255
|
+
row.appendChild(cb);
|
|
3256
|
+
}
|
|
2456
3257
|
if (msg.role === 'user') {
|
|
2457
|
-
|
|
3258
|
+
var agent = parseAgentMessage(msg.content);
|
|
3259
|
+
if (agent) {
|
|
3260
|
+
row.appendChild(buildAgentMsgEl(agent));
|
|
3261
|
+
} else {
|
|
3262
|
+
row.appendChild(buildUserMsgEl(msg));
|
|
3263
|
+
}
|
|
2458
3264
|
} else {
|
|
2459
3265
|
row.appendChild(buildAssistantMsgEl(msg.content, false));
|
|
2460
3266
|
}
|
|
3267
|
+
appendTimestamp(row, msg.timestamp);
|
|
2461
3268
|
messagesEl.appendChild(row);
|
|
2462
3269
|
}
|
|
2463
3270
|
if (state.streaming) {
|
|
@@ -2467,6 +3274,132 @@
|
|
|
2467
3274
|
messagesEl.appendChild(row);
|
|
2468
3275
|
}
|
|
2469
3276
|
scrollToBottom();
|
|
3277
|
+
updateSelectionBar();
|
|
3278
|
+
}
|
|
3279
|
+
|
|
3280
|
+
function updateSelectionBar() {
|
|
3281
|
+
// Remove existing bar
|
|
3282
|
+
var existingBar = panel.querySelector('.cumulus-selection-bar');
|
|
3283
|
+
if (existingBar) existingBar.remove();
|
|
3284
|
+
if (!state.selectionMode) return;
|
|
3285
|
+
|
|
3286
|
+
var selectedCount = Object.keys(state.selectedIds).length;
|
|
3287
|
+
var selectableCount = state.messages.filter(function (m) {
|
|
3288
|
+
return !!m.id;
|
|
3289
|
+
}).length;
|
|
3290
|
+
|
|
3291
|
+
var bar = document.createElement('div');
|
|
3292
|
+
bar.className = 'cumulus-selection-bar';
|
|
3293
|
+
|
|
3294
|
+
var countEl = document.createElement('span');
|
|
3295
|
+
countEl.className = 'cumulus-selection-count';
|
|
3296
|
+
countEl.textContent = selectedCount + ' selected';
|
|
3297
|
+
bar.appendChild(countEl);
|
|
3298
|
+
|
|
3299
|
+
var selectAllBtn = document.createElement('button');
|
|
3300
|
+
selectAllBtn.className = 'cumulus-selection-select-all-btn';
|
|
3301
|
+
selectAllBtn.textContent =
|
|
3302
|
+
selectedCount === selectableCount && selectableCount > 0 ? 'Deselect All' : 'Select All';
|
|
3303
|
+
selectAllBtn.addEventListener('click', function () {
|
|
3304
|
+
if (selectedCount === selectableCount && selectableCount > 0) {
|
|
3305
|
+
// Deselect all
|
|
3306
|
+
state.selectedIds = {};
|
|
3307
|
+
} else {
|
|
3308
|
+
// Select all with IDs
|
|
3309
|
+
state.messages.forEach(function (m) {
|
|
3310
|
+
if (m.id) state.selectedIds[m.id] = true;
|
|
3311
|
+
});
|
|
3312
|
+
}
|
|
3313
|
+
renderPanelMessages();
|
|
3314
|
+
});
|
|
3315
|
+
bar.appendChild(selectAllBtn);
|
|
3316
|
+
|
|
3317
|
+
var deleteBtn = document.createElement('button');
|
|
3318
|
+
deleteBtn.className = 'cumulus-selection-delete-btn';
|
|
3319
|
+
deleteBtn.textContent = 'Delete';
|
|
3320
|
+
deleteBtn.disabled = selectedCount === 0;
|
|
3321
|
+
deleteBtn.addEventListener('click', function () {
|
|
3322
|
+
if (selectedCount === 0) return;
|
|
3323
|
+
showDeleteMessagesConfirm(threadName, panel, Object.keys(state.selectedIds));
|
|
3324
|
+
});
|
|
3325
|
+
bar.appendChild(deleteBtn);
|
|
3326
|
+
|
|
3327
|
+
// Insert before input area
|
|
3328
|
+
var inputAreaEl = panel.querySelector('.cumulus-input-area');
|
|
3329
|
+
if (inputAreaEl) {
|
|
3330
|
+
panel.insertBefore(bar, inputAreaEl);
|
|
3331
|
+
} else {
|
|
3332
|
+
panel.appendChild(bar);
|
|
3333
|
+
}
|
|
3334
|
+
}
|
|
3335
|
+
|
|
3336
|
+
function toggleSelectionMode() {
|
|
3337
|
+
state.selectionMode = !state.selectionMode;
|
|
3338
|
+
state.selectedIds = {};
|
|
3339
|
+
selectBtn.textContent = state.selectionMode ? 'Done' : 'Select';
|
|
3340
|
+
if (state.selectionMode) {
|
|
3341
|
+
selectBtn.classList.add('active');
|
|
3342
|
+
} else {
|
|
3343
|
+
selectBtn.classList.remove('active');
|
|
3344
|
+
}
|
|
3345
|
+
renderPanelMessages();
|
|
3346
|
+
}
|
|
3347
|
+
|
|
3348
|
+
function showDeleteMessagesConfirm(tName, panelEl, ids) {
|
|
3349
|
+
// Remove any existing confirm dialog
|
|
3350
|
+
var existing = panelEl.querySelector('.cumulus-confirm-dialog');
|
|
3351
|
+
if (existing) existing.remove();
|
|
3352
|
+
|
|
3353
|
+
var dialog = document.createElement('div');
|
|
3354
|
+
dialog.className = 'cumulus-confirm-dialog';
|
|
3355
|
+
|
|
3356
|
+
var box = document.createElement('div');
|
|
3357
|
+
box.className = 'cumulus-confirm-dialog-box';
|
|
3358
|
+
|
|
3359
|
+
var title = document.createElement('div');
|
|
3360
|
+
title.className = 'cumulus-confirm-dialog-title';
|
|
3361
|
+
title.textContent = 'Delete Messages';
|
|
3362
|
+
box.appendChild(title);
|
|
3363
|
+
|
|
3364
|
+
var text = document.createElement('div');
|
|
3365
|
+
text.className = 'cumulus-confirm-dialog-text';
|
|
3366
|
+
text.textContent =
|
|
3367
|
+
ids.length + ' message' + (ids.length === 1 ? '' : 's') + ' will be permanently deleted.';
|
|
3368
|
+
box.appendChild(text);
|
|
3369
|
+
|
|
3370
|
+
var warn = document.createElement('div');
|
|
3371
|
+
warn.className = 'cumulus-confirm-dialog-warn';
|
|
3372
|
+
warn.textContent = 'This cannot be undone.';
|
|
3373
|
+
box.appendChild(warn);
|
|
3374
|
+
|
|
3375
|
+
var actions = document.createElement('div');
|
|
3376
|
+
actions.className = 'cumulus-confirm-dialog-actions';
|
|
3377
|
+
|
|
3378
|
+
var cancelBtn = document.createElement('button');
|
|
3379
|
+
cancelBtn.className = 'cumulus-confirm-dialog-cancel';
|
|
3380
|
+
cancelBtn.textContent = 'Cancel';
|
|
3381
|
+
cancelBtn.addEventListener('click', function () {
|
|
3382
|
+
dialog.remove();
|
|
3383
|
+
});
|
|
3384
|
+
actions.appendChild(cancelBtn);
|
|
3385
|
+
|
|
3386
|
+
var confirmBtn = document.createElement('button');
|
|
3387
|
+
confirmBtn.className = 'cumulus-confirm-dialog-confirm';
|
|
3388
|
+
confirmBtn.setAttribute('data-testid', 'webchat-delete-messages-confirm');
|
|
3389
|
+
confirmBtn.textContent = 'Delete';
|
|
3390
|
+
confirmBtn.addEventListener('click', function () {
|
|
3391
|
+
confirmBtn.disabled = true;
|
|
3392
|
+
confirmBtn.textContent = 'Deleting\u2026';
|
|
3393
|
+
if (connection) {
|
|
3394
|
+
connection.send({ type: 'delete_messages', threadName: tName, messageIds: ids });
|
|
3395
|
+
}
|
|
3396
|
+
dialog.remove();
|
|
3397
|
+
});
|
|
3398
|
+
actions.appendChild(confirmBtn);
|
|
3399
|
+
|
|
3400
|
+
box.appendChild(actions);
|
|
3401
|
+
dialog.appendChild(box);
|
|
3402
|
+
panelEl.appendChild(dialog);
|
|
2470
3403
|
}
|
|
2471
3404
|
|
|
2472
3405
|
function updatePanelSendBtn() {
|
|
@@ -2557,6 +3490,9 @@
|
|
|
2557
3490
|
state._renderMessages = renderPanelMessages;
|
|
2558
3491
|
state._updateSendBtn = updatePanelSendBtn;
|
|
2559
3492
|
|
|
3493
|
+
// Initial render (messages may already be loaded from a previous view)
|
|
3494
|
+
renderPanelMessages();
|
|
3495
|
+
|
|
2560
3496
|
// ── Send message for this panel ──
|
|
2561
3497
|
function sendPanelMessage() {
|
|
2562
3498
|
var text = inputEl.value.trim();
|
|
@@ -2568,6 +3504,7 @@
|
|
|
2568
3504
|
if (!text && state.pendingAttachments.length === 0) {
|
|
2569
3505
|
state.stopRequested = true;
|
|
2570
3506
|
state.streaming = false;
|
|
3507
|
+
clearStreamingTimeout(threadName);
|
|
2571
3508
|
updatePanelSendBtn();
|
|
2572
3509
|
if (state.streamBuffer) {
|
|
2573
3510
|
state.messages.push({ role: 'assistant', content: state.streamBuffer });
|
|
@@ -2601,6 +3538,7 @@
|
|
|
2601
3538
|
state.stopRequested = false;
|
|
2602
3539
|
state.streamBuffer = '';
|
|
2603
3540
|
state.interjecting = false;
|
|
3541
|
+
resetStreamingTimeout(threadName);
|
|
2604
3542
|
updatePanelSendBtn();
|
|
2605
3543
|
renderPanelMessages();
|
|
2606
3544
|
|
|
@@ -2749,14 +3687,12 @@
|
|
|
2749
3687
|
var filePath = addInput.value.trim();
|
|
2750
3688
|
if (!filePath) return;
|
|
2751
3689
|
connection &&
|
|
2752
|
-
connection.send(
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
})
|
|
2759
|
-
);
|
|
3690
|
+
connection.send({
|
|
3691
|
+
type: 'include_add',
|
|
3692
|
+
threadName: threadName,
|
|
3693
|
+
filePath: filePath,
|
|
3694
|
+
scope: currentScope,
|
|
3695
|
+
});
|
|
2760
3696
|
addInput.value = '';
|
|
2761
3697
|
});
|
|
2762
3698
|
|
|
@@ -2779,8 +3715,7 @@
|
|
|
2779
3715
|
panel.appendChild(overlay);
|
|
2780
3716
|
|
|
2781
3717
|
// Request the list
|
|
2782
|
-
connection &&
|
|
2783
|
-
connection.send(JSON.stringify({ type: 'include_list', threadName: threadName }));
|
|
3718
|
+
connection && connection.send({ type: 'include_list', threadName: threadName });
|
|
2784
3719
|
}
|
|
2785
3720
|
|
|
2786
3721
|
function renderIncludeList(panel, threadName, files) {
|
|
@@ -2817,13 +3752,11 @@
|
|
|
2817
3752
|
removeBtn.setAttribute('title', 'Remove');
|
|
2818
3753
|
removeBtn.addEventListener('click', function () {
|
|
2819
3754
|
connection &&
|
|
2820
|
-
connection.send(
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
})
|
|
2826
|
-
);
|
|
3755
|
+
connection.send({
|
|
3756
|
+
type: 'include_remove',
|
|
3757
|
+
threadName: threadName,
|
|
3758
|
+
filePath: f.path,
|
|
3759
|
+
});
|
|
2827
3760
|
});
|
|
2828
3761
|
item.appendChild(removeBtn);
|
|
2829
3762
|
|
|
@@ -2868,8 +3801,7 @@
|
|
|
2868
3801
|
panel.appendChild(overlay);
|
|
2869
3802
|
|
|
2870
3803
|
// Request the turn list
|
|
2871
|
-
connection &&
|
|
2872
|
-
connection.send(JSON.stringify({ type: 'revert_list', threadName: threadName }));
|
|
3804
|
+
connection && connection.send({ type: 'revert_list', threadName: threadName });
|
|
2873
3805
|
}
|
|
2874
3806
|
|
|
2875
3807
|
function renderRevertList(panel, threadName, turns) {
|
|
@@ -2948,8 +3880,7 @@
|
|
|
2948
3880
|
cancelBtn.textContent = 'Cancel';
|
|
2949
3881
|
cancelBtn.addEventListener('click', function () {
|
|
2950
3882
|
// Re-request list
|
|
2951
|
-
connection &&
|
|
2952
|
-
connection.send(JSON.stringify({ type: 'revert_list', threadName: threadName }));
|
|
3883
|
+
connection && connection.send({ type: 'revert_list', threadName: threadName });
|
|
2953
3884
|
});
|
|
2954
3885
|
actions.appendChild(cancelBtn);
|
|
2955
3886
|
|
|
@@ -2961,13 +3892,11 @@
|
|
|
2961
3892
|
revertBtn.disabled = true;
|
|
2962
3893
|
revertBtn.textContent = 'Reverting\u2026';
|
|
2963
3894
|
connection &&
|
|
2964
|
-
connection.send(
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
})
|
|
2970
|
-
);
|
|
3895
|
+
connection.send({
|
|
3896
|
+
type: 'revert_execute',
|
|
3897
|
+
threadName: threadName,
|
|
3898
|
+
targetMessageId: turn.userMessageId,
|
|
3899
|
+
});
|
|
2971
3900
|
});
|
|
2972
3901
|
actions.appendChild(revertBtn);
|
|
2973
3902
|
|
|
@@ -3072,8 +4001,26 @@
|
|
|
3072
4001
|
|
|
3073
4002
|
// ── Message router ──
|
|
3074
4003
|
function handleServerMessage(data) {
|
|
4004
|
+
if (data.type !== 'token') {
|
|
4005
|
+
console.log('[CumulusChat] WS recv:', data.type, data.threadName || '', data);
|
|
4006
|
+
}
|
|
3075
4007
|
switch (data.type) {
|
|
3076
4008
|
case 'auth_ok':
|
|
4009
|
+
// Reset streaming state on reconnect — server may have finished while we were disconnected
|
|
4010
|
+
Object.keys(threadStates).forEach(function (tn) {
|
|
4011
|
+
var s = threadStates[tn];
|
|
4012
|
+
if (s.streaming) {
|
|
4013
|
+
if (s.streamBuffer) {
|
|
4014
|
+
s.messages.push({ role: 'assistant', content: s.streamBuffer });
|
|
4015
|
+
s.streamBuffer = '';
|
|
4016
|
+
}
|
|
4017
|
+
s.streaming = false;
|
|
4018
|
+
s.stopRequested = false;
|
|
4019
|
+
s.interjecting = false;
|
|
4020
|
+
}
|
|
4021
|
+
// Reload history from server to pick up any responses we missed
|
|
4022
|
+
s.historyLoaded = false;
|
|
4023
|
+
});
|
|
3077
4024
|
showAppView();
|
|
3078
4025
|
registerPushNotifications();
|
|
3079
4026
|
break;
|
|
@@ -3117,12 +4064,50 @@
|
|
|
3117
4064
|
}
|
|
3118
4065
|
break;
|
|
3119
4066
|
|
|
4067
|
+
case 'thread_deleted':
|
|
4068
|
+
// Server confirms thread deleted; remove from sidebar and panels
|
|
4069
|
+
if (data.threadName) {
|
|
4070
|
+
allThreads = allThreads.filter(function (t) {
|
|
4071
|
+
return t.name !== data.threadName;
|
|
4072
|
+
});
|
|
4073
|
+
delete threadStates[data.threadName];
|
|
4074
|
+
removeThreadFromView(data.threadName);
|
|
4075
|
+
renderSidebar();
|
|
4076
|
+
}
|
|
4077
|
+
break;
|
|
4078
|
+
|
|
4079
|
+
case 'messages_deleted':
|
|
4080
|
+
// Server confirms messages deleted; reload history for that thread
|
|
4081
|
+
if (data.threadName) {
|
|
4082
|
+
var dState = getThreadState(data.threadName);
|
|
4083
|
+
dState.messages = [];
|
|
4084
|
+
dState.historyLoaded = false;
|
|
4085
|
+
dState.selectionMode = false;
|
|
4086
|
+
dState.selectedIds = {};
|
|
4087
|
+
// Exit selection mode on visible panel
|
|
4088
|
+
var dPanel = document.querySelector('[data-thread-panel="' + data.threadName + '"]');
|
|
4089
|
+
if (dPanel) {
|
|
4090
|
+
var dMsgs = dPanel.querySelector('.cumulus-messages');
|
|
4091
|
+
if (dMsgs) dMsgs.classList.remove('cumulus-selection-mode');
|
|
4092
|
+
var dBar = dPanel.querySelector('.cumulus-selection-bar');
|
|
4093
|
+
if (dBar) dBar.remove();
|
|
4094
|
+
var dSelectBtn = dPanel.querySelector('[data-testid="webchat-select-btn"]');
|
|
4095
|
+
if (dSelectBtn) {
|
|
4096
|
+
dSelectBtn.textContent = 'Select';
|
|
4097
|
+
dSelectBtn.classList.remove('active');
|
|
4098
|
+
}
|
|
4099
|
+
}
|
|
4100
|
+
connection &&
|
|
4101
|
+
connection.send({ type: 'history', threadName: data.threadName, limit: 200 });
|
|
4102
|
+
}
|
|
4103
|
+
break;
|
|
4104
|
+
|
|
3120
4105
|
case 'history':
|
|
3121
4106
|
if (data.threadName) {
|
|
3122
4107
|
var state = getThreadState(data.threadName);
|
|
3123
4108
|
if (data.messages && data.messages.length > 0) {
|
|
3124
4109
|
state.messages = data.messages.map(function (m) {
|
|
3125
|
-
return { id: m.id, role: m.role, content: m.content };
|
|
4110
|
+
return { id: m.id, role: m.role, content: m.content, timestamp: m.timestamp };
|
|
3126
4111
|
});
|
|
3127
4112
|
}
|
|
3128
4113
|
refreshThreadPanel(data.threadName);
|
|
@@ -3138,6 +4123,8 @@
|
|
|
3138
4123
|
state.streamBuffer = '';
|
|
3139
4124
|
}
|
|
3140
4125
|
state.streamBuffer += data.text;
|
|
4126
|
+
// Reset streaming timeout on each token
|
|
4127
|
+
resetStreamingTimeout(data.threadName);
|
|
3141
4128
|
// Update lastActivity for this thread in allThreads
|
|
3142
4129
|
updateThreadActivity(data.threadName);
|
|
3143
4130
|
refreshThreadPanel(data.threadName);
|
|
@@ -3150,6 +4137,7 @@
|
|
|
3150
4137
|
|
|
3151
4138
|
case 'done':
|
|
3152
4139
|
if (data.threadName) {
|
|
4140
|
+
clearStreamingTimeout(data.threadName);
|
|
3153
4141
|
var state = getThreadState(data.threadName);
|
|
3154
4142
|
if (state.stopRequested) {
|
|
3155
4143
|
state.stopRequested = false;
|
|
@@ -3176,11 +4164,12 @@
|
|
|
3176
4164
|
case 'interjected':
|
|
3177
4165
|
// Server confirms the old stream was aborted due to interjection.
|
|
3178
4166
|
// The partial response was already frozen client-side in sendPanelMessage.
|
|
3179
|
-
|
|
4167
|
+
if (data.threadName) clearStreamingTimeout(data.threadName);
|
|
3180
4168
|
break;
|
|
3181
4169
|
|
|
3182
4170
|
case 'error':
|
|
3183
4171
|
if (data.threadName) {
|
|
4172
|
+
clearStreamingTimeout(data.threadName);
|
|
3184
4173
|
var state = getThreadState(data.threadName);
|
|
3185
4174
|
state.stopRequested = false;
|
|
3186
4175
|
state.streaming = false;
|
|
@@ -3201,6 +4190,22 @@
|
|
|
3201
4190
|
}
|
|
3202
4191
|
break;
|
|
3203
4192
|
|
|
4193
|
+
case 'new_messages':
|
|
4194
|
+
// Agent inject completed — append new messages to the thread and re-render
|
|
4195
|
+
if (data.threadName && data.messages && data.messages.length > 0) {
|
|
4196
|
+
var state = getThreadState(data.threadName);
|
|
4197
|
+
for (var nm = 0; nm < data.messages.length; nm++) {
|
|
4198
|
+
state.messages.push({
|
|
4199
|
+
id: data.messages[nm].id,
|
|
4200
|
+
role: data.messages[nm].role,
|
|
4201
|
+
content: data.messages[nm].content,
|
|
4202
|
+
});
|
|
4203
|
+
}
|
|
4204
|
+
updateThreadActivity(data.threadName);
|
|
4205
|
+
refreshThreadPanel(data.threadName);
|
|
4206
|
+
}
|
|
4207
|
+
break;
|
|
4208
|
+
|
|
3204
4209
|
// ── Include/Revert responses ──
|
|
3205
4210
|
case 'include_list':
|
|
3206
4211
|
if (data.threadName && data.files) {
|
|
@@ -3233,13 +4238,11 @@
|
|
|
3233
4238
|
tState.historyLoaded = false;
|
|
3234
4239
|
// Re-request history
|
|
3235
4240
|
connection &&
|
|
3236
|
-
connection.send(
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
})
|
|
3242
|
-
);
|
|
4241
|
+
connection.send({
|
|
4242
|
+
type: 'history',
|
|
4243
|
+
threadName: data.threadName,
|
|
4244
|
+
limit: 200,
|
|
4245
|
+
});
|
|
3243
4246
|
if (overlay) overlay.remove();
|
|
3244
4247
|
} else {
|
|
3245
4248
|
// Show error in overlay
|
|
@@ -3263,6 +4266,10 @@
|
|
|
3263
4266
|
break;
|
|
3264
4267
|
}
|
|
3265
4268
|
}
|
|
4269
|
+
// Mark unread if thread is not currently visible
|
|
4270
|
+
if (visibleThreads.indexOf(threadName) === -1) {
|
|
4271
|
+
unreadThreads[threadName] = true;
|
|
4272
|
+
}
|
|
3266
4273
|
// Re-render sidebar to reflect new sort order / move thread up
|
|
3267
4274
|
renderSidebar();
|
|
3268
4275
|
}
|
|
@@ -3415,5 +4422,7 @@
|
|
|
3415
4422
|
}
|
|
3416
4423
|
|
|
3417
4424
|
window.CumulusChat = widget;
|
|
4425
|
+
window.CumulusChat.version = '0.27.15';
|
|
4426
|
+
console.log('[CumulusChat] Widget loaded, version ' + window.CumulusChat.version);
|
|
3418
4427
|
}
|
|
3419
4428
|
})();
|