bunnyquery 1.4.5 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/engine.cjs CHANGED
@@ -1070,10 +1070,18 @@ function mapHistoryListToMessages(list, platform, opts) {
1070
1070
  }
1071
1071
 
1072
1072
  // src/engine/session.ts
1073
- function sleep(ms) {
1074
- return new Promise(function(r) {
1075
- setTimeout(r, ms);
1076
- });
1073
+ var _g = typeof globalThis !== "undefined" ? globalThis : {};
1074
+ function nowMs() {
1075
+ return _g.performance && typeof _g.performance.now === "function" ? _g.performance.now() : Date.now();
1076
+ }
1077
+ function nextFrame(cb) {
1078
+ if (typeof _g.requestAnimationFrame === "function") {
1079
+ _g.requestAnimationFrame(cb);
1080
+ return;
1081
+ }
1082
+ setTimeout(function() {
1083
+ cb(nowMs());
1084
+ }, 16);
1077
1085
  }
1078
1086
  var ChatSession = class {
1079
1087
  constructor(host) {
@@ -1160,35 +1168,26 @@ var ChatSession = class {
1160
1168
  if (dispatchItemId) self.historyItemPolls.delete(dispatchItemId);
1161
1169
  var existing = self.aiChatHistoryCache[params.key] || { messages: [], endOfList: false, startKeyHistory: [] };
1162
1170
  var reply = { role: "assistant", content: result.content, isError: result.isError };
1163
- var onThisVisibleChat = self.host.isViewMounted() && self.getHistoryCacheKey() === params.key;
1164
- if (onThisVisibleChat) {
1165
- self.aiChatHistoryCache[params.key] = {
1166
- messages: existing.messages.concat([reply]),
1167
- endOfList: existing.endOfList,
1168
- startKeyHistory: existing.startKeyHistory
1169
- };
1170
- } else {
1171
- var msgs = existing.messages.slice();
1172
- var idx = -1;
1173
- for (var i = msgs.length - 1; i >= 0; i--) {
1174
- var m = msgs[i];
1175
- if (m && m.isPending && m.role === "assistant" && !m.isBackgroundTask) {
1176
- idx = i;
1177
- break;
1178
- }
1179
- }
1180
- if (idx !== -1) {
1181
- reply._serverItemId = msgs[idx]._serverItemId;
1182
- msgs[idx] = reply;
1183
- } else {
1184
- msgs.push(reply);
1171
+ var msgs = existing.messages.slice();
1172
+ var idx = -1;
1173
+ for (var i = msgs.length - 1; i >= 0; i--) {
1174
+ var m = msgs[i];
1175
+ if (m && m.isPending && m.role === "assistant" && !m.isBackgroundTask) {
1176
+ idx = i;
1177
+ break;
1185
1178
  }
1186
- self.aiChatHistoryCache[params.key] = {
1187
- messages: msgs,
1188
- endOfList: existing.endOfList,
1189
- startKeyHistory: existing.startKeyHistory
1190
- };
1191
1179
  }
1180
+ if (idx !== -1) {
1181
+ reply._serverItemId = msgs[idx]._serverItemId;
1182
+ msgs[idx] = reply;
1183
+ } else {
1184
+ msgs.push(reply);
1185
+ }
1186
+ self.aiChatHistoryCache[params.key] = {
1187
+ messages: msgs,
1188
+ endOfList: existing.endOfList,
1189
+ startKeyHistory: existing.startKeyHistory
1190
+ };
1192
1191
  return result;
1193
1192
  });
1194
1193
  this.pendingAgentRequests[params.key] = run;
@@ -1331,7 +1330,9 @@ var ChatSession = class {
1331
1330
  if (existing._serverItemId !== void 0) promoted._serverItemId = existing._serverItemId;
1332
1331
  if (existing.isSendingToServer) promoted.isSendingToServer = true;
1333
1332
  this.state.messages[nextIdx] = promoted;
1334
- this.state.messages.splice(nextIdx + 1, 0, { role: "assistant", content: "", isPending: true });
1333
+ var placeholder = { role: "assistant", content: "", isPending: true };
1334
+ if (existing._serverItemId !== void 0) placeholder._serverItemId = existing._serverItemId;
1335
+ this.state.messages.splice(nextIdx + 1, 0, placeholder);
1335
1336
  this.host.notify();
1336
1337
  }
1337
1338
  resolveQueuedUserBubble(serverId) {
@@ -1541,61 +1542,118 @@ var ChatSession = class {
1541
1542
  });
1542
1543
  }
1543
1544
  // --- typewriter -------------------------------------------------------
1545
+ // Reveal `fullText` into a message bubble at a constant wall-clock RATE
1546
+ // (chars/second) driven by requestAnimationFrame, rather than a fixed number
1547
+ // of characters per fixed-delay tick. This is what keeps typing smooth and
1548
+ // cheap on slow machines:
1549
+ //
1550
+ // * Each frame reveals `elapsed_ms * CHARS_PER_SEC` characters, so the
1551
+ // visual speed is the same regardless of how long a frame actually took.
1552
+ // * As the bubble's markdown grows, each re-render gets more expensive, so
1553
+ // frames get longer — which makes each frame reveal MORE characters and
1554
+ // therefore do FEWER, larger renders. That converts the old O(n^2)
1555
+ // "re-render the whole growing string once per 3 characters" (which got
1556
+ // slower and slower and pegged the CPU) into roughly O(n): the number of
1557
+ // renders self-throttles to what the machine can actually paint.
1558
+ // * rAF paces us to the browser's paint cycle and pauses in background
1559
+ // tabs, so we never queue work faster than it can be drawn.
1544
1560
  typewriteIntoIndex(idx, fullText, localId) {
1545
1561
  var self = this;
1546
1562
  if (!fullText) return Promise.resolve();
1547
- var TICK_MS = 16, charsPerTick = 3, FENCE_REVEAL_MS = 200;
1548
- var fenceTicks = Math.max(1, Math.floor(FENCE_REVEAL_MS / TICK_MS));
1549
- var fenceRegions = [], m;
1563
+ var CHARS_PER_SEC = 300;
1564
+ var MIN_STEP = 1;
1565
+ var MAX_FRAME_MS = 1e3;
1566
+ var regions = [], m;
1550
1567
  var fenceRegex = /```[\w.-]+\.[a-zA-Z0-9]+\n[\s\S]*?```/g;
1551
- while ((m = fenceRegex.exec(fullText)) !== null) fenceRegions.push({ start: m.index, end: m.index + m[0].length });
1552
- var linkRegions = [], lm;
1568
+ while ((m = fenceRegex.exec(fullText)) !== null) regions.push({ start: m.index, end: m.index + m[0].length });
1553
1569
  var linkRegex = createInlineLinkRegex();
1554
- while ((lm = linkRegex.exec(fullText)) !== null) linkRegions.push({ start: lm.index, end: lm.index + lm[0].length });
1570
+ while ((m = linkRegex.exec(fullText)) !== null) regions.push({ start: m.index, end: m.index + m[0].length });
1571
+ regions.sort(function(a, b) {
1572
+ return a.start - b.start;
1573
+ });
1555
1574
  this.state.typing = true;
1556
1575
  this.state.typingAbort = false;
1557
1576
  var i = 0;
1558
- return (function loop() {
1559
- if (self.state.typingAbort || i >= fullText.length) return Promise.resolve();
1560
- var step = charsPerTick;
1561
- var region = fenceRegions.find(function(r) {
1562
- return i >= r.start && i < r.end;
1563
- });
1564
- var linkRegion = linkRegions.find(function(r) {
1565
- return i >= r.start && i < r.end;
1566
- });
1567
- if (region) step = Math.max(charsPerTick, Math.ceil((region.end - i) / fenceTicks));
1568
- else if (linkRegion) step = Math.max(charsPerTick, linkRegion.end - i);
1569
- else {
1570
- var nextLink = linkRegions.find(function(r) {
1571
- return i < r.start && i + step > r.start;
1572
- });
1573
- if (nextLink) step = nextLink.end - i;
1577
+ var last = nowMs();
1578
+ return new Promise(function(resolve) {
1579
+ var done = false;
1580
+ var doc = _g.document;
1581
+ function isHidden() {
1582
+ return !!(doc && doc.hidden);
1583
+ }
1584
+ function cleanup() {
1585
+ if (doc && doc.removeEventListener) doc.removeEventListener("visibilitychange", onVisibility);
1586
+ }
1587
+ function finish() {
1588
+ if (done) return;
1589
+ done = true;
1590
+ cleanup();
1591
+ if (!self.state.typingAbort) {
1592
+ var fi = localId ? self.state.messages.findIndex(function(mm) {
1593
+ return mm._localId === localId;
1594
+ }) : idx;
1595
+ if (fi !== -1) {
1596
+ var t = self.state.messages[fi];
1597
+ if (t) {
1598
+ t.content = fullText;
1599
+ self.host.refreshMessageBubble(fi);
1600
+ }
1601
+ }
1602
+ }
1603
+ self.state.typing = false;
1604
+ resolve();
1605
+ }
1606
+ function onVisibility() {
1607
+ if (isHidden()) finish();
1574
1608
  }
1575
- i = Math.min(fullText.length, i + step);
1576
- var currentIdx = localId ? self.state.messages.findIndex(function(mm) {
1577
- return mm._localId === localId;
1578
- }) : idx;
1579
- if (currentIdx === -1) return Promise.resolve();
1580
- var target = self.state.messages[currentIdx];
1581
- if (!target) return Promise.resolve();
1582
- target.content = fullText.slice(0, i);
1583
- self.host.refreshMessageBubble(currentIdx);
1584
- return Promise.resolve(self.host.scrollToBottomIfSticky()).then(function() {
1585
- return sleep(TICK_MS);
1586
- }).then(loop);
1587
- })().then(function() {
1588
- if (!self.state.typingAbort) {
1589
- var fi = localId ? self.state.messages.findIndex(function(mm) {
1609
+ if (doc && doc.addEventListener) doc.addEventListener("visibilitychange", onVisibility);
1610
+ function frame(t) {
1611
+ if (done) return;
1612
+ if (self.state.typingAbort || i >= fullText.length || isHidden()) {
1613
+ finish();
1614
+ return;
1615
+ }
1616
+ var dt = t - last;
1617
+ last = t;
1618
+ if (!(dt > 0)) dt = 16;
1619
+ if (dt > MAX_FRAME_MS) dt = MAX_FRAME_MS;
1620
+ var step = Math.round(dt * CHARS_PER_SEC / 1e3);
1621
+ if (step < MIN_STEP) step = MIN_STEP;
1622
+ var next = Math.min(fullText.length, i + step);
1623
+ for (var changed = true; changed; ) {
1624
+ changed = false;
1625
+ for (var k = 0; k < regions.length; k++) {
1626
+ var r = regions[k];
1627
+ if (next > r.start && i < r.end && r.end > next) {
1628
+ next = r.end;
1629
+ changed = true;
1630
+ }
1631
+ }
1632
+ }
1633
+ if (next > fullText.length) next = fullText.length;
1634
+ i = next;
1635
+ var currentIdx = localId ? self.state.messages.findIndex(function(mm) {
1590
1636
  return mm._localId === localId;
1591
1637
  }) : idx;
1592
- var t = fi !== -1 ? self.state.messages[fi] : self.state.messages[idx];
1593
- if (t) {
1594
- t.content = fullText;
1595
- self.host.refreshMessageBubble(fi !== -1 ? fi : idx);
1638
+ if (currentIdx === -1) {
1639
+ finish();
1640
+ return;
1596
1641
  }
1642
+ var target = self.state.messages[currentIdx];
1643
+ if (!target) {
1644
+ finish();
1645
+ return;
1646
+ }
1647
+ target.content = fullText.slice(0, i);
1648
+ self.host.refreshMessageBubble(currentIdx);
1649
+ self.host.scrollToBottomIfSticky();
1650
+ nextFrame(frame);
1651
+ }
1652
+ if (isHidden()) {
1653
+ finish();
1654
+ return;
1597
1655
  }
1598
- self.state.typing = false;
1656
+ nextFrame(frame);
1599
1657
  });
1600
1658
  }
1601
1659
  enqueueTypewrite(idx, fullText, localId) {
@@ -1909,7 +1967,7 @@ var ChatSession = class {
1909
1967
  if (item.status !== "running" && item.status !== "pending") return;
1910
1968
  if (!item.poll || !item.id) return;
1911
1969
  if (self.historyItemPolls.has(item.id)) return;
1912
- if ((item.status === "running" || item.status === "pending") && self.pendingAgentRequests[self.getHistoryCacheKey()]) return;
1970
+ if (self.pendingAgentRequests[self.getHistoryCacheKey()] && !item._isBgTask && !item._isOnBgQueue) return;
1913
1971
  self.historyItemPolls.set(item.id, true);
1914
1972
  var capturedId = item.id;
1915
1973
  var pp = item.poll({