dsh-ui-fortifier 0.5.1 → 0.5.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/README.md CHANGED
@@ -24,6 +24,7 @@ Adds a "ui-fortifier" tab in Settings to toggle every feature module of this plu
24
24
  | --- | --- |
25
25
  | provider-label | Shows the currently selected provider to the left of the model selector. |
26
26
  | model-picker | Cascading model picker (provider → model) next to the model selector in the input row. |
27
+ | session-id-copy | Copy-session-ID button in the Session header's utility group. |
27
28
  | open-dsh-folder | Adds an "Open .dsh folder" button next to "Open configuration file" at the top-right of the Settings page. |
28
29
  | settings-frame | Makes the Settings panel draggable and resizable. |
29
30
 
@@ -38,7 +39,7 @@ Clone this repository into a dsh workspace package directory, then install and b
38
39
  ```sh
39
40
  git clone https://github.com/pgnqukezrdxmhjso/dsh-ui-fortifier packages/my/dsh-ui-fortifier
40
41
  pnpm install
41
- pnpm --filter dsh-ui-fortifier bundle
42
+ pnpm --filter dsh-ui-fortifier build
42
43
  ```
43
44
 
44
45
  ---
package/README.zh.md CHANGED
@@ -24,6 +24,7 @@ dsh plugin --profile web add dsh-ui-fortifier
24
24
  | --- | --- |
25
25
  | provider-label | 在模型选择按钮左侧显示当前选择的提供方。 |
26
26
  | model-picker | 输入行模型选择器旁提供级联模型选择器(提供商 → 模型)。 |
27
+ | session-id-copy | 会话标题栏工具区提供复制会话 ID 按钮。 |
27
28
  | open-dsh-folder | 在设置页右上角「打开配置文件」旁增加「打开 .dsh 文件夹」。 |
28
29
  | settings-frame | 设置面板可拖动位置与拖动大小。 |
29
30
 
@@ -38,7 +39,7 @@ dsh plugin --profile web add dsh-ui-fortifier
38
39
  ```sh
39
40
  git clone https://github.com/pgnqukezrdxmhjso/dsh-ui-fortifier packages/my/dsh-ui-fortifier
40
41
  pnpm install
41
- pnpm --filter dsh-ui-fortifier bundle
42
+ pnpm --filter dsh-ui-fortifier build
42
43
  ```
43
44
 
44
45
  ---
package/lib/client.js CHANGED
@@ -9,13 +9,13 @@ window.__ModuleLoader__.load({
9
9
  let react = require("react");
10
10
  let react_dom = require("react-dom");
11
11
  //#region \0dsh-css:D:\data\code\deepseek-harness\packages\my\dsh-ui-fortifier\src\client\settings\FortifierSettingsPage.module.css.mjs
12
- const css$4 = ".t1_2kq_page{flex-direction:column;gap:8px;max-width:520px;padding:16px;display:flex}.t1_2kq_heading{color:var(--dsw-alias-label-primary);margin:0;font-size:16px;line-height:24px}.t1_2kq_list{flex-direction:column;margin:0;padding:0;list-style:none;display:flex}.t1_2kq_row{justify-content:space-between;align-items:center;gap:12px;padding:10px 12px;display:flex}.t1_2kq_row+.t1_2kq_row{border-top:.5px solid var(--dsw-alias-border-l3)}.t1_2kq_row:hover{background:var(--dsw-alias-interactive-bg-hover)}.t1_2kq_rowLabel{color:var(--dsw-alias-label-primary);font-size:13px;line-height:20px}.t1_2kq_switch{box-sizing:border-box;background:var(--dsw-alias-border-l3);cursor:pointer;border:0;border-radius:10px;flex:none;width:36px;height:20px;padding:2px;position:relative}.t1_2kq_switchOn{background:var(--dsw-alias-brand-primary)}.t1_2kq_switch:disabled{cursor:default;opacity:.5}.t1_2kq_switch:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.t1_2kq_thumb{corner-shape:round;background:var(--dsw-alias-label-primary-foreground);border-radius:50%;width:16px;height:16px;transition:transform .12s;display:block}.t1_2kq_switchOn .t1_2kq_thumb{transform:translate(16px)}";
13
- const tagId$4 = "dsh-ui-fortifier/FortifierSettingsPage.module.css";
14
- if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$4) + "]") === null) {
12
+ const css$5 = ".t1_2kq_page{flex-direction:column;gap:8px;max-width:520px;padding:16px;display:flex}.t1_2kq_heading{color:var(--dsw-alias-label-primary);margin:0;font-size:16px;line-height:24px}.t1_2kq_list{flex-direction:column;margin:0;padding:0;list-style:none;display:flex}.t1_2kq_row{justify-content:space-between;align-items:center;gap:12px;padding:10px 12px;display:flex}.t1_2kq_row+.t1_2kq_row{border-top:.5px solid var(--dsw-alias-border-l3)}.t1_2kq_row:hover{background:var(--dsw-alias-interactive-bg-hover)}.t1_2kq_rowLabel{color:var(--dsw-alias-label-primary);font-size:13px;line-height:20px}.t1_2kq_switch{box-sizing:border-box;background:var(--dsw-alias-border-l3);cursor:pointer;border:0;border-radius:10px;flex:none;width:36px;height:20px;padding:2px;position:relative}.t1_2kq_switchOn{background:var(--dsw-alias-brand-primary)}.t1_2kq_switch:disabled{cursor:default;opacity:.5}.t1_2kq_switch:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.t1_2kq_thumb{corner-shape:round;background:var(--dsw-alias-label-primary-foreground);border-radius:50%;width:16px;height:16px;transition:transform .12s;display:block}.t1_2kq_switchOn .t1_2kq_thumb{transform:translate(16px)}";
13
+ const tagId$5 = "dsh-ui-fortifier/FortifierSettingsPage.module.css";
14
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$5) + "]") === null) {
15
15
  const tag = document.createElement("style");
16
16
  tag.dataset.plugin = "dsh-ui-fortifier";
17
- tag.dataset.pluginCss = tagId$4;
18
- tag.textContent = css$4;
17
+ tag.dataset.pluginCss = tagId$5;
18
+ tag.textContent = css$5;
19
19
  document.head.appendChild(tag);
20
20
  }
