dsh-more 0.0.2 → 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.
package/dist/client.js CHANGED
@@ -25,21 +25,25 @@ __export(index_exports, {
25
25
  inject: () => inject
26
26
  });
27
27
  module.exports = __toCommonJS(index_exports);
28
- var import_react7 = 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
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" }
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" }
37
39
  ];
38
40
  var DEFAULT_PATCH_SETTINGS = {
39
41
  "message-edit": true,
40
42
  "message-delete": true,
41
43
  "session-delete": true,
42
- "conversation-markdown-export": true
44
+ "conversation-markdown-export": true,
45
+ "model-reasoning-efforts": true,
46
+ "conversation-quick-navigation": true
43
47
  };
44
48
  function decodePatchSettings(value) {
45
49
  if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
@@ -72,7 +76,7 @@ var DshMoreApiError = class extends Error {
72
76
  }
73
77
  code;
74
78
  };
75
- async function callPatchApi(patchId, action, payload) {
79
+ async function requestPatch(patchId, action, payload) {
76
80
  let response;
77
81
  try {
78
82
  response = await fetch(`${PLUGIN_API_PREFIX}/${encodeURIComponent(patchId)}/${encodeURIComponent(action)}`, {
@@ -86,12 +90,28 @@ async function callPatchApi(patchId, action, payload) {
86
90
  } catch (error) {
87
91
  throw new DshMoreApiError("network", error instanceof Error ? error.message : String(error));
88
92
  }
93
+ return response;
94
+ }
95
+ async function responseError(response) {
89
96
  const body = await response.json().catch(() => null);
90
- if (!response.ok || body?.ok !== true || body.value === void 0) {
91
- throw new DshMoreApiError(body?.error?.code ?? "http", body?.error?.message ?? `HTTP ${String(response.status)}`);
92
- }
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);
93
103
  return body.value;
94
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
+ }
95
115
  function apiErrorText(error) {
96
116
  if (error instanceof DshMoreApiError || error instanceof Error) return error.message;
97
117
  return String(error);
@@ -176,10 +196,16 @@ async function settleSessionRemoval(sessions, workspaces, sessionId) {
176
196
  function adjacentVisibleSession(sessions, workspaces, deletingSessionId) {
177
197
  if (sessions.current !== deletingSessionId) return void 0;
178
198
  const archived = new Set(workspaces.archivedSessionIds);
179
- const visible = sessions.ids.filter((id) => id !== deletingSessionId && !archived.has(id));
180
- if (visible.length === 0) return void 0;
181
199
  const deletingIndex = sessions.ids.indexOf(deletingSessionId);
182
- 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;
183
209
  }
184
210
 
185
211
  // src/patches/message-edit/shared.ts
@@ -439,44 +465,71 @@ function replaceExactText(root, before, after) {
439
465
  }
440
466
  function installSessionDeleteMenuItems(onDelete) {
441
467
  let frame = null;
442
- const deleteItems = /* @__PURE__ */ new WeakMap();
443
- const sync = () => {
444
- for (const archiveButton of document.querySelectorAll('button[role="menuitem"]')) {
445
- const text = archiveButton.textContent?.trim();
446
- const deleteLabel = text === "\u5F52\u6863\u4F1A\u8BDD" ? "\u6C38\u4E45\u5220\u9664\u4F1A\u8BDD" : text === "Archive session" ? "Permanently delete session" : void 0;
447
- if (deleteLabel === void 0) continue;
448
- const wrapper = archiveButton.parentElement;
449
- if (wrapper === null) continue;
450
- if (archiveButton.hasAttribute(DELETE_SOURCE_ATTRIBUTE)) {
451
- if (deleteItems.get(archiveButton)?.isConnected === true) continue;
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) {
452
511
  archiveButton.removeAttribute(DELETE_SOURCE_ATTRIBUTE);
512
+ deleteItems.delete(archiveButton);
513
+ pendingButtons.add(archiveButton);
453
514
  }
454
- const clone = wrapper.cloneNode(true);
455
- const deleteButton = clone.querySelector('button[role="menuitem"]');
456
- if (deleteButton === null) continue;
457
- archiveButton.setAttribute(DELETE_SOURCE_ATTRIBUTE, "");
458
- clone.setAttribute(DELETE_MENU_ATTRIBUTE, "");
459
- deleteItems.set(archiveButton, clone);
460
- replaceExactText(deleteButton, text, deleteLabel);
461
- deleteButton.setAttribute("aria-label", deleteLabel);
462
- deleteButton.classList.add("dshmore-session-delete-menu-item");
463
- deleteButton.addEventListener("click", (event) => {
464
- event.preventDefault();
465
- event.stopImmediatePropagation();
466
- onDelete(archiveButton);
467
- });
468
- wrapper.after(clone);
469
515
  }
516
+ for (const button of pendingButtons) syncButton(button);
517
+ pendingButtons.clear();
470
518
  };
471
519
  const schedule = () => {
472
520
  if (frame !== null) return;
473
- frame = window.requestAnimationFrame(() => {
474
- frame = null;
475
- sync();
476
- });
521
+ frame = window.requestAnimationFrame(flush);
477
522
  };
478
- sync();
479
- 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
+ });
480
533
  observer.observe(document.body, { childList: true, subtree: true });
481
534
  return () => {
482
535
  observer.disconnect();
@@ -492,12 +545,6 @@ function installSessionDeleteMenuItems(onDelete) {
492
545
  function SessionDeleteController(props) {
493
546
  const settings = (0, import_react3.useSyncExternalStore)(props.activation.subscribe, props.activation.getSnapshot);
494
547
  const enabled = settings[SESSION_DELETE_PATCH_ID];
495
- const sessionList = props.useSessions((state) => state);
496
- const sessionListRef = (0, import_react3.useRef)(sessionList);
497
- sessionListRef.current = sessionList;
498
- const workspaceList = props.useWorkspaces((state) => state);
499
- const workspaceListRef = (0, import_react3.useRef)(workspaceList);
500
- workspaceListRef.current = workspaceList;
501
548
  const [pending, setPending] = (0, import_react3.useState)(null);
502
549
  const pendingRef = (0, import_react3.useRef)(null);
503
550
  const deleteIntentRef = (0, import_react3.useRef)(false);
@@ -506,13 +553,18 @@ function SessionDeleteController(props) {
506
553
  (0, import_react3.useEffect)(() => {
507
554
  if (!enabled) return;
508
555
  const workspaces = props.ctx.workspaces;
556
+ const sessions = props.ctx.sessions;
509
557
  const mutable = workspaces;
510
558
  const original = mutable.archiveSession;
511
559
  mutable.archiveSession = (sessionId) => {
512
560
  if (!deleteIntentRef.current) return original.call(mutable, sessionId);
513
561
  deleteIntentRef.current = false;
514
562
  return new Promise((resolve) => {
515
- const request = { sessionId, resolve };
563
+ const request = {
564
+ sessionId,
565
+ title: sessions.list.getSnapshot().byId[sessionId]?.displayTitle ?? String(sessionId),
566
+ resolve
567
+ };
516
568
  pendingRef.current?.resolve();
517
569
  pendingRef.current = request;
518
570
  setError(null);
@@ -548,8 +600,8 @@ function SessionDeleteController(props) {
548
600
  const sessions = props.ctx.sessions;
549
601
  const workspaces = props.ctx.workspaces;
550
602
  const fallbackSessionId = adjacentVisibleSession(
551
- sessionListRef.current,
552
- workspaceListRef.current,
603
+ sessions.list.getSnapshot(),
604
+ workspaces.list.getSnapshot(),
553
605
  pending.sessionId
554
606
  );
555
607
  if (fallbackSessionId !== void 0) openSessionWithoutGap(sessions, fallbackSessionId);
@@ -568,7 +620,7 @@ function SessionDeleteController(props) {
568
620
  setBusy(false);
569
621
  }
570
622
  };
571
- const title = pending === null ? "" : sessionListRef.current.byId[pending.sessionId]?.displayTitle ?? pending.sessionId;
623
+ const title = pending?.title ?? "";
572
624
  if (!enabled) return null;
573
625
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
574
626
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("style", { children: styles + sessionDeleteStyles }),
@@ -665,13 +717,13 @@ function ConversationMarkdownExportButton(props) {
665
717
  setBusy(true);
666
718
  setError(null);
667
719
  try {
668
- const result = await callPatchApi(
720
+ const markdown = await callPatchBlobApi(
669
721
  CONVERSATION_MARKDOWN_EXPORT_PATCH_ID,
670
722
  "render",
671
723
  { sessionId: props.sessionId, title }
672
724
  );
673
725
  if (generation.current !== requestGeneration || activeSession.current !== requestSessionId) return;
674
- saveMarkdown(markdownFilename(title, String(props.sessionId)), result.markdown);
726
+ saveMarkdown(markdownFilename(title, String(props.sessionId)), markdown);
675
727
  } catch (caught) {
676
728
  if (generation.current === requestGeneration && activeSession.current === requestSessionId) setError(apiErrorText(caught));
677
729
  } finally {
@@ -723,9 +775,807 @@ var clientPatch2 = {
723
775
  }
724
776
  };
725
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
+
726
1576
  // src/generated/client-registry.ts
727
1577
  var MESSAGE_ACTION_PATCHES = [{ id: "message-edit", order: 100, useActions: useMessageActions }, { id: "message-delete", order: 200, useActions: useMessageActions2 }];
728
- var CLIENT_PATCHES = [clientPatch, clientPatch2];
1578
+ var CLIENT_PATCHES = [clientPatch, clientPatch2, clientPatch3, clientPatch4];
729
1579
 
730
1580
  // src/kernel/client/activation.ts
731
1581
  var ClientPatchActivation = class {
@@ -748,19 +1598,19 @@ var ClientPatchActivation = class {
748
1598
  };
749
1599
 
750
1600
  // src/kernel/client/config-card.tsx
751
- var import_react5 = require("react");
752
- var import_dsh_client_ui_primitives5 = require("@deepseek-ai/dsh-client-ui-primitives");
753
- var import_jsx_runtime5 = 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");
754
1604
  function PatchConfigCard({ activation, initiallyOpen = false }) {
755
- const scope = (0, import_react5.useSyncExternalStore)(
1605
+ const scope = (0, import_react7.useSyncExternalStore)(
756
1606
  activation.subscribe,
757
1607
  activation.getSettingsSnapshot,
758
1608
  activation.getSettingsSnapshot
759
1609
  );
760
1610
  const settings = activation.getSnapshot();
761
- const [open, setOpen] = (0, import_react5.useState)(initiallyOpen);
762
- const [busy, setBusy] = (0, import_react5.useState)(/* @__PURE__ */ new Set());
763
- const [error, setError] = (0, import_react5.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);
764
1614
  if (scope.status === "unavailable") return null;
765
1615
  const update = async (id, enabled) => {
766
1616
  setBusy((current) => /* @__PURE__ */ new Set([...current, id]));
@@ -777,9 +1627,9 @@ function PatchConfigCard({ activation, initiallyOpen = false }) {
777
1627
  });
778
1628
  }
779
1629
  };
780
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("li", { className: `dshmore-config-card${open ? " dshmore-config-card-open" : ""}`, children: [
781
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("style", { children: styles }),
782
- /* @__PURE__ */ (0, import_jsx_runtime5.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)(
783
1633
  "button",
784
1634
  {
785
1635
  type: "button",
@@ -790,23 +1640,23 @@ function PatchConfigCard({ activation, initiallyOpen = false }) {
790
1640
  setOpen((current) => !current);
791
1641
  },
792
1642
  children: [
793
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "dshmore-config-heading-copy", children: [
794
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("strong", { children: "DSH More" }),
795
- /* @__PURE__ */ (0, import_jsx_runtime5.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" })
796
1646
  ] }),
797
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_dsh_client_ui_primitives5.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" : ""}` })
798
1648
  ]
799
1649
  }
800
1650
  ),
801
- open && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "dshmore-config-body", children: [
802
- /* @__PURE__ */ (0, import_jsx_runtime5.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) => {
803
1653
  const waiting = busy.has(patch.id);
804
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "dshmore-config-row", children: [
805
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "dshmore-config-copy", children: [
806
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("strong", { children: patch.name }),
807
- /* @__PURE__ */ (0, import_jsx_runtime5.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 })
808
1658
  ] }),
809
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1659
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
810
1660
  "input",
811
1661
  {
812
1662
  type: "checkbox",
@@ -821,9 +1671,9 @@ function PatchConfigCard({ activation, initiallyOpen = false }) {
821
1671
  )
822
1672
  ] }, patch.id);
823
1673
  }) }),
824
- scope.status === "loading" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "dshmore-config-note", children: "\u6B63\u5728\u8BFB\u53D6\u63D2\u4EF6\u914D\u7F6E\u2026" }),
825
- scope.status === "ready" && !scope.writable && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "dshmore-config-note", children: "\u5F53\u524D Settings \u5B58\u50A8\u4E3A\u53EA\u8BFB\u3002" }),
826
- error !== null && /* @__PURE__ */ (0, import_jsx_runtime5.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 })
827
1677
  ] })
828
1678
  ] });
829
1679
  }
