bunnyquery 1.3.4 → 1.3.5
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.js +40 -0
- package/dist/engine.cjs +40 -0
- package/dist/engine.cjs.map +1 -1
- package/dist/engine.d.mts +2 -0
- package/dist/engine.d.ts +2 -0
- package/dist/engine.mjs +40 -0
- package/dist/engine.mjs.map +1 -1
- package/package.json +1 -1
- package/src/engine/session.ts +54 -0
package/package.json
CHANGED
package/src/engine/session.ts
CHANGED
|
@@ -352,6 +352,7 @@ export class ChatSession {
|
|
|
352
352
|
this.host.notify(); this.enqueueTypewrite(aiIdx, answer, lid);
|
|
353
353
|
}
|
|
354
354
|
}
|
|
355
|
+
this._removeStrayPendingAssistants();
|
|
355
356
|
this.promoteNextQueuedToRunning();
|
|
356
357
|
this.updateHistoryCache();
|
|
357
358
|
this.host.notify();
|
|
@@ -386,12 +387,14 @@ export class ChatSession {
|
|
|
386
387
|
if (thPos2 !== -1) this.state.messages.splice(thPos2, 1);
|
|
387
388
|
}
|
|
388
389
|
if (serverId) this.cancelledServerIds.delete(serverId);
|
|
390
|
+
this._removeStrayPendingAssistants();
|
|
389
391
|
this.promoteNextQueuedToRunning(); this.updateHistoryCache(); this.host.notify(); this.host.scrollToBottom(true);
|
|
390
392
|
return;
|
|
391
393
|
}
|
|
392
394
|
var targetIdx = this.resolveQueuedUserBubble(serverId);
|
|
393
395
|
if (targetIdx === undefined) { this.host.notify(); this.updateHistoryCache(); return; }
|
|
394
396
|
this.insertAtTarget({ role: 'assistant', content: getErrorMessage(err), isError: true }, targetIdx);
|
|
397
|
+
this._removeStrayPendingAssistants();
|
|
395
398
|
this.promoteNextQueuedToRunning(); this.updateHistoryCache(); this.host.notify(); this.host.scrollToBottom(true);
|
|
396
399
|
}
|
|
397
400
|
|
|
@@ -516,17 +519,52 @@ export class ChatSession {
|
|
|
516
519
|
if (pendingIdx === -1) return Promise.resolve();
|
|
517
520
|
if (latest.isError || !latest.content) {
|
|
518
521
|
this.state.messages[pendingIdx] = { role: 'assistant', content: latest.content || '', isError: !!latest.isError };
|
|
522
|
+
this._removeStrayPendingAssistants();
|
|
519
523
|
this.host.notify();
|
|
520
524
|
this.promoteNextQueuedToRunning();
|
|
521
525
|
return Promise.resolve();
|
|
522
526
|
}
|
|
523
527
|
var lid = this._newLocalId();
|
|
524
528
|
this.state.messages[pendingIdx] = { role: 'assistant', content: '', isPending: false, _localId: lid };
|
|
529
|
+
this._removeStrayPendingAssistants();
|
|
525
530
|
this.host.notify();
|
|
526
531
|
this.promoteNextQueuedToRunning();
|
|
527
532
|
return this.enqueueTypewrite(pendingIdx, latest.content, lid);
|
|
528
533
|
}
|
|
529
534
|
|
|
535
|
+
// Remove any leftover non-background pending ("Thinking…") assistant bubbles.
|
|
536
|
+
// There is normally at most ONE such bubble at a time (promoteNext* refuses to
|
|
537
|
+
// add a second), so any extra is a duplicate — it appears when a concurrent
|
|
538
|
+
// history refetch re-maps the still-"running" turn into a pending placeholder
|
|
539
|
+
// (with a real _serverItemId) while the local pending bubble (no _serverItemId)
|
|
540
|
+
// is rescued and re-appended (see loadHistory rescue below). Each resolve path
|
|
541
|
+
// only replaces the FIRST pending bubble, so without this a stray "Thinking…"
|
|
542
|
+
// survives next to the reply/error. MUST run AFTER the resolved bubble has been
|
|
543
|
+
// made non-pending and BEFORE promoteNext*() (so a freshly-promoted Thinking,
|
|
544
|
+
// which is added only once no pending assistant remains, is preserved).
|
|
545
|
+
_removeStrayPendingAssistants(): void {
|
|
546
|
+
for (var k = this.state.messages.length - 1; k >= 0; k--) {
|
|
547
|
+
var m = this.state.messages[k];
|
|
548
|
+
if (m.isPending && m.role === 'assistant' && !m.isBackgroundTask) this.state.messages.splice(k, 1);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Drop the pending flags on the resolved turn's USER bubble (preserving its
|
|
553
|
+
// content + background-task marker). Needed because a bg "Indexing:" turn's user
|
|
554
|
+
// bubble carries isPendingInProcess; leaving it set keeps the bubble visually
|
|
555
|
+
// stuck and keeps its bgTaskQueue entry alive forever.
|
|
556
|
+
_clearPendingUserBubble(itemId: string): void {
|
|
557
|
+
var uIdx = this.state.messages.findIndex(function (m) {
|
|
558
|
+
return m.role === 'user' && m._serverItemId === itemId &&
|
|
559
|
+
(m.isPendingInProcess || m.isPendingQueued || m.isSendingToServer);
|
|
560
|
+
});
|
|
561
|
+
if (uIdx === -1) return;
|
|
562
|
+
var u = this.state.messages[uIdx];
|
|
563
|
+
var cleaned: ChatMessage = { role: 'user', content: u.content, _serverItemId: itemId };
|
|
564
|
+
if (u.isBackgroundTask) cleaned.isBackgroundTask = true;
|
|
565
|
+
this.state.messages[uIdx] = cleaned;
|
|
566
|
+
}
|
|
567
|
+
|
|
530
568
|
// If an immediate-send request for the current cache key is still in flight
|
|
531
569
|
// (e.g. the view unmounted then remounted mid-request), show the sending
|
|
532
570
|
// state, await it, then render the reply from the cache. Skipped when the
|
|
@@ -561,6 +599,11 @@ export class ChatSession {
|
|
|
561
599
|
: ((platform === 'openai' ? extractOpenAIText(response) : extractClaudeText(response)) || '').trim();
|
|
562
600
|
var idx = this.state.messages.findIndex(function (m) { return m.isPending && m._serverItemId === itemId; });
|
|
563
601
|
if (idx !== -1) {
|
|
602
|
+
// A bg "Indexing:" turn pushes a user bubble (isPendingInProcess) ALONGSIDE
|
|
603
|
+
// the assistant Thinking; replacing only the assistant leaves that user
|
|
604
|
+
// bubble stuck pending — and drainBgTaskQueue then never clears its queue
|
|
605
|
+
// entry (its stillPending check stays true). Un-pend it here too.
|
|
606
|
+
this._clearPendingUserBubble(itemId);
|
|
564
607
|
if (isErr) {
|
|
565
608
|
this.state.messages[idx] = { role: 'assistant', content: answer, isError: true, _serverItemId: itemId };
|
|
566
609
|
this.host.notify(); this.updateHistoryCache(); return;
|
|
@@ -690,12 +733,23 @@ export class ChatSession {
|
|
|
690
733
|
mapped.forEach(function (m: any) { if (m._serverItemId) serverIds[m._serverItemId] = 1; });
|
|
691
734
|
var locallyCancelled: any = {};
|
|
692
735
|
self.state.messages.forEach(function (m) { if (m.isCancelled && m._serverItemId) locallyCancelled[m._serverItemId] = 1; });
|
|
736
|
+
// If the freshly-mapped server list ALREADY shows this in-flight turn
|
|
737
|
+
// as a pending placeholder (a non-bg pending assistant, which carries
|
|
738
|
+
// a real _serverItemId), re-pushing the local no-_serverItemId
|
|
739
|
+
// user+Thinking would DUPLICATE it. Each resolve path only clears the
|
|
740
|
+
// first pending bubble, so the duplicate strands a "Thinking…" beside
|
|
741
|
+
// the reply/error. There is at most one in-flight regular turn, so a
|
|
742
|
+
// mapped pending assistant IS this turn — skip the redundant local copy.
|
|
743
|
+
var mappedHasPendingAssistant = mapped.some(function (m: any) {
|
|
744
|
+
return m.isPending && m.role === 'assistant' && !m.isBackgroundTask;
|
|
745
|
+
});
|
|
693
746
|
var rescued: ChatMessage[] = [];
|
|
694
747
|
for (var ri = 0; ri < self.state.messages.length; ri++) {
|
|
695
748
|
var mm = self.state.messages[ri];
|
|
696
749
|
if (mm.isBackgroundTask) continue;
|
|
697
750
|
if (mm._serverItemId && serverIds[mm._serverItemId]) continue;
|
|
698
751
|
if (!mm._serverItemId) {
|
|
752
|
+
if (mappedHasPendingAssistant) continue;
|
|
699
753
|
if (mm.isSendingToServer || mm.isPendingQueued || mm.isPendingInProcess || mm.isPending) rescued.push(mm);
|
|
700
754
|
else if (self.state.sending && mm.role === 'user') {
|
|
701
755
|
var next = self.state.messages[ri + 1];
|