bunnyquery 1.11.0 → 1.12.1
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/README.md +5 -0
- package/bunnyquery.css +59 -0
- package/bunnyquery.js +260 -6
- package/dist/engine.cjs +243 -5
- package/dist/engine.cjs.map +1 -1
- package/dist/engine.d.mts +79 -1
- package/dist/engine.d.ts +79 -1
- package/dist/engine.mjs +243 -6
- package/dist/engine.mjs.map +1 -1
- package/package.json +1 -1
- package/src/engine/budget.ts +13 -3
- package/src/engine/errors.ts +15 -0
- package/src/engine/history.ts +5 -1
- package/src/engine/host.ts +4 -0
- package/src/engine/index.ts +1 -0
- package/src/engine/indexing_groups.ts +24 -3
- package/src/engine/session.ts +239 -3
- package/styles/chat.css +59 -0
package/dist/engine.cjs
CHANGED
|
@@ -627,6 +627,11 @@ function getErrorMessage(input) {
|
|
|
627
627
|
}
|
|
628
628
|
return "Something went wrong.";
|
|
629
629
|
}
|
|
630
|
+
function isRetriedFailure(input) {
|
|
631
|
+
var envErr = csrEnvelopeError(input);
|
|
632
|
+
if (envErr !== void 0) input = envErr;
|
|
633
|
+
return !!input && typeof input === "object" && input.retried === true;
|
|
634
|
+
}
|
|
630
635
|
function isErrorResponseBody(response) {
|
|
631
636
|
var envErr = csrEnvelopeError(response);
|
|
632
637
|
if (envErr !== void 0) response = envErr;
|
|
@@ -1070,6 +1075,9 @@ function truncateLabelForDisplay(label) {
|
|
|
1070
1075
|
var CONTEXT_WINDOW_DEFAULT = { claude: 2e5, openai: 128e3 };
|
|
1071
1076
|
var CONTEXT_WINDOW_BY_MODEL = {
|
|
1072
1077
|
// claude, exact ids
|
|
1078
|
+
"claude-fable-5-1": 1e6,
|
|
1079
|
+
"claude-opus-5-5": 1e6,
|
|
1080
|
+
"claude-sonnet-5-5": 1e6,
|
|
1073
1081
|
"claude-fable-5": 1e6,
|
|
1074
1082
|
"claude-opus-5": 1e6,
|
|
1075
1083
|
"claude-opus-4-8": 1e6,
|
|
@@ -1083,6 +1091,9 @@ var CONTEXT_WINDOW_BY_MODEL = {
|
|
|
1083
1091
|
"claude-haiku-4-5": 2e5,
|
|
1084
1092
|
"claude-3-5-sonnet": 2e5,
|
|
1085
1093
|
// openai, exact ids
|
|
1094
|
+
"gpt-6-astra": 105e4,
|
|
1095
|
+
"gpt-6-sol": 105e4,
|
|
1096
|
+
"gpt-6-luna": 105e4,
|
|
1086
1097
|
"gpt-5.6-sol": 105e4,
|
|
1087
1098
|
"gpt-5.6-terra": 105e4,
|
|
1088
1099
|
"gpt-5.6-luna": 105e4,
|
|
@@ -1099,11 +1110,15 @@ var CONTEXT_WINDOW_BY_MODEL = {
|
|
|
1099
1110
|
"claude-opus": 1e6,
|
|
1100
1111
|
"claude-sonnet": 1e6,
|
|
1101
1112
|
"claude-haiku": 2e5,
|
|
1113
|
+
"gpt-6": 105e4,
|
|
1102
1114
|
"gpt-5.6": 105e4,
|
|
1103
1115
|
"gpt-5": 128e3
|
|
1104
1116
|
};
|
|
1105
1117
|
var MAX_OUTPUT_BY_MODEL = {
|
|
1106
1118
|
// claude
|
|
1119
|
+
"claude-fable-5-1": 128e3,
|
|
1120
|
+
"claude-opus-5-5": 128e3,
|
|
1121
|
+
"claude-sonnet-5-5": 128e3,
|
|
1107
1122
|
"claude-fable-5": 128e3,
|
|
1108
1123
|
"claude-opus-5": 128e3,
|
|
1109
1124
|
"claude-opus-4-8": 128e3,
|
|
@@ -1112,6 +1127,9 @@ var MAX_OUTPUT_BY_MODEL = {
|
|
|
1112
1127
|
"claude-haiku-4-5": 64e3,
|
|
1113
1128
|
"claude-3-5-sonnet": 8e3,
|
|
1114
1129
|
// openai
|
|
1130
|
+
"gpt-6-astra": 128e3,
|
|
1131
|
+
"gpt-6-sol": 128e3,
|
|
1132
|
+
"gpt-6-luna": 128e3,
|
|
1115
1133
|
"gpt-5.6-sol": 128e3,
|
|
1116
1134
|
"gpt-5.6-terra": 128e3,
|
|
1117
1135
|
"gpt-5.6-luna": 128e3,
|
|
@@ -1128,6 +1146,7 @@ var MAX_OUTPUT_BY_MODEL = {
|
|
|
1128
1146
|
"claude-opus": 128e3,
|
|
1129
1147
|
"claude-sonnet": 64e3,
|
|
1130
1148
|
"claude-haiku": 64e3,
|
|
1149
|
+
"gpt-6": 128e3,
|
|
1131
1150
|
"gpt-5.6": 128e3,
|
|
1132
1151
|
"gpt-5": 128e3
|
|
1133
1152
|
};
|
|
@@ -3284,6 +3303,7 @@ function mapHistoryListToMessages(list, platform, opts) {
|
|
|
3284
3303
|
mapped.push(ph);
|
|
3285
3304
|
} else if (isQueued) ; else if (isErrorResponse) {
|
|
3286
3305
|
var em = { role: "assistant", content: getErrorMessage(response), isError: true };
|
|
3306
|
+
if (isRetriedFailure(response)) em.isRetried = true;
|
|
3287
3307
|
if (item._fromBgChain) em._fromBgChain = true;
|
|
3288
3308
|
if (item._isBgTask) em.isBackgroundTask = true;
|
|
3289
3309
|
if (serverItemId !== void 0) em._serverItemId = serverItemId;
|
|
@@ -4312,7 +4332,9 @@ var ChatSession = class {
|
|
|
4312
4332
|
timers = [];
|
|
4313
4333
|
};
|
|
4314
4334
|
var stopBase = base && typeof base.stop === "function" ? base.stop.bind(base) : null;
|
|
4335
|
+
var resolveRaced = null;
|
|
4315
4336
|
var raced = new Promise(function(resolve, reject) {
|
|
4337
|
+
resolveRaced = resolve;
|
|
4316
4338
|
base.then(function(res) {
|
|
4317
4339
|
if (settled) return;
|
|
4318
4340
|
settled = true;
|
|
@@ -4353,9 +4375,16 @@ var ChatSession = class {
|
|
|
4353
4375
|
});
|
|
4354
4376
|
});
|
|
4355
4377
|
raced.stop = function() {
|
|
4378
|
+
if (settled) return;
|
|
4356
4379
|
settled = true;
|
|
4357
4380
|
clearProbes();
|
|
4358
|
-
if (stopBase)
|
|
4381
|
+
if (stopBase) {
|
|
4382
|
+
try {
|
|
4383
|
+
stopBase();
|
|
4384
|
+
} catch (e) {
|
|
4385
|
+
}
|
|
4386
|
+
}
|
|
4387
|
+
if (resolveRaced) resolveRaced({ id: itemId, status: "stopped" });
|
|
4359
4388
|
};
|
|
4360
4389
|
return raced;
|
|
4361
4390
|
}
|
|
@@ -5368,6 +5397,7 @@ var ChatSession = class {
|
|
|
5368
5397
|
}
|
|
5369
5398
|
return response;
|
|
5370
5399
|
}).then(function(response) {
|
|
5400
|
+
if (isPollStopped(response) || self._isCancelledPollResult(response)) return { content: "", isError: false, cancelled: true };
|
|
5371
5401
|
if (isErrorResponseBody(response)) return { content: getErrorMessage(response), isError: true };
|
|
5372
5402
|
var answer = params.aiPlatform === "openai" ? extractOpenAIText(response) : extractClaudeText(response);
|
|
5373
5403
|
answer = (answer || "").trim();
|
|
@@ -5377,6 +5407,10 @@ var ChatSession = class {
|
|
|
5377
5407
|
}).then(function(result) {
|
|
5378
5408
|
delete self.pendingAgentRequests[params.key];
|
|
5379
5409
|
if (dispatchItemId) self.historyItemPolls.delete(dispatchItemId);
|
|
5410
|
+
if (result.cancelled) {
|
|
5411
|
+
if (dispatchItemId) self._settleCancelledTurn(dispatchItemId, params.key);
|
|
5412
|
+
return result;
|
|
5413
|
+
}
|
|
5380
5414
|
var reply = { role: "assistant", content: result.content, isError: result.isError };
|
|
5381
5415
|
if (dispatchItemId) reply._serverItemId = dispatchItemId;
|
|
5382
5416
|
self._applyReplyToCache(params.key, reply, dispatchItemId);
|
|
@@ -5855,8 +5889,9 @@ var ChatSession = class {
|
|
|
5855
5889
|
}
|
|
5856
5890
|
});
|
|
5857
5891
|
Promise.resolve(run).catch(function() {
|
|
5858
|
-
}).then(function() {
|
|
5892
|
+
}).then(function(result) {
|
|
5859
5893
|
self.state.sending = false;
|
|
5894
|
+
if (result && result.cancelled) return;
|
|
5860
5895
|
if (!(self.host.isViewMounted() && self.getHistoryCacheKey() === key)) return;
|
|
5861
5896
|
return Promise.resolve(self.typewriteLatestReply(key)).then(function() {
|
|
5862
5897
|
self.host.scrollToBottomIfSticky(true);
|
|
@@ -6035,6 +6070,10 @@ var ChatSession = class {
|
|
|
6035
6070
|
}
|
|
6036
6071
|
onQueuedSendResponse(_composed, response, platform, serverId, ownerKey) {
|
|
6037
6072
|
if (serverId) this.historyItemPolls.delete(serverId);
|
|
6073
|
+
if (this._isCancelledPollResult(response)) {
|
|
6074
|
+
if (serverId) this._settleCancelledTurn(serverId, ownerKey);
|
|
6075
|
+
return;
|
|
6076
|
+
}
|
|
6038
6077
|
if (ownerKey && this.getHistoryCacheKey() !== ownerKey) {
|
|
6039
6078
|
var offReply = isErrorResponseBody(response) ? { role: "assistant", content: getErrorMessage(response), isError: true } : { role: "assistant", content: ((platform === "openai" ? extractOpenAIText(response) : extractClaudeText(response)) || "").trim() || "No text response received from AI provider." };
|
|
6040
6079
|
this._applyReplyToCache(ownerKey, offReply, serverId);
|
|
@@ -6234,6 +6273,191 @@ var ChatSession = class {
|
|
|
6234
6273
|
}
|
|
6235
6274
|
});
|
|
6236
6275
|
}
|
|
6276
|
+
/**
|
|
6277
|
+
* Stop waiting for the reply to the chat turn that is running now, and move on.
|
|
6278
|
+
*
|
|
6279
|
+
* `msg` is the turn's pending ASSISTANT bubble (the dot-trail "Thinking"
|
|
6280
|
+
* placeholder), which is where both views draw the control. A turn's two bubbles
|
|
6281
|
+
* carry the same _serverItemId, so it is also how the request is found.
|
|
6282
|
+
* cancelQueuedMessage is the sibling for a turn still waiting in the queue and
|
|
6283
|
+
* takes the USER bubble; the two differ in what they are handed and in what has
|
|
6284
|
+
* to be unwound afterwards, not in what the server is asked.
|
|
6285
|
+
*
|
|
6286
|
+
* What ends the wait:
|
|
6287
|
+
* 1. csr-cancel marks the running row cancelled. The worker stops relaying and
|
|
6288
|
+
* never stores an answer for it, so the reply cannot turn up later on this
|
|
6289
|
+
* device or another, and history replays the turn as cancelled;
|
|
6290
|
+
* 2. the poll on it is stopped, which settles whichever chain was awaiting it
|
|
6291
|
+
* with a stopped result: dispatchAgentRequest's for an immediate send, the
|
|
6292
|
+
* queued chain for a promoted turn, the history poll after a reload. Each
|
|
6293
|
+
* reads a stopped or cancelled result as "no reply" and releases what it
|
|
6294
|
+
* holds (state.sending, pendingAgentRequests, historyItemPolls), which is
|
|
6295
|
+
* what lets the next send go out immediately instead of queueing;
|
|
6296
|
+
* 3. the bubbles settle at once, without waiting for that chain: the user
|
|
6297
|
+
* bubble takes its cancelled form, the placeholder goes, and the next queued
|
|
6298
|
+
* turn, if there is one, is promoted so its own "Thinking" appears.
|
|
6299
|
+
*
|
|
6300
|
+
* What it does NOT do: the provider call already in flight on the worker runs
|
|
6301
|
+
* to its end (a streamed one stops within about a second). A message sent
|
|
6302
|
+
* behind it on the same queue starts once the worker acks the cancelled row.
|
|
6303
|
+
*
|
|
6304
|
+
* Refused without a server id (the ack has not come back yet; the views disable
|
|
6305
|
+
* the control until it has) and while a cancel is already out. Indexing passes
|
|
6306
|
+
* are not this method's business: their Stop is cancelIndexingGroup, which ends
|
|
6307
|
+
* the file's whole chain.
|
|
6308
|
+
*/
|
|
6309
|
+
cancelPendingReply(msg, idx) {
|
|
6310
|
+
var self = this;
|
|
6311
|
+
var id = this.host.getIdentity();
|
|
6312
|
+
var serverId = msg._serverItemId;
|
|
6313
|
+
if (!serverId || msg._cancelling) return;
|
|
6314
|
+
if (msg.role !== "assistant" || !msg.isPending || msg.isBackgroundTask) return;
|
|
6315
|
+
var platform = id.platform;
|
|
6316
|
+
if (platform !== "claude" && platform !== "openai") return;
|
|
6317
|
+
var url = platform === "claude" ? ANTHROPIC_MESSAGES_API_URL : OPENAI_RESPONSES_API_URL;
|
|
6318
|
+
var queueBase = id.userId || id.projectId;
|
|
6319
|
+
var queue = msg._useBgQueue ? bgIndexingQueueName(queueBase) : queueBase;
|
|
6320
|
+
var findPlaceholder = function() {
|
|
6321
|
+
return self.state.messages.findIndex(function(m) {
|
|
6322
|
+
return m._serverItemId === serverId && m.role === "assistant" && !!m.isPending;
|
|
6323
|
+
});
|
|
6324
|
+
};
|
|
6325
|
+
var at = this.state.messages[idx] && this.state.messages[idx]._serverItemId === serverId && this.state.messages[idx].role === "assistant" ? idx : findPlaceholder();
|
|
6326
|
+
if (at !== -1) {
|
|
6327
|
+
this.state.messages[at] = Object.assign({}, this.state.messages[at], { _cancelling: true, _cancelError: void 0 });
|
|
6328
|
+
}
|
|
6329
|
+
this.host.notify();
|
|
6330
|
+
var unmark = function(errMsg) {
|
|
6331
|
+
var ci = findPlaceholder();
|
|
6332
|
+
if (ci === -1) return;
|
|
6333
|
+
self.state.messages[ci] = Object.assign({}, self.state.messages[ci], { _cancelling: false, _cancelError: errMsg });
|
|
6334
|
+
self.host.notify();
|
|
6335
|
+
};
|
|
6336
|
+
Promise.resolve(this.host.cancelRequest({
|
|
6337
|
+
url,
|
|
6338
|
+
method: "POST",
|
|
6339
|
+
id: serverId,
|
|
6340
|
+
queue,
|
|
6341
|
+
service: id.projectId,
|
|
6342
|
+
owner: id.owner
|
|
6343
|
+
})).then(function(result) {
|
|
6344
|
+
if (result && result.removed) {
|
|
6345
|
+
self._stopPoll(serverId);
|
|
6346
|
+
self._settleCancelledTurn(serverId);
|
|
6347
|
+
return;
|
|
6348
|
+
}
|
|
6349
|
+
unmark(void 0);
|
|
6350
|
+
}).catch(function(err) {
|
|
6351
|
+
unmark(err && typeof err.message === "string" && err.message ? err.message : "Could not stop this request.");
|
|
6352
|
+
});
|
|
6353
|
+
}
|
|
6354
|
+
/** The cancelled form of a request bubble, with everything that keeps it in
|
|
6355
|
+
* place carried over: the file markers that hold an indexing pass in its
|
|
6356
|
+
* collapsed row, the chat it belongs to, its send time and its render key. */
|
|
6357
|
+
_cancelledCopyOf(m, itemId) {
|
|
6358
|
+
var cancelled = { role: "user", content: m.content, isCancelled: true };
|
|
6359
|
+
if (itemId !== void 0) cancelled._serverItemId = itemId;
|
|
6360
|
+
else if (m._serverItemId !== void 0) cancelled._serverItemId = m._serverItemId;
|
|
6361
|
+
if (m.isBackgroundTask) cancelled.isBackgroundTask = true;
|
|
6362
|
+
if (m._indexFile) cancelled._indexFile = m._indexFile;
|
|
6363
|
+
if (m._useBgQueue) cancelled._useBgQueue = true;
|
|
6364
|
+
if (m._ownerKey !== void 0) cancelled._ownerKey = m._ownerKey;
|
|
6365
|
+
if (m._ts !== void 0) cancelled._ts = m._ts;
|
|
6366
|
+
if (m._localId !== void 0) cancelled._localId = m._localId;
|
|
6367
|
+
return cancelled;
|
|
6368
|
+
}
|
|
6369
|
+
/**
|
|
6370
|
+
* Settle a turn as cancelled wherever it lives, and let the queue move on.
|
|
6371
|
+
*
|
|
6372
|
+
* On the chat on screen: every pending placeholder carrying the id goes (a
|
|
6373
|
+
* history refetch can have re-mapped the running turn into a second one), the
|
|
6374
|
+
* request bubble takes its cancelled form, and the next queued turn is promoted
|
|
6375
|
+
* so the conversation does not sit on an "(In queue)" bubble with nothing
|
|
6376
|
+
* running. For a chat that is NOT on screen (the user moved on while the turn
|
|
6377
|
+
* ran, and it was cancelled from elsewhere) the same edit is made in that chat's
|
|
6378
|
+
* history cache, which is what the next visit renders.
|
|
6379
|
+
*
|
|
6380
|
+
* Idempotent on purpose: a cancel this client made settles the bubbles at once,
|
|
6381
|
+
* and the chain that was awaiting the poll settles again a moment later when the
|
|
6382
|
+
* stop resolves it. The second pass finds nothing to do. Deliberately NOT
|
|
6383
|
+
* _removeStrayPendingAssistants: by then the placeholder on screen is the freshly
|
|
6384
|
+
* promoted turn's, whose user bubble is pending, and the sweep would take it.
|
|
6385
|
+
*/
|
|
6386
|
+
_settleCancelledTurn(itemId, ownerKey) {
|
|
6387
|
+
if (!itemId) return;
|
|
6388
|
+
if (ownerKey && this.getHistoryCacheKey() !== ownerKey) {
|
|
6389
|
+
this._applyCancelToCache(ownerKey, itemId);
|
|
6390
|
+
return;
|
|
6391
|
+
}
|
|
6392
|
+
var changed = false, wasBg = false;
|
|
6393
|
+
for (var i = this.state.messages.length - 1; i >= 0; i--) {
|
|
6394
|
+
var m = this.state.messages[i];
|
|
6395
|
+
if (!m || m._serverItemId !== itemId) continue;
|
|
6396
|
+
if (m.role === "assistant" && m.isPending) {
|
|
6397
|
+
this.state.messages.splice(i, 1);
|
|
6398
|
+
changed = true;
|
|
6399
|
+
continue;
|
|
6400
|
+
}
|
|
6401
|
+
if (m.role === "user" && !m.isCancelled) {
|
|
6402
|
+
if (m.isBackgroundTask) wasBg = true;
|
|
6403
|
+
this.state.messages[i] = this._cancelledCopyOf(m, itemId);
|
|
6404
|
+
changed = true;
|
|
6405
|
+
}
|
|
6406
|
+
}
|
|
6407
|
+
this.cancelledServerIds.delete(itemId);
|
|
6408
|
+
if (!changed) return;
|
|
6409
|
+
if (wasBg) this.promoteNextBgQueuedToRunning();
|
|
6410
|
+
else this.promoteNextQueuedToRunning();
|
|
6411
|
+
this.updateHistoryCache();
|
|
6412
|
+
this.host.notify();
|
|
6413
|
+
}
|
|
6414
|
+
/**
|
|
6415
|
+
* _settleCancelledTurn for a chat that is not on screen: the same edit, made in
|
|
6416
|
+
* its history cache. Matched by id where the cached bubbles carry one; an
|
|
6417
|
+
* off-chat send caches its pair before the ack (see dispatchComposedMessage), so
|
|
6418
|
+
* the fallback is positional, exactly as _applyReplyToCache's is: the trailing
|
|
6419
|
+
* pending placeholder, and the last request bubble still marked in flight.
|
|
6420
|
+
*/
|
|
6421
|
+
_applyCancelToCache(key, itemId) {
|
|
6422
|
+
var existing = key ? this.aiChatHistoryCache[key] : void 0;
|
|
6423
|
+
if (!existing) return;
|
|
6424
|
+
var msgs = existing.messages.slice();
|
|
6425
|
+
var changed = false;
|
|
6426
|
+
var pIdx = -1, uIdx = -1;
|
|
6427
|
+
for (var i = msgs.length - 1; i >= 0; i--) {
|
|
6428
|
+
var m = msgs[i];
|
|
6429
|
+
if (!m || m._serverItemId !== itemId) continue;
|
|
6430
|
+
if (m.role === "assistant" && m.isPending) {
|
|
6431
|
+
msgs.splice(i, 1);
|
|
6432
|
+
changed = true;
|
|
6433
|
+
pIdx = i;
|
|
6434
|
+
} else if (m.role === "user" && !m.isCancelled && uIdx === -1) uIdx = i;
|
|
6435
|
+
}
|
|
6436
|
+
if (pIdx === -1) {
|
|
6437
|
+
for (var p = msgs.length - 1; p >= 0; p--) {
|
|
6438
|
+
var pm = msgs[p];
|
|
6439
|
+
if (!pm || !pm.isPending || pm.role !== "assistant" || pm.isBackgroundTask || pm._serverItemId !== void 0) continue;
|
|
6440
|
+
msgs.splice(p, 1);
|
|
6441
|
+
changed = true;
|
|
6442
|
+
break;
|
|
6443
|
+
}
|
|
6444
|
+
}
|
|
6445
|
+
if (uIdx === -1) {
|
|
6446
|
+
for (var u = msgs.length - 1; u >= 0; u--) {
|
|
6447
|
+
var um = msgs[u];
|
|
6448
|
+
if (!um || um.role !== "user" || um.isBackgroundTask || um._serverItemId !== void 0 || um._stageId) continue;
|
|
6449
|
+
if (!(um.isPendingQueued || um.isPendingInProcess || um.isSendingToServer)) continue;
|
|
6450
|
+
uIdx = u;
|
|
6451
|
+
break;
|
|
6452
|
+
}
|
|
6453
|
+
}
|
|
6454
|
+
if (uIdx !== -1) {
|
|
6455
|
+
msgs[uIdx] = this._cancelledCopyOf(msgs[uIdx], itemId);
|
|
6456
|
+
changed = true;
|
|
6457
|
+
}
|
|
6458
|
+
if (!changed) return;
|
|
6459
|
+
this.aiChatHistoryCache[key] = { messages: msgs, endOfList: existing.endOfList, startKeyHistory: existing.startKeyHistory };
|
|
6460
|
+
}
|
|
6237
6461
|
/**
|
|
6238
6462
|
* Stop indexing a file, from its collapsed row — every pass at once, not just
|
|
6239
6463
|
* the bubble the user happens to see.
|
|
@@ -6300,6 +6524,7 @@ var ChatSession = class {
|
|
|
6300
6524
|
}
|
|
6301
6525
|
}
|
|
6302
6526
|
this._adoptWorkerIndexingPasses(0);
|
|
6527
|
+
this._sweepCancelledIndexing();
|
|
6303
6528
|
var ids = group.cancellableIds || [];
|
|
6304
6529
|
if (!ids.length) {
|
|
6305
6530
|
this.host.notify();
|
|
@@ -6582,9 +6807,10 @@ var ChatSession = class {
|
|
|
6582
6807
|
this.state.sending = true;
|
|
6583
6808
|
this.host.scrollToBottomIfSticky(true);
|
|
6584
6809
|
return Promise.resolve(pending).catch(function() {
|
|
6585
|
-
}).then(function() {
|
|
6810
|
+
}).then(function(result) {
|
|
6586
6811
|
if (token !== self.state.gateRefreshToken) return;
|
|
6587
6812
|
self.state.sending = false;
|
|
6813
|
+
if (result && result.cancelled) return;
|
|
6588
6814
|
return Promise.resolve(self.typewriteLatestReply(key)).then(function() {
|
|
6589
6815
|
self.host.scrollToBottomIfSticky(true);
|
|
6590
6816
|
});
|
|
@@ -8213,8 +8439,19 @@ function buildChatDisplayList(messages, opts) {
|
|
|
8213
8439
|
} else if (stopped) {
|
|
8214
8440
|
grp.status = "cancelled";
|
|
8215
8441
|
} else {
|
|
8216
|
-
var
|
|
8217
|
-
|
|
8442
|
+
var retriedIds = {};
|
|
8443
|
+
for (var ri = 0; ri < grp.members.length; ri++) {
|
|
8444
|
+
var rm = grp.members[ri].msg;
|
|
8445
|
+
if (rm.isRetried && rm._serverItemId) retriedIds[rm._serverItemId] = true;
|
|
8446
|
+
}
|
|
8447
|
+
var li = grp.members.length - 1;
|
|
8448
|
+
while (li >= 0 && (grp.members[li].msg.isRetried || grp.members[li].msg._serverItemId && retriedIds[grp.members[li].msg._serverItemId])) li--;
|
|
8449
|
+
if (li < 0) {
|
|
8450
|
+
grp.status = "active";
|
|
8451
|
+
} else {
|
|
8452
|
+
var last = grp.members[li].msg;
|
|
8453
|
+
grp.status = last.isError ? "error" : "done";
|
|
8454
|
+
}
|
|
8218
8455
|
}
|
|
8219
8456
|
var sawFirstPass = false;
|
|
8220
8457
|
for (var pi = 0; pi < grp.members.length; pi++) {
|
|
@@ -8679,6 +8916,7 @@ exports.isOfficeFile = isOfficeFile;
|
|
|
8679
8916
|
exports.isPagedReadFile = isPagedReadFile;
|
|
8680
8917
|
exports.isPreviewableImagePath = isPreviewableImagePath;
|
|
8681
8918
|
exports.isProviderApiKeyError = isProviderApiKeyError;
|
|
8919
|
+
exports.isRetriedFailure = isRetriedFailure;
|
|
8682
8920
|
exports.isServerExtractable = isServerExtractable;
|
|
8683
8921
|
exports.isServiceDbAttachmentHref = isServiceDbAttachmentHref;
|
|
8684
8922
|
exports.isWindowedReadFile = isWindowedReadFile;
|