@luckydraw/cumulus 0.28.2 → 0.28.4
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.
|
@@ -1226,6 +1226,10 @@
|
|
|
1226
1226
|
// WeakMap: DOM element -> BlockHandle[] for lifecycle management
|
|
1227
1227
|
var blexHandles = typeof WeakMap !== 'undefined' ? new WeakMap() : null;
|
|
1228
1228
|
|
|
1229
|
+
// Per-thread store for blex interaction values (poll answers, confirm clicks, etc.)
|
|
1230
|
+
// Key: "threadName" → Map<"msgTimestamp:blockIdx", interactionValue>
|
|
1231
|
+
var blexInteractionStore = {};
|
|
1232
|
+
|
|
1229
1233
|
// Extract ~~~blex:TYPE\n{json}\n~~~ fences from text
|
|
1230
1234
|
// Returns { text: string (with placeholders), blocks: Array<{type, json, idx}> }
|
|
1231
1235
|
function extractBlexBlocks(text) {
|
|
@@ -1240,18 +1244,23 @@
|
|
|
1240
1244
|
}
|
|
1241
1245
|
|
|
1242
1246
|
// Render blex blocks into placeholder divs within a message element
|
|
1243
|
-
function renderBlexBlocks(el, blexBlocks, isStreaming) {
|
|
1247
|
+
function renderBlexBlocks(el, blexBlocks, isStreaming, msgKey, threadName) {
|
|
1244
1248
|
if (!blexBlocks || blexBlocks.length === 0) return;
|
|
1245
1249
|
if (typeof Blex === 'undefined') {
|
|
1246
1250
|
console.warn('[blex] Blex library not loaded — blex.min.js may have failed to fetch');
|
|
1247
1251
|
return;
|
|
1248
1252
|
}
|
|
1249
1253
|
var placeholders = el.querySelectorAll('.blex-block-container');
|
|
1254
|
+
// Get the interaction store for this thread
|
|
1255
|
+
var store = threadName
|
|
1256
|
+
? blexInteractionStore[threadName] || (blexInteractionStore[threadName] = {})
|
|
1257
|
+
: null;
|
|
1250
1258
|
|
|
1251
1259
|
placeholders.forEach(function (container) {
|
|
1252
1260
|
var idx = parseInt(container.getAttribute('data-blex-idx'), 10);
|
|
1253
1261
|
if (isNaN(idx) || idx >= blexBlocks.length) return;
|
|
1254
1262
|
var block = blexBlocks[idx];
|
|
1263
|
+
var storeKey = msgKey ? msgKey + ':' + idx : null;
|
|
1255
1264
|
|
|
1256
1265
|
try {
|
|
1257
1266
|
var data = JSON.parse(block.json);
|
|
@@ -1261,8 +1270,14 @@
|
|
|
1261
1270
|
// During streaming, show placeholder skeleton
|
|
1262
1271
|
Blex.renderPlaceholder(block.type, container);
|
|
1263
1272
|
} else {
|
|
1273
|
+
// Check for previously stored interaction value
|
|
1274
|
+
var renderOpts = {};
|
|
1275
|
+
if (store && storeKey && store[storeKey]) {
|
|
1276
|
+
renderOpts.previousValue = store[storeKey];
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1264
1279
|
// Final render — full interactive block
|
|
1265
|
-
Blex.renderBlock(blockObj, container)
|
|
1280
|
+
Blex.renderBlock(blockObj, container, renderOpts)
|
|
1266
1281
|
.then(function (handle) {
|
|
1267
1282
|
// Store handle immediately (inside async callback, not after sync loop)
|
|
1268
1283
|
if (blexHandles) {
|
|
@@ -1273,6 +1288,11 @@
|
|
|
1273
1288
|
if (handle && handle.onInteraction) {
|
|
1274
1289
|
// Wire interaction handler
|
|
1275
1290
|
handle.onInteraction(function (interaction) {
|
|
1291
|
+
// Store the interaction value for persistence across re-renders
|
|
1292
|
+
if (store && storeKey && interaction.value !== undefined) {
|
|
1293
|
+
store[storeKey] = interaction.value;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1276
1296
|
// Find the panel — try el.closest first, fall back to document query
|
|
1277
1297
|
// (el may be detached from DOM if renderPanelMessages rebuilt the message list)
|
|
1278
1298
|
var panel = el.closest('.cumulus-thread-panel') || el.closest('.cumulus-panel');
|
|
@@ -1287,8 +1307,11 @@
|
|
|
1287
1307
|
|
|
1288
1308
|
if (inputEl && interaction.serialized) {
|
|
1289
1309
|
if (interaction.immediate) {
|
|
1290
|
-
// Immediate:
|
|
1291
|
-
|
|
1310
|
+
// Immediate: prepend any existing input text, then auto-send
|
|
1311
|
+
var existingText = inputEl.value.trim();
|
|
1312
|
+
inputEl.value = existingText
|
|
1313
|
+
? existingText + '\n\n' + interaction.serialized
|
|
1314
|
+
: interaction.serialized;
|
|
1292
1315
|
inputEl.dispatchEvent(new Event('input', { bubbles: true }));
|
|
1293
1316
|
// Find and click the send button
|
|
1294
1317
|
var sendBtn = panel ? panel.querySelector('[data-testid*="send"]') : null;
|
|
@@ -1389,14 +1412,25 @@
|
|
|
1389
1412
|
// ── Inline Annotations (Google Docs-style highlight + comment) ──────────
|
|
1390
1413
|
|
|
1391
1414
|
// Add an annotation chip to the chip tray
|
|
1392
|
-
function addAnnotationChip(panel, quote, comment) {
|
|
1415
|
+
function addAnnotationChip(panel, quote, comment, isCodeBlock, codeLang) {
|
|
1393
1416
|
if (!panel) return;
|
|
1394
1417
|
var tray = panel.querySelector('.cumulus-chip-tray');
|
|
1395
1418
|
if (!tray) return;
|
|
1396
1419
|
|
|
1397
1420
|
var truncatedQuote = quote.length > 30 ? quote.substring(0, 30) + '\u2026' : quote;
|
|
1398
1421
|
var truncatedComment = comment.length > 30 ? comment.substring(0, 30) + '\u2026' : comment;
|
|
1399
|
-
var serialized
|
|
1422
|
+
var serialized;
|
|
1423
|
+
if (isCodeBlock) {
|
|
1424
|
+
serialized =
|
|
1425
|
+
'```' +
|
|
1426
|
+
(codeLang && codeLang !== 'text' ? codeLang : '') +
|
|
1427
|
+
'\n' +
|
|
1428
|
+
quote +
|
|
1429
|
+
'\n```\n' +
|
|
1430
|
+
comment;
|
|
1431
|
+
} else {
|
|
1432
|
+
serialized = '> ' + quote.replace(/\n/g, '\n> ') + '\n' + comment;
|
|
1433
|
+
}
|
|
1400
1434
|
|
|
1401
1435
|
var chip = document.createElement('span');
|
|
1402
1436
|
chip.className = 'cumulus-annotation-chip';
|
|
@@ -1420,7 +1454,7 @@
|
|
|
1420
1454
|
}
|
|
1421
1455
|
|
|
1422
1456
|
// Show the annotation popover near a text selection
|
|
1423
|
-
function showAnnotationPopover(panel, selectedText, anchorRect) {
|
|
1457
|
+
function showAnnotationPopover(panel, selectedText, anchorRect, isCodeBlock, codeLang) {
|
|
1424
1458
|
// Remove any existing popover
|
|
1425
1459
|
dismissAnnotationPopover(panel);
|
|
1426
1460
|
|
|
@@ -1469,7 +1503,7 @@
|
|
|
1469
1503
|
submitBtn.addEventListener('click', function () {
|
|
1470
1504
|
var comment = textarea.value.trim();
|
|
1471
1505
|
if (comment) {
|
|
1472
|
-
addAnnotationChip(panel, selectedText, comment);
|
|
1506
|
+
addAnnotationChip(panel, selectedText, comment, isCodeBlock, codeLang);
|
|
1473
1507
|
}
|
|
1474
1508
|
dismissAnnotationPopover(panel);
|
|
1475
1509
|
window.getSelection().removeAllRanges();
|
|
@@ -1491,10 +1525,8 @@
|
|
|
1491
1525
|
|
|
1492
1526
|
panel.appendChild(popover);
|
|
1493
1527
|
|
|
1494
|
-
//
|
|
1495
|
-
|
|
1496
|
-
textarea.focus();
|
|
1497
|
-
}, 0);
|
|
1528
|
+
// Don't auto-focus the textarea — it clears the text selection.
|
|
1529
|
+
// User can click into it when ready to type.
|
|
1498
1530
|
}
|
|
1499
1531
|
|
|
1500
1532
|
function dismissAnnotationPopover(panel) {
|
|
@@ -1521,10 +1553,22 @@
|
|
|
1521
1553
|
var msgEl = container.closest ? container.closest('.cumulus-msg.assistant') : null;
|
|
1522
1554
|
if (!msgEl) return;
|
|
1523
1555
|
|
|
1556
|
+
// Detect if selection is inside a code block
|
|
1557
|
+
var codeAncestor = container.closest ? container.closest('pre, code') : null;
|
|
1558
|
+
var isCodeBlock = !!codeAncestor;
|
|
1559
|
+
var codeLang = '';
|
|
1560
|
+
if (isCodeBlock) {
|
|
1561
|
+
var wrapper = container.closest ? container.closest('.code-block-wrapper') : null;
|
|
1562
|
+
if (wrapper) {
|
|
1563
|
+
var langEl = wrapper.querySelector('.code-block-language');
|
|
1564
|
+
if (langEl) codeLang = langEl.textContent.trim();
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1524
1568
|
// Get anchor position for the popover
|
|
1525
1569
|
var rect = range.getBoundingClientRect();
|
|
1526
1570
|
|
|
1527
|
-
showAnnotationPopover(panel, selectedText, rect);
|
|
1571
|
+
showAnnotationPopover(panel, selectedText, rect, isCodeBlock, codeLang);
|
|
1528
1572
|
}, 10);
|
|
1529
1573
|
});
|
|
1530
1574
|
|
|
@@ -2170,7 +2214,8 @@
|
|
|
2170
2214
|
function buildUserMsgEl(msg) {
|
|
2171
2215
|
var el = document.createElement('div');
|
|
2172
2216
|
el.className = 'cumulus-msg user' + (isWideUserMessage(msg.content) ? ' wide' : '');
|
|
2173
|
-
|
|
2217
|
+
var mdResult = renderMarkdown(msg.content);
|
|
2218
|
+
el.innerHTML = mdResult.html;
|
|
2174
2219
|
if (msg.attachments && msg.attachments.length > 0) {
|
|
2175
2220
|
var attRow = document.createElement('div');
|
|
2176
2221
|
attRow.className = 'cumulus-msg-attachments';
|
|
@@ -2195,7 +2240,7 @@
|
|
|
2195
2240
|
return el;
|
|
2196
2241
|
}
|
|
2197
2242
|
|
|
2198
|
-
function buildAssistantMsgEl(content, isStreaming) {
|
|
2243
|
+
function buildAssistantMsgEl(content, isStreaming, msgKey) {
|
|
2199
2244
|
var el = document.createElement('div');
|
|
2200
2245
|
el.className = 'cumulus-msg assistant';
|
|
2201
2246
|
if (isStreaming) el.setAttribute('data-testid', 'webchat-streaming');
|
|
@@ -2207,7 +2252,7 @@
|
|
|
2207
2252
|
}
|
|
2208
2253
|
// Render blex blocks — skip during streaming to avoid destroy/recreate churn
|
|
2209
2254
|
if (!isStreaming) {
|
|
2210
|
-
renderBlexBlocks(el, mdResult.blexBlocks, false);
|
|
2255
|
+
renderBlexBlocks(el, mdResult.blexBlocks, false, msgKey, threadName);
|
|
2211
2256
|
}
|
|
2212
2257
|
el.querySelectorAll('.code-block-copy-btn').forEach(function (btn) {
|
|
2213
2258
|
btn.addEventListener('click', function () {
|
|
@@ -2298,7 +2343,7 @@
|
|
|
2298
2343
|
row.appendChild(buildUserMsgEl(msg));
|
|
2299
2344
|
}
|
|
2300
2345
|
} else {
|
|
2301
|
-
row.appendChild(buildAssistantMsgEl(msg.content, false));
|
|
2346
|
+
row.appendChild(buildAssistantMsgEl(msg.content, false, String(msg.timestamp)));
|
|
2302
2347
|
}
|
|
2303
2348
|
appendTimestamp(row, msg.timestamp);
|
|
2304
2349
|
messagesEl.appendChild(row);
|
|
@@ -3735,7 +3780,8 @@
|
|
|
3735
3780
|
function buildUserMsgEl(msg) {
|
|
3736
3781
|
var el = document.createElement('div');
|
|
3737
3782
|
el.className = 'cumulus-msg user' + (isWideUserMessage(msg.content) ? ' wide' : '');
|
|
3738
|
-
|
|
3783
|
+
var mdResult = renderMarkdown(msg.content);
|
|
3784
|
+
el.innerHTML = mdResult.html;
|
|
3739
3785
|
if (msg.attachments && msg.attachments.length > 0) {
|
|
3740
3786
|
var attRow = document.createElement('div');
|
|
3741
3787
|
attRow.className = 'cumulus-msg-attachments';
|
|
@@ -3760,7 +3806,7 @@
|
|
|
3760
3806
|
return el;
|
|
3761
3807
|
}
|
|
3762
3808
|
|
|
3763
|
-
function buildAssistantMsgEl(content, isStreaming) {
|
|
3809
|
+
function buildAssistantMsgEl(content, isStreaming, msgKey) {
|
|
3764
3810
|
var el = document.createElement('div');
|
|
3765
3811
|
el.className = 'cumulus-msg assistant';
|
|
3766
3812
|
if (isStreaming) el.setAttribute('data-testid', 'webchat-streaming');
|
|
@@ -3772,7 +3818,7 @@
|
|
|
3772
3818
|
}
|
|
3773
3819
|
// Render blex blocks — skip during streaming to avoid destroy/recreate churn
|
|
3774
3820
|
if (!isStreaming) {
|
|
3775
|
-
renderBlexBlocks(el, mdResult.blexBlocks, false);
|
|
3821
|
+
renderBlexBlocks(el, mdResult.blexBlocks, false, msgKey, threadName);
|
|
3776
3822
|
}
|
|
3777
3823
|
el.querySelectorAll('.code-block-copy-btn').forEach(function (btn) {
|
|
3778
3824
|
btn.addEventListener('click', function () {
|
|
@@ -3855,7 +3901,7 @@
|
|
|
3855
3901
|
row.appendChild(buildUserMsgEl(msg));
|
|
3856
3902
|
}
|
|
3857
3903
|
} else {
|
|
3858
|
-
row.appendChild(buildAssistantMsgEl(msg.content, false));
|
|
3904
|
+
row.appendChild(buildAssistantMsgEl(msg.content, false, String(msg.timestamp)));
|
|
3859
3905
|
}
|
|
3860
3906
|
appendTimestamp(row, msg.timestamp);
|
|
3861
3907
|
messagesEl.appendChild(row);
|
|
@@ -4131,13 +4177,15 @@
|
|
|
4131
4177
|
return;
|
|
4132
4178
|
}
|
|
4133
4179
|
|
|
4134
|
-
// Freeze the partial assistant response as a message
|
|
4180
|
+
// Freeze the partial assistant response as a message so the interjection
|
|
4181
|
+
// appears AFTER the (partial) LLM response, not next to the prior user message
|
|
4135
4182
|
if (state.streamBuffer) {
|
|
4136
4183
|
state.messages.push({ role: 'assistant', content: state.streamBuffer });
|
|
4137
4184
|
}
|
|
4138
4185
|
// Reset streaming state — the server will send 'interjected' for the old stream
|
|
4139
4186
|
state.streamBuffer = '';
|
|
4140
4187
|
state.interjecting = true;
|
|
4188
|
+
state.interjectionStreamId = (state.interjectionStreamId || 0) + 1;
|
|
4141
4189
|
// Don't set streaming=false — the new message will keep streaming
|
|
4142
4190
|
}
|
|
4143
4191
|
|