@luckydraw/cumulus 0.27.13 → 0.27.15

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.
@@ -2,7 +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.12
5
+ * @version 0.27.15
6
6
  *
7
7
  * < 80KB unminified.
8
8
  */
@@ -155,6 +155,38 @@
155
155
  ' word-wrap: break-word;',
156
156
  '}',
157
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
+
158
190
  /* ── Markdown rendered inside assistant messages ── */
159
191
  '.cumulus-msg.assistant p { margin: 0.4em 0; }',
160
192
  '.cumulus-msg.assistant p:first-child { margin-top: 0; }',
@@ -686,6 +718,81 @@
686
718
  ' border-radius: 3px; font-family: inherit; white-space: nowrap;',
687
719
  '}',
688
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
+ /* ── Sidebar thread delete button ── */
724
+ '.cumulus-thread-item { position: relative; }',
725
+ '.cumulus-thread-delete-btn {',
726
+ ' display: none; position: absolute; right: 6px; top: 50%; transform: translateY(-50%);',
727
+ ' background: none; border: none; color: #666;',
728
+ ' cursor: pointer; font-size: 14px; padding: 2px 5px; line-height: 1;',
729
+ ' border-radius: 3px; z-index: 1;',
730
+ '}',
731
+ '.cumulus-thread-item:hover .cumulus-thread-delete-btn { display: block; }',
732
+ '.cumulus-thread-delete-btn:hover { color: #ef4444; background: rgba(239,68,68,0.1); }',
733
+
734
+ /* ── Message checkbox (selection mode) ── */
735
+ '.cumulus-msg-checkbox {',
736
+ ' display: none; flex-shrink: 0; align-self: flex-start; margin-top: 0.4em;',
737
+ ' width: 16px; height: 16px; cursor: pointer; accent-color: #0066cc;',
738
+ '}',
739
+ '.cumulus-selection-mode .cumulus-msg-checkbox { display: block; }',
740
+ '.cumulus-selection-mode .cumulus-msg-row {',
741
+ ' flex-direction: row; align-items: flex-start; gap: 8px;',
742
+ '}',
743
+ '.cumulus-selection-mode .cumulus-msg-row > *:not(.cumulus-msg-checkbox) { flex: 1; min-width: 0; }',
744
+
745
+ /* ── Selection action bar (floats at bottom of messages area) ── */
746
+ '.cumulus-selection-bar {',
747
+ ' display: flex; align-items: center; gap: 8px;',
748
+ ' padding: 8px 12px;',
749
+ ' background: #2d2d2d; border-top: 1px solid #3a3a3a;',
750
+ ' flex-shrink: 0;',
751
+ '}',
752
+ '.cumulus-selection-count { font-size: 13px; color: #aaa; flex: 1; }',
753
+ '.cumulus-selection-select-all-btn {',
754
+ ' background: none; border: 1px solid #555; color: #ccc;',
755
+ ' padding: 4px 10px; border-radius: 4px; font-size: 12px;',
756
+ ' cursor: pointer; font-family: inherit;',
757
+ '}',
758
+ '.cumulus-selection-select-all-btn:hover { border-color: #888; color: #e0e0e0; }',
759
+ '.cumulus-selection-delete-btn {',
760
+ ' background: #8b2222; border: none; color: white;',
761
+ ' padding: 4px 12px; border-radius: 4px; font-size: 12px;',
762
+ ' cursor: pointer; font-family: inherit; font-weight: 600;',
763
+ '}',
764
+ '.cumulus-selection-delete-btn:hover:not(:disabled) { background: #a33; }',
765
+ '.cumulus-selection-delete-btn:disabled { background: #3a2a2a; color: #666; cursor: not-allowed; }',
766
+
767
+ /* ── Confirm dialog overlay ── */
768
+ '.cumulus-confirm-dialog {',
769
+ ' position: absolute; top: 0; left: 0; right: 0; bottom: 0;',
770
+ ' background: rgba(0,0,0,0.6); z-index: 20;',
771
+ ' display: flex; align-items: center; justify-content: center;',
772
+ '}',
773
+ '.cumulus-confirm-dialog-box {',
774
+ ' background: #2d2d2d; border: 1px solid #4a4a4a; border-radius: 8px;',
775
+ ' padding: 1.5em; max-width: 320px; width: 90%;',
776
+ ' display: flex; flex-direction: column; gap: 1em; text-align: center;',
777
+ ' box-shadow: 0 8px 32px rgba(0,0,0,0.5);',
778
+ '}',
779
+ '.cumulus-confirm-dialog-title { font-weight: 600; font-size: 15px; color: #e0e0e0; }',
780
+ '.cumulus-confirm-dialog-text { font-size: 13px; color: #aaa; }',
781
+ '.cumulus-confirm-dialog-warn { font-size: 12px; color: #f59e0b; }',
782
+ '.cumulus-confirm-dialog-actions { display: flex; gap: 8px; justify-content: center; }',
783
+ '.cumulus-confirm-dialog-cancel {',
784
+ ' padding: 0.4em 1.2em; border-radius: 4px; cursor: pointer;',
785
+ ' font-size: 13px; border: 1px solid #3a3a3a; background: #2a2a2a; color: #ccc;',
786
+ ' font-family: inherit;',
787
+ '}',
788
+ '.cumulus-confirm-dialog-cancel:hover { background: #3a3a3a; }',
789
+ '.cumulus-confirm-dialog-confirm {',
790
+ ' padding: 0.4em 1.2em; border-radius: 4px; cursor: pointer;',
791
+ ' font-size: 13px; border: none; background: #8b2222; color: white;',
792
+ ' font-family: inherit; font-weight: 600;',
793
+ '}',
794
+ '.cumulus-confirm-dialog-confirm:hover { background: #a33; }',
795
+ '.cumulus-confirm-dialog-confirm:disabled { background: #3a2a2a; color: #666; cursor: not-allowed; }',
689
796
 
