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/dist/engine.cjs
CHANGED
|
@@ -901,18 +901,31 @@ function classifyInlineLink(full, groups, ctx) {
|
|
|
901
901
|
part: { type: "link", label: truncateLabelForDisplay(urlLabel), fullLabel: urlLabel, href: originalHref, expired: false }
|
|
902
902
|
});
|
|
903
903
|
}
|
|
904
|
+
function canonicalizePathForm(value) {
|
|
905
|
+
if (!value) return value;
|
|
906
|
+
try {
|
|
907
|
+
return value.normalize("NFC");
|
|
908
|
+
} catch (e) {
|
|
909
|
+
return value;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
904
912
|
function linkUnavailableKeyForPath(remotePath) {
|
|
905
|
-
return "path:" + (remotePath || "");
|
|
913
|
+
return "path:" + canonicalizePathForm(remotePath || "");
|
|
906
914
|
}
|
|
907
915
|
function linkUnavailableKeyForHref(href) {
|
|
908
|
-
|
|
916
|
+
var carried = readExpiredAttachmentHref(href);
|
|
917
|
+
if (carried) return linkUnavailableKeyForPath(carried);
|
|
918
|
+
return "href:" + canonicalizePathForm(href || "");
|
|
909
919
|
}
|
|
910
920
|
function linkUnavailableKeysForPath(remotePath) {
|
|
911
921
|
if (!remotePath) return [];
|
|
912
|
-
|
|
922
|
+
var keys = [
|
|
913
923
|
linkUnavailableKeyForPath(remotePath),
|
|
914
924
|
linkUnavailableKeyForHref(buildDisplayExpiredAttachmentHref(remotePath))
|
|
915
925
|
];
|
|
926
|
+
return keys.filter(function(k, i) {
|
|
927
|
+
return keys.indexOf(k) === i;
|
|
928
|
+
});
|
|
916
929
|
}
|
|
917
930
|
function isLinkUnavailable(link, map) {
|
|
918
931
|
if (!link || !map) return false;
|
|
@@ -1313,7 +1326,7 @@ function renderInlineLinkHtml(link, opts) {
|
|
|
1313
1326
|
if (link.remotePath) attrs.push('data-bq-remote-path="' + escapeInlineHtml(link.remotePath) + '"');
|
|
1314
1327
|
if (link.fullLabel) attrs.push('data-bq-full-label="' + escapeInlineHtml(link.fullLabel) + '"');
|
|
1315
1328
|
if (!preview) return "<a " + attrs.join(" ") + ">" + escapeInlineHtml(labelText) + "</a>";
|
|
1316
|
-
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 : "") + '"
|
|
1329
|
+
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>";
|
|
1317
1330
|
}
|
|
1318
1331
|
|
|
1319
1332
|
// src/engine/image_preview.ts
|
|
@@ -1399,31 +1412,48 @@ function hydrateOne(img, ctx) {
|
|
|
1399
1412
|
var warm = peekImagePreviewUrl(ctx, path);
|
|
1400
1413
|
if (warm) {
|
|
1401
1414
|
img.setAttribute("src", warm);
|
|
1415
|
+
notifyLayoutChange(img, ctx, path);
|
|
1402
1416
|
return;
|
|
1403
1417
|
}
|
|
1404
1418
|
resolveImagePreviewUrl(ctx, path, type).then(function(url) {
|
|
1405
1419
|
if (img.getAttribute("data-bq-img-state") !== "loading") return;
|
|
1406
1420
|
img.setAttribute("src", url);
|
|
1421
|
+
notifyLayoutChange(img, ctx, path);
|
|
1407
1422
|
}, function(e) {
|
|
1408
1423
|
img.setAttribute("data-bq-img-state", "error");
|
|
1424
|
+
notifyLayoutChange(img, ctx, path);
|
|
1409
1425
|
if (ctx.onError) ctx.onError(path, e);
|
|
1410
1426
|
});
|
|
1411
1427
|
}
|
|
1412
1428
|
function onImageError(img, ctx, path, type) {
|
|
1413
1429
|
if (img.getAttribute("data-bq-img-retry") === "1") {
|
|
1414
1430
|
img.setAttribute("data-bq-img-state", "error");
|
|
1431
|
+
notifyLayoutChange(img, ctx, path);
|
|
1415
1432
|
if (ctx.onError) ctx.onError(path, new Error("image preview failed to load"));
|
|
1416
1433
|
return;
|
|
1417
1434
|
}
|
|
1418
1435
|
img.setAttribute("data-bq-img-retry", "1");
|
|
1419
1436
|
img.removeAttribute("src");
|
|
1437
|
+
notifyLayoutChange(img, ctx, path);
|
|
1420
1438
|
resolveImagePreviewUrl(ctx, path, type, true).then(function(url) {
|
|
1421
1439
|
img.setAttribute("src", url);
|
|
1440
|
+
notifyLayoutChange(img, ctx, path);
|
|
1422
1441
|
}, function(e) {
|
|
1423
1442
|
img.setAttribute("data-bq-img-state", "error");
|
|
1443
|
+
notifyLayoutChange(img, ctx, path);
|
|
1424
1444
|
if (ctx.onError) ctx.onError(path, e);
|
|
1425
1445
|
});
|
|
1426
1446
|
}
|
|
1447
|
+
function notifyLayoutChange(img, ctx, path) {
|
|
1448
|
+
if (!ctx.onLayoutChange) return;
|
|
1449
|
+
ctx.onLayoutChange(previewLayoutBox(img), path);
|
|
1450
|
+
}
|
|
1451
|
+
var PREVIEW_LAYOUT_BOX_SELECTOR = "a.bq-link-button.is-image-preview";
|
|
1452
|
+
function previewLayoutBox(img) {
|
|
1453
|
+
var closest = img.closest;
|
|
1454
|
+
if (typeof closest !== "function") return img;
|
|
1455
|
+
return closest.call(img, PREVIEW_LAYOUT_BOX_SELECTOR) || img;
|
|
1456
|
+
}
|
|
1427
1457
|
|
|
1428
1458
|
// src/engine/time.ts
|
|
1429
1459
|
function wallClockNow() {
|
|
@@ -2531,6 +2561,327 @@ function mapHistoryListToMessages(list, platform, opts) {
|
|
|
2531
2561
|
}
|
|
2532
2562
|
return { messages: mapped, runningItemIds };
|
|
2533
2563
|
}
|
|
2564
|
+
function shouldRescueInFlightMessage(m, ctx) {
|
|
2565
|
+
if (!m) return false;
|
|
2566
|
+
if (m.isBackgroundTask) return false;
|
|
2567
|
+
if (m._ownerKey !== void 0 && ctx.loadKey !== void 0 && m._ownerKey !== ctx.loadKey) return false;
|
|
2568
|
+
if (m._serverItemId && ctx.hasServerId(m._serverItemId)) return false;
|
|
2569
|
+
if (m._stageId) return true;
|
|
2570
|
+
if (!m._serverItemId && ctx.pageHasPendingAssistant) return false;
|
|
2571
|
+
if (m.isSendingToServer || m.isPendingQueued || m.isPendingInProcess || m.isPending) return true;
|
|
2572
|
+
if (ctx.sending && m.role === "user") {
|
|
2573
|
+
var next = ctx.next;
|
|
2574
|
+
if (!next || next.isBackgroundTask || !next.isPending) return false;
|
|
2575
|
+
return next._serverItemId === void 0 || next._serverItemId === m._serverItemId;
|
|
2576
|
+
}
|
|
2577
|
+
return false;
|
|
2578
|
+
}
|
|
2579
|
+
|
|
2580
|
+
// src/engine/scroll_anchor.ts
|
|
2581
|
+
var ROW_KEY_ATTR = "data-row-key";
|
|
2582
|
+
var ROW_POS_ATTR = "data-row-pos";
|
|
2583
|
+
var MAX_ALTS = 2;
|
|
2584
|
+
var ALT_SCAN_LIMIT = 64;
|
|
2585
|
+
var UNKNOWN_ROW_POS = "\0?";
|
|
2586
|
+
function createScrollAnchor(options) {
|
|
2587
|
+
var held = null;
|
|
2588
|
+
var seen = typeof WeakMap === "function" ? /* @__PURE__ */ new WeakMap() : null;
|
|
2589
|
+
function capture() {
|
|
2590
|
+
var box = options.getBox();
|
|
2591
|
+
if (!box || options.isStuck()) return null;
|
|
2592
|
+
var boxTop = box.getBoundingClientRect().top;
|
|
2593
|
+
var kids = box.children;
|
|
2594
|
+
var fallback = null;
|
|
2595
|
+
var fallbackAt = -1;
|
|
2596
|
+
for (var i = 0; i < kids.length; i++) {
|
|
2597
|
+
var el = kids[i];
|
|
2598
|
+
if (!el || typeof el.getAttribute !== "function") continue;
|
|
2599
|
+
var key = el.getAttribute(ROW_KEY_ATTR);
|
|
2600
|
+
if (!key) continue;
|
|
2601
|
+
var top = el.getBoundingClientRect().top - boxTop;
|
|
2602
|
+
if (top + el.offsetHeight <= 0) continue;
|
|
2603
|
+
if (top >= box.clientHeight) break;
|
|
2604
|
+
var rawPos = el.getAttribute(ROW_POS_ATTR);
|
|
2605
|
+
var pos = rawPos === null ? null : rawPos || UNKNOWN_ROW_POS;
|
|
2606
|
+
var cand = {
|
|
2607
|
+
key,
|
|
2608
|
+
top,
|
|
2609
|
+
pos,
|
|
2610
|
+
scrollTop: box.scrollTop,
|
|
2611
|
+
scrollHeight: box.scrollHeight,
|
|
2612
|
+
el
|
|
2613
|
+
};
|
|
2614
|
+
if (rawPos === null) {
|
|
2615
|
+
cand.alts = collectAlts(box, boxTop, i + 1);
|
|
2616
|
+
return cand;
|
|
2617
|
+
}
|
|
2618
|
+
if (!fallback) {
|
|
2619
|
+
fallback = cand;
|
|
2620
|
+
fallbackAt = i;
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
if (fallback) {
|
|
2624
|
+
fallback.alts = collectAlts(box, boxTop, fallbackAt + 1, true);
|
|
2625
|
+
return fallback;
|
|
2626
|
+
}
|
|
2627
|
+
return {
|
|
2628
|
+
key: null,
|
|
2629
|
+
top: 0,
|
|
2630
|
+
pos: null,
|
|
2631
|
+
scrollTop: box.scrollTop,
|
|
2632
|
+
scrollHeight: box.scrollHeight,
|
|
2633
|
+
el: null
|
|
2634
|
+
};
|
|
2635
|
+
}
|
|
2636
|
+
function collectAlts(box, boxTop, from, ordinaryOnly) {
|
|
2637
|
+
var out = [];
|
|
2638
|
+
var kids = box.children;
|
|
2639
|
+
var stop = Math.min(kids.length, from + ALT_SCAN_LIMIT);
|
|
2640
|
+
for (var i = from; i < stop && out.length < MAX_ALTS; i++) {
|
|
2641
|
+
var el = kids[i];
|
|
2642
|
+
if (!el || typeof el.getAttribute !== "function") continue;
|
|
2643
|
+
var key = el.getAttribute(ROW_KEY_ATTR);
|
|
2644
|
+
if (!key) continue;
|
|
2645
|
+
var rawPos = el.getAttribute(ROW_POS_ATTR);
|
|
2646
|
+
if (ordinaryOnly && rawPos !== null) continue;
|
|
2647
|
+
out.push({
|
|
2648
|
+
key,
|
|
2649
|
+
top: el.getBoundingClientRect().top - boxTop,
|
|
2650
|
+
pos: rawPos === null ? null : rawPos || UNKNOWN_ROW_POS,
|
|
2651
|
+
el
|
|
2652
|
+
});
|
|
2653
|
+
}
|
|
2654
|
+
return out.length ? out : void 0;
|
|
2655
|
+
}
|
|
2656
|
+
function findRow(box, anchor) {
|
|
2657
|
+
var el = anchor.el;
|
|
2658
|
+
if (el && el.parentNode === box) return el;
|
|
2659
|
+
if (!anchor.key) return null;
|
|
2660
|
+
var kids = box.children;
|
|
2661
|
+
for (var i = 0; i < kids.length; i++) {
|
|
2662
|
+
var kid = kids[i];
|
|
2663
|
+
if (!kid || typeof kid.getAttribute !== "function") continue;
|
|
2664
|
+
if (kid.getAttribute(ROW_KEY_ATTR) === anchor.key) return kid;
|
|
2665
|
+
}
|
|
2666
|
+
return null;
|
|
2667
|
+
}
|
|
2668
|
+
var sawFrozen = false;
|
|
2669
|
+
var parked = null;
|
|
2670
|
+
var parkedStuck = false;
|
|
2671
|
+
var returning = false;
|
|
2672
|
+
var wroteTop = -1;
|
|
2673
|
+
var restoreExact = true;
|
|
2674
|
+
var restorePinned = false;
|
|
2675
|
+
function frozen() {
|
|
2676
|
+
var f = !!options.isFrozen && options.isFrozen();
|
|
2677
|
+
if (f) sawFrozen = true;
|
|
2678
|
+
return f;
|
|
2679
|
+
}
|
|
2680
|
+
function restore(anchor, unbounded) {
|
|
2681
|
+
if (frozen()) return;
|
|
2682
|
+
var box = options.getBox();
|
|
2683
|
+
if (!box || !anchor || options.isStuck()) return;
|
|
2684
|
+
var el = findRow(box, anchor);
|
|
2685
|
+
if (el) {
|
|
2686
|
+
var livePos = el.getAttribute(ROW_POS_ATTR) || UNKNOWN_ROW_POS;
|
|
2687
|
+
if (anchor.pos !== null && anchor.pos !== UNKNOWN_ROW_POS && livePos !== UNKNOWN_ROW_POS && livePos !== anchor.pos) el = null;
|
|
2688
|
+
}
|
|
2689
|
+
if (el) {
|
|
2690
|
+
var boxTop = box.getBoundingClientRect().top;
|
|
2691
|
+
var delta = el.getBoundingClientRect().top - boxTop - anchor.top;
|
|
2692
|
+
var slack = Math.abs(box.scrollHeight - anchor.scrollHeight) + box.clientHeight;
|
|
2693
|
+
if (!unbounded && (delta > slack || delta < -slack)) {
|
|
2694
|
+
lost(box, anchor);
|
|
2695
|
+
return;
|
|
2696
|
+
}
|
|
2697
|
+
var want = box.scrollTop + delta;
|
|
2698
|
+
if (delta >= 1 || delta <= -1) box.scrollTop += delta;
|
|
2699
|
+
wroteTop = box.scrollTop;
|
|
2700
|
+
restorePinned = true;
|
|
2701
|
+
restoreExact = box.scrollTop >= want - 1 && box.scrollTop <= want + 1;
|
|
2702
|
+
held = {
|
|
2703
|
+
key: anchor.key,
|
|
2704
|
+
top: anchor.top,
|
|
2705
|
+
pos: anchor.pos,
|
|
2706
|
+
scrollTop: box.scrollTop,
|
|
2707
|
+
scrollHeight: box.scrollHeight,
|
|
2708
|
+
el,
|
|
2709
|
+
// Carried, not dropped: one successful restore used to disarm the
|
|
2710
|
+
// standbys for every later hold.
|
|
2711
|
+
alts: anchor.alts
|
|
2712
|
+
};
|
|
2713
|
+
return;
|
|
2714
|
+
}
|
|
2715
|
+
var alts = anchor.alts;
|
|
2716
|
+
for (var ai = 0; alts && ai < alts.length; ai++) {
|
|
2717
|
+
var alt = alts[ai];
|
|
2718
|
+
var ael = findRow(box, { key: alt.key, top: alt.top, pos: alt.pos, scrollTop: anchor.scrollTop, scrollHeight: anchor.scrollHeight, el: alt.el });
|
|
2719
|
+
if (!ael) continue;
|
|
2720
|
+
if (alt.pos !== null && alt.pos !== UNKNOWN_ROW_POS) {
|
|
2721
|
+
var altLive = ael.getAttribute(ROW_POS_ATTR) || UNKNOWN_ROW_POS;
|
|
2722
|
+
if (altLive !== UNKNOWN_ROW_POS && altLive !== alt.pos) continue;
|
|
2723
|
+
}
|
|
2724
|
+
var aboxTop = box.getBoundingClientRect().top;
|
|
2725
|
+
var adelta = ael.getBoundingClientRect().top - aboxTop - alt.top;
|
|
2726
|
+
var aslack = Math.abs(box.scrollHeight - anchor.scrollHeight) + box.clientHeight;
|
|
2727
|
+
if (!unbounded && (adelta > aslack || adelta < -aslack)) continue;
|
|
2728
|
+
var awant = box.scrollTop + adelta;
|
|
2729
|
+
if (adelta >= 1 || adelta <= -1) box.scrollTop += adelta;
|
|
2730
|
+
wroteTop = box.scrollTop;
|
|
2731
|
+
restorePinned = true;
|
|
2732
|
+
restoreExact = box.scrollTop >= awant - 1 && box.scrollTop <= awant + 1;
|
|
2733
|
+
held = {
|
|
2734
|
+
key: alt.key,
|
|
2735
|
+
top: alt.top,
|
|
2736
|
+
pos: alt.pos,
|
|
2737
|
+
scrollTop: box.scrollTop,
|
|
2738
|
+
scrollHeight: box.scrollHeight,
|
|
2739
|
+
el: ael,
|
|
2740
|
+
alts: alts.slice(ai + 1)
|
|
2741
|
+
};
|
|
2742
|
+
return;
|
|
2743
|
+
}
|
|
2744
|
+
lost(box, anchor);
|
|
2745
|
+
}
|
|
2746
|
+
function lost(box, anchor) {
|
|
2747
|
+
held = null;
|
|
2748
|
+
var grew = box.scrollHeight - anchor.scrollHeight;
|
|
2749
|
+
if (grew > 0) {
|
|
2750
|
+
box.scrollTop = anchor.scrollTop + grew;
|
|
2751
|
+
wroteTop = box.scrollTop;
|
|
2752
|
+
return;
|
|
2753
|
+
}
|
|
2754
|
+
if (options.rawFallback) {
|
|
2755
|
+
box.scrollTop = anchor.scrollTop;
|
|
2756
|
+
wroteTop = box.scrollTop;
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
function preserve(mutate) {
|
|
2760
|
+
var anchor = capture();
|
|
2761
|
+
var result = mutate();
|
|
2762
|
+
restore(anchor);
|
|
2763
|
+
return result;
|
|
2764
|
+
}
|
|
2765
|
+
function remember() {
|
|
2766
|
+
var b0 = options.getBox();
|
|
2767
|
+
if (returning && b0 && b0.scrollTop !== wroteTop) {
|
|
2768
|
+
parked = null;
|
|
2769
|
+
parkedStuck = false;
|
|
2770
|
+
returning = false;
|
|
2771
|
+
}
|
|
2772
|
+
if (frozen()) return;
|
|
2773
|
+
held = capture();
|
|
2774
|
+
}
|
|
2775
|
+
function hold() {
|
|
2776
|
+
if (frozen()) return;
|
|
2777
|
+
if (sawFrozen || returning) {
|
|
2778
|
+
settleReturn();
|
|
2779
|
+
return;
|
|
2780
|
+
}
|
|
2781
|
+
var box = options.getBox();
|
|
2782
|
+
if (!box || options.isStuck()) {
|
|
2783
|
+
held = null;
|
|
2784
|
+
return;
|
|
2785
|
+
}
|
|
2786
|
+
if (!held) {
|
|
2787
|
+
held = capture();
|
|
2788
|
+
return;
|
|
2789
|
+
}
|
|
2790
|
+
if (box.scrollTop !== held.scrollTop) {
|
|
2791
|
+
held = capture();
|
|
2792
|
+
return;
|
|
2793
|
+
}
|
|
2794
|
+
restore(held);
|
|
2795
|
+
}
|
|
2796
|
+
function absorb(el) {
|
|
2797
|
+
if (!el) return;
|
|
2798
|
+
var box = options.getBox();
|
|
2799
|
+
if (!box) return;
|
|
2800
|
+
var h = el.offsetHeight;
|
|
2801
|
+
var prev = seen ? seen.get(el) : void 0;
|
|
2802
|
+
if (seen) seen.set(el, h);
|
|
2803
|
+
if (options.isStuck()) return;
|
|
2804
|
+
if (prev === void 0) prev = 0;
|
|
2805
|
+
var delta = h - prev;
|
|
2806
|
+
if (delta === 0) return;
|
|
2807
|
+
if (frozen()) {
|
|
2808
|
+
foldFrozenGrowth(box, el, delta);
|
|
2809
|
+
return;
|
|
2810
|
+
}
|
|
2811
|
+
if (el.getBoundingClientRect().top >= box.getBoundingClientRect().top) return;
|
|
2812
|
+
box.scrollTop += delta;
|
|
2813
|
+
wroteTop = box.scrollTop;
|
|
2814
|
+
if (held) held = capture();
|
|
2815
|
+
}
|
|
2816
|
+
function park() {
|
|
2817
|
+
parked = capture();
|
|
2818
|
+
parkedStuck = !!options.isStuck();
|
|
2819
|
+
returning = true;
|
|
2820
|
+
}
|
|
2821
|
+
function pinBottom() {
|
|
2822
|
+
var box = options.getBox();
|
|
2823
|
+
if (!box) return;
|
|
2824
|
+
box.scrollTop = box.scrollHeight;
|
|
2825
|
+
wroteTop = box.scrollTop;
|
|
2826
|
+
}
|
|
2827
|
+
function settleReturn() {
|
|
2828
|
+
if (frozen()) return false;
|
|
2829
|
+
sawFrozen = false;
|
|
2830
|
+
if (parkedStuck) {
|
|
2831
|
+
pinBottom();
|
|
2832
|
+
return true;
|
|
2833
|
+
}
|
|
2834
|
+
var target = parked || held;
|
|
2835
|
+
restoreExact = true;
|
|
2836
|
+
restorePinned = false;
|
|
2837
|
+
restore(target, true);
|
|
2838
|
+
parked = !restorePinned || !restoreExact ? target : null;
|
|
2839
|
+
returning = !!parked;
|
|
2840
|
+
return false;
|
|
2841
|
+
}
|
|
2842
|
+
function isReturning() {
|
|
2843
|
+
return returning;
|
|
2844
|
+
}
|
|
2845
|
+
function thaw() {
|
|
2846
|
+
settleReturn();
|
|
2847
|
+
}
|
|
2848
|
+
function foldFrozenGrowth(box, el, delta) {
|
|
2849
|
+
var elTop = el.getBoundingClientRect().top;
|
|
2850
|
+
foldInto(box, held, elTop, delta);
|
|
2851
|
+
foldInto(box, parked, elTop, delta);
|
|
2852
|
+
}
|
|
2853
|
+
function foldInto(box, a, elTop, delta) {
|
|
2854
|
+
if (!a || a.top >= 0) return;
|
|
2855
|
+
var rowEl = findRow(box, a);
|
|
2856
|
+
if (!rowEl) return;
|
|
2857
|
+
var within = elTop - rowEl.getBoundingClientRect().top;
|
|
2858
|
+
if (within < 0 || within >= rowEl.offsetHeight) return;
|
|
2859
|
+
if (within >= -a.top) return;
|
|
2860
|
+
a.top -= delta;
|
|
2861
|
+
a.scrollHeight += delta;
|
|
2862
|
+
}
|
|
2863
|
+
function forget() {
|
|
2864
|
+
held = null;
|
|
2865
|
+
parked = null;
|
|
2866
|
+
parkedStuck = false;
|
|
2867
|
+
returning = false;
|
|
2868
|
+
}
|
|
2869
|
+
return {
|
|
2870
|
+
capture,
|
|
2871
|
+
restore,
|
|
2872
|
+
preserve,
|
|
2873
|
+
remember,
|
|
2874
|
+
hold,
|
|
2875
|
+
park,
|
|
2876
|
+
settleReturn,
|
|
2877
|
+
isReturning,
|
|
2878
|
+
pinBottom,
|
|
2879
|
+
isFrozen: frozen,
|
|
2880
|
+
thaw,
|
|
2881
|
+
absorb,
|
|
2882
|
+
forget
|
|
2883
|
+
};
|
|
2884
|
+
}
|
|
2534
2885
|
|
|
2535
2886
|
// src/engine/viewport_fill.ts
|
|
2536
2887
|
var HISTORY_FILL_SLACK_PX = 64;
|
|
@@ -2642,6 +2993,7 @@ var WORKER_PASS_ADOPT_LIMIT = 20;
|
|
|
2642
2993
|
var LIVE_INDEX_SNAPSHOT_MAX_AGE_MS = 5e3;
|
|
2643
2994
|
var INDEX_DISPATCH_CLAIM_MS = 2 * 60 * 1e3;
|
|
2644
2995
|
var WORKER_PASS_ADOPT_ATTEMPTS = [0, 2e3, 6e3];
|
|
2996
|
+
var EARLY_PROBE_SCHEDULE_MS = [400, 900, 1700];
|
|
2645
2997
|
var INDEXING_DRAIN_BUSY_POLL_MS = 8e3;
|
|
2646
2998
|
var INDEXING_DRAIN_CONFIRM_POLL_MS = 3e3;
|
|
2647
2999
|
var INDEXING_DRAIN_IDLE_LOOKS = 2;
|
|
@@ -3037,7 +3389,93 @@ var ChatSession = class {
|
|
|
3037
3389
|
*
|
|
3038
3390
|
* `stop` comes from the SDK and may be absent on an older skapi-js, in which case the
|
|
3039
3391
|
* poll simply cannot be stopped and is left running — see pausePolling.
|
|
3392
|
+
*
|
|
3393
|
+
* (This block documents _trackPoll, further down. The two methods below sit between it
|
|
3394
|
+
* and its subject.)
|
|
3040
3395
|
*/
|
|
3396
|
+
/**
|
|
3397
|
+
* Foreground poll with an early-probe race.
|
|
3398
|
+
*
|
|
3399
|
+
* skapi's poll() is a bare setInterval(fn, latency) with NO check at t=0, so the earliest a
|
|
3400
|
+
* reply can be observed is one full POLL_INTERVAL (3s) after dispatch. For a long generation
|
|
3401
|
+
* that granularity is free. For a SHORT one it is nearly pure dead time: a greeting that the
|
|
3402
|
+
* provider finishes in 1s still waits until the 3s tick, which measured as a large share of a
|
|
3403
|
+
* 5s "yo" round trip.
|
|
3404
|
+
*
|
|
3405
|
+
* So keep the 3s interval as the steady state, and additionally point-look-up the item a few
|
|
3406
|
+
* times early, on a widening schedule. Whichever answers first wins and the other is stopped.
|
|
3407
|
+
* The probe uses the csrHistoryItemLookup hook both clients already implement; without it this
|
|
3408
|
+
* degrades to exactly the old behaviour.
|
|
3409
|
+
*
|
|
3410
|
+
* FOREGROUND ONLY. Background indexing polls keep the flat cadence: nobody is watching them,
|
|
3411
|
+
* and they are the ones bounded by MAX_CONCURRENT_BG_POLLS, so adding probes there would spend
|
|
3412
|
+
* the request budget the cap exists to protect.
|
|
3413
|
+
*/
|
|
3414
|
+
attachForegroundPoll(source, itemId, opts) {
|
|
3415
|
+
return this._fgPollWithEarlyProbe(source, itemId, opts);
|
|
3416
|
+
}
|
|
3417
|
+
_fgPollWithEarlyProbe(source, itemId, opts) {
|
|
3418
|
+
var base = source.poll(Object.assign({ latency: POLL_INTERVAL }, opts || {}));
|
|
3419
|
+
var lookup = chatEngineConfig().csrHistoryItemLookup;
|
|
3420
|
+
var ident = this.host.getIdentity();
|
|
3421
|
+
var platform = ident && ident.platform;
|
|
3422
|
+
if (!lookup || !itemId || !ident || !ident.projectId || platform !== "claude" && platform !== "openai") {
|
|
3423
|
+
return base;
|
|
3424
|
+
}
|
|
3425
|
+
var settled = false;
|
|
3426
|
+
var timers = [];
|
|
3427
|
+
var clearProbes = function() {
|
|
3428
|
+
for (var i = 0; i < timers.length; i++) clearTimeout(timers[i]);
|
|
3429
|
+
timers = [];
|
|
3430
|
+
};
|
|
3431
|
+
var stopBase = base && typeof base.stop === "function" ? base.stop.bind(base) : null;
|
|
3432
|
+
var raced = new Promise(function(resolve, reject) {
|
|
3433
|
+
base.then(function(res) {
|
|
3434
|
+
if (settled) return;
|
|
3435
|
+
settled = true;
|
|
3436
|
+
clearProbes();
|
|
3437
|
+
resolve(res);
|
|
3438
|
+
}, function(err) {
|
|
3439
|
+
if (settled) return;
|
|
3440
|
+
settled = true;
|
|
3441
|
+
clearProbes();
|
|
3442
|
+
reject(err);
|
|
3443
|
+
});
|
|
3444
|
+
var fullId = buildHistoryItemFullId(platform, ident.projectId, itemId);
|
|
3445
|
+
EARLY_PROBE_SCHEDULE_MS.forEach(function(delay) {
|
|
3446
|
+
timers.push(setTimeout(function() {
|
|
3447
|
+
if (settled) return;
|
|
3448
|
+
Promise.resolve(lookup(fullId, ident.projectId, ident.owner)).then(function(body) {
|
|
3449
|
+
if (settled || !body || typeof body !== "object") return;
|
|
3450
|
+
if (body.status === "pending" || body.status === "running") return;
|
|
3451
|
+
if (isPollStopped(body)) return;
|
|
3452
|
+
settled = true;
|
|
3453
|
+
clearProbes();
|
|
3454
|
+
if (stopBase) {
|
|
3455
|
+
try {
|
|
3456
|
+
stopBase();
|
|
3457
|
+
} catch (e) {
|
|
3458
|
+
}
|
|
3459
|
+
}
|
|
3460
|
+
if (opts && typeof opts.onResponse === "function") {
|
|
3461
|
+
try {
|
|
3462
|
+
opts.onResponse(body);
|
|
3463
|
+
} catch (e) {
|
|
3464
|
+
}
|
|
3465
|
+
}
|
|
3466
|
+
resolve(body);
|
|
3467
|
+
}, function() {
|
|
3468
|
+
});
|
|
3469
|
+
}, delay));
|
|
3470
|
+
});
|
|
3471
|
+
});
|
|
3472
|
+
raced.stop = function() {
|
|
3473
|
+
settled = true;
|
|
3474
|
+
clearProbes();
|
|
3475
|
+
if (stopBase) stopBase();
|
|
3476
|
+
};
|
|
3477
|
+
return raced;
|
|
3478
|
+
}
|
|
3041
3479
|
_trackPoll(id, kind, p) {
|
|
3042
3480
|
var stop = p && typeof p.stop === "function" ? p.stop.bind(p) : void 0;
|
|
3043
3481
|
if (!stop) {
|
|
@@ -3213,6 +3651,68 @@ var ChatSession = class {
|
|
|
3213
3651
|
startKeyHistory: this.state.historyStartKeyHistory.slice()
|
|
3214
3652
|
};
|
|
3215
3653
|
}
|
|
3654
|
+
/**
|
|
3655
|
+
* Give the immediate-send pair the server's id for their turn, the moment the
|
|
3656
|
+
* dispatch learns it.
|
|
3657
|
+
*
|
|
3658
|
+
* WHY THIS EXISTS. An immediate send pushes its user bubble and its
|
|
3659
|
+
* "Thinking..." placeholder locally, and until now neither ever carried a
|
|
3660
|
+
* _serverItemId — only the QUEUED path stamped one, off its ack. So for the
|
|
3661
|
+
* whole life of the turn there was no way to tell the local copy and the
|
|
3662
|
+
* server's copy of the SAME turn apart, and the history merge fell back to a
|
|
3663
|
+
* heuristic: rescue the local pair unless the freshly-fetched page happens to
|
|
3664
|
+
* contain a pending assistant.
|
|
3665
|
+
*
|
|
3666
|
+
* That heuristic has a hole exactly one poll interval wide. The server settles
|
|
3667
|
+
* the request; for up to POLL_INTERVAL the client has not noticed, so
|
|
3668
|
+
* `state.sending` is still true and the local pair is still on screen — while a
|
|
3669
|
+
* history fetch issued in that window returns the turn ALREADY SETTLED, with no
|
|
3670
|
+
* pending assistant in it. The rescue then re-appends the local pair below the
|
|
3671
|
+
* server's copy (the question, twice), and when the poll finally resolves,
|
|
3672
|
+
* typewriteLatestReply writes the answer into the rescued placeholder because it
|
|
3673
|
+
* is the only pending assistant left (the answer, twice). updateHistoryCache
|
|
3674
|
+
* persists the result, so it survives every later visit.
|
|
3675
|
+
*
|
|
3676
|
+
* Navigating away while waiting and coming back is what lands a fetch in that
|
|
3677
|
+
* window: a remount runs refreshGate -> a fresh first page, at an arbitrary
|
|
3678
|
+
* moment relative to the 3s poll.
|
|
3679
|
+
*
|
|
3680
|
+
* With the id on the bubbles, both clients' rescue loops skip them through the
|
|
3681
|
+
* dedup they already have (`_serverItemId is in this page`), the reply the
|
|
3682
|
+
* dispatch caches inherits the id too, and nothing needs a new special case.
|
|
3683
|
+
*
|
|
3684
|
+
* Matched by _localId, never by index: a file's indexing rows are spliced in
|
|
3685
|
+
* above these bubbles while the request is in flight.
|
|
3686
|
+
*/
|
|
3687
|
+
_stampTurnWithItemId(key, userLid, placeholderLid, itemId) {
|
|
3688
|
+
if (!itemId) return;
|
|
3689
|
+
var changed = false;
|
|
3690
|
+
var stamp = function(list) {
|
|
3691
|
+
var hit = false;
|
|
3692
|
+
for (var i = 0; i < list.length; i++) {
|
|
3693
|
+
var m = list[i];
|
|
3694
|
+
if (!m || !m._localId) continue;
|
|
3695
|
+
if (m._localId !== userLid && m._localId !== placeholderLid) continue;
|
|
3696
|
+
if (m._serverItemId === itemId) continue;
|
|
3697
|
+
list[i] = Object.assign({}, m, { _serverItemId: itemId });
|
|
3698
|
+
hit = true;
|
|
3699
|
+
}
|
|
3700
|
+
return hit;
|
|
3701
|
+
};
|
|
3702
|
+
changed = stamp(this.state.messages);
|
|
3703
|
+
var cached = key ? this.aiChatHistoryCache[key] : void 0;
|
|
3704
|
+
if (cached) {
|
|
3705
|
+
var msgs = cached.messages.slice();
|
|
3706
|
+
if (stamp(msgs)) {
|
|
3707
|
+
this.aiChatHistoryCache[key] = {
|
|
3708
|
+
messages: msgs,
|
|
3709
|
+
endOfList: cached.endOfList,
|
|
3710
|
+
startKeyHistory: cached.startKeyHistory
|
|
3711
|
+
};
|
|
3712
|
+
}
|
|
3713
|
+
}
|
|
3714
|
+
if (changed) this.host.notify();
|
|
3715
|
+
}
|
|
3216
3716
|
/**
|
|
3217
3717
|
* Land a resolved reply in the history cache of a chat that is NOT currently
|
|
3218
3718
|
* visible, without touching state.messages. Mirrors the cache-only path in
|
|
@@ -3294,8 +3794,9 @@ var ChatSession = class {
|
|
|
3294
3794
|
if (initial.id) {
|
|
3295
3795
|
if (dispatchItemId && dispatchItemId !== initial.id) self.historyItemPolls.delete(dispatchItemId);
|
|
3296
3796
|
dispatchItemId = initial.id;
|
|
3797
|
+
if (typeof params.onItemId === "function") params.onItemId(initial.id);
|
|
3297
3798
|
}
|
|
3298
|
-
var dp =
|
|
3799
|
+
var dp = self._fgPollWithEarlyProbe(initial, initial.id);
|
|
3299
3800
|
if (initial.id) self._trackPoll(initial.id, "fg", dp);
|
|
3300
3801
|
return dp;
|
|
3301
3802
|
}
|
|
@@ -3320,28 +3821,9 @@ var ChatSession = class {
|
|
|
3320
3821
|
}).then(function(result) {
|
|
3321
3822
|
delete self.pendingAgentRequests[params.key];
|
|
3322
3823
|
if (dispatchItemId) self.historyItemPolls.delete(dispatchItemId);
|
|
3323
|
-
var existing = self.aiChatHistoryCache[params.key] || { messages: [], endOfList: false, startKeyHistory: [] };
|
|
3324
3824
|
var reply = { role: "assistant", content: result.content, isError: result.isError };
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
for (var i = msgs.length - 1; i >= 0; i--) {
|
|
3328
|
-
var m = msgs[i];
|
|
3329
|
-
if (m && m.isPending && m.role === "assistant" && !m.isBackgroundTask) {
|
|
3330
|
-
idx = i;
|
|
3331
|
-
break;
|
|
3332
|
-
}
|
|
3333
|
-
}
|
|
3334
|
-
if (idx !== -1) {
|
|
3335
|
-
reply._serverItemId = msgs[idx]._serverItemId;
|
|
3336
|
-
msgs[idx] = reply;
|
|
3337
|
-
} else {
|
|
3338
|
-
msgs.push(reply);
|
|
3339
|
-
}
|
|
3340
|
-
self.aiChatHistoryCache[params.key] = {
|
|
3341
|
-
messages: msgs,
|
|
3342
|
-
endOfList: existing.endOfList,
|
|
3343
|
-
startKeyHistory: existing.startKeyHistory
|
|
3344
|
-
};
|
|
3825
|
+
if (dispatchItemId) reply._serverItemId = dispatchItemId;
|
|
3826
|
+
self._applyReplyToCache(params.key, reply, dispatchItemId);
|
|
3345
3827
|
return result;
|
|
3346
3828
|
});
|
|
3347
3829
|
this.pendingAgentRequests[params.key] = run;
|
|
@@ -3730,8 +4212,9 @@ var ChatSession = class {
|
|
|
3730
4212
|
}
|
|
3731
4213
|
this.host.notify();
|
|
3732
4214
|
this.updateHistoryCache();
|
|
3733
|
-
this.
|
|
4215
|
+
this.scrollForDispatch(stageId);
|
|
3734
4216
|
var capturedComposed = composed, capturedPlatform = aiPlatform, capturedKey = key;
|
|
4217
|
+
var capturedQueuedLid = queuedBubble._localId;
|
|
3735
4218
|
Promise.resolve(this._callProviderFor(aiPlatform, composed, boundedQ.messages, systemPrompt, aiModel, chatQueue, extractContent, fileUrls, id.projectId, id.owner)).then(function(result) {
|
|
3736
4219
|
var sendingIdx = self.getHistoryCacheKey() !== capturedKey ? -1 : self.state.messages.findIndex(function(m) {
|
|
3737
4220
|
return m.isSendingToServer && (m.isPendingQueued || m.isPendingInProcess) && m.role === "user" && !m._stageId && (m._ownerKey === void 0 || m._ownerKey === capturedKey);
|
|
@@ -3743,8 +4226,9 @@ var ChatSession = class {
|
|
|
3743
4226
|
self.state.messages[sendingIdx] = upd;
|
|
3744
4227
|
self.host.notify();
|
|
3745
4228
|
}
|
|
4229
|
+
if (serverId) self._stampTurnWithItemId(capturedKey, capturedQueuedLid, void 0, serverId);
|
|
3746
4230
|
if (result && result.poll && (result.status === "pending" || result.status === "running")) {
|
|
3747
|
-
var qp =
|
|
4231
|
+
var qp = self._fgPollWithEarlyProbe(result, serverId);
|
|
3748
4232
|
if (serverId) self._trackPoll(serverId, "fg", qp);
|
|
3749
4233
|
return qp.then(function(res) {
|
|
3750
4234
|
if (isPollStopped(res)) return;
|
|
@@ -3760,7 +4244,7 @@ var ChatSession = class {
|
|
|
3760
4244
|
return;
|
|
3761
4245
|
}
|
|
3762
4246
|
var immediateUser = { role: "user", content: composed, _localId: this._newLocalId(), _ts: wallClockNow(), ...key ? { _ownerKey: key } : {} };
|
|
3763
|
-
var immediatePlaceholder = { role: "assistant", content: "", isPending: true, isPendingInProcess: true, ...key ? { _ownerKey: key } : {} };
|
|
4247
|
+
var immediatePlaceholder = { role: "assistant", content: "", isPending: true, isPendingInProcess: true, _localId: this._newLocalId(), ...key ? { _ownerKey: key } : {} };
|
|
3764
4248
|
var iStage = this._stageIndex(this.state.messages, stageId);
|
|
3765
4249
|
if (iStage !== -1) {
|
|
3766
4250
|
var iEx = this.state.messages[iStage];
|
|
@@ -3774,7 +4258,7 @@ var ChatSession = class {
|
|
|
3774
4258
|
this.host.notify();
|
|
3775
4259
|
this.updateHistoryCache();
|
|
3776
4260
|
this.state.sending = true;
|
|
3777
|
-
this.
|
|
4261
|
+
this.scrollForDispatch(stageId);
|
|
3778
4262
|
var historyForLlm = this.state.messages.filter(function(m) {
|
|
3779
4263
|
if (m === immediateUser) return false;
|
|
3780
4264
|
return !m.isPending && !m.isPendingQueued && !m.isPendingInProcess && !m.isPendingOlder && !m.isCancelled && !m.isBackgroundTask && !m.isError;
|
|
@@ -3787,6 +4271,7 @@ var ChatSession = class {
|
|
|
3787
4271
|
projectId: id.projectId,
|
|
3788
4272
|
history: historyForLlm
|
|
3789
4273
|
});
|
|
4274
|
+
var immediateUserLid = immediateUser._localId, immediatePlaceholderLid = immediatePlaceholder._localId;
|
|
3790
4275
|
var run = this.dispatchAgentRequest({
|
|
3791
4276
|
key,
|
|
3792
4277
|
projectId: id.projectId,
|
|
@@ -3798,7 +4283,15 @@ var ChatSession = class {
|
|
|
3798
4283
|
boundedMessages: bounded.messages,
|
|
3799
4284
|
userId: chatQueue,
|
|
3800
4285
|
extractContent,
|
|
3801
|
-
fileUrls
|
|
4286
|
+
fileUrls,
|
|
4287
|
+
// THE fix for a turn rendering twice after navigate-away-and-back. Until
|
|
4288
|
+
// this existed the immediate-send pair carried no server id for its whole
|
|
4289
|
+
// life (only the QUEUED path stamped one, off its ack), so nothing could
|
|
4290
|
+
// tell the local copy and the server's copy of the same turn apart. See
|
|
4291
|
+
// _stampTurnWithItemId.
|
|
4292
|
+
onItemId: function(itemId) {
|
|
4293
|
+
self._stampTurnWithItemId(key, immediateUserLid, immediatePlaceholderLid, itemId);
|
|
4294
|
+
}
|
|
3802
4295
|
});
|
|
3803
4296
|
Promise.resolve(run).catch(function() {
|
|
3804
4297
|
}).then(function() {
|
|
@@ -3809,6 +4302,28 @@ var ChatSession = class {
|
|
|
3809
4302
|
});
|
|
3810
4303
|
});
|
|
3811
4304
|
}
|
|
4305
|
+
/**
|
|
4306
|
+
* Scroll for a dispatch that is going out NOW, but never for one arriving late.
|
|
4307
|
+
*
|
|
4308
|
+
* A turn with attachments does not dispatch when the user hits Send: it waits out
|
|
4309
|
+
* its uploads and then its whole indexing chain, which is minutes
|
|
4310
|
+
* (awaitIndexingDrained). By then the reader has very often scrolled up into
|
|
4311
|
+
* history to pass the time, and the forcing scrollToBottom yanked them out of it
|
|
4312
|
+
* — and worse, force-pinned stickToBottom, which no-ops every method on the
|
|
4313
|
+
* scroll anchor and re-arms the queue-detect poll whose only bail is
|
|
4314
|
+
* !stickToBottom.
|
|
4315
|
+
*
|
|
4316
|
+
* `stageId` is the exact marker for that case: only the attachment path ever
|
|
4317
|
+
* produces one. The gesture itself was already paid for at stage time, where
|
|
4318
|
+
* stageOutgoingMessage forces the scroll while the user is still looking at the
|
|
4319
|
+
* composer. Deliberately NOT gated on "did we find the staged bubble" — a remount
|
|
4320
|
+
* rebuilds from the cache and the turn is appended rather than replaced, which is
|
|
4321
|
+
* just as late and just as unrequested.
|
|
4322
|
+
*/
|
|
4323
|
+
scrollForDispatch(stageId) {
|
|
4324
|
+
if (stageId) this.host.scrollToBottomIfSticky(true);
|
|
4325
|
+
else this.host.scrollToBottom(true);
|
|
4326
|
+
}
|
|
3812
4327
|
promoteNextBgQueuedToRunning() {
|
|
3813
4328
|
if (this.state.messages.some(function(m) {
|
|
3814
4329
|
return m.isPending && m.role === "assistant" && m.isBackgroundTask;
|
|
@@ -3934,6 +4449,29 @@ var ChatSession = class {
|
|
|
3934
4449
|
else if (targetIdx >= 0) this.state.messages.splice(targetIdx, 0, msg);
|
|
3935
4450
|
else this.state.messages.push(msg);
|
|
3936
4451
|
}
|
|
4452
|
+
/**
|
|
4453
|
+
* The server's OWN copy of this turn is already on screen.
|
|
4454
|
+
*
|
|
4455
|
+
* A first-page fetch can land between the server settling the item and this
|
|
4456
|
+
* poll's tick, and now that the local bubbles carry the item id
|
|
4457
|
+
* (_stampTurnWithItemId) the rescue correctly drops them and renders the
|
|
4458
|
+
* server's settled pair instead. There is then nothing left to resolve: the
|
|
4459
|
+
* -1 fallback would push the answer in a SECOND time at the bottom of the list,
|
|
4460
|
+
* and the positional fallbacks would hijack some other turn's bubble.
|
|
4461
|
+
*
|
|
4462
|
+
* The USER bubble counts, not just a settled assistant: an item whose answer is
|
|
4463
|
+
* empty produces no assistant bubble at all in the mapper, and that variant
|
|
4464
|
+
* would otherwise still bottom-push "No text response received...". While a turn
|
|
4465
|
+
* is genuinely live its user bubble is always pending (the queued branch sets
|
|
4466
|
+
* isPendingQueued, promoteNextQueuedToRunning sets isPendingInProcess), so this
|
|
4467
|
+
* cannot fire early.
|
|
4468
|
+
*/
|
|
4469
|
+
_turnAlreadyRendered(serverId) {
|
|
4470
|
+
if (!serverId) return false;
|
|
4471
|
+
return this.state.messages.some(function(m) {
|
|
4472
|
+
return m._serverItemId === serverId && !m.isPending && !m.isPendingQueued && !m.isPendingInProcess;
|
|
4473
|
+
});
|
|
4474
|
+
}
|
|
3937
4475
|
onQueuedSendResponse(_composed, response, platform, serverId, ownerKey) {
|
|
3938
4476
|
if (serverId) this.historyItemPolls.delete(serverId);
|
|
3939
4477
|
if (ownerKey && this.getHistoryCacheKey() !== ownerKey) {
|
|
@@ -3942,6 +4480,12 @@ var ChatSession = class {
|
|
|
3942
4480
|
if (serverId) this.cancelledServerIds.delete(serverId);
|
|
3943
4481
|
return;
|
|
3944
4482
|
}
|
|
4483
|
+
if (this._turnAlreadyRendered(serverId)) {
|
|
4484
|
+
if (serverId) this.cancelledServerIds.delete(serverId);
|
|
4485
|
+
this.host.notify();
|
|
4486
|
+
this.updateHistoryCache();
|
|
4487
|
+
return;
|
|
4488
|
+
}
|
|
3945
4489
|
var targetIdx = this.resolveQueuedUserBubble(serverId);
|
|
3946
4490
|
if (targetIdx === void 0) {
|
|
3947
4491
|
this.host.notify();
|
|
@@ -3983,6 +4527,12 @@ var ChatSession = class {
|
|
|
3983
4527
|
if (serverId) this.cancelledServerIds.delete(serverId);
|
|
3984
4528
|
return;
|
|
3985
4529
|
}
|
|
4530
|
+
if (this._turnAlreadyRendered(serverId)) {
|
|
4531
|
+
if (serverId) this.cancelledServerIds.delete(serverId);
|
|
4532
|
+
this.host.notify();
|
|
4533
|
+
this.updateHistoryCache();
|
|
4534
|
+
return;
|
|
4535
|
+
}
|
|
3986
4536
|
var isNotExists = err && (err.code === "NOT_EXISTS" || err.body && err.body.code === "NOT_EXISTS");
|
|
3987
4537
|
if (isNotExists) {
|
|
3988
4538
|
var userIdx = serverId ? this.state.messages.findIndex(function(m) {
|
|
@@ -5244,21 +5794,15 @@ var ChatSession = class {
|
|
|
5244
5794
|
var rescued = [];
|
|
5245
5795
|
for (var ri = 0; ri < self.state.messages.length; ri++) {
|
|
5246
5796
|
var mm = self.state.messages[ri];
|
|
5247
|
-
if (mm
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
if (mm.isSendingToServer || mm.isPendingQueued || mm.isPendingInProcess || mm.isPending) rescued.push(mm);
|
|
5257
|
-
else if (self.state.sending && mm.role === "user") {
|
|
5258
|
-
var next = self.state.messages[ri + 1];
|
|
5259
|
-
if (next && !next.isBackgroundTask && next.isPending && !next._serverItemId) rescued.push(mm);
|
|
5260
|
-
}
|
|
5261
|
-
}
|
|
5797
|
+
if (shouldRescueInFlightMessage(mm, {
|
|
5798
|
+
hasServerId: function(sid) {
|
|
5799
|
+
return !!serverIds[sid];
|
|
5800
|
+
},
|
|
5801
|
+
pageHasPendingAssistant: mappedHasPendingAssistant,
|
|
5802
|
+
sending: !!self.state.sending,
|
|
5803
|
+
next: self.state.messages[ri + 1],
|
|
5804
|
+
loadKey
|
|
5805
|
+
})) rescued.push(mm);
|
|
5262
5806
|
}
|
|
5263
5807
|
var oldestInPage1 = void 0;
|
|
5264
5808
|
mapped.forEach(function(m) {
|
|
@@ -5309,6 +5853,9 @@ var ChatSession = class {
|
|
|
5309
5853
|
keptOlderPages = prependOlder.length > 0 || interleave.length > 0;
|
|
5310
5854
|
self.state.messages = prependOlder.length ? prependOlder.concat(page1) : page1;
|
|
5311
5855
|
rescued.forEach(function(m) {
|
|
5856
|
+
if (m._serverItemId && self.state.messages.some(function(x) {
|
|
5857
|
+
return x._serverItemId === m._serverItemId && x.role === m.role;
|
|
5858
|
+
})) return;
|
|
5312
5859
|
self.state.messages.push(m);
|
|
5313
5860
|
});
|
|
5314
5861
|
if (Object.keys(locallyCancelled).length) {
|
|
@@ -5433,6 +5980,7 @@ var ChatSession = class {
|
|
|
5433
5980
|
releaseBgFlag();
|
|
5434
5981
|
self.updateHistoryCache();
|
|
5435
5982
|
self.host.notify();
|
|
5983
|
+
if (self.host.settleScroll) self.host.settleScroll();
|
|
5436
5984
|
}, function() {
|
|
5437
5985
|
releaseBgFlag();
|
|
5438
5986
|
self.host.notify();
|
|
@@ -5460,8 +6008,8 @@ var ChatSession = class {
|
|
|
5460
6008
|
if ((item._isBgTask || item._isOnBgQueue) && self.isPollingPaused()) return;
|
|
5461
6009
|
if ((item._isBgTask || item._isOnBgQueue) && !bgAllow[item.id]) return;
|
|
5462
6010
|
var capturedId = item.id;
|
|
5463
|
-
var
|
|
5464
|
-
|
|
6011
|
+
var isBg = !!(item._isBgTask || item._isOnBgQueue);
|
|
6012
|
+
var pollOpts = {
|
|
5465
6013
|
onResponse: function(response) {
|
|
5466
6014
|
if (isPollStopped(response)) return;
|
|
5467
6015
|
self.handleHistoryItemResolution(capturedId, response, platform);
|
|
@@ -5476,9 +6024,9 @@ var ChatSession = class {
|
|
|
5476
6024
|
var uIdx = self.state.messages.findIndex(function(m) {
|
|
5477
6025
|
return m.role === "user" && m._serverItemId === capturedId && !m.isCancelled;
|
|
5478
6026
|
});
|
|
5479
|
-
var
|
|
6027
|
+
var isBg2 = aIdx !== -1 && !!self.state.messages[aIdx].isBackgroundTask || uIdx !== -1 && !!self.state.messages[uIdx].isBackgroundTask;
|
|
5480
6028
|
if (aIdx !== -1) self.state.messages.splice(aIdx, 1);
|
|
5481
|
-
if (!
|
|
6029
|
+
if (!isBg2) {
|
|
5482
6030
|
if (uIdx !== -1) {
|
|
5483
6031
|
var ex = self.state.messages[uIdx];
|
|
5484
6032
|
self.state.messages[uIdx] = { role: "user", content: ex.content, isCancelled: true, _serverItemId: ex._serverItemId };
|
|
@@ -5505,7 +6053,8 @@ var ChatSession = class {
|
|
|
5505
6053
|
self.updateHistoryCache();
|
|
5506
6054
|
}
|
|
5507
6055
|
}
|
|
5508
|
-
}
|
|
6056
|
+
};
|
|
6057
|
+
var pp = isBg ? item.poll(Object.assign({ latency: POLL_INTERVAL }, pollOpts)) : self._fgPollWithEarlyProbe(item, capturedId, pollOpts);
|
|
5509
6058
|
self._trackPoll(capturedId, item._isBgTask || item._isOnBgQueue ? "bg" : "fg", pp);
|
|
5510
6059
|
if (pp && pp.catch) pp.catch(function() {
|
|
5511
6060
|
});
|
|
@@ -5513,7 +6062,7 @@ var ChatSession = class {
|
|
|
5513
6062
|
self.drainBgTaskQueue();
|
|
5514
6063
|
}
|
|
5515
6064
|
if (!fetchMore) self.refreshLiveIndexState();
|
|
5516
|
-
if (!fetchMore) return self.host.scrollToBottomIfSticky();
|
|
6065
|
+
if (!fetchMore) return self.host.settleScroll ? self.host.settleScroll() : self.host.scrollToBottomIfSticky();
|
|
5517
6066
|
}).catch(function(err) {
|
|
5518
6067
|
console.warn("[chat-engine] getChatHistory failed", err);
|
|
5519
6068
|
}).then(function() {
|
|
@@ -6180,6 +6729,7 @@ exports.POLL_INTERVAL = POLL_INTERVAL;
|
|
|
6180
6729
|
exports.PRESIGN_SAFETY_MARGIN_MS = PRESIGN_SAFETY_MARGIN_MS;
|
|
6181
6730
|
exports.PREVIEWABLE_IMAGE_CONTENT_TYPES = PREVIEWABLE_IMAGE_CONTENT_TYPES;
|
|
6182
6731
|
exports.PREVIEW_BROWSER_CACHE_SECONDS = PREVIEW_BROWSER_CACHE_SECONDS;
|
|
6732
|
+
exports.PREVIEW_LAYOUT_BOX_SELECTOR = PREVIEW_LAYOUT_BOX_SELECTOR;
|
|
6183
6733
|
exports.PREVIEW_URL_EXPIRES_SECONDS = PREVIEW_URL_EXPIRES_SECONDS;
|
|
6184
6734
|
exports.RENDER_FROM_TOKEN = RENDER_FROM_TOKEN;
|
|
6185
6735
|
exports.RTF_EXTS = RTF_EXTS;
|
|
@@ -6205,6 +6755,7 @@ exports.buildIndexingWindowMessage = buildIndexingWindowMessage;
|
|
|
6205
6755
|
exports.callClaudeWithMcp = callClaudeWithMcp;
|
|
6206
6756
|
exports.callClaudeWithPublicMcp = callClaudeWithPublicMcp;
|
|
6207
6757
|
exports.callOpenAIWithPublicMcp = callOpenAIWithPublicMcp;
|
|
6758
|
+
exports.canonicalizePathForm = canonicalizePathForm;
|
|
6208
6759
|
exports.chatEngineConfig = chatEngineConfig;
|
|
6209
6760
|
exports.classifyInlineLink = classifyInlineLink;
|
|
6210
6761
|
exports.clearAttachmentParsers = clearAttachmentParsers;
|
|
@@ -6214,6 +6765,7 @@ exports.configureChatEngine = configureChatEngine;
|
|
|
6214
6765
|
exports.contentTypeForExt = contentTypeForExt;
|
|
6215
6766
|
exports.createHistoryFiller = createHistoryFiller;
|
|
6216
6767
|
exports.createInlineLinkRegex = createInlineLinkRegex;
|
|
6768
|
+
exports.createScrollAnchor = createScrollAnchor;
|
|
6217
6769
|
exports.encodePathSegments = encodePathSegments;
|
|
6218
6770
|
exports.encodingClassForExt = encodingClassForExt;
|
|
6219
6771
|
exports.ensureHtmlCharset = ensureHtmlCharset;
|
|
@@ -6283,6 +6835,7 @@ exports.peekImagePreviewUrl = peekImagePreviewUrl;
|
|
|
6283
6835
|
exports.prepareDownloadText = prepareDownloadText;
|
|
6284
6836
|
exports.presignExpiryEpochMs = presignExpiryEpochMs;
|
|
6285
6837
|
exports.previewImageContentType = previewImageContentType;
|
|
6838
|
+
exports.previewLayoutBox = previewLayoutBox;
|
|
6286
6839
|
exports.previewMintCacheToken = previewMintCacheToken;
|
|
6287
6840
|
exports.previewableExtOf = previewableExtOf;
|
|
6288
6841
|
exports.readExpiredAttachmentHref = readExpiredAttachmentHref;
|
|
@@ -6296,6 +6849,7 @@ exports.runIndexUniqueId = runIndexUniqueId;
|
|
|
6296
6849
|
exports.safeDecodeURIComponent = safeDecodeURIComponent;
|
|
6297
6850
|
exports.sanitizeAttachmentLinksForHistory = sanitizeAttachmentLinksForHistory;
|
|
6298
6851
|
exports.setProjectContextWindow = setProjectContextWindow;
|
|
6852
|
+
exports.shouldRescueInFlightMessage = shouldRescueInFlightMessage;
|
|
6299
6853
|
exports.stripFileBlocksFromHistory = stripFileBlocksFromHistory;
|
|
6300
6854
|
exports.transformContentWithImages = transformContentWithImages;
|
|
6301
6855
|
exports.transformContentWithOpenAIImages = transformContentWithOpenAIImages;
|