@@ -833,21 +1683,15 @@ function installPatchConfigCard(ctx, activation) {
833
1683
  key: PATCH_SETTINGS_NAMESPACE,
834
1684
  priority: 90,
835
1685
  registrant: PLUGIN_NAME
836
- }, (props) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PatchConfigCard, { ...props, activation })));
837
- }
838
-
839
- // src/kernel/message-visibility.ts
840
- var MESSAGE_VISIBILITY_PROJECTION_KEY = "dshMoreMessageDelete";
841
-
842
- // src/platform/dsh/client/message-targets.tsx
843
- var import_react6 = require("react");
844
-
845
- // src/platform/dsh/client/message-content.ts
846
- function contentText(content) {
847
- 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 })));
848
1687
  }
849
1688
 
850
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
+ };
851
1695
  function messageNodeInfo(node) {
852
1696
  const candidate = node;
853
1697
  if (candidate?.kind === "user" && Number.isSafeInteger(candidate.data?.seq) && Array.isArray(candidate.data?.content)) {
@@ -876,6 +1720,38 @@ function presentationSeq(node) {
876
1720
  if (Number.isSafeInteger(closing?.finalNode?.seq)) return closing?.finalNode?.seq;
877
1721
  return void 0;
878
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
+ }
879
1755
  function hostForRow(row, kind) {
880
1756
  const existing = row.querySelector(":scope [data-dshmore-message-actions]");
881
1757
  if (kind === "assistant") {
@@ -919,11 +1795,11 @@ function hostForRow(row, kind) {
919
1795
  else row.appendChild(host);
920
1796
  return host;
921
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
+ }
922
1801
  function sameTargets(left, right) {
923
- return left.length === right.length && left.every((target, index) => {
924
- const other = right[index];
925
- return other !== void 0 && target.key === other.key && target.seq === other.seq && target.host === other.host && target.text === other.text;
926
- });
1802
+ return left.length === right.length && left.every((target, index) => sameTarget(target, right[index]));
927
1803
  }
928
1804
  function trajectoryRecordKey(row) {
929
1805
  const encoded = row.dataset.trajectoryRowKey;
@@ -944,68 +1820,153 @@ function trajectorySourceSeq(row) {
944
1820
  const seq = Number(raw);
945
1821
  return Number.isSafeInteger(seq) && seq >= 0 ? seq : void 0;
946
1822
  }
947
- function useMessageTargets(props, hiddenSeqs, hiddenTrajectoryKeys) {
948
- const snapshot = props.useSession((state) => state);
949
- const [targets, setTargets] = (0, import_react6.useState)([]);
950
- (0, import_react6.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
+ }
951
1839
  let frame = null;
952
- const scan = () => {
953
- frame = null;
954
- const next = [];
955
- for (const row of document.querySelectorAll("[data-chat-flow-key]")) {
956
- const key = row.dataset.chatFlowKey;
957
- if (key === void 0) continue;
958
- const node = snapshot.chat.nodes.get(key);
959
- const seq = presentationSeq(node);
960
- if (seq !== void 0 && hiddenSeqs.has(seq)) {
961
- row.dataset.dshmoreHidden = "";
962
- row.querySelector("[data-dshmore-message-actions]")?.remove();
963
- continue;
964
- }
965
- if (row.dataset.dshmoreHidden !== void 0) {
966
- delete row.dataset.dshmoreHidden;
967
- }
968
- const info = messageNodeInfo(node);
969
- 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;
970
1854
  }
971
- for (const row of document.querySelectorAll("[data-trajectory-row-key]")) {
972
- const seq = trajectorySourceSeq(row);
973
- const key = trajectoryRecordKey(row);
974
- if (seq !== void 0 && hiddenSeqs.has(seq) || key !== void 0 && hiddenTrajectoryKeys.has(key)) {
975
- row.dataset.dshmoreHidden = "";
976
- } else if (row.dataset.dshmoreHidden !== void 0) {
977
- delete row.dataset.dshmoreHidden;
978
- }
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;
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;
979
1890
  }
1891
+ const next = surfaceRef.current.rows.flatMap((row) => {
1892
+ const target = mountedTargets.get(row.key);
1893
+ return target === void 0 ? [] : [target];
1894
+ });
980
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();
981
1915
  };
982
1916
  const schedule = () => {
983
- if (frame === null) frame = window.requestAnimationFrame(scan);
1917
+ if (frame === null) frame = window.requestAnimationFrame(flush);
984
1918
  };
985
- scan();
986
- 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
+ });
987
1943
  observer.observe(document.body, { childList: true, subtree: true });
1944
+ flush();
988
1945
  return () => {
989
1946
  observer.disconnect();
990
1947
  if (frame !== null) window.cancelAnimationFrame(frame);
1948
+ requestFullScanRef.current = null;
991
1949
  for (const row of document.querySelectorAll("[data-dshmore-hidden]")) {
992
1950
  delete row.dataset.dshmoreHidden;
993
1951
  }
994
1952
  for (const host of document.querySelectorAll("[data-dshmore-message-actions]")) host.remove();
995
1953
  };
996
- }, [snapshot, hiddenSeqs, hiddenTrajectoryKeys]);
1954
+ }, [active]);
1955
+ (0, import_react8.useLayoutEffect)(() => {
1956
+ requestFullScanRef.current?.();
1957
+ }, [surface, hiddenSeqs, hiddenTrajectoryKeys]);
997
1958
  return targets;
