bunnyquery 1.12.1 → 1.13.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/bunnyquery.js +87 -4
- package/dist/engine.cjs +34 -3
- package/dist/engine.cjs.map +1 -1
- package/dist/engine.d.mts +14 -0
- package/dist/engine.d.ts +14 -0
- package/dist/engine.mjs +34 -3
- package/dist/engine.mjs.map +1 -1
- package/package.json +1 -1
- package/src/engine/history.ts +10 -1
- package/src/engine/prompts/chat_system_prompt.ts +23 -0
- package/src/engine/session.ts +38 -3
package/bunnyquery.js
CHANGED
|
@@ -352,10 +352,15 @@ Extracted content of attached office files (read inline below; do NOT fetch thei
|
|
|
352
352
|
const { projectId, serviceName, serviceDescription, greeting, canUpload} = params;
|
|
353
353
|
const g = params.indexAccessGroup;
|
|
354
354
|
const indexGroupLiteral = typeof g === "number" ? String(g) : g === "public" || g === "private" || g === "authorized" || g === "admin" ? `"${g}"` : '"authorized"';
|
|
355
|
+
const groundingRules = `ANSWER ONLY FROM THE DATA. Every fact you state about this project's data - a name, a number, a date, a status, what a document says, whether something exists - must come from one of four places: a tool result returned in THIS conversation, a file or image attached to it (including one you read through its attachment link), the project description at the end of this prompt, or what the user told you themselves in this chat. Your general knowledge, the open internet and a plausible guess are NOT sources. Never use them to answer a question about the user's data, to fill a gap in it, or to complete a record that came back partial. A value that is not in what you retrieved is unknown: say it is not in the data instead of supplying a likely one.
|
|
356
|
+
WHEN THE DATA DOES NOT HAVE IT, SAY SO. Once you have searched the way these rules require and nothing answers the question, tell the user plainly, in their language, that this project's data does not contain it, and say what you searched (the tables, files or terms) so they can tell missing data from a missed search. Do not answer from memory instead, and do not turn a missing answer into a general explanation of the topic. Offer what would let you answer it: ${canUpload === false ? `another name or file to look under, or asking the project's owner to add it` : `the document to upload, or another name or file to look under`}.
|
|
357
|
+
KEEP WHAT THE DATA SAYS APART FROM WHAT YOU WORKED OUT. A total you computed, a comparison, a trend, an interpretation or a recommendation is yours, not the data's: label it as such ("calculated from...", "this suggests...") and name the rows or files it was made from. Never present an estimate, an assumption or an inference as something the data states.
|
|
358
|
+
These three rules govern every claim about the project's data. They do not limit the About BunnyQuery section (answer product questions from it), a greeting, or text the user explicitly asks you to write.`;
|
|
355
359
|
let systemPrompt = `
|
|
356
360
|
You are a dedicated assistant for the project ID: "${projectId}".
|
|
357
361
|
Scope: Only answer questions about this project and its data. Do not answer questions about other projects or topics unrelated to this project. When the user refers to "my database", "my data", or "my files", treat those as references to this project's database and file storage. The ONE exception is BunnyQuery itself - what this app is, what it can do, and how to use it - which is always in scope: answer it from the "About BunnyQuery" section at the end of this prompt.
|
|
358
362
|
Knowledge lookup: Before saying you don't know or that something isn't in the chat history, ALWAYS query this project's database through the available MCP tools to look for the answer. The user's data is the source of truth - the chat transcript is not. Only respond with "I don't know" or "I couldn't find that" after you have actually searched the project's data and come back empty.
|
|
363
|
+
${groundingRules}
|
|
359
364
|
NUMBERS FROM A SPREADSHEET: use queryGrid, never mental arithmetic over records. A total, a count, an average, a "how many mention X", a "which one is biggest" - all of those are computed server-side over EVERY row of the file and come back with the sheet, the row count and the row numbers they were made from. Records are a SAMPLE, and a sample added up is a confident wrong number. Quote the row count and the sheet alongside the figure so the reader can check it.
|
|
360
365
|
CALL queryGrid describe FIRST, before any figure. Workbooks routinely state the same money more than once: a detail sheet, then per-song, per-album and per-artist sheets that each re-total it, plus a summary sheet whose bottom row is the file total. Those look like four different answers and are one. describe names which sheets restate which, and which rows are totals. Pick ONE sheet, say which you picked, and never add figures across a sheet and its summary. If the reply carries a warning about restatement, repeat it to the user.
|
|
361
366
|
A FILE TOTAL IS NOT A ROW'S TOTAL. The biggest number on a summary sheet is the whole file, not the thing that was asked about. Before quoting any figure, check it is scoped to what the question named: filter by the column that identifies it and report how many rows matched.
|
|
@@ -3111,8 +3116,9 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
3111
3116
|
var createdTs = Number(item && item.created);
|
|
3112
3117
|
var updatedTs = Number(item && item.updated);
|
|
3113
3118
|
var executedTs = Number(item && item.executed);
|
|
3119
|
+
var respondedTs = Number(item && item.responded);
|
|
3114
3120
|
var userTs = isFinite(createdTs) && createdTs > 0 ? createdTs : isFinite(updatedTs) && updatedTs > 0 ? updatedTs : void 0;
|
|
3115
|
-
var replyTs = isFinite(updatedTs) && updatedTs > 0 ? updatedTs : isFinite(createdTs) && createdTs > 0 ? createdTs : void 0;
|
|
3121
|
+
var replyTs = isFinite(respondedTs) && respondedTs > 0 ? respondedTs : isFinite(updatedTs) && updatedTs > 0 ? updatedTs : isFinite(createdTs) && createdTs > 0 ? createdTs : void 0;
|
|
3116
3122
|
if (userText) {
|
|
3117
3123
|
var displayContent;
|
|
3118
3124
|
var indexFile = void 0;
|
|
@@ -3739,6 +3745,13 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
3739
3745
|
// the request budget MAX_CONCURRENT_BG_POLLS exists to protect.
|
|
3740
3746
|
/** Live streams by server item id. One per in-flight streamed turn. */
|
|
3741
3747
|
this.liveStreams = {};
|
|
3748
|
+
/** When skapi recorded a turn's response as having finished arriving, by server
|
|
3749
|
+
* item id. Read off the terminal envelope of a STREAMED turn (the only settle
|
|
3750
|
+
* that carries it, see _settleLiveStream) and used as the answer bubble's time
|
|
3751
|
+
* in place of this browser's clock. Entries are never removed: a settle reaches
|
|
3752
|
+
* here twice (the poll's onResponse, then the promise), and the value for an id
|
|
3753
|
+
* cannot change. */
|
|
3754
|
+
this._respondedAt = {};
|
|
3742
3755
|
// ─── compact-stub hydration ─────────────────────────────────────────────
|
|
3743
3756
|
// Split-fetch bg pages arrive as label stubs (no bodies). When the user
|
|
3744
3757
|
// expands a row, the real reply text is fetched per item (csr-poll point
|
|
@@ -4264,6 +4277,23 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
4264
4277
|
});
|
|
4265
4278
|
return n;
|
|
4266
4279
|
}
|
|
4280
|
+
_noteRespondedAt(itemId, response) {
|
|
4281
|
+
if (!itemId || !isCsrStatusEnvelope(response)) return;
|
|
4282
|
+
var at = Number(response.responded);
|
|
4283
|
+
if (!isFinite(at) || at <= 0) return;
|
|
4284
|
+
if (!this._respondedAt) this._respondedAt = {};
|
|
4285
|
+
this._respondedAt[itemId] = at;
|
|
4286
|
+
}
|
|
4287
|
+
/** The time a live answer bubble is stamped with: when skapi received the
|
|
4288
|
+
* response, if the settle told us, else now. "Now" is what every bubble got
|
|
4289
|
+
* before, and it is still right for a buffered turn, whose settle hands back the
|
|
4290
|
+
* destination's body and nothing of skapi's; a history load then replaces it
|
|
4291
|
+
* with the server's value. */
|
|
4292
|
+
_replyStampFor(msg) {
|
|
4293
|
+
var id = msg ? msg._serverItemId : void 0;
|
|
4294
|
+
var at = typeof id === "string" && id && this._respondedAt ? this._respondedAt[id] : void 0;
|
|
4295
|
+
return typeof at === "number" ? at : wallClockNow();
|
|
4296
|
+
}
|
|
4267
4297
|
/**
|
|
4268
4298
|
* Open (or re-open) the live stream for `itemId`, or null when this poll must
|
|
4269
4299
|
* not carry one.
|
|
@@ -4453,6 +4483,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
4453
4483
|
_settleLiveStream(st, response) {
|
|
4454
4484
|
this._closeLiveStream(st, true);
|
|
4455
4485
|
if (!isCsrStatusEnvelope(response)) return response;
|
|
4486
|
+
this._noteRespondedAt(st.id, response);
|
|
4456
4487
|
if (response.status !== "resolved") return response;
|
|
4457
4488
|
if (!this._mayFinalize(st)) this._rec().incomplete[st.id] = true;
|
|
4458
4489
|
if (st.finalBody == null) return response;
|
|
@@ -5894,7 +5925,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
5894
5925
|
return thinkingIdx !== -1 ? thinkingIdx : userIdx >= 0 ? userIdx + 1 : -1;
|
|
5895
5926
|
}
|
|
5896
5927
|
insertAtTarget(msg, targetIdx) {
|
|
5897
|
-
if (msg && msg.role === "assistant" && msg._ts === void 0) msg._ts =
|
|
5928
|
+
if (msg && msg.role === "assistant" && msg._ts === void 0) msg._ts = this._replyStampFor(msg);
|
|
5898
5929
|
var tgt = targetIdx >= 0 ? this.state.messages[targetIdx] : void 0;
|
|
5899
5930
|
var replaceable = !!tgt && !!tgt.isPending && !tgt.isBackgroundTask && this._isOwnPlaceholderOf(targetIdx, this._owningUserIndex(targetIdx));
|
|
5900
5931
|
if (replaceable) this.state.messages[targetIdx] = msg;
|
|
@@ -6517,7 +6548,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
6517
6548
|
enqueueTypewrite(idx, fullText, localId, paintedText) {
|
|
6518
6549
|
var self = this;
|
|
6519
6550
|
var target = this.state.messages[idx];
|
|
6520
|
-
if (target && target._ts === void 0) target._ts =
|
|
6551
|
+
if (target && target._ts === void 0) target._ts = this._replyStampFor(target);
|
|
6521
6552
|
if (!this.typewriterQueue) this.typewriterQueue = Promise.resolve();
|
|
6522
6553
|
this.typewriterQueue = this.typewriterQueue.then(function() {
|
|
6523
6554
|
return self.typewriteIntoIndex(idx, fullText, localId, paintedText);
|
|
@@ -8597,7 +8628,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
8597
8628
|
(function() {
|
|
8598
8629
|
var MCP_PROD = "https://mcp.broadwayinc.computer";
|
|
8599
8630
|
var MCP_DEV = "https://mcp-dev.broadwayinc.computer";
|
|
8600
|
-
var BQ_VERSION = "1.
|
|
8631
|
+
var BQ_VERSION = "1.13.0" ;
|
|
8601
8632
|
var ATTACHMENT_URL_EXPIRES_SECONDS = 600;
|
|
8602
8633
|
var GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
8603
8634
|
var GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
@@ -12173,6 +12204,42 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
12173
12204
|
}
|
|
12174
12205
|
return byFile;
|
|
12175
12206
|
}
|
|
12207
|
+
var stopIndexOthers = { checked: false, row: null, text: null, box: null };
|
|
12208
|
+
function otherStoppableIndexGroups(target) {
|
|
12209
|
+
if (!target) return [];
|
|
12210
|
+
var list = buildChatDisplayList(CS.messages, displayListOptions());
|
|
12211
|
+
var others = [];
|
|
12212
|
+
for (var i = 0; i < list.length; i++) {
|
|
12213
|
+
var row = list[i];
|
|
12214
|
+
if (row.kind !== "indexing") continue;
|
|
12215
|
+
if (!indexGroupStoppable(row.group) || row.group.key === target.key) continue;
|
|
12216
|
+
others.push(row.group);
|
|
12217
|
+
}
|
|
12218
|
+
return others;
|
|
12219
|
+
}
|
|
12220
|
+
function countIndexFiles(groups) {
|
|
12221
|
+
var seen = {}, n = 0;
|
|
12222
|
+
for (var i = 0; i < groups.length; i++) {
|
|
12223
|
+
if (seen[groups[i].key]) continue;
|
|
12224
|
+
seen[groups[i].key] = true;
|
|
12225
|
+
n++;
|
|
12226
|
+
}
|
|
12227
|
+
return n;
|
|
12228
|
+
}
|
|
12229
|
+
function stopOthersLabel(n) {
|
|
12230
|
+
return n === 1 ? "Also stop the other file being indexed" : "Also stop the " + n + " other files being indexed";
|
|
12231
|
+
}
|
|
12232
|
+
function refreshStopIndexOthers() {
|
|
12233
|
+
if (!stopIndexOthers.row) return;
|
|
12234
|
+
var n = countIndexFiles(otherStoppableIndexGroups(findCancellableIndexGroup(stopIndexState.runKey, stopIndexState.fileKey)));
|
|
12235
|
+
if (!n) {
|
|
12236
|
+
stopIndexOthers.checked = false;
|
|
12237
|
+
if (stopIndexOthers.box) stopIndexOthers.box.checked = false;
|
|
12238
|
+
} else if (stopIndexOthers.text) {
|
|
12239
|
+
stopIndexOthers.text.textContent = stopOthersLabel(n);
|
|
12240
|
+
}
|
|
12241
|
+
stopIndexOthers.row.style.display = n ? "" : "none";
|
|
12242
|
+
}
|
|
12176
12243
|
function stopIndexModalIsOpen() {
|
|
12177
12244
|
var hnd = stopIndexState.handle;
|
|
12178
12245
|
if (hnd && hnd.root && hnd.root.parentNode) return true;
|
|
@@ -12191,6 +12258,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
12191
12258
|
function syncStopIndexModal() {
|
|
12192
12259
|
if (!stopIndexModalIsOpen()) return;
|
|
12193
12260
|
if (!findCancellableIndexGroup(stopIndexState.runKey, stopIndexState.fileKey)) closeStopIndexModal();
|
|
12261
|
+
else refreshStopIndexOthers();
|
|
12194
12262
|
}
|
|
12195
12263
|
function openStopIndexModal(group) {
|
|
12196
12264
|
if (!indexGroupStoppable(group)) return;
|
|
@@ -12203,10 +12271,24 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
12203
12271
|
stopBtn.addEventListener("click", function() {
|
|
12204
12272
|
var runKey = stopIndexState.runKey;
|
|
12205
12273
|
var fileKey = stopIndexState.fileKey;
|
|
12274
|
+
var alsoOthers = stopIndexOthers.checked;
|
|
12206
12275
|
closeStopIndexModal();
|
|
12207
12276
|
var live = findCancellableIndexGroup(runKey, fileKey);
|
|
12277
|
+
var others = alsoOthers ? otherStoppableIndexGroups(live) : [];
|
|
12208
12278
|
if (live) session.cancelIndexingGroup(live);
|
|
12279
|
+
for (var oi = 0; oi < others.length; oi++) session.cancelIndexingGroup(others[oi]);
|
|
12280
|
+
});
|
|
12281
|
+
var othersBox = h("input", { type: "checkbox" });
|
|
12282
|
+
othersBox.addEventListener("change", function() {
|
|
12283
|
+
stopIndexOthers.checked = !!othersBox.checked;
|
|
12209
12284
|
});
|
|
12285
|
+
var othersText = h("span");
|
|
12286
|
+
var othersRow = h("label", { class: "bq-overwrite-applyall" }, othersBox, othersText);
|
|
12287
|
+
stopIndexOthers.checked = false;
|
|
12288
|
+
stopIndexOthers.box = othersBox;
|
|
12289
|
+
stopIndexOthers.text = othersText;
|
|
12290
|
+
stopIndexOthers.row = othersRow;
|
|
12291
|
+
refreshStopIndexOthers();
|
|
12210
12292
|
return h(
|
|
12211
12293
|
"div",
|
|
12212
12294
|
{ class: "bq-modal" },
|
|
@@ -12226,6 +12308,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
12226
12308
|
{ class: "bq-modal-desc bq-modal-delete-warn" },
|
|
12227
12309
|
"Whatever has been indexed so far stays searchable, and the pass already running finishes on the server. The remaining passes are dropped, not paused, so the file stays partly indexed until you reindex it."
|
|
12228
12310
|
),
|
|
12311
|
+
othersRow,
|
|
12229
12312
|
h(
|
|
12230
12313
|
"div",
|
|
12231
12314
|
{ class: "bq-modal-btns" },
|
package/dist/engine.cjs
CHANGED
|
@@ -359,10 +359,15 @@ function buildChatSystemPrompt(params) {
|
|
|
359
359
|
const { projectId, serviceName, serviceDescription, greeting, canUpload, client } = params;
|
|
360
360
|
const g = params.indexAccessGroup;
|
|
361
361
|
const indexGroupLiteral = typeof g === "number" ? String(g) : g === "public" || g === "private" || g === "authorized" || g === "admin" ? `"${g}"` : '"authorized"';
|
|
362
|
+
const groundingRules = `ANSWER ONLY FROM THE DATA. Every fact you state about this project's data - a name, a number, a date, a status, what a document says, whether something exists - must come from one of four places: a tool result returned in THIS conversation, a file or image attached to it (including one you read through its attachment link), the project description at the end of this prompt, or what the user told you themselves in this chat. Your general knowledge, the open internet and a plausible guess are NOT sources. Never use them to answer a question about the user's data, to fill a gap in it, or to complete a record that came back partial. A value that is not in what you retrieved is unknown: say it is not in the data instead of supplying a likely one.
|
|
363
|
+
WHEN THE DATA DOES NOT HAVE IT, SAY SO. Once you have searched the way these rules require and nothing answers the question, tell the user plainly, in their language, that this project's data does not contain it, and say what you searched (the tables, files or terms) so they can tell missing data from a missed search. Do not answer from memory instead, and do not turn a missing answer into a general explanation of the topic. Offer what would let you answer it: ${canUpload === false ? `another name or file to look under, or asking the project's owner to add it` : `the document to upload, or another name or file to look under`}.
|
|
364
|
+
KEEP WHAT THE DATA SAYS APART FROM WHAT YOU WORKED OUT. A total you computed, a comparison, a trend, an interpretation or a recommendation is yours, not the data's: label it as such ("calculated from...", "this suggests...") and name the rows or files it was made from. Never present an estimate, an assumption or an inference as something the data states.
|
|
365
|
+
These three rules govern every claim about the project's data. They do not limit the About BunnyQuery section (answer product questions from it), a greeting, or text the user explicitly asks you to write.`;
|
|
362
366
|
let systemPrompt = `
|
|
363
367
|
You are a dedicated assistant for the project ID: "${projectId}".
|
|
364
368
|
Scope: Only answer questions about this project and its data. Do not answer questions about other projects or topics unrelated to this project. When the user refers to "my database", "my data", or "my files", treat those as references to this project's database and file storage. The ONE exception is BunnyQuery itself - what this app is, what it can do, and how to use it - which is always in scope: answer it from the "About BunnyQuery" section at the end of this prompt.
|
|
365
369
|
Knowledge lookup: Before saying you don't know or that something isn't in the chat history, ALWAYS query this project's database through the available MCP tools to look for the answer. The user's data is the source of truth - the chat transcript is not. Only respond with "I don't know" or "I couldn't find that" after you have actually searched the project's data and come back empty.
|
|
370
|
+
${groundingRules}
|
|
366
371
|
NUMBERS FROM A SPREADSHEET: use queryGrid, never mental arithmetic over records. A total, a count, an average, a "how many mention X", a "which one is biggest" - all of those are computed server-side over EVERY row of the file and come back with the sheet, the row count and the row numbers they were made from. Records are a SAMPLE, and a sample added up is a confident wrong number. Quote the row count and the sheet alongside the figure so the reader can check it.
|
|
367
372
|
CALL queryGrid describe FIRST, before any figure. Workbooks routinely state the same money more than once: a detail sheet, then per-song, per-album and per-artist sheets that each re-total it, plus a summary sheet whose bottom row is the file total. Those look like four different answers and are one. describe names which sheets restate which, and which rows are totals. Pick ONE sheet, say which you picked, and never add figures across a sheet and its summary. If the reply carries a warning about restatement, repeat it to the user.
|
|
368
373
|
A FILE TOTAL IS NOT A ROW'S TOTAL. The biggest number on a summary sheet is the whole file, not the thing that was asked about. Before quoting any figure, check it is scoped to what the question named: filter by the column that identifies it and report how many rows matched.
|
|
@@ -3255,8 +3260,9 @@ function mapHistoryListToMessages(list, platform, opts) {
|
|
|
3255
3260
|
var createdTs = Number(item && item.created);
|
|
3256
3261
|
var updatedTs = Number(item && item.updated);
|
|
3257
3262
|
var executedTs = Number(item && item.executed);
|
|
3263
|
+
var respondedTs = Number(item && item.responded);
|
|
3258
3264
|
var userTs = isFinite(createdTs) && createdTs > 0 ? createdTs : isFinite(updatedTs) && updatedTs > 0 ? updatedTs : void 0;
|
|
3259
|
-
var replyTs = isFinite(updatedTs) && updatedTs > 0 ? updatedTs : isFinite(createdTs) && createdTs > 0 ? createdTs : void 0;
|
|
3265
|
+
var replyTs = isFinite(respondedTs) && respondedTs > 0 ? respondedTs : isFinite(updatedTs) && updatedTs > 0 ? updatedTs : isFinite(createdTs) && createdTs > 0 ? createdTs : void 0;
|
|
3260
3266
|
if (userText) {
|
|
3261
3267
|
var displayContent;
|
|
3262
3268
|
var indexFile = void 0;
|
|
@@ -3883,6 +3889,13 @@ var ChatSession = class {
|
|
|
3883
3889
|
// the request budget MAX_CONCURRENT_BG_POLLS exists to protect.
|
|
3884
3890
|
/** Live streams by server item id. One per in-flight streamed turn. */
|
|
3885
3891
|
this.liveStreams = {};
|
|
3892
|
+
/** When skapi recorded a turn's response as having finished arriving, by server
|
|
3893
|
+
* item id. Read off the terminal envelope of a STREAMED turn (the only settle
|
|
3894
|
+
* that carries it, see _settleLiveStream) and used as the answer bubble's time
|
|
3895
|
+
* in place of this browser's clock. Entries are never removed: a settle reaches
|
|
3896
|
+
* here twice (the poll's onResponse, then the promise), and the value for an id
|
|
3897
|
+
* cannot change. */
|
|
3898
|
+
this._respondedAt = {};
|
|
3886
3899
|
// ─── compact-stub hydration ─────────────────────────────────────────────
|
|
3887
3900
|
// Split-fetch bg pages arrive as label stubs (no bodies). When the user
|
|
3888
3901
|
// expands a row, the real reply text is fetched per item (csr-poll point
|
|
@@ -4408,6 +4421,23 @@ var ChatSession = class {
|
|
|
4408
4421
|
});
|
|
4409
4422
|
return n;
|
|
4410
4423
|
}
|
|
4424
|
+
_noteRespondedAt(itemId, response) {
|
|
4425
|
+
if (!itemId || !isCsrStatusEnvelope(response)) return;
|
|
4426
|
+
var at = Number(response.responded);
|
|
4427
|
+
if (!isFinite(at) || at <= 0) return;
|
|
4428
|
+
if (!this._respondedAt) this._respondedAt = {};
|
|
4429
|
+
this._respondedAt[itemId] = at;
|
|
4430
|
+
}
|
|
4431
|
+
/** The time a live answer bubble is stamped with: when skapi received the
|
|
4432
|
+
* response, if the settle told us, else now. "Now" is what every bubble got
|
|
4433
|
+
* before, and it is still right for a buffered turn, whose settle hands back the
|
|
4434
|
+
* destination's body and nothing of skapi's; a history load then replaces it
|
|
4435
|
+
* with the server's value. */
|
|
4436
|
+
_replyStampFor(msg) {
|
|
4437
|
+
var id = msg ? msg._serverItemId : void 0;
|
|
4438
|
+
var at = typeof id === "string" && id && this._respondedAt ? this._respondedAt[id] : void 0;
|
|
4439
|
+
return typeof at === "number" ? at : wallClockNow();
|
|
4440
|
+
}
|
|
4411
4441
|
/**
|
|
4412
4442
|
* Open (or re-open) the live stream for `itemId`, or null when this poll must
|
|
4413
4443
|
* not carry one.
|
|
@@ -4597,6 +4627,7 @@ var ChatSession = class {
|
|
|
4597
4627
|
_settleLiveStream(st, response) {
|
|
4598
4628
|
this._closeLiveStream(st, true);
|
|
4599
4629
|
if (!isCsrStatusEnvelope(response)) return response;
|
|
4630
|
+
this._noteRespondedAt(st.id, response);
|
|
4600
4631
|
if (response.status !== "resolved") return response;
|
|
4601
4632
|
if (!this._mayFinalize(st)) this._rec().incomplete[st.id] = true;
|
|
4602
4633
|
if (st.finalBody == null) return response;
|
|
@@ -6038,7 +6069,7 @@ var ChatSession = class {
|
|
|
6038
6069
|
return thinkingIdx !== -1 ? thinkingIdx : userIdx >= 0 ? userIdx + 1 : -1;
|
|
6039
6070
|
}
|
|
6040
6071
|
insertAtTarget(msg, targetIdx) {
|
|
6041
|
-
if (msg && msg.role === "assistant" && msg._ts === void 0) msg._ts =
|
|
6072
|
+
if (msg && msg.role === "assistant" && msg._ts === void 0) msg._ts = this._replyStampFor(msg);
|
|
6042
6073
|
var tgt = targetIdx >= 0 ? this.state.messages[targetIdx] : void 0;
|
|
6043
6074
|
var replaceable = !!tgt && !!tgt.isPending && !tgt.isBackgroundTask && this._isOwnPlaceholderOf(targetIdx, this._owningUserIndex(targetIdx));
|
|
6044
6075
|
if (replaceable) this.state.messages[targetIdx] = msg;
|
|
@@ -6661,7 +6692,7 @@ var ChatSession = class {
|
|
|
6661
6692
|
enqueueTypewrite(idx, fullText, localId, paintedText) {
|
|
6662
6693
|
var self = this;
|
|
6663
6694
|
var target = this.state.messages[idx];
|
|
6664
|
-
if (target && target._ts === void 0) target._ts =
|
|
6695
|
+
if (target && target._ts === void 0) target._ts = this._replyStampFor(target);
|
|
6665
6696
|
if (!this.typewriterQueue) this.typewriterQueue = Promise.resolve();
|
|
6666
6697
|
this.typewriterQueue = this.typewriterQueue.then(function() {
|
|
6667
6698
|
return self.typewriteIntoIndex(idx, fullText, localId, paintedText);
|