690
797
  /* ── Overlay (covers the thread panel) ── */
691
798
  '.cumulus-overlay {',
@@ -921,6 +1028,67 @@
921
1028
  // else, then reinsert code blocks. This prevents markdown patterns inside
922
1029
  // code from being processed.
923
1030
 
1031
+ // ── Agent message detection + timestamps ──────────────────────────────────
1032
+ var AGENT_MSG_RE = /^\[([^\]]+)\s*\u2192\s*([^\]]+)\]:\s*/; // [sender → recipient]:
1033
+ var AGENT_COLORS = ['#9966cc', '#2aa198', '#e69500', '#d33682', '#85c025', '#ff6b6b'];
1034
+
1035
+ function getAgentColor(name) {
1036
+ var hash = 0;
1037
+ for (var i = 0; i < name.length; i++) hash = ((hash << 5) - hash + name.charCodeAt(i)) | 0;
1038
+ return AGENT_COLORS[Math.abs(hash) % AGENT_COLORS.length];
1039
+ }
1040
+
1041
+ function parseAgentMessage(content) {
1042
+ var match = content.match(AGENT_MSG_RE);
1043
+ if (!match) return null;
1044
+ var body = content.slice(match[0].length).replace(/\n\n\(Reply using send_to_agent\([^)]*\)[^)]*\)\s*$/, '').trim();
1045
+ // Also strip CC/broadcast reply hints
1046
+ body = body.replace(/\n\n\(You are CC'd on this message[^)]*\)\s*$/, '').trim();
1047
+ body = body.replace(/\n\n\(Review the above messages[^)]*\)\s*$/, '').trim();
1048
+ return { sender: match[1].trim(), recipient: match[2].trim(), body: body };
1049
+ }
1050
+
1051
+ function formatTime(timestamp) {
1052
+ if (!timestamp) return null;
1053
+ var d = new Date(timestamp);
1054
+ return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
1055
+ }
1056
+
1057
+ function appendTimestamp(parent, timestamp) {
1058
+ var time = formatTime(timestamp);
1059
+ if (time) {
1060
+ var timeEl = document.createElement('div');
1061
+ timeEl.className = 'cumulus-msg-time';
1062
+ timeEl.textContent = time;
1063
+ parent.appendChild(timeEl);
1064
+ }
1065
+ }
1066
+
1067
+ function buildAgentMsgEl(agent) {
1068
+ var el = document.createElement('div');
1069
+ el.className = 'cumulus-msg agent';
1070
+ el.style.setProperty('--agent-color', getAgentColor(agent.sender));
1071
+
1072
+ var header = document.createElement('div');
1073
+ header.className = 'cumulus-agent-header';
1074
+ var nameEl = document.createElement('span');
1075
+ nameEl.className = 'cumulus-agent-name';
1076
+ nameEl.textContent = agent.sender;
1077
+ header.appendChild(nameEl);
1078
+ var ctx = document.createElement('span');
1079
+ ctx.className = 'cumulus-agent-context';
1080
+ ctx.textContent = '\u2192 ' + agent.recipient;
1081
+ header.appendChild(ctx);
1082
+ el.appendChild(header);
1083
+
1084
+ var body = document.createElement('div');
1085
+ body.innerHTML = renderMarkdown(agent.body);
1086
+ el.appendChild(body);
1087
+ return el;
1088
+ }
1089
+
1090
+ // ── Markdown ──────────────────────────────────────────────────────────────
1091
+
924
1092
  function renderMarkdown(text) {
925
1093
  // Phase 1: Extract fenced code blocks — replace with unique tokens
926
1094
  var codeBlocks = [];
@@ -1336,6 +1504,8 @@
1336
1504
  var connectionStatus = 'disconnected';
1337
1505
  var authenticated = false;
1338
1506
  var pendingAttachments = [];
1507
+ var selectionMode = false;
1508
+ var selectedIds = {};
1339
1509
 
1340
1510
  // ── Panel ──
1341
1511
  var panel = document.createElement('div');
@@ -1352,6 +1522,9 @@
1352
1522
  '<span class="cumulus-status-dot" data-testid="webchat-status-dot"></span>' +
1353
1523
  '<span data-testid="webchat-status-text">Disconnected</span>' +
1354
1524
  '</span>' +
1525
+ '<div class="cumulus-panel-actions">' +
1526
+ '<button class="cumulus-panel-action-btn" data-testid="webchat-select-btn" title="Select messages to delete">Select</button>' +
1527
+ '</div>' +
1355
1528
  '<button class="cumulus-close-btn" data-testid="webchat-close">&times;</button>';
1356
1529
  panel.appendChild(header);
1357
1530
 
@@ -1450,7 +1623,7 @@
1450
1623
 
1451
1624
  // ── Rendering ──
1452
1625
  function scrollToBottom() {
1453
- requestAnimationFrame(function() {
1626
+ requestAnimationFrame(function () {
1454
1627
  messagesEl.scrollTop = messagesEl.scrollHeight;
1455
1628
  });
1456
1629
  }
@@ -1521,20 +1694,51 @@
1521
1694
 
1522
1695
  function renderMessages() {
1523
1696
  messagesEl.innerHTML = '';
1697
+ // Apply selection mode class
1698
+ if (selectionMode) {
1699
+ messagesEl.classList.add('cumulus-selection-mode');
1700
+ } else {
1701
+ messagesEl.classList.remove('cumulus-selection-mode');
1702
+ }
1524
1703
  if (messages.length === 0 && !streaming) {
1525
1704
  messagesEl.innerHTML =
1526
1705
  '<div class="cumulus-empty">Send a message to start chatting</div>';
1706
+ updateEmbeddedSelectionBar();
1527
1707
  return;
1528
1708
  }
1529
1709
  for (var i = 0; i < messages.length; i++) {
1530
1710
  var msg = messages[i];
1531
1711
  var row = document.createElement('div');
1532
1712
  row.className = 'cumulus-msg-row';
1713
+ // Add checkbox in selection mode
1714
+ if (selectionMode && msg.id) {
1715
+ var cb = document.createElement('input');
1716
+ cb.type = 'checkbox';
1717
+ cb.className = 'cumulus-msg-checkbox';
1718
+ cb.checked = !!selectedIds[msg.id];
1719
+ (function (msgId) {
1720
+ cb.addEventListener('change', function () {
1721
+ if (cb.checked) {
1722
+ selectedIds[msgId] = true;
1723
+ } else {
1724
+ delete selectedIds[msgId];
1725
+ }
1726
+ updateEmbeddedSelectionBar();
1727
+ });
1728
+ })(msg.id);
1729
+ row.appendChild(cb);
1730
+ }
1533
1731
  if (msg.role === 'user') {
1534
- row.appendChild(buildUserMsgEl(msg));
1732
+ var agent = parseAgentMessage(msg.content);
1733
+ if (agent) {
1734
+ row.appendChild(buildAgentMsgEl(agent));
1735
+ } else {
1736
+ row.appendChild(buildUserMsgEl(msg));
1737
+ }
1535
1738
  } else {
1536
1739
  row.appendChild(buildAssistantMsgEl(msg.content, false));
1537
1740
  }
1741
+ appendTimestamp(row, msg.timestamp);
1538
1742
  messagesEl.appendChild(row);
1539
1743
  }
1540
1744
  if (streaming) {
@@ -1544,6 +1748,109 @@
1544
1748
  messagesEl.appendChild(row);
1545
1749
  }
1546
1750
  scrollToBottom();
1751
+ updateEmbeddedSelectionBar();
1752
+ }
1753
+
1754
+ function updateEmbeddedSelectionBar() {
1755
+ var existingBar = panel.querySelector('.cumulus-selection-bar');
1756
+ if (existingBar) existingBar.remove();
1757
+ if (!selectionMode) return;
1758
+
1759
+ var selectedCount = Object.keys(selectedIds).length;
1760
+ var selectableCount = messages.filter(function (m) { return !!m.id; }).length;
1761
+
1762
+ var bar = document.createElement('div');
1763
+ bar.className = 'cumulus-selection-bar';
1764
+
1765
+ var countEl = document.createElement('span');
1766
+ countEl.className = 'cumulus-selection-count';
1767
+ countEl.textContent = selectedCount + ' selected';
1768
+ bar.appendChild(countEl);
1769
+
1770
+ var selectAllBtn = document.createElement('button');
1771
+ selectAllBtn.className = 'cumulus-selection-select-all-btn';
1772
+ selectAllBtn.textContent = selectedCount === selectableCount && selectableCount > 0 ? 'Deselect All' : 'Select All';
1773
+ selectAllBtn.addEventListener('click', function () {
1774
+ if (selectedCount === selectableCount && selectableCount > 0) {
1775
+ selectedIds = {};
1776
+ } else {
1777
+ messages.forEach(function (m) {
1778
+ if (m.id) selectedIds[m.id] = true;
1779
+ });
1780
+ }
1781
+ renderMessages();
1782
+ });
1783
+ bar.appendChild(selectAllBtn);
1784
+
1785
+ var deleteBtn = document.createElement('button');
1786
+ deleteBtn.className = 'cumulus-selection-delete-btn';
1787
+ deleteBtn.textContent = 'Delete';
1788
+ deleteBtn.disabled = selectedCount === 0;
1789
+ deleteBtn.addEventListener('click', function () {
1790
+ if (selectedCount === 0) return;
1791
+ showEmbeddedDeleteMessagesConfirm(Object.keys(selectedIds));
1792
+ });
1793
+ bar.appendChild(deleteBtn);
1794
+
1795
+ var inputAreaEl = panel.querySelector('.cumulus-input-area');
1796
+ if (inputAreaEl) {
1797
+ panel.insertBefore(bar, inputAreaEl);
1798
+ } else {
1799
+ panel.appendChild(bar);
1800
+ }
1801
+ }
1802
+
1803
+ function showEmbeddedDeleteMessagesConfirm(ids) {
1804
+ var existing = panel.querySelector('.cumulus-confirm-dialog');
1805
+ if (existing) existing.remove();
1806
+
1807
+ var dialog = document.createElement('div');
1808
+ dialog.className = 'cumulus-confirm-dialog';
1809
+
1810
+ var box = document.createElement('div');
1811
+ box.className = 'cumulus-confirm-dialog-box';
1812
+
1813
+ var title = document.createElement('div');
1814
+ title.className = 'cumulus-confirm-dialog-title';
1815
+ title.textContent = 'Delete Messages';
1816
+ box.appendChild(title);
1817
+
1818
+ var text = document.createElement('div');
1819
+ text.className = 'cumulus-confirm-dialog-text';
1820
+ text.textContent = ids.length + ' message' + (ids.length === 1 ? '' : 's') + ' will be permanently deleted.';
1821
+ box.appendChild(text);
1822
+
1823
+ var warn = document.createElement('div');
1824
+ warn.className = 'cumulus-confirm-dialog-warn';
1825
+ warn.textContent = 'This cannot be undone.';
1826
+ box.appendChild(warn);
1827
+
1828
+ var actions = document.createElement('div');
1829
+ actions.className = 'cumulus-confirm-dialog-actions';
1830
+
1831
+ var cancelBtn = document.createElement('button');
1832
+ cancelBtn.className = 'cumulus-confirm-dialog-cancel';
1833
+ cancelBtn.textContent = 'Cancel';
1834
+ cancelBtn.addEventListener('click', function () { dialog.remove(); });
1835
+ actions.appendChild(cancelBtn);
1836
+
1837
+ var confirmBtn = document.createElement('button');
1838
+ confirmBtn.className = 'cumulus-confirm-dialog-confirm';
1839
+ confirmBtn.setAttribute('data-testid', 'webchat-delete-messages-confirm');
1840
+ confirmBtn.textContent = 'Delete';
1841
+ confirmBtn.addEventListener('click', function () {
1842
+ confirmBtn.disabled = true;
1843
+ confirmBtn.textContent = 'Deleting\u2026';
1844
+ if (connection) {
1845
+ connection.send({ type: 'delete_messages', threadName: sessionId, messageIds: ids });
1846
+ }
1847
+ dialog.remove();
1848
+ });
1849
+ actions.appendChild(confirmBtn);
1850
+
1851
+ box.appendChild(actions);
1852
+ dialog.appendChild(box);
1853
+ panel.appendChild(dialog);
1547
1854
  }
1548
1855
 
1549
1856
  function updateSendBtn() {
@@ -1640,7 +1947,7 @@
1640
1947
  case 'history':
1641
1948
  if (data.messages && data.messages.length > 0) {
1642
1949
  messages = data.messages.map(function (m) {
1643
- return { role: m.role, content: m.content };
1950
+ return { id: m.id, role: m.role, content: m.content, timestamp: m.timestamp };
1644
1951
  });
1645
1952
  renderMessages();
1646
1953
  }
@@ -1705,6 +2012,21 @@
1705
2012
  renderMessages();
1706
2013
  }
1707
2014
  break;
2015
+ case 'messages_deleted':
2016
+ // Server confirmed deletion — reload history and exit selection mode
2017
+ selectionMode = false;
2018
+ selectedIds = {};
2019
+ var embSelectBtn = panel.querySelector('[data-testid="webchat-select-btn"]');
2020
+ if (embSelectBtn) {
2021
+ embSelectBtn.textContent = 'Select';
2022
+ embSelectBtn.classList.remove('active');
2023
+ }
2024
+ messages = [];
2025
+ // Re-request history
2026
+ if (connection) {
2027
+ connection.send({ type: 'history', threadName: sessionId, limit: 200 });
2028
+ }
2029
+ break;
1708
2030
  }
1709
2031
  }
1710
2032
 
@@ -1820,6 +2142,22 @@
1820
2142
  });
