bunnyquery 1.8.13 → 1.8.14
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 +508 -90
- package/dist/engine.cjs +446 -51
- package/dist/engine.cjs.map +1 -1
- package/dist/engine.d.mts +482 -212
- package/dist/engine.d.ts +482 -212
- package/dist/engine.mjs +442 -52
- package/dist/engine.mjs.map +1 -1
- package/package.json +3 -2
- package/src/engine/history.ts +64 -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 +367 -0
- package/src/engine/session.ts +302 -60
- 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,169 @@ 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 UNKNOWN_ROW_POS = "\0?";
|
|
2584
|
+
function createScrollAnchor(options) {
|
|
2585
|
+
var held = null;
|
|
2586
|
+
var seen = typeof WeakMap === "function" ? /* @__PURE__ */ new WeakMap() : null;
|
|
2587
|
+
function capture() {
|
|
2588
|
+
var box = options.getBox();
|
|
2589
|
+
if (!box || options.isStuck()) return null;
|
|
2590
|
+
var boxTop = box.getBoundingClientRect().top;
|
|
2591
|
+
var kids = box.children;
|
|
2592
|
+
var fallback = null;
|
|
2593
|
+
for (var i = 0; i < kids.length; i++) {
|
|
2594
|
+
var el = kids[i];
|
|
2595
|
+
if (!el || typeof el.getAttribute !== "function") continue;
|
|
2596
|
+
var key = el.getAttribute(ROW_KEY_ATTR);
|
|
2597
|
+
if (!key) continue;
|
|
2598
|
+
var top = el.getBoundingClientRect().top - boxTop;
|
|
2599
|
+
if (top + el.offsetHeight <= 0) continue;
|
|
2600
|
+
if (top >= box.clientHeight) break;
|
|
2601
|
+
var rawPos = el.getAttribute(ROW_POS_ATTR);
|
|
2602
|
+
var pos = rawPos === null ? null : rawPos || UNKNOWN_ROW_POS;
|
|
2603
|
+
var cand = {
|
|
2604
|
+
key,
|
|
2605
|
+
top,
|
|
2606
|
+
pos,
|
|
2607
|
+
scrollTop: box.scrollTop,
|
|
2608
|
+
scrollHeight: box.scrollHeight,
|
|
2609
|
+
el
|
|
2610
|
+
};
|
|
2611
|
+
if (rawPos === null) return cand;
|
|
2612
|
+
if (!fallback) fallback = cand;
|
|
2613
|
+
}
|
|
2614
|
+
return fallback || {
|
|
2615
|
+
key: null,
|
|
2616
|
+
top: 0,
|
|
2617
|
+
pos: null,
|
|
2618
|
+
scrollTop: box.scrollTop,
|
|
2619
|
+
scrollHeight: box.scrollHeight,
|
|
2620
|
+
el: null
|
|
2621
|
+
};
|
|
2622
|
+
}
|
|
2623
|
+
function findRow(box, anchor) {
|
|
2624
|
+
var el = anchor.el;
|
|
2625
|
+
if (el && el.parentNode === box) return el;
|
|
2626
|
+
if (!anchor.key) return null;
|
|
2627
|
+
var kids = box.children;
|
|
2628
|
+
for (var i = 0; i < kids.length; i++) {
|
|
2629
|
+
var kid = kids[i];
|
|
2630
|
+
if (!kid || typeof kid.getAttribute !== "function") continue;
|
|
2631
|
+
if (kid.getAttribute(ROW_KEY_ATTR) === anchor.key) return kid;
|
|
2632
|
+
}
|
|
2633
|
+
return null;
|
|
2634
|
+
}
|
|
2635
|
+
function restore(anchor) {
|
|
2636
|
+
var box = options.getBox();
|
|
2637
|
+
if (!box || !anchor || options.isStuck()) return;
|
|
2638
|
+
var el = findRow(box, anchor);
|
|
2639
|
+
if (el) {
|
|
2640
|
+
var livePos = el.getAttribute(ROW_POS_ATTR) || UNKNOWN_ROW_POS;
|
|
2641
|
+
if (anchor.pos !== null && anchor.pos !== UNKNOWN_ROW_POS && livePos !== UNKNOWN_ROW_POS && livePos !== anchor.pos) {
|
|
2642
|
+
lost(box, anchor);
|
|
2643
|
+
return;
|
|
2644
|
+
}
|
|
2645
|
+
var boxTop = box.getBoundingClientRect().top;
|
|
2646
|
+
var delta = el.getBoundingClientRect().top - boxTop - anchor.top;
|
|
2647
|
+
var slack = Math.abs(box.scrollHeight - anchor.scrollHeight) + box.clientHeight;
|
|
2648
|
+
if (delta > slack || delta < -slack) {
|
|
2649
|
+
lost(box, anchor);
|
|
2650
|
+
return;
|
|
2651
|
+
}
|
|
2652
|
+
if (delta >= 1 || delta <= -1) box.scrollTop += delta;
|
|
2653
|
+
held = {
|
|
2654
|
+
key: anchor.key,
|
|
2655
|
+
top: anchor.top,
|
|
2656
|
+
pos: anchor.pos,
|
|
2657
|
+
scrollTop: box.scrollTop,
|
|
2658
|
+
scrollHeight: box.scrollHeight,
|
|
2659
|
+
el
|
|
2660
|
+
};
|
|
2661
|
+
return;
|
|
2662
|
+
}
|
|
2663
|
+
lost(box, anchor);
|
|
2664
|
+
}
|
|
2665
|
+
function lost(box, anchor) {
|
|
2666
|
+
held = null;
|
|
2667
|
+
var grew = box.scrollHeight - anchor.scrollHeight;
|
|
2668
|
+
if (grew > 0) {
|
|
2669
|
+
box.scrollTop = anchor.scrollTop + grew;
|
|
2670
|
+
return;
|
|
2671
|
+
}
|
|
2672
|
+
if (options.rawFallback) box.scrollTop = anchor.scrollTop;
|
|
2673
|
+
}
|
|
2674
|
+
function preserve(mutate) {
|
|
2675
|
+
var anchor = capture();
|
|
2676
|
+
var result = mutate();
|
|
2677
|
+
restore(anchor);
|
|
2678
|
+
return result;
|
|
2679
|
+
}
|
|
2680
|
+
function remember() {
|
|
2681
|
+
held = capture();
|
|
2682
|
+
}
|
|
2683
|
+
function hold() {
|
|
2684
|
+
var box = options.getBox();
|
|
2685
|
+
if (!box || options.isStuck()) {
|
|
2686
|
+
held = null;
|
|
2687
|
+
return;
|
|
2688
|
+
}
|
|
2689
|
+
if (!held) {
|
|
2690
|
+
held = capture();
|
|
2691
|
+
return;
|
|
2692
|
+
}
|
|
2693
|
+
if (box.scrollTop !== held.scrollTop) {
|
|
2694
|
+
held = capture();
|
|
2695
|
+
return;
|
|
2696
|
+
}
|
|
2697
|
+
restore(held);
|
|
2698
|
+
}
|
|
2699
|
+
function absorb(el) {
|
|
2700
|
+
if (!el) return;
|
|
2701
|
+
var box = options.getBox();
|
|
2702
|
+
if (!box) return;
|
|
2703
|
+
var h = el.offsetHeight;
|
|
2704
|
+
var prev = seen ? seen.get(el) : void 0;
|
|
2705
|
+
if (seen) seen.set(el, h);
|
|
2706
|
+
if (options.isStuck()) return;
|
|
2707
|
+
if (prev === void 0) prev = 0;
|
|
2708
|
+
var delta = h - prev;
|
|
2709
|
+
if (delta === 0) return;
|
|
2710
|
+
if (el.getBoundingClientRect().top >= box.getBoundingClientRect().top) return;
|
|
2711
|
+
box.scrollTop += delta;
|
|
2712
|
+
if (held) held = capture();
|
|
2713
|
+
}
|
|
2714
|
+
function forget() {
|
|
2715
|
+
held = null;
|
|
2716
|
+
}
|
|
2717
|
+
return {
|
|
2718
|
+
capture,
|
|
2719
|
+
restore,
|
|
2720
|
+
preserve,
|
|
2721
|
+
remember,
|
|
2722
|
+
hold,
|
|
2723
|
+
absorb,
|
|
2724
|
+
forget
|
|
2725
|
+
};
|
|
2726
|
+
}
|
|
2534
2727
|
|
|
2535
2728
|
// src/engine/viewport_fill.ts
|
|
2536
2729
|
var HISTORY_FILL_SLACK_PX = 64;
|
|
@@ -2642,6 +2835,7 @@ var WORKER_PASS_ADOPT_LIMIT = 20;
|
|
|
2642
2835
|
var LIVE_INDEX_SNAPSHOT_MAX_AGE_MS = 5e3;
|
|
2643
2836
|
var INDEX_DISPATCH_CLAIM_MS = 2 * 60 * 1e3;
|
|
2644
2837
|
var WORKER_PASS_ADOPT_ATTEMPTS = [0, 2e3, 6e3];
|
|
2838
|
+
var EARLY_PROBE_SCHEDULE_MS = [400, 900, 1700];
|
|
2645
2839
|
var INDEXING_DRAIN_BUSY_POLL_MS = 8e3;
|
|
2646
2840
|
var INDEXING_DRAIN_CONFIRM_POLL_MS = 3e3;
|
|
2647
2841
|
var INDEXING_DRAIN_IDLE_LOOKS = 2;
|
|
@@ -3037,7 +3231,93 @@ var ChatSession = class {
|
|
|
3037
3231
|
*
|
|
3038
3232
|
* `stop` comes from the SDK and may be absent on an older skapi-js, in which case the
|
|
3039
3233
|
* poll simply cannot be stopped and is left running — see pausePolling.
|
|
3234
|
+
*
|
|
3235
|
+
* (This block documents _trackPoll, further down. The two methods below sit between it
|
|
3236
|
+
* and its subject.)
|
|
3040
3237
|
*/
|
|
3238
|
+
/**
|
|
3239
|
+
* Foreground poll with an early-probe race.
|
|
3240
|
+
*
|
|
3241
|
+
* skapi's poll() is a bare setInterval(fn, latency) with NO check at t=0, so the earliest a
|
|
3242
|
+
* reply can be observed is one full POLL_INTERVAL (3s) after dispatch. For a long generation
|
|
3243
|
+
* that granularity is free. For a SHORT one it is nearly pure dead time: a greeting that the
|
|
3244
|
+
* provider finishes in 1s still waits until the 3s tick, which measured as a large share of a
|
|
3245
|
+
* 5s "yo" round trip.
|
|
3246
|
+
*
|
|
3247
|
+
* So keep the 3s interval as the steady state, and additionally point-look-up the item a few
|
|
3248
|
+
* times early, on a widening schedule. Whichever answers first wins and the other is stopped.
|
|
3249
|
+
* The probe uses the csrHistoryItemLookup hook both clients already implement; without it this
|
|
3250
|
+
* degrades to exactly the old behaviour.
|
|
3251
|
+
*
|
|
3252
|
+
* FOREGROUND ONLY. Background indexing polls keep the flat cadence: nobody is watching them,
|
|
3253
|
+
* and they are the ones bounded by MAX_CONCURRENT_BG_POLLS, so adding probes there would spend
|
|
3254
|
+
* the request budget the cap exists to protect.
|
|
3255
|
+
*/
|
|
3256
|
+
attachForegroundPoll(source, itemId, opts) {
|
|
3257
|
+
return this._fgPollWithEarlyProbe(source, itemId, opts);
|
|
3258
|
+
}
|
|
3259
|
+
_fgPollWithEarlyProbe(source, itemId, opts) {
|
|
3260
|
+
var base = source.poll(Object.assign({ latency: POLL_INTERVAL }, opts || {}));
|
|
3261
|
+
var lookup = chatEngineConfig().csrHistoryItemLookup;
|
|
3262
|
+
var ident = this.host.getIdentity();
|
|
3263
|
+
var platform = ident && ident.platform;
|
|
3264
|
+
if (!lookup || !itemId || !ident || !ident.projectId || platform !== "claude" && platform !== "openai") {
|
|
3265
|
+
return base;
|
|
3266
|
+
}
|
|
3267
|
+
var settled = false;
|
|
3268
|
+
var timers = [];
|
|
3269
|
+
var clearProbes = function() {
|
|
3270
|
+
for (var i = 0; i < timers.length; i++) clearTimeout(timers[i]);
|
|
3271
|
+
timers = [];
|
|
3272
|
+
};
|
|
3273
|
+
var stopBase = base && typeof base.stop === "function" ? base.stop.bind(base) : null;
|
|
3274
|
+
var raced = new Promise(function(resolve, reject) {
|
|
3275
|
+
base.then(function(res) {
|
|
3276
|
+
if (settled) return;
|
|
3277
|
+
settled = true;
|
|
3278
|
+
clearProbes();
|
|
3279
|
+
resolve(res);
|
|
3280
|
+
}, function(err) {
|
|
3281
|
+
if (settled) return;
|
|
3282
|
+
settled = true;
|
|
3283
|
+
clearProbes();
|
|
3284
|
+
reject(err);
|
|
3285
|
+
});
|
|
3286
|
+
var fullId = buildHistoryItemFullId(platform, ident.projectId, itemId);
|
|
3287
|
+
EARLY_PROBE_SCHEDULE_MS.forEach(function(delay) {
|
|
3288
|
+
timers.push(setTimeout(function() {
|
|
3289
|
+
if (settled) return;
|
|
3290
|
+
Promise.resolve(lookup(fullId, ident.projectId, ident.owner)).then(function(body) {
|
|
3291
|
+
if (settled || !body || typeof body !== "object") return;
|
|
3292
|
+
if (body.status === "pending" || body.status === "running") return;
|
|
3293
|
+
if (isPollStopped(body)) return;
|
|
3294
|
+
settled = true;
|
|
3295
|
+
clearProbes();
|
|
3296
|
+
if (stopBase) {
|
|
3297
|
+
try {
|
|
3298
|
+
stopBase();
|
|
3299
|
+
} catch (e) {
|
|
3300
|
+
}
|
|
3301
|
+
}
|
|
3302
|
+
if (opts && typeof opts.onResponse === "function") {
|
|
3303
|
+
try {
|
|
3304
|
+
opts.onResponse(body);
|
|
3305
|
+
} catch (e) {
|
|
3306
|
+
}
|
|
3307
|
+
}
|
|
3308
|
+
resolve(body);
|
|
3309
|
+
}, function() {
|
|
3310
|
+
});
|
|
3311
|
+
}, delay));
|
|
3312
|
+
});
|
|
3313
|
+
});
|
|
3314
|
+
raced.stop = function() {
|
|
3315
|
+
settled = true;
|
|
3316
|
+
clearProbes();
|
|
3317
|
+
if (stopBase) stopBase();
|
|
3318
|
+
};
|
|
3319
|
+
return raced;
|
|
3320
|
+
}
|
|
3041
3321
|
_trackPoll(id, kind, p) {
|
|
3042
3322
|
var stop = p && typeof p.stop === "function" ? p.stop.bind(p) : void 0;
|
|
3043
3323
|
if (!stop) {
|
|
@@ -3213,6 +3493,68 @@ var ChatSession = class {
|
|
|
3213
3493
|
startKeyHistory: this.state.historyStartKeyHistory.slice()
|
|
3214
3494
|
};
|
|
3215
3495
|
}
|
|
3496
|
+
/**
|
|
3497
|
+
* Give the immediate-send pair the server's id for their turn, the moment the
|
|
3498
|
+
* dispatch learns it.
|
|
3499
|
+
*
|
|
3500
|
+
* WHY THIS EXISTS. An immediate send pushes its user bubble and its
|
|
3501
|
+
* "Thinking..." placeholder locally, and until now neither ever carried a
|
|
3502
|
+
* _serverItemId — only the QUEUED path stamped one, off its ack. So for the
|
|
3503
|
+
* whole life of the turn there was no way to tell the local copy and the
|
|
3504
|
+
* server's copy of the SAME turn apart, and the history merge fell back to a
|
|
3505
|
+
* heuristic: rescue the local pair unless the freshly-fetched page happens to
|
|
3506
|
+
* contain a pending assistant.
|
|
3507
|
+
*
|
|
3508
|
+
* That heuristic has a hole exactly one poll interval wide. The server settles
|
|
3509
|
+
* the request; for up to POLL_INTERVAL the client has not noticed, so
|
|
3510
|
+
* `state.sending` is still true and the local pair is still on screen — while a
|
|
3511
|
+
* history fetch issued in that window returns the turn ALREADY SETTLED, with no
|
|
3512
|
+
* pending assistant in it. The rescue then re-appends the local pair below the
|
|
3513
|
+
* server's copy (the question, twice), and when the poll finally resolves,
|
|
3514
|
+
* typewriteLatestReply writes the answer into the rescued placeholder because it
|
|
3515
|
+
* is the only pending assistant left (the answer, twice). updateHistoryCache
|
|
3516
|
+
* persists the result, so it survives every later visit.
|
|
3517
|
+
*
|
|
3518
|
+
* Navigating away while waiting and coming back is what lands a fetch in that
|
|
3519
|
+
* window: a remount runs refreshGate -> a fresh first page, at an arbitrary
|
|
3520
|
+
* moment relative to the 3s poll.
|
|
3521
|
+
*
|
|
3522
|
+
* With the id on the bubbles, both clients' rescue loops skip them through the
|
|
3523
|
+
* dedup they already have (`_serverItemId is in this page`), the reply the
|
|
3524
|
+
* dispatch caches inherits the id too, and nothing needs a new special case.
|
|
3525
|
+
*
|
|
3526
|
+
* Matched by _localId, never by index: a file's indexing rows are spliced in
|
|
3527
|
+
* above these bubbles while the request is in flight.
|
|
3528
|
+
*/
|
|
3529
|
+
_stampTurnWithItemId(key, userLid, placeholderLid, itemId) {
|
|
3530
|
+
if (!itemId) return;
|
|
3531
|
+
var changed = false;
|
|
3532
|
+
var stamp = function(list) {
|
|
3533
|
+
var hit = false;
|
|
3534
|
+
for (var i = 0; i < list.length; i++) {
|
|
3535
|
+
var m = list[i];
|
|
3536
|
+
if (!m || !m._localId) continue;
|
|
3537
|
+
if (m._localId !== userLid && m._localId !== placeholderLid) continue;
|
|
3538
|
+
if (m._serverItemId === itemId) continue;
|
|
3539
|
+
list[i] = Object.assign({}, m, { _serverItemId: itemId });
|
|
3540
|
+
hit = true;
|
|
3541
|
+
}
|
|
3542
|
+
return hit;
|
|
3543
|
+
};
|
|
3544
|
+
changed = stamp(this.state.messages);
|
|
3545
|
+
var cached = key ? this.aiChatHistoryCache[key] : void 0;
|
|
3546
|
+
if (cached) {
|
|
3547
|
+
var msgs = cached.messages.slice();
|
|
3548
|
+
if (stamp(msgs)) {
|
|
3549
|
+
this.aiChatHistoryCache[key] = {
|
|
3550
|
+
messages: msgs,
|
|
3551
|
+
endOfList: cached.endOfList,
|
|
3552
|
+
startKeyHistory: cached.startKeyHistory
|
|
3553
|
+
};
|
|
3554
|
+
}
|
|
3555
|
+
}
|
|
3556
|
+
if (changed) this.host.notify();
|
|
3557
|
+
}
|
|
3216
3558
|
/**
|
|
3217
3559
|
* Land a resolved reply in the history cache of a chat that is NOT currently
|
|
3218
3560
|
* visible, without touching state.messages. Mirrors the cache-only path in
|
|
@@ -3294,8 +3636,9 @@ var ChatSession = class {
|
|
|
3294
3636
|
if (initial.id) {
|
|
3295
3637
|
if (dispatchItemId && dispatchItemId !== initial.id) self.historyItemPolls.delete(dispatchItemId);
|
|
3296
3638
|
dispatchItemId = initial.id;
|
|
3639
|
+
if (typeof params.onItemId === "function") params.onItemId(initial.id);
|
|
3297
3640
|
}
|
|
3298
|
-
var dp =
|
|
3641
|
+
var dp = self._fgPollWithEarlyProbe(initial, initial.id);
|
|
3299
3642
|
if (initial.id) self._trackPoll(initial.id, "fg", dp);
|
|
3300
3643
|
return dp;
|
|
3301
3644
|
}
|
|
@@ -3320,28 +3663,9 @@ var ChatSession = class {
|
|
|
3320
3663
|
}).then(function(result) {
|
|
3321
3664
|
delete self.pendingAgentRequests[params.key];
|
|
3322
3665
|
if (dispatchItemId) self.historyItemPolls.delete(dispatchItemId);
|
|
3323
|
-
var existing = self.aiChatHistoryCache[params.key] || { messages: [], endOfList: false, startKeyHistory: [] };
|
|
3324
3666
|
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
|
-
};
|
|
3667
|
+
if (dispatchItemId) reply._serverItemId = dispatchItemId;
|
|
3668
|
+
self._applyReplyToCache(params.key, reply, dispatchItemId);
|
|
3345
3669
|
return result;
|
|
3346
3670
|
});
|
|
3347
3671
|
this.pendingAgentRequests[params.key] = run;
|
|
@@ -3730,8 +4054,9 @@ var ChatSession = class {
|
|
|
3730
4054
|
}
|
|
3731
4055
|
this.host.notify();
|
|
3732
4056
|
this.updateHistoryCache();
|
|
3733
|
-
this.
|
|
4057
|
+
this.scrollForDispatch(stageId);
|
|
3734
4058
|
var capturedComposed = composed, capturedPlatform = aiPlatform, capturedKey = key;
|
|
4059
|
+
var capturedQueuedLid = queuedBubble._localId;
|
|
3735
4060
|
Promise.resolve(this._callProviderFor(aiPlatform, composed, boundedQ.messages, systemPrompt, aiModel, chatQueue, extractContent, fileUrls, id.projectId, id.owner)).then(function(result) {
|
|
3736
4061
|
var sendingIdx = self.getHistoryCacheKey() !== capturedKey ? -1 : self.state.messages.findIndex(function(m) {
|
|
3737
4062
|
return m.isSendingToServer && (m.isPendingQueued || m.isPendingInProcess) && m.role === "user" && !m._stageId && (m._ownerKey === void 0 || m._ownerKey === capturedKey);
|
|
@@ -3743,8 +4068,9 @@ var ChatSession = class {
|
|
|
3743
4068
|
self.state.messages[sendingIdx] = upd;
|
|
3744
4069
|
self.host.notify();
|
|
3745
4070
|
}
|
|
4071
|
+
if (serverId) self._stampTurnWithItemId(capturedKey, capturedQueuedLid, void 0, serverId);
|
|
3746
4072
|
if (result && result.poll && (result.status === "pending" || result.status === "running")) {
|
|
3747
|
-
var qp =
|
|
4073
|
+
var qp = self._fgPollWithEarlyProbe(result, serverId);
|
|
3748
4074
|
if (serverId) self._trackPoll(serverId, "fg", qp);
|
|
3749
4075
|
return qp.then(function(res) {
|
|
3750
4076
|
if (isPollStopped(res)) return;
|
|
@@ -3760,7 +4086,7 @@ var ChatSession = class {
|
|
|
3760
4086
|
return;
|
|
3761
4087
|
}
|
|
3762
4088
|
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 } : {} };
|
|
4089
|
+
var immediatePlaceholder = { role: "assistant", content: "", isPending: true, isPendingInProcess: true, _localId: this._newLocalId(), ...key ? { _ownerKey: key } : {} };
|
|
3764
4090
|
var iStage = this._stageIndex(this.state.messages, stageId);
|
|
3765
4091
|
if (iStage !== -1) {
|
|
3766
4092
|
var iEx = this.state.messages[iStage];
|
|
@@ -3774,7 +4100,7 @@ var ChatSession = class {
|
|
|
3774
4100
|
this.host.notify();
|
|
3775
4101
|
this.updateHistoryCache();
|
|
3776
4102
|
this.state.sending = true;
|
|
3777
|
-
this.
|
|
4103
|
+
this.scrollForDispatch(stageId);
|
|
3778
4104
|
var historyForLlm = this.state.messages.filter(function(m) {
|
|
3779
4105
|
if (m === immediateUser) return false;
|
|
3780
4106
|
return !m.isPending && !m.isPendingQueued && !m.isPendingInProcess && !m.isPendingOlder && !m.isCancelled && !m.isBackgroundTask && !m.isError;
|
|
@@ -3787,6 +4113,7 @@ var ChatSession = class {
|
|
|
3787
4113
|
projectId: id.projectId,
|
|
3788
4114
|
history: historyForLlm
|
|
3789
4115
|
});
|
|
4116
|
+
var immediateUserLid = immediateUser._localId, immediatePlaceholderLid = immediatePlaceholder._localId;
|
|
3790
4117
|
var run = this.dispatchAgentRequest({
|
|
3791
4118
|
key,
|
|
3792
4119
|
projectId: id.projectId,
|
|
@@ -3798,7 +4125,15 @@ var ChatSession = class {
|
|
|
3798
4125
|
boundedMessages: bounded.messages,
|
|
3799
4126
|
userId: chatQueue,
|
|
3800
4127
|
extractContent,
|
|
3801
|
-
fileUrls
|
|
4128
|
+
fileUrls,
|
|
4129
|
+
// THE fix for a turn rendering twice after navigate-away-and-back. Until
|
|
4130
|
+
// this existed the immediate-send pair carried no server id for its whole
|
|
4131
|
+
// life (only the QUEUED path stamped one, off its ack), so nothing could
|
|
4132
|
+
// tell the local copy and the server's copy of the same turn apart. See
|
|
4133
|
+
// _stampTurnWithItemId.
|
|
4134
|
+
onItemId: function(itemId) {
|
|
4135
|
+
self._stampTurnWithItemId(key, immediateUserLid, immediatePlaceholderLid, itemId);
|
|
4136
|
+
}
|
|
3802
4137
|
});
|
|
3803
4138
|
Promise.resolve(run).catch(function() {
|
|
3804
4139
|
}).then(function() {
|
|
@@ -3809,6 +4144,28 @@ var ChatSession = class {
|
|
|
3809
4144
|
});
|
|
3810
4145
|
});
|
|
3811
4146
|
}
|
|
4147
|
+
/**
|
|
4148
|
+
* Scroll for a dispatch that is going out NOW, but never for one arriving late.
|
|
4149
|
+
*
|
|
4150
|
+
* A turn with attachments does not dispatch when the user hits Send: it waits out
|
|
4151
|
+
* its uploads and then its whole indexing chain, which is minutes
|
|
4152
|
+
* (awaitIndexingDrained). By then the reader has very often scrolled up into
|
|
4153
|
+
* history to pass the time, and the forcing scrollToBottom yanked them out of it
|
|
4154
|
+
* — and worse, force-pinned stickToBottom, which no-ops every method on the
|
|
4155
|
+
* scroll anchor and re-arms the queue-detect poll whose only bail is
|
|
4156
|
+
* !stickToBottom.
|
|
4157
|
+
*
|
|
4158
|
+
* `stageId` is the exact marker for that case: only the attachment path ever
|
|
4159
|
+
* produces one. The gesture itself was already paid for at stage time, where
|
|
4160
|
+
* stageOutgoingMessage forces the scroll while the user is still looking at the
|
|
4161
|
+
* composer. Deliberately NOT gated on "did we find the staged bubble" — a remount
|
|
4162
|
+
* rebuilds from the cache and the turn is appended rather than replaced, which is
|
|
4163
|
+
* just as late and just as unrequested.
|
|
4164
|
+
*/
|
|
4165
|
+
scrollForDispatch(stageId) {
|
|
4166
|
+
if (stageId) this.host.scrollToBottomIfSticky(true);
|
|
4167
|
+
else this.host.scrollToBottom(true);
|
|
4168
|
+
}
|
|
3812
4169
|
promoteNextBgQueuedToRunning() {
|
|
3813
4170
|
if (this.state.messages.some(function(m) {
|
|
3814
4171
|
return m.isPending && m.role === "assistant" && m.isBackgroundTask;
|
|
@@ -3934,6 +4291,29 @@ var ChatSession = class {
|
|
|
3934
4291
|
else if (targetIdx >= 0) this.state.messages.splice(targetIdx, 0, msg);
|
|
3935
4292
|
else this.state.messages.push(msg);
|
|
3936
4293
|
}
|
|
4294
|
+
/**
|
|
4295
|
+
* The server's OWN copy of this turn is already on screen.
|
|
4296
|
+
*
|
|
4297
|
+
* A first-page fetch can land between the server settling the item and this
|
|
4298
|
+
* poll's tick, and now that the local bubbles carry the item id
|
|
4299
|
+
* (_stampTurnWithItemId) the rescue correctly drops them and renders the
|
|
4300
|
+
* server's settled pair instead. There is then nothing left to resolve: the
|
|
4301
|
+
* -1 fallback would push the answer in a SECOND time at the bottom of the list,
|
|
4302
|
+
* and the positional fallbacks would hijack some other turn's bubble.
|
|
4303
|
+
*
|
|
4304
|
+
* The USER bubble counts, not just a settled assistant: an item whose answer is
|
|
4305
|
+
* empty produces no assistant bubble at all in the mapper, and that variant
|
|
4306
|
+
* would otherwise still bottom-push "No text response received...". While a turn
|
|
4307
|
+
* is genuinely live its user bubble is always pending (the queued branch sets
|
|
4308
|
+
* isPendingQueued, promoteNextQueuedToRunning sets isPendingInProcess), so this
|
|
4309
|
+
* cannot fire early.
|
|
4310
|
+
*/
|
|
4311
|
+
_turnAlreadyRendered(serverId) {
|
|
4312
|
+
if (!serverId) return false;
|
|
4313
|
+
return this.state.messages.some(function(m) {
|
|
4314
|
+
return m._serverItemId === serverId && !m.isPending && !m.isPendingQueued && !m.isPendingInProcess;
|
|
4315
|
+
});
|
|
4316
|
+
}
|
|
3937
4317
|
onQueuedSendResponse(_composed, response, platform, serverId, ownerKey) {
|
|
3938
4318
|
if (serverId) this.historyItemPolls.delete(serverId);
|
|
3939
4319
|
if (ownerKey && this.getHistoryCacheKey() !== ownerKey) {
|
|
@@ -3942,6 +4322,12 @@ var ChatSession = class {
|
|
|
3942
4322
|
if (serverId) this.cancelledServerIds.delete(serverId);
|
|
3943
4323
|
return;
|
|
3944
4324
|
}
|
|
4325
|
+
if (this._turnAlreadyRendered(serverId)) {
|
|
4326
|
+
if (serverId) this.cancelledServerIds.delete(serverId);
|
|
4327
|
+
this.host.notify();
|
|
4328
|
+
this.updateHistoryCache();
|
|
4329
|
+
return;
|
|
4330
|
+
}
|
|
3945
4331
|
var targetIdx = this.resolveQueuedUserBubble(serverId);
|
|
3946
4332
|
if (targetIdx === void 0) {
|
|
3947
4333
|
this.host.notify();
|
|
@@ -3983,6 +4369,12 @@ var ChatSession = class {
|
|
|
3983
4369
|
if (serverId) this.cancelledServerIds.delete(serverId);
|
|
3984
4370
|
return;
|
|
3985
4371
|
}
|
|
4372
|
+
if (this._turnAlreadyRendered(serverId)) {
|
|
4373
|
+
if (serverId) this.cancelledServerIds.delete(serverId);
|
|
4374
|
+
this.host.notify();
|
|
4375
|
+
this.updateHistoryCache();
|
|
4376
|
+
return;
|
|
4377
|
+
}
|
|
3986
4378
|
var isNotExists = err && (err.code === "NOT_EXISTS" || err.body && err.body.code === "NOT_EXISTS");
|
|
3987
4379
|
if (isNotExists) {
|
|
3988
4380
|
var userIdx = serverId ? this.state.messages.findIndex(function(m) {
|
|
@@ -5244,21 +5636,15 @@ var ChatSession = class {
|
|
|
5244
5636
|
var rescued = [];
|
|
5245
5637
|
for (var ri = 0; ri < self.state.messages.length; ri++) {
|
|
5246
5638
|
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
|
-
}
|
|
5639
|
+
if (shouldRescueInFlightMessage(mm, {
|
|
5640
|
+
hasServerId: function(sid) {
|
|
5641
|
+
return !!serverIds[sid];
|
|
5642
|
+
},
|
|
5643
|
+
pageHasPendingAssistant: mappedHasPendingAssistant,
|
|
5644
|
+
sending: !!self.state.sending,
|
|
5645
|
+
next: self.state.messages[ri + 1],
|
|
5646
|
+
loadKey
|
|
5647
|
+
})) rescued.push(mm);
|
|
5262
5648
|
}
|
|
5263
5649
|
var oldestInPage1 = void 0;
|
|
5264
5650
|
mapped.forEach(function(m) {
|
|
@@ -5309,6 +5695,9 @@ var ChatSession = class {
|
|
|
5309
5695
|
keptOlderPages = prependOlder.length > 0 || interleave.length > 0;
|
|
5310
5696
|
self.state.messages = prependOlder.length ? prependOlder.concat(page1) : page1;
|
|
5311
5697
|
rescued.forEach(function(m) {
|
|
5698
|
+
if (m._serverItemId && self.state.messages.some(function(x) {
|
|
5699
|
+
return x._serverItemId === m._serverItemId && x.role === m.role;
|
|
5700
|
+
})) return;
|
|
5312
5701
|
self.state.messages.push(m);
|
|
5313
5702
|
});
|
|
5314
5703
|
if (Object.keys(locallyCancelled).length) {
|
|
@@ -5460,8 +5849,8 @@ var ChatSession = class {
|
|
|
5460
5849
|
if ((item._isBgTask || item._isOnBgQueue) && self.isPollingPaused()) return;
|
|
5461
5850
|
if ((item._isBgTask || item._isOnBgQueue) && !bgAllow[item.id]) return;
|
|
5462
5851
|
var capturedId = item.id;
|
|
5463
|
-
var
|
|
5464
|
-
|
|
5852
|
+
var isBg = !!(item._isBgTask || item._isOnBgQueue);
|
|
5853
|
+
var pollOpts = {
|
|
5465
5854
|
onResponse: function(response) {
|
|
5466
5855
|
if (isPollStopped(response)) return;
|
|
5467
5856
|
self.handleHistoryItemResolution(capturedId, response, platform);
|
|
@@ -5476,9 +5865,9 @@ var ChatSession = class {
|
|
|
5476
5865
|
var uIdx = self.state.messages.findIndex(function(m) {
|
|
5477
5866
|
return m.role === "user" && m._serverItemId === capturedId && !m.isCancelled;
|
|
5478
5867
|
});
|
|
5479
|
-
var
|
|
5868
|
+
var isBg2 = aIdx !== -1 && !!self.state.messages[aIdx].isBackgroundTask || uIdx !== -1 && !!self.state.messages[uIdx].isBackgroundTask;
|
|
5480
5869
|
if (aIdx !== -1) self.state.messages.splice(aIdx, 1);
|
|
5481
|
-
if (!
|
|
5870
|
+
if (!isBg2) {
|
|
5482
5871
|
if (uIdx !== -1) {
|
|
5483
5872
|
var ex = self.state.messages[uIdx];
|
|
5484
5873
|
self.state.messages[uIdx] = { role: "user", content: ex.content, isCancelled: true, _serverItemId: ex._serverItemId };
|
|
@@ -5505,7 +5894,8 @@ var ChatSession = class {
|
|
|
5505
5894
|
self.updateHistoryCache();
|
|
5506
5895
|
}
|
|
5507
5896
|
}
|
|
5508
|
-
}
|
|
5897
|
+
};
|
|
5898
|
+
var pp = isBg ? item.poll(Object.assign({ latency: POLL_INTERVAL }, pollOpts)) : self._fgPollWithEarlyProbe(item, capturedId, pollOpts);
|
|
5509
5899
|
self._trackPoll(capturedId, item._isBgTask || item._isOnBgQueue ? "bg" : "fg", pp);
|
|
5510
5900
|
if (pp && pp.catch) pp.catch(function() {
|
|
5511
5901
|
});
|
|
@@ -6180,6 +6570,7 @@ exports.POLL_INTERVAL = POLL_INTERVAL;
|
|
|
6180
6570
|
exports.PRESIGN_SAFETY_MARGIN_MS = PRESIGN_SAFETY_MARGIN_MS;
|
|
6181
6571
|
exports.PREVIEWABLE_IMAGE_CONTENT_TYPES = PREVIEWABLE_IMAGE_CONTENT_TYPES;
|
|
6182
6572
|
exports.PREVIEW_BROWSER_CACHE_SECONDS = PREVIEW_BROWSER_CACHE_SECONDS;
|
|
6573
|
+
exports.PREVIEW_LAYOUT_BOX_SELECTOR = PREVIEW_LAYOUT_BOX_SELECTOR;
|
|
6183
6574
|
exports.PREVIEW_URL_EXPIRES_SECONDS = PREVIEW_URL_EXPIRES_SECONDS;
|
|
6184
6575
|
exports.RENDER_FROM_TOKEN = RENDER_FROM_TOKEN;
|
|
6185
6576
|
exports.RTF_EXTS = RTF_EXTS;
|
|
@@ -6205,6 +6596,7 @@ exports.buildIndexingWindowMessage = buildIndexingWindowMessage;
|
|
|
6205
6596
|
exports.callClaudeWithMcp = callClaudeWithMcp;
|
|
6206
6597
|
exports.callClaudeWithPublicMcp = callClaudeWithPublicMcp;
|
|
6207
6598
|
exports.callOpenAIWithPublicMcp = callOpenAIWithPublicMcp;
|
|
6599
|
+
exports.canonicalizePathForm = canonicalizePathForm;
|
|
6208
6600
|
exports.chatEngineConfig = chatEngineConfig;
|
|
6209
6601
|
exports.classifyInlineLink = classifyInlineLink;
|
|
6210
6602
|
exports.clearAttachmentParsers = clearAttachmentParsers;
|
|
@@ -6214,6 +6606,7 @@ exports.configureChatEngine = configureChatEngine;
|
|
|
6214
6606
|
exports.contentTypeForExt = contentTypeForExt;
|
|
6215
6607
|
exports.createHistoryFiller = createHistoryFiller;
|
|
6216
6608
|
exports.createInlineLinkRegex = createInlineLinkRegex;
|
|
6609
|
+
exports.createScrollAnchor = createScrollAnchor;
|
|
6217
6610
|
exports.encodePathSegments = encodePathSegments;
|
|
6218
6611
|
exports.encodingClassForExt = encodingClassForExt;
|
|
6219
6612
|
exports.ensureHtmlCharset = ensureHtmlCharset;
|
|
@@ -6283,6 +6676,7 @@ exports.peekImagePreviewUrl = peekImagePreviewUrl;
|
|
|
6283
6676
|
exports.prepareDownloadText = prepareDownloadText;
|
|
6284
6677
|
exports.presignExpiryEpochMs = presignExpiryEpochMs;
|
|
6285
6678
|
exports.previewImageContentType = previewImageContentType;
|
|
6679
|
+
exports.previewLayoutBox = previewLayoutBox;
|
|
6286
6680
|
exports.previewMintCacheToken = previewMintCacheToken;
|
|
6287
6681
|
exports.previewableExtOf = previewableExtOf;
|
|
6288
6682
|
exports.readExpiredAttachmentHref = readExpiredAttachmentHref;
|
|
@@ -6296,6 +6690,7 @@ exports.runIndexUniqueId = runIndexUniqueId;
|
|
|
6296
6690
|
exports.safeDecodeURIComponent = safeDecodeURIComponent;
|
|
6297
6691
|
exports.sanitizeAttachmentLinksForHistory = sanitizeAttachmentLinksForHistory;
|
|
6298
6692
|
exports.setProjectContextWindow = setProjectContextWindow;
|
|
6693
|
+
exports.shouldRescueInFlightMessage = shouldRescueInFlightMessage;
|
|
6299
6694
|
exports.stripFileBlocksFromHistory = stripFileBlocksFromHistory;
|
|
6300
6695
|
exports.transformContentWithImages = transformContentWithImages;
|
|
6301
6696
|
exports.transformContentWithOpenAIImages = transformContentWithOpenAIImages;
|