998
1959
  }
999
1960
 
1000
1961
  // src/client/index.tsx
1001
- var import_jsx_runtime6 = require("react/jsx-runtime");
1962
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1002
1963
  var inject = ["slots", "sessions", "workspaces", "settingsScope"];
1003
1964
  function MessageActionPatchController({ patch, props, targets, enabled }) {
1004
1965
  const actions = patch.useActions(props, enabled);
1005
1966
  const style = { "--dshmore-action-order": patch.order };
1006
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1007
- enabled && targets.map((target) => (0, import_react_dom.createPortal)(
1008
- /* @__PURE__ */ (0, import_jsx_runtime6.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) }),
1009
1970
  target.host,
1010
1971
  `${patch.id}:${target.key}`
1011
1972
  )),
@@ -1013,14 +1974,15 @@ function MessageActionPatchController({ patch, props, targets, enabled }) {
1013
1974
  ] });
1014
1975
  }
1015
1976
  function MessageActionsController(props) {
1016
- const settings = (0, import_react7.useSyncExternalStore)(props.activation.subscribe, props.activation.getSnapshot);
1977
+ const settings = (0, import_react9.useSyncExternalStore)(props.activation.subscribe, props.activation.getSnapshot);
1017
1978
  const projected = props.useProjection(MESSAGE_VISIBILITY_PROJECTION_KEY);
1018
- const hiddenSeqs = (0, import_react7.useMemo)(() => new Set(projected?.deletedSeqs ?? []), [projected]);
1019
- const hiddenTrajectoryKeys = (0, import_react7.useMemo)(() => new Set(projected?.hiddenTrajectoryKeys ?? []), [projected]);
1020
- const targets = useMessageTargets(props, hiddenSeqs, hiddenTrajectoryKeys);
1021
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1022
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("style", { children: styles }),
1023
- MESSAGE_ACTION_PATCHES.map((patch) => /* @__PURE__ */ (0, import_jsx_runtime6.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)(
1024
1986
  MessageActionPatchController,
1025
1987
  {
1026
1988
  patch,
@@ -1044,7 +2006,7 @@ function apply(ctx) {
1044
2006
  id: `${PLUGIN_NAME}-message-actions`,
1045
2007
  order: 90,
1046
2008
  registrant: PLUGIN_NAME
1047
- }, (props) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MessageActionsController, { ...props, ctx, activation })));
2009
+ }, (props) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(MessageActionsController, { ...props, ctx, activation })));
1048
2010
  for (const patch of CLIENT_PATCHES) patch.install(ctx, activation);
1049
2011
  }
1050
2012
  return module.exports; } });