bunnyquery 1.4.3 → 1.4.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 +44 -10
- package/dist/engine.cjs +43 -9
- package/dist/engine.cjs.map +1 -1
- package/dist/engine.mjs +43 -9
- package/dist/engine.mjs.map +1 -1
- package/package.json +1 -1
- package/src/engine/session.ts +76 -15
package/package.json
CHANGED
package/src/engine/session.ts
CHANGED
|
@@ -105,11 +105,23 @@ export class ChatSession {
|
|
|
105
105
|
|
|
106
106
|
dispatchAgentRequest(params: any) {
|
|
107
107
|
var self = this;
|
|
108
|
+
// Id of the in-flight item this dispatch polls. Recorded in
|
|
109
|
+
// historyItemPolls (set below, deleted when the dispatch settles) so a
|
|
110
|
+
// remount / history refetch dedups against THIS poll instead of stacking
|
|
111
|
+
// a duplicate history poll on the same item. (The cacheKey guard alone
|
|
112
|
+
// stops covering an item once pendingAgentRequests clears — e.g. a queued
|
|
113
|
+
// message still in flight after the immediate one resolved.)
|
|
114
|
+
var dispatchItemId: string | undefined;
|
|
108
115
|
var sendAndPoll = function () {
|
|
109
116
|
return Promise.resolve(
|
|
110
117
|
self._callProviderFor(params.aiPlatform, params.text, params.boundedMessages, params.systemPrompt, params.aiModel, params.userId, params.extractContent)
|
|
111
118
|
).then(function (initial: any) {
|
|
112
119
|
if (initial && initial.poll && (initial.status === 'pending' || initial.status === 'running')) {
|
|
120
|
+
if (initial.id) {
|
|
121
|
+
if (dispatchItemId && dispatchItemId !== initial.id) self.historyItemPolls.delete(dispatchItemId);
|
|
122
|
+
dispatchItemId = initial.id;
|
|
123
|
+
self.historyItemPolls.set(initial.id, true);
|
|
124
|
+
}
|
|
113
125
|
return initial.poll({ latency: POLL_INTERVAL });
|
|
114
126
|
}
|
|
115
127
|
return initial;
|
|
@@ -138,19 +150,50 @@ export class ChatSession {
|
|
|
138
150
|
})
|
|
139
151
|
.catch(function (err: any) { return { content: getErrorMessage(err), isError: true }; })
|
|
140
152
|
.then(function (result: any) {
|
|
141
|
-
//
|
|
142
|
-
// independently of the view lifecycle, so a reply
|
|
143
|
-
//
|
|
144
|
-
// the cache via typewriteLatestReply (and resumePendingRequest on
|
|
145
|
-
// remount). The displayed bubble in state.messages is updated by the
|
|
146
|
-
// caller's typewriteLatestReply, not here.
|
|
147
|
-
var existing = self.aiChatHistoryCache[params.key] || { messages: [], endOfList: false, startKeyHistory: [] };
|
|
148
|
-
self.aiChatHistoryCache[params.key] = {
|
|
149
|
-
messages: existing.messages.concat([{ role: 'assistant', content: result.content, isError: result.isError }]),
|
|
150
|
-
endOfList: existing.endOfList,
|
|
151
|
-
startKeyHistory: existing.startKeyHistory,
|
|
152
|
-
};
|
|
153
|
+
// Land the resolved reply in the shared history cache. This runs
|
|
154
|
+
// independently of the view lifecycle, so a reply is captured even
|
|
155
|
+
// if the chatbox unmounted mid-request.
|
|
153
156
|
delete self.pendingAgentRequests[params.key];
|
|
157
|
+
if (dispatchItemId) self.historyItemPolls.delete(dispatchItemId);
|
|
158
|
+
var existing = self.aiChatHistoryCache[params.key] || { messages: [], endOfList: false, startKeyHistory: [] };
|
|
159
|
+
var reply: ChatMessage = { role: 'assistant', content: result.content, isError: result.isError };
|
|
160
|
+
|
|
161
|
+
var onThisVisibleChat = self.host.isViewMounted() && self.getHistoryCacheKey() === params.key;
|
|
162
|
+
if (onThisVisibleChat) {
|
|
163
|
+
// The chatbox is showing THIS chat: its typewriteLatestReply will
|
|
164
|
+
// swap the "Thinking..." bubble in state.messages and re-snapshot
|
|
165
|
+
// the cache. Just append the reply for it to pick up.
|
|
166
|
+
self.aiChatHistoryCache[params.key] = {
|
|
167
|
+
messages: existing.messages.concat([reply]),
|
|
168
|
+
endOfList: existing.endOfList,
|
|
169
|
+
startKeyHistory: existing.startKeyHistory,
|
|
170
|
+
};
|
|
171
|
+
} else {
|
|
172
|
+
// Resolved while this chat is NOT the visible view (the chatbox
|
|
173
|
+
// unmounted, or the user moved to another project). No view
|
|
174
|
+
// typewriter will run, so REPLACE the trailing pending
|
|
175
|
+
// "Thinking..." bubble in the cache with the answer rather than
|
|
176
|
+
// appending a duplicate — otherwise a remount renders a stuck
|
|
177
|
+
// "Thinking..." (whose bubble was never resolved). The next
|
|
178
|
+
// fresh history fetch reconciles it either way.
|
|
179
|
+
var msgs = existing.messages.slice();
|
|
180
|
+
var idx = -1;
|
|
181
|
+
for (var i = msgs.length - 1; i >= 0; i--) {
|
|
182
|
+
var m = msgs[i];
|
|
183
|
+
if (m && m.isPending && m.role === 'assistant' && !m.isBackgroundTask) { idx = i; break; }
|
|
184
|
+
}
|
|
185
|
+
if (idx !== -1) {
|
|
186
|
+
reply._serverItemId = msgs[idx]._serverItemId;
|
|
187
|
+
msgs[idx] = reply;
|
|
188
|
+
} else {
|
|
189
|
+
msgs.push(reply);
|
|
190
|
+
}
|
|
191
|
+
self.aiChatHistoryCache[params.key] = {
|
|
192
|
+
messages: msgs,
|
|
193
|
+
endOfList: existing.endOfList,
|
|
194
|
+
startKeyHistory: existing.startKeyHistory,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
154
197
|
return result;
|
|
155
198
|
});
|
|
156
199
|
this.pendingAgentRequests[params.key] = run;
|
|
@@ -205,6 +248,9 @@ export class ChatSession {
|
|
|
205
248
|
self.state.messages[sendingIdx] = upd; self.host.notify();
|
|
206
249
|
}
|
|
207
250
|
if (result && result.poll && (result.status === 'pending' || result.status === 'running')) {
|
|
251
|
+
// Track this queued item's poll so a remount/refetch dedups
|
|
252
|
+
// against it instead of attaching a duplicate history poll.
|
|
253
|
+
if (serverId) self.historyItemPolls.set(serverId, true);
|
|
208
254
|
return result.poll({ latency: POLL_INTERVAL })
|
|
209
255
|
.then(function (res: any) { return self.onQueuedSendResponse(capturedComposed, res, capturedPlatform, serverId); })
|
|
210
256
|
.catch(function (err: any) { return self.onQueuedSendError(capturedComposed, err, serverId); });
|
|
@@ -238,14 +284,23 @@ export class ChatSession {
|
|
|
238
284
|
platform: aiPlatform, model: aiModel, systemPrompt: systemPrompt, serviceId: id.serviceId,
|
|
239
285
|
history: historyForLlm,
|
|
240
286
|
});
|
|
241
|
-
var requestToken = this.state.gateRefreshToken;
|
|
242
287
|
var run = this.dispatchAgentRequest({
|
|
243
288
|
key: key, serviceId: id.serviceId, owner: id.owner, aiPlatform: aiPlatform, aiModel: aiModel,
|
|
244
289
|
systemPrompt: systemPrompt, text: composed, boundedMessages: bounded.messages, userId: chatQueue,
|
|
245
290
|
extractContent: extractContent,
|
|
246
291
|
});
|
|
292
|
+
// Render the reply into the "Thinking..." bubble whenever the chatbox is
|
|
293
|
+
// CURRENTLY showing this chat — even after an unmount/remount. The old
|
|
294
|
+
// guard bailed on `requestToken !== gateRefreshToken`, but EVERY remount
|
|
295
|
+
// bumps gateRefreshToken (refreshGate), so a request that finished after
|
|
296
|
+
// the user navigated away and back left a stuck "Thinking..." (no view
|
|
297
|
+
// typewriter ever ran, and resumePendingRequest bails when a pending
|
|
298
|
+
// bubble is already present). Gate on "is this the visible chat" instead.
|
|
299
|
+
// When it ISN'T (unmounted / another project), dispatchAgentRequest has
|
|
300
|
+
// already replaced the pending bubble with the answer IN THE CACHE, so a
|
|
301
|
+
// later loadChatHistory renders it.
|
|
247
302
|
Promise.resolve(run).catch(function () { }).then(function () {
|
|
248
|
-
if (
|
|
303
|
+
if (!(self.host.isViewMounted() && self.getHistoryCacheKey() === key)) return;
|
|
249
304
|
self.state.sending = false;
|
|
250
305
|
return Promise.resolve(self.typewriteLatestReply(key)).then(function () { self.host.scrollToBottom(true); });
|
|
251
306
|
});
|
|
@@ -333,6 +388,7 @@ export class ChatSession {
|
|
|
333
388
|
}
|
|
334
389
|
|
|
335
390
|
onQueuedSendResponse(_composed: string, response: any, platform: string, serverId?: string): void {
|
|
391
|
+
if (serverId) this.historyItemPolls.delete(serverId);
|
|
336
392
|
var targetIdx = this.resolveQueuedUserBubble(serverId);
|
|
337
393
|
if (targetIdx === undefined) { this.host.notify(); this.updateHistoryCache(); return; }
|
|
338
394
|
if (isErrorResponseBody(response)) {
|
|
@@ -362,6 +418,7 @@ export class ChatSession {
|
|
|
362
418
|
|
|
363
419
|
onQueuedSendError(_composed: string, err: any, serverId?: string): void {
|
|
364
420
|
var self = this;
|
|
421
|
+
if (serverId) this.historyItemPolls.delete(serverId);
|
|
365
422
|
var isNotExists = err && (err.code === 'NOT_EXISTS' || (err.body && err.body.code === 'NOT_EXISTS'));
|
|
366
423
|
if (isNotExists) {
|
|
367
424
|
var userIdx = serverId
|
|
@@ -789,7 +846,11 @@ export class ChatSession {
|
|
|
789
846
|
if (item.status !== 'running' && item.status !== 'pending') return;
|
|
790
847
|
if (!item.poll || !item.id) return;
|
|
791
848
|
if (self.historyItemPolls.has(item.id)) return;
|
|
792
|
-
|
|
849
|
+
// running OR pending: a live dispatchAgentRequest already polls this
|
|
850
|
+
// item; attaching a second (history) poll would double the interval
|
|
851
|
+
// fetch on a remount (skapi item.poll() loops survive unmount —
|
|
852
|
+
// they're uncancellable, so the dispatch poll is still running).
|
|
853
|
+
if ((item.status === 'running' || item.status === 'pending') && self.pendingAgentRequests[self.getHistoryCacheKey()]) return;
|
|
793
854
|
self.historyItemPolls.set(item.id, true);
|
|
794
855
|
var capturedId = item.id;
|
|
795
856
|
var pp = item.poll({
|