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 +2 -1
- package/README.zh.md +2 -1
- package/lib/client.js +307 -62
- package/lib/index.js +2 -1
- package/lib/types/client/locales.d.ts +1 -1
- package/lib/types/client/session-id-copy/SessionIdCopyAction.d.ts +11 -0
- package/lib/types/client/session-id-copy/index.d.ts +13 -0
- package/lib/types/types.d.ts +2 -0
- package/package.json +15 -15
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
|
|
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
|
|
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$
|
|
13
|
-
const tagId$
|
|
14
|
-
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$
|
|
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$
|
|
18
|
-
tag.textContent = css$
|
|
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$
|
|
184
|
-
const tagId$
|
|
185
|
-
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$
|
|
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$
|
|
189
|
-
tag.textContent = css$
|
|
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
|
|
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$
|
|
222
|
-
const tagId$
|
|
223
|
-
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$
|
|
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$
|
|
227
|
-
tag.textContent = css$
|
|
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
|
|
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$
|
|
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$
|
|
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$
|
|
305
|
-
const tagId$
|
|
306
|
-
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$
|
|
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$
|
|
310
|
-
tag.textContent = css$
|
|
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
|
|
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
|
|
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
|
|
441
|
-
dialog.style.top = `${saved
|
|
442
|
-
dialog.style.width = `${saved
|
|
443
|
-
dialog.style.height = `${saved
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
515
|
-
if (frame.current
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
565
|
-
if (resizeFrame.current
|
|
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
|
|
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
|
|
601
|
-
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:
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
package/lib/types/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-ui-fortifier",
|
|
3
3
|
"description": "UI 强化",
|
|
4
|
-
"version": "0.5.
|
|
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/
|
|
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/
|
|
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/
|
|
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-
|
|
65
|
-
"@deepseek-ai/dsh-client-ui-
|
|
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-
|
|
69
|
-
"@deepseek-ai/dsh-
|
|
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
|
}
|