dsh-more 0.0.1 → 0.0.3

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.
Files changed (29) hide show
  1. package/CONTRIBUTING.md +6 -6
  2. package/README.md +21 -9
  3. package/README.zh-CN.md +21 -9
  4. package/dist/client.js +1232 -174
  5. package/dist/index.js +451 -25
  6. package/dist/types/generated/host-settings-schema.d.ts +6 -0
  7. package/dist/types/generated/patch-catalog.d.ts +24 -0
  8. package/dist/types/patches/conversation-markdown-export/client/filename.d.ts +1 -0
  9. package/dist/types/patches/conversation-markdown-export/client/index.d.ts +2 -0
  10. package/dist/types/patches/conversation-markdown-export/host/index.d.ts +2 -0
  11. package/dist/types/patches/conversation-markdown-export/host/transcript.d.ts +9 -0
  12. package/dist/types/patches/conversation-markdown-export/shared.d.ts +5 -0
  13. package/dist/types/patches/conversation-quick-navigation/client/index.d.ts +22 -0
  14. package/dist/types/patches/conversation-quick-navigation/client/navigation.d.ts +26 -0
  15. package/dist/types/patches/conversation-quick-navigation/host/index.d.ts +2 -0
  16. package/dist/types/patches/conversation-quick-navigation/shared.d.ts +1 -0
  17. package/dist/types/patches/model-reasoning-efforts/client/index.d.ts +39 -0
  18. package/dist/types/patches/model-reasoning-efforts/host/index.d.ts +2 -0
  19. package/dist/types/patches/model-reasoning-efforts/host/validate.d.ts +13 -0
  20. package/dist/types/patches/model-reasoning-efforts/shared.d.ts +51 -0
  21. package/dist/types/patches/session-delete/host/session-deletion.d.ts +0 -1
  22. package/dist/types/platform/dsh/client/api.d.ts +2 -0
  23. package/dist/types/platform/dsh/client/message-targets.d.ts +13 -1
  24. package/dist/types/platform/dsh/client/styles.d.ts +1 -1
  25. package/dist/types/platform/dsh/host/continuation.d.ts +4 -0
  26. package/dist/types/platform/dsh/host/error.d.ts +1 -1
  27. package/dist/types/platform/dsh/host/wire.d.ts +7 -0
  28. package/dsh.plugin.json +12 -2
  29. package/package.json +7 -4
package/dist/client.js CHANGED
@@ -25,19 +25,25 @@ __export(index_exports, {
25
25
  inject: () => inject
26
26
  });
27
27
  module.exports = __toCommonJS(index_exports);
28
- var import_react6 = require("react");
29
- var import_react_dom = require("react-dom");
28
+ var import_react9 = require("react");
29
+ var import_react_dom2 = require("react-dom");
30
30
 
31
31
  // src/generated/patch-catalog.ts
32
32
  var PATCH_CATALOG = [
33
33
  { "id": "message-edit", "name": "\u7F16\u8F91\u5E76\u91CD\u65B0\u5F00\u59CB", "description": "\u7F16\u8F91\u7528\u6237\u6D88\u606F\u5E76\u4E22\u5F03\u8BE5\u6D88\u606F\u4E4B\u540E\u7684\u5386\u53F2", "defaultEnabled": true, "order": 100, "clientKind": "message-action" },
34
34
  { "id": "message-delete", "name": "\u5355\u6761\u6D88\u606F\u5220\u9664", "description": "\u5220\u9664\u6307\u5B9A\u6D88\u606F\u5E76\u4FDD\u7559\u524D\u540E\u5386\u53F2\u8BB0\u5F55", "defaultEnabled": true, "order": 200, "clientKind": "message-action" },
35
- { "id": "session-delete", "name": "\u6C38\u4E45\u5220\u9664\u4F1A\u8BDD", "description": "\u505C\u6B62\u76EE\u6807\u4F1A\u8BDD\u5E76\u4ECE\u78C1\u76D8\u6C38\u4E45\u5220\u9664\uFF0C\u4E0D\u5F71\u54CD\u539F\u751F\u5F52\u6863", "defaultEnabled": true, "order": 300, "clientKind": "standalone" }
35
+ { "id": "session-delete", "name": "\u6C38\u4E45\u5220\u9664\u4F1A\u8BDD", "description": "\u505C\u6B62\u76EE\u6807\u4F1A\u8BDD\u5E76\u4ECE\u78C1\u76D8\u6C38\u4E45\u5220\u9664\uFF0C\u4E0D\u5F71\u54CD\u539F\u751F\u5F52\u6863", "defaultEnabled": true, "order": 300, "clientKind": "standalone" },
36
+ { "id": "conversation-markdown-export", "name": "\u5BFC\u51FA\u5B8C\u6574\u804A\u5929\u8BB0\u5F55", "description": "\u5C06\u5F53\u524D\u4F1A\u8BDD\u7684\u5B8C\u6574\u804A\u5929\u8BB0\u5F55\u4E0B\u8F7D\u4E3A Markdown \u6587\u4EF6", "defaultEnabled": true, "order": 400, "clientKind": "standalone" },
37
+ { "id": "model-reasoning-efforts", "name": "\u6A21\u578B\u601D\u8003\u5F3A\u5EA6", "description": "\u5728\u6A21\u578B\u914D\u7F6E\u754C\u9762\u4E3A\u6BCF\u4E2A\u6A21\u578B\u914D\u7F6E\u652F\u6301\u7684\u601D\u8003\u5F3A\u5EA6\u5E76\u5199\u5165 settings.yaml \u7684 reasoningEfforts", "defaultEnabled": true, "order": 500, "clientKind": "standalone" },
38
+ { "id": "conversation-quick-navigation", "name": "\u5BF9\u8BDD\u5FEB\u901F\u5BFC\u822A", "description": "\u81EA\u52A8\u52A0\u8F7D\u5B8C\u6574\u8BB0\u5F55\uFF0C\u5E76\u7528\u5E38\u9A7B\u76EE\u5F55\u5FEB\u901F\u5B9A\u4F4D\u5F53\u524D\u4F1A\u8BDD\u4E2D\u7684\u6BCF\u4E00\u8F6E\u63D0\u95EE", "defaultEnabled": true, "order": 600, "clientKind": "standalone" }
36
39
  ];
37
40
  var DEFAULT_PATCH_SETTINGS = {
38
41
  "message-edit": true,
39
42
  "message-delete": true,
40
- "session-delete": true
43
+ "session-delete": true,
44
+ "conversation-markdown-export": true,
45
+ "model-reasoning-efforts": true,
46
+ "conversation-quick-navigation": true
41
47
  };
42
48
  function decodePatchSettings(value) {
43
49
  if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
@@ -68,8 +74,9 @@ var DshMoreApiError = class extends Error {
68
74
  this.code = code;
69
75
  this.name = "DshMoreApiError";
70
76
  }
77
+ code;
71
78
  };
72
- async function callPatchApi(patchId, action, payload) {
79
+ async function requestPatch(patchId, action, payload) {
73
80
  let response;
74
81
  try {
75
82
  response = await fetch(`${PLUGIN_API_PREFIX}/${encodeURIComponent(patchId)}/${encodeURIComponent(action)}`, {
@@ -83,12 +90,28 @@ async function callPatchApi(patchId, action, payload) {
83
90
  } catch (error) {
84
91
  throw new DshMoreApiError("network", error instanceof Error ? error.message : String(error));
85
92
  }
93
+ return response;
94
+ }
95
+ async function responseError(response) {
86
96
  const body = await response.json().catch(() => null);
87
- if (!response.ok || body?.ok !== true || body.value === void 0) {
88
- throw new DshMoreApiError(body?.error?.code ?? "http", body?.error?.message ?? `HTTP ${String(response.status)}`);
89
- }
97
+ return new DshMoreApiError(body?.error?.code ?? "http", body?.error?.message ?? `HTTP ${String(response.status)}`);
98
+ }
99
+ async function callPatchApi(patchId, action, payload) {
100
+ const response = await requestPatch(patchId, action, payload);
101
+ const body = await response.json().catch(() => null);
102
+ if (!response.ok || body?.ok !== true || body.value === void 0) throw await responseErrorFromBody(response, body);
90
103
  return body.value;
91
104
  }
105
+ function responseErrorFromBody(response, body) {
106
+ return new DshMoreApiError(body?.error?.code ?? "http", body?.error?.message ?? `HTTP ${String(response.status)}`);
107
+ }
108
+ async function callPatchBlobApi(patchId, action, payload) {
109
+ const response = await requestPatch(patchId, action, payload);
110
+ if (!response.ok) throw await responseError(response);
111
+ const contentType = response.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase();
112
+ if (contentType !== "text/markdown") throw new DshMoreApiError("unexpected-response", "\u5BFC\u51FA\u63A5\u53E3\u8FD4\u56DE\u4E86\u672A\u77E5\u5185\u5BB9\u3002");
113
+ return response.blob();
114
+ }
92
115
  function apiErrorText(error) {
93
116
  if (error instanceof DshMoreApiError || error instanceof Error) return error.message;
94
117
  return String(error);
@@ -173,10 +196,16 @@ async function settleSessionRemoval(sessions, workspaces, sessionId) {
173
196
  function adjacentVisibleSession(sessions, workspaces, deletingSessionId) {
174
197
  if (sessions.current !== deletingSessionId) return void 0;
175
198
  const archived = new Set(workspaces.archivedSessionIds);
176
- const visible = sessions.ids.filter((id) => id !== deletingSessionId && !archived.has(id));
177
- if (visible.length === 0) return void 0;
178
199
  const deletingIndex = sessions.ids.indexOf(deletingSessionId);
179
- return visible.find((id) => sessions.ids.indexOf(id) > deletingIndex) ?? visible.at(-1);
200
+ for (let index = deletingIndex + 1; index < sessions.ids.length; index += 1) {
201
+ const id = sessions.ids[index];
202
+ if (id !== void 0 && id !== deletingSessionId && !archived.has(id)) return id;
203
+ }
204
+ for (let index = deletingIndex - 1; index >= 0; index -= 1) {
205
+ const id = sessions.ids[index];
206
+ if (id !== void 0 && id !== deletingSessionId && !archived.has(id)) return id;
207
+ }
208
+ return void 0;
180
209
  }
181
210
 
182
211
  // src/patches/message-edit/shared.ts
@@ -191,41 +220,28 @@ var api = {
191
220
  function useMessageActions(props, enabled) {
192
221
  const [target, setTarget] = (0, import_react.useState)(null);
193
222
  const [text, setText] = (0, import_react.useState)("");
194
- const [preview, setPreview] = (0, import_react.useState)(null);
195
223
  const [busy, setBusy] = (0, import_react.useState)(false);
196
224
  const [error, setError] = (0, import_react.useState)(null);
197
225
  (0, import_react.useEffect)(() => {
198
226
  if (enabled) return;
199
227
  setTarget(null);
200
- setPreview(null);
201
228
  setError(null);
202
229
  }, [enabled]);
203
230
  const open = (next) => {
204
231
  setTarget(next);
205
232
  setText(next.text);
206
- setPreview(null);
207
233
  setError(null);
208
234
  };
209
- const previewChange = async () => {
235
+ const applyChange = async () => {
210
236
  if (target === null) return;
211
237
  setBusy(true);
212
238
  setError(null);
213
- try {
214
- setPreview(await api.preview({ sessionId: props.sessionId, targetSeq: target.seq, text }));
215
- } catch (caught) {
216
- setError(apiErrorText(caught));
217
- } finally {
218
- setBusy(false);
219
- }
220
- };
221
- const commit = async () => {
222
- if (target === null || preview === null) return;
223
- setBusy(true);
224
- setError(null);
225
239
  const sessions = props.ctx.sessions;
226
- const continuationSessionId = preview.continuationSessionId;
227
- const handoff = followSessionHandoff(sessions, continuationSessionId);
240
+ let handoff;
228
241
  try {
242
+ const preview = await api.preview({ sessionId: props.sessionId, targetSeq: target.seq, text });
243
+ const continuationSessionId = preview.continuationSessionId;
244
+ handoff = followSessionHandoff(sessions, continuationSessionId);
229
245
  const result = await api.commit({
230
246
  sessionId: props.sessionId,
231
247
  targetSeq: target.seq,
@@ -236,13 +252,12 @@ function useMessageActions(props, enabled) {
236
252
  await handoff.finish(result.sessionId);
237
253
  setTarget(null);
238
254
  } catch (caught) {
239
- handoff.cancel();
255
+ handoff?.cancel();
240
256
  const sourceId = props.sessionId;
241
257
  const sessionList = sessions.list.getSnapshot();
242
258
  const sourceAvailable = sessionList.byId[sourceId] !== void 0;
243
259
  const sourceArchived = props.ctx.workspaces.list.getSnapshot().archivedSessionIds.includes(sourceId);
244
260
  if (sourceAvailable && !sourceArchived && sessionList.current !== sourceId) openSessionWithoutGap(sessions, sourceId);
245
- setPreview(null);
246
261
  setError(apiErrorText(caught));
247
262
  } finally {
248
263
  setBusy(false);
@@ -262,12 +277,9 @@ function useMessageActions(props, enabled) {
262
277
  closeLabel: "\u5173\u95ED",
263
278
  className: "dshmore-edit-modal",
264
279
  description: "\u4FEE\u6539\u540E\u4F1A\u521B\u5EFA\u5E72\u51C0\u7684\u524D\u7F00\u4F1A\u8BDD\uFF0C\u8FD9\u6761\u6D88\u606F\u4E4B\u540E\u7684\u5185\u5BB9\u4E0D\u4F1A\u4FDD\u7559\u3002",
265
- footer: preview === null ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
280
+ footer: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
266
281
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "ghost", disabled: busy, onClick: () => setTarget(null), children: "\u53D6\u6D88" }),
267
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "primary", disabled: busy || text.trim() === "", onClick: () => void previewChange(), children: busy ? "\u6B63\u5728\u68C0\u67E5\u2026" : "\u9884\u89C8\u4FEE\u6539" })
268
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
269
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "ghost", disabled: busy, onClick: () => setPreview(null), children: "\u8FD4\u56DE\u4FEE\u6539" }),
270
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "primary", disabled: busy, onClick: () => void commit(), children: busy ? "\u6B63\u5728\u91CD\u5EFA\u2026" : "\u786E\u8BA4\u7F16\u8F91\u5E76\u4E22\u5F03\u540E\u7EED" })
282
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "primary", disabled: busy || text.trim() === "", onClick: () => void applyChange(), children: busy ? "\u6B63\u5728\u4FEE\u6539\u2026" : "\u4FEE\u6539\u5E76\u91CD\u65B0\u5F00\u59CB" })
271
283
  ] }),
272
284
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dshmore-dialog-body", children: [
273
285
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -275,23 +287,15 @@ function useMessageActions(props, enabled) {
275
287
  {
276
288
  className: "dshmore-editor",
277
289
  value: text,
278
- disabled: busy || preview !== null,
290
+ disabled: busy,
279
291
  onChange: (event) => {
280
292
  setText(event.currentTarget.value);
281
- setPreview(null);
282
293
  setError(null);
283
294
  },
284
295
  rows: 7,
285
296
  autoFocus: true
286
297
  }
287
298
  ),
288
- preview !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dshmore-warning", children: [
289
- "\u5C06\u4ECE\u7B2C ",
290
- preview.turn,
291
- " \u8F6E\u4E4B\u524D\u91CD\u5EFA\u4F1A\u8BDD\uFF0C\u5E76\u4E22\u5F03\u5F53\u524D\u8F6E\u53CA\u4E4B\u540E\u5171 ",
292
- preview.laterTurnCount,
293
- " \u8F6E\u5185\u5BB9\u3002"
294
- ] }),
295
299
  error !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dshmore-error", children: error })
