@luckydraw/cumulus 0.28.3 → 0.28.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/gateway/adapters/webchat.d.ts.map +1 -1
- package/dist/gateway/adapters/webchat.js +25 -7
- package/dist/gateway/adapters/webchat.js.map +1 -1
- package/dist/gateway/gateway-agents-mcp.js +87 -0
- package/dist/gateway/gateway-agents-mcp.js.map +1 -1
- package/dist/gateway/static/blex.min.js +53 -13
- package/dist/gateway/static/chat.html +5 -0
- package/dist/gateway/static/icon-192.png +0 -0
- package/dist/gateway/static/icon-192.svg +4 -0
- package/dist/gateway/static/manifest.json +15 -0
- package/dist/gateway/static/widget.js +276 -64
- package/package.json +1 -1
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
|
6
|
+
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
7
|
+
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
|
8
|
+
<meta name="apple-mobile-web-app-title" content="Cumulus">
|
|
9
|
+
<link rel="manifest" href="/manifest.json">
|
|
10
|
+
<link rel="apple-touch-icon" href="/icon-192.png">
|
|
6
11
|
<title>Cumulus Chat</title>
|
|
7
12
|
<style>
|
|
8
13
|
*, *::before, *::after { box-sizing: border-box; }
|
|
Binary file
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="192" height="192" viewBox="0 0 192 192">
|
|
2
|
+
<rect width="192" height="192" rx="32" fill="#2d2d2d"/>
|
|
3
|
+
<text x="96" y="120" font-family="system-ui, -apple-system, sans-serif" font-size="100" font-weight="700" fill="#e0e0e0" text-anchor="middle">C</text>
|
|
4
|
+
</svg>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Cumulus Chat",
|
|
3
|
+
"short_name": "Cumulus",
|
|
4
|
+
"start_url": "/chat",
|
|
5
|
+
"display": "standalone",
|
|
6
|
+
"background_color": "#1e1e1e",
|
|
7
|
+
"theme_color": "#1e1e1e",
|
|
8
|
+
"icons": [
|
|
9
|
+
{
|
|
10
|
+
"src": "/icon-192.png",
|
|
11
|
+
"sizes": "192x192",
|
|
12
|
+
"type": "image/png"
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
}
|
|
@@ -234,10 +234,13 @@
|
|
|
234
234
|
' display: inline-flex; align-items: center; gap: 0.3em;',
|
|
235
235
|
' background: #2a3a4a; color: #d4e8f8; border: 1px solid #3a5060;',
|
|
236
236
|
' border-radius: 1em; padding: 0.15em 0.6em; font-size: 0.82em;',
|
|
237
|
-
' cursor: default; max-width: 32ch;
|
|
237
|
+
' cursor: default; max-width: 32ch;',
|
|
238
|
+
'}',
|
|
239
|
+
'.cumulus-interaction-chip .chip-label {',
|
|
240
|
+
' overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;',
|
|
238
241
|
'}',
|
|
239
242
|
'.cumulus-interaction-chip .chip-dismiss {',
|
|
240
|
-
' cursor: pointer; opacity: 0.6; font-size: 0.9em; margin-left: 0.2em;',
|
|
243
|
+
' cursor: pointer; opacity: 0.6; font-size: 0.9em; margin-left: 0.2em; flex-shrink: 0;',
|
|
241
244
|
'}',
|
|
242
245
|
'.cumulus-interaction-chip .chip-dismiss:hover { opacity: 1; }',
|
|
243
246
|
|
|
@@ -246,10 +249,13 @@
|
|
|
246
249
|
' display: inline-flex; align-items: center; gap: 0.3em;',
|
|
247
250
|
' background: #2a3a2a; color: #b8e6b8; border: 1px solid #3a6040;',
|
|
248
251
|
' border-radius: 1em; padding: 0.15em 0.6em; font-size: 0.82em;',
|
|
249
|
-
' cursor: default; max-width: 40ch;
|
|
252
|
+
' cursor: default; max-width: 40ch;',
|
|
253
|
+
'}',
|
|
254
|
+
'.cumulus-annotation-chip .chip-label {',
|
|
255
|
+
' overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;',
|
|
250
256
|
'}',
|
|
251
257
|
'.cumulus-annotation-chip .chip-dismiss {',
|
|
252
|
-
' cursor: pointer; opacity: 0.6; font-size: 0.9em; margin-left: 0.2em;',
|
|
258
|
+
' cursor: pointer; opacity: 0.6; font-size: 0.9em; margin-left: 0.2em; flex-shrink: 0;',
|
|
253
259
|
'}',
|
|
254
260
|
'.cumulus-annotation-chip .chip-dismiss:hover { opacity: 1; }',
|
|
255
261
|
|
|
@@ -1226,6 +1232,26 @@
|
|
|
1226
1232
|
// WeakMap: DOM element -> BlockHandle[] for lifecycle management
|
|
1227
1233
|
var blexHandles = typeof WeakMap !== 'undefined' ? new WeakMap() : null;
|
|
1228
1234
|
|
|
1235
|
+
// Per-thread store for blex interaction values (poll answers, confirm clicks, etc.)
|
|
1236
|
+
// Key: "threadName" → Map<"msgTimestamp:blockIdx", interactionValue>
|
|
1237
|
+
// Backed by localStorage for persistence across page refreshes
|
|
1238
|
+
var BLEX_STORE_KEY = 'cumulus-blex-interactions';
|
|
1239
|
+
var blexInteractionStore = {};
|
|
1240
|
+
try {
|
|
1241
|
+
var saved = localStorage.getItem(BLEX_STORE_KEY);
|
|
1242
|
+
if (saved) blexInteractionStore = JSON.parse(saved);
|
|
1243
|
+
} catch (e) {
|
|
1244
|
+
/* ignore corrupt/missing localStorage */
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
function _flushBlexStore() {
|
|
1248
|
+
try {
|
|
1249
|
+
localStorage.setItem(BLEX_STORE_KEY, JSON.stringify(blexInteractionStore));
|
|
1250
|
+
} catch (e) {
|
|
1251
|
+
/* quota exceeded or private browsing */
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1229
1255
|
// Extract ~~~blex:TYPE\n{json}\n~~~ fences from text
|
|
1230
1256
|
// Returns { text: string (with placeholders), blocks: Array<{type, json, idx}> }
|
|
1231
1257
|
function extractBlexBlocks(text) {
|
|
@@ -1240,18 +1266,23 @@
|
|
|
1240
1266
|
}
|
|
1241
1267
|
|
|
1242
1268
|
// Render blex blocks into placeholder divs within a message element
|
|
1243
|
-
function renderBlexBlocks(el, blexBlocks, isStreaming) {
|
|
1269
|
+
function renderBlexBlocks(el, blexBlocks, isStreaming, msgKey, threadName) {
|
|
1244
1270
|
if (!blexBlocks || blexBlocks.length === 0) return;
|
|
1245
1271
|
if (typeof Blex === 'undefined') {
|
|
1246
1272
|
console.warn('[blex] Blex library not loaded — blex.min.js may have failed to fetch');
|
|
1247
1273
|
return;
|
|
1248
1274
|
}
|
|
1249
1275
|
var placeholders = el.querySelectorAll('.blex-block-container');
|
|
1276
|
+
// Get the interaction store for this thread
|
|
1277
|
+
var store = threadName
|
|
1278
|
+
? blexInteractionStore[threadName] || (blexInteractionStore[threadName] = {})
|
|
1279
|
+
: null;
|
|
1250
1280
|
|
|
1251
1281
|
placeholders.forEach(function (container) {
|
|
1252
1282
|
var idx = parseInt(container.getAttribute('data-blex-idx'), 10);
|
|
1253
1283
|
if (isNaN(idx) || idx >= blexBlocks.length) return;
|
|
1254
1284
|
var block = blexBlocks[idx];
|
|
1285
|
+
var storeKey = msgKey ? msgKey + ':' + idx : null;
|
|
1255
1286
|
|
|
1256
1287
|
try {
|
|
1257
1288
|
var data = JSON.parse(block.json);
|
|
@@ -1261,8 +1292,14 @@
|
|
|
1261
1292
|
// During streaming, show placeholder skeleton
|
|
1262
1293
|
Blex.renderPlaceholder(block.type, container);
|
|
1263
1294
|
} else {
|
|
1295
|
+
// Check for previously stored interaction value
|
|
1296
|
+
var renderOpts = {};
|
|
1297
|
+
if (store && storeKey && store[storeKey]) {
|
|
1298
|
+
renderOpts.previousValue = store[storeKey];
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1264
1301
|
// Final render — full interactive block
|
|
1265
|
-
Blex.renderBlock(blockObj, container)
|
|
1302
|
+
Blex.renderBlock(blockObj, container, renderOpts)
|
|
1266
1303
|
.then(function (handle) {
|
|
1267
1304
|
// Store handle immediately (inside async callback, not after sync loop)
|
|
1268
1305
|
if (blexHandles) {
|
|
@@ -1273,6 +1310,12 @@
|
|
|
1273
1310
|
if (handle && handle.onInteraction) {
|
|
1274
1311
|
// Wire interaction handler
|
|
1275
1312
|
handle.onInteraction(function (interaction) {
|
|
1313
|
+
// Store the interaction value for persistence across re-renders + refreshes
|
|
1314
|
+
if (store && storeKey && interaction.value !== undefined) {
|
|
1315
|
+
store[storeKey] = interaction.value;
|
|
1316
|
+
_flushBlexStore();
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1276
1319
|
// Find the panel — try el.closest first, fall back to document query
|
|
1277
1320
|
// (el may be detached from DOM if renderPanelMessages rebuilt the message list)
|
|
1278
1321
|
var panel = el.closest('.cumulus-thread-panel') || el.closest('.cumulus-panel');
|
|
@@ -1287,8 +1330,11 @@
|
|
|
1287
1330
|
|
|
1288
1331
|
if (inputEl && interaction.serialized) {
|
|
1289
1332
|
if (interaction.immediate) {
|
|
1290
|
-
// Immediate:
|
|
1291
|
-
|
|
1333
|
+
// Immediate: prepend any existing input text, then auto-send
|
|
1334
|
+
var existingText = inputEl.value.trim();
|
|
1335
|
+
inputEl.value = existingText
|
|
1336
|
+
? existingText + '\n\n' + interaction.serialized
|
|
1337
|
+
: interaction.serialized;
|
|
1292
1338
|
inputEl.dispatchEvent(new Event('input', { bubbles: true }));
|
|
1293
1339
|
// Find and click the send button
|
|
1294
1340
|
var sendBtn = panel ? panel.querySelector('[data-testid*="send"]') : null;
|
|
@@ -1296,8 +1342,8 @@
|
|
|
1296
1342
|
sendBtn.click();
|
|
1297
1343
|
}
|
|
1298
1344
|
} else {
|
|
1299
|
-
// Deferred: add to chip tray
|
|
1300
|
-
addInteractionChip(panel, interaction, handle);
|
|
1345
|
+
// Deferred: add to chip tray (sourceId deduplicates rapid-fire from same block)
|
|
1346
|
+
addInteractionChip(panel, interaction, handle, block.type + '-' + idx);
|
|
1301
1347
|
}
|
|
1302
1348
|
}
|
|
1303
1349
|
});
|
|
@@ -1337,16 +1383,27 @@
|
|
|
1337
1383
|
}
|
|
1338
1384
|
|
|
1339
1385
|
// Add a deferred interaction chip to the chip tray
|
|
1340
|
-
|
|
1386
|
+
// If a chip from the same source already exists, replace it (prevents spam from rapid interactions)
|
|
1387
|
+
function addInteractionChip(panel, interaction, handle, sourceId) {
|
|
1341
1388
|
if (!panel) return;
|
|
1342
1389
|
var tray = panel.querySelector('.cumulus-chip-tray');
|
|
1343
1390
|
if (!tray) return;
|
|
1344
1391
|
|
|
1392
|
+
// Replace existing chip from same source instead of appending
|
|
1393
|
+
if (sourceId) {
|
|
1394
|
+
var existing = tray.querySelector(
|
|
1395
|
+
'.cumulus-interaction-chip[data-source="' + sourceId + '"]'
|
|
1396
|
+
);
|
|
1397
|
+
if (existing) existing.remove();
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1345
1400
|
var chip = document.createElement('span');
|
|
1346
1401
|
chip.className = 'cumulus-interaction-chip';
|
|
1347
1402
|
chip.setAttribute('data-serialized', interaction.serialized || '');
|
|
1403
|
+
if (sourceId) chip.setAttribute('data-source', sourceId);
|
|
1348
1404
|
|
|
1349
1405
|
var label = document.createElement('span');
|
|
1406
|
+
label.className = 'chip-label';
|
|
1350
1407
|
label.textContent =
|
|
1351
1408
|
(interaction.icon || '\u2022') +
|
|
1352
1409
|
' ' +
|
|
@@ -1389,29 +1446,53 @@
|
|
|
1389
1446
|
// ── Inline Annotations (Google Docs-style highlight + comment) ──────────
|
|
1390
1447
|
|
|
1391
1448
|
// Add an annotation chip to the chip tray
|
|
1392
|
-
function addAnnotationChip(panel, quote, comment) {
|
|
1449
|
+
function addAnnotationChip(panel, quote, comment, isCodeBlock, codeLang) {
|
|
1393
1450
|
if (!panel) return;
|
|
1394
1451
|
var tray = panel.querySelector('.cumulus-chip-tray');
|
|
1395
1452
|
if (!tray) return;
|
|
1396
1453
|
|
|
1397
1454
|
var truncatedQuote = quote.length > 30 ? quote.substring(0, 30) + '\u2026' : quote;
|
|
1398
1455
|
var truncatedComment = comment.length > 30 ? comment.substring(0, 30) + '\u2026' : comment;
|
|
1399
|
-
var serialized
|
|
1456
|
+
var serialized;
|
|
1457
|
+
if (isCodeBlock) {
|
|
1458
|
+
serialized =
|
|
1459
|
+
'```' +
|
|
1460
|
+
(codeLang && codeLang !== 'text' ? codeLang : '') +
|
|
1461
|
+
'\n' +
|
|
1462
|
+
quote +
|
|
1463
|
+
'\n```\n' +
|
|
1464
|
+
comment;
|
|
1465
|
+
} else {
|
|
1466
|
+
serialized = '> ' + quote.replace(/\n/g, '\n> ') + '\n' + comment;
|
|
1467
|
+
}
|
|
1400
1468
|
|
|
1401
1469
|
var chip = document.createElement('span');
|
|
1402
1470
|
chip.className = 'cumulus-annotation-chip';
|
|
1403
1471
|
chip.setAttribute('data-serialized', serialized);
|
|
1472
|
+
chip.setAttribute('data-quote', quote);
|
|
1473
|
+
chip.setAttribute('data-comment', comment);
|
|
1474
|
+
chip.setAttribute('data-is-code', isCodeBlock ? '1' : '');
|
|
1475
|
+
chip.setAttribute('data-code-lang', codeLang || '');
|
|
1404
1476
|
chip.title = '"' + quote + '"\n\n' + comment;
|
|
1405
1477
|
|
|
1406
1478
|
var label = document.createElement('span');
|
|
1479
|
+
label.className = 'chip-label';
|
|
1407
1480
|
label.textContent =
|
|
1408
1481
|
'\uD83D\uDCDD \u201C' + truncatedQuote + '\u201D \u2192 ' + truncatedComment;
|
|
1409
1482
|
chip.appendChild(label);
|
|
1410
1483
|
|
|
1484
|
+
// Click label to edit
|
|
1485
|
+
label.style.cursor = 'pointer';
|
|
1486
|
+
label.addEventListener('click', function (e) {
|
|
1487
|
+
e.stopPropagation();
|
|
1488
|
+
showAnnotationEditPopover(panel, chip);
|
|
1489
|
+
});
|
|
1490
|
+
|
|
1411
1491
|
var dismiss = document.createElement('span');
|
|
1412
1492
|
dismiss.className = 'chip-dismiss';
|
|
1413
1493
|
dismiss.textContent = '\u00d7';
|
|
1414
|
-
dismiss.addEventListener('click', function () {
|
|
1494
|
+
dismiss.addEventListener('click', function (e) {
|
|
1495
|
+
e.stopPropagation();
|
|
1415
1496
|
chip.remove();
|
|
1416
1497
|
});
|
|
1417
1498
|
chip.appendChild(dismiss);
|
|
@@ -1419,8 +1500,106 @@
|
|
|
1419
1500
|
tray.appendChild(chip);
|
|
1420
1501
|
}
|
|
1421
1502
|
|
|
1503
|
+
// Show the annotation popover near a chip for editing
|
|
1504
|
+
function showAnnotationEditPopover(panel, chip) {
|
|
1505
|
+
dismissAnnotationPopover(panel);
|
|
1506
|
+
|
|
1507
|
+
var quote = chip.getAttribute('data-quote') || '';
|
|
1508
|
+
var comment = chip.getAttribute('data-comment') || '';
|
|
1509
|
+
var isCodeBlock = chip.getAttribute('data-is-code') === '1';
|
|
1510
|
+
var codeLang = chip.getAttribute('data-code-lang') || '';
|
|
1511
|
+
|
|
1512
|
+
var popover = document.createElement('div');
|
|
1513
|
+
popover.className = 'cumulus-annotation-popover';
|
|
1514
|
+
|
|
1515
|
+
// Position near the chip
|
|
1516
|
+
var panelRect = panel.getBoundingClientRect();
|
|
1517
|
+
var chipRect = chip.getBoundingClientRect();
|
|
1518
|
+
var top = chipRect.top - panelRect.top + panel.scrollTop - 160;
|
|
1519
|
+
if (top < 4) top = chipRect.bottom - panelRect.top + panel.scrollTop + 4;
|
|
1520
|
+
var left = chipRect.left - panelRect.left;
|
|
1521
|
+
if (left + 260 > panelRect.width) left = panelRect.width - 270;
|
|
1522
|
+
if (left < 4) left = 4;
|
|
1523
|
+
popover.style.top = top + 'px';
|
|
1524
|
+
popover.style.left = left + 'px';
|
|
1525
|
+
|
|
1526
|
+
var quoteEl = document.createElement('div');
|
|
1527
|
+
quoteEl.className = 'annotation-quote';
|
|
1528
|
+
quoteEl.textContent = '\u201C' + quote + '\u201D';
|
|
1529
|
+
popover.appendChild(quoteEl);
|
|
1530
|
+
|
|
1531
|
+
var textarea = document.createElement('textarea');
|
|
1532
|
+
textarea.placeholder = 'Edit comment\u2026';
|
|
1533
|
+
textarea.rows = 4;
|
|
1534
|
+
textarea.value = comment;
|
|
1535
|
+
textarea.setAttribute('data-testid', 'annotation-edit-comment');
|
|
1536
|
+
popover.appendChild(textarea);
|
|
1537
|
+
|
|
1538
|
+
var actions = document.createElement('div');
|
|
1539
|
+
actions.className = 'annotation-actions';
|
|
1540
|
+
|
|
1541
|
+
var cancelBtn = document.createElement('button');
|
|
1542
|
+
cancelBtn.className = 'annotation-cancel';
|
|
1543
|
+
cancelBtn.textContent = 'Cancel';
|
|
1544
|
+
cancelBtn.addEventListener('click', function () {
|
|
1545
|
+
dismissAnnotationPopover(panel);
|
|
1546
|
+
});
|
|
1547
|
+
|
|
1548
|
+
var submitBtn = document.createElement('button');
|
|
1549
|
+
submitBtn.className = 'annotation-submit';
|
|
1550
|
+
submitBtn.textContent = 'Update';
|
|
1551
|
+
submitBtn.setAttribute('data-testid', 'annotation-edit-submit');
|
|
1552
|
+
submitBtn.addEventListener('click', function () {
|
|
1553
|
+
var newComment = textarea.value.trim();
|
|
1554
|
+
if (!newComment) {
|
|
1555
|
+
dismissAnnotationPopover(panel);
|
|
1556
|
+
return;
|
|
1557
|
+
}
|
|
1558
|
+
// Update chip data
|
|
1559
|
+
var newSerialized;
|
|
1560
|
+
if (isCodeBlock) {
|
|
1561
|
+
newSerialized =
|
|
1562
|
+
'```' +
|
|
1563
|
+
(codeLang && codeLang !== 'text' ? codeLang : '') +
|
|
1564
|
+
'\n' +
|
|
1565
|
+
quote +
|
|
1566
|
+
'\n```\n' +
|
|
1567
|
+
newComment;
|
|
1568
|
+
} else {
|
|
1569
|
+
newSerialized = '> ' + quote.replace(/\n/g, '\n> ') + '\n' + newComment;
|
|
1570
|
+
}
|
|
1571
|
+
chip.setAttribute('data-serialized', newSerialized);
|
|
1572
|
+
chip.setAttribute('data-comment', newComment);
|
|
1573
|
+
var truncQ = quote.length > 30 ? quote.substring(0, 30) + '\u2026' : quote;
|
|
1574
|
+
var truncC = newComment.length > 30 ? newComment.substring(0, 30) + '\u2026' : newComment;
|
|
1575
|
+
var labelEl = chip.querySelector('.chip-label');
|
|
1576
|
+
if (labelEl) {
|
|
1577
|
+
labelEl.textContent = '\uD83D\uDCDD \u201C' + truncQ + '\u201D \u2192 ' + truncC;
|
|
1578
|
+
}
|
|
1579
|
+
chip.title = '"' + quote + '"\n\n' + newComment;
|
|
1580
|
+
dismissAnnotationPopover(panel);
|
|
1581
|
+
});
|
|
1582
|
+
|
|
1583
|
+
actions.appendChild(cancelBtn);
|
|
1584
|
+
actions.appendChild(submitBtn);
|
|
1585
|
+
popover.appendChild(actions);
|
|
1586
|
+
|
|
1587
|
+
textarea.addEventListener('keydown', function (e) {
|
|
1588
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
1589
|
+
e.preventDefault();
|
|
1590
|
+
submitBtn.click();
|
|
1591
|
+
} else if (e.key === 'Escape') {
|
|
1592
|
+
dismissAnnotationPopover(panel);
|
|
1593
|
+
}
|
|
1594
|
+
});
|
|
1595
|
+
|
|
1596
|
+
panel.appendChild(popover);
|
|
1597
|
+
textarea.focus();
|
|
1598
|
+
textarea.setSelectionRange(textarea.value.length, textarea.value.length);
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1422
1601
|
// Show the annotation popover near a text selection
|
|
1423
|
-
function showAnnotationPopover(panel, selectedText, anchorRect) {
|
|
1602
|
+
function showAnnotationPopover(panel, selectedText, anchorRect, isCodeBlock, codeLang) {
|
|
1424
1603
|
// Remove any existing popover
|
|
1425
1604
|
dismissAnnotationPopover(panel);
|
|
1426
1605
|
|
|
@@ -1469,7 +1648,7 @@
|
|
|
1469
1648
|
submitBtn.addEventListener('click', function () {
|
|
1470
1649
|
var comment = textarea.value.trim();
|
|
1471
1650
|
if (comment) {
|
|
1472
|
-
addAnnotationChip(panel, selectedText, comment);
|
|
1651
|
+
addAnnotationChip(panel, selectedText, comment, isCodeBlock, codeLang);
|
|
1473
1652
|
}
|
|
1474
1653
|
dismissAnnotationPopover(panel);
|
|
1475
1654
|
window.getSelection().removeAllRanges();
|
|
@@ -1519,10 +1698,22 @@
|
|
|
1519
1698
|
var msgEl = container.closest ? container.closest('.cumulus-msg.assistant') : null;
|
|
1520
1699
|
if (!msgEl) return;
|
|
1521
1700
|
|
|
1701
|
+
// Detect if selection is inside a code block
|
|
1702
|
+
var codeAncestor = container.closest ? container.closest('pre, code') : null;
|
|
1703
|
+
var isCodeBlock = !!codeAncestor;
|
|
1704
|
+
var codeLang = '';
|
|
1705
|
+
if (isCodeBlock) {
|
|
1706
|
+
var wrapper = container.closest ? container.closest('.code-block-wrapper') : null;
|
|
1707
|
+
if (wrapper) {
|
|
1708
|
+
var langEl = wrapper.querySelector('.code-block-language');
|
|
1709
|
+
if (langEl) codeLang = langEl.textContent.trim();
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1522
1713
|
// Get anchor position for the popover
|
|
1523
1714
|
var rect = range.getBoundingClientRect();
|
|
1524
1715
|
|
|
1525
|
-
showAnnotationPopover(panel, selectedText, rect);
|
|
1716
|
+
showAnnotationPopover(panel, selectedText, rect, isCodeBlock, codeLang);
|
|
1526
1717
|
}, 10);
|
|
1527
1718
|
});
|
|
1528
1719
|
|
|
@@ -2168,7 +2359,8 @@
|
|
|
2168
2359
|
function buildUserMsgEl(msg) {
|
|
2169
2360
|
var el = document.createElement('div');
|
|
2170
2361
|
el.className = 'cumulus-msg user' + (isWideUserMessage(msg.content) ? ' wide' : '');
|
|
2171
|
-
|
|
2362
|
+
var mdResult = renderMarkdown(msg.content);
|
|
2363
|
+
el.innerHTML = mdResult.html;
|
|
2172
2364
|
if (msg.attachments && msg.attachments.length > 0) {
|
|
2173
2365
|
var attRow = document.createElement('div');
|
|
2174
2366
|
attRow.className = 'cumulus-msg-attachments';
|
|
@@ -2193,7 +2385,7 @@
|
|
|
2193
2385
|
return el;
|
|
2194
2386
|
}
|
|
2195
2387
|
|
|
2196
|
-
function buildAssistantMsgEl(content, isStreaming) {
|
|
2388
|
+
function buildAssistantMsgEl(content, isStreaming, msgKey) {
|
|
2197
2389
|
var el = document.createElement('div');
|
|
2198
2390
|
el.className = 'cumulus-msg assistant';
|
|
2199
2391
|
if (isStreaming) el.setAttribute('data-testid', 'webchat-streaming');
|
|
@@ -2205,7 +2397,7 @@
|
|
|
2205
2397
|
}
|
|
2206
2398
|
// Render blex blocks — skip during streaming to avoid destroy/recreate churn
|
|
2207
2399
|
if (!isStreaming) {
|
|
2208
|
-
renderBlexBlocks(el, mdResult.blexBlocks, false);
|
|
2400
|
+
renderBlexBlocks(el, mdResult.blexBlocks, false, msgKey, threadName);
|
|
2209
2401
|
}
|
|
2210
2402
|
el.querySelectorAll('.code-block-copy-btn').forEach(function (btn) {
|
|
2211
2403
|
btn.addEventListener('click', function () {
|
|
@@ -2296,7 +2488,7 @@
|
|
|
2296
2488
|
row.appendChild(buildUserMsgEl(msg));
|
|
2297
2489
|
}
|
|
2298
2490
|
} else {
|
|
2299
|
-
row.appendChild(buildAssistantMsgEl(msg.content, false));
|
|
2491
|
+
row.appendChild(buildAssistantMsgEl(msg.content, false, String(msg.timestamp)));
|
|
2300
2492
|
}
|
|
2301
2493
|
appendTimestamp(row, msg.timestamp);
|
|
2302
2494
|
messagesEl.appendChild(row);
|
|
@@ -3733,7 +3925,8 @@
|
|
|
3733
3925
|
function buildUserMsgEl(msg) {
|
|
3734
3926
|
var el = document.createElement('div');
|
|
3735
3927
|
el.className = 'cumulus-msg user' + (isWideUserMessage(msg.content) ? ' wide' : '');
|
|
3736
|
-
|
|
3928
|
+
var mdResult = renderMarkdown(msg.content);
|
|
3929
|
+
el.innerHTML = mdResult.html;
|
|
3737
3930
|
if (msg.attachments && msg.attachments.length > 0) {
|
|
3738
3931
|
var attRow = document.createElement('div');
|
|
3739
3932
|
attRow.className = 'cumulus-msg-attachments';
|
|
@@ -3758,7 +3951,7 @@
|
|
|
3758
3951
|
return el;
|
|
3759
3952
|
}
|
|
3760
3953
|
|
|
3761
|
-
function buildAssistantMsgEl(content, isStreaming) {
|
|
3954
|
+
function buildAssistantMsgEl(content, isStreaming, msgKey) {
|
|
3762
3955
|
var el = document.createElement('div');
|
|
3763
3956
|
el.className = 'cumulus-msg assistant';
|
|
3764
3957
|
if (isStreaming) el.setAttribute('data-testid', 'webchat-streaming');
|
|
@@ -3770,7 +3963,7 @@
|
|
|
3770
3963
|
}
|
|
3771
3964
|
// Render blex blocks — skip during streaming to avoid destroy/recreate churn
|
|
3772
3965
|
if (!isStreaming) {
|
|
3773
|
-
renderBlexBlocks(el, mdResult.blexBlocks, false);
|
|
3966
|
+
renderBlexBlocks(el, mdResult.blexBlocks, false, msgKey, threadName);
|
|
3774
3967
|
}
|
|
3775
3968
|
el.querySelectorAll('.code-block-copy-btn').forEach(function (btn) {
|
|
3776
3969
|
btn.addEventListener('click', function () {
|
|
@@ -3853,7 +4046,7 @@
|
|
|
3853
4046
|
row.appendChild(buildUserMsgEl(msg));
|
|
3854
4047
|
}
|
|
3855
4048
|
} else {
|
|
3856
|
-
row.appendChild(buildAssistantMsgEl(msg.content, false));
|
|
4049
|
+
row.appendChild(buildAssistantMsgEl(msg.content, false, String(msg.timestamp)));
|
|
3857
4050
|
}
|
|
3858
4051
|
appendTimestamp(row, msg.timestamp);
|
|
3859
4052
|
messagesEl.appendChild(row);
|
|
@@ -4129,13 +4322,15 @@
|
|
|
4129
4322
|
return;
|
|
4130
4323
|
}
|
|
4131
4324
|
|
|
4132
|
-
// Freeze the partial assistant response as a message
|
|
4325
|
+
// Freeze the partial assistant response as a message so the interjection
|
|
4326
|
+
// appears AFTER the (partial) LLM response, not next to the prior user message
|
|
4133
4327
|
if (state.streamBuffer) {
|
|
4134
4328
|
state.messages.push({ role: 'assistant', content: state.streamBuffer });
|
|
4135
4329
|
}
|
|
4136
4330
|
// Reset streaming state — the server will send 'interjected' for the old stream
|
|
4137
4331
|
state.streamBuffer = '';
|
|
4138
4332
|
state.interjecting = true;
|
|
4333
|
+
state.interjectionStreamId = (state.interjectionStreamId || 0) + 1;
|
|
4139
4334
|
// Don't set streaming=false — the new message will keep streaming
|
|
4140
4335
|
}
|
|
4141
4336
|
|
|
@@ -4537,55 +4732,72 @@
|
|
|
4537
4732
|
// ── Push notification subscription ──
|
|
4538
4733
|
function registerPushNotifications() {
|
|
4539
4734
|
if (!('serviceWorker' in navigator) || !('PushManager' in window)) {
|
|
4540
|
-
console.log('[Cumulus] Push notifications not supported');
|
|
4735
|
+
console.log('[Cumulus] Push notifications not supported in this browser');
|
|
4541
4736
|
return;
|
|
4542
4737
|
}
|
|
4543
4738
|
|
|
4544
|
-
|
|
4545
|
-
.
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
// Check existing subscription
|
|
4550
|
-
return registration.pushManager.getSubscription().then(function (existing) {
|
|
4551
|
-
if (existing) {
|
|
4552
|
-
// Already subscribed — send to server in case it's new/different
|
|
4553
|
-
sendPushSubscription(existing);
|
|
4554
|
-
return;
|
|
4555
|
-
}
|
|
4739
|
+
if (!('Notification' in window)) {
|
|
4740
|
+
console.log('[Cumulus] Notification API not available');
|
|
4741
|
+
return;
|
|
4742
|
+
}
|
|
4556
4743
|
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4744
|
+
// Must request permission explicitly (required for iOS PWA)
|
|
4745
|
+
Notification.requestPermission().then(function (permission) {
|
|
4746
|
+
console.log('[Cumulus] Notification permission:', permission);
|
|
4747
|
+
if (permission !== 'granted') {
|
|
4748
|
+
console.log('[Cumulus] Push notifications denied by user');
|
|
4749
|
+
return;
|
|
4750
|
+
}
|
|
4751
|
+
|
|
4752
|
+
navigator.serviceWorker
|
|
4753
|
+
.register('/sw.js', { scope: '/' })
|
|
4754
|
+
.then(function (registration) {
|
|
4755
|
+
console.log('[Cumulus] Service worker registered, scope:', registration.scope);
|
|
4756
|
+
|
|
4757
|
+
// Wait for the service worker to be ready
|
|
4758
|
+
return navigator.serviceWorker.ready.then(function (reg) {
|
|
4759
|
+
// Check existing subscription
|
|
4760
|
+
return reg.pushManager.getSubscription().then(function (existing) {
|
|
4761
|
+
if (existing) {
|
|
4762
|
+
console.log('[Cumulus] Existing push subscription found');
|
|
4763
|
+
sendPushSubscription(existing);
|
|
4567
4764
|
return;
|
|
4568
4765
|
}
|
|
4569
4766
|
|
|
4570
|
-
//
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4767
|
+
// Get VAPID key from server
|
|
4768
|
+
var loc = window.location;
|
|
4769
|
+
var apiUrl = loc.protocol + '//' + loc.host;
|
|
4770
|
+
fetch(apiUrl + '/api/push/vapid-key')
|
|
4771
|
+
.then(function (r) {
|
|
4772
|
+
return r.json();
|
|
4773
|
+
})
|
|
4774
|
+
.then(function (data) {
|
|
4775
|
+
if (!data.publicKey) {
|
|
4776
|
+
console.log('[Cumulus] Push not configured on server (no VAPID key)');
|
|
4777
|
+
return;
|
|
4778
|
+
}
|
|
4779
|
+
|
|
4780
|
+
console.log('[Cumulus] Subscribing to push with VAPID key');
|
|
4781
|
+
return reg.pushManager
|
|
4782
|
+
.subscribe({
|
|
4783
|
+
userVisibleOnly: true,
|
|
4784
|
+
applicationServerKey: urlBase64ToUint8Array(data.publicKey),
|
|
4785
|
+
})
|
|
4786
|
+
.then(function (subscription) {
|
|
4787
|
+
console.log('[Cumulus] Push subscription created:', subscription.endpoint);
|
|
4788
|
+
sendPushSubscription(subscription);
|
|
4789
|
+
});
|
|
4575
4790
|
})
|
|
4576
|
-
.
|
|
4577
|
-
console.
|
|
4578
|
-
sendPushSubscription(subscription);
|
|
4791
|
+
.catch(function (err) {
|
|
4792
|
+
console.warn('[Cumulus] Push subscription failed:', err.message || err);
|
|
4579
4793
|
});
|
|
4580
|
-
})
|
|
4581
|
-
.catch(function (err) {
|
|
4582
|
-
console.warn('[Cumulus] Push subscription failed:', err);
|
|
4583
4794
|
});
|
|
4795
|
+
});
|
|
4796
|
+
})
|
|
4797
|
+
.catch(function (err) {
|
|
4798
|
+
console.warn('[Cumulus] Service worker registration failed:', err.message || err);
|
|
4584
4799
|
});
|
|
4585
|
-
|
|
4586
|
-
.catch(function (err) {
|
|
4587
|
-
console.warn('[Cumulus] Service worker registration failed:', err);
|
|
4588
|
-
});
|
|
4800
|
+
});
|
|
4589
4801
|
}
|
|
4590
4802
|
|
|
4591
4803
|
function sendPushSubscription(subscription) {
|