21
21
  var FortifierSettingsPage_module_css_default = {
@@ -74,6 +74,7 @@ window.__ModuleLoader__.load({
74
74
  "config.open-dsh-folder": "Settings top-right - Open .dsh folder",
75
75
  "config.settings-frame": "Settings panel - Drag and resize",
76
76
  "config.model-picker": "Input - Cascading model picker",
77
+ "config.session-id-copy": "Session header - Copy session ID",
77
78
  openDshFolder: "Open .dsh folder",
78
79
  "openDshFolder.error": "Could not open the .dsh folder",
79
80
  "openDshFolder.pathLabel": "Path: ",
@@ -90,7 +91,10 @@ window.__ModuleLoader__.load({
90
91
  "modelPicker.loadError": "Failed to load models",
91
92
  "modelPicker.retry": "Retry",
92
93
  "modelPicker.provider": "Provider: {name}",
93
- "modelPicker.model": "Model: {name}"
94
+ "modelPicker.model": "Model: {name}",
95
+ "sessionIdCopy.action": "Copy Session ID",
96
+ "sessionIdCopy.hint": "Session ID: {id}",
97
+ "sessionIdCopy.copied": "Copied"
94
98
  };
95
99
  /** 中文文案。 */
96
100
  const zh = {
@@ -100,6 +104,7 @@ window.__ModuleLoader__.load({
100
104
  "config.open-dsh-folder": "设置右上角 - 打开 .dsh 文件夹",
101
105
  "config.settings-frame": "设置面板 - 拖动与缩放",
102
106
  "config.model-picker": "输入框 - 级联模型选择器",
107
+ "config.session-id-copy": "会话标题栏 - 复制 Session ID",
103
108
  openDshFolder: "打开 .dsh 文件夹",
104
109
  "openDshFolder.error": "无法打开 .dsh 文件夹",
105
110
  "openDshFolder.pathLabel": "路径:",
@@ -116,7 +121,10 @@ window.__ModuleLoader__.load({
116
121
  "modelPicker.loadError": "模型加载失败",
117
122
  "modelPicker.retry": "重试",
118
123
  "modelPicker.provider": "提供商:{name}",
119
- "modelPicker.model": "模型:{name}"
124
+ "modelPicker.model": "模型:{name}",
125
+ "sessionIdCopy.action": "复制 Session ID",
126
+ "sessionIdCopy.hint": "Session ID: {id}",
127
+ "sessionIdCopy.copied": "已复制"
120
128
  };
121
129
  //#endregion
122
130
  //#region src/client/settings/index.ts
@@ -180,13 +188,13 @@ window.__ModuleLoader__.load({
180
188
  }
181
189
  //#endregion
182
190
  //#region \0dsh-css:D:\data\code\deepseek-harness\packages\my\dsh-ui-fortifier\src\client\provider-label\ProviderLabel.module.css.mjs
183
- const css$3 = ".LjXNpG_label{text-overflow:ellipsis;white-space:nowrap;max-width:160px;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px;overflow:hidden}";
184
- const tagId$3 = "dsh-ui-fortifier/ProviderLabel.module.css";
185
- if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$3) + "]") === null) {
191
+ const css$4 = ".LjXNpG_label{text-overflow:ellipsis;white-space:nowrap;max-width:160px;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px;overflow:hidden}";
192
+ const tagId$4 = "dsh-ui-fortifier/ProviderLabel.module.css";
193
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$4) + "]") === null) {
186
194
  const tag = document.createElement("style");
187
195
  tag.dataset.plugin = "dsh-ui-fortifier";
188
- tag.dataset.pluginCss = tagId$3;
189
- tag.textContent = css$3;
196
+ tag.dataset.pluginCss = tagId$4;
197
+ tag.textContent = css$4;
190
198
  document.head.appendChild(tag);
191
199
  }
192
200
  var ProviderLabel_module_css_default = { "label": "LjXNpG_label" };
@@ -194,7 +202,7 @@ window.__ModuleLoader__.load({
194
202
  //#region src/client/provider-label/ProviderLabel.tsx
195
203
  function ProviderLabel(props) {
196
204
  const provider = props.useDirectory((value) => value).current?.provider;
197
- if (provider === void 0 || provider.length === 0) return null;
205
+ if (!provider) return null;
198
206
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
199
207
  className: ProviderLabel_module_css_default.label,
200
208
  title: provider,
@@ -218,13 +226,13 @@ window.__ModuleLoader__.load({
218
226
  }
219
227
  //#endregion
220
228
  //#region \0dsh-css:D:\data\code\deepseek-harness\packages\my\dsh-ui-fortifier\src\client\open-dsh-folder\OpenDshFolderAction.module.css.mjs
221
- const css$2 = "._8KsKXW_action{align-items:center;gap:8px;min-width:0;display:flex}._8KsKXW_error{max-width:180px;color:var(--dsw-alias-state-error-primary);text-overflow:ellipsis;white-space:nowrap;font-size:12px;line-height:18px;overflow:hidden}";
222
- const tagId$2 = "dsh-ui-fortifier/OpenDshFolderAction.module.css";
223
- if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
229
+ const css$3 = "._8KsKXW_action{align-items:center;gap:8px;min-width:0;display:flex}._8KsKXW_error{max-width:180px;color:var(--dsw-alias-state-error-primary);text-overflow:ellipsis;white-space:nowrap;font-size:12px;line-height:18px;overflow:hidden}";
230
+ const tagId$3 = "dsh-ui-fortifier/OpenDshFolderAction.module.css";
231
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$3) + "]") === null) {
224
232
  const tag = document.createElement("style");
225
233
  tag.dataset.plugin = "dsh-ui-fortifier";
226
- tag.dataset.pluginCss = tagId$2;
227
- tag.textContent = css$2;
234
+ tag.dataset.pluginCss = tagId$3;
235
+ tag.textContent = css$3;
228
236
  document.head.appendChild(tag);
229
237
  }
230
238
  var OpenDshFolderAction_module_css_default = {
@@ -239,11 +247,11 @@ window.__ModuleLoader__.load({
239
247
  * @returns 操作按钮及其结果提示。
240
248
  */
241
249
  function OpenDshFolderAction(props) {
242
- const { openDshFolder, t } = props;
250
+ const t = props.t;
243
251
  const [state, setState] = (0, react.useState)({ kind: "idle" });
244
252
  const open = async () => {
245
253
  setState({ kind: "opening" });
246
- const result = await openDshFolder();
254
+ const result = await props.openDshFolder();
247
255
  if (!result.ok) {
248
256
  setState({ kind: "error" });
249
257
  return;
@@ -281,7 +289,7 @@ window.__ModuleLoader__.load({
281
289
  //#endregion
282
290
  //#region src/client/open-dsh-folder/index.ts
283
291
  /** 本模块文案命名空间(与 host 侧一致)。 */
284
- const NS$1 = "ui-fortifier";
292
+ const NS$2 = "ui-fortifier";
285
293
  /**
286
294
  * 注册「打开 .dsh 文件夹」到设置页右上角操作区,仅在回环连接下注册。
287
295
  * @param ctx - 需已提供 slots/connection 的客户端上下文。
@@ -295,19 +303,19 @@ window.__ModuleLoader__.load({
295
303
  name: "settings.action",
296
304
  id: "open-dsh-folder",
297
305
  order: 1,
298
- locale: NS$1,
306
+ locale: NS$2,
299
307
  inject: () => ({ openDshFolder })
300
308
  }, OpenDshFolderAction));
301
309
  }
302
310
  //#endregion
303
311
  //#region \0dsh-css:D:\data\code\deepseek-harness\packages\my\dsh-ui-fortifier\src\client\settings-frame\SettingsFrameEnhancer.module.css.mjs
304
- const css$1 = ".hB-7Ba_hostAnchor{display:none}.hB-7Ba_dragHandle{box-sizing:border-box;width:22px;height:22px;color:var(--dsw-alias-label-secondary);cursor:grab;vertical-align:middle;touch-action:none;background:0 0;border:none;border-radius:6px;flex:none;justify-content:center;align-items:center;margin:0 8px 0 0;padding:0;display:inline-flex}.hB-7Ba_dragHandle:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.hB-7Ba_gripIcon{flex:none;width:10px;height:14px}.hB-7Ba_resizeHandle{cursor:nwse-resize;touch-action:none;background:0 0;border:none;width:16px;height:16px;margin:0;padding:0;position:absolute;bottom:8px;right:8px}.hB-7Ba_resizeHandle:before,.hB-7Ba_resizeHandle:after{content:\"\";pointer-events:none;position:absolute;bottom:0;right:0}.hB-7Ba_resizeHandle:after{border-right:2px solid var(--dsw-alias-label-secondary);border-bottom:2px solid var(--dsw-alias-label-secondary);width:10px;height:10px}.hB-7Ba_resizeHandle:before{border-right:2px solid var(--dsw-alias-label-tertiary);border-bottom:2px solid var(--dsw-alias-label-tertiary);width:6px;height:6px;bottom:4px;right:4px}.hB-7Ba_resizeHandle:hover:after{border-color:var(--dsw-alias-label-primary)}";
305
- const tagId$1 = "dsh-ui-fortifier/SettingsFrameEnhancer.module.css";
306
- if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
312
+ const css$2 = ".hB-7Ba_hostAnchor{display:none}.hB-7Ba_dragHandle{box-sizing:border-box;width:22px;height:22px;color:var(--dsw-alias-label-secondary);cursor:grab;vertical-align:middle;touch-action:none;background:0 0;border:none;border-radius:6px;flex:none;justify-content:center;align-items:center;margin:0 8px 0 0;padding:0;display:inline-flex}.hB-7Ba_dragHandle:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.hB-7Ba_gripIcon{flex:none;width:10px;height:14px}.hB-7Ba_resizeHandle{cursor:nwse-resize;touch-action:none;background:0 0;border:none;width:16px;height:16px;margin:0;padding:0;position:absolute;bottom:8px;right:8px}.hB-7Ba_resizeHandle:before,.hB-7Ba_resizeHandle:after{content:\"\";pointer-events:none;position:absolute;bottom:0;right:0}.hB-7Ba_resizeHandle:after{border-right:2px solid var(--dsw-alias-label-secondary);border-bottom:2px solid var(--dsw-alias-label-secondary);width:10px;height:10px}.hB-7Ba_resizeHandle:before{border-right:2px solid var(--dsw-alias-label-tertiary);border-bottom:2px solid var(--dsw-alias-label-tertiary);width:6px;height:6px;bottom:4px;right:4px}.hB-7Ba_resizeHandle:hover:after{border-color:var(--dsw-alias-label-primary)}";
313
+ const tagId$2 = "dsh-ui-fortifier/SettingsFrameEnhancer.module.css";
314
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
307
315
  const tag = document.createElement("style");
308
316
  tag.dataset.plugin = "dsh-ui-fortifier";
309
- tag.dataset.pluginCss = tagId$1;
310
- tag.textContent = css$1;
317
+ tag.dataset.pluginCss = tagId$2;
318
+ tag.textContent = css$2;
311
319
  document.head.appendChild(tag);
312
320
  }
313
321
  var SettingsFrameEnhancer_module_css_default = {
@@ -332,7 +340,7 @@ window.__ModuleLoader__.load({
332
340
  /** 读取持久化的面板几何;缺失或损坏返回 undefined。 */
333
341
  function readFrameGeometry() {
334
342
  const raw = localStorage.getItem(FRAME_STORAGE_KEY);
335
- if (raw === null) return void 0;
343
+ if (!raw) return void 0;
336
344
  try {
337
345
  const parsed = JSON.parse(raw);
338
346
  if (typeof parsed.left === "number" && Number.isFinite(parsed.left) && typeof parsed.top === "number" && Number.isFinite(parsed.top) && typeof parsed.width === "number" && Number.isFinite(parsed.width) && typeof parsed.height === "number" && Number.isFinite(parsed.height)) return parsed;
@@ -431,16 +439,16 @@ window.__ModuleLoader__.load({
431
439
  const [headerRow, setHeaderRow] = (0, react.useState)(null);
432
440
  (0, react.useEffect)(() => {
433
441
  const anchor = anchorRef.current;
434
- if (anchor === null) return;
442
+ if (!anchor) return;
435
443
  const dialog = anchor.closest("[role=\"dialog\"]");
436
444
  if (!(dialog instanceof HTMLElement)) return;
437
445
  const rect = dialog.getBoundingClientRect();
438
446
  const saved = readFrameGeometry();
439
447
  dialog.style.position = "fixed";
440
- dialog.style.left = `${saved === void 0 ? rect.left : saved.left}px`;
441
- dialog.style.top = `${saved === void 0 ? rect.top : saved.top}px`;
442
- dialog.style.width = `${saved === void 0 ? rect.width : saved.width}px`;
443
- dialog.style.height = `${saved === void 0 ? rect.height : saved.height}px`;
448
+ dialog.style.left = `${saved ? saved.left : rect.left}px`;
449
+ dialog.style.top = `${saved ? saved.top : rect.top}px`;
450
+ dialog.style.width = `${saved ? saved.width : rect.width}px`;
451
+ dialog.style.height = `${saved ? saved.height : rect.height}px`;
444
452
  dialog.style.margin = "0";
445
453
  dialog.style.maxWidth = "none";
446
454
  constrainToViewport(dialog);
@@ -450,7 +458,7 @@ window.__ModuleLoader__.load({
450
458
  setHeaderRow(row instanceof HTMLElement ? row : null);
451
459
  let windowResizeFrame = null;
452
460
  const onWindowResize = () => {
453
- if (windowResizeFrame !== null) return;
461
+ if (windowResizeFrame) return;
454
462
  windowResizeFrame = requestAnimationFrame(() => {
455
463
  windowResizeFrame = null;
456
464
  constrainToViewport(dialog);
@@ -459,7 +467,7 @@ window.__ModuleLoader__.load({
459
467
  };
460
468
  window.addEventListener("resize", onWindowResize);
461
469
  return () => {
462
- if (windowResizeFrame !== null) {
470
+ if (windowResizeFrame) {
463
471
  cancelAnimationFrame(windowResizeFrame);
464
472
  windowResizeFrame = null;
465
473
  }
@@ -477,7 +485,7 @@ window.__ModuleLoader__.load({
477
485
  }, []);
478
486
  (0, react.useEffect)(() => {
479
487
  const handle = dragHandleRef.current;
480
- if (handle === null || headerRow === null) return;
488
+ if (!handle || !headerRow) return;
481
489
  if (headerRow.firstChild === handle) return;
482
490
  headerRow.insertBefore(handle, headerRow.firstChild);
483
491
  }, [headerRow]);
@@ -488,7 +496,7 @@ window.__ModuleLoader__.load({
488
496
  });
489
497
  const frame = (0, react.useRef)(null);
490
498
  const onDragPointerDown = (event) => {
491
- if (panel === null) return;
499
+ if (!panel) return;
492
500
  event.preventDefault();
493
501
  event.currentTarget.setPointerCapture(event.pointerId);
494
502
  dragState.current = {
@@ -497,28 +505,28 @@ window.__ModuleLoader__.load({
497
505
  };
498
506
  };
499
507
  const onDragPointerMove = (event) => {
500
- if (dragState.current === null) return;
508
+ if (!dragState.current) return;
501
509
  latest.current = {
502
510
  x: event.clientX,
503
511
  y: event.clientY
504
512
  };
505
513
  frame.current ??= requestAnimationFrame(() => {
506
514
  frame.current = null;
507
- if (panel === null || dragState.current === null) return;
515
+ if (!panel || !dragState.current) return;
508
516
  panel.style.left = `${latest.current.x - dragState.current.offsetX}px`;
509
517
  panel.style.top = `${latest.current.y - dragState.current.offsetY}px`;
510
518
  constrainPanel(panel);
511
519
  });
512
520
  };
513
521
  const onDragPointerUp = (event) => {
514
- if (dragState.current === null) return;
515
- if (frame.current !== null) {
522
+ if (!dragState.current) return;
523
+ if (frame.current) {
516
524
  cancelAnimationFrame(frame.current);
517
525
  frame.current = null;
518
526
  }
519
527
  dragState.current = null;
520
528
  event.currentTarget.releasePointerCapture(event.pointerId);
521
- if (panel === null) return;
529
+ if (!panel) return;
522
530
  constrainPanel(panel);
523
531
  persistGeometry(panel);
524
532
  };
@@ -527,7 +535,7 @@ window.__ModuleLoader__.load({
527
535
  const resizeMax = (0, react.useRef)(null);
528
536
  const resizeFrame = (0, react.useRef)(null);
529
537
  const onResizePointerDown = (event) => {
530
- if (panel === null) return;
538
+ if (!panel) return;
531
539
  event.preventDefault();
532
540
  event.stopPropagation();
533
541
  event.currentTarget.setPointerCapture(event.pointerId);
@@ -545,14 +553,14 @@ window.__ModuleLoader__.load({
545
553
  };
546
554
  };
547
555
  const onResizePointerMove = (event) => {
548
- if (resizeOrigin.current === null || resizeBase.current === null || resizeMax.current === null) return;
556
+ if (!resizeOrigin.current || !resizeBase.current || !resizeMax.current) return;
549
557
  latest.current = {
550
558
  x: event.clientX,
551
559
  y: event.clientY
552
560
  };
553
561
  resizeFrame.current ??= requestAnimationFrame(() => {
554
562
  resizeFrame.current = null;
555
- if (panel === null || resizeOrigin.current === null || resizeBase.current === null || resizeMax.current === null) return;
563
+ if (!panel || !resizeOrigin.current || !resizeBase.current || !resizeMax.current) return;
556
564
  const width = clamp(resizeBase.current.width + (latest.current.x - resizeOrigin.current.x), MIN_WIDTH, Math.max(MIN_WIDTH, resizeMax.current.width));
557
565
  const height = clamp(resizeBase.current.height + (latest.current.y - resizeOrigin.current.y), MIN_HEIGHT, Math.max(MIN_HEIGHT, resizeMax.current.height));
558
566
  panel.style.width = `${width}px`;
@@ -561,15 +569,15 @@ window.__ModuleLoader__.load({
561
569
  });
562
570
  };
563
571
  const onResizePointerUp = (event) => {
564
- if (resizeOrigin.current === null) return;
565
- if (resizeFrame.current !== null) {
572
+ if (!resizeOrigin.current) return;
573
+ if (resizeFrame.current) {
566
574
  cancelAnimationFrame(resizeFrame.current);
567
575
  resizeFrame.current = null;
568
576
  }
569
577
  resizeOrigin.current = null;
570
578
  resizeBase.current = null;
571
579
  event.currentTarget.releasePointerCapture(event.pointerId);
572
- if (panel !== null) persistGeometry(panel);
580
+ if (panel) persistGeometry(panel);
573
581
  };
574
582
  const dragHandle = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
575
583
  ref: dragHandleRef,
@@ -597,14 +605,14 @@ window.__ModuleLoader__.load({
597
605
  ref: anchorRef,
598
606
  className: SettingsFrameEnhancer_module_css_default.hostAnchor
599
607
  }),
600
- headerRow !== null && (0, react_dom.createPortal)(dragHandle, headerRow),
601
- panel !== null && (0, react_dom.createPortal)(resizeHandle, panel)
608
+ headerRow && (0, react_dom.createPortal)(dragHandle, headerRow),
609
+ panel && (0, react_dom.createPortal)(resizeHandle, panel)
602
610
  ] });
603
611
  }
604
612
  //#endregion
605
613
  //#region src/client/settings-frame/index.ts
606
614
  /** 本模块文案命名空间(与 host 侧一致)。 */
607
- const NS = "ui-fortifier";
615
+ const NS$1 = "ui-fortifier";
608
616
  /**
609
617
  * 注册「拖动位置/大小」增强到设置页右上角操作区。
610
618
  * @param ctx - 需已提供 slots 的客户端上下文。
@@ -616,19 +624,19 @@ window.__ModuleLoader__.load({
616
624
  name: "settings.action",
617
625
  id: "settings-frame-enhancer",
618
626
  order: 2,
619
- locale: NS,
627
+ locale: NS$1,
620
628
  inject: injected
621
629
  }, SettingsFrameEnhancer));
622
630
  }
623
631
  //#endregion
624
632
  //#region \0dsh-css:D:\data\code\deepseek-harness\packages\my\dsh-ui-fortifier\src\client\model-picker\ModelPicker.module.css.mjs
625
- const css = ".AGI7WG_root{flex:none;display:inline-flex}.AGI7WG_trigger{box-sizing:border-box;width:28px;height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:14px;justify-content:center;align-items:center;padding:0;display:inline-flex}.AGI7WG_trigger:hover{background:var(--dsw-alias-interactive-bg-hover)}.AGI7WG_trigger:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.AGI7WG_triggerIcon{flex:none;width:14px;height:14px}.AGI7WG_menu{z-index:1000;box-sizing:border-box;background:var(--dsw-specific-menu);--dsw-elevation-stroke-color:var(--dsw-alias-border-l1);min-width:420px;max-width:min(520px,100vw - 32px);height:min(420px,100vh - 96px);box-shadow:var(--dsw-elevation-prominent);color:var(--dsw-alias-label-primary);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);border:0;border-radius:16px;flex-direction:column;padding:6px;display:flex;position:absolute;bottom:calc(100% + 8px);right:0;overflow:hidden;transform:translate(-72px)}.AGI7WG_search{box-sizing:border-box;background:var(--dsw-alias-bg-layer-2);border:.5px solid var(--dsw-alias-border-l3);border-radius:10px;flex:none;align-items:center;gap:6px;height:28px;margin:0 2px 6px;padding:0 8px;display:flex}.AGI7WG_search:focus-within{border-color:var(--dsw-alias-brand-primary)}.AGI7WG_searchIcon{color:var(--dsw-alias-label-tertiary);flex:none;display:inline-flex}.AGI7WG_searchIcon svg{width:13px;height:13px}.AGI7WG_searchInput{min-width:0;color:var(--dsw-alias-label-primary);font:inherit;background:0 0;border:none;outline:none;flex:1;padding:0;font-size:13px;line-height:20px}.AGI7WG_searchInput::placeholder{color:var(--dsw-alias-label-tertiary)}.AGI7WG_columns{flex:1;min-height:0;display:flex;overflow:hidden}.AGI7WG_providers{flex:none;width:160px;padding-right:4px;overflow-y:auto}.AGI7WG_models{flex:1;min-width:0;padding-left:4px;overflow-y:auto}.AGI7WG_columnTitle{color:var(--dsw-alias-label-tertiary);padding:5px 8px 3px;font-size:12px;font-weight:500;line-height:18px}.AGI7WG_cell{box-sizing:border-box;width:100%;min-height:34px;color:inherit;font:inherit;text-align:left;cursor:pointer;background:0 0;border:none;border-radius:10px;align-items:center;gap:8px;padding:6px 8px;display:flex}.AGI7WG_cell:hover:not(:disabled),.AGI7WG_cell:focus-visible{background:var(--dsw-alias-interactive-bg-hover)}.AGI7WG_cellActive{background:var(--dsw-alias-interactive-bg-active);color:var(--dsw-alias-label-primary)}.AGI7WG_cellSelected{color:var(--dsw-alias-label-primary);font-weight:500}.AGI7WG_cell:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}.AGI7WG_modelName{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;overflow:hidden}.AGI7WG_check{color:var(--dsw-alias-brand-primary);flex:none}.AGI7WG_status{color:var(--dsw-alias-label-tertiary);padding:10px;font-size:13px;line-height:20px}.AGI7WG_error{background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary);border-radius:8px;justify-content:space-between;align-items:flex-start;gap:8px;margin-bottom:4px;padding:7px 8px;font-size:12px;line-height:18px;display:flex}.AGI7WG_retry{color:inherit;font:inherit;cursor:pointer;background:0 0;border:none;flex:none;padding:0;font-weight:600}";
626
- const tagId = "dsh-ui-fortifier/ModelPicker.module.css";
627
- if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
633
+ const css$1 = ".AGI7WG_root{flex:none;display:inline-flex}.AGI7WG_trigger{box-sizing:border-box;width:28px;height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:14px;justify-content:center;align-items:center;padding:0;display:inline-flex}.AGI7WG_trigger:hover{background:var(--dsw-alias-interactive-bg-hover)}.AGI7WG_trigger:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.AGI7WG_triggerIcon{flex:none;width:14px;height:14px}.AGI7WG_menu{z-index:1100;box-sizing:border-box;background:var(--dsw-specific-menu);--dsw-elevation-stroke-color:var(--dsw-alias-border-l1);min-width:min(420px,100vw - 32px);max-width:min(520px,100vw - 32px);height:min(420px,100vh - 96px);box-shadow:var(--dsw-elevation-prominent);color:var(--dsw-alias-label-primary);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);border:0;border-radius:16px;flex-direction:column;padding:6px;display:flex;position:fixed;overflow:hidden}.AGI7WG_search{box-sizing:border-box;background:var(--dsw-alias-bg-layer-2);border:.5px solid var(--dsw-alias-border-l3);border-radius:10px;flex:none;align-items:center;gap:6px;height:28px;margin:0 2px 6px;padding:0 8px;display:flex}.AGI7WG_search:focus-within{border-color:var(--dsw-alias-brand-primary)}.AGI7WG_searchIcon{color:var(--dsw-alias-label-tertiary);flex:none;display:inline-flex}.AGI7WG_searchIcon svg{width:13px;height:13px}.AGI7WG_searchInput{min-width:0;color:var(--dsw-alias-label-primary);font:inherit;background:0 0;border:none;outline:none;flex:1;padding:0;font-size:13px;line-height:20px}.AGI7WG_searchInput::placeholder{color:var(--dsw-alias-label-tertiary)}.AGI7WG_columns{flex:1;min-height:0;display:flex;overflow:hidden}.AGI7WG_providers{flex:none;width:160px;padding-right:4px;overflow-y:auto}.AGI7WG_models{flex:1;min-width:0;padding-left:4px;overflow-y:auto}.AGI7WG_columnTitle{color:var(--dsw-alias-label-tertiary);padding:5px 8px 3px;font-size:12px;font-weight:500;line-height:18px}.AGI7WG_cell{box-sizing:border-box;width:100%;min-height:34px;color:inherit;font:inherit;text-align:left;cursor:pointer;background:0 0;border:none;border-radius:10px;align-items:center;gap:8px;padding:6px 8px;display:flex}.AGI7WG_cell:hover:not(:disabled),.AGI7WG_cell:focus-visible{background:var(--dsw-alias-interactive-bg-hover)}.AGI7WG_cellActive{background:var(--dsw-alias-interactive-bg-active);color:var(--dsw-alias-label-primary)}.AGI7WG_cellSelected{color:var(--dsw-alias-label-primary);font-weight:500}.AGI7WG_cell:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}.AGI7WG_modelName{overflow-wrap:anywhere;flex:1;min-width:0}.AGI7WG_check{color:var(--dsw-alias-brand-primary);flex:none}.AGI7WG_status{color:var(--dsw-alias-label-tertiary);padding:10px;font-size:13px;line-height:20px}.AGI7WG_error{background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary);border-radius:8px;justify-content:space-between;align-items:flex-start;gap:8px;margin-bottom:4px;padding:7px 8px;font-size:12px;line-height:18px;display:flex}.AGI7WG_retry{color:inherit;font:inherit;cursor:pointer;background:0 0;border:none;flex:none;padding:0;font-weight:600}";
634
+ const tagId$1 = "dsh-ui-fortifier/ModelPicker.module.css";
635
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
628
636
  const tag = document.createElement("style");
629
637
  tag.dataset.plugin = "dsh-ui-fortifier";
630
- tag.dataset.pluginCss = tagId;
631
- tag.textContent = css;
638
+ tag.dataset.pluginCss = tagId$1;
639
+ tag.textContent = css$1;
632
640
  document.head.appendChild(tag);
633
641
  }
634
642
  var ModelPicker_module_css_default = {
@@ -664,6 +672,86 @@ window.__ModuleLoader__.load({
664
672
  return query === "" || name.toLowerCase().includes(query);
665
673
  }
666
674
  /**
675
+ * 在滚动容器内把目标滚进视野。只调整容器自身的 scrollTop,不联动页面滚动,
676
+ * 因此面板浮在 body 上时不会带动背景页面。
677
+ * @param container - 滚动容器,缺失时不处理。
678
+ * @param target - 目标元素,缺失(如已被搜索过滤掉)时不处理。
679
+ */
680
+ function scrollIntoViewWithin(container, target) {
681
+ if (!container || !target) return;
682
+ const containerRect = container.getBoundingClientRect();
683
+ const targetRect = target.getBoundingClientRect();
684
+ if (targetRect.top < containerRect.top) {
685
+ container.scrollTop -= containerRect.top - targetRect.top;
686
+ return;
687
+ }
688
+ if (targetRect.bottom > containerRect.bottom) container.scrollTop += targetRect.bottom - containerRect.bottom;
689
+ }
690
+ /**
691
+ * 未定位态:隐藏但按原点参与布局,使首帧 offsetWidth/offsetHeight 为真实尺寸,
692
+ * 定位不存在时也不会闪现在原点(与官方 ModelSelect 同法)。
693
+ */
694
+ const MEASURE_STYLE = {
695
+ visibility: "hidden",
696
+ left: 0,
697
+ top: 0
698
+ };
699
+ /**
700
+ * 面板坐标:右边缘对齐触发按钮右边缘,仅当这样越出视口时才平移回边界内。
701
+ * 官方 useAnchoredPosition 固定以触发器左边缘为起点且无对齐选项,故本地实现。
702
+ * @param open - 面板是否展开;关闭时清空坐标。
703
+ * @param anchorRef - 触发按钮。
704
+ * @param panelRef - 面板,用于量取自身尺寸做视口钳位。
705
+ * @param gap - 面板下边缘与触发器上边缘的间距。
706
+ * @param margin - 面板与视口各边的最小距离。
707
+ * @returns 面板的 fixed 坐标;首次量取前为 null。
708
+ */
709
+ function useRightAlignedMenuPosition(open, anchorRef, panelRef, gap, margin) {
710
+ const [position, setPosition] = (0, react.useState)(null);
711
+ (0, react.useLayoutEffect)(() => {
712
+ if (!open) {
713
+ setPosition(null);
714
+ return;
715
+ }
716
+ const place = () => {
717
+ const rect = anchorRef.current?.getBoundingClientRect();
718
+ if (!rect) return;
719
+ const panel = panelRef.current;
720
+ const width = panel?.offsetWidth ?? 0;
721
+ const height = panel?.offsetHeight ?? 0;
722
+ let left = rect.right - width;
723
+ let top = rect.top - gap - height;
724
+ if (width > 0) left = Math.min(Math.max(left, margin), window.innerWidth - width - margin);
725
+ if (height > 0) top = Math.min(Math.max(top, margin), window.innerHeight - height - margin);
726
+ setPosition({
727
+ left,
728
+ top
729
+ });
730
+ };
731
+ place();
732
+ window.addEventListener("scroll", place, true);
733
+ window.addEventListener("resize", place);
734
+ const panel = panelRef.current;
735
+ let observer = null;
736
+ if (typeof ResizeObserver !== "undefined" && panel) {
737
+ observer = new ResizeObserver(place);
738
+ observer.observe(panel);
739
+ }
740
+ return () => {
741
+ observer?.disconnect();
742
+ window.removeEventListener("scroll", place, true);
743
+ window.removeEventListener("resize", place);
744
+ };
745
+ }, [
746
+ open,
747
+ anchorRef,
748
+ panelRef,
749
+ gap,
750
+ margin
751
+ ]);
752
+ return position;
753
+ }
754
+ /**
667
755
  * 渲染级联模型选择器(左列提供商、右列模型)。
668
756
  * @param props - owner 参数、文案与注入面。
669
757
  * @returns 图标触发器按钮与展开时的两列面板。
@@ -680,7 +768,15 @@ window.__ModuleLoader__.load({
680
768
  const [loading, setLoading] = (0, react.useState)(false);
681
769
  const rootRef = (0, react.useRef)(null);
682
770
  const triggerRef = (0, react.useRef)(null);
771
+ const menuRef = (0, react.useRef)(null);
772
+ const providersRef = (0, react.useRef)(null);
773
+ const modelsRef = (0, react.useRef)(null);
774
+ const currentProviderRef = (0, react.useRef)(null);
775
+ const currentModelRef = (0, react.useRef)(null);
683
776
  const searchRef = (0, react.useRef)(null);
777
+ const positionedRef = (0, react.useRef)(false);
778
+ const pendingCurrentScrollRef = (0, react.useRef)(false);
779
+ const position = useRightAlignedMenuPosition(picker.open, triggerRef, menuRef, 8, 12);
684
780
  const groups = state.groups;
685
781
  const current = state.current;
686
782
  const query = picker.query.trim().toLowerCase();
@@ -697,7 +793,7 @@ window.__ModuleLoader__.load({
697
793
  const activeGroup = visibleGroups.find((group) => group.id === picker.provider) ?? visibleGroups[0];
698
794
  const activeProvider = activeGroup?.id ?? null;
699
795
  const activeModels = (0, react.useMemo)(() => {
700
- if (activeGroup === void 0) return [];
796
+ if (!activeGroup) return [];
701
797
  if (query === "") return activeGroup.models;
702
798
  if (hasModelHit) return activeGroup.models.filter((model) => matchesQuery(model.name, query));
703
799
  return activeGroup.models;
@@ -706,10 +802,38 @@ window.__ModuleLoader__.load({
706
802
  query,
707
803
  hasModelHit
708
804
  ]);
805
+ (0, react.useLayoutEffect)(() => {
806
+ if (!picker.open) {
807
+ positionedRef.current = false;
808
+ pendingCurrentScrollRef.current = false;
809
+ return;
810
+ }
811
+ if (pendingCurrentScrollRef.current) {
812
+ if (!currentModelRef.current) return;
813
+ scrollIntoViewWithin(modelsRef.current, currentModelRef.current);
814
+ pendingCurrentScrollRef.current = false;
815
+ return;
816
+ }
817
+ if (positionedRef.current) return;
818
+ if (!currentProviderRef.current && !currentModelRef.current) return;
819
+ scrollIntoViewWithin(providersRef.current, currentProviderRef.current);
820
+ scrollIntoViewWithin(modelsRef.current, currentModelRef.current);
821
+ positionedRef.current = true;
822
+ }, [
823
+ picker.open,
824
+ activeProvider,
825
+ current?.provider,
826
+ current?.model,
827
+ visibleGroups,
828
+ activeModels
829
+ ]);
709
830
  (0, react.useEffect)(() => {
710
831
  if (!picker.open) return;
711
832
  const closeOutside = (event) => {
712
- if (!rootRef.current?.contains(event.target)) setPicker((p) => ({
833
+ const target = event.target;
834
+ if (rootRef.current?.contains(target) === true) return;
835
+ if (menuRef.current?.contains(target) === true) return;
836
+ setPicker((p) => ({
713
837
  ...p,
714
838
  open: false
715
839
  }));
@@ -745,11 +869,22 @@ window.__ModuleLoader__.load({
745
869
  ...p,
746
870
  provider
747
871
  }));
872
+ if (provider === current?.provider) {
873
+ if (provider === activeProvider) {
874
+ scrollIntoViewWithin(modelsRef.current, currentModelRef.current);
875
+ pendingCurrentScrollRef.current = false;
876
+ return;
877
+ }
878
+ pendingCurrentScrollRef.current = true;
879
+ return;
880
+ }
881
+ pendingCurrentScrollRef.current = false;
882
+ if (modelsRef.current) modelsRef.current.scrollTop = 0;
748
883
  };
749
884
  const chooseModel = (modelId) => {
750
885
  if (activeProvider === null) return;
751
886
  const model = activeGroup?.models.find((m) => m.id === modelId);
752
- if (model === void 0) return;
887
+ if (!model) return;
753
888
  const selection = {
754
889
  provider: activeProvider,
755
890
  model: modelId,
@@ -828,10 +963,12 @@ window.__ModuleLoader__.load({
828
963
  })
829
964
  ]
830
965
  })
831
- }), picker.open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
966
+ }), picker.open && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
967
+ ref: menuRef,
832
968
  className: ModelPicker_module_css_default.menu,
833
969
  role: "dialog",
834
970
  "aria-label": t("modelPicker.trigger"),
971
+ style: position ?? MEASURE_STYLE,
835
972
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
836
973
  className: ModelPicker_module_css_default.search,
837
974
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -870,6 +1007,7 @@ window.__ModuleLoader__.load({
870
1007
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
871
1008
  className: ModelPicker_module_css_default.columns,
872
1009
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("aside", {
1010
+ ref: providersRef,
873
1011
  className: ModelPicker_module_css_default.providers,
874
1012
  "aria-label": t("modelPicker.providers"),
875
1013
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -885,6 +1023,7 @@ window.__ModuleLoader__.load({
885
1023
  className: ModelPicker_module_css_default.status,
886
1024
  children: t("modelPicker.emptySearch")
887
1025
  }) : visibleGroups.map((group) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1026
+ ref: group.id === activeProvider ? currentProviderRef : null,
888
1027
  type: "button",
889
1028
  className: `${ModelPicker_module_css_default.cell} ${group.id === activeProvider ? ModelPicker_module_css_default.cellActive : ""}`,
890
1029
  onClick: () => {
@@ -893,6 +1032,7 @@ window.__ModuleLoader__.load({
893
1032
  children: group.name
894
1033
  }, group.id))]
895
1034
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1035
+ ref: modelsRef,
896
1036
  className: ModelPicker_module_css_default.models,
897
1037
  "aria-label": t("modelPicker.models"),
898
1038
  children: [
@@ -925,6 +1065,7 @@ window.__ModuleLoader__.load({
925
1065
  }) : activeModels.map((model) => {
926
1066
  const selected = current?.provider === activeProvider && current.model === model.id;
927
1067
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1068
+ ref: selected ? currentModelRef : null,
928
1069
  type: "button",
929
1070
  className: `${ModelPicker_module_css_default.cell} ${selected ? ModelPicker_module_css_default.cellSelected : ""}`,
930
1071
  disabled: state.status === "selecting",
@@ -944,7 +1085,7 @@ window.__ModuleLoader__.load({
944
1085
  ]
945
1086
  })]
946
1087
  })]
947
- })]
1088
+ }), document.body)]
948
1089
  });
949
1090
  }
950
1091
  //#endregion
@@ -973,6 +1114,105 @@ window.__ModuleLoader__.load({
973
1114
  }, ModelPicker));
974
1115
  }
975
1116
  //#endregion
1117
+ //#region \0dsh-css:D:\data\code\deepseek-harness\packages\my\dsh-ui-fortifier\src\client\session-id-copy\SessionIdCopyAction.module.css.mjs
1118
+ const css = ".i0sS3a_button{box-sizing:border-box;height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:14px;flex:none;justify-content:center;align-items:center;gap:3px;padding:0 7px;display:inline-flex}.i0sS3a_button svg{width:13px;height:13px}.i0sS3a_button:hover{background:var(--dsw-alias-interactive-bg-hover)}.i0sS3a_button:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}";
1119
+ const tagId = "dsh-ui-fortifier/SessionIdCopyAction.module.css";
1120
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
1121
+ const tag = document.createElement("style");
1122
+ tag.dataset.plugin = "dsh-ui-fortifier";
1123
+ tag.dataset.pluginCss = tagId;
1124
+ tag.textContent = css;
1125
+ document.head.appendChild(tag);
1126
+ }
1127
+ var SessionIdCopyAction_module_css_default = { "button": "i0sS3a_button" };
1128
+ //#endregion
1129
+ //#region src/client/session-id-copy/SessionIdCopyAction.tsx
1130
+ /**
1131
+ * session-id-copy 功能:会话标题栏的复制 Session ID 按钮。
1132
+ */
1133
+ /**
1134
+ * 井号字形:官方图标库没有标识符类图标,故内联两竖两横的 # 描边。
1135
+ * 与相邻的复制图标并排,让按钮直接表达「复制标识符」而非只有复制。
1136
+ * @returns 装饰性内联图标(按钮已有 aria-label)。
1137
+ */
1138
+ function HashGlyph() {
1139
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
1140
+ viewBox: "0 0 16 16",
1141
+ fill: "none",
1142
+ "aria-hidden": "true",
1143
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1144
+ d: "M6.2 2.2 4.9 13.8M11.1 2.2 9.8 13.8M2.6 5.6h11.2M2.2 10.4h11.2",
1145
+ stroke: "currentColor",
1146
+ strokeWidth: "1.4",
1147
+ strokeLinecap: "round"
1148
+ })
1149
+ });
1150
+ }
1151
+ /**
1152
+ * 渲染复制 Session ID 的图标按钮:悬停提示直接给出会话 ID,点击写入剪贴板,
1153
+ * 成功后按钮短暂换成对勾。
1154
+ * @param props - owner 参数(含当前会话 ID)与文案。
1155
+ * @returns 会话标题栏工具区的复制按钮。
1156
+ */
1157
+ function SessionIdCopyAction(props) {
1158
+ const t = props.t;
1159
+ const id = String(props.sessionId);
1160
+ const [copied, setCopied] = (0, react.useState)(false);
1161
+ const pending = (0, react.useRef)(false);
1162
+ const timer = (0, react.useRef)(null);
1163
+ const epoch = (0, react.useRef)(0);
1164
+ (0, react.useEffect)(() => () => {
1165
+ epoch.current += 1;
1166
+ pending.current = false;
1167
+ if (timer.current) clearTimeout(timer.current);
1168
+ }, []);
1169
+ const copy = () => {
1170
+ if (copied || pending.current) return;
1171
+ const started = epoch.current;
1172
+ pending.current = true;
1173
+ (0, _deepseek_ai_dsh_client_ui_primitives.writeClipboard)(id).then((accepted) => {
1174
+ if (started !== epoch.current) return;
1175
+ pending.current = false;
1176
+ if (!accepted) return;
1177
+ setCopied(true);
1178
+ timer.current = window.setTimeout(() => {
1179
+ timer.current = null;
1180
+ setCopied(false);
1181
+ }, 1e3);
1182
+ });
1183
+ };
1184
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
1185
+ label: copied ? t("sessionIdCopy.copied") : t("sessionIdCopy.hint", { id }),
1186
+ side: "bottom",
1187
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1188
+ type: "button",
1189
+ className: SessionIdCopyAction_module_css_default.button,
1190
+ "aria-label": t("sessionIdCopy.action"),
1191
+ onClick: copy,
1192
+ children: copied ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutline16, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(HashGlyph, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCopyOutline16, {})] })
1193
+ })
1194
+ });
1195
+ }
1196
+ //#endregion
1197
+ //#region src/client/session-id-copy/index.ts
1198
+ /** 本模块文案命名空间(与 host 侧一致)。 */
1199
+ const NS = "ui-fortifier";
1200
+ /**
1201
+ * 注册复制会话 ID 按钮到会话标题栏工具区(list 槽,追加式;order -20 排在最左,
1202
+ * 位于「在本地打开」按钮之前)。官方三点菜单的菜单项为硬编码、无插槽,
1203
+ * 故本功能以相邻按钮而非菜单项实现。
1204
+ * @param ctx - 需已提供 slots/locale 的客户端上下文。
1205
+ * @returns 停止注入并卸载注册的 disposer。
1206
+ */
1207
+ function installSessionIdCopy(ctx) {
1208
+ return ctx.slots.inject("conversation.session.header.utilities", () => ctx.slots.register({
1209
+ name: "conversation.session.header.utilities",
1210
+ id: "session-id-copy",
1211
+ order: -20,
1212
+ locale: NS
1213
+ }, SessionIdCopyAction));
1214
+ }
1215
+ //#endregion
976
1216
  //#region src/client/index.ts
977
1217
  /** 功能模块清单:字段名(settings 开关 key) → 模块安装面。 */
978
1218
  const modules = {
@@ -991,12 +1231,17 @@ window.__ModuleLoader__.load({
991
1231
  "model-picker": {
992
1232
  module: installModelPicker,
993
1233
  dispose: void 0
1234
+ },
1235
+ "session-id-copy": {
1236
+ module: installSessionIdCopy,
1237
+ dispose: void 0
994
1238
  }
995
1239
  };
996
1240
  /** 设置页开关行的显示顺序(按位置排序);不在表中的字段排在其后。 */
997
1241
  const MODULE_ORDER = [
998
1242
  "provider-label",
999
1243
  "model-picker",
1244
+ "session-id-copy",
1000
1245
  "open-dsh-folder",
1001
1246
  "settings-frame"
1002
1247
  ];
package/lib/index.js CHANGED
@@ -161,7 +161,8 @@ const Config = z.object({
161
161
  "provider-label": z.boolean().default(true),
162
162
  "open-dsh-folder": z.boolean().default(true),
163
163
  "settings-frame": z.boolean().default(true),
164
- "model-picker": z.boolean().default(true)
164
+ "model-picker": z.boolean().default(true),
165
+ "session-id-copy": z.boolean().default(true)
165
166
  });
166
167
  /**
167
168
  * 注册功能开关的 settings 命名空间并暴露打开 .dsh 文件夹的 Remote 服务。
@@ -1,6 +1,6 @@
1
1
  /** dsh-ui-fortifier 的文案。 */
2
2
  /** locale key。 */
3
- export type UiFortifierLocaleKey = 'nav' | 'empty' | 'config.provider-label' | 'config.open-dsh-folder' | 'config.settings-frame' | 'config.model-picker' | 'openDshFolder' | 'openDshFolder.error' | 'openDshFolder.pathLabel' | 'settingsFrame.drag' | 'settingsFrame.resize' | 'modelPicker.trigger' | 'modelPicker.searchPlaceholder' | 'modelPicker.providers' | 'modelPicker.models' | 'modelPicker.emptyProviders' | 'modelPicker.emptySearch' | 'modelPicker.emptyModels' | 'modelPicker.loading' | 'modelPicker.loadError' | 'modelPicker.retry' | 'modelPicker.provider' | 'modelPicker.model';
3
+ export type UiFortifierLocaleKey = 'nav' | 'empty' | 'config.provider-label' | 'config.open-dsh-folder' | 'config.settings-frame' | 'config.model-picker' | 'config.session-id-copy' | 'openDshFolder' | 'openDshFolder.error' | 'openDshFolder.pathLabel' | 'settingsFrame.drag' | 'settingsFrame.resize' | 'modelPicker.trigger' | 'modelPicker.searchPlaceholder' | 'modelPicker.providers' | 'modelPicker.models' | 'modelPicker.emptyProviders' | 'modelPicker.emptySearch' | 'modelPicker.emptyModels' | 'modelPicker.loading' | 'modelPicker.loadError' | 'modelPicker.retry' | 'modelPicker.provider' | 'modelPicker.model' | 'sessionIdCopy.action' | 'sessionIdCopy.hint' | 'sessionIdCopy.copied';
4
4
  /** 英文文案。 */
5
5
  export declare const en: Record<UiFortifierLocaleKey, string>;
6
6
  /** 中文文案。 */
@@ -0,0 +1,11 @@
1
+ import type { PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
2
+ /** 槽位 owner 参数与本地化文案。 */
3
+ export type SessionIdCopyActionProps = PropsRuntime<'conversation.session.header.utilities'> & PropsLocale<'ui-fortifier'>;
4
+ /**
5
+ * 渲染复制 Session ID 的图标按钮:悬停提示直接给出会话 ID,点击写入剪贴板,
6
+ * 成功后按钮短暂换成对勾。
7
+ * @param props - owner 参数(含当前会话 ID)与文案。
8
+ * @returns 会话标题栏工具区的复制按钮。
9
+ */
10
+ export declare function SessionIdCopyAction(props: SessionIdCopyActionProps): import("react").JSX.Element;
11
+ //# sourceMappingURL=SessionIdCopyAction.d.ts.map
@@ -0,0 +1,13 @@
1
+ /**
2
+ * session-id-copy 功能:在会话标题栏工具区注册复制会话 ID 的按钮。
3
+ */
4
+ import type { Context as ClientContext } from '@deepseek-ai/cordis';
5
+ /**
6
+ * 注册复制会话 ID 按钮到会话标题栏工具区(list 槽,追加式;order -20 排在最左,
7
+ * 位于「在本地打开」按钮之前)。官方三点菜单的菜单项为硬编码、无插槽,
8
+ * 故本功能以相邻按钮而非菜单项实现。
9
+ * @param ctx - 需已提供 slots/locale 的客户端上下文。
10
+ * @returns 停止注入并卸载注册的 disposer。
11
+ */
12
+ export declare function installSessionIdCopy(ctx: ClientContext): () => void;
13
+ //# sourceMappingURL=index.d.ts.map
@@ -12,6 +12,8 @@ export interface Config {
12
12
  'settings-frame': boolean;
13
13
  /** 输入框右侧显示级联模型选择器(先选提供商、再选模型)。 */
14
14
  'model-picker': boolean;
15
+ /** 会话标题栏显示复制会话 ID 按钮。 */
16
+ 'session-id-copy': boolean;
15
17
  }
16
18
  /** 打开 .dsh 文件夹的返回值。 */
17
19
  export interface DshFolderOpenValue {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-ui-fortifier",
3
3
  "description": "UI 强化",
4
- "version": "0.5.1",
4
+ "version": "0.5.3",
5
5
  "license": "MIT",
6
6
  "contributors": [
7
7
  "pgnqukezrdxmhjso"
@@ -44,10 +44,10 @@
44
44
  "lib/types/**/*.d.ts"
45
45
  ],
46
46
  "peerDependencies": {
47
- "@deepseek-ai/dsh-home-paths": "^0.1.5-rc.2",
47
+ "@deepseek-ai/cordis": "^4.0.2",
48
48
  "@deepseek-ai/dsh-typert-protocol": "^0.1.5-rc.2",
49
49
  "@deepseek-ai/dsh-native-command": "^0.1.5-rc.2",
50
- "@deepseek-ai/cordis": "^4.0.2",
50
+ "@deepseek-ai/dsh-home-paths": "^0.1.5-rc.2",
51
51
  "@deepseek-ai/schemastery": "^3.18.2"
52
52
  },
53
53
  "devDependencies": {
@@ -55,22 +55,22 @@
55
55
  "@types/react-dom": "~18.3.0",
56
56
  "react": "^18.2.0",
57
57
  "react-dom": "^18.2.0",
58
- "@deepseek-ai/cordis": "^4.0.2",
59
- "@deepseek-ai/dsh-client-ui-conversation": "^0.1.5-rc.2",
60
58
  "@deepseek-ai/dsh-api-remotes": "^0.1.5-rc.2",
61
- "@deepseek-ai/dsh-client-ui-model-selection": "^0.1.5-rc.2",
62
- "@deepseek-ai/dsh-client-connection": "^0.1.5-rc.2",
59
+ "@deepseek-ai/cordis": "^4.0.2",
63
60
  "@deepseek-ai/dsh-api-session-controller": "^0.1.5-rc.2",
64
- "@deepseek-ai/dsh-client-ui-primitives": "^0.1.5-rc.2",
65
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.5-rc.2",
61
+ "@deepseek-ai/dsh-client-connection": "^0.1.5-rc.2",
62
+ "@deepseek-ai/dsh-client-ui-conversation": "^0.1.5-rc.2",
66
63
  "@deepseek-ai/dsh-client-ui-renderer": "^0.1.5-rc.2",
64
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.5-rc.2",
67
65
  "@deepseek-ai/dsh-client-ui-settings": "^0.1.5-rc.2",
68
- "@deepseek-ai/dsh-client-locale": "^0.1.5-rc.2",
69
- "@deepseek-ai/dsh-home-paths": "^0.1.5-rc.2",
66
+ "@deepseek-ai/dsh-client-ui-model-selection": "^0.1.5-rc.2",
67
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.5-rc.2",
70
68
  "@deepseek-ai/dsh-native-command": "^0.1.5-rc.2",
71
- "@deepseek-ai/dsh-settings": "^0.1.5-rc.2",
72
- "@deepseek-ai/dsh-typert-protocol": "^0.1.5-rc.2",
73
69
  "@deepseek-ai/dsh-session": "^0.1.5-rc.2",
70
+ "@deepseek-ai/dsh-client-locale": "^0.1.5-rc.2",
71
+ "@deepseek-ai/dsh-typert-protocol": "^0.1.5-rc.2",
72
+ "@deepseek-ai/dsh-settings": "^0.1.5-rc.2",
73
+ "@deepseek-ai/dsh-home-paths": "^0.1.5-rc.2",
74
74
  "@deepseek-ai/schemastery": "^3.18.2"
75
75
  },
76
76
  "dsh": {
@@ -97,8 +97,8 @@
97
97
  "verify": "node ../dsh-pgnqukezrdxmhjso-base/verify.mjs",
98
98
  "pub": "pnpm run build && pnpm login --registry=https://registry.npmjs.org && pnpm publish --registry=https://registry.npmjs.org --no-git-checks",
99
99
  "pack": "pnpm run build && pnpm pack --pack-destination lib",
100
- "pack-test": "pnpm run pack && (pnpm dsh plugin --profile pkgtest remove %npm_package_name% & pnpm dsh plugin --profile pkgtest add ./lib/%npm_package_name%-%npm_package_version%.tgz)",
101
- "npm-test": "pnpm dsh plugin --profile pkgtest remove %npm_package_name% & pnpm dsh plugin --profile pkgtest add %npm_package_name%",
100
+ "pack-test-i": "pnpm run pack && (pnpm dsh plugin --profile pkgtest remove %npm_package_name% & pnpm dsh plugin --profile pkgtest add ./lib/%npm_package_name%-%npm_package_version%.tgz)",
101
+ "npm-test-i": "pnpm dsh plugin --profile pkgtest remove %npm_package_name% & pnpm dsh plugin --profile pkgtest add %npm_package_name%@%npm_package_version%",
102
102
  "test-create": "pnpm dsh --profile pkgtest --from-default-profile web",
103
103
  "test-start": "pnpm dsh --profile pkgtest --port 3081 --no-open"
104
104
  }