296
300
  ] })
297
301
  }
@@ -403,6 +407,7 @@ var import_dsh_client_ui_primitives3 = require("@deepseek-ai/dsh-client-ui-primi
403
407
 
404
408
  // src/platform/dsh/client/styles.ts
405
409
  var styles = `
410
+ [data-dshmore-hidden] { display: none !important; }
406
411
  .dshmore-inline-actions { display: inline-flex; align-items: center; gap: 10px; }
407
412
  .dshmore-inline-button { width: 28px; height: 28px; color: var(--dsw-alias-label-tertiary); cursor: pointer; background: transparent; border: 0; border-radius: 28px; display: inline-flex; align-items: center; justify-content: center; padding: 6px; }
408
413
  .dshmore-inline-button:hover { color: var(--dsw-alias-label-secondary); background: var(--dsw-alias-interactive-bg-hover); }
@@ -460,38 +465,71 @@ function replaceExactText(root, before, after) {
460
465
  }
461
466
  function installSessionDeleteMenuItems(onDelete) {
462
467
  let frame = null;
463
- const sync = () => {
464
- for (const archiveButton of document.querySelectorAll('button[role="menuitem"]')) {
465
- const text = archiveButton.textContent?.trim();
466
- const deleteLabel = text === "\u5F52\u6863\u4F1A\u8BDD" ? "\u6C38\u4E45\u5220\u9664\u4F1A\u8BDD" : text === "Archive session" ? "Permanently delete session" : void 0;
467
- if (deleteLabel === void 0 || archiveButton.hasAttribute(DELETE_SOURCE_ATTRIBUTE)) continue;
468
- const wrapper = archiveButton.parentElement;
469
- if (wrapper === null) continue;
470
- const clone = wrapper.cloneNode(true);
471
- const deleteButton = clone.querySelector('button[role="menuitem"]');
472
- if (deleteButton === null) continue;
473
- archiveButton.setAttribute(DELETE_SOURCE_ATTRIBUTE, "");
474
- clone.setAttribute(DELETE_MENU_ATTRIBUTE, "");
475
- replaceExactText(deleteButton, text, deleteLabel);
476
- deleteButton.setAttribute("aria-label", deleteLabel);
477
- deleteButton.classList.add("dshmore-session-delete-menu-item");
478
- deleteButton.addEventListener("click", (event) => {
479
- event.preventDefault();
480
- event.stopImmediatePropagation();
481
- onDelete(archiveButton);
482
- });
483
- wrapper.after(clone);
468
+ const deleteItems = /* @__PURE__ */ new Map();
469
+ const pendingButtons = /* @__PURE__ */ new Set();
470
+ const syncButton = (archiveButton) => {
471
+ if (!archiveButton.isConnected) return;
472
+ const text = archiveButton.textContent?.trim();
473
+ const deleteLabel = text === "\u5F52\u6863\u4F1A\u8BDD" ? "\u6C38\u4E45\u5220\u9664\u4F1A\u8BDD" : text === "Archive session" ? "Permanently delete session" : void 0;
474
+ if (deleteLabel === void 0) return;
475
+ const wrapper = archiveButton.parentElement;
476
+ if (wrapper === null) return;
477
+ if (archiveButton.hasAttribute(DELETE_SOURCE_ATTRIBUTE)) {
478
+ if (deleteItems.get(archiveButton)?.isConnected === true) return;
479
+ archiveButton.removeAttribute(DELETE_SOURCE_ATTRIBUTE);
480
+ }
481
+ const clone = wrapper.cloneNode(true);
482
+ const deleteButton = clone.querySelector('button[role="menuitem"]');
483
+ if (deleteButton === null) return;
484
+ archiveButton.setAttribute(DELETE_SOURCE_ATTRIBUTE, "");
485
+ clone.setAttribute(DELETE_MENU_ATTRIBUTE, "");
486
+ deleteItems.set(archiveButton, clone);
487
+ replaceExactText(deleteButton, text, deleteLabel);
488
+ deleteButton.setAttribute("aria-label", deleteLabel);
489
+ deleteButton.classList.add("dshmore-session-delete-menu-item");
490
+ deleteButton.addEventListener("click", (event) => {
491
+ event.preventDefault();
492
+ event.stopImmediatePropagation();
493
+ onDelete(archiveButton);
494
+ });
495
+ wrapper.after(clone);
496
+ };
497
+ const collectButtons = (node) => {
498
+ const element = node instanceof Element ? node : node.parentElement;
499
+ if (element === null) return;
500
+ const ancestor = element.closest('button[role="menuitem"]');
501
+ if (ancestor !== null) pendingButtons.add(ancestor);
502
+ if (element.matches('button[role="menuitem"]')) pendingButtons.add(element);
503
+ element.querySelectorAll('button[role="menuitem"]').forEach((button) => pendingButtons.add(button));
504
+ };
505
+ const flush = () => {
506
+ frame = null;
507
+ for (const [archiveButton, deleteItem] of deleteItems) {
508
+ if (!archiveButton.isConnected) {
509
+ deleteItems.delete(archiveButton);
510
+ } else if (!deleteItem.isConnected) {
511
+ archiveButton.removeAttribute(DELETE_SOURCE_ATTRIBUTE);
512
+ deleteItems.delete(archiveButton);
513
+ pendingButtons.add(archiveButton);
514
+ }
484
515
  }
516
+ for (const button of pendingButtons) syncButton(button);
517
+ pendingButtons.clear();
485
518
  };
486
519
  const schedule = () => {
487
520
  if (frame !== null) return;
488
- frame = window.requestAnimationFrame(() => {
489
- frame = null;
490
- sync();
491
- });
521
+ frame = window.requestAnimationFrame(flush);
492
522
  };
493
- sync();
494
- const observer = new MutationObserver(schedule);
523
+ document.querySelectorAll('button[role="menuitem"]').forEach((button) => pendingButtons.add(button));
524
+ flush();
525
+ const observer = new MutationObserver((records) => {
526
+ let removedKnownItem = false;
527
+ for (const record of records) {
528
+ record.addedNodes.forEach(collectButtons);
529
+ if (record.removedNodes.length > 0 && deleteItems.size > 0) removedKnownItem = true;
530
+ }
531
+ if (pendingButtons.size > 0 || removedKnownItem) schedule();
532
+ });
495
533
  observer.observe(document.body, { childList: true, subtree: true });
496
534
  return () => {
497
535
  observer.disconnect();
@@ -507,12 +545,6 @@ function installSessionDeleteMenuItems(onDelete) {
507
545
  function SessionDeleteController(props) {
508
546
  const settings = (0, import_react3.useSyncExternalStore)(props.activation.subscribe, props.activation.getSnapshot);
509
547
  const enabled = settings[SESSION_DELETE_PATCH_ID];
510
- const sessionList = props.useSessions((state) => state);
511
- const sessionListRef = (0, import_react3.useRef)(sessionList);
512
- sessionListRef.current = sessionList;
513
- const workspaceList = props.useWorkspaces((state) => state);
514
- const workspaceListRef = (0, import_react3.useRef)(workspaceList);
515
- workspaceListRef.current = workspaceList;
516
548
  const [pending, setPending] = (0, import_react3.useState)(null);
517
549
  const pendingRef = (0, import_react3.useRef)(null);
518
550
  const deleteIntentRef = (0, import_react3.useRef)(false);
@@ -521,13 +553,18 @@ function SessionDeleteController(props) {
521
553
  (0, import_react3.useEffect)(() => {
522
554
  if (!enabled) return;
523
555
  const workspaces = props.ctx.workspaces;
556
+ const sessions = props.ctx.sessions;
524
557
  const mutable = workspaces;
525
558
  const original = mutable.archiveSession;
526
559
  mutable.archiveSession = (sessionId) => {
527
560
  if (!deleteIntentRef.current) return original.call(mutable, sessionId);
528
561
  deleteIntentRef.current = false;
529
562
  return new Promise((resolve) => {
530
- const request = { sessionId, resolve };
563
+ const request = {
564
+ sessionId,
565
+ title: sessions.list.getSnapshot().byId[sessionId]?.displayTitle ?? String(sessionId),
566
+ resolve
567
+ };
531
568
  pendingRef.current?.resolve();
532
569
  pendingRef.current = request;
533
570
  setError(null);
@@ -563,8 +600,8 @@ function SessionDeleteController(props) {
563
600
  const sessions = props.ctx.sessions;
564
601
  const workspaces = props.ctx.workspaces;
565
602
  const fallbackSessionId = adjacentVisibleSession(
566
- sessionListRef.current,
567
- workspaceListRef.current,
603
+ sessions.list.getSnapshot(),
604
+ workspaces.list.getSnapshot(),
568
605
  pending.sessionId
569
606
  );
570
607
  if (fallbackSessionId !== void 0) openSessionWithoutGap(sessions, fallbackSessionId);
@@ -583,7 +620,7 @@ function SessionDeleteController(props) {
583
620
  setBusy(false);
584
621
  }
585
622
  };
586
- const title = pending === null ? "" : sessionListRef.current.byId[pending.sessionId]?.displayTitle ?? pending.sessionId;
623
+ const title = pending?.title ?? "";
587
624
  if (!enabled) return null;
588
625
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
589
626
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("style", { children: styles + sessionDeleteStyles }),
@@ -623,15 +660,929 @@ var clientPatch = {
623
660
  }
624
661
  };
625
662
 
663
+ // src/patches/conversation-markdown-export/client/index.tsx
664
+ var import_react4 = require("react");
665
+ var import_dsh_client_ui_primitives4 = require("@deepseek-ai/dsh-client-ui-primitives");
666
+
667
+ // src/patches/conversation-markdown-export/shared.ts
668
+ var CONVERSATION_MARKDOWN_EXPORT_PATCH_ID = "conversation-markdown-export";
669
+
670
+ // src/patches/conversation-markdown-export/client/filename.ts
671
+ function safeStem(value) {
672
+ return [...value.normalize("NFKC").replace(/[\u0000-\u001f\u007f/\\?%*:|"<>]+/g, "-").replace(/\s+/g, " ").replace(/^[.\s-]+|[.\s-]+$/g, "")].slice(0, 80).join("").trim();
673
+ }
674
+ function markdownFilename(title, sessionId) {
675
+ const fallback = safeStem(`dsh-conversation-${sessionId}`) || "dsh-conversation";
676
+ return `${safeStem(title) || fallback}.md`;
677
+ }
678
+
679
+ // src/patches/conversation-markdown-export/client/index.tsx
680
+ var import_jsx_runtime4 = require("react/jsx-runtime");
681
+ var exportStyles = `
682
+ .dshmore-markdown-export-button { box-sizing: border-box; min-width: 128px; height: 32px; color: var(--dsw-alias-label-primary); cursor: pointer; background: transparent; border: 1px solid var(--dsw-alias-border-l2); border-radius: 18px; display: inline-flex; align-items: center; justify-content: center; gap: 5px; padding: 6px 12px; font-family: var(--dsw-font-family); font-size: 13px; line-height: 20px; }
683
+ .dshmore-markdown-export-button:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }
684
+ .dshmore-markdown-export-button:disabled { color: var(--dsw-alias-label-dimmed); cursor: wait; }
685
+ .dshmore-markdown-export-button > span, .dshmore-markdown-export-button > svg { flex: none; }
686
+ `;
687
+ function saveMarkdown(filename, markdown) {
688
+ const url = URL.createObjectURL(new Blob(["\uFEFF", markdown], { type: "text/markdown;charset=utf-8" }));
689
+ const anchor = document.createElement("a");
690
+ anchor.href = url;
691
+ anchor.download = filename;
692
+ anchor.style.display = "none";
693
+ document.body.appendChild(anchor);
694
+ anchor.click();
695
+ anchor.remove();
696
+ window.setTimeout(() => {
697
+ URL.revokeObjectURL(url);
698
+ }, 0);
699
+ }
700
+ function ConversationMarkdownExportButton(props) {
701
+ const settings = (0, import_react4.useSyncExternalStore)(props.activation.subscribe, props.activation.getSnapshot);
702
+ const enabled = settings[CONVERSATION_MARKDOWN_EXPORT_PATCH_ID];
703
+ const title = props.useSessions((state) => state.byId[props.sessionId]?.displayTitle ?? String(props.sessionId));
704
+ const [busy, setBusy] = (0, import_react4.useState)(false);
705
+ const [error, setError] = (0, import_react4.useState)(null);
706
+ const generation = (0, import_react4.useRef)(0);
707
+ const activeSession = (0, import_react4.useRef)(null);
708
+ activeSession.current = enabled ? String(props.sessionId) : null;
709
+ (0, import_react4.useEffect)(() => {
710
+ generation.current += 1;
711
+ setBusy(false);
712
+ setError(null);
713
+ }, [enabled, props.sessionId]);
714
+ const download = async () => {
715
+ const requestSessionId = String(props.sessionId);
716
+ const requestGeneration = ++generation.current;
717
+ setBusy(true);
718
+ setError(null);
719
+ try {
720
+ const markdown = await callPatchBlobApi(
721
+ CONVERSATION_MARKDOWN_EXPORT_PATCH_ID,
722
+ "render",
723
+ { sessionId: props.sessionId, title }
724
+ );
725
+ if (generation.current !== requestGeneration || activeSession.current !== requestSessionId) return;
726
+ saveMarkdown(markdownFilename(title, String(props.sessionId)), markdown);
727
+ } catch (caught) {
728
+ if (generation.current === requestGeneration && activeSession.current === requestSessionId) setError(apiErrorText(caught));
729
+ } finally {
730
+ if (generation.current === requestGeneration && activeSession.current === requestSessionId) setBusy(false);
731
+ }
732
+ };
733
+ if (!enabled) return null;
734
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
735
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("style", { children: exportStyles }),
736
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
737
+ "button",
738
+ {
739
+ type: "button",
740
+ className: "dshmore-markdown-export-button",
741
+ disabled: busy,
742
+ "aria-busy": busy,
743
+ "aria-label": "\u5BFC\u51FA\u5B8C\u6574\u804A\u5929\u8BB0\u5F55\u4E3A Markdown",
744
+ onClick: () => {
745
+ void download();
746
+ },
747
+ children: [
748
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: busy ? "\u6B63\u5728\u5BFC\u51FA\u2026" : "\u5BFC\u51FA Markdown" }),
749
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_dsh_client_ui_primitives4.IconDownloadOutline16, { size: 12 })
750
+ ]
751
+ }
752
+ ),
753
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
754
+ import_dsh_client_ui_primitives4.Modal,
755
+ {
756
+ open: error !== null,
757
+ onClose: () => setError(null),
758
+ title: "\u65E0\u6CD5\u5BFC\u51FA\u804A\u5929\u8BB0\u5F55",
759
+ closeLabel: "\u5173\u95ED",
760
+ description: error ?? "",
761
+ footer: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_dsh_client_ui_primitives4.Button, { variant: "primary", onClick: () => setError(null), children: "\u5173\u95ED" })
762
+ }
763
+ )
764
+ ] });
765
+ }
766
+ var clientPatch2 = {
767
+ id: CONVERSATION_MARKDOWN_EXPORT_PATCH_ID,
768
+ install: (ctx, activation) => {
769
+ ctx.slots.inject("conversation.session.header.utilities", () => ctx.slots.register({
770
+ name: "conversation.session.header.utilities",
771
+ id: `${PLUGIN_NAME}-${CONVERSATION_MARKDOWN_EXPORT_PATCH_ID}`,
772
+ order: 100,
773
+ registrant: PLUGIN_NAME
774
+ }, (props) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ConversationMarkdownExportButton, { ...props, activation })));
775
+ }
776
+ };
777
+
778
+ // src/patches/model-reasoning-efforts/client/index.tsx
779
+ var import_react5 = require("react");
780
+ var import_dsh_client_ui_primitives5 = require("@deepseek-ai/dsh-client-ui-primitives");
781
+
782
+ // src/patches/model-reasoning-efforts/shared.ts
783
+ var MODEL_REASONING_EFFORTS_PATCH_ID = "model-reasoning-efforts";
784
+ var LLM_PI_AI_SETTINGS_NAMESPACE = "llm-pi-ai";
785
+ var THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
786
+
787
+ // src/patches/model-reasoning-efforts/client/index.tsx
788
+ var import_jsx_runtime5 = require("react/jsx-runtime");
789
+ var EFFORT_BUTTON_ATTR = "data-dshmore-effort";
790
+ var LEVEL_LABELS = {
791
+ off: "\u4E0D\u601D\u8003",
792
+ minimal: "\u6781\u7B80",
793
+ low: "\u4F4E",
794
+ medium: "\u4E2D",
795
+ high: "\u9AD8",
796
+ xhigh: "\u6781\u9AD8",
797
+ max: "\u6700\u9AD8"
798
+ };
799
+ function emptyDraft() {
800
+ return { nonReasoning: false, levels: {} };
801
+ }
802
+ function draftFromServer(efforts) {
803
+ if (efforts === false) return { nonReasoning: true, levels: {} };
804
+ if (efforts !== null && typeof efforts === "object") {
805
+ const levels = {};
806
+ for (const level of THINKING_LEVELS) {
807
+ const wire = efforts[level];
808
+ if (wire !== void 0) levels[level] = { enabled: true, wire: wire ?? "" };
809
+ }
810
+ return { nonReasoning: false, levels };
811
+ }
812
+ return emptyDraft();
813
+ }
814
+ function buildDict(draft) {
815
+ const dict = {};
816
+ for (const level of THINKING_LEVELS) {
817
+ const entry = draft.levels[level];
818
+ if (entry === void 0 || !entry.enabled) continue;
819
+ dict[level] = level === "off" ? null : entry.wire;
820
+ }
821
+ return dict;
822
+ }
823
+ function summaryLabel(efforts) {
824
+ if (efforts === false) return "\u4E0D\u652F\u6301\u601D\u8003";
825
+ if (efforts === null || typeof efforts !== "object") return "\u672A\u914D\u7F6E";
826
+ const parts = THINKING_LEVELS.filter((level) => level !== "off" && efforts[level] !== void 0).map((level) => LEVEL_LABELS[level]);
827
+ return parts.length === 0 ? "\u672A\u914D\u7F6E" : parts.join("\u3001");
828
+ }
829
+ function findModelEntries() {
830
+ const entries = [];
831
+ const catalogs = document.querySelectorAll('[class*="modelCatalog"]');
832
+ for (const catalog of catalogs) {
833
+ for (const entry of catalog.querySelectorAll('[class*="modelEntry"]')) entries.push(entry);
834
+ }
835
+ return entries;
836
+ }
837
+ function providerRouteOf(entry) {
838
+ const editor = entry.closest('[class*="editor"]');
839
+ if (editor === null) return null;
840
+ const routeSpan = editor.querySelector('[class*="editorRoute"]');
841
+ const route = routeSpan?.textContent?.trim();
842
+ if (route !== void 0 && route !== "") return route;
843
+ const rowCard = editor.closest('[class*="rowCard"]');
844
+ const name = rowCard?.querySelector('[class*="rowName"]')?.textContent?.trim();
845
+ return name !== void 0 && name !== "" ? name : null;
846
+ }
847
+ function modelIdOf(entry) {
848
+ const input = entry.querySelector('[class*="modelRow"] input[type="text"]');
849
+ const id = input?.value.trim();
850
+ return id !== void 0 && id !== "" ? id : null;
851
+ }
852
+ function EffortEditorBody({ draft, setDraft, writable, busy }) {
853
+ const toggleLevel = (level, enabled) => {
854
+ setDraft((current) => {
855
+ const existing = current.levels[level];
856
+ const levels = { ...current.levels };
857
+ levels[level] = existing === void 0 ? { enabled, wire: level === "off" ? "" : level } : { ...existing, enabled };
858
+ return { ...current, levels };
859
+ });
860
+ };
861
+ const setWire = (level, wire) => {
862
+ setDraft((current) => {
863
+ const existing = current.levels[level];
864
+ if (existing === void 0) return current;
865
+ return { ...current, levels: { ...current.levels, [level]: { ...existing, wire } } };
866
+ });
867
+ };
868
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "dshmore-effort-editor", children: [
869
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "dshmore-effort-switch", children: [
870
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
871
+ "input",
872
+ {
873
+ type: "checkbox",
874
+ role: "switch",
875
+ checked: draft.nonReasoning,
876
+ disabled: !writable || busy,
877
+ onChange: (event) => {
878
+ setDraft((current) => ({ ...current, nonReasoning: event.currentTarget.checked }));
879
+ }
880
+ }
881
+ ),
882
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: "\u8BE5\u6A21\u578B\u4E0D\u652F\u6301\u601D\u8003\uFF08reasoningEfforts: false\uFF09" })
883
+ ] }),
884
+ !draft.nonReasoning && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "dshmore-effort-levels", children: THINKING_LEVELS.map((level) => {
885
+ const entry = draft.levels[level];
886
+ const enabled = entry?.enabled === true;
887
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "dshmore-effort-level", children: [
888
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
889
+ "input",
890
+ {
891
+ type: "checkbox",
892
+ checked: enabled,
893
+ disabled: !writable || busy,
894
+ onChange: (event) => {
895
+ toggleLevel(level, event.currentTarget.checked);
896
+ }
897
+ }
898
+ ),
899
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "dshmore-effort-level-label", children: LEVEL_LABELS[level] }),
900
+ enabled && level !== "off" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
901
+ "input",
902
+ {
903
+ className: "dshmore-effort-wire",
904
+ type: "text",
905
+ value: entry?.wire ?? "",
906
+ disabled: !writable || busy,
907
+ placeholder: "\u7EBF\u4E0A\u503C",
908
+ "aria-label": `${LEVEL_LABELS[level]} \u7684\u7EBF\u4E0A\u503C`,
909
+ onChange: (event) => {
910
+ setWire(level, event.currentTarget.value);
911
+ }
912
+ }
913
+ )
914
+ ] }, level);
915
+ }) })
916
+ ] });
917
+ }
918
+ function EffortEditorModal({ provider, modelId, initial, writable, onClose }) {
919
+ const [draft, setDraft] = (0, import_react5.useState)(() => draftFromServer(initial ?? null));
920
+ const [busy, setBusy] = (0, import_react5.useState)(false);
921
+ const [error, setError] = (0, import_react5.useState)(null);
922
+ (0, import_react5.useEffect)(() => {
923
+ setDraft(draftFromServer(initial ?? null));
924
+ }, [initial]);
925
+ const known = initial !== void 0;
926
+ const save = async () => {
927
+ if (!known) return;
928
+ const payload = draft.nonReasoning ? false : buildDict(draft);
929
+ if (payload !== false && !THINKING_LEVELS.some((level) => level !== "off" && payload[level] !== void 0)) {
930
+ setError("\u81F3\u5C11\u9700\u8981\u5F00\u542F\u4E00\u4E2A\u975E\u300C\u4E0D\u601D\u8003\u300D\u7684\u601D\u8003\u7EA7\u522B\uFF0C\u6216\u4F7F\u7528\u300C\u6E05\u9664\u914D\u7F6E\u300D\u3002");
931
+ return;
932
+ }
933
+ setBusy(true);
934
+ setError(null);
935
+ try {
936
+ await callPatchApi(MODEL_REASONING_EFFORTS_PATCH_ID, "save", {
937
+ provider,
938
+ modelId,
939
+ reasoningEfforts: payload
940
+ });
941
+ onClose();
942
+ } catch (caught) {
943
+ setError(apiErrorText(caught));
944
+ } finally {
945
+ setBusy(false);
946
+ }
947
+ };
948
+ const clear = async () => {
949
+ if (!known) return;
950
+ setBusy(true);
951
+ setError(null);
952
+ try {
953
+ await callPatchApi(MODEL_REASONING_EFFORTS_PATCH_ID, "save", {
954
+ provider,
955
+ modelId,
956
+ reasoningEfforts: null
957
+ });
958
+ onClose();
959
+ } catch (caught) {
960
+ setError(apiErrorText(caught));
961
+ } finally {
962
+ setBusy(false);
963
+ }
964
+ };
965
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
966
+ import_dsh_client_ui_primitives5.Modal,
967
+ {
968
+ open: true,
969
+ onClose,
970
+ title: `\u601D\u8003\u5F3A\u5EA6 \xB7 ${modelId}`,
971
+ closeLabel: "\u5173\u95ED",
972
+ description: `\u63D0\u4F9B\u65B9 ${provider}`,
973
+ footer: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
974
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_dsh_client_ui_primitives5.Button, { variant: "ghost", disabled: busy, onClick: onClose, children: "\u5173\u95ED" }),
975
+ known && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_dsh_client_ui_primitives5.Button, { variant: "ghost", disabled: !writable || busy, onClick: () => {
976
+ void clear();
977
+ }, children: "\u6E05\u9664\u914D\u7F6E" }),
978
+ known && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_dsh_client_ui_primitives5.Button, { variant: "primary", disabled: !writable || busy, onClick: () => {
979
+ void save();
980
+ }, children: busy ? "\u4FDD\u5B58\u4E2D\u2026" : "\u4FDD\u5B58" })
981
+ ] }),
982
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "dshmore-effort-dialog-body", children: [
983
+ !known && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "dshmore-effort-note", children: "\u8BE5\u6A21\u578B\u5C1A\u672A\u4FDD\u5B58\u5230 settings.yaml\uFF0C\u8BF7\u5148\u5728\u4E0A\u65B9\u4FDD\u5B58\u63D0\u4F9B\u65B9\u914D\u7F6E\u3002" }),
984
+ known && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(EffortEditorBody, { draft, setDraft, writable, busy }),
985
+ !writable && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "dshmore-effort-warn", children: "\u5F53\u524D Settings \u5B58\u50A8\u4E3A\u53EA\u8BFB\uFF0C\u65E0\u6CD5\u4FDD\u5B58\u4FEE\u6539\u3002" }),
986
+ error !== null && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "dshmore-effort-error", children: error })
987
+ ] })
988
+ }
989
+ );
990
+ }
991
+ function ModelsPageEffortPortal(props) {
992
+ const settings = (0, import_react5.useSyncExternalStore)(props.activation.subscribe, props.activation.getSnapshot, props.activation.getSnapshot);
993
+ const enabled = settings[MODEL_REASONING_EFFORTS_PATCH_ID];
994
+ const scopeSnapshot = (0, import_react5.useSyncExternalStore)(props.scope.subscribe, props.scope.getSnapshot, props.scope.getSnapshot);
995
+ const [editing, setEditing] = (0, import_react5.useState)(null);
996
+ const [data, setData] = (0, import_react5.useState)(null);
997
+ const dataRef = (0, import_react5.useRef)(data);
998
+ dataRef.current = data;
999
+ const generation = (0, import_react5.useRef)(0);
1000
+ const fillingRef = (0, import_react5.useRef)(false);
1001
+ const reload = (0, import_react5.useCallback)(async () => {
1002
+ const current = ++generation.current;
1003
+ try {
1004
+ const snapshot = await callPatchApi(MODEL_REASONING_EFFORTS_PATCH_ID, "read", {});
1005
+ if (generation.current !== current) return;
1006
+ setData(snapshot);
1007
+ if (enabled && snapshot.writable && snapshot.rows.some((row) => row.reasoningEfforts === null) && !fillingRef.current) {
1008
+ fillingRef.current = true;
1009
+ void callPatchApi(MODEL_REASONING_EFFORTS_PATCH_ID, "fillDefaults", {}).catch(() => void 0).finally(() => {
1010
+ fillingRef.current = false;
1011
+ });
1012
+ }
1013
+ } catch {
1014
+ }
1015
+ }, [enabled]);
1016
+ (0, import_react5.useEffect)(() => {
1017
+ if (enabled) void reload();
1018
+ }, [enabled, reload, scopeSnapshot]);
1019
+ const syncButtons = (0, import_react5.useCallback)(() => {
1020
+ if (!enabled) return;
1021
+ const active = /* @__PURE__ */ new Set();
1022
+ for (const entry of findModelEntries()) {
1023
+ const provider = providerRouteOf(entry);
1024
+ const modelId = modelIdOf(entry);
1025
+ if (provider === null || modelId === null) continue;
1026
+ active.add(entry);
1027
+ let button = entry.querySelector(`[${EFFORT_BUTTON_ATTR}]`);
1028
+ if (button === null) {
1029
+ button = document.createElement("button");
1030
+ button.type = "button";
1031
+ button.className = "dshmore-effort-row-button";
1032
+ button.setAttribute(EFFORT_BUTTON_ATTR, "");
1033
+ button.addEventListener("click", () => {
1034
+ const route = button?.dataset.provider;
1035
+ const model = button?.dataset.model;
1036
+ if (route !== void 0 && model !== void 0) setEditing({ provider: route, modelId: model });
1037
+ });
1038
+ const row = entry.querySelector('[class*="modelRow"]');
1039
+ if (row === null) continue;
1040
+ entry.insertBefore(button, row.nextSibling);
1041
+ }
1042
+ if (button.dataset.provider !== provider) button.dataset.provider = provider;
1043
+ if (button.dataset.model !== modelId) button.dataset.model = modelId;
1044
+ const rowData = dataRef.current?.rows.find((row) => row.provider === provider && row.modelId === modelId);
1045
+ const text = rowData === void 0 ? "\u601D\u8003\u5F3A\u5EA6" : `\u601D\u8003\u5F3A\u5EA6 \xB7 ${summaryLabel(rowData.reasoningEfforts)}`;
1046
+ if (button.textContent !== text) button.textContent = text;
1047
+ }
1048
+ document.querySelectorAll(`[${EFFORT_BUTTON_ATTR}]`).forEach((button) => {
1049
+ const host = button.parentElement;
1050
+ if (host === null || !active.has(host)) button.remove();
1051
+ });
1052
+ }, [enabled]);
1053
+ (0, import_react5.useEffect)(() => {
1054
+ if (!enabled) {
1055
+ document.querySelectorAll(`[${EFFORT_BUTTON_ATTR}]`).forEach((element) => element.remove());
1056
+ return;
1057
+ }
1058
+ let frame = null;
1059
+ const schedule = () => {
1060
+ if (frame !== null) return;
1061
+ frame = window.requestAnimationFrame(() => {
1062
+ frame = null;
1063
+ syncButtons();
1064
+ });
1065
+ };
1066
+ syncButtons();
1067
+ const observer = new MutationObserver(schedule);
1068
+ observer.observe(document.body, { childList: true, subtree: true });
1069
+ return () => {
1070
+ observer.disconnect();
1071
+ if (frame !== null) window.cancelAnimationFrame(frame);
1072
+ };
1073
+ }, [enabled, syncButtons]);
1074
+ (0, import_react5.useEffect)(() => {
1075
+ if (enabled) syncButtons();
1076
+ }, [enabled, syncButtons, data]);
1077
+ const editingRow = editing === null ? void 0 : data?.rows.find((row) => row.provider === editing.provider && row.modelId === editing.modelId);
1078
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
1079
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("style", { children: effortStyles }),
1080
+ editing !== null && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1081
+ EffortEditorModal,
1082
+ {
1083
+ provider: editing.provider,
1084
+ modelId: editing.modelId,
1085
+ initial: editingRow === void 0 ? void 0 : editingRow.reasoningEfforts,
1086
+ writable: data?.writable ?? true,
1087
+ onClose: () => {
1088
+ setEditing(null);
1089
+ }
1090
+ }
1091
+ )
1092
+ ] });
1093
+ }
1094
+ var effortStyles = `
1095
+ .dshmore-effort-row-button { box-sizing: border-box; width: 100%; height: 28px; color: var(--dsw-alias-label-secondary); font: inherit; cursor: pointer; background: transparent; border: 1px dashed var(--dsw-alias-border-l3); border-radius: 6px; margin-top: 6px; padding: 0 10px; font-size: 12px; line-height: 18px; display: inline-flex; align-items: center; justify-content: flex-start; gap: 6px; }
1096
+ .dshmore-effort-row-button:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }
1097
+ .dshmore-effort-dialog-body { box-sizing: border-box; display: flex; flex-direction: column; gap: 12px; width: 100%; min-width: 0; overflow: hidden; }
1098
+ .dshmore-effort-editor { display: flex; flex-direction: column; gap: 12px; }
1099
+ .dshmore-effort-switch, .dshmore-effort-level { display: flex; align-items: center; gap: 8px; color: var(--dsw-alias-label-secondary); font-size: 13px; line-height: 20px; cursor: pointer; }
1100
+ .dshmore-effort-switch input, .dshmore-effort-level input[type="checkbox"] { accent-color: var(--dsw-alias-state-business-primary); cursor: pointer; }
1101
+ .dshmore-effort-levels { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 8px 12px; }
1102
+ .dshmore-effort-level-label { flex: none; }
1103
+ .dshmore-effort-wire { box-sizing: border-box; height: 28px; min-width: 0; width: 100%; font: inherit; color: var(--dsw-alias-label-primary); background: var(--dsw-alias-bg-layer-1); border: 1px solid var(--dsw-alias-border-l2); border-radius: 6px; padding: 0 8px; font-size: 12px; line-height: 18px; }
1104
+ .dshmore-effort-wire:focus { border-color: var(--dsw-alias-brand-primary); outline: none; }
1105
+ .dshmore-effort-wire::placeholder { color: var(--dsw-alias-label-dimmed); }
1106
+ .dshmore-effort-note { color: var(--dsw-alias-label-tertiary); margin: 0; font-size: 12px; line-height: 18px; }
1107
+ .dshmore-effort-warn { color: var(--dsw-alias-state-warn-label); margin: 0; font-size: 12px; line-height: 18px; }
1108
+ .dshmore-effort-error { padding: 8px 10px; border-radius: 8px; background: rgba(208,58,58,.10); color: var(--dsw-alias-state-error-primary, rgb(220,90,90)); font-size: 12px; line-height: 18px; }
1109
+ `;
1110
+ var clientPatch3 = {
1111
+ id: MODEL_REASONING_EFFORTS_PATCH_ID,
1112
+ install: (ctx, activation) => {
1113
+ const scope = ctx.settingsScope.bind({ namespace: LLM_PI_AI_SETTINGS_NAMESPACE });
1114
+ const boundScope = {
1115
+ getSnapshot: scope.getSnapshot.bind(scope),
1116
+ subscribe: scope.subscribe.bind(scope),
1117
+ set: scope.set.bind(scope),
1118
+ unset: scope.unset.bind(scope)
1119
+ };
1120
+ ctx.slots.inject("shell.overlay", () => ctx.slots.register({
1121
+ name: "shell.overlay",
1122
+ id: `${PLUGIN_NAME}-${MODEL_REASONING_EFFORTS_PATCH_ID}`,
1123
+ order: 90,
1124
+ registrant: PLUGIN_NAME
1125
+ }, (props) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ModelsPageEffortPortal, { ...props, scope: boundScope, activation })));
1126
+ }
1127
+ };
1128
+
1129
+ // src/patches/conversation-quick-navigation/client/index.tsx
1130
+ var import_react6 = require("react");
1131
+ var import_react_dom = require("react-dom");
1132
+ var import_dsh_client_ui_primitives6 = require("@deepseek-ai/dsh-client-ui-primitives");
1133
+
1134
+ // src/kernel/message-visibility.ts
1135
+ var MESSAGE_VISIBILITY_PROJECTION_KEY = "dshMoreMessageDelete";
1136
+
1137
+ // src/patches/conversation-quick-navigation/shared.ts
1138
+ var CONVERSATION_QUICK_NAVIGATION_PATCH_ID = "conversation-quick-navigation";
1139
+
1140
+ // src/platform/dsh/client/message-content.ts
1141
+ function contentText(content) {
1142
+ return content.filter((block) => block.type === "text").map((block) => block.text).join(" ").replace(/\s+/g, " ").trim();
1143
+ }
1144
+
1145
+ // src/patches/conversation-quick-navigation/client/navigation.ts
1146
+ var MAX_LABEL_CODE_POINTS = 72;
1147
+ function conciseLabel(content, fallbackIndex) {
1148
+ const text = contentText(content);
1149
+ if (text === "") return `\u7B2C ${String(fallbackIndex)} \u8F6E \xB7 \u975E\u6587\u672C\u6D88\u606F`;
1150
+ const codePoints = [...text];
1151
+ if (codePoints.length <= MAX_LABEL_CODE_POINTS) return text;
1152
+ return `${codePoints.slice(0, MAX_LABEL_CODE_POINTS - 1).join("")}\u2026`;
1153
+ }
1154
+ function turnSummary(node, key, index) {
1155
+ const candidate = node;
1156
+ if (candidate?.kind !== "user" && candidate?.kind !== "steering") return null;
1157
+ if (!Number.isSafeInteger(candidate.data?.seq) || !Array.isArray(candidate.data?.content)) return null;
1158
+ return {
1159
+ key,
1160
+ seq: candidate.data.seq,
1161
+ label: conciseLabel(candidate.data.content, index)
1162
+ };
1163
+ }
1164
+ function sameTurns(left, right) {
1165
+ return left.length === right.length && left.every((turn, index) => {
1166
+ const other = right[index];
1167
+ return other !== void 0 && turn.key === other.key && turn.seq === other.seq && turn.label === other.label;
1168
+ });
1169
+ }
1170
+ function createConversationTurnSelector() {
1171
+ let previousOrder;
1172
+ let previous = [];
1173
+ return (snapshot) => {
1174
+ if (snapshot.chat.order === previousOrder) return previous;
1175
+ previousOrder = snapshot.chat.order;
1176
+ const next = [];
1177
+ for (const key of snapshot.chat.order) {
1178
+ const summary = turnSummary(snapshot.chat.nodes.get(key), key, next.length + 1);
1179
+ if (summary !== null) next.push(summary);
1180
+ }
1181
+ if (!sameTurns(previous, next)) previous = next;
1182
+ return previous;
1183
+ };
1184
+ }
1185
+ function historyWindowSignature(snapshot) {
1186
+ const first = snapshot.chat.order[0] ?? "";
1187
+ const last = snapshot.chat.order.at(-1) ?? "";
1188
+ return `${first}\0${last}\0${String(snapshot.chat.order.length)}`;
1189
+ }
1190
+ function shouldAutoLoadOlder(state) {
1191
+ return state.enabled && state.surfaceAvailable && state.hasMore && !state.loadingOlder && !state.paused;
1192
+ }
1193
+ function activeTurnAt(positions, readingLine) {
1194
+ let active = positions[0]?.key ?? null;
1195
+ for (const position of positions) {
1196
+ if (position.top > readingLine) break;
1197
+ active = position.key;
1198
+ }
1199
+ return active;
1200
+ }
1201
+ function scrollOffsetForRow(currentScrollTop, scrollportTop, rowTop, topInset = 20) {
1202
+ return Math.max(0, currentScrollTop + rowTop - scrollportTop - topInset);
1203
+ }
1204
+
1205
+ // src/patches/conversation-quick-navigation/client/index.tsx
1206
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1207
+ var MAX_NO_PROGRESS_ATTEMPTS = 3;
1208
+ var MENU_LIST_ID = "dshmore-conversation-nav-list";
1209
+ var navigationCollapsedPreference = false;
1210
+ function conversationRow(root, key) {
1211
+ for (const row of root.querySelectorAll("[data-chat-flow-key]")) {
1212
+ if (row.dataset.chatFlowKey === key) return row;
1213
+ }
1214
+ return null;
1215
+ }
1216
+ function sessionRoot(marker) {
1217
+ return marker?.closest("[data-phase]") ?? null;
1218
+ }
1219
+ function conversationScrollport(marker) {
1220
+ return sessionRoot(marker)?.querySelector("[data-conversation-scroll]") ?? null;
1221
+ }
1222
+ function nativeLoadOlderButton(scrollport) {
1223
+ const flow = scrollport.querySelector("[data-chat-flow]");
1224
+ if (flow === null) return null;
1225
+ for (const child of flow.children) {
1226
+ if (child instanceof HTMLElement && child.dataset.chatFlowKey !== void 0) break;
1227
+ const button = child.querySelector("button");
1228
+ if (button !== null) return button;
1229
+ }
1230
+ return null;
1231
+ }
1232
+ function ConversationNavigationMenu({
1233
+ turns,
1234
+ activeKey,
1235
+ hasMore,
1236
+ loadingOlder,
1237
+ autoLoadPaused,
1238
+ collapsed,
1239
+ onRetry,
1240
+ onToggleCollapsed,
1241
+ onJump,
1242
+ listRef
1243
+ }) {
1244
+ const loading = hasMore && !autoLoadPaused;
1245
+ const compactStatus = autoLoadPaused ? `${String(turns.length)} \u8F6E \xB7 \u5DF2\u6682\u505C` : loading ? `${String(turns.length)} \u8F6E \xB7 \u52A0\u8F7D\u4E2D` : `${String(turns.length)} \u8F6E`;
1246
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1247
+ "nav",
1248
+ {
1249
+ className: "dshmore-conversation-nav-menu",
1250
+ "data-collapsed": collapsed ? "true" : "false",
1251
+ "aria-label": "\u5F53\u524D\u4F1A\u8BDD\u7684\u5BF9\u8BDD\u76EE\u5F55",
1252
+ children: [
1253
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("header", { className: "dshmore-conversation-nav-header", children: [
1254
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "dshmore-conversation-nav-heading", children: [
1255
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "dshmore-conversation-nav-title-row", children: [
1256
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "dshmore-conversation-nav-title", children: "\u5BF9\u8BDD\u76EE\u5F55" }),
1257
+ collapsed && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "dshmore-conversation-nav-compact-status", role: "status", "aria-live": "polite", children: [
1258
+ loading && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "dshmore-conversation-nav-loading-dot", "aria-hidden": "true" }),
1259
+ compactStatus
1260
+ ] })
1261
+ ] }),
1262
+ !collapsed && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "dshmore-conversation-nav-count", role: "status", "aria-live": "polite", children: [
1263
+ loading && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "dshmore-conversation-nav-loading-dot", "aria-hidden": "true" }),
1264
+ autoLoadPaused ? `\u5DF2\u8F7D\u5165 ${String(turns.length)} \u8F6E\uFF0C\u81EA\u52A8\u52A0\u8F7D\u5DF2\u6682\u505C` : loading ? `\u6B63\u5728\u52A0\u8F7D\u5168\u90E8\u8BB0\u5F55 \xB7 \u5DF2\u8F7D\u5165 ${String(turns.length)} \u8F6E` : `\u5171 ${String(turns.length)} \u8F6E`
1265
+ ] })
1266
+ ] }),
1267
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "dshmore-conversation-nav-actions", children: [
1268
+ !collapsed && autoLoadPaused && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "dshmore-conversation-nav-retry", onClick: onRetry, children: "\u7EE7\u7EED\u52A0\u8F7D" }),
1269
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1270
+ "button",
1271
+ {
1272
+ type: "button",
1273
+ className: "dshmore-conversation-nav-toggle",
1274
+ "aria-label": collapsed ? "\u5C55\u5F00\u5BF9\u8BDD\u76EE\u5F55" : "\u6298\u53E0\u5BF9\u8BDD\u76EE\u5F55",
1275
+ "aria-controls": MENU_LIST_ID,
1276
+ "aria-expanded": !collapsed,
1277
+ onClick: onToggleCollapsed,
1278
+ children: collapsed ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_dsh_client_ui_primitives6.IconChevronDownOutline14, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_dsh_client_ui_primitives6.IconChevronUpOutline14, { size: 14 })
1279
+ }
1280
+ )
1281
+ ] })
1282
+ ] }),
1283
+ !collapsed && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { id: MENU_LIST_ID, ref: listRef, className: "dshmore-conversation-nav-list", children: [
1284
+ turns.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "dshmore-conversation-nav-empty", children: loadingOlder || hasMore ? "\u6B63\u5728\u8BFB\u53D6\u5386\u53F2\u8BB0\u5F55\u2026" : "\u5F53\u524D\u6CA1\u6709\u53EF\u5B9A\u4F4D\u7684\u5BF9\u8BDD" }),
1285
+ turns.map((turn, index) => {
1286
+ const active = turn.key === activeKey;
1287
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1288
+ "button",
1289
+ {
1290
+ type: "button",
1291
+ className: "dshmore-conversation-nav-item",
1292
+ "aria-current": active ? "location" : void 0,
1293
+ onClick: () => {
1294
+ onJump(turn.key);
1295
+ },
1296
+ children: [
1297
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "dshmore-conversation-nav-index", children: String(index + 1).padStart(2, "0") }),
1298
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "dshmore-conversation-nav-label", children: turn.label })
1299
+ ]
1300
+ },
1301
+ turn.key
1302
+ );
1303
+ })
1304
+ ] })
1305
+ ]
1306
+ }
1307
+ );
1308
+ }
1309
+ function ConversationQuickNavigation(props) {
1310
+ const settings = (0, import_react6.useSyncExternalStore)(props.activation.subscribe, props.activation.getSnapshot, props.activation.getSnapshot);
1311
+ const enabled = settings[CONVERSATION_QUICK_NAVIGATION_PATCH_ID];
1312
+ const selector = (0, import_react6.useMemo)(createConversationTurnSelector, [props.sessionId]);
1313
+ const allTurns = props.useSession(selector);
1314
+ const hasMore = props.useSession((snapshot) => snapshot.hasMore);
1315
+ const loadingOlder = props.useSession((snapshot) => snapshot.loadingOlder);
1316
+ const windowSignature = props.useSession(historyWindowSignature);
1317
+ const projected = props.useProjection(MESSAGE_VISIBILITY_PROJECTION_KEY);
1318
+ const hiddenSeqs = (0, import_react6.useMemo)(() => new Set(projected?.deletedSeqs ?? []), [projected]);
1319
+ const turns = (0, import_react6.useMemo)(
1320
+ () => allTurns.filter((turn) => !hiddenSeqs.has(turn.seq)),
1321
+ [allTurns, hiddenSeqs]
1322
+ );
1323
+ const markerRef = (0, import_react6.useRef)(null);
1324
+ const listRef = (0, import_react6.useRef)(null);
1325
+ const pendingLoadSignatureRef = (0, import_react6.useRef)(null);
1326
+ const noProgressAttemptsRef = (0, import_react6.useRef)(0);
1327
+ const [portalTarget, setPortalTarget] = (0, import_react6.useState)(null);
1328
+ const [activeKey, setActiveKey] = (0, import_react6.useState)(null);
1329
+ const [surfaceAvailable, setSurfaceAvailable] = (0, import_react6.useState)(false);
1330
+ const [composerInset, setComposerInset] = (0, import_react6.useState)(176);
1331
+ const [autoLoadPaused, setAutoLoadPaused] = (0, import_react6.useState)(false);
1332
+ const [loadTick, setLoadTick] = (0, import_react6.useState)(0);
1333
+ const [collapsed, setCollapsed] = (0, import_react6.useState)(() => navigationCollapsedPreference);
1334
+ (0, import_react6.useLayoutEffect)(() => {
1335
+ const root = sessionRoot(markerRef.current);
1336
+ setPortalTarget(root);
1337
+ if (root === null) return;
1338
+ const composer = root.querySelector("[data-composer-seat]");
1339
+ const updateInset = () => {
1340
+ setComposerInset(Math.max(96, (composer?.offsetHeight ?? 152) + 20));
1341
+ };
1342
+ updateInset();
1343
+ if (composer === null || typeof ResizeObserver === "undefined") return;
1344
+ const observer = new ResizeObserver(updateInset);
1345
+ observer.observe(composer);
1346
+ return () => {
1347
+ observer.disconnect();
1348
+ };
1349
+ }, [props.sessionId]);
1350
+ (0, import_react6.useEffect)(() => {
1351
+ pendingLoadSignatureRef.current = null;
1352
+ noProgressAttemptsRef.current = 0;
1353
+ setAutoLoadPaused(false);
1354
+ }, [enabled, props.sessionId]);
1355
+ (0, import_react6.useEffect)(() => {
1356
+ if (hasMore) return;
1357
+ pendingLoadSignatureRef.current = null;
1358
+ noProgressAttemptsRef.current = 0;
1359
+ setAutoLoadPaused(false);
1360
+ }, [hasMore]);
1361
+ (0, import_react6.useEffect)(() => {
1362
+ if (!enabled) {
1363
+ setActiveKey(null);
1364
+ setSurfaceAvailable(false);
1365
+ return;
1366
+ }
1367
+ let frame = null;
1368
+ let scrollport = null;
1369
+ const schedule = () => {
1370
+ if (frame !== null) return;
1371
+ frame = window.requestAnimationFrame(recompute);
1372
+ };
1373
+ const bindScrollport = (next) => {
1374
+ if (next === scrollport) return;
1375
+ scrollport?.removeEventListener("scroll", schedule);
1376
+ scrollport = next;
1377
+ scrollport?.addEventListener("scroll", schedule, { passive: true });
1378
+ };
1379
+ const recompute = () => {
1380
+ frame = null;
1381
+ const nextScrollport = conversationScrollport(markerRef.current);
1382
+ bindScrollport(nextScrollport);
1383
+ const flowAvailable = nextScrollport !== null && nextScrollport.querySelector("[data-chat-flow]") !== null;
1384
+ setSurfaceAvailable(flowAvailable);
1385
+ if (nextScrollport === null || !flowAvailable) {
1386
+ setActiveKey(null);
1387
+ return;
1388
+ }
1389
+ const viewport = nextScrollport.getBoundingClientRect();
1390
+ const readingLine = viewport.top + Math.min(120, nextScrollport.clientHeight * 0.24);
1391
+ const positions = [];
1392
+ for (const turn of turns) {
1393
+ const row = conversationRow(nextScrollport, turn.key);
1394
+ if (row !== null) positions.push({ key: turn.key, top: row.getBoundingClientRect().top });
1395
+ }
1396
+ setActiveKey(activeTurnAt(positions, readingLine));
1397
+ };
1398
+ const observer = new MutationObserver(schedule);
1399
+ const root = sessionRoot(markerRef.current);
1400
+ if (root !== null) observer.observe(root, { childList: true, subtree: true });
1401
+ window.addEventListener("resize", schedule);
1402
+ recompute();
1403
+ return () => {
1404
+ observer.disconnect();
1405
+ window.removeEventListener("resize", schedule);
1406
+ scrollport?.removeEventListener("scroll", schedule);
1407
+ if (frame !== null) window.cancelAnimationFrame(frame);
1408
+ };
1409
+ }, [enabled, turns]);
1410
+ (0, import_react6.useEffect)(() => {
1411
+ if (!shouldAutoLoadOlder({ enabled, surfaceAvailable, hasMore, loadingOlder, paused: autoLoadPaused })) return;
1412
+ const pendingSignature = pendingLoadSignatureRef.current;
1413
+ let delay2 = 0;
1414
+ if (pendingSignature !== null) {
1415
+ if (pendingSignature === windowSignature) {
1416
+ noProgressAttemptsRef.current += 1;
1417
+ if (noProgressAttemptsRef.current >= MAX_NO_PROGRESS_ATTEMPTS) {
1418
+ pendingLoadSignatureRef.current = null;
1419
+ setAutoLoadPaused(true);
1420
+ return;
1421
+ }
1422
+ delay2 = noProgressAttemptsRef.current * 350;
1423
+ } else {
1424
+ noProgressAttemptsRef.current = 0;
1425
+ }
1426
+ pendingLoadSignatureRef.current = null;
1427
+ }
1428
+ let frame = null;
1429
+ let retryTimer = null;
1430
+ let requestTimer = null;
1431
+ const request = () => {
1432
+ frame = window.requestAnimationFrame(() => {
1433
+ frame = null;
1434
+ const scrollport = conversationScrollport(markerRef.current);
1435
+ const button = scrollport === null ? null : nativeLoadOlderButton(scrollport);
1436
+ if (button === null || button.disabled) {
1437
+ retryTimer = window.setTimeout(() => {
1438
+ setLoadTick((current) => current + 1);
1439
+ }, 180);
1440
+ return;
1441
+ }
1442
+ pendingLoadSignatureRef.current = windowSignature;
1443
+ button.click();
1444
+ retryTimer = window.setTimeout(() => {
1445
+ setLoadTick((current) => current + 1);
1446
+ }, 1600);
1447
+ });
1448
+ };
1449
+ if (delay2 > 0) requestTimer = window.setTimeout(request, delay2);
1450
+ else request();
1451
+ return () => {
1452
+ if (frame !== null) window.cancelAnimationFrame(frame);
1453
+ if (retryTimer !== null) window.clearTimeout(retryTimer);
1454
+ if (requestTimer !== null) window.clearTimeout(requestTimer);
1455
+ };
1456
+ }, [autoLoadPaused, enabled, hasMore, loadTick, loadingOlder, surfaceAvailable, windowSignature]);
1457
+ (0, import_react6.useLayoutEffect)(() => {
1458
+ if (collapsed || activeKey === null) return;
1459
+ const list = listRef.current;
1460
+ const active = list?.querySelector('[aria-current="location"]');
1461
+ if (list === null || active === null || active === void 0) return;
1462
+ const top = active.offsetTop;
1463
+ const bottom = top + active.offsetHeight;
1464
+ if (top < list.scrollTop) list.scrollTop = top;
1465
+ else if (bottom > list.scrollTop + list.clientHeight) list.scrollTop = bottom - list.clientHeight;
1466
+ }, [activeKey, collapsed]);
1467
+ const retryAutoLoad = () => {
1468
+ pendingLoadSignatureRef.current = null;
1469
+ noProgressAttemptsRef.current = 0;
1470
+ setAutoLoadPaused(false);
1471
+ setLoadTick((current) => current + 1);
1472
+ };
1473
+ const toggleCollapsed = () => {
1474
+ setCollapsed((current) => {
1475
+ navigationCollapsedPreference = !current;
1476
+ return navigationCollapsedPreference;
1477
+ });
1478
+ };
1479
+ const jump = (key) => {
1480
+ const scrollport = conversationScrollport(markerRef.current);
1481
+ if (scrollport === null) return;
1482
+ const row = conversationRow(scrollport, key);
1483
+ if (row === null) return;
1484
+ const viewport = scrollport.getBoundingClientRect();
1485
+ const rowRect = row.getBoundingClientRect();
1486
+ const top = scrollOffsetForRow(scrollport.scrollTop, viewport.top, rowRect.top);
1487
+ const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1488
+ scrollport.scrollTo({ top, behavior: reducedMotion ? "auto" : "smooth" });
1489
+ setActiveKey(key);
1490
+ };
1491
+ const rootStyle = { "--dshmore-conversation-nav-bottom": `${String(composerInset)}px` };
1492
+ const surface = enabled && surfaceAvailable && portalTarget !== null ? (0, import_react_dom.createPortal)(
1493
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1494
+ "div",
1495
+ {
1496
+ className: "dshmore-conversation-nav-root",
1497
+ "data-collapsed": collapsed ? "true" : "false",
1498
+ style: rootStyle,
1499
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1500
+ ConversationNavigationMenu,
1501
+ {
1502
+ turns,
1503
+ activeKey,
1504
+ hasMore,
1505
+ loadingOlder,
1506
+ autoLoadPaused,
1507
+ collapsed,
1508
+ onRetry: retryAutoLoad,
1509
+ onToggleCollapsed: toggleCollapsed,
1510
+ onJump: jump,
1511
+ listRef
1512
+ }
1513
+ )
1514
+ }
1515
+ ),
1516
+ portalTarget
1517
+ ) : null;
1518
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1519
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { ref: markerRef, className: "dshmore-conversation-nav-marker", "aria-hidden": "true" }),
1520
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("style", { children: navigationStyles }),
1521
+ surface
1522
+ ] });
1523
+ }
1524
+ var navigationStyles = `
1525
+ .dshmore-conversation-nav-marker { display: none; }
1526
+ .dshmore-conversation-nav-root { position: absolute; z-index: 20; right: 14px; top: 78px; bottom: var(--dshmore-conversation-nav-bottom, 176px); width: min(286px, calc(100% - 40px)); pointer-events: none; display: flex; align-items: flex-start; font-family: var(--dsw-font-family); }
1527
+ .dshmore-conversation-nav-root[data-collapsed="true"] { bottom: auto; height: 58px; }
1528
+ .dshmore-conversation-nav-menu { box-sizing: border-box; width: 100%; max-height: min(620px, 100%); min-height: 0; overflow: hidden; pointer-events: auto; display: flex; flex-direction: column; border: 1px solid var(--dsw-alias-border-l2); border-radius: 12px; color: var(--dsw-alias-label-primary); background: var(--dsw-alias-bg-base); box-shadow: var(--dsw-shadow-lv1, var(--dsw-shadow-lv2)); animation: dshmore-conversation-nav-enter 150ms ease-out; }
1529
+ .dshmore-conversation-nav-menu[data-collapsed="true"] { height: 58px; max-height: 58px; }
1530
+ .dshmore-conversation-nav-header { box-sizing: border-box; flex: none; min-height: 58px; padding: 12px 12px 10px 14px; display: flex; align-items: center; justify-content: space-between; gap: 10px; border-bottom: 1px solid var(--dsw-alias-border-l3); }
1531
+ .dshmore-conversation-nav-menu[data-collapsed="true"] .dshmore-conversation-nav-header { height: 58px; min-height: 58px; padding: 0 12px 0 14px; border-bottom: 0; }
1532
+ .dshmore-conversation-nav-heading { min-width: 0; }
1533
+ .dshmore-conversation-nav-title-row { min-width: 0; display: flex; align-items: baseline; gap: 6px; }
1534
+ .dshmore-conversation-nav-title { font: var(--dsw-font-s-strong-14); }
1535
+ .dshmore-conversation-nav-compact-status { min-width: 0; overflow: hidden; color: var(--dsw-alias-label-caption); text-overflow: ellipsis; white-space: nowrap; font-size: 11px; line-height: 16px; }
1536
+ .dshmore-conversation-nav-count { margin-top: 2px; color: var(--dsw-alias-label-caption); font-size: 11px; line-height: 16px; text-wrap: pretty; }
1537
+ .dshmore-conversation-nav-loading-dot { width: 6px; height: 6px; margin-right: 6px; display: inline-block; border-radius: 999px; background: var(--dsw-alias-state-business-primary); vertical-align: 1px; animation: dshmore-conversation-nav-pulse 1.1s ease-in-out infinite; }
1538
+ .dshmore-conversation-nav-retry { flex: none; min-height: 28px; padding: 4px 9px; border: 1px solid var(--dsw-alias-border-l2); border-radius: 8px; color: var(--dsw-alias-label-secondary); background: transparent; cursor: pointer; font: inherit; font-size: 11px; }
1539
+ .dshmore-conversation-nav-retry:hover { color: var(--dsw-alias-label-primary); background: var(--dsw-alias-interactive-bg-hover); }
1540
+ .dshmore-conversation-nav-actions { flex: none; display: flex; align-items: center; gap: 6px; }
1541
+ .dshmore-conversation-nav-toggle { box-sizing: border-box; width: 28px; height: 28px; flex: none; padding: 0; display: grid; place-items: center; border: 0; border-radius: 999px; color: var(--dsw-alias-label-secondary); background: transparent; cursor: pointer; }
1542
+ .dshmore-conversation-nav-toggle:hover { color: var(--dsw-alias-label-primary); background: var(--dsw-alias-interactive-bg-hover); }
1543
+ .dshmore-conversation-nav-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding: 6px; scrollbar-width: thin; }
1544
+ .dshmore-conversation-nav-empty { padding: 18px 12px; color: var(--dsw-alias-label-caption); text-align: center; font-size: 12px; line-height: 18px; text-wrap: pretty; }
1545
+ .dshmore-conversation-nav-item { box-sizing: border-box; width: 100%; min-height: 46px; padding: 7px 9px; display: grid; grid-template-columns: 28px minmax(0, 1fr); align-items: start; gap: 2px; border: 0; border-radius: 9px; color: var(--dsw-alias-label-secondary); background: transparent; cursor: pointer; text-align: left; font-family: inherit; transition: background-color 120ms ease, color 120ms ease; }
1546
+ .dshmore-conversation-nav-item:hover { color: var(--dsw-alias-label-primary); background: var(--dsw-alias-interactive-bg-hover); }
1547
+ .dshmore-conversation-nav-item:focus-visible, .dshmore-conversation-nav-retry:focus-visible, .dshmore-conversation-nav-toggle:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary); outline-offset: -2px; }
1548
+ .dshmore-conversation-nav-item[aria-current="location"] { color: var(--dsw-alias-label-primary); background: var(--dsw-alias-interactive-bg-hover-solid); }
1549
+ .dshmore-conversation-nav-index { color: var(--dsw-alias-label-caption); font-size: 11px; line-height: 20px; font-variant-numeric: tabular-nums; }
1550
+ .dshmore-conversation-nav-item[aria-current="location"] .dshmore-conversation-nav-index { color: var(--dsw-alias-state-business-primary); font-weight: 600; }
1551
+ .dshmore-conversation-nav-label { min-width: 0; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; font-size: 13px; line-height: 20px; overflow-wrap: anywhere; text-wrap: pretty; }
1552
+ @keyframes dshmore-conversation-nav-enter { from { opacity: 0; transform: translateX(6px); } to { opacity: 1; transform: translateX(0); } }
1553
+ @keyframes dshmore-conversation-nav-pulse { 50% { opacity: .35; transform: scale(.82); } }
1554
+ @media (max-width: 720px) {
1555
+ .dshmore-conversation-nav-root { right: 8px; top: 74px; bottom: auto; width: calc(100% - 16px); height: min(220px, 32dvh); align-items: stretch; }
1556
+ .dshmore-conversation-nav-root[data-collapsed="true"] { height: 58px; }
1557
+ .dshmore-conversation-nav-menu { max-height: 100%; }
1558
+ }
1559
+ @media (prefers-reduced-motion: reduce) {
1560
+ .dshmore-conversation-nav-menu, .dshmore-conversation-nav-loading-dot { animation: none; }
1561
+ .dshmore-conversation-nav-item { transition: none; }
1562
+ }
1563
+ `;
1564
+ var clientPatch4 = {
1565
+ id: CONVERSATION_QUICK_NAVIGATION_PATCH_ID,
1566
+ install: (ctx, activation) => {
1567
+ ctx.slots.inject("conversation.session.header.utilities", () => ctx.slots.register({
1568
+ name: "conversation.session.header.utilities",
1569
+ id: `${PLUGIN_NAME}-${CONVERSATION_QUICK_NAVIGATION_PATCH_ID}`,
1570
+ order: 120,
1571
+ registrant: PLUGIN_NAME
1572
+ }, (props) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ConversationQuickNavigation, { ...props, activation })));
1573
+ }
1574
+ };
1575
+
626
1576
  // src/generated/client-registry.ts
627
1577
  var MESSAGE_ACTION_PATCHES = [{ id: "message-edit", order: 100, useActions: useMessageActions }, { id: "message-delete", order: 200, useActions: useMessageActions2 }];
628
- var CLIENT_PATCHES = [clientPatch];
1578
+ var CLIENT_PATCHES = [clientPatch, clientPatch2, clientPatch3, clientPatch4];
629
1579
 
630
1580
  // src/kernel/client/activation.ts
631
1581
  var ClientPatchActivation = class {
632
1582
  constructor(scope) {
633
1583
  this.scope = scope;
634
1584
  }
1585
+ scope;
635
1586
  lastScope;
636
1587
  lastValue = { ...DEFAULT_PATCH_SETTINGS };
637
1588
  getSnapshot = () => {
@@ -647,19 +1598,19 @@ var ClientPatchActivation = class {
647
1598
  };
648
1599
 
649
1600
  // src/kernel/client/config-card.tsx
650
- var import_react4 = require("react");
651
- var import_dsh_client_ui_primitives4 = require("@deepseek-ai/dsh-client-ui-primitives");
652
- var import_jsx_runtime4 = require("react/jsx-runtime");
1601
+ var import_react7 = require("react");
1602
+ var import_dsh_client_ui_primitives7 = require("@deepseek-ai/dsh-client-ui-primitives");
1603
+ var import_jsx_runtime7 = require("react/jsx-runtime");
653
1604
  function PatchConfigCard({ activation, initiallyOpen = false }) {
654
- const scope = (0, import_react4.useSyncExternalStore)(
1605
+ const scope = (0, import_react7.useSyncExternalStore)(
655
1606
  activation.subscribe,
656
1607
  activation.getSettingsSnapshot,
657
1608
  activation.getSettingsSnapshot
658
1609
  );
659
1610
  const settings = activation.getSnapshot();
660
- const [open, setOpen] = (0, import_react4.useState)(initiallyOpen);
661
- const [busy, setBusy] = (0, import_react4.useState)(/* @__PURE__ */ new Set());
662
- const [error, setError] = (0, import_react4.useState)(null);
1611
+ const [open, setOpen] = (0, import_react7.useState)(initiallyOpen);
1612
+ const [busy, setBusy] = (0, import_react7.useState)(/* @__PURE__ */ new Set());
1613
+ const [error, setError] = (0, import_react7.useState)(null);
663
1614
  if (scope.status === "unavailable") return null;
664
1615
  const update = async (id, enabled) => {
665
1616
  setBusy((current) => /* @__PURE__ */ new Set([...current, id]));
@@ -676,9 +1627,9 @@ function PatchConfigCard({ activation, initiallyOpen = false }) {
676
1627
  });
677
1628
  }
678
1629
  };
679
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("li", { className: `dshmore-config-card${open ? " dshmore-config-card-open" : ""}`, children: [
680
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("style", { children: styles }),
681
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1630
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("li", { className: `dshmore-config-card${open ? " dshmore-config-card-open" : ""}`, children: [
1631
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("style", { children: styles }),
1632
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
682
1633
  "button",
683
1634
  {
684
1635
  type: "button",
@@ -689,23 +1640,23 @@ function PatchConfigCard({ activation, initiallyOpen = false }) {
689
1640
  setOpen((current) => !current);
690
1641
  },
691
1642
  children: [
692
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "dshmore-config-heading-copy", children: [
693
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { children: "DSH More" }),
694
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "\u52A8\u6001\u5F00\u542F\u6216\u5173\u95ED\u72EC\u7ACB\u8865\u4E01\uFF0C\u9ED8\u8BA4\u5168\u90E8\u5F00\u542F\u3002" })
1643
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "dshmore-config-heading-copy", children: [
1644
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("strong", { children: "DSH More" }),
1645
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: "\u52A8\u6001\u5F00\u542F\u6216\u5173\u95ED\u72EC\u7ACB\u8865\u4E01\uFF0C\u9ED8\u8BA4\u5168\u90E8\u5F00\u542F\u3002" })
695
1646
  ] }),
696
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_dsh_client_ui_primitives4.IconChevronDownOutline14, { className: `dshmore-config-chevron${open ? " dshmore-config-chevron-open" : ""}` })
1647
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_dsh_client_ui_primitives7.IconChevronDownOutline14, { className: `dshmore-config-chevron${open ? " dshmore-config-chevron-open" : ""}` })
697
1648
  ]
698
1649
  }
699
1650
  ),
700
- open && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dshmore-config-body", children: [
701
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dshmore-config-list", children: PATCH_CATALOG.map((patch) => {
1651
+ open && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dshmore-config-body", children: [
1652
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "dshmore-config-list", children: PATCH_CATALOG.map((patch) => {
702
1653
  const waiting = busy.has(patch.id);
703
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "dshmore-config-row", children: [
704
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "dshmore-config-copy", children: [
705
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { children: patch.name }),
706
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: patch.description })
1654
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "dshmore-config-row", children: [
1655
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "dshmore-config-copy", children: [
1656
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("strong", { children: patch.name }),
1657
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: patch.description })
707
1658
  ] }),
708
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1659
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
709
1660
  "input",
710
1661
  {
711
1662
  type: "checkbox",
@@ -720,9 +1671,9 @@ function PatchConfigCard({ activation, initiallyOpen = false }) {
720
1671
  )
721
1672
  ] }, patch.id);
722
1673
  }) }),
723
- scope.status === "loading" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "dshmore-config-note", children: "\u6B63\u5728\u8BFB\u53D6\u63D2\u4EF6\u914D\u7F6E\u2026" }),
724
- scope.status === "ready" && !scope.writable && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "dshmore-config-note", children: "\u5F53\u524D Settings \u5B58\u50A8\u4E3A\u53EA\u8BFB\u3002" }),
725
- error !== null && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "dshmore-config-error", children: error })
1674
+ scope.status === "loading" && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "dshmore-config-note", children: "\u6B63\u5728\u8BFB\u53D6\u63D2\u4EF6\u914D\u7F6E\u2026" }),
1675
+ scope.status === "ready" && !scope.writable && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "dshmore-config-note", children: "\u5F53\u524D Settings \u5B58\u50A8\u4E3A\u53EA\u8BFB\u3002" }),
1676
+ error !== null && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "dshmore-config-error", children: error })
726
1677
  ] })
727
1678
  ] });
728
1679
  }
@@ -732,21 +1683,15 @@ function installPatchConfigCard(ctx, activation) {
732
1683
  key: PATCH_SETTINGS_NAMESPACE,
733
1684
  priority: 90,
734
1685
  registrant: PLUGIN_NAME
735
- }, (props) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PatchConfigCard, { ...props, activation })));
736
- }
737
-
738
- // src/kernel/message-visibility.ts
739
- var MESSAGE_VISIBILITY_PROJECTION_KEY = "dshMoreMessageDelete";
740
-
741
- // src/platform/dsh/client/message-targets.tsx
742
- var import_react5 = require("react");
743
-
744
- // src/platform/dsh/client/message-content.ts
745
- function contentText(content) {
746
- return content.filter((block) => block.type === "text").map((block) => block.text).join(" ").replace(/\s+/g, " ").trim();
1686
+ }, (props) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PatchConfigCard, { ...props, activation })));
747
1687
  }
748
1688
 
749
1689
  // src/platform/dsh/client/message-targets.tsx
1690
+ var import_react8 = require("react");
1691
+ var EMPTY_MESSAGE_SURFACE = {
1692
+ rows: [],
1693
+ byKey: /* @__PURE__ */ new Map()
1694
+ };
750
1695
  function messageNodeInfo(node) {
751
1696
  const candidate = node;
752
1697
  if (candidate?.kind === "user" && Number.isSafeInteger(candidate.data?.seq) && Array.isArray(candidate.data?.content)) {
@@ -775,6 +1720,38 @@ function presentationSeq(node) {
775
1720
  if (Number.isSafeInteger(closing?.finalNode?.seq)) return closing?.finalNode?.seq;
776
1721
  return void 0;
777
1722
  }
1723
+ function sameSurfaceRow(left, right) {
1724
+ return left !== void 0 && left.key === right.key && left.presentationSeq === right.presentationSeq && left.action?.seq === right.action?.seq && left.action?.kind === right.action?.kind && left.action?.text === right.action?.text;
1725
+ }
1726
+ function createMessageSurfaceSelector(active = true) {
1727
+ if (!active) return () => EMPTY_MESSAGE_SURFACE;
1728
+ const nodeCache = /* @__PURE__ */ new WeakMap();
1729
+ let previous = EMPTY_MESSAGE_SURFACE;
1730
+ let previousOrder;
1731
+ return (snapshot) => {
1732
+ if (snapshot.chat.order === previousOrder) return previous;
1733
+ previousOrder = snapshot.chat.order;
1734
+ const rows = [];
1735
+ for (const key of snapshot.chat.order) {
1736
+ const node = snapshot.chat.nodes.get(key);
1737
+ if (node === void 0) continue;
1738
+ let row = nodeCache.get(node);
1739
+ if (row === void 0) {
1740
+ row = {
1741
+ key,
1742
+ presentationSeq: presentationSeq(node),
1743
+ action: messageNodeInfo(node)
1744
+ };
1745
+ nodeCache.set(node, row);
1746
+ }
1747
+ const retained = previous.byKey.get(key);
1748
+ rows.push(sameSurfaceRow(retained, row) ? retained : row);
1749
+ }
1750
+ if (rows.length === previous.rows.length && rows.every((row, index) => row === previous.rows[index])) return previous;
1751
+ previous = { rows, byKey: new Map(rows.map((row) => [row.key, row])) };
1752
+ return previous;
1753
+ };
1754
+ }
778
1755
  function hostForRow(row, kind) {
779
1756
  const existing = row.querySelector(":scope [data-dshmore-message-actions]");
780
1757
  if (kind === "assistant") {
@@ -818,11 +1795,11 @@ function hostForRow(row, kind) {
818
1795
  else row.appendChild(host);
819
1796
  return host;
820
1797
  }
1798
+ function sameTarget(left, right) {
1799
+ return right !== void 0 && left.key === right.key && left.seq === right.seq && left.host === right.host && left.text === right.text;
1800
+ }
821
1801
  function sameTargets(left, right) {
822
- return left.length === right.length && left.every((target, index) => {
823
- const other = right[index];
824
- return other !== void 0 && target.key === other.key && target.seq === other.seq && target.host === other.host && target.text === other.text;
825
- });
1802
+ return left.length === right.length && left.every((target, index) => sameTarget(target, right[index]));
826
1803
  }
827
1804
  function trajectoryRecordKey(row) {
828
1805
  const encoded = row.dataset.trajectoryRowKey;
@@ -843,73 +1820,153 @@ function trajectorySourceSeq(row) {
843
1820
  const seq = Number(raw);
844
1821
  return Number.isSafeInteger(seq) && seq >= 0 ? seq : void 0;
845
1822
  }
846
- function useMessageTargets(props, hiddenSeqs, hiddenTrajectoryKeys) {
847
- const snapshot = props.useSession((state) => state);
848
- const [targets, setTargets] = (0, import_react5.useState)([]);
849
- (0, import_react5.useLayoutEffect)(() => {
1823
+ function useMessageTargets(props, hiddenSeqs, hiddenTrajectoryKeys, active = true) {
1824
+ const selector = (0, import_react8.useMemo)(() => createMessageSurfaceSelector(active), [active, props.sessionId]);
1825
+ const surface = props.useSession(selector);
1826
+ const surfaceRef = (0, import_react8.useRef)(surface);
1827
+ const hiddenSeqsRef = (0, import_react8.useRef)(hiddenSeqs);
1828
+ const hiddenTrajectoryKeysRef = (0, import_react8.useRef)(hiddenTrajectoryKeys);
1829
+ surfaceRef.current = surface;
1830
+ hiddenSeqsRef.current = hiddenSeqs;
1831
+ hiddenTrajectoryKeysRef.current = hiddenTrajectoryKeys;
1832
+ const [targets, setTargets] = (0, import_react8.useState)([]);
1833
+ const requestFullScanRef = (0, import_react8.useRef)(null);
1834
+ (0, import_react8.useLayoutEffect)(() => {
1835
+ if (!active) {
1836
+ setTargets((current) => current.length === 0 ? current : []);
1837
+ return;
1838
+ }
850
1839
  let frame = null;
851
- const scan = () => {
852
- frame = null;
853
- const next = [];
854
- for (const row of document.querySelectorAll("[data-chat-flow-key]")) {
855
- const key = row.dataset.chatFlowKey;
856
- if (key === void 0) continue;
857
- const node = snapshot.chat.nodes.get(key);
858
- const seq = presentationSeq(node);
859
- if (seq !== void 0 && hiddenSeqs.has(seq)) {
860
- row.dataset.dshmoreHidden = "";
861
- row.style.display = "none";
862
- row.querySelector("[data-dshmore-message-actions]")?.remove();
863
- continue;
864
- }
865
- if (row.dataset.dshmoreHidden !== void 0) {
866
- delete row.dataset.dshmoreHidden;
867
- row.style.removeProperty("display");
868
- }
869
- const info = messageNodeInfo(node);
870
- if (info !== null) next.push({ key, ...info, host: hostForRow(row, info.kind) });
1840
+ let fullScanRequested = true;
1841
+ let pruneDisconnected = false;
1842
+ let targetsDirty = false;
1843
+ const pendingChatRows = /* @__PURE__ */ new Set();
1844
+ const pendingTrajectoryRows = /* @__PURE__ */ new Set();
1845
+ const mountedTargets = /* @__PURE__ */ new Map();
1846
+ const processChatRow = (row) => {
1847
+ const key = row.dataset.chatFlowKey;
1848
+ if (key === void 0) return;
1849
+ const descriptor = surfaceRef.current.byKey.get(key);
1850
+ if (descriptor === void 0) {
1851
+ if (mountedTargets.delete(key)) targetsDirty = true;
1852
+ row.querySelector("[data-dshmore-message-actions]")?.remove();
1853
+ return;
871
1854
  }
872
- for (const row of document.querySelectorAll("[data-trajectory-row-key]")) {
873
- const seq = trajectorySourceSeq(row);
874
- const key = trajectoryRecordKey(row);
875
- if (seq !== void 0 && hiddenSeqs.has(seq) || key !== void 0 && hiddenTrajectoryKeys.has(key)) {
876
- row.dataset.dshmoreHidden = "";
877
- row.style.display = "none";
878
- } else if (row.dataset.dshmoreHidden !== void 0) {
879
- delete row.dataset.dshmoreHidden;
880
- row.style.removeProperty("display");
881
- }
1855
+ if (descriptor.presentationSeq !== void 0 && hiddenSeqsRef.current.has(descriptor.presentationSeq)) {
1856
+ row.dataset.dshmoreHidden = "";
1857
+ if (mountedTargets.delete(key)) targetsDirty = true;
1858
+ row.querySelector("[data-dshmore-message-actions]")?.remove();
1859
+ return;
1860
+ }
1861
+ if (row.dataset.dshmoreHidden !== void 0) delete row.dataset.dshmoreHidden;
1862
+ if (descriptor.action === null) {
1863
+ if (mountedTargets.delete(key)) targetsDirty = true;
1864
+ row.querySelector("[data-dshmore-message-actions]")?.remove();
1865
+ return;
882
1866
  }
1867
+ const next = {
1868
+ key,
1869
+ ...descriptor.action,
1870
+ host: hostForRow(row, descriptor.action.kind)
1871
+ };
1872
+ const current = mountedTargets.get(key);
1873
+ if (current === void 0 || !sameTarget(current, next)) {
1874
+ mountedTargets.set(key, next);
1875
+ targetsDirty = true;
1876
+ }
1877
+ };
1878
+ const processTrajectoryRow = (row) => {
1879
+ const seq = trajectorySourceSeq(row);
1880
+ const key = trajectoryRecordKey(row);
1881
+ if (seq !== void 0 && hiddenSeqsRef.current.has(seq) || key !== void 0 && hiddenTrajectoryKeysRef.current.has(key)) {
1882
+ row.dataset.dshmoreHidden = "";
1883
+ } else if (row.dataset.dshmoreHidden !== void 0) {
1884
+ delete row.dataset.dshmoreHidden;
1885
+ }
1886
+ };
1887
+ const publishTargets = () => {
1888
+ for (const [key, target] of mountedTargets) {
1889
+ if (!target.host.isConnected && mountedTargets.delete(key)) targetsDirty = true;
1890
+ }
1891
+ const next = surfaceRef.current.rows.flatMap((row) => {
1892
+ const target = mountedTargets.get(row.key);
1893
+ return target === void 0 ? [] : [target];
1894
+ });
883
1895
  setTargets((current) => sameTargets(current, next) ? current : next);
1896
+ targetsDirty = false;
1897
+ };
1898
+ const flush = () => {
1899
+ frame = null;
1900
+ if (fullScanRequested) {
1901
+ fullScanRequested = false;
1902
+ mountedTargets.clear();
1903
+ targetsDirty = true;
1904
+ for (const row of document.querySelectorAll("[data-chat-flow-key]")) processChatRow(row);
1905
+ for (const row of document.querySelectorAll("[data-trajectory-row-key]")) processTrajectoryRow(row);
1906
+ } else {
1907
+ for (const row of pendingChatRows) if (row.isConnected) processChatRow(row);
1908
+ for (const row of pendingTrajectoryRows) if (row.isConnected) processTrajectoryRow(row);
1909
+ }
1910
+ pendingChatRows.clear();
1911
+ pendingTrajectoryRows.clear();
1912
+ const shouldPrune = pruneDisconnected;
1913
+ pruneDisconnected = false;
1914
+ if (targetsDirty || shouldPrune) publishTargets();
884
1915
  };
885
1916
  const schedule = () => {
886
- if (frame === null) frame = window.requestAnimationFrame(scan);
1917
+ if (frame === null) frame = window.requestAnimationFrame(flush);
887
1918
  };
888
- scan();
889
- const observer = new MutationObserver(schedule);
1919
+ const requestFullScan = () => {
1920
+ fullScanRequested = true;
1921
+ schedule();
1922
+ };
1923
+ const collectRows = (node) => {
1924
+ const element = node instanceof Element ? node : node.parentElement;
1925
+ if (element === null) return;
1926
+ const chatAncestor = element.closest("[data-chat-flow-key]");
1927
+ if (chatAncestor !== null) pendingChatRows.add(chatAncestor);
1928
+ const trajectoryAncestor = element.closest("[data-trajectory-row-key]");
1929
+ if (trajectoryAncestor !== null) pendingTrajectoryRows.add(trajectoryAncestor);
1930
+ if (element.matches("[data-chat-flow-key]")) pendingChatRows.add(element);
1931
+ if (element.matches("[data-trajectory-row-key]")) pendingTrajectoryRows.add(element);
1932
+ element.querySelectorAll("[data-chat-flow-key]").forEach((row) => pendingChatRows.add(row));
1933
+ element.querySelectorAll("[data-trajectory-row-key]").forEach((row) => pendingTrajectoryRows.add(row));
1934
+ };
1935
+ requestFullScanRef.current = requestFullScan;
1936
+ const observer = new MutationObserver((records) => {
1937
+ for (const record of records) {
1938
+ if (record.removedNodes.length > 0) pruneDisconnected = true;
1939
+ record.addedNodes.forEach(collectRows);
1940
+ }
1941
+ if (pendingChatRows.size > 0 || pendingTrajectoryRows.size > 0 || pruneDisconnected) schedule();
1942
+ });
890
1943
  observer.observe(document.body, { childList: true, subtree: true });
1944
+ flush();
891
1945
  return () => {
892
1946
  observer.disconnect();
893
1947
  if (frame !== null) window.cancelAnimationFrame(frame);
1948
+ requestFullScanRef.current = null;
894
1949
  for (const row of document.querySelectorAll("[data-dshmore-hidden]")) {
895
1950
  delete row.dataset.dshmoreHidden;
896
- row.style.removeProperty("display");
897
1951
  }
898
1952
  for (const host of document.querySelectorAll("[data-dshmore-message-actions]")) host.remove();
899
1953
  };
900
- }, [snapshot, hiddenSeqs, hiddenTrajectoryKeys]);
1954
+ }, [active]);
1955
+ (0, import_react8.useLayoutEffect)(() => {
1956
+ requestFullScanRef.current?.();
1957
+ }, [surface, hiddenSeqs, hiddenTrajectoryKeys]);
901
1958
  return targets;
902
1959
  }
903
1960
 
904
1961
  // src/client/index.tsx
905
- var import_jsx_runtime5 = require("react/jsx-runtime");
1962
+ var import_jsx_runtime8 = require("react/jsx-runtime");
906
1963
  var inject = ["slots", "sessions", "workspaces", "settingsScope"];
907
1964
  function MessageActionPatchController({ patch, props, targets, enabled }) {
908
1965
  const actions = patch.useActions(props, enabled);
909
1966
  const style = { "--dshmore-action-order": patch.order };
910
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
911
- enabled && targets.map((target) => (0, import_react_dom.createPortal)(
912
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "dshmore-action-contribution", style, children: actions.renderAction(target) }),
1967
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
1968
+ enabled && targets.map((target) => (0, import_react_dom2.createPortal)(
1969
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "dshmore-action-contribution", style, children: actions.renderAction(target) }),
913
1970
  target.host,
914
1971
  `${patch.id}:${target.key}`
915
1972
  )),
@@ -917,14 +1974,15 @@ function MessageActionPatchController({ patch, props, targets, enabled }) {
917
1974
  ] });
918
1975
  }
919
1976
  function MessageActionsController(props) {
920
- const settings = (0, import_react6.useSyncExternalStore)(props.activation.subscribe, props.activation.getSnapshot);
1977
+ const settings = (0, import_react9.useSyncExternalStore)(props.activation.subscribe, props.activation.getSnapshot);
921
1978
  const projected = props.useProjection(MESSAGE_VISIBILITY_PROJECTION_KEY);
922
- const hiddenSeqs = (0, import_react6.useMemo)(() => new Set(projected?.deletedSeqs ?? []), [projected]);
923
- const hiddenTrajectoryKeys = (0, import_react6.useMemo)(() => new Set(projected?.hiddenTrajectoryKeys ?? []), [projected]);
924
- const targets = useMessageTargets(props, hiddenSeqs, hiddenTrajectoryKeys);
925
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
926
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("style", { children: styles }),
927
- MESSAGE_ACTION_PATCHES.map((patch) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1979
+ const hiddenSeqs = (0, import_react9.useMemo)(() => new Set(projected?.deletedSeqs ?? []), [projected]);
1980
+ const hiddenTrajectoryKeys = (0, import_react9.useMemo)(() => new Set(projected?.hiddenTrajectoryKeys ?? []), [projected]);
1981
+ const messageActionsEnabled = MESSAGE_ACTION_PATCHES.some((patch) => settings[patch.id]);
1982
+ const targets = useMessageTargets(props, hiddenSeqs, hiddenTrajectoryKeys, messageActionsEnabled);
1983
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
1984
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("style", { children: styles }),
1985
+ MESSAGE_ACTION_PATCHES.map((patch) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
928
1986
  MessageActionPatchController,
929
1987
  {
930
1988
  patch,
@@ -948,7 +2006,7 @@ function apply(ctx) {
948
2006
  id: `${PLUGIN_NAME}-message-actions`,
949
2007
  order: 90,
950
2008
  registrant: PLUGIN_NAME
951
- }, (props) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MessageActionsController, { ...props, ctx, activation })));
2009
+ }, (props) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(MessageActionsController, { ...props, ctx, activation })));
952
2010
  for (const patch of CLIENT_PATCHES) patch.install(ctx, activation);
953
2011
  }
954
2012
  return module.exports; } });