@yfwu2020/dsh-selection-explain 0.11.1 → 0.12.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/lib/client.js CHANGED
@@ -151,7 +151,6 @@ var VOICE_SILENCE_RMS = 0.012;
151
151
  var CONTEXT_WINDOW = 1500;
152
152
  var KEY_CONTEXT_CHARS = 300;
153
153
  var MAX_QUOTES = 4;
154
- var MAX_QUOTE_CHARS = 3e3;
155
154
  var MAX_QUOTE_SELECTION = 2e4;
156
155
  var QUOTE_ONLY_QUESTION = "\u5C31\u4E0A\u9762\u5F15\u7528\u7684\u6587\u5B57\uFF0C\u8BF4\u8BF4\u5B83\u5728\u8FD9\u91CC\u662F\u4EC0\u4E48\u610F\u601D\u3002";
157
156
  var QUOTE_CONTEXT_API = "/selection-explain/api/quote-context";
@@ -1836,6 +1835,13 @@ function adaptPreviewHtml(code, previewId) {
1836
1835
  }
1837
1836
  var previewSeq = 0;
1838
1837
  var previewFrames = {};
1838
+ function releasePreviewFrames(root) {
1839
+ if (!root) return;
1840
+ for (var key in previewFrames) {
1841
+ var frame = previewFrames[key].frame;
1842
+ if (frame === root || root.contains && root.contains(frame)) delete previewFrames[key];
1843
+ }
1844
+ }
1839
1845
  function registerPreviewFrame(frame) {
1840
1846
  previewSeq += 1;
1841
1847
  var id = "pv" + previewSeq;
@@ -1919,6 +1925,7 @@ function webPreviewBlock(code) {
1919
1925
  return { root: wrap, pre, frame };
1920
1926
  }
1921
1927
  function renderRich(parent2, text, options) {
1928
+ releasePreviewFrames(parent2);
1922
1929
  parent2.textContent = "";
1923
1930
  var lines = String(text || "").replace(/\r/g, "").split("\n");
1924
1931
  var current = null;
@@ -3259,7 +3266,7 @@ function createVoiceCardController(env) {
3259
3266
  }
3260
3267
  function unpinVCardBox() {
3261
3268
  if (vcardState.unpinTimer) {
3262
- clearTimeout(vcardState.unpinTimer);
3269
+ env.timerScope.clear(vcardState.unpinTimer);
3263
3270
  vcardState.unpinTimer = null;
3264
3271
  }
3265
3272
  if (vcard && vcardState.unpinHandler) {
@@ -3273,7 +3280,7 @@ function createVoiceCardController(env) {
3273
3280
  function scheduleVCardUnpin() {
3274
3281
  if (!vcard) return;
3275
3282
  if (vcardState.unpinTimer) {
3276
- clearTimeout(vcardState.unpinTimer);
3283
+ env.timerScope.clear(vcardState.unpinTimer);
3277
3284
  vcardState.unpinTimer = null;
3278
3285
  }
3279
3286
  if (vcardState.unpinHandler) {
@@ -3582,7 +3589,7 @@ function createVoiceCardController(env) {
3582
3589
  line.setAttribute("data-off", "a");
3583
3590
  var done = function() {
3584
3591
  if (vcardState.crtTimer) {
3585
- clearTimeout(vcardState.crtTimer);
3592
+ env.timerScope.clear(vcardState.crtTimer);
3586
3593
  vcardState.crtTimer = null;
3587
3594
  }
3588
3595
  if (vcard && vcardState.crtHandler) {
@@ -3601,7 +3608,7 @@ function createVoiceCardController(env) {
3601
3608
  function finishCrtOff() {
3602
3609
  vcardState.closing = false;
3603
3610
  if (vcardState.crtTimer) {
3604
- clearTimeout(vcardState.crtTimer);
3611
+ env.timerScope.clear(vcardState.crtTimer);
3605
3612
  vcardState.crtTimer = null;
3606
3613
  }
3607
3614
  if (vcard && vcardState.crtHandler) {
@@ -3755,14 +3762,13 @@ function createVoiceCardController(env) {
3755
3762
  function startMicWatch() {
3756
3763
  if (vcardState.timer) return;
3757
3764
  if (!env.state.selection && !env.state.quoteSelection) return;
3758
- vcardState.timer = setInterval(micTick, MIC_POLL_MS);
3759
- env.timers.push(vcardState.timer);
3765
+ vcardState.timer = env.timerScope.interval(micTick, MIC_POLL_MS);
3760
3766
  micTick();
3761
3767
  }
3762
3768
  function stopMicWatch() {
3763
3769
  quoteVoiceHandled = false;
3764
3770
  if (!vcardState.timer) return;
3765
- clearInterval(vcardState.timer);
3771
+ env.timerScope.clear(vcardState.timer);
3766
3772
  vcardState.timer = null;
3767
3773
  }
3768
3774
  function micTick() {
@@ -3882,8 +3888,6 @@ function createQuotesController(env) {
3882
3888
  function addQuote(text, label, extra) {
3883
3889
  var clean = String(text || "").replace(/\u00a0/g, " ").trim();
3884
3890
  if (!clean) return false;
3885
- var truncated = clean.length > MAX_QUOTE_CHARS;
3886
- if (truncated) clean = clean.slice(0, MAX_QUOTE_CHARS) + "\u2026";
3887
3891
  for (var i = 0; i < env.state.quotes.length; i += 1) {
3888
3892
  if (env.state.quotes[i].text === clean) {
3889
3893
  env.setStatus("\u8FD9\u6BB5\u5DF2\u7ECF\u5728\u5F15\u7528\u91CC\u4E86");
@@ -3896,19 +3900,27 @@ function createQuotesController(env) {
3896
3900
  }
3897
3901
  quoteSeq += 1;
3898
3902
  var context = extra && typeof extra.context === "string" ? extra.context.trim() : "";
3899
- env.state.quotes.push({
3903
+ var quote = {
3900
3904
  id: quoteSeq,
3901
3905
  label: label || "\u5F15\u7528",
3902
3906
  text: clean,
3903
3907
  context,
3904
- session: !!(extra && extra.session === true)
3905
- });
3908
+ session: !!(extra && extra.session === true),
3909
+ sessionId: extra && extra.sessionId || env.currentSessionId(),
3910
+ contextJob: null
3911
+ };
3912
+ env.state.quotes.push(quote);
3913
+ if (quote.session && quote.sessionId) {
3914
+ quote.contextJob = fetchQuoteContext(quote.sessionId, quote.text).then(function(context2) {
3915
+ if (context2) quote.context = context2;
3916
+ });
3917
+ }
3906
3918
  renderQuotes();
3907
3919
  env.refreshAskState();
3908
3920
  env.askRow.style.display = "";
3909
3921
  if (env.composerVisible()) env.focusAskBox();
3910
3922
  env.setStatus(
3911
- "\u5DF2\u52A0\u5165\u5F15\u7528\uFF08" + quoteLabelOf(env.state.quotes[env.state.quotes.length - 1]) + " \xB7 " + clean.length + " \u5B57" + (truncated ? "\uFF08\u539F\u6587\u66F4\u957F\uFF0C\u5DF2\u622A\u65AD\uFF09" : "") + (context ? " \xB7 \u5E26\u4E0A\u4E0B\u6587 " + context.length + " \u5B57" : "") + "\uFF09"
3923
+ "\u5DF2\u52A0\u5165\u5F15\u7528\uFF08" + quoteLabelOf(env.state.quotes[env.state.quotes.length - 1]) + " \xB7 " + clean.length + " \u5B57" + (context ? " \xB7 \u5E26\u4E0A\u4E0B\u6587 " + context.length + " \u5B57" : "") + "\uFF09"
3912
3924
  );
3913
3925
  return true;
3914
3926
  }
@@ -3973,6 +3985,22 @@ function createQuotesController(env) {
3973
3985
  function quoteContextFor(selection) {
3974
3986
  if (!selection) return { context: "", session: false };
3975
3987
  if (selection.source === "panel") {
3988
+ var index = selection.turnIndex;
3989
+ var through = env.state.contextState && env.state.contextState.through || 0;
3990
+ var visibleBefore = env.state.turns.slice(0, index).filter(function(t) {
3991
+ return !t.hidden;
3992
+ }).length;
3993
+ if (index >= 0 && visibleBefore < through) {
3994
+ var pieces = env.state.turns.slice(Math.max(0, index - 1), index + 2).filter(function(t) {
3995
+ return !t.hidden;
3996
+ }).map(function(t) {
3997
+ var text = t.role === "assistant" ? env.foldForHistory(t.text || "") : sentTextOf(t);
3998
+ var at = text.indexOf(selection.text);
3999
+ var start = at >= 0 ? Math.max(0, at - 500) : 0;
4000
+ return (t.role === "user" ? "\u7528\u6237\uFF1A" : "\u52A9\u624B\uFF1A") + text.slice(start, start + Math.max(1500, selection.text.length + 500));
4001
+ });
4002
+ return { context: pieces.join("\n"), session: false };
4003
+ }
3976
4004
  return { context: "", session: false };
3977
4005
  }
3978
4006
  if (selection.source === "iframe") {
@@ -3993,22 +4021,16 @@ function createQuotesController(env) {
3993
4021
  for (var i = 0; i < quotes.length; i += 1) {
3994
4022
  if (quotes[i].session === true) jobs.push(quotes[i]);
3995
4023
  }
3996
- var sessionId = env.currentSessionId();
3997
- if (jobs.length === 0 || !sessionId) return Promise.resolve(quotes);
3998
- return Promise.all(
3999
- jobs.map(function(quote) {
4000
- return fetchQuoteContext(sessionId, quote.text).then(function(context) {
4001
- if (context) quote.context = context;
4002
- return quote;
4003
- });
4004
- })
4005
- ).then(function() {
4024
+ if (jobs.length === 0) return Promise.resolve(quotes);
4025
+ return Promise.all(jobs.map(function(quote) {
4026
+ return quote.contextJob || Promise.resolve();
4027
+ })).then(function() {
4006
4028
  return quotes;
4007
4029
  });
4008
4030
  }
4009
4031
  function fetchQuoteContext(sessionId, text) {
4010
4032
  var controller = typeof AbortController === "function" ? new AbortController() : null;
4011
- var timer = setTimeout(function() {
4033
+ var timer = env.timerScope.later(function() {
4012
4034
  if (controller) {
4013
4035
  try {
4014
4036
  controller.abort();
@@ -4016,7 +4038,6 @@ function createQuotesController(env) {
4016
4038
  }
4017
4039
  }
4018
4040
  }, QUOTE_CONTEXT_TIMEOUT);
4019
- env.timers.push(timer);
4020
4041
  return fetch(QUOTE_CONTEXT_API, {
4021
4042
  method: "POST",
4022
4043
  headers: { "content-type": "application/json" },
@@ -4025,11 +4046,11 @@ function createQuotesController(env) {
4025
4046
  }).then(function(response) {
4026
4047
  return response.ok ? response.json() : null;
4027
4048
  }).then(function(data) {
4028
- clearTimeout(timer);
4049
+ env.timerScope.clear(timer);
4029
4050
  if (data && data.ok === true && data.matched === true && typeof data.context === "string") return data.context;
4030
4051
  return "";
4031
4052
  }).catch(function() {
4032
- clearTimeout(timer);
4053
+ env.timerScope.clear(timer);
4033
4054
  return "";
4034
4055
  });
4035
4056
  }
@@ -4071,6 +4092,7 @@ function createQuotesController(env) {
4071
4092
  out.push({
4072
4093
  role: turn.role,
4073
4094
  text: sentTextOf(turn),
4095
+ ...turn.contextExcluded === true ? { contextExcluded: true } : {},
4074
4096
  ...turn.attachments && turn.attachments.length > 0 ? { attachments: turn.attachments } : {}
4075
4097
  });
4076
4098
  }
@@ -4919,7 +4941,7 @@ function createPreviewController(env) {
4919
4941
  // src/client/panel.js
4920
4942
  function createPanelController(env) {
4921
4943
  function panelOrCurrentSessionId() {
4922
- return env.panelSessionId || currentSessionId();
4944
+ return env.state.contextState && typeof env.state.contextState.sessionId === "string" ? env.state.contextState.sessionId : env.panelSessionId || currentSessionId();
4923
4945
  }
4924
4946
  function currentSessionId() {
4925
4947
  try {
@@ -5017,6 +5039,7 @@ function createPanelController(env) {
5017
5039
  function openPanelWithInner(text, context, label, anchor, keyContext, options) {
5018
5040
  env.state.skipInterpretation = !!(options && options.askFirst);
5019
5041
  env.panelSessionId = currentSessionId();
5042
+ env.state.contextState = null;
5020
5043
  env.state.payload = buildPayload(text, context, label);
5021
5044
  env.state.kind = env.state.payload.kind;
5022
5045
  env.hideButton();
@@ -5282,12 +5305,12 @@ function createPanelController(env) {
5282
5305
  env.panel.style.transition = "transform " + PANEL_GROW_MS + "ms " + PANEL_GROW_EASE;
5283
5306
  env.panel.style.transform = "none";
5284
5307
  if (env.panelGrowTimer) {
5285
- clearTimeout(env.panelGrowTimer);
5308
+ env.timerScope.clear(env.panelGrowTimer);
5286
5309
  env.panelGrowTimer = null;
5287
5310
  }
5288
5311
  var settle = function() {
5289
5312
  if (env.panelGrowTimer) {
5290
- clearTimeout(env.panelGrowTimer);
5313
+ env.timerScope.clear(env.panelGrowTimer);
5291
5314
  env.panelGrowTimer = null;
5292
5315
  }
5293
5316
  if (env.panelGrowHandler) {
@@ -5310,7 +5333,7 @@ function createPanelController(env) {
5310
5333
  env.panelOpen = false;
5311
5334
  env.panel.style.display = "none";
5312
5335
  if (env.panelGrowTimer) {
5313
- clearTimeout(env.panelGrowTimer);
5336
+ env.timerScope.clear(env.panelGrowTimer);
5314
5337
  env.panelGrowTimer = null;
5315
5338
  }
5316
5339
  if (env.panelGrowHandler) {
@@ -5328,6 +5351,11 @@ function createPanelController(env) {
5328
5351
  if (env.state.request) env.state.aborted = true;
5329
5352
  if (env.state.request) env.state.request.abort();
5330
5353
  env.state.request = null;
5354
+ if (env.state.askRequest) {
5355
+ env.state.stopped = true;
5356
+ env.state.askRequest.abort();
5357
+ }
5358
+ releaseRenderedContent();
5331
5359
  env.paintPill();
5332
5360
  env.syncRouteWatch();
5333
5361
  }
@@ -5356,7 +5384,20 @@ function createPanelController(env) {
5356
5384
  var what = env.state.toolBusy ? "\u6B63\u5728\u68C0\u7D22\u8D44\u6599\u2026" : env.state.phase === "loading" ? "\u6B63\u5728\u5206\u6790\u2026" : "\u751F\u6210\u4E2D\u2026";
5357
5385
  return what + " " + seconds + "s";
5358
5386
  }
5387
+ function releaseRenderedContent() {
5388
+ env.clearWaiting();
5389
+ env.clearRenderedTurns();
5390
+ for (var content of [env.translationSection.content, env.detailSection.content]) {
5391
+ releasePreviewFrames(content);
5392
+ content.textContent = "";
5393
+ }
5394
+ }
5359
5395
  function paint() {
5396
+ if (!env.panelOpen) {
5397
+ releaseRenderedContent();
5398
+ env.paintPill();
5399
+ return;
5400
+ }
5360
5401
  var streaming = env.state.phase === "loading" || env.state.phase === "streaming";
5361
5402
  var stage = env.state.stage;
5362
5403
  var selected = env.state.payload && env.state.payload.text ? env.state.payload.text : env.state.selection ? env.state.selection.text : "";
@@ -5555,6 +5596,7 @@ function createPanelController(env) {
5555
5596
  env.panel.style.top = Math.round(top) + "px";
5556
5597
  }
5557
5598
  return {
5599
+ releaseRenderedContent,
5558
5600
  openForSelection,
5559
5601
  currentSessionId,
5560
5602
  loadPanelSize,
@@ -6143,9 +6185,69 @@ function createPillController(env) {
6143
6185
  };
6144
6186
  }
6145
6187
 
6188
+ // src/chat-context.ts
6189
+ var CHAT_REQUEST_BYTES = 512 * 1024;
6190
+ var EMPTY_DIGEST = "811c9dc5";
6191
+ function readContextState(raw) {
6192
+ if (!raw || typeof raw !== "object") return null;
6193
+ const s = raw;
6194
+ if (s.version !== 1 || !Number.isSafeInteger(s.through) || s.through < 0 || !/^[a-f0-9]{8}$/.test(s.source) || !/^[a-f0-9]{8}$/.test(s.digest) || typeof s.summary !== "string" || s.summary.length > 12e3 || s.through > 0 && !s.summary.trim()) return null;
6195
+ if (s.background !== null && (!s.background || typeof s.background.transcript !== "string" || s.background.transcript.length > 2e5 || typeof s.background.marked !== "boolean")) return null;
6196
+ if (s.sessionId !== void 0 && (typeof s.sessionId !== "string" || s.sessionId.length > 500)) return null;
6197
+ return {
6198
+ version: 1,
6199
+ ...typeof s.sessionId === "string" ? { sessionId: s.sessionId } : {},
6200
+ source: s.source,
6201
+ through: s.through,
6202
+ digest: s.digest,
6203
+ summary: s.summary,
6204
+ background: s.background ? { transcript: s.background.transcript, marked: s.background.marked } : null
6205
+ };
6206
+ }
6207
+ function modelTurn(turn) {
6208
+ if (turn.contextExcluded) return {
6209
+ role: turn.role === "assistant" ? "assistant" : "user",
6210
+ text: turn.role === "assistant" ? "\uFF08\u8BE5\u8F6E\u672A\u751F\u6210\u56DE\u7B54\u3002\uFF09" : "\uFF08\u8BE5\u63D0\u95EE\u672A\u63D0\u4EA4\u7ED9\u6A21\u578B\uFF0C\u539F\u59CB\u5185\u5BB9\u4ECD\u4FDD\u5B58\u5728\u754C\u9762\u5386\u53F2\u4E2D\uFF1B\u9700\u8981\u65F6\u8BF7\u91CD\u65B0\u5F15\u7528\u5FC5\u8981\u90E8\u5206\u3002\uFF09"
6211
+ };
6212
+ return {
6213
+ role: turn.role === "assistant" ? "assistant" : "user",
6214
+ text: turn.sent || turn.text || "",
6215
+ ...turn.attachments?.length ? { attachments: turn.attachments } : {}
6216
+ };
6217
+ }
6218
+ function historyDigest(turns, initial = EMPTY_DIGEST) {
6219
+ let hash = Number.parseInt(initial, 16) >>> 0;
6220
+ for (const raw of turns) {
6221
+ const t = modelTurn(raw);
6222
+ const text = JSON.stringify([t.role, t.text, (t.attachments || []).map((a) => [a.kind, a.ref.attachmentId])]) + "\n";
6223
+ for (let i = 0; i < text.length; i++) hash = Math.imul(hash ^ text.charCodeAt(i), 16777619) >>> 0;
6224
+ }
6225
+ return hash.toString(16).padStart(8, "0");
6226
+ }
6227
+ function contextSource(session, text, context, initial) {
6228
+ return historyDigest([{ role: "user", text: JSON.stringify([session, text, context, initial]) }]);
6229
+ }
6230
+ function prepareChatHistory(turns, rawState) {
6231
+ const visible = turns.filter((t) => !t.hidden);
6232
+ let state = readContextState(rawState);
6233
+ if (state && (state.through > visible.length || historyDigest(visible.slice(0, state.through)) !== state.digest)) state = null;
6234
+ const start = state?.through || 0;
6235
+ const materials = [];
6236
+ const seen = /* @__PURE__ */ new Set();
6237
+ visible.forEach((turn, index) => {
6238
+ for (const item of turn.attachments || []) {
6239
+ const key = item.kind + ":" + item.ref.attachmentId;
6240
+ if (!seen.has(key)) {
6241
+ seen.add(key);
6242
+ materials.push({ kind: item.kind, ref: item.ref, turn: index });
6243
+ }
6244
+ }
6245
+ });
6246
+ return { history: visible.slice(start).map(modelTurn), historyStart: start, contextState: state, materials };
6247
+ }
6248
+
6146
6249
  // src/client/history.js
6147
6250
  function createHistoryController(env) {
6148
- var FOLD_HISTORY_KEEP_LATEST = true;
6149
6251
  var foldCount = 0;
6150
6252
  var HTML_VOID_TAGS = {
6151
6253
  area: 1,
@@ -6426,16 +6528,6 @@ function createHistoryController(env) {
6426
6528
  src = src.replace(/```html[ \t]*\n([\s\S]*)$/, one);
6427
6529
  return src;
6428
6530
  }
6429
- function historyTextOf(turn, keepRaw) {
6430
- if (!turn) return "";
6431
- if (turn.role === "user") return env.sentTextOf(turn);
6432
- if (keepRaw) return turn.text;
6433
- if (typeof turn.foldedCache === "string") return turn.foldedCache;
6434
- if (turn.streaming === true) return turn.text;
6435
- var folded = foldForHistory(turn.text);
6436
- turn.foldedCache = folded;
6437
- return folded;
6438
- }
6439
6531
  function currentSurfaceTheme() {
6440
6532
  try {
6441
6533
  var bodyEl = document.body;
@@ -6464,6 +6556,7 @@ function createHistoryController(env) {
6464
6556
  function visibleRaw() {
6465
6557
  return env.state.raw;
6466
6558
  }
6559
+ var historySaveSequence = 0;
6467
6560
  function saveHistory(options) {
6468
6561
  var payload = env.state.payload;
6469
6562
  if (!payload || !env.state.cacheKey) return;
@@ -6474,6 +6567,22 @@ function createHistoryController(env) {
6474
6567
  if (turn.hidden === true) continue;
6475
6568
  turns.push(exported[i]);
6476
6569
  }
6570
+ var owner = env.state.turns;
6571
+ var ownerKey = env.state.cacheKey;
6572
+ var sequence = ++historySaveSequence;
6573
+ function noteSaved(error) {
6574
+ if (sequence !== historySaveSequence || env.state.turns !== owner || env.state.cacheKey !== ownerKey) return;
6575
+ var last = owner[owner.length - 1];
6576
+ if (!last || last.role !== "assistant") return;
6577
+ if (error) {
6578
+ last.notice = "\u5386\u53F2\u8BB0\u5F55\u4FDD\u5B58\u5931\u8D25\uFF0C\u5F53\u524D\u539F\u6587\u4ECD\u5728\u5C0F\u7A97\u4E2D\uFF1B\u5237\u65B0\u6216\u9000\u51FA\u524D\u8BF7\u5148\u590D\u5236\u91CD\u8981\u5185\u5BB9\u3002";
6579
+ last.noticeCode = "history-save-failed";
6580
+ } else if (last.noticeCode === "history-save-failed") {
6581
+ last.notice = "";
6582
+ last.noticeCode = "";
6583
+ } else return;
6584
+ renderTurns();
6585
+ }
6477
6586
  try {
6478
6587
  fetch(HISTORY, {
6479
6588
  method: "POST",
@@ -6486,15 +6595,23 @@ function createHistoryController(env) {
6486
6595
  parts: env.state.parts,
6487
6596
  turns,
6488
6597
  toolDigest: env.state.toolDigest,
6598
+ contextState: env.state.contextState || null,
6489
6599
  pinned: !!(options && options.pinned),
6490
6600
  // 这段对话有没有解读,必须跟着条目落盘:回放时靠它决定画不画「翻译」一节。
6491
6601
  // 不落盘的话,语音卡那条"跳过翻译与详解、直接追问"的对话下次开出来,
6492
6602
  // 会顶着一张空翻译卡片("这次没有返回内容")——用户实测。
6493
6603
  skipped: env.state.skipInterpretation === true
6494
6604
  })
6605
+ }).then(function(response) {
6606
+ if (!response.ok) throw new Error("HTTP " + response.status);
6607
+ return response.json();
6608
+ }).then(function(result) {
6609
+ noteSaved(!result || result.ok !== true);
6495
6610
  }).catch(function() {
6611
+ noteSaved(true);
6496
6612
  });
6497
6613
  } catch (error) {
6614
+ noteSaved(true);
6498
6615
  }
6499
6616
  }
6500
6617
  function applyHistoryEntry(entry, source) {
@@ -6510,6 +6627,8 @@ function createHistoryController(env) {
6510
6627
  }
6511
6628
  var noInterpretation = !String(parts.translation || "").trim() && !String(parts.detail || "").trim();
6512
6629
  env.state.skipInterpretation = entry.skipped === true || entry.skipped === void 0 && noInterpretation && hasAskTurn;
6630
+ env.state.contextState = readContextState(entry.contextState);
6631
+ if (env.state.contextState && typeof env.state.contextState.sessionId === "string") env.panelSessionId = env.state.contextState.sessionId;
6513
6632
  env.state.turns = Array.isArray(entry.turns) ? entry.turns.slice() : [];
6514
6633
  env.clearQuotes();
6515
6634
  env.state.toolDigest = typeof entry.toolDigest === "string" ? entry.toolDigest : "";
@@ -6541,8 +6660,7 @@ function createHistoryController(env) {
6541
6660
  settled = true;
6542
6661
  if (env.state.cacheKey === cacheKey && onMiss) onMiss();
6543
6662
  }
6544
- var timer = setTimeout(miss, 1500);
6545
- env.timers.push(timer);
6663
+ var timer = env.timerScope.later(miss, 1500);
6546
6664
  try {
6547
6665
  fetch(HISTORY + "?key=" + encodeURIComponent(cacheKey)).then(function(response) {
6548
6666
  return response.json();
@@ -6554,7 +6672,7 @@ function createHistoryController(env) {
6554
6672
  }
6555
6673
  if (data && data.ok === true && data.entry) {
6556
6674
  settled = true;
6557
- clearTimeout(timer);
6675
+ env.timerScope.clear(timer);
6558
6676
  applyHistoryEntry(data.entry, "history");
6559
6677
  return;
6560
6678
  }
@@ -6709,7 +6827,7 @@ function createHistoryController(env) {
6709
6827
  });
6710
6828
  bar.appendChild(undo);
6711
6829
  env.historyList.appendChild(bar);
6712
- setTimeout(function() {
6830
+ env.timerScope.later(function() {
6713
6831
  if (bar.parentNode) bar.parentNode.removeChild(bar);
6714
6832
  }, 5e3);
6715
6833
  }
@@ -6841,24 +6959,81 @@ function createHistoryController(env) {
6841
6959
  if (Date.now() - stickAt < 150) return;
6842
6960
  env.state.follow = nearBottom();
6843
6961
  }
6962
+ var turnWindowSize = 40;
6963
+ var turnWindowKey = null;
6964
+ var renderedTurns = [];
6965
+ function clearRenderedTurns() {
6966
+ env.clearWaiting(env.chatLog);
6967
+ releasePreviewFrames(env.chatLog);
6968
+ env.chatLog.textContent = "";
6969
+ renderedTurns = [];
6970
+ turnWindowSize = 40;
6971
+ turnWindowKey = null;
6972
+ }
6844
6973
  function renderTurns(options) {
6974
+ if (!env.panelOpen) {
6975
+ clearRenderedTurns();
6976
+ return;
6977
+ }
6845
6978
  var force = !!(options && options.stick === true);
6846
- var stick = force || nearBottom();
6979
+ var stick = !(options && options.preservePosition) && (force || nearBottom());
6847
6980
  var visible = [];
6848
6981
  for (var i = 0; i < env.state.turns.length; i++) {
6849
6982
  if (env.state.turns[i].hidden !== true) visible.push(env.state.turns[i]);
6850
6983
  }
6851
- if (env.chatLog.children.length !== visible.length) {
6984
+ if (turnWindowKey !== env.state.cacheKey) {
6985
+ turnWindowKey = env.state.cacheKey;
6986
+ turnWindowSize = 40;
6987
+ }
6988
+ var earlier = Math.max(0, visible.length - turnWindowSize);
6989
+ visible = visible.slice(earlier);
6990
+ var sameWindow = renderedTurns.length === visible.length && visible.every(function(turn2, index) {
6991
+ return renderedTurns[index] === turn2;
6992
+ });
6993
+ var prependCount = visible.length - renderedTurns.length;
6994
+ var canPrepend = options && options.prepend && renderedTurns.length > 0 && prependCount > 0 && renderedTurns.every(function(turn2, index) {
6995
+ return visible[prependCount + index] === turn2;
6996
+ });
6997
+ if (canPrepend) {
6998
+ var moreButton = env.chatLog.children[0];
6999
+ var firstBubble = env.chatLog.children[1];
7000
+ if (earlier > 0) moreButton.textContent = "\u663E\u793A\u66F4\u65E9\u7684\u6D88\u606F\uFF08\u8FD8\u6709 " + earlier + " \u6761\uFF09";
7001
+ else env.chatLog.removeChild(moreButton);
7002
+ for (var p = 0; p < prependCount; p++) {
7003
+ var prepended = bubbleFor(visible[p]);
7004
+ prepended.__turn = visible[p];
7005
+ env.chatLog.insertBefore(prepended, firstBubble);
7006
+ }
7007
+ renderedTurns = visible.slice();
7008
+ } else if (!sameWindow || env.chatLog.children.length !== visible.length + (earlier > 0 ? 1 : 0)) {
6852
7009
  env.clearWaiting(env.chatLog);
7010
+ releasePreviewFrames(env.chatLog);
6853
7011
  env.chatLog.textContent = "";
7012
+ if (earlier > 0) {
7013
+ var more = el("button", "dsh-sel-expand", "\u663E\u793A\u66F4\u65E9\u7684\u6D88\u606F\uFF08\u8FD8\u6709 " + earlier + " \u6761\uFF09");
7014
+ more.type = "button";
7015
+ more.setAttribute("data-history-more", "1");
7016
+ listen(more, "click", function(event) {
7017
+ event.preventDefault();
7018
+ event.stopPropagation();
7019
+ var top = env.body.scrollTop;
7020
+ var height = env.body.scrollHeight;
7021
+ turnWindowSize += 40;
7022
+ renderTurns({ preservePosition: true, prepend: true });
7023
+ env.body.scrollTop = top + Math.max(0, env.body.scrollHeight - height);
7024
+ env.state.follow = false;
7025
+ });
7026
+ env.chatLog.appendChild(more);
7027
+ }
6854
7028
  for (var j = 0; j < visible.length; j++) {
6855
7029
  var bubble = bubbleFor(visible[j]);
6856
7030
  bubble.__turn = visible[j];
6857
7031
  env.chatLog.appendChild(bubble);
6858
7032
  }
7033
+ renderedTurns = visible.slice();
6859
7034
  var fresh = visible[visible.length - 1];
6860
7035
  if (fresh && fresh.role === "assistant" && fresh.asking === true && !fresh.text) {
6861
- env.showWaiting(env.chatLog.children[visible.length - 1], "\u6B63\u5728\u56DE\u7B54\u2026", {
7036
+ env.showWaiting(env.chatLog.children[env.chatLog.children.length - 1], "\u6B63\u5728\u56DE\u7B54\u2026", {
6862
7037
  startedAt: fresh.startedAt,
6863
7038
  effort: fresh.effort,
6864
7039
  effortOf: function() {
@@ -6870,7 +7045,7 @@ function createHistoryController(env) {
6870
7045
  });
6871
7046
  }
6872
7047
  } else if (visible.length > 0) {
6873
- var node = env.chatLog.children[visible.length - 1];
7048
+ var node = env.chatLog.children[env.chatLog.children.length - 1];
6874
7049
  var turn = visible[visible.length - 1];
6875
7050
  var painted = false;
6876
7051
  node.className = "dsh-sel-bubble " + (turn.role === "user" ? "dsh-sel-bubble-user" : "dsh-sel-bubble-bot") + (turn.error ? " dsh-sel-bubble-err" : "");
@@ -7053,6 +7228,7 @@ function createHistoryController(env) {
7053
7228
  return seed.length > 1500 ? seed.slice(0, 1500) + "\u2026" : seed;
7054
7229
  }
7055
7230
  return {
7231
+ clearRenderedTurns,
7056
7232
  hideHistoryList,
7057
7233
  placeHistoryList,
7058
7234
  toggleHistoryList,
@@ -7085,13 +7261,6 @@ function createHistoryController(env) {
7085
7261
  loadHistoryEntry,
7086
7262
  seedHistoryFromExplanation,
7087
7263
  ensureChatFollow,
7088
- get FOLD_HISTORY_KEEP_LATEST() {
7089
- return FOLD_HISTORY_KEEP_LATEST;
7090
- },
7091
- set FOLD_HISTORY_KEEP_LATEST(value) {
7092
- FOLD_HISTORY_KEEP_LATEST = value;
7093
- },
7094
- historyTextOf,
7095
7264
  clearTransientNotice,
7096
7265
  saveHistory,
7097
7266
  applyToolEvent,
@@ -7212,7 +7381,8 @@ function createConversationController(env) {
7212
7381
  if (env.state.turns.length === 0 && (env.state.raw.trim() || env.state.parts.translation || env.state.parts.detail)) {
7213
7382
  env.state.turns.push({ role: "assistant", text: env.seedHistoryFromExplanation(), seed: true, hidden: true });
7214
7383
  }
7215
- env.state.turns.push({ role: "user", text: asked || QUOTE_ONLY_QUESTION, quotes, sent, attachments: withAttachments });
7384
+ var questionTurn = { role: "user", text: asked || QUOTE_ONLY_QUESTION, quotes, sent, attachments: withAttachments };
7385
+ env.state.turns.push(questionTurn);
7216
7386
  var answerTurn = {
7217
7387
  role: "assistant",
7218
7388
  text: "",
@@ -7234,23 +7404,22 @@ function createConversationController(env) {
7234
7404
  if (env.state.sessionOpen !== void 0) {
7235
7405
  }
7236
7406
  var startedAt = Date.now();
7237
- var timer = setInterval(function() {
7407
+ var timer = env.timerScope.interval(function() {
7238
7408
  env.paintPill();
7239
7409
  env.setStatus(
7240
7410
  (env.state.toolBusy ? "\u6B63\u5728\u68C0\u7D22\u8D44\u6599\u2026 " : "\u8FFD\u95EE\u4E2D\u2026 ") + ((Date.now() - startedAt) / 1e3).toFixed(1) + "s"
7241
7411
  );
7242
7412
  updateWaitClocks();
7243
7413
  }, 200);
7244
- env.timers.push(timer);
7245
7414
  var historyTurns = env.state.turns.slice(0, env.state.turns.length - 2);
7246
- var history = historyTurns.map(function(turn, index) {
7247
- var keepRaw = env.FOLD_HISTORY_KEEP_LATEST && index === historyTurns.length - 1;
7248
- return {
7249
- role: turn.role,
7250
- text: env.historyTextOf(turn, keepRaw),
7251
- ...turn.attachments && turn.attachments.length > 0 ? { attachments: turn.attachments } : {}
7252
- };
7253
- });
7415
+ var initialExplanation = env.state.skipInterpretation ? "" : env.seedHistoryFromExplanation();
7416
+ var source = contextSource(env.panelOrCurrentSessionId(), payload.text.trim(), payload.context, initialExplanation);
7417
+ if (env.state.contextState && env.state.contextState.source !== source) env.state.contextState = null;
7418
+ var prepared = prepareChatHistory(historyTurns, env.state.contextState);
7419
+ env.state.contextState = prepared.contextState;
7420
+ var history = prepared.history;
7421
+ var requestTurns = env.state.turns;
7422
+ var requestKey = env.state.cacheKey;
7254
7423
  env.state.stopped = false;
7255
7424
  var askController = new AbortController();
7256
7425
  env.state.askRequest = {
@@ -7262,7 +7431,7 @@ function createConversationController(env) {
7262
7431
  }
7263
7432
  };
7264
7433
  function finish(error) {
7265
- clearInterval(timer);
7434
+ env.timerScope.clear(timer);
7266
7435
  env.state.asking = false;
7267
7436
  env.state.toolBusy = false;
7268
7437
  env.clearTransientNotice(answerTurn);
@@ -7286,24 +7455,36 @@ function createConversationController(env) {
7286
7455
  );
7287
7456
  env.askFocusAfterTurn();
7288
7457
  }
7458
+ var requestBody = JSON.stringify({
7459
+ text: payload.text,
7460
+ context: payload.context,
7461
+ label: payload.label,
7462
+ sessionId: env.panelOrCurrentSessionId(),
7463
+ question: sent,
7464
+ history,
7465
+ key: requestKey,
7466
+ historyStart: prepared.historyStart,
7467
+ contextState: prepared.contextState,
7468
+ materials: prepared.materials,
7469
+ initialExplanation,
7470
+ // 附件只带**引用**(图片/文件都已经在 host 的附件服务里),body 不会因为一张截图胖几 MB
7471
+ ...withAttachments.length > 0 ? { attachments: withAttachments } : {},
7472
+ // 网页输出要跟着当前深浅走:深色 Harness 里给一张白底页面会很刺眼
7473
+ ...env.state.webAnswer ? { webAnswer: true, theme: env.currentSurfaceTheme() } : {},
7474
+ ...env.state.modelChoice ? { provider: env.state.modelChoice.provider, model: env.state.modelChoice.model } : {},
7475
+ ...env.state.effort ? { effort: env.state.effort } : {},
7476
+ ...env.state.toolDigest ? { toolDigest: env.state.toolDigest } : {}
7477
+ });
7478
+ if (new TextEncoder().encode(requestBody).byteLength > CHAT_REQUEST_BYTES) {
7479
+ questionTurn.contextExcluded = true;
7480
+ answerTurn.contextExcluded = true;
7481
+ finish("\u672C\u8F6E\u6750\u6599\u8D85\u8FC7\u8BF7\u6C42\u5BB9\u91CF\uFF0C\u8BF7\u7F29\u5C0F\u63D0\u95EE\u6216\u5F15\u7528\u8303\u56F4\uFF1B\u539F\u6587\u4ECD\u4FDD\u7559\u5728\u5F53\u524D\u5386\u53F2\u4E2D");
7482
+ return;
7483
+ }
7289
7484
  fetch(API, {
7290
7485
  method: "POST",
7291
7486
  headers: { "content-type": "application/json" },
7292
- body: JSON.stringify({
7293
- text: payload.text,
7294
- context: payload.context,
7295
- label: payload.label,
7296
- sessionId: env.panelOrCurrentSessionId(),
7297
- question: sent,
7298
- history,
7299
- // 附件只带**引用**(图片/文件都已经在 host 的附件服务里),body 不会因为一张截图胖几 MB
7300
- ...withAttachments.length > 0 ? { attachments: withAttachments } : {},
7301
- // 网页输出要跟着当前深浅走:深色 Harness 里给一张白底页面会很刺眼
7302
- ...env.state.webAnswer ? { webAnswer: true, theme: env.currentSurfaceTheme() } : {},
7303
- ...env.state.modelChoice ? { provider: env.state.modelChoice.provider, model: env.state.modelChoice.model } : {},
7304
- ...env.state.effort ? { effort: env.state.effort } : {},
7305
- ...env.state.toolDigest ? { toolDigest: env.state.toolDigest } : {}
7306
- }),
7487
+ body: requestBody,
7307
7488
  signal: askController.signal
7308
7489
  }).then(function(response) {
7309
7490
  if (!response.ok) {
@@ -7329,7 +7510,18 @@ function createConversationController(env) {
7329
7510
  } catch (error) {
7330
7511
  continue;
7331
7512
  }
7332
- if (message.type === "delta") {
7513
+ if (message.type === "context-state") {
7514
+ if (!askController.signal.aborted && env.state.turns === requestTurns && env.state.cacheKey === requestKey) {
7515
+ env.state.contextState = readContextState(message.state);
7516
+ }
7517
+ } else if (message.type === "material-restored") {
7518
+ var material = message.attachment;
7519
+ if (!askController.signal.aborted && material && (material.kind === "image" || material.kind === "file") && material.ref && material.ref.attachmentId) {
7520
+ if (!questionTurn.attachments.some(function(a) {
7521
+ return a.kind === material.kind && a.ref.attachmentId === material.ref.attachmentId;
7522
+ })) questionTurn.attachments.push(material);
7523
+ }
7524
+ } else if (message.type === "delta") {
7333
7525
  env.clearTransientNotice(answerTurn);
7334
7526
  if (!answerTurn.text.trim()) answerTurn.asking = false;
7335
7527
  answerTurn.text += message.text;
@@ -7368,6 +7560,10 @@ function createConversationController(env) {
7368
7560
  }
7369
7561
  scheduleTurnsRender();
7370
7562
  } else if (message.type === "error") {
7563
+ if (message.code === "context-rejected") {
7564
+ questionTurn.contextExcluded = true;
7565
+ answerTurn.contextExcluded = true;
7566
+ }
7371
7567
  finish(message.message || "\u6A21\u578B\u8FD4\u56DE\u9519\u8BEF");
7372
7568
  }
7373
7569
  }
@@ -7525,16 +7721,15 @@ function createConversationController(env) {
7525
7721
  env.state.startedAt = Date.now();
7526
7722
  env.state.elapsed = 0;
7527
7723
  env.paint();
7528
- var ticker = setInterval(function() {
7724
+ var ticker = env.timerScope.interval(function() {
7529
7725
  env.paintPill();
7530
7726
  if (env.state.phase === "loading" || env.state.phase === "streaming") {
7531
7727
  env.setStatus(env.progressText());
7532
7728
  updateWaitClocks();
7533
7729
  }
7534
7730
  }, 200);
7535
- env.timers.push(ticker);
7536
7731
  function stopTicker() {
7537
- clearInterval(ticker);
7732
+ env.timerScope.clear(ticker);
7538
7733
  }
7539
7734
  function fail(message) {
7540
7735
  if (env.state.phase === "done") return;
@@ -9123,6 +9318,101 @@ function createVoiceController(env) {
9123
9318
  };
9124
9319
  }
9125
9320
 
9321
+ // src/client/memory.js
9322
+ function createResultCache(maxEntries = 20, maxBytes = 8 * 1024 * 1024) {
9323
+ const entries = /* @__PURE__ */ new Map();
9324
+ let textBytes = 0;
9325
+ let disposed = false;
9326
+ function remove(key) {
9327
+ const entry = entries.get(key);
9328
+ if (!entry) return;
9329
+ textBytes -= entry.bytes;
9330
+ entries.delete(key);
9331
+ }
9332
+ const cache = {
9333
+ get size() {
9334
+ return entries.size;
9335
+ },
9336
+ // Conservative UTF-16 size of serialized text, not a measurement of JS heap size.
9337
+ get textBytes() {
9338
+ return textBytes;
9339
+ },
9340
+ keys() {
9341
+ return entries.keys();
9342
+ },
9343
+ get(key) {
9344
+ const entry = entries.get(key);
9345
+ if (!entry) return void 0;
9346
+ entries.delete(key);
9347
+ entries.set(key, entry);
9348
+ return JSON.parse(entry.json);
9349
+ },
9350
+ set(key, value) {
9351
+ if (disposed) return cache;
9352
+ remove(key);
9353
+ let json;
9354
+ try {
9355
+ json = JSON.stringify(value);
9356
+ } catch {
9357
+ return cache;
9358
+ }
9359
+ if (typeof json !== "string") return cache;
9360
+ const bytes = (String(key).length + json.length) * 2;
9361
+ if (bytes > maxBytes || maxEntries <= 0) return cache;
9362
+ entries.set(key, { json, bytes });
9363
+ textBytes += bytes;
9364
+ while (entries.size > maxEntries || textBytes > maxBytes) remove(entries.keys().next().value);
9365
+ return cache;
9366
+ },
9367
+ clear() {
9368
+ entries.clear();
9369
+ textBytes = 0;
9370
+ },
9371
+ dispose() {
9372
+ disposed = true;
9373
+ cache.clear();
9374
+ }
9375
+ };
9376
+ return cache;
9377
+ }
9378
+ function createTimerScope() {
9379
+ const active = /* @__PURE__ */ new Set();
9380
+ let disposed = false;
9381
+ return {
9382
+ get size() {
9383
+ return active.size;
9384
+ },
9385
+ later(fn, ms) {
9386
+ if (disposed) return null;
9387
+ const id = setTimeout(() => {
9388
+ active.delete(id);
9389
+ if (!disposed) fn();
9390
+ }, ms);
9391
+ active.add(id);
9392
+ return id;
9393
+ },
9394
+ interval(fn, ms) {
9395
+ if (disposed) return null;
9396
+ const id = setInterval(fn, ms);
9397
+ active.add(id);
9398
+ return id;
9399
+ },
9400
+ clear(id) {
9401
+ clearTimeout(id);
9402
+ clearInterval(id);
9403
+ active.delete(id);
9404
+ },
9405
+ dispose() {
9406
+ disposed = true;
9407
+ active.forEach((id) => {
9408
+ clearTimeout(id);
9409
+ clearInterval(id);
9410
+ });
9411
+ active.clear();
9412
+ }
9413
+ };
9414
+ }
9415
+
9126
9416
  // src/client/index.js
9127
9417
  function apply(ctx) {
9128
9418
  var state = {
@@ -9235,7 +9525,8 @@ function apply(ctx) {
9235
9525
  /** 升格请求进行中。 */
9236
9526
  promoting: false
9237
9527
  };
9238
- var cache = /* @__PURE__ */ new Map();
9528
+ var cache = createResultCache();
9529
+ var timerScope = createTimerScope();
9239
9530
  var styleEl = el("style");
9240
9531
  styleEl.setAttribute("data-plugin", "@yfwu2020/dsh-selection-explain");
9241
9532
  styleEl.textContent = CSS;
@@ -9670,8 +9961,8 @@ function apply(ctx) {
9670
9961
  get frameTouchedAt() {
9671
9962
  return previewController.frameTouchedAt;
9672
9963
  },
9673
- get timers() {
9674
- return timers;
9964
+ get timerScope() {
9965
+ return timerScope;
9675
9966
  },
9676
9967
  get voice() {
9677
9968
  return voiceController.voice;
@@ -9696,6 +9987,9 @@ function apply(ctx) {
9696
9987
  }
9697
9988
  });
9698
9989
  var quotesController = createQuotesController({
9990
+ get foldForHistory() {
9991
+ return historyController.foldForHistory;
9992
+ },
9699
9993
  get state() {
9700
9994
  return state;
9701
9995
  },
@@ -9729,8 +10023,8 @@ function apply(ctx) {
9729
10023
  get currentSessionId() {
9730
10024
  return panelController.currentSessionId;
9731
10025
  },
9732
- get timers() {
9733
- return timers;
10026
+ get timerScope() {
10027
+ return timerScope;
9734
10028
  }
9735
10029
  });
9736
10030
  var previewController = createPreviewController({
@@ -9751,6 +10045,9 @@ function apply(ctx) {
9751
10045
  }
9752
10046
  });
9753
10047
  var panelController = createPanelController({
10048
+ get timerScope() {
10049
+ return timerScope;
10050
+ },
9754
10051
  get panelSessionId() {
9755
10052
  return panelSessionId;
9756
10053
  },
@@ -9787,6 +10084,12 @@ function apply(ctx) {
9787
10084
  get renderTurns() {
9788
10085
  return historyController.renderTurns;
9789
10086
  },
10087
+ get clearRenderedTurns() {
10088
+ return historyController.clearRenderedTurns;
10089
+ },
10090
+ get clearWaiting() {
10091
+ return conversationController.clearWaiting;
10092
+ },
9790
10093
  get sendAsk() {
9791
10094
  return conversationController.sendAsk;
9792
10095
  },
@@ -10046,6 +10349,9 @@ function apply(ctx) {
10046
10349
  }
10047
10350
  });
10048
10351
  var historyController = createHistoryController({
10352
+ set panelSessionId(value) {
10353
+ panelSessionId = value;
10354
+ },
10049
10355
  get sentTextOf() {
10050
10356
  return quotesController.sentTextOf;
10051
10357
  },
@@ -10076,8 +10382,8 @@ function apply(ctx) {
10076
10382
  get paint() {
10077
10383
  return panelController.paint;
10078
10384
  },
10079
- get timers() {
10080
- return timers;
10385
+ get timerScope() {
10386
+ return timerScope;
10081
10387
  },
10082
10388
  get panelOpen() {
10083
10389
  return panelOpen;
@@ -10207,14 +10513,8 @@ function apply(ctx) {
10207
10513
  get paintPill() {
10208
10514
  return pillController.paintPill;
10209
10515
  },
10210
- get timers() {
10211
- return timers;
10212
- },
10213
- get FOLD_HISTORY_KEEP_LATEST() {
10214
- return historyController.FOLD_HISTORY_KEEP_LATEST;
10215
- },
10216
- get historyTextOf() {
10217
- return historyController.historyTextOf;
10516
+ get timerScope() {
10517
+ return timerScope;
10218
10518
  },
10219
10519
  get clearTransientNotice() {
10220
10520
  return historyController.clearTransientNotice;
@@ -10488,7 +10788,6 @@ function apply(ctx) {
10488
10788
  var STREAM_RENDER_MS = 90;
10489
10789
  var turnsRenderPending = false;
10490
10790
  var lastTurnsRenderAt = 0;
10491
- var timers = [];
10492
10791
  var PILL_POLL_MIN = 2e3;
10493
10792
  var PILL_POLL_MAX = 6e4;
10494
10793
  var pillPollDelay = PILL_POLL_MIN;
@@ -10508,9 +10807,7 @@ function apply(ctx) {
10508
10807
  var pillObserver = null;
10509
10808
  var waitList = [];
10510
10809
  function later(fn, ms) {
10511
- var id = setTimeout(fn, ms);
10512
- timers.push(id);
10513
- return id;
10810
+ return timerScope.later(fn, ms);
10514
10811
  }
10515
10812
  var offBridgeMessage = listen(window, "message", function(event) {
10516
10813
  var data = event && event.data;
@@ -11080,6 +11377,14 @@ function apply(ctx) {
11080
11377
  turns: function() {
11081
11378
  return state.turns.slice();
11082
11379
  },
11380
+ memory: function() {
11381
+ return {
11382
+ cacheEntries: cache.size,
11383
+ cacheTextBytes: cache.textBytes,
11384
+ timers: timerScope.size,
11385
+ previewFrames: Object.keys(previewFrames).length
11386
+ };
11387
+ },
11083
11388
  /** 自检用:当前待发送的引用(引用区里那几张卡片)。 */
11084
11389
  quotes: function() {
11085
11390
  return state.quotes.map(function(quote2) {
@@ -11424,6 +11729,7 @@ function apply(ctx) {
11424
11729
  state.selection = null;
11425
11730
  state.bridgeActive = false;
11426
11731
  state.parts = { translation: "", detail: "" };
11732
+ state.contextState = null;
11427
11733
  state.turns = [];
11428
11734
  quotesController.clearQuotes();
11429
11735
  state.raw = "";
@@ -11451,6 +11757,13 @@ function apply(ctx) {
11451
11757
  console.log("[dsh-selection-explain] \u5212\u8BCD\u89E3\u8BFB\u5DF2\u5C31\u7EEA\uFF08\u9009\u4E2D\u6587\u5B57\u8BD5\u8BD5\uFF09");
11452
11758
  }
11453
11759
  return function() {
11760
+ panelOpen = false;
11761
+ timerScope.dispose();
11762
+ cache.dispose();
11763
+ voiceCardController.closeVCard(false);
11764
+ attachController.clear();
11765
+ panelController.releaseRenderedContent();
11766
+ if (state.askRequest) state.askRequest.abort();
11454
11767
  offPopEnd();
11455
11768
  offQuotePopEnd();
11456
11769
  offMouseUp();
@@ -11538,13 +11851,17 @@ function apply(ctx) {
11538
11851
  }
11539
11852
  });
11540
11853
  previewController.bridged.clear();
11854
+ previewController.frameTouchedAt.clear();
11541
11855
  state.turns = [];
11542
11856
  state.quotes = [];
11857
+ state.selection = null;
11858
+ state.quoteSelection = null;
11859
+ state.payload = null;
11860
+ state.parts = { translation: "", detail: "" };
11861
+ state.raw = "";
11543
11862
  if (state.request) state.request.abort();
11544
- for (var i = 0; i < timers.length; i++) clearTimeout(timers[i]);
11545
11863
  if (pillIdleId) clearTimeout(pillIdleId);
11546
11864
  if (pillController.pillStarTimer) clearTimeout(pillController.pillStarTimer);
11547
- cache.clear();
11548
11865
  if (layer.parentNode) layer.parentNode.removeChild(layer);
11549
11866
  if (button.parentNode) button.parentNode.removeChild(button);
11550
11867
  if (quoteButton.parentNode) quoteButton.parentNode.removeChild(quoteButton);