1821
2143
  }
1822
2144
 
2145
+ // Select button for embedded mode
2146
+ var embSelectBtn = panel.querySelector('[data-testid="webchat-select-btn"]');
2147
+ if (embSelectBtn) {
2148
+ embSelectBtn.addEventListener('click', function () {
2149
+ selectionMode = !selectionMode;
2150
+ selectedIds = {};
2151
+ embSelectBtn.textContent = selectionMode ? 'Done' : 'Select';
2152
+ if (selectionMode) {
2153
+ embSelectBtn.classList.add('active');
2154
+ } else {
2155
+ embSelectBtn.classList.remove('active');
2156
+ }
2157
+ renderMessages();
2158
+ });
2159
+ }
2160
+
1823
2161
  // Embedded mode: no auth panel handling needed (API key from attribute)
1824
2162
  function startConnection() {
1825
2163
  if (connection) {
@@ -1880,6 +2218,8 @@
1880
2218
  interjecting: false,
1881
2219
  pendingAttachments: [],
1882
2220
  historyLoaded: false,
2221
+ selectionMode: false,
2222
+ selectedIds: {},
1883
2223
  };
1884
2224
  }
1885
2225
  return threadStates[threadName];
@@ -2167,6 +2507,18 @@
2167
2507
  item.appendChild(countEl);
