bunnyquery 1.8.13 → 1.8.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.
- package/bunnyquery.css +8 -0
- package/bunnyquery.js +718 -104
- package/dist/engine.cjs +606 -52
- package/dist/engine.cjs.map +1 -1
- package/dist/engine.d.mts +544 -212
- package/dist/engine.d.ts +544 -212
- package/dist/engine.mjs +602 -53
- package/dist/engine.mjs.map +1 -1
- package/package.json +3 -2
- package/src/engine/history.ts +64 -0
- package/src/engine/host.ts +14 -0
- package/src/engine/image_preview.ts +0 -0
- package/src/engine/index.ts +17 -0
- package/src/engine/link_markup.ts +9 -1
- package/src/engine/links.ts +31 -3
- package/src/engine/scroll_anchor.ts +684 -0
- package/src/engine/session.ts +313 -61
- package/src/widget.css +8 -0
package/bunnyquery.js
CHANGED
|
@@ -857,18 +857,31 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
857
857
|
part: { type: "link", label: truncateLabelForDisplay(urlLabel), fullLabel: urlLabel, href: originalHref, expired: false }
|
|
858
858
|
});
|
|
859
859
|
}
|
|
860
|
+
function canonicalizePathForm(value) {
|
|
861
|
+
if (!value) return value;
|
|
862
|
+
try {
|
|
863
|
+
return value.normalize("NFC");
|
|
864
|
+
} catch (e) {
|
|
865
|
+
return value;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
860
868
|
function linkUnavailableKeyForPath(remotePath) {
|
|
861
|
-
return "path:" + (remotePath || "");
|
|
869
|
+
return "path:" + canonicalizePathForm(remotePath || "");
|
|
862
870
|
}
|
|
863
871
|
function linkUnavailableKeyForHref(href) {
|
|
864
|
-
|
|
872
|
+
var carried = readExpiredAttachmentHref(href);
|
|
873
|
+
if (carried) return linkUnavailableKeyForPath(carried);
|
|
874
|
+
return "href:" + canonicalizePathForm(href || "");
|
|
865
875
|
}
|
|
866
876
|
function linkUnavailableKeysForPath(remotePath) {
|
|
867
877
|
if (!remotePath) return [];
|
|
868
|
-
|
|
878
|
+
var keys = [
|
|
869
879
|
linkUnavailableKeyForPath(remotePath),
|
|
870
880
|
linkUnavailableKeyForHref(buildDisplayExpiredAttachmentHref(remotePath))
|
|
871
881
|
];
|
|
882
|
+
return keys.filter(function(k, i) {
|
|
883
|
+
return keys.indexOf(k) === i;
|
|
884
|
+
});
|
|
872
885
|
}
|
|
873
886
|
function isLinkUnavailable(link, map) {
|
|
874
887
|
if (!link || !map) return false;
|
|
@@ -1247,7 +1260,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1247
1260
|
if (link.remotePath) attrs.push('data-bq-remote-path="' + escapeInlineHtml(link.remotePath) + '"');
|
|
1248
1261
|
if (link.fullLabel) attrs.push('data-bq-full-label="' + escapeInlineHtml(link.fullLabel) + '"');
|
|
1249
1262
|
if (!preview) return "<a " + attrs.join(" ") + ">" + escapeInlineHtml(labelText) + "</a>";
|
|
1250
|
-
return "<a " + attrs.join(" ") + '><img class="bq-img-preview" alt="' + escapeInlineHtml(full) + '" data-bq-img-path="' + escapeInlineHtml(link.remotePath || "") + '" data-bq-img-type="' + escapeInlineHtml(link.image ? link.image.contentType : "") + '"
|
|
1263
|
+
return "<a " + attrs.join(" ") + '><img class="bq-img-preview" alt="' + escapeInlineHtml(full) + '" data-bq-img-path="' + escapeInlineHtml(link.remotePath || "") + '" data-bq-img-type="' + escapeInlineHtml(link.image ? link.image.contentType : "") + '" decoding="async"><span class="bq-loader" data-bq-img-loader="1"></span><span class="bq-img-preview-caption" translate="no">' + escapeInlineHtml(labelText) + "</span></a>";
|
|
1251
1264
|
}
|
|
1252
1265
|
|
|
1253
1266
|
// src/engine/image_preview.ts
|
|
@@ -1327,31 +1340,48 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1327
1340
|
var warm = peekImagePreviewUrl(ctx, path);
|
|
1328
1341
|
if (warm) {
|
|
1329
1342
|
img.setAttribute("src", warm);
|
|
1343
|
+
notifyLayoutChange(img, ctx, path);
|
|
1330
1344
|
return;
|
|
1331
1345
|
}
|
|
1332
1346
|
resolveImagePreviewUrl(ctx, path, type).then(function(url) {
|
|
1333
1347
|
if (img.getAttribute("data-bq-img-state") !== "loading") return;
|
|
1334
1348
|
img.setAttribute("src", url);
|
|
1349
|
+
notifyLayoutChange(img, ctx, path);
|
|
1335
1350
|
}, function(e) {
|
|
1336
1351
|
img.setAttribute("data-bq-img-state", "error");
|
|
1352
|
+
notifyLayoutChange(img, ctx, path);
|
|
1337
1353
|
if (ctx.onError) ctx.onError(path, e);
|
|
1338
1354
|
});
|
|
1339
1355
|
}
|
|
1340
1356
|
function onImageError(img, ctx, path, type) {
|
|
1341
1357
|
if (img.getAttribute("data-bq-img-retry") === "1") {
|
|
1342
1358
|
img.setAttribute("data-bq-img-state", "error");
|
|
1359
|
+
notifyLayoutChange(img, ctx, path);
|
|
1343
1360
|
if (ctx.onError) ctx.onError(path, new Error("image preview failed to load"));
|
|
1344
1361
|
return;
|
|
1345
1362
|
}
|
|
1346
1363
|
img.setAttribute("data-bq-img-retry", "1");
|
|
1347
1364
|
img.removeAttribute("src");
|
|
1365
|
+
notifyLayoutChange(img, ctx, path);
|
|
1348
1366
|
resolveImagePreviewUrl(ctx, path, type, true).then(function(url) {
|
|
1349
1367
|
img.setAttribute("src", url);
|
|
1368
|
+
notifyLayoutChange(img, ctx, path);
|
|
1350
1369
|
}, function(e) {
|
|
1351
1370
|
img.setAttribute("data-bq-img-state", "error");
|
|
1371
|
+
notifyLayoutChange(img, ctx, path);
|
|
1352
1372
|
if (ctx.onError) ctx.onError(path, e);
|
|
1353
1373
|
});
|
|
1354
1374
|
}
|
|
1375
|
+
function notifyLayoutChange(img, ctx, path) {
|
|
1376
|
+
if (!ctx.onLayoutChange) return;
|
|
1377
|
+
ctx.onLayoutChange(previewLayoutBox(img), path);
|
|
1378
|
+
}
|
|
1379
|
+
var PREVIEW_LAYOUT_BOX_SELECTOR = "a.bq-link-button.is-image-preview";
|
|
1380
|
+
function previewLayoutBox(img) {
|
|
1381
|
+
var closest = img.closest;
|
|
1382
|
+
if (typeof closest !== "function") return img;
|
|
1383
|
+
return closest.call(img, PREVIEW_LAYOUT_BOX_SELECTOR) || img;
|
|
1384
|
+
}
|
|
1355
1385
|
|
|
1356
1386
|
// src/engine/time.ts
|
|
1357
1387
|
function wallClockNow() {
|
|
@@ -2389,6 +2419,327 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
2389
2419
|
}
|
|
2390
2420
|
return { messages: mapped, runningItemIds };
|
|
2391
2421
|
}
|
|
2422
|
+
function shouldRescueInFlightMessage(m, ctx) {
|
|
2423
|
+
if (!m) return false;
|
|
2424
|
+
if (m.isBackgroundTask) return false;
|
|
2425
|
+
if (m._ownerKey !== void 0 && ctx.loadKey !== void 0 && m._ownerKey !== ctx.loadKey) return false;
|
|
2426
|
+
if (m._serverItemId && ctx.hasServerId(m._serverItemId)) return false;
|
|
2427
|
+
if (m._stageId) return true;
|
|
2428
|
+
if (!m._serverItemId && ctx.pageHasPendingAssistant) return false;
|
|
2429
|
+
if (m.isSendingToServer || m.isPendingQueued || m.isPendingInProcess || m.isPending) return true;
|
|
2430
|
+
if (ctx.sending && m.role === "user") {
|
|
2431
|
+
var next = ctx.next;
|
|
2432
|
+
if (!next || next.isBackgroundTask || !next.isPending) return false;
|
|
2433
|
+
return next._serverItemId === void 0 || next._serverItemId === m._serverItemId;
|
|
2434
|
+
}
|
|
2435
|
+
return false;
|
|
2436
|
+
}
|
|
2437
|
+
|
|
2438
|
+
// src/engine/scroll_anchor.ts
|
|
2439
|
+
var ROW_KEY_ATTR = "data-row-key";
|
|
2440
|
+
var ROW_POS_ATTR = "data-row-pos";
|
|
2441
|
+
var MAX_ALTS = 2;
|
|
2442
|
+
var ALT_SCAN_LIMIT = 64;
|
|
2443
|
+
var UNKNOWN_ROW_POS = "\0?";
|
|
2444
|
+
function createScrollAnchor(options) {
|
|
2445
|
+
var held = null;
|
|
2446
|
+
var seen = typeof WeakMap === "function" ? /* @__PURE__ */ new WeakMap() : null;
|
|
2447
|
+
function capture() {
|
|
2448
|
+
var box = options.getBox();
|
|
2449
|
+
if (!box || options.isStuck()) return null;
|
|
2450
|
+
var boxTop = box.getBoundingClientRect().top;
|
|
2451
|
+
var kids = box.children;
|
|
2452
|
+
var fallback = null;
|
|
2453
|
+
var fallbackAt = -1;
|
|
2454
|
+
for (var i = 0; i < kids.length; i++) {
|
|
2455
|
+
var el = kids[i];
|
|
2456
|
+
if (!el || typeof el.getAttribute !== "function") continue;
|
|
2457
|
+
var key = el.getAttribute(ROW_KEY_ATTR);
|
|
2458
|
+
if (!key) continue;
|
|
2459
|
+
var top = el.getBoundingClientRect().top - boxTop;
|
|
2460
|
+
if (top + el.offsetHeight <= 0) continue;
|
|
2461
|
+
if (top >= box.clientHeight) break;
|
|
2462
|
+
var rawPos = el.getAttribute(ROW_POS_ATTR);
|
|
2463
|
+
var pos = rawPos === null ? null : rawPos || UNKNOWN_ROW_POS;
|
|
2464
|
+
var cand = {
|
|
2465
|
+
key,
|
|
2466
|
+
top,
|
|
2467
|
+
pos,
|
|
2468
|
+
scrollTop: box.scrollTop,
|
|
2469
|
+
scrollHeight: box.scrollHeight,
|
|
2470
|
+
el
|
|
2471
|
+
};
|
|
2472
|
+
if (rawPos === null) {
|
|
2473
|
+
cand.alts = collectAlts(box, boxTop, i + 1);
|
|
2474
|
+
return cand;
|
|
2475
|
+
}
|
|
2476
|
+
if (!fallback) {
|
|
2477
|
+
fallback = cand;
|
|
2478
|
+
fallbackAt = i;
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
if (fallback) {
|
|
2482
|
+
fallback.alts = collectAlts(box, boxTop, fallbackAt + 1, true);
|
|
2483
|
+
return fallback;
|
|
2484
|
+
}
|
|
2485
|
+
return {
|
|
2486
|
+
key: null,
|
|
2487
|
+
top: 0,
|
|
2488
|
+
pos: null,
|
|
2489
|
+
scrollTop: box.scrollTop,
|
|
2490
|
+
scrollHeight: box.scrollHeight,
|
|
2491
|
+
el: null
|
|
2492
|
+
};
|
|
2493
|
+
}
|
|
2494
|
+
function collectAlts(box, boxTop, from, ordinaryOnly) {
|
|
2495
|
+
var out = [];
|
|
2496
|
+
var kids = box.children;
|
|
2497
|
+
var stop = Math.min(kids.length, from + ALT_SCAN_LIMIT);
|
|
2498
|
+
for (var i = from; i < stop && out.length < MAX_ALTS; i++) {
|
|
2499
|
+
var el = kids[i];
|
|
2500
|
+
if (!el || typeof el.getAttribute !== "function") continue;
|
|
2501
|
+
var key = el.getAttribute(ROW_KEY_ATTR);
|
|
2502
|
+
if (!key) continue;
|
|
2503
|
+
var rawPos = el.getAttribute(ROW_POS_ATTR);
|
|
2504
|
+
if (ordinaryOnly && rawPos !== null) continue;
|
|
2505
|
+
out.push({
|
|
2506
|
+
key,
|
|
2507
|
+
top: el.getBoundingClientRect().top - boxTop,
|
|
2508
|
+
pos: rawPos === null ? null : rawPos || UNKNOWN_ROW_POS,
|
|
2509
|
+
el
|
|
2510
|
+
});
|
|
2511
|
+
}
|
|
2512
|
+
return out.length ? out : void 0;
|
|
2513
|
+
}
|
|
2514
|
+
function findRow(box, anchor) {
|
|
2515
|
+
var el = anchor.el;
|
|
2516
|
+
if (el && el.parentNode === box) return el;
|
|
2517
|
+
if (!anchor.key) return null;
|
|
2518
|
+
var kids = box.children;
|
|
2519
|
+
for (var i = 0; i < kids.length; i++) {
|
|
2520
|
+
var kid = kids[i];
|
|
2521
|
+
if (!kid || typeof kid.getAttribute !== "function") continue;
|
|
2522
|
+
if (kid.getAttribute(ROW_KEY_ATTR) === anchor.key) return kid;
|
|
2523
|
+
}
|
|
2524
|
+
return null;
|
|
2525
|
+
}
|
|
2526
|
+
var sawFrozen = false;
|
|
2527
|
+
var parked = null;
|
|
2528
|
+
var parkedStuck = false;
|
|
2529
|
+
var returning = false;
|
|
2530
|
+
var wroteTop = -1;
|
|
2531
|
+
var restoreExact = true;
|
|
2532
|
+
var restorePinned = false;
|
|
2533
|
+
function frozen() {
|
|
2534
|
+
var f = !!options.isFrozen && options.isFrozen();
|
|
2535
|
+
if (f) sawFrozen = true;
|
|
2536
|
+
return f;
|
|
2537
|
+
}
|
|
2538
|
+
function restore(anchor, unbounded) {
|
|
2539
|
+
if (frozen()) return;
|
|
2540
|
+
var box = options.getBox();
|
|
2541
|
+
if (!box || !anchor || options.isStuck()) return;
|
|
2542
|
+
var el = findRow(box, anchor);
|
|
2543
|
+
if (el) {
|
|
2544
|
+
var livePos = el.getAttribute(ROW_POS_ATTR) || UNKNOWN_ROW_POS;
|
|
2545
|
+
if (anchor.pos !== null && anchor.pos !== UNKNOWN_ROW_POS && livePos !== UNKNOWN_ROW_POS && livePos !== anchor.pos) el = null;
|
|
2546
|
+
}
|
|
2547
|
+
if (el) {
|
|
2548
|
+
var boxTop = box.getBoundingClientRect().top;
|
|
2549
|
+
var delta = el.getBoundingClientRect().top - boxTop - anchor.top;
|
|
2550
|
+
var slack = Math.abs(box.scrollHeight - anchor.scrollHeight) + box.clientHeight;
|
|
2551
|
+
if (!unbounded && (delta > slack || delta < -slack)) {
|
|
2552
|
+
lost(box, anchor);
|
|
2553
|
+
return;
|
|
2554
|
+
}
|
|
2555
|
+
var want = box.scrollTop + delta;
|
|
2556
|
+
if (delta >= 1 || delta <= -1) box.scrollTop += delta;
|
|
2557
|
+
wroteTop = box.scrollTop;
|
|
2558
|
+
restorePinned = true;
|
|
2559
|
+
restoreExact = box.scrollTop >= want - 1 && box.scrollTop <= want + 1;
|
|
2560
|
+
held = {
|
|
2561
|
+
key: anchor.key,
|
|
2562
|
+
top: anchor.top,
|
|
2563
|
+
pos: anchor.pos,
|
|
2564
|
+
scrollTop: box.scrollTop,
|
|
2565
|
+
scrollHeight: box.scrollHeight,
|
|
2566
|
+
el,
|
|
2567
|
+
// Carried, not dropped: one successful restore used to disarm the
|
|
2568
|
+
// standbys for every later hold.
|
|
2569
|
+
alts: anchor.alts
|
|
2570
|
+
};
|
|
2571
|
+
return;
|
|
2572
|
+
}
|
|
2573
|
+
var alts = anchor.alts;
|
|
2574
|
+
for (var ai = 0; alts && ai < alts.length; ai++) {
|
|
2575
|
+
var alt = alts[ai];
|
|
2576
|
+
var ael = findRow(box, { key: alt.key, top: alt.top, pos: alt.pos, scrollTop: anchor.scrollTop, scrollHeight: anchor.scrollHeight, el: alt.el });
|
|
2577
|
+
if (!ael) continue;
|
|
2578
|
+
if (alt.pos !== null && alt.pos !== UNKNOWN_ROW_POS) {
|
|
2579
|
+
var altLive = ael.getAttribute(ROW_POS_ATTR) || UNKNOWN_ROW_POS;
|
|
2580
|
+
if (altLive !== UNKNOWN_ROW_POS && altLive !== alt.pos) continue;
|
|
2581
|
+
}
|
|
2582
|
+
var aboxTop = box.getBoundingClientRect().top;
|
|
2583
|
+
var adelta = ael.getBoundingClientRect().top - aboxTop - alt.top;
|
|
2584
|
+
var aslack = Math.abs(box.scrollHeight - anchor.scrollHeight) + box.clientHeight;
|
|
2585
|
+
if (!unbounded && (adelta > aslack || adelta < -aslack)) continue;
|
|
2586
|
+
var awant = box.scrollTop + adelta;
|
|
2587
|
+
if (adelta >= 1 || adelta <= -1) box.scrollTop += adelta;
|
|
2588
|
+
wroteTop = box.scrollTop;
|
|
2589
|
+
restorePinned = true;
|
|
2590
|
+
restoreExact = box.scrollTop >= awant - 1 && box.scrollTop <= awant + 1;
|
|
2591
|
+
held = {
|
|
2592
|
+
key: alt.key,
|
|
2593
|
+
top: alt.top,
|
|
2594
|
+
pos: alt.pos,
|
|
2595
|
+
scrollTop: box.scrollTop,
|
|
2596
|
+
scrollHeight: box.scrollHeight,
|
|
2597
|
+
el: ael,
|
|
2598
|
+
alts: alts.slice(ai + 1)
|
|
2599
|
+
};
|
|
2600
|
+
return;
|
|
2601
|
+
}
|
|
2602
|
+
lost(box, anchor);
|
|
2603
|
+
}
|
|
2604
|
+
function lost(box, anchor) {
|
|
2605
|
+
held = null;
|
|
2606
|
+
var grew = box.scrollHeight - anchor.scrollHeight;
|
|
2607
|
+
if (grew > 0) {
|
|
2608
|
+
box.scrollTop = anchor.scrollTop + grew;
|
|
2609
|
+
wroteTop = box.scrollTop;
|
|
2610
|
+
return;
|
|
2611
|
+
}
|
|
2612
|
+
if (options.rawFallback) {
|
|
2613
|
+
box.scrollTop = anchor.scrollTop;
|
|
2614
|
+
wroteTop = box.scrollTop;
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
function preserve(mutate) {
|
|
2618
|
+
var anchor = capture();
|
|
2619
|
+
var result = mutate();
|
|
2620
|
+
restore(anchor);
|
|
2621
|
+
return result;
|
|
2622
|
+
}
|
|
2623
|
+
function remember() {
|
|
2624
|
+
var b0 = options.getBox();
|
|
2625
|
+
if (returning && b0 && b0.scrollTop !== wroteTop) {
|
|
2626
|
+
parked = null;
|
|
2627
|
+
parkedStuck = false;
|
|
2628
|
+
returning = false;
|
|
2629
|
+
}
|
|
2630
|
+
if (frozen()) return;
|
|
2631
|
+
held = capture();
|
|
2632
|
+
}
|
|
2633
|
+
function hold() {
|
|
2634
|
+
if (frozen()) return;
|
|
2635
|
+
if (sawFrozen || returning) {
|
|
2636
|
+
settleReturn();
|
|
2637
|
+
return;
|
|
2638
|
+
}
|
|
2639
|
+
var box = options.getBox();
|
|
2640
|
+
if (!box || options.isStuck()) {
|
|
2641
|
+
held = null;
|
|
2642
|
+
return;
|
|
2643
|
+
}
|
|
2644
|
+
if (!held) {
|
|
2645
|
+
held = capture();
|
|
2646
|
+
return;
|
|
2647
|
+
}
|
|
2648
|
+
if (box.scrollTop !== held.scrollTop) {
|
|
2649
|
+
held = capture();
|
|
2650
|
+
return;
|
|
2651
|
+
}
|
|
2652
|
+
restore(held);
|
|
2653
|
+
}
|
|
2654
|
+
function absorb(el) {
|
|
2655
|
+
if (!el) return;
|
|
2656
|
+
var box = options.getBox();
|
|
2657
|
+
if (!box) return;
|
|
2658
|
+
var h = el.offsetHeight;
|
|
2659
|
+
var prev = seen ? seen.get(el) : void 0;
|
|
2660
|
+
if (seen) seen.set(el, h);
|
|
2661
|
+
if (options.isStuck()) return;
|
|
2662
|
+
if (prev === void 0) prev = 0;
|
|
2663
|
+
var delta = h - prev;
|
|
2664
|
+
if (delta === 0) return;
|
|
2665
|
+
if (frozen()) {
|
|
2666
|
+
foldFrozenGrowth(box, el, delta);
|
|
2667
|
+
return;
|
|
2668
|
+
}
|
|
2669
|
+
if (el.getBoundingClientRect().top >= box.getBoundingClientRect().top) return;
|
|
2670
|
+
box.scrollTop += delta;
|
|
2671
|
+
wroteTop = box.scrollTop;
|
|
2672
|
+
if (held) held = capture();
|
|
2673
|
+
}
|
|
2674
|
+
function park() {
|
|
2675
|
+
parked = capture();
|
|
2676
|
+
parkedStuck = !!options.isStuck();
|
|
2677
|
+
returning = true;
|
|
2678
|
+
}
|
|
2679
|
+
function pinBottom() {
|
|
2680
|
+
var box = options.getBox();
|
|
2681
|
+
if (!box) return;
|
|
2682
|
+
box.scrollTop = box.scrollHeight;
|
|
2683
|
+
wroteTop = box.scrollTop;
|
|
2684
|
+
}
|
|
2685
|
+
function settleReturn() {
|
|
2686
|
+
if (frozen()) return false;
|
|
2687
|
+
sawFrozen = false;
|
|
2688
|
+
if (parkedStuck) {
|
|
2689
|
+
pinBottom();
|
|
2690
|
+
return true;
|
|
2691
|
+
}
|
|
2692
|
+
var target = parked || held;
|
|
2693
|
+
restoreExact = true;
|
|
2694
|
+
restorePinned = false;
|
|
2695
|
+
restore(target, true);
|
|
2696
|
+
parked = !restorePinned || !restoreExact ? target : null;
|
|
2697
|
+
returning = !!parked;
|
|
2698
|
+
return false;
|
|
2699
|
+
}
|
|
2700
|
+
function isReturning() {
|
|
2701
|
+
return returning;
|
|
2702
|
+
}
|
|
2703
|
+
function thaw() {
|
|
2704
|
+
settleReturn();
|
|
2705
|
+
}
|
|
2706
|
+
function foldFrozenGrowth(box, el, delta) {
|
|
2707
|
+
var elTop = el.getBoundingClientRect().top;
|
|
2708
|
+
foldInto(box, held, elTop, delta);
|
|
2709
|
+
foldInto(box, parked, elTop, delta);
|
|
2710
|
+
}
|
|
2711
|
+
function foldInto(box, a, elTop, delta) {
|
|
2712
|
+
if (!a || a.top >= 0) return;
|
|
2713
|
+
var rowEl = findRow(box, a);
|
|
2714
|
+
if (!rowEl) return;
|
|
2715
|
+
var within = elTop - rowEl.getBoundingClientRect().top;
|
|
2716
|
+
if (within < 0 || within >= rowEl.offsetHeight) return;
|
|
2717
|
+
if (within >= -a.top) return;
|
|
2718
|
+
a.top -= delta;
|
|
2719
|
+
a.scrollHeight += delta;
|
|
2720
|
+
}
|
|
2721
|
+
function forget() {
|
|
2722
|
+
held = null;
|
|
2723
|
+
parked = null;
|
|
2724
|
+
parkedStuck = false;
|
|
2725
|
+
returning = false;
|
|
2726
|
+
}
|
|
2727
|
+
return {
|
|
2728
|
+
capture,
|
|
2729
|
+
restore,
|
|
2730
|
+
preserve,
|
|
2731
|
+
remember,
|
|
2732
|
+
hold,
|
|
2733
|
+
park,
|
|
2734
|
+
settleReturn,
|
|
2735
|
+
isReturning,
|
|
2736
|
+
pinBottom,
|
|
2737
|
+
isFrozen: frozen,
|
|
2738
|
+
thaw,
|
|
2739
|
+
absorb,
|
|
2740
|
+
forget
|
|
2741
|
+
};
|
|
2742
|
+
}
|
|
2392
2743
|
|
|
2393
2744
|
// src/engine/viewport_fill.ts
|
|
2394
2745
|
var HISTORY_FILL_SLACK_PX = 64;
|
|
@@ -2500,6 +2851,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
2500
2851
|
var LIVE_INDEX_SNAPSHOT_MAX_AGE_MS = 5e3;
|
|
2501
2852
|
var INDEX_DISPATCH_CLAIM_MS = 2 * 60 * 1e3;
|
|
2502
2853
|
var WORKER_PASS_ADOPT_ATTEMPTS = [0, 2e3, 6e3];
|
|
2854
|
+
var EARLY_PROBE_SCHEDULE_MS = [400, 900, 1700];
|
|
2503
2855
|
var INDEXING_DRAIN_BUSY_POLL_MS = 8e3;
|
|
2504
2856
|
var INDEXING_DRAIN_CONFIRM_POLL_MS = 3e3;
|
|
2505
2857
|
var INDEXING_DRAIN_IDLE_LOOKS = 2;
|
|
@@ -2895,7 +3247,93 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
2895
3247
|
*
|
|
2896
3248
|
* `stop` comes from the SDK and may be absent on an older skapi-js, in which case the
|
|
2897
3249
|
* poll simply cannot be stopped and is left running — see pausePolling.
|
|
3250
|
+
*
|
|
3251
|
+
* (This block documents _trackPoll, further down. The two methods below sit between it
|
|
3252
|
+
* and its subject.)
|
|
3253
|
+
*/
|
|
3254
|
+
/**
|
|
3255
|
+
* Foreground poll with an early-probe race.
|
|
3256
|
+
*
|
|
3257
|
+
* skapi's poll() is a bare setInterval(fn, latency) with NO check at t=0, so the earliest a
|
|
3258
|
+
* reply can be observed is one full POLL_INTERVAL (3s) after dispatch. For a long generation
|
|
3259
|
+
* that granularity is free. For a SHORT one it is nearly pure dead time: a greeting that the
|
|
3260
|
+
* provider finishes in 1s still waits until the 3s tick, which measured as a large share of a
|
|
3261
|
+
* 5s "yo" round trip.
|
|
3262
|
+
*
|
|
3263
|
+
* So keep the 3s interval as the steady state, and additionally point-look-up the item a few
|
|
3264
|
+
* times early, on a widening schedule. Whichever answers first wins and the other is stopped.
|
|
3265
|
+
* The probe uses the csrHistoryItemLookup hook both clients already implement; without it this
|
|
3266
|
+
* degrades to exactly the old behaviour.
|
|
3267
|
+
*
|
|
3268
|
+
* FOREGROUND ONLY. Background indexing polls keep the flat cadence: nobody is watching them,
|
|
3269
|
+
* and they are the ones bounded by MAX_CONCURRENT_BG_POLLS, so adding probes there would spend
|
|
3270
|
+
* the request budget the cap exists to protect.
|
|
2898
3271
|
*/
|
|
3272
|
+
attachForegroundPoll(source, itemId, opts) {
|
|
3273
|
+
return this._fgPollWithEarlyProbe(source, itemId, opts);
|
|
3274
|
+
}
|
|
3275
|
+
_fgPollWithEarlyProbe(source, itemId, opts) {
|
|
3276
|
+
var base = source.poll(Object.assign({ latency: POLL_INTERVAL }, opts || {}));
|
|
3277
|
+
var lookup = chatEngineConfig().csrHistoryItemLookup;
|
|
3278
|
+
var ident = this.host.getIdentity();
|
|
3279
|
+
var platform = ident && ident.platform;
|
|
3280
|
+
if (!lookup || !itemId || !ident || !ident.projectId || platform !== "claude" && platform !== "openai") {
|
|
3281
|
+
return base;
|
|
3282
|
+
}
|
|
3283
|
+
var settled = false;
|
|
3284
|
+
var timers = [];
|
|
3285
|
+
var clearProbes = function() {
|
|
3286
|
+
for (var i = 0; i < timers.length; i++) clearTimeout(timers[i]);
|
|
3287
|
+
timers = [];
|
|
3288
|
+
};
|
|
3289
|
+
var stopBase = base && typeof base.stop === "function" ? base.stop.bind(base) : null;
|
|
3290
|
+
var raced = new Promise(function(resolve, reject) {
|
|
3291
|
+
base.then(function(res) {
|
|
3292
|
+
if (settled) return;
|
|
3293
|
+
settled = true;
|
|
3294
|
+
clearProbes();
|
|
3295
|
+
resolve(res);
|
|
3296
|
+
}, function(err) {
|
|
3297
|
+
if (settled) return;
|
|
3298
|
+
settled = true;
|
|
3299
|
+
clearProbes();
|
|
3300
|
+
reject(err);
|
|
3301
|
+
});
|
|
3302
|
+
var fullId = buildHistoryItemFullId(platform, ident.projectId, itemId);
|
|
3303
|
+
EARLY_PROBE_SCHEDULE_MS.forEach(function(delay) {
|
|
3304
|
+
timers.push(setTimeout(function() {
|
|
3305
|
+
if (settled) return;
|
|
3306
|
+
Promise.resolve(lookup(fullId, ident.projectId, ident.owner)).then(function(body) {
|
|
3307
|
+
if (settled || !body || typeof body !== "object") return;
|
|
3308
|
+
if (body.status === "pending" || body.status === "running") return;
|
|
3309
|
+
if (isPollStopped(body)) return;
|
|
3310
|
+
settled = true;
|
|
3311
|
+
clearProbes();
|
|
3312
|
+
if (stopBase) {
|
|
3313
|
+
try {
|
|
3314
|
+
stopBase();
|
|
3315
|
+
} catch (e) {
|
|
3316
|
+
}
|
|
3317
|
+
}
|
|
3318
|
+
if (opts && typeof opts.onResponse === "function") {
|
|
3319
|
+
try {
|
|
3320
|
+
opts.onResponse(body);
|
|
3321
|
+
} catch (e) {
|
|
3322
|
+
}
|
|
3323
|
+
}
|
|
3324
|
+
resolve(body);
|
|
3325
|
+
}, function() {
|
|
3326
|
+
});
|
|
3327
|
+
}, delay));
|
|
3328
|
+
});
|
|
3329
|
+
});
|
|
3330
|
+
raced.stop = function() {
|
|
3331
|
+
settled = true;
|
|
3332
|
+
clearProbes();
|
|
3333
|
+
if (stopBase) stopBase();
|
|
3334
|
+
};
|
|
3335
|
+
return raced;
|
|
3336
|
+
}
|
|
2899
3337
|
_trackPoll(id, kind, p) {
|
|
2900
3338
|
var stop = p && typeof p.stop === "function" ? p.stop.bind(p) : void 0;
|
|
2901
3339
|
if (!stop) {
|
|
@@ -3071,6 +3509,68 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
3071
3509
|
startKeyHistory: this.state.historyStartKeyHistory.slice()
|
|
3072
3510
|
};
|
|
3073
3511
|
}
|
|
3512
|
+
/**
|
|
3513
|
+
* Give the immediate-send pair the server's id for their turn, the moment the
|
|
3514
|
+
* dispatch learns it.
|
|
3515
|
+
*
|
|
3516
|
+
* WHY THIS EXISTS. An immediate send pushes its user bubble and its
|
|
3517
|
+
* "Thinking..." placeholder locally, and until now neither ever carried a
|
|
3518
|
+
* _serverItemId — only the QUEUED path stamped one, off its ack. So for the
|
|
3519
|
+
* whole life of the turn there was no way to tell the local copy and the
|
|
3520
|
+
* server's copy of the SAME turn apart, and the history merge fell back to a
|
|
3521
|
+
* heuristic: rescue the local pair unless the freshly-fetched page happens to
|
|
3522
|
+
* contain a pending assistant.
|
|
3523
|
+
*
|
|
3524
|
+
* That heuristic has a hole exactly one poll interval wide. The server settles
|
|
3525
|
+
* the request; for up to POLL_INTERVAL the client has not noticed, so
|
|
3526
|
+
* `state.sending` is still true and the local pair is still on screen — while a
|
|
3527
|
+
* history fetch issued in that window returns the turn ALREADY SETTLED, with no
|
|
3528
|
+
* pending assistant in it. The rescue then re-appends the local pair below the
|
|
3529
|
+
* server's copy (the question, twice), and when the poll finally resolves,
|
|
3530
|
+
* typewriteLatestReply writes the answer into the rescued placeholder because it
|
|
3531
|
+
* is the only pending assistant left (the answer, twice). updateHistoryCache
|
|
3532
|
+
* persists the result, so it survives every later visit.
|
|
3533
|
+
*
|
|
3534
|
+
* Navigating away while waiting and coming back is what lands a fetch in that
|
|
3535
|
+
* window: a remount runs refreshGate -> a fresh first page, at an arbitrary
|
|
3536
|
+
* moment relative to the 3s poll.
|
|
3537
|
+
*
|
|
3538
|
+
* With the id on the bubbles, both clients' rescue loops skip them through the
|
|
3539
|
+
* dedup they already have (`_serverItemId is in this page`), the reply the
|
|
3540
|
+
* dispatch caches inherits the id too, and nothing needs a new special case.
|
|
3541
|
+
*
|
|
3542
|
+
* Matched by _localId, never by index: a file's indexing rows are spliced in
|
|
3543
|
+
* above these bubbles while the request is in flight.
|
|
3544
|
+
*/
|
|
3545
|
+
_stampTurnWithItemId(key, userLid, placeholderLid, itemId) {
|
|
3546
|
+
if (!itemId) return;
|
|
3547
|
+
var changed = false;
|
|
3548
|
+
var stamp = function(list) {
|
|
3549
|
+
var hit = false;
|
|
3550
|
+
for (var i = 0; i < list.length; i++) {
|
|
3551
|
+
var m = list[i];
|
|
3552
|
+
if (!m || !m._localId) continue;
|
|
3553
|
+
if (m._localId !== userLid && m._localId !== placeholderLid) continue;
|
|
3554
|
+
if (m._serverItemId === itemId) continue;
|
|
3555
|
+
list[i] = Object.assign({}, m, { _serverItemId: itemId });
|
|
3556
|
+
hit = true;
|
|
3557
|
+
}
|
|
3558
|
+
return hit;
|
|
3559
|
+
};
|
|
3560
|
+
changed = stamp(this.state.messages);
|
|
3561
|
+
var cached = key ? this.aiChatHistoryCache[key] : void 0;
|
|
3562
|
+
if (cached) {
|
|
3563
|
+
var msgs = cached.messages.slice();
|
|
3564
|
+
if (stamp(msgs)) {
|
|
3565
|
+
this.aiChatHistoryCache[key] = {
|
|
3566
|
+
messages: msgs,
|
|
3567
|
+
endOfList: cached.endOfList,
|
|
3568
|
+
startKeyHistory: cached.startKeyHistory
|
|
3569
|
+
};
|
|
3570
|
+
}
|
|
3571
|
+
}
|
|
3572
|
+
if (changed) this.host.notify();
|
|
3573
|
+
}
|
|
3074
3574
|
/**
|
|
3075
3575
|
* Land a resolved reply in the history cache of a chat that is NOT currently
|
|
3076
3576
|
* visible, without touching state.messages. Mirrors the cache-only path in
|
|
@@ -3152,8 +3652,9 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
3152
3652
|
if (initial.id) {
|
|
3153
3653
|
if (dispatchItemId && dispatchItemId !== initial.id) self.historyItemPolls.delete(dispatchItemId);
|
|
3154
3654
|
dispatchItemId = initial.id;
|
|
3655
|
+
if (typeof params.onItemId === "function") params.onItemId(initial.id);
|
|
3155
3656
|
}
|
|
3156
|
-
var dp =
|
|
3657
|
+
var dp = self._fgPollWithEarlyProbe(initial, initial.id);
|
|
3157
3658
|
if (initial.id) self._trackPoll(initial.id, "fg", dp);
|
|
3158
3659
|
return dp;
|
|
3159
3660
|
}
|
|
@@ -3178,28 +3679,9 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
3178
3679
|
}).then(function(result) {
|
|
3179
3680
|
delete self.pendingAgentRequests[params.key];
|
|
3180
3681
|
if (dispatchItemId) self.historyItemPolls.delete(dispatchItemId);
|
|
3181
|
-
var existing = self.aiChatHistoryCache[params.key] || { messages: [], endOfList: false, startKeyHistory: [] };
|
|
3182
3682
|
var reply = { role: "assistant", content: result.content, isError: result.isError };
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
for (var i = msgs.length - 1; i >= 0; i--) {
|
|
3186
|
-
var m = msgs[i];
|
|
3187
|
-
if (m && m.isPending && m.role === "assistant" && !m.isBackgroundTask) {
|
|
3188
|
-
idx = i;
|
|
3189
|
-
break;
|
|
3190
|
-
}
|
|
3191
|
-
}
|
|
3192
|
-
if (idx !== -1) {
|
|
3193
|
-
reply._serverItemId = msgs[idx]._serverItemId;
|
|
3194
|
-
msgs[idx] = reply;
|
|
3195
|
-
} else {
|
|
3196
|
-
msgs.push(reply);
|
|
3197
|
-
}
|
|
3198
|
-
self.aiChatHistoryCache[params.key] = {
|
|
3199
|
-
messages: msgs,
|
|
3200
|
-
endOfList: existing.endOfList,
|
|
3201
|
-
startKeyHistory: existing.startKeyHistory
|
|
3202
|
-
};
|
|
3683
|
+
if (dispatchItemId) reply._serverItemId = dispatchItemId;
|
|
3684
|
+
self._applyReplyToCache(params.key, reply, dispatchItemId);
|
|
3203
3685
|
return result;
|
|
3204
3686
|
});
|
|
3205
3687
|
this.pendingAgentRequests[params.key] = run;
|
|
@@ -3588,8 +4070,9 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
3588
4070
|
}
|
|
3589
4071
|
this.host.notify();
|
|
3590
4072
|
this.updateHistoryCache();
|
|
3591
|
-
this.
|
|
4073
|
+
this.scrollForDispatch(stageId);
|
|
3592
4074
|
var capturedComposed = composed, capturedPlatform = aiPlatform, capturedKey = key;
|
|
4075
|
+
var capturedQueuedLid = queuedBubble._localId;
|
|
3593
4076
|
Promise.resolve(this._callProviderFor(aiPlatform, composed, boundedQ.messages, systemPrompt, aiModel, chatQueue, extractContent, fileUrls, id.projectId, id.owner)).then(function(result) {
|
|
3594
4077
|
var sendingIdx = self.getHistoryCacheKey() !== capturedKey ? -1 : self.state.messages.findIndex(function(m) {
|
|
3595
4078
|
return m.isSendingToServer && (m.isPendingQueued || m.isPendingInProcess) && m.role === "user" && !m._stageId && (m._ownerKey === void 0 || m._ownerKey === capturedKey);
|
|
@@ -3601,8 +4084,9 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
3601
4084
|
self.state.messages[sendingIdx] = upd;
|
|
3602
4085
|
self.host.notify();
|
|
3603
4086
|
}
|
|
4087
|
+
if (serverId) self._stampTurnWithItemId(capturedKey, capturedQueuedLid, void 0, serverId);
|
|
3604
4088
|
if (result && result.poll && (result.status === "pending" || result.status === "running")) {
|
|
3605
|
-
var qp =
|
|
4089
|
+
var qp = self._fgPollWithEarlyProbe(result, serverId);
|
|
3606
4090
|
if (serverId) self._trackPoll(serverId, "fg", qp);
|
|
3607
4091
|
return qp.then(function(res) {
|
|
3608
4092
|
if (isPollStopped(res)) return;
|
|
@@ -3618,7 +4102,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
3618
4102
|
return;
|
|
3619
4103
|
}
|
|
3620
4104
|
var immediateUser = { role: "user", content: composed, _localId: this._newLocalId(), _ts: wallClockNow(), ...key ? { _ownerKey: key } : {} };
|
|
3621
|
-
var immediatePlaceholder = { role: "assistant", content: "", isPending: true, isPendingInProcess: true, ...key ? { _ownerKey: key } : {} };
|
|
4105
|
+
var immediatePlaceholder = { role: "assistant", content: "", isPending: true, isPendingInProcess: true, _localId: this._newLocalId(), ...key ? { _ownerKey: key } : {} };
|
|
3622
4106
|
var iStage = this._stageIndex(this.state.messages, stageId);
|
|
3623
4107
|
if (iStage !== -1) {
|
|
3624
4108
|
var iEx = this.state.messages[iStage];
|
|
@@ -3632,7 +4116,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
3632
4116
|
this.host.notify();
|
|
3633
4117
|
this.updateHistoryCache();
|
|
3634
4118
|
this.state.sending = true;
|
|
3635
|
-
this.
|
|
4119
|
+
this.scrollForDispatch(stageId);
|
|
3636
4120
|
var historyForLlm = this.state.messages.filter(function(m) {
|
|
3637
4121
|
if (m === immediateUser) return false;
|
|
3638
4122
|
return !m.isPending && !m.isPendingQueued && !m.isPendingInProcess && !m.isPendingOlder && !m.isCancelled && !m.isBackgroundTask && !m.isError;
|
|
@@ -3645,6 +4129,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
3645
4129
|
projectId: id.projectId,
|
|
3646
4130
|
history: historyForLlm
|
|
3647
4131
|
});
|
|
4132
|
+
var immediateUserLid = immediateUser._localId, immediatePlaceholderLid = immediatePlaceholder._localId;
|
|
3648
4133
|
var run = this.dispatchAgentRequest({
|
|
3649
4134
|
key,
|
|
3650
4135
|
projectId: id.projectId,
|
|
@@ -3656,7 +4141,15 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
3656
4141
|
boundedMessages: bounded.messages,
|
|
3657
4142
|
userId: chatQueue,
|
|
3658
4143
|
extractContent,
|
|
3659
|
-
fileUrls
|
|
4144
|
+
fileUrls,
|
|
4145
|
+
// THE fix for a turn rendering twice after navigate-away-and-back. Until
|
|
4146
|
+
// this existed the immediate-send pair carried no server id for its whole
|
|
4147
|
+
// life (only the QUEUED path stamped one, off its ack), so nothing could
|
|
4148
|
+
// tell the local copy and the server's copy of the same turn apart. See
|
|
4149
|
+
// _stampTurnWithItemId.
|
|
4150
|
+
onItemId: function(itemId) {
|
|
4151
|
+
self._stampTurnWithItemId(key, immediateUserLid, immediatePlaceholderLid, itemId);
|
|
4152
|
+
}
|
|
3660
4153
|
});
|
|
3661
4154
|
Promise.resolve(run).catch(function() {
|
|
3662
4155
|
}).then(function() {
|
|
@@ -3667,6 +4160,28 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
3667
4160
|
});
|
|
3668
4161
|
});
|
|
3669
4162
|
}
|
|
4163
|
+
/**
|
|
4164
|
+
* Scroll for a dispatch that is going out NOW, but never for one arriving late.
|
|
4165
|
+
*
|
|
4166
|
+
* A turn with attachments does not dispatch when the user hits Send: it waits out
|
|
4167
|
+
* its uploads and then its whole indexing chain, which is minutes
|
|
4168
|
+
* (awaitIndexingDrained). By then the reader has very often scrolled up into
|
|
4169
|
+
* history to pass the time, and the forcing scrollToBottom yanked them out of it
|
|
4170
|
+
* — and worse, force-pinned stickToBottom, which no-ops every method on the
|
|
4171
|
+
* scroll anchor and re-arms the queue-detect poll whose only bail is
|
|
4172
|
+
* !stickToBottom.
|
|
4173
|
+
*
|
|
4174
|
+
* `stageId` is the exact marker for that case: only the attachment path ever
|
|
4175
|
+
* produces one. The gesture itself was already paid for at stage time, where
|
|
4176
|
+
* stageOutgoingMessage forces the scroll while the user is still looking at the
|
|
4177
|
+
* composer. Deliberately NOT gated on "did we find the staged bubble" — a remount
|
|
4178
|
+
* rebuilds from the cache and the turn is appended rather than replaced, which is
|
|
4179
|
+
* just as late and just as unrequested.
|
|
4180
|
+
*/
|
|
4181
|
+
scrollForDispatch(stageId) {
|
|
4182
|
+
if (stageId) this.host.scrollToBottomIfSticky(true);
|
|
4183
|
+
else this.host.scrollToBottom(true);
|
|
4184
|
+
}
|
|
3670
4185
|
promoteNextBgQueuedToRunning() {
|
|
3671
4186
|
if (this.state.messages.some(function(m) {
|
|
3672
4187
|
return m.isPending && m.role === "assistant" && m.isBackgroundTask;
|
|
@@ -3792,6 +4307,29 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
3792
4307
|
else if (targetIdx >= 0) this.state.messages.splice(targetIdx, 0, msg);
|
|
3793
4308
|
else this.state.messages.push(msg);
|
|
3794
4309
|
}
|
|
4310
|
+
/**
|
|
4311
|
+
* The server's OWN copy of this turn is already on screen.
|
|
4312
|
+
*
|
|
4313
|
+
* A first-page fetch can land between the server settling the item and this
|
|
4314
|
+
* poll's tick, and now that the local bubbles carry the item id
|
|
4315
|
+
* (_stampTurnWithItemId) the rescue correctly drops them and renders the
|
|
4316
|
+
* server's settled pair instead. There is then nothing left to resolve: the
|
|
4317
|
+
* -1 fallback would push the answer in a SECOND time at the bottom of the list,
|
|
4318
|
+
* and the positional fallbacks would hijack some other turn's bubble.
|
|
4319
|
+
*
|
|
4320
|
+
* The USER bubble counts, not just a settled assistant: an item whose answer is
|
|
4321
|
+
* empty produces no assistant bubble at all in the mapper, and that variant
|
|
4322
|
+
* would otherwise still bottom-push "No text response received...". While a turn
|
|
4323
|
+
* is genuinely live its user bubble is always pending (the queued branch sets
|
|
4324
|
+
* isPendingQueued, promoteNextQueuedToRunning sets isPendingInProcess), so this
|
|
4325
|
+
* cannot fire early.
|
|
4326
|
+
*/
|
|
4327
|
+
_turnAlreadyRendered(serverId) {
|
|
4328
|
+
if (!serverId) return false;
|
|
4329
|
+
return this.state.messages.some(function(m) {
|
|
4330
|
+
return m._serverItemId === serverId && !m.isPending && !m.isPendingQueued && !m.isPendingInProcess;
|
|
4331
|
+
});
|
|
4332
|
+
}
|
|
3795
4333
|
onQueuedSendResponse(_composed, response, platform, serverId, ownerKey) {
|
|
3796
4334
|
if (serverId) this.historyItemPolls.delete(serverId);
|
|
3797
4335
|
if (ownerKey && this.getHistoryCacheKey() !== ownerKey) {
|
|
@@ -3800,6 +4338,12 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
3800
4338
|
if (serverId) this.cancelledServerIds.delete(serverId);
|
|
3801
4339
|
return;
|
|
3802
4340
|
}
|
|
4341
|
+
if (this._turnAlreadyRendered(serverId)) {
|
|
4342
|
+
if (serverId) this.cancelledServerIds.delete(serverId);
|
|
4343
|
+
this.host.notify();
|
|
4344
|
+
this.updateHistoryCache();
|
|
4345
|
+
return;
|
|
4346
|
+
}
|
|
3803
4347
|
var targetIdx = this.resolveQueuedUserBubble(serverId);
|
|
3804
4348
|
if (targetIdx === void 0) {
|
|
3805
4349
|
this.host.notify();
|
|
@@ -3841,6 +4385,12 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
3841
4385
|
if (serverId) this.cancelledServerIds.delete(serverId);
|
|
3842
4386
|
return;
|
|
3843
4387
|
}
|
|
4388
|
+
if (this._turnAlreadyRendered(serverId)) {
|
|
4389
|
+
if (serverId) this.cancelledServerIds.delete(serverId);
|
|
4390
|
+
this.host.notify();
|
|
4391
|
+
this.updateHistoryCache();
|
|
4392
|
+
return;
|
|
4393
|
+
}
|
|
3844
4394
|
var isNotExists = err && (err.code === "NOT_EXISTS" || err.body && err.body.code === "NOT_EXISTS");
|
|
3845
4395
|
if (isNotExists) {
|
|
3846
4396
|
var userIdx = serverId ? this.state.messages.findIndex(function(m) {
|
|
@@ -5102,21 +5652,15 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
5102
5652
|
var rescued = [];
|
|
5103
5653
|
for (var ri = 0; ri < self.state.messages.length; ri++) {
|
|
5104
5654
|
var mm = self.state.messages[ri];
|
|
5105
|
-
if (mm
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
if (mm.isSendingToServer || mm.isPendingQueued || mm.isPendingInProcess || mm.isPending) rescued.push(mm);
|
|
5115
|
-
else if (self.state.sending && mm.role === "user") {
|
|
5116
|
-
var next = self.state.messages[ri + 1];
|
|
5117
|
-
if (next && !next.isBackgroundTask && next.isPending && !next._serverItemId) rescued.push(mm);
|
|
5118
|
-
}
|
|
5119
|
-
}
|
|
5655
|
+
if (shouldRescueInFlightMessage(mm, {
|
|
5656
|
+
hasServerId: function(sid) {
|
|
5657
|
+
return !!serverIds[sid];
|
|
5658
|
+
},
|
|
5659
|
+
pageHasPendingAssistant: mappedHasPendingAssistant,
|
|
5660
|
+
sending: !!self.state.sending,
|
|
5661
|
+
next: self.state.messages[ri + 1],
|
|
5662
|
+
loadKey
|
|
5663
|
+
})) rescued.push(mm);
|
|
5120
5664
|
}
|
|
5121
5665
|
var oldestInPage1 = void 0;
|
|
5122
5666
|
mapped.forEach(function(m) {
|
|
@@ -5167,6 +5711,9 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
5167
5711
|
keptOlderPages = prependOlder.length > 0 || interleave.length > 0;
|
|
5168
5712
|
self.state.messages = prependOlder.length ? prependOlder.concat(page1) : page1;
|
|
5169
5713
|
rescued.forEach(function(m) {
|
|
5714
|
+
if (m._serverItemId && self.state.messages.some(function(x) {
|
|
5715
|
+
return x._serverItemId === m._serverItemId && x.role === m.role;
|
|
5716
|
+
})) return;
|
|
5170
5717
|
self.state.messages.push(m);
|
|
5171
5718
|
});
|
|
5172
5719
|
if (Object.keys(locallyCancelled).length) {
|
|
@@ -5291,6 +5838,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
5291
5838
|
releaseBgFlag();
|
|
5292
5839
|
self.updateHistoryCache();
|
|
5293
5840
|
self.host.notify();
|
|
5841
|
+
if (self.host.settleScroll) self.host.settleScroll();
|
|
5294
5842
|
}, function() {
|
|
5295
5843
|
releaseBgFlag();
|
|
5296
5844
|
self.host.notify();
|
|
@@ -5318,8 +5866,8 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
5318
5866
|
if ((item._isBgTask || item._isOnBgQueue) && self.isPollingPaused()) return;
|
|
5319
5867
|
if ((item._isBgTask || item._isOnBgQueue) && !bgAllow[item.id]) return;
|
|
5320
5868
|
var capturedId = item.id;
|
|
5321
|
-
var
|
|
5322
|
-
|
|
5869
|
+
var isBg = !!(item._isBgTask || item._isOnBgQueue);
|
|
5870
|
+
var pollOpts = {
|
|
5323
5871
|
onResponse: function(response) {
|
|
5324
5872
|
if (isPollStopped(response)) return;
|
|
5325
5873
|
self.handleHistoryItemResolution(capturedId, response, platform);
|
|
@@ -5334,9 +5882,9 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
5334
5882
|
var uIdx = self.state.messages.findIndex(function(m) {
|
|
5335
5883
|
return m.role === "user" && m._serverItemId === capturedId && !m.isCancelled;
|
|
5336
5884
|
});
|
|
5337
|
-
var
|
|
5885
|
+
var isBg2 = aIdx !== -1 && !!self.state.messages[aIdx].isBackgroundTask || uIdx !== -1 && !!self.state.messages[uIdx].isBackgroundTask;
|
|
5338
5886
|
if (aIdx !== -1) self.state.messages.splice(aIdx, 1);
|
|
5339
|
-
if (!
|
|
5887
|
+
if (!isBg2) {
|
|
5340
5888
|
if (uIdx !== -1) {
|
|
5341
5889
|
var ex = self.state.messages[uIdx];
|
|
5342
5890
|
self.state.messages[uIdx] = { role: "user", content: ex.content, isCancelled: true, _serverItemId: ex._serverItemId };
|
|
@@ -5363,7 +5911,8 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
5363
5911
|
self.updateHistoryCache();
|
|
5364
5912
|
}
|
|
5365
5913
|
}
|
|
5366
|
-
}
|
|
5914
|
+
};
|
|
5915
|
+
var pp = isBg ? item.poll(Object.assign({ latency: POLL_INTERVAL }, pollOpts)) : self._fgPollWithEarlyProbe(item, capturedId, pollOpts);
|
|
5367
5916
|
self._trackPoll(capturedId, item._isBgTask || item._isOnBgQueue ? "bg" : "fg", pp);
|
|
5368
5917
|
if (pp && pp.catch) pp.catch(function() {
|
|
5369
5918
|
});
|
|
@@ -5371,7 +5920,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
5371
5920
|
self.drainBgTaskQueue();
|
|
5372
5921
|
}
|
|
5373
5922
|
if (!fetchMore) self.refreshLiveIndexState();
|
|
5374
|
-
if (!fetchMore) return self.host.scrollToBottomIfSticky();
|
|
5923
|
+
if (!fetchMore) return self.host.settleScroll ? self.host.settleScroll() : self.host.scrollToBottomIfSticky();
|
|
5375
5924
|
}).catch(function(err) {
|
|
5376
5925
|
console.warn("[chat-engine] getChatHistory failed", err);
|
|
5377
5926
|
}).then(function() {
|
|
@@ -5998,7 +6547,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
5998
6547
|
(function() {
|
|
5999
6548
|
var MCP_PROD = "https://mcp.broadwayinc.computer";
|
|
6000
6549
|
var MCP_DEV = "https://mcp-dev.broadwayinc.computer";
|
|
6001
|
-
var BQ_VERSION = "1.8.
|
|
6550
|
+
var BQ_VERSION = "1.8.14" ;
|
|
6002
6551
|
var ATTACHMENT_URL_EXPIRES_SECONDS = 600;
|
|
6003
6552
|
var GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
6004
6553
|
var GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
@@ -7678,10 +8227,10 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
7678
8227
|
refreshMessageBubble(i);
|
|
7679
8228
|
},
|
|
7680
8229
|
scrollToBottom: function(smooth) {
|
|
7681
|
-
return scrollToBottom(
|
|
8230
|
+
return scrollToBottom();
|
|
7682
8231
|
},
|
|
7683
8232
|
scrollToBottomIfSticky: function(smooth) {
|
|
7684
|
-
return scrollToBottomIfSticky(
|
|
8233
|
+
return scrollToBottomIfSticky();
|
|
7685
8234
|
},
|
|
7686
8235
|
// A first page can render shorter than the box (a file's every indexing
|
|
7687
8236
|
// pass folds into ONE row), and a box that cannot scroll never fires the
|
|
@@ -7690,6 +8239,9 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
7690
8239
|
onHistoryLoaded: function(fetchMore, token) {
|
|
7691
8240
|
if (!fetchMore) ensureHistoryFillsViewport(token);
|
|
7692
8241
|
},
|
|
8242
|
+
settleScroll: function() {
|
|
8243
|
+
settleScrollAfterRefresh();
|
|
8244
|
+
},
|
|
7693
8245
|
cancelRequest: function(opts) {
|
|
7694
8246
|
return S.skapi.cancelClientSecretRequest(opts);
|
|
7695
8247
|
},
|
|
@@ -7881,7 +8433,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
7881
8433
|
if (job.stageId) session.settleStagedMessage(job.stageId);
|
|
7882
8434
|
CS.messages.push({ role: "assistant", content: "Something went wrong while uploading attachments. " + (err && err.message || ""), isError: true });
|
|
7883
8435
|
renderMessages();
|
|
7884
|
-
|
|
8436
|
+
scrollToBottomIfSticky();
|
|
7885
8437
|
return null;
|
|
7886
8438
|
});
|
|
7887
8439
|
}
|
|
@@ -7934,28 +8486,60 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
7934
8486
|
};
|
|
7935
8487
|
enqueueAttachmentSend({ text, batchId, stageId, pinned });
|
|
7936
8488
|
}
|
|
7937
|
-
function scrollToBottom(
|
|
8489
|
+
function scrollToBottom() {
|
|
8490
|
+
if (chatScrollAnchor.isFrozen()) {
|
|
8491
|
+
CS.stickToBottom = true;
|
|
8492
|
+
chatScrollAnchor.pinBottom();
|
|
8493
|
+
return Promise.resolve();
|
|
8494
|
+
}
|
|
7938
8495
|
return raf2().then(function() {
|
|
7939
8496
|
if (!CS.messagesBox) return;
|
|
7940
8497
|
CS.stickToBottom = true;
|
|
7941
|
-
|
|
7942
|
-
else CS.messagesBox.scrollTop = CS.messagesBox.scrollHeight;
|
|
8498
|
+
chatScrollAnchor.pinBottom();
|
|
7943
8499
|
});
|
|
7944
8500
|
}
|
|
7945
|
-
function scrollToBottomIfSticky(
|
|
8501
|
+
function scrollToBottomIfSticky() {
|
|
7946
8502
|
if (!CS.stickToBottom) return Promise.resolve();
|
|
8503
|
+
if (chatScrollAnchor.isFrozen()) {
|
|
8504
|
+
chatScrollAnchor.pinBottom();
|
|
8505
|
+
return Promise.resolve();
|
|
8506
|
+
}
|
|
7947
8507
|
return raf2().then(function() {
|
|
7948
8508
|
if (!CS.stickToBottom || !CS.messagesBox) return;
|
|
7949
|
-
|
|
7950
|
-
else CS.messagesBox.scrollTop = CS.messagesBox.scrollHeight;
|
|
8509
|
+
chatScrollAnchor.pinBottom();
|
|
7951
8510
|
});
|
|
7952
8511
|
}
|
|
8512
|
+
function settleScrollAfterRefresh(afterAbsence) {
|
|
8513
|
+
if (afterAbsence || chatScrollAnchor.isReturning()) {
|
|
8514
|
+
if (chatScrollAnchor.settleReturn()) CS.stickToBottom = true;
|
|
8515
|
+
return;
|
|
8516
|
+
}
|
|
8517
|
+
if (CS.stickToBottom) scrollToBottomIfSticky();
|
|
8518
|
+
else chatScrollAnchor.hold();
|
|
8519
|
+
}
|
|
8520
|
+
var lastHistoryScrollTop = 0;
|
|
7953
8521
|
function onHistoryScroll() {
|
|
7954
8522
|
if (!CS.messagesBox || CS.chatSettingsOpen) return;
|
|
7955
8523
|
var el = CS.messagesBox;
|
|
7956
|
-
|
|
8524
|
+
if (chatScrollAnchor.isFrozen()) {
|
|
8525
|
+
lastHistoryScrollTop = el.scrollTop;
|
|
8526
|
+
return;
|
|
8527
|
+
}
|
|
8528
|
+
var atBottom = el.scrollHeight - el.scrollTop - el.clientHeight <= 16;
|
|
8529
|
+
if (!atBottom) CS.stickToBottom = false;
|
|
8530
|
+
else if (el.scrollTop >= lastHistoryScrollTop) CS.stickToBottom = true;
|
|
8531
|
+
lastHistoryScrollTop = el.scrollTop;
|
|
8532
|
+
chatScrollAnchor.remember();
|
|
7957
8533
|
if (el.scrollTop <= 60) pageOlderHistoryUntilTaller();
|
|
7958
8534
|
}
|
|
8535
|
+
function onMessagesFontsSettled() {
|
|
8536
|
+
chatScrollAnchor.hold();
|
|
8537
|
+
}
|
|
8538
|
+
function onMessagesImageSettled(e) {
|
|
8539
|
+
var t = e && e.target;
|
|
8540
|
+
if (!t || t.tagName !== "IMG") return;
|
|
8541
|
+
chatScrollAnchor.absorb(previewLayoutBox(t));
|
|
8542
|
+
}
|
|
7959
8543
|
var _touchStartY = 0;
|
|
7960
8544
|
function onMessagesWheel(e) {
|
|
7961
8545
|
if (e.deltaY < 0) CS.stickToBottom = false;
|
|
@@ -8288,12 +8872,13 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
8288
8872
|
var uid = S.user && S.user.user_id;
|
|
8289
8873
|
if (uid) body.uid = uid;
|
|
8290
8874
|
}
|
|
8291
|
-
|
|
8875
|
+
function unwrap(res) {
|
|
8292
8876
|
var u = typeof res === "string" ? res : res && res.url;
|
|
8293
8877
|
if (!u) throw new Error("No temporary URL returned.");
|
|
8294
8878
|
if (/^https?:\/\//i.test(u)) return u;
|
|
8295
8879
|
return "https://db." + hostDomain() + "/" + u;
|
|
8296
|
-
}
|
|
8880
|
+
}
|
|
8881
|
+
return S.skapi.util.request("get-signed-url", body, reqOpts).then(unwrap);
|
|
8297
8882
|
}
|
|
8298
8883
|
var ATTACH_ICON_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="18" height="18"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>';
|
|
8299
8884
|
var FILE_ICON_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>';
|
|
@@ -8841,7 +9426,15 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
8841
9426
|
// earlier failure left on this file's chips.
|
|
8842
9427
|
onLoad: function(path) {
|
|
8843
9428
|
clearLinkUnavailable(linkUnavailableKeysForPath(path));
|
|
8844
|
-
scrollToBottomIfSticky(
|
|
9429
|
+
scrollToBottomIfSticky();
|
|
9430
|
+
},
|
|
9431
|
+
// The resizes an <img> makes with no event of its own to announce
|
|
9432
|
+
// them: the src landing, and the src being dropped for a retry (which
|
|
9433
|
+
// collapses an already-painted picture to nothing). load and error are
|
|
9434
|
+
// covered by the listener on the message box, which also catches the
|
|
9435
|
+
// markdown images this module never sees.
|
|
9436
|
+
onLayoutChange: function(img) {
|
|
9437
|
+
chatScrollAnchor.absorb(img);
|
|
8845
9438
|
},
|
|
8846
9439
|
// The mint was refused, or the url it minted would not load. Either
|
|
8847
9440
|
// way there is no url for this file, so the caption chip left behind
|
|
@@ -8863,16 +9456,23 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
8863
9456
|
}
|
|
8864
9457
|
var refreshedLinkExpiryTimer = null;
|
|
8865
9458
|
function expireAllRefreshedLinks() {
|
|
8866
|
-
|
|
8867
|
-
for (var
|
|
9459
|
+
var changed = false;
|
|
9460
|
+
for (var k in refreshedExpiredLinkMap) {
|
|
9461
|
+
delete refreshedExpiredLinkMap[k];
|
|
9462
|
+
changed = true;
|
|
9463
|
+
}
|
|
9464
|
+
for (var u in unavailableLinkMap) {
|
|
9465
|
+
delete unavailableLinkMap[u];
|
|
9466
|
+
changed = true;
|
|
9467
|
+
}
|
|
9468
|
+
return changed;
|
|
8868
9469
|
}
|
|
8869
9470
|
function scheduleNextLinkExpiryBoundary() {
|
|
8870
9471
|
if (refreshedLinkExpiryTimer) clearTimeout(refreshedLinkExpiryTimer);
|
|
8871
9472
|
var now = Date.now();
|
|
8872
9473
|
var next = Math.ceil(now / LINK_REFRESH_WINDOW_MS) * LINK_REFRESH_WINDOW_MS;
|
|
8873
9474
|
refreshedLinkExpiryTimer = setTimeout(function() {
|
|
8874
|
-
expireAllRefreshedLinks();
|
|
8875
|
-
renderMessages();
|
|
9475
|
+
if (expireAllRefreshedLinks()) renderMessages();
|
|
8876
9476
|
scheduleNextLinkExpiryBoundary();
|
|
8877
9477
|
}, Math.max(1, next - now));
|
|
8878
9478
|
}
|
|
@@ -9608,40 +10208,33 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
9608
10208
|
function indexGroupAnchorId(group) {
|
|
9609
10209
|
return group.anchorId || "";
|
|
9610
10210
|
}
|
|
10211
|
+
var chatScrollAnchor = createScrollAnchor({
|
|
10212
|
+
getBox: function() {
|
|
10213
|
+
return CS.messagesBox;
|
|
10214
|
+
},
|
|
10215
|
+
isStuck: function() {
|
|
10216
|
+
return !!CS.stickToBottom;
|
|
10217
|
+
},
|
|
10218
|
+
// A hidden tab keeps mutating this list - a resumed poll, the head refresh
|
|
10219
|
+
// and its deferred background batch, a settling request - and every one of
|
|
10220
|
+
// those would otherwise move a scroll position nobody is looking at. Freeze
|
|
10221
|
+
// instead, and put the reader back once they can see it (settleScroll).
|
|
10222
|
+
isFrozen: function() {
|
|
10223
|
+
return typeof document !== "undefined" && !!document.hidden;
|
|
10224
|
+
},
|
|
10225
|
+
rawFallback: true
|
|
10226
|
+
});
|
|
9611
10227
|
function captureScrollAnchor() {
|
|
9612
|
-
|
|
9613
|
-
if (!box || CS.stickToBottom) return null;
|
|
9614
|
-
var boxTop = box.getBoundingClientRect().top;
|
|
9615
|
-
var kids = box.children;
|
|
9616
|
-
var fallback = null;
|
|
9617
|
-
for (var i = 0; i < kids.length; i++) {
|
|
9618
|
-
if (!kids[i].getAttribute) continue;
|
|
9619
|
-
var key = kids[i].getAttribute("data-row-key");
|
|
9620
|
-
if (!key) continue;
|
|
9621
|
-
var top = kids[i].getBoundingClientRect().top - boxTop;
|
|
9622
|
-
if (top + kids[i].offsetHeight <= 0) continue;
|
|
9623
|
-
var cand = { key, top, scrollTop: box.scrollTop, pos: kids[i].getAttribute("data-row-pos") };
|
|
9624
|
-
if (cand.pos === null) return cand;
|
|
9625
|
-
if (!fallback) fallback = cand;
|
|
9626
|
-
}
|
|
9627
|
-
return fallback || { key: null, top: 0, scrollTop: box.scrollTop };
|
|
10228
|
+
return chatScrollAnchor.capture();
|
|
9628
10229
|
}
|
|
9629
10230
|
function restoreScrollAnchor(anchor) {
|
|
9630
10231
|
var box = CS.messagesBox;
|
|
9631
10232
|
if (!box) return;
|
|
9632
|
-
if (
|
|
9633
|
-
|
|
10233
|
+
if (CS.stickToBottom) {
|
|
10234
|
+
chatScrollAnchor.pinBottom();
|
|
9634
10235
|
return;
|
|
9635
10236
|
}
|
|
9636
|
-
|
|
9637
|
-
var kids = box.children;
|
|
9638
|
-
for (var i = 0; anchor.key && i < kids.length; i++) {
|
|
9639
|
-
if (!kids[i].getAttribute || kids[i].getAttribute("data-row-key") !== anchor.key) continue;
|
|
9640
|
-
if (anchor.pos && kids[i].getAttribute("data-row-pos") !== anchor.pos) break;
|
|
9641
|
-
box.scrollTop += kids[i].getBoundingClientRect().top - boxTop - anchor.top;
|
|
9642
|
-
return;
|
|
9643
|
-
}
|
|
9644
|
-
box.scrollTop = anchor.scrollTop;
|
|
10237
|
+
chatScrollAnchor.restore(anchor);
|
|
9645
10238
|
}
|
|
9646
10239
|
function syncDraftingIndicator() {
|
|
9647
10240
|
if (!CS.messagesBox) return;
|
|
@@ -9684,6 +10277,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
9684
10277
|
if (CS.loadingHistory && !CS.loadingOlderHistory) {
|
|
9685
10278
|
CS.messagesBox.appendChild(historyLoadingEl(true));
|
|
9686
10279
|
syncDraftingIndicator();
|
|
10280
|
+
restoreScrollAnchor(anchor);
|
|
9687
10281
|
return;
|
|
9688
10282
|
}
|
|
9689
10283
|
var emptyStubEls = [];
|
|
@@ -9692,12 +10286,16 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
9692
10286
|
for (var ge = 0; ge < emptyEntries.length; ge++) {
|
|
9693
10287
|
if (emptyEntries[ge].kind !== "indexing") continue;
|
|
9694
10288
|
var sg = emptyEntries[ge].group;
|
|
9695
|
-
|
|
10289
|
+
var stubEl = buildIndexGroupEl(sg, !!CS.indexGroupsOpen[sg.key]);
|
|
10290
|
+
stubEl.setAttribute("data-row-key", "g" + sg.runKey);
|
|
10291
|
+
stubEl.setAttribute("data-row-pos", indexGroupAnchorId(sg));
|
|
10292
|
+
emptyStubEls.push(stubEl);
|
|
9696
10293
|
}
|
|
9697
10294
|
} catch (e) {
|
|
9698
10295
|
}
|
|
9699
10296
|
for (var gse = 0; gse < emptyStubEls.length; gse++) CS.messagesBox.appendChild(emptyStubEls[gse]);
|
|
9700
10297
|
syncDraftingIndicator();
|
|
10298
|
+
restoreScrollAnchor(anchor);
|
|
9701
10299
|
return;
|
|
9702
10300
|
}
|
|
9703
10301
|
var rows = buildChatDisplayList(CS.messages, displayListOptions());
|
|
@@ -9762,14 +10360,17 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
9762
10360
|
if (idx < 0 || idx >= CS.messages.length) return;
|
|
9763
10361
|
var oldEl = CS.messageEls[idx];
|
|
9764
10362
|
if (!oldEl || !oldEl.parentNode) return;
|
|
9765
|
-
|
|
9766
|
-
|
|
9767
|
-
|
|
9768
|
-
|
|
10363
|
+
chatScrollAnchor.preserve(function() {
|
|
10364
|
+
var newEl = buildMessageEl(CS.messages[idx], idx);
|
|
10365
|
+
if (oldEl.classList.contains("bq-index-pass")) newEl.classList.add("bq-index-pass");
|
|
10366
|
+
oldEl.parentNode.replaceChild(newEl, oldEl);
|
|
10367
|
+
CS.messageEls[idx] = newEl;
|
|
10368
|
+
});
|
|
9769
10369
|
hydrateMessageImagePreviews();
|
|
9770
10370
|
}
|
|
9771
10371
|
function renderChat() {
|
|
9772
10372
|
clearImagePreviewCache(S.projectId || "default");
|
|
10373
|
+
chatScrollAnchor.forget();
|
|
9773
10374
|
for (var uk in unavailableLinkMap) delete unavailableLinkMap[uk];
|
|
9774
10375
|
CS.messages = [];
|
|
9775
10376
|
CS.messageEls = [];
|
|
@@ -9844,6 +10445,11 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
9844
10445
|
box.addEventListener("wheel", onMessagesWheel, { passive: true });
|
|
9845
10446
|
box.addEventListener("touchstart", onMessagesTouchStart, { passive: true });
|
|
9846
10447
|
box.addEventListener("touchmove", onMessagesTouchMove, { passive: true });
|
|
10448
|
+
box.addEventListener("load", onMessagesImageSettled, true);
|
|
10449
|
+
box.addEventListener("error", onMessagesImageSettled, true);
|
|
10450
|
+
if (document.fonts && document.fonts.addEventListener) {
|
|
10451
|
+
document.fonts.addEventListener("loadingdone", onMessagesFontsSettled);
|
|
10452
|
+
}
|
|
9847
10453
|
CS.messagesBox = box;
|
|
9848
10454
|
var input = h("textarea", { class: "bq-input", rows: "1", placeholder: "Ask anything about: " + (S.serviceName || "your project") });
|
|
9849
10455
|
CS.inputEl = input;
|
|
@@ -9867,7 +10473,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
9867
10473
|
if (drafting !== CS.drafting) {
|
|
9868
10474
|
CS.drafting = drafting;
|
|
9869
10475
|
syncDraftingIndicator();
|
|
9870
|
-
scrollToBottomIfSticky(
|
|
10476
|
+
scrollToBottomIfSticky();
|
|
9871
10477
|
}
|
|
9872
10478
|
});
|
|
9873
10479
|
input.addEventListener("keydown", function(e) {
|
|
@@ -10221,18 +10827,26 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
10221
10827
|
S._resizeBound = true;
|
|
10222
10828
|
window.addEventListener("resize", function() {
|
|
10223
10829
|
scheduleAttachmentOverflowRecompute();
|
|
10224
|
-
scrollToBottomIfSticky(
|
|
10830
|
+
scrollToBottomIfSticky();
|
|
10225
10831
|
ensureHistoryFillsViewport();
|
|
10226
10832
|
});
|
|
10227
10833
|
}
|
|
10228
10834
|
if (!S._visBound && typeof document !== "undefined" && document.addEventListener) {
|
|
10229
10835
|
S._visBound = true;
|
|
10836
|
+
window.addEventListener("blur", function() {
|
|
10837
|
+
chatScrollAnchor.park();
|
|
10838
|
+
});
|
|
10839
|
+
window.addEventListener("focus", function() {
|
|
10840
|
+
settleScrollAfterRefresh(true);
|
|
10841
|
+
});
|
|
10230
10842
|
document.addEventListener("visibilitychange", function() {
|
|
10231
10843
|
if (document.visibilityState === "hidden") {
|
|
10844
|
+
chatScrollAnchor.park();
|
|
10232
10845
|
if (session && session.pausePolling) session.pausePolling("hidden");
|
|
10233
10846
|
return;
|
|
10234
10847
|
}
|
|
10235
10848
|
if (document.visibilityState === "visible") {
|
|
10849
|
+
settleScrollAfterRefresh(true);
|
|
10236
10850
|
var refreshed = S.user ? ensureMcpGrantFresh() : null;
|
|
10237
10851
|
Promise.resolve(refreshed).catch(function() {
|
|
10238
10852
|
}).then(function() {
|