2168
2508
  }
2169
2509
 
2510
+ // Delete button (revealed on hover via CSS)
2511
+ var deleteBtn = document.createElement('button');
2512
+ deleteBtn.className = 'cumulus-thread-delete-btn';
2513
+ deleteBtn.setAttribute('title', 'Delete thread');
2514
+ deleteBtn.setAttribute('data-testid', 'webchat-thread-delete');
2515
+ deleteBtn.textContent = '\u2715';
2516
+ deleteBtn.addEventListener('click', function (e) {
2517
+ e.stopPropagation();
2518
+ showThreadDeleteConfirm(name, item);
2519
+ });
2520
+ item.appendChild(deleteBtn);
2521
+
2170
2522
  item.addEventListener('click', function (e) {
2171
2523
  var multiSelect = e.metaKey || e.ctrlKey;
2172
2524
  if (multiSelect) {
@@ -2179,6 +2531,75 @@
2179
2531
  return item;
2180
2532
  }
2181
2533
 
2534
+ // ── Thread delete confirmation ──
2535
+ function showThreadDeleteConfirm(threadName, anchorEl) {
2536
+ // Remove any existing confirm dialogs
2537
+ var existing = document.querySelector('.cumulus-confirm-dialog[data-context="thread-delete"]');
2538
+ if (existing) {
2539
+ existing.remove();
2540
+ return;
2541
+ }
2542
+
2543
+ // Build inline confirm dialog attached to the sidebar scroll area
2544
+ var dialog = document.createElement('div');
2545
+ dialog.className = 'cumulus-confirm-dialog';
2546
+ dialog.setAttribute('data-context', 'thread-delete');
2547
+ dialog.style.cssText = '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;';
2548
+
2549
+ var box = document.createElement('div');
2550
+ box.className = 'cumulus-confirm-dialog-box';
2551
+
2552
+ var title = document.createElement('div');
2553
+ title.className = 'cumulus-confirm-dialog-title';
2554
+ title.textContent = 'Delete Thread';
2555
+ box.appendChild(title);
2556
+
2557
+ var text = document.createElement('div');
2558
+ text.className = 'cumulus-confirm-dialog-text';
2559
+ text.textContent = 'Delete "' + threadName + '"?';
2560
+ box.appendChild(text);
2561
+
2562
+ var warn = document.createElement('div');
2563
+ warn.className = 'cumulus-confirm-dialog-warn';
2564
+ warn.textContent = 'All messages and history will be permanently removed.';
2565
+ box.appendChild(warn);
2566
+
2567
+ var actions = document.createElement('div');
2568
+ actions.className = 'cumulus-confirm-dialog-actions';
2569
+
2570
+ var cancelBtn = document.createElement('button');
2571
+ cancelBtn.className = 'cumulus-confirm-dialog-cancel';
2572
+ cancelBtn.textContent = 'Cancel';
2573
+ cancelBtn.addEventListener('click', function () {
2574
+ dialog.remove();
2575
+ });
2576
+ actions.appendChild(cancelBtn);
2577
+
2578
+ var confirmBtn = document.createElement('button');
2579
+ confirmBtn.className = 'cumulus-confirm-dialog-confirm';
2580
+ confirmBtn.setAttribute('data-testid', 'webchat-thread-delete-confirm');
2581
+ confirmBtn.textContent = 'Delete';
2582
+ confirmBtn.addEventListener('click', function () {
2583
+ confirmBtn.disabled = true;
2584
+ confirmBtn.textContent = 'Deleting\u2026';
2585
+ if (connection) {
2586
+ connection.send({ type: 'delete_thread', threadName: threadName });
2587
+ }
2588
+ dialog.remove();
2589
+ });
2590
+ actions.appendChild(confirmBtn);
2591
+
2592
+ box.appendChild(actions);
2593
+ dialog.appendChild(box);
2594
+
2595
+ // Dismiss on backdrop click
2596
+ dialog.addEventListener('click', function (e) {
2597
+ if (e.target === dialog) dialog.remove();
2598
+ });
2599
+
2600
+ document.body.appendChild(dialog);
2601
+ }
2602
+
2182
2603
  // ── Panel management ──
2183
2604
  function soloThread(name) {
2184
2605
  visibleThreads = [name];
@@ -2319,6 +2740,16 @@
2319
2740
  });
2320
2741
  actions.appendChild(revertBtn);
2321
2742
 
2743
+ var selectBtn = document.createElement('button');
2744
+ selectBtn.className = 'cumulus-panel-action-btn';
2745
+ selectBtn.setAttribute('data-testid', 'webchat-select-btn');
2746
+ selectBtn.setAttribute('title', 'Select messages to delete');
2747
+ selectBtn.textContent = 'Select';
2748
+ selectBtn.addEventListener('click', function () {
2749
+ toggleSelectionMode();
2750
+ });
2751
+ actions.appendChild(selectBtn);
2752
+
2322
2753
  panelHeader.appendChild(actions);
2323
2754
 
2324
2755
  var closeBtn = document.createElement('button');
@@ -2387,8 +2818,8 @@
2387
2818
 
2388
2819
  // ── Panel-local render functions ──
2389
2820
  function scrollToBottom() {
2390
- requestAnimationFrame(function() {
2391
- requestAnimationFrame(function() {
2821
+ requestAnimationFrame(function () {
2822
+ requestAnimationFrame(function () {
2392
2823
  messagesEl.scrollTop = messagesEl.scrollHeight;
2393
2824
  });
2394
2825
  });
@@ -2460,20 +2891,51 @@
2460
2891
 
2461
2892
  function renderPanelMessages() {
2462
2893
  messagesEl.innerHTML = '';
2894
+ // Apply selection mode class to messages container
2895
+ if (state.selectionMode) {
2896
+ messagesEl.classList.add('cumulus-selection-mode');
2897
+ } else {
2898
+ messagesEl.classList.remove('cumulus-selection-mode');
2899
+ }
2463
2900
  if (state.messages.length === 0 && !state.streaming) {
2464
2901
  messagesEl.innerHTML =
2465
2902
  '<div class="cumulus-empty">Send a message to start chatting</div>';
2903
+ updateSelectionBar();
2466
2904
  return;
2467
2905
  }
2468
2906
  for (var i = 0; i < state.messages.length; i++) {
2469
2907
  var msg = state.messages[i];
2470
2908
  var row = document.createElement('div');
2471
2909
  row.className = 'cumulus-msg-row';
2910
+ // Add checkbox if in selection mode and message has an id
2911
+ if (state.selectionMode && msg.id) {
2912
+ var cb = document.createElement('input');
2913
+ cb.type = 'checkbox';
2914
+ cb.className = 'cumulus-msg-checkbox';
2915
+ cb.checked = !!state.selectedIds[msg.id];
2916
+ (function (msgId) {
2917
+ cb.addEventListener('change', function () {
2918
+ if (cb.checked) {
2919
+ state.selectedIds[msgId] = true;
2920
+ } else {
2921
+ delete state.selectedIds[msgId];
2922
+ }
2923
+ updateSelectionBar();
2924
+ });
2925
+ })(msg.id);
2926
+ row.appendChild(cb);
2927
+ }
2472
2928
  if (msg.role === 'user') {
2473
- row.appendChild(buildUserMsgEl(msg));
2929
+ var agent = parseAgentMessage(msg.content);
2930
+ if (agent) {
2931
+ row.appendChild(buildAgentMsgEl(agent));
2932
+ } else {
2933
+ row.appendChild(buildUserMsgEl(msg));
2934
+ }
2474
2935
  } else {
2475
2936
  row.appendChild(buildAssistantMsgEl(msg.content, false));
2476
2937
  }
2938
+ appendTimestamp(row, msg.timestamp);
2477
2939
  messagesEl.appendChild(row);
2478
2940
  }
2479
2941
  if (state.streaming) {
@@ -2483,6 +2945,128 @@
2483
2945
  messagesEl.appendChild(row);
2484
2946
  }
2485
2947
  scrollToBottom();
2948
+ updateSelectionBar();
2949
+ }
2950
+
2951
+ function updateSelectionBar() {
2952
+ // Remove existing bar
2953
+ var existingBar = panel.querySelector('.cumulus-selection-bar');
2954
+ if (existingBar) existingBar.remove();
2955
+ if (!state.selectionMode) return;
2956
+
2957
+ var selectedCount = Object.keys(state.selectedIds).length;
2958
+ var selectableCount = state.messages.filter(function (m) { return !!m.id; }).length;
2959
+
2960
+ var bar = document.createElement('div');
2961
+ bar.className = 'cumulus-selection-bar';
2962
+
2963
+ var countEl = document.createElement('span');
2964
+ countEl.className = 'cumulus-selection-count';
2965
+ countEl.textContent = selectedCount + ' selected';
2966
+ bar.appendChild(countEl);
2967
+
2968
+ var selectAllBtn = document.createElement('button');
2969
+ selectAllBtn.className = 'cumulus-selection-select-all-btn';
2970
+ selectAllBtn.textContent = selectedCount === selectableCount && selectableCount > 0 ? 'Deselect All' : 'Select All';
2971
+ selectAllBtn.addEventListener('click', function () {
2972
+ if (selectedCount === selectableCount && selectableCount > 0) {
2973
+ // Deselect all
2974
+ state.selectedIds = {};
2975
+ } else {
2976
+ // Select all with IDs
2977
+ state.messages.forEach(function (m) {
2978
+ if (m.id) state.selectedIds[m.id] = true;
2979
+ });
2980
+ }
2981
+ renderPanelMessages();
2982
+ });
2983
+ bar.appendChild(selectAllBtn);
2984
+
2985
+ var deleteBtn = document.createElement('button');
2986
+ deleteBtn.className = 'cumulus-selection-delete-btn';
2987
+ deleteBtn.textContent = 'Delete';
2988
+ deleteBtn.disabled = selectedCount === 0;
2989
+ deleteBtn.addEventListener('click', function () {
2990
+ if (selectedCount === 0) return;
2991
+ showDeleteMessagesConfirm(threadName, panel, Object.keys(state.selectedIds));
2992
+ });
2993
+ bar.appendChild(deleteBtn);
2994
+
2995
+ // Insert before input area
2996
+ var inputAreaEl = panel.querySelector('.cumulus-input-area');
2997
+ if (inputAreaEl) {
2998
+ panel.insertBefore(bar, inputAreaEl);
2999
+ } else {
3000
+ panel.appendChild(bar);
3001
+ }
3002
+ }
3003
+
3004
+ function toggleSelectionMode() {
3005
+ state.selectionMode = !state.selectionMode;
3006
+ state.selectedIds = {};
3007
+ selectBtn.textContent = state.selectionMode ? 'Done' : 'Select';
3008
+ if (state.selectionMode) {
3009
+ selectBtn.classList.add('active');
3010
+ } else {
3011
+ selectBtn.classList.remove('active');
3012
+ }
3013
+ renderPanelMessages();
3014
+ }
3015
+
3016
+ function showDeleteMessagesConfirm(tName, panelEl, ids) {
3017
+ // Remove any existing confirm dialog
3018
+ var existing = panelEl.querySelector('.cumulus-confirm-dialog');
3019
+ if (existing) existing.remove();
3020
+
3021
+ var dialog = document.createElement('div');
3022
+ dialog.className = 'cumulus-confirm-dialog';
3023
+
3024
+ var box = document.createElement('div');
3025
+ box.className = 'cumulus-confirm-dialog-box';
3026
+
3027
+ var title = document.createElement('div');
3028
+ title.className = 'cumulus-confirm-dialog-title';
3029
+ title.textContent = 'Delete Messages';
3030
+ box.appendChild(title);
3031
+
3032
+ var text = document.createElement('div');
3033
+ text.className = 'cumulus-confirm-dialog-text';
3034
+ text.textContent = ids.length + ' message' + (ids.length === 1 ? '' : 's') + ' will be permanently deleted.';
3035
+ box.appendChild(text);
3036
+
3037
+ var warn = document.createElement('div');
3038
+ warn.className = 'cumulus-confirm-dialog-warn';
3039
+ warn.textContent = 'This cannot be undone.';
3040
+ box.appendChild(warn);
3041
+
3042
+ var actions = document.createElement('div');
3043
+ actions.className = 'cumulus-confirm-dialog-actions';
3044
+
3045
+ var cancelBtn = document.createElement('button');
3046
+ cancelBtn.className = 'cumulus-confirm-dialog-cancel';
3047
+ cancelBtn.textContent = 'Cancel';
3048
+ cancelBtn.addEventListener('click', function () {
3049
+ dialog.remove();
3050
+ });
3051
+ actions.appendChild(cancelBtn);
3052
+
3053
+ var confirmBtn = document.createElement('button');
3054
+ confirmBtn.className = 'cumulus-confirm-dialog-confirm';
3055
+ confirmBtn.setAttribute('data-testid', 'webchat-delete-messages-confirm');
3056
+ confirmBtn.textContent = 'Delete';
3057
+ confirmBtn.addEventListener('click', function () {
3058
+ confirmBtn.disabled = true;
3059
+ confirmBtn.textContent = 'Deleting\u2026';
3060
+ if (connection) {
3061
+ connection.send({ type: 'delete_messages', threadName: tName, messageIds: ids });
3062
+ }
3063
+ dialog.remove();
3064
+ });
3065
+ actions.appendChild(confirmBtn);
3066
+
3067
+ box.appendChild(actions);
3068
+ dialog.appendChild(box);
3069
+ panelEl.appendChild(dialog);
2486
3070
  }
2487
3071
 
2488
3072
  function updatePanelSendBtn() {
@@ -3052,12 +3636,50 @@
3052
3636
  }
3053
3637
  break;
3054
3638
 
3639
+ case 'thread_deleted':
3640
+ // Server confirms thread deleted; remove from sidebar and panels
3641
+ if (data.threadName) {
3642
+ allThreads = allThreads.filter(function (t) {
3643
+ return t.name !== data.threadName;
3644
+ });
3645
+ delete threadStates[data.threadName];
3646
+ removeThreadFromView(data.threadName);
3647
+ renderSidebar();
3648
+ }
3649
+ break;
3650
+
3651
+ case 'messages_deleted':
3652
+ // Server confirms messages deleted; reload history for that thread
3653
+ if (data.threadName) {
3654
+ var dState = getThreadState(data.threadName);
3655
+ dState.messages = [];
3656
+ dState.historyLoaded = false;
3657
+ dState.selectionMode = false;
3658
+ dState.selectedIds = {};
3659
+ // Exit selection mode on visible panel
3660
+ var dPanel = document.querySelector('[data-thread-panel="' + data.threadName + '"]');
3661
+ if (dPanel) {
3662
+ var dMsgs = dPanel.querySelector('.cumulus-messages');
3663
+ if (dMsgs) dMsgs.classList.remove('cumulus-selection-mode');
3664
+ var dBar = dPanel.querySelector('.cumulus-selection-bar');
3665
+ if (dBar) dBar.remove();
3666
+ var dSelectBtn = dPanel.querySelector('[data-testid="webchat-select-btn"]');
3667
+ if (dSelectBtn) {
3668
+ dSelectBtn.textContent = 'Select';
3669
+ dSelectBtn.classList.remove('active');
3670
+ }
3671
+ }
3672
+ connection &&
3673
+ connection.send({ type: 'history', threadName: data.threadName, limit: 200 });
3674
+ }
3675
+ break;
3676
+
3055
3677
  case 'history':
3056
3678
  if (data.threadName) {
3057
3679
  var state = getThreadState(data.threadName);
3058
3680
  if (data.messages && data.messages.length > 0) {
3059
3681
  state.messages = data.messages.map(function (m) {
3060
- return { id: m.id, role: m.role, content: m.content };
3682
+ return { id: m.id, role: m.role, content: m.content, timestamp: m.timestamp };
3061
3683
  });
3062
3684
  }
3063
3685
  refreshThreadPanel(data.threadName);
@@ -3141,7 +3763,11 @@
3141
3763
  if (data.threadName && data.messages && data.messages.length > 0) {
3142
3764
  var state = getThreadState(data.threadName);
3143
3765
  for (var nm = 0; nm < data.messages.length; nm++) {
3144
- state.messages.push({ id: data.messages[nm].id, role: data.messages[nm].role, content: data.messages[nm].content });
3766
+ state.messages.push({
3767
+ id: data.messages[nm].id,
3768
+ role: data.messages[nm].role,
3769
+ content: data.messages[nm].content,
3770
+ });
3145
3771
  }
3146
3772
  updateThreadActivity(data.threadName);
3147
3773
  refreshThreadPanel(data.threadName);
@@ -3362,7 +3988,7 @@
3362
3988
  }
3363
3989
 
3364
3990
  window.CumulusChat = widget;
3365
- window.CumulusChat.version = '0.27.12';
3991
+ window.CumulusChat.version = '0.27.15';
3366
3992
  console.log('[CumulusChat] Widget loaded, version ' + window.CumulusChat.version);
3367
3993
  }
3368
3994
  })();