dsh-ui-fortifier 0.5.3 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,19 +18,20 @@ dsh plugin --profile web add dsh-ui-fortifier
18
18
 
19
19
  ## Features
20
20
 
21
- Adds a "ui-fortifier" tab in Settings to toggle every feature module of this plugin.
21
+ Adds a configuration area to this plugin's own card on the sidebar's Plugins page, toggling every feature module of this plugin.
22
22
 
23
23
  | Feature | Description |
24
24
  | --- | --- |
25
+ | session-id-copy | Copy-session-ID button in the Session header's utility group. |
26
+ | compact-button | Compact-context button at the right end of the composer tool row's left group; runs `/compact`. |
25
27
  | provider-label | Shows the currently selected provider to the left of the model selector. |
26
28
  | 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. |
28
- | open-dsh-folder | Adds an "Open .dsh folder" button next to "Open configuration file" at the top-right of the Settings page. |
29
+ | open-dsh-folder | Adds an "Open dshHome folder" button next to "Open configuration file" at the top-right of the Settings page. |
29
30
  | settings-frame | Makes the Settings panel draggable and resizable. |
30
31
 
31
32
  ## dsh version used for development
32
33
 
33
- - dsh version used for development: `0.1.5-rc.2`. `@deepseek-ai/dsh-*` dependencies use `workspace:^`.
34
+ - dsh version used for development: `0.1.7-rc.2`. `@deepseek-ai/dsh-*` dependencies use `workspace:^`.
34
35
 
35
36
  ## Secondary Development
36
37
 
package/README.zh.md CHANGED
@@ -18,19 +18,20 @@ dsh plugin --profile web add dsh-ui-fortifier
18
18
 
19
19
  ## 功能
20
20
 
21
- 在设置页新增「ui-fortifier」标签页,集中开关本插件各功能模块。
21
+ 在侧边栏「插件」页本插件自己的卡片上提供配置区,集中开关本插件各功能模块。
22
22
 
23
23
  | 功能 | 描述 |
24
24
  | --- | --- |
25
+ | session-id-copy | 会话标题栏工具区提供复制会话 ID 按钮。 |
26
+ | compact-button | 输入框工具行左组右端提供压缩上下文按钮,等价于提交 `/compact` 指令。 |
25
27
  | provider-label | 在模型选择按钮左侧显示当前选择的提供方。 |
26
28
  | model-picker | 输入行模型选择器旁提供级联模型选择器(提供商 → 模型)。 |
27
- | session-id-copy | 会话标题栏工具区提供复制会话 ID 按钮。 |
28
- | open-dsh-folder | 在设置页右上角「打开配置文件」旁增加「打开 .dsh 文件夹」。 |
29
+ | open-dsh-folder | 在设置页右上角「打开配置文件」旁增加「打开dshHome文件夹」。 |
29
30
  | settings-frame | 设置面板可拖动位置与拖动大小。 |
30
31
 
31
32
  ## 构建时使用的 dsh 版本
32
33
 
33
- - 构建时使用的 dsh 版本:`0.1.5-rc.2`;`@deepseek-ai/dsh-*` 依赖使用 `workspace:^`。
34
+ - 构建时使用的 dsh 版本:`0.1.7-rc.2`;`@deepseek-ai/dsh-*` 依赖使用 `workspace:^`。
34
35
 
35
36
  ## 二次开发说明
36
37
 
package/lib/client.js CHANGED
@@ -4,18 +4,18 @@ window.__ModuleLoader__.load({
4
4
  var module = { exports: {} };
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
- let react_jsx_runtime = require("react/jsx-runtime");
8
- let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
9
7
  let react = require("react");
8
+ let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
9
+ let react_jsx_runtime = require("react/jsx-runtime");
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$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) {
12
+ const css$6 = ".t1_2kq_page{flex-direction:column;gap:8px;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}";
13
+ const tagId$6 = "dsh-ui-fortifier/FortifierSettingsPage.module.css";
14
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$6) + "]") === null) {
15
15
  const tag = document.createElement("style");
16
16
  tag.dataset.plugin = "dsh-ui-fortifier";
17
- tag.dataset.pluginCss = tagId$5;
18
- tag.textContent = css$5;
17
+ tag.dataset.pluginCss = tagId$6;
18
+ tag.textContent = css$6;
19
19
  document.head.appendChild(tag);
20
20
  }
21
21
  var FortifierSettingsPage_module_css_default = {
@@ -23,23 +23,57 @@ window.__ModuleLoader__.load({
23
23
  "list": "t1_2kq_list",
24
24
  "page": "t1_2kq_page",
25
25
  "row": "t1_2kq_row",
26
- "rowLabel": "t1_2kq_rowLabel",
27
- "switch": "t1_2kq_switch",
28
- "switchOn": "t1_2kq_switchOn",
29
- "thumb": "t1_2kq_thumb"
26
+ "rowLabel": "t1_2kq_rowLabel"
30
27
  };
31
28
  //#endregion
32
29
  //#region src/client/settings/FortifierSettingsPage.tsx
33
- /** 设置页:读取配置项自动生成切换按钮行。 */
30
+ /**
31
+ * 配置区:插件页上本包卡片里的六个功能开关。
32
+ */
33
+ /**
34
+ * 渲染六个功能开关。
35
+ * @param props - 组装后的插槽 props。
36
+ * @returns 插件页本包卡片上的配置区。
37
+ */
34
38
  function FortifierSettingsPage(props) {
35
39
  const t = props.t;
36
40
  const rows = props.useToggles((value) => value);
41
+ const [local, setLocal] = (0, react.useState)(() => /* @__PURE__ */ new Map());
42
+ const [pendingWrites, setPendingWrites] = (0, react.useState)(0);
43
+ const synced = (0, react.useRef)(/* @__PURE__ */ new Map());
44
+ (0, react.useEffect)(() => {
45
+ const host = new Map(rows.map((row) => [row.field, row.value]));
46
+ const unchanged = [...host].every(([field, value]) => synced.current.get(field) === value) && host.size === synced.current.size;
47
+ if (pendingWrites > 0 || unchanged) return;
48
+ synced.current = host;
49
+ setLocal(/* @__PURE__ */ new Map());
50
+ }, [rows, pendingWrites]);
51
+ /** 记一次未落定的写入,落定后减一;被拒或传输失败也照样放行,好让宿主值接管。 */
52
+ const trackWrite = (write) => {
53
+ setPendingWrites((count) => count + 1);
54
+ write.catch(() => false).finally(() => {
55
+ setPendingWrites((count) => Math.max(0, count - 1));
56
+ });
57
+ };
58
+ /**
59
+ * 提交一个开关:先落在本地(控件立即显示新值),再写宿主。
60
+ * @param row - 要切换的开关行。
61
+ */
62
+ const commit = (row) => {
63
+ const next = !(local.get(row.field) ?? row.value);
64
+ const pending = new Map(local);
65
+ pending.set(row.field, next);
66
+ setLocal(pending);
67
+ synced.current = new Map(synced.current).set(row.field, next);
68
+ trackWrite(props.set(row.field, next));
69
+ };
70
+ if (props.view === "summary") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: t("nav") });
37
71
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
38
72
  className: FortifierSettingsPage_module_css_default.page,
39
73
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
40
74
  className: FortifierSettingsPage_module_css_default.heading,
41
75
  children: t("nav")
42
- }), rows.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("empty") }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
76
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
43
77
  className: FortifierSettingsPage_module_css_default.list,
44
78
  children: rows.map((row) => {
45
79
  const label = t(`config.${row.field}`);
@@ -48,16 +82,12 @@ window.__ModuleLoader__.load({
48
82
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
49
83
  className: FortifierSettingsPage_module_css_default.rowLabel,
50
84
  children: label
51
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
52
- type: "button",
53
- role: "switch",
54
- "aria-checked": row.value,
55
- "aria-label": label,
56
- className: `${FortifierSettingsPage_module_css_default.switch} ${row.value ? FortifierSettingsPage_module_css_default.switchOn : ""}`,
57
- onClick: () => {
58
- props.set(row.field, !row.value);
59
- },
60
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: FortifierSettingsPage_module_css_default.thumb })
85
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
86
+ checked: local.get(row.field) ?? row.value,
87
+ label,
88
+ onChange: () => {
89
+ commit(row);
90
+ }
61
91
  })]
62
92
  }, row.field);
63
93
  })
@@ -69,14 +99,14 @@ window.__ModuleLoader__.load({
69
99
  /** 英文文案。 */
70
100
  const en = {
71
101
  nav: "ui-fortifier",
72
- empty: "No feature modules registered.",
73
102
  "config.provider-label": "Input - Provider",
74
- "config.open-dsh-folder": "Settings top-right - Open .dsh folder",
103
+ "config.open-dsh-folder": "Settings top-right - Open dshHome folder",
75
104
  "config.settings-frame": "Settings panel - Drag and resize",
76
105
  "config.model-picker": "Input - Cascading model picker",
77
106
  "config.session-id-copy": "Session header - Copy session ID",
78
- openDshFolder: "Open .dsh folder",
79
- "openDshFolder.error": "Could not open the .dsh folder",
107
+ "config.compact-button": "Input left group - Compact context",
108
+ openDshFolder: "Open dshHome folder",
109
+ "openDshFolder.error": "Could not open the dshHome folder",
80
110
  "openDshFolder.pathLabel": "Path: ",
81
111
  "settingsFrame.drag": "Drag the settings panel",
82
112
  "settingsFrame.resize": "Resize the settings panel",
@@ -94,19 +124,23 @@ window.__ModuleLoader__.load({
94
124
  "modelPicker.model": "Model: {name}",
95
125
  "sessionIdCopy.action": "Copy Session ID",
96
126
  "sessionIdCopy.hint": "Session ID: {id}",
97
- "sessionIdCopy.copied": "Copied"
127
+ "sessionIdCopy.copied": "Copied",
128
+ "compactButton.action": "Compact context",
129
+ "compactButton.confirmTitle": "Compact context",
130
+ "compactButton.confirmText": "Confirm compaction?",
131
+ "compactButton.confirm": "Confirm"
98
132
  };
99
133
  /** 中文文案。 */
100
134
  const zh = {
101
135
  nav: "ui-fortifier",
102
- empty: "没有已注册的功能模块。",
103
136
  "config.provider-label": "输入框 - 提供商",
104
- "config.open-dsh-folder": "设置右上角 - 打开 .dsh 文件夹",
137
+ "config.open-dsh-folder": "设置右上角 - 打开dshHome文件夹",
105
138
  "config.settings-frame": "设置面板 - 拖动与缩放",
106
139
  "config.model-picker": "输入框 - 级联模型选择器",
107
140
  "config.session-id-copy": "会话标题栏 - 复制 Session ID",
108
- openDshFolder: "打开 .dsh 文件夹",
109
- "openDshFolder.error": "无法打开 .dsh 文件夹",
141
+ "config.compact-button": "输入框左组 - 压缩上下文",
142
+ openDshFolder: "打开dshHome文件夹",
143
+ "openDshFolder.error": "无法打开dshHome文件夹",
110
144
  "openDshFolder.pathLabel": "路径:",
111
145
  "settingsFrame.drag": "拖动设置面板",
112
146
  "settingsFrame.resize": "缩放设置面板",
@@ -124,77 +158,76 @@ window.__ModuleLoader__.load({
124
158
  "modelPicker.model": "模型:{name}",
125
159
  "sessionIdCopy.action": "复制 Session ID",
126
160
  "sessionIdCopy.hint": "Session ID: {id}",
127
- "sessionIdCopy.copied": "已复制"
161
+ "sessionIdCopy.copied": "已复制",
162
+ "compactButton.action": "压缩上下文",
163
+ "compactButton.confirmTitle": "压缩上下文",
164
+ "compactButton.confirmText": "确认压缩?",
165
+ "compactButton.confirm": "确认"
128
166
  };
129
167
  //#endregion
130
168
  //#region src/client/settings/index.ts
131
- /** 本插件设置命名空间(与 host 侧一致)。 */
169
+ /** 本插件在 profile 里的入口 id,同时是设置契约的命名空间。 */
132
170
  const UI_FORTIFIER = "ui-fortifier";
133
171
  /**
134
- * 注册设置页并绑定功能开关的 settings 作用域。
135
- * @param ctx - 需已提供 slots/locale/settingsScope 的客户端上下文。
136
- * @param moduleOrder - 功能模块字段的显示顺序(settings 开关 key)。
137
- * @returns 功能开关的 settings 绑定,供调用方控制子模块注册。
172
+ * 本插件在 profile 里的包名,也是插件页派发配置区的键。
173
+ *
174
+ * 该页以 `pkg.name` 派发(`renderSlot('plugins.bundle.config', ..., { entryKey: pkg.name })`),
175
+ * 所以这里的值必须与 package.json 的 `name` 逐字一致;写错不会报错,只会让配置区静默
176
+ * 地不出现。
177
+ */
178
+ const BUNDLE_NAME = "dsh-ui-fortifier";
179
+ /**
180
+ * 注册配置区并把入口配置的表单绑成本页的数据源。
181
+ * @param ctx - 需已提供 slots/locale/configForms 的客户端上下文。
182
+ * @param moduleOrder - 功能开关字段的显示顺序。
183
+ * @returns 本入口的表单,供调用方控制子模块注册。
138
184
  */
139
185
  function installSettings(ctx, moduleOrder) {
140
186
  ctx.effect(() => ctx.locale.register(UI_FORTIFIER, {
141
187
  zh,
142
188
  en
143
189
  }), "dsh-ui-fortifier: copy dictionaries");
144
- const scope = ctx.settingsScope.bind({ namespace: UI_FORTIFIER });
145
- const mirror = ctx.settingsScope.describe();
146
- const view = () => mirror.getSnapshot().view?.namespaces.find((row) => row.ns === UI_FORTIFIER);
147
- const sectionInjected = () => {
190
+ const form = ctx.configForms.get(UI_FORTIFIER);
191
+ const injected = () => {
148
192
  let cached = [];
149
- let cachedSignature = "";
193
+ let signature = "";
150
194
  return {
151
195
  hooks: { toggles: {
152
196
  getSnapshot: () => {
153
- const value = view()?.value ?? {};
197
+ const value = form.getSnapshot().value;
154
198
  const rows = moduleOrder.map((field) => ({
155
199
  field,
156
- value: value[field] ?? true
200
+ value: value?.[field] ?? true
157
201
  }));
158
- const signature = JSON.stringify(rows);
159
- if (signature !== cachedSignature) {
202
+ const current = JSON.stringify(rows);
203
+ if (current !== signature) {
160
204
  cached = rows;
161
- cachedSignature = signature;
205
+ signature = current;
162
206
  }
163
207
  return cached;
164
208
  },
165
- subscribe: (listener) => {
166
- const offScope = scope.subscribe(listener);
167
- const offMirror = mirror.subscribe(listener);
168
- return () => {
169
- offScope();
170
- offMirror();
171
- };
172
- }
209
+ subscribe: (listener) => form.subscribe(listener)
173
210
  } },
174
- set: (field, value) => {
175
- scope.set(field, value);
176
- }
211
+ set: (field, value) => form.set(field, value)
177
212
  };
178
213
  };
179
- ctx.slots.inject("settings.section", () => ctx.slots.register({
180
- name: "settings.section",
181
- id: UI_FORTIFIER,
182
- order: 30,
183
- label: () => ctx.locale.bind(UI_FORTIFIER)("nav"),
214
+ ctx.slots.inject("plugins.bundle.config", () => ctx.slots.register({
215
+ name: "plugins.bundle.config",
216
+ key: BUNDLE_NAME,
184
217
  locale: UI_FORTIFIER,
185
- inject: sectionInjected
218
+ inject: injected
186
219
  }, FortifierSettingsPage));
187
- return scope;
220
+ return form;
188
221
  }
189
222
  //#endregion
190
223
  //#region \0dsh-css:D:\data\code\deepseek-harness\packages\my\dsh-ui-fortifier\src\client\provider-label\ProviderLabel.module.css.mjs
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) {
224
+ const css$5 = ".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}";
225
+ const tagId$5 = "dsh-ui-fortifier/ProviderLabel.module.css";
226
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$5) + "]") === null) {
194
227
  const tag = document.createElement("style");
195
228
  tag.dataset.plugin = "dsh-ui-fortifier";
196
- tag.dataset.pluginCss = tagId$4;
197
- tag.textContent = css$4;
229
+ tag.dataset.pluginCss = tagId$5;
230
+ tag.textContent = css$5;
198
231
  document.head.appendChild(tag);
199
232
  }
200
233
  var ProviderLabel_module_css_default = { "label": "LjXNpG_label" };
@@ -226,13 +259,13 @@ window.__ModuleLoader__.load({
226
259
  }
227
260
  //#endregion
228
261
  //#region \0dsh-css:D:\data\code\deepseek-harness\packages\my\dsh-ui-fortifier\src\client\open-dsh-folder\OpenDshFolderAction.module.css.mjs
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) {
262
+ const css$4 = "._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}";
263
+ const tagId$4 = "dsh-ui-fortifier/OpenDshFolderAction.module.css";
264
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$4) + "]") === null) {
232
265
  const tag = document.createElement("style");
233
266
  tag.dataset.plugin = "dsh-ui-fortifier";
234
- tag.dataset.pluginCss = tagId$3;
235
- tag.textContent = css$3;
267
+ tag.dataset.pluginCss = tagId$4;
268
+ tag.textContent = css$4;
236
269
  document.head.appendChild(tag);
237
270
  }
238
271
  var OpenDshFolderAction_module_css_default = {
@@ -242,7 +275,7 @@ window.__ModuleLoader__.load({
242
275
  //#endregion
243
276
  //#region src/client/open-dsh-folder/OpenDshFolderAction.tsx
244
277
  /**
245
- * 渲染「打开 .dsh 文件夹」操作,点击后请求 Host 打开 .dsh 目录。
278
+ * 渲染「打开 dshHome 文件夹」操作,点击后请求 Host 打开 dshHome 目录。
246
279
  * @param props - header owner 参数、本地化文案与注入的打开回调。
247
280
  * @returns 操作按钮及其结果提示。
248
281
  */
@@ -291,7 +324,7 @@ window.__ModuleLoader__.load({
291
324
  /** 本模块文案命名空间(与 host 侧一致)。 */
292
325
  const NS$2 = "ui-fortifier";
293
326
  /**
294
- * 注册「打开 .dsh 文件夹」到设置页右上角操作区,仅在回环连接下注册。
327
+ * 注册「打开 dshHome 文件夹」到设置页右上角操作区,仅在回环连接下注册。
295
328
  * @param ctx - 需已提供 slots/connection 的客户端上下文。
296
329
  * @returns 停止注入并卸载注册的 disposer。
297
330
  */
@@ -309,13 +342,13 @@ window.__ModuleLoader__.load({
309
342
  }
310
343
  //#endregion
311
344
  //#region \0dsh-css:D:\data\code\deepseek-harness\packages\my\dsh-ui-fortifier\src\client\settings-frame\SettingsFrameEnhancer.module.css.mjs
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) {
345
+ const css$3 = ".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)}";
346
+ const tagId$3 = "dsh-ui-fortifier/SettingsFrameEnhancer.module.css";
347
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$3) + "]") === null) {
315
348
  const tag = document.createElement("style");
316
349
  tag.dataset.plugin = "dsh-ui-fortifier";
317
- tag.dataset.pluginCss = tagId$2;
318
- tag.textContent = css$2;
350
+ tag.dataset.pluginCss = tagId$3;
351
+ tag.textContent = css$3;
319
352
  document.head.appendChild(tag);
320
353
  }
321
354
  var SettingsFrameEnhancer_module_css_default = {
@@ -630,13 +663,13 @@ window.__ModuleLoader__.load({
630
663
  }
631
664
  //#endregion
632
665
  //#region \0dsh-css:D:\data\code\deepseek-harness\packages\my\dsh-ui-fortifier\src\client\model-picker\ModelPicker.module.css.mjs
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) {
666
+ const css$2 = ".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;--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;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}";
667
+ const tagId$2 = "dsh-ui-fortifier/ModelPicker.module.css";
668
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
636
669
  const tag = document.createElement("style");
637
670
  tag.dataset.plugin = "dsh-ui-fortifier";
638
- tag.dataset.pluginCss = tagId$1;
639
- tag.textContent = css$1;
671
+ tag.dataset.pluginCss = tagId$2;
672
+ tag.textContent = css$2;
640
673
  document.head.appendChild(tag);
641
674
  }
642
675
  var ModelPicker_module_css_default = {
@@ -691,7 +724,7 @@ window.__ModuleLoader__.load({
691
724
  * 未定位态:隐藏但按原点参与布局,使首帧 offsetWidth/offsetHeight 为真实尺寸,
692
725
  * 定位不存在时也不会闪现在原点(与官方 ModelSelect 同法)。
693
726
  */
694
- const MEASURE_STYLE = {
727
+ const MEASURE_STYLE$1 = {
695
728
  visibility: "hidden",
696
729
  left: 0,
697
730
  top: 0
@@ -963,12 +996,12 @@ window.__ModuleLoader__.load({
963
996
  })
964
997
  ]
965
998
  })
966
- }), picker.open && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
999
+ }), picker.open && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.MenuSurface, {
967
1000
  ref: menuRef,
968
1001
  className: ModelPicker_module_css_default.menu,
969
1002
  role: "dialog",
970
1003
  "aria-label": t("modelPicker.trigger"),
971
- style: position ?? MEASURE_STYLE,
1004
+ style: position ?? MEASURE_STYLE$1,
972
1005
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
973
1006
  className: ModelPicker_module_css_default.search,
974
1007
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -1115,13 +1148,13 @@ window.__ModuleLoader__.load({
1115
1148
  }
1116
1149
  //#endregion
1117
1150
  //#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) {
1151
+ const css$1 = ".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}";
1152
+ const tagId$1 = "dsh-ui-fortifier/SessionIdCopyAction.module.css";
1153
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
1121
1154
  const tag = document.createElement("style");
1122
1155
  tag.dataset.plugin = "dsh-ui-fortifier";
1123
- tag.dataset.pluginCss = tagId;
1124
- tag.textContent = css;
1156
+ tag.dataset.pluginCss = tagId$1;
1157
+ tag.textContent = css$1;
1125
1158
  document.head.appendChild(tag);
1126
1159
  }
1127
1160
  var SessionIdCopyAction_module_css_default = { "button": "i0sS3a_button" };
@@ -1189,7 +1222,7 @@ window.__ModuleLoader__.load({
1189
1222
  className: SessionIdCopyAction_module_css_default.button,
1190
1223
  "aria-label": t("sessionIdCopy.action"),
1191
1224
  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, {})] })
1225
+ children: copied ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutlineRegular, {}) : /* @__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.IconCopyOutlineRegular, {})] })
1193
1226
  })
1194
1227
  });
1195
1228
  }
@@ -1213,8 +1246,165 @@ window.__ModuleLoader__.load({
1213
1246
  }, SessionIdCopyAction));
1214
1247
  }
1215
1248
  //#endregion
1249
+ //#region \0dsh-css:D:\data\code\deepseek-harness\packages\my\dsh-ui-fortifier\src\client\compact-button\CompactContextButton.module.css.mjs
1250
+ const css = ".zxJg-q_root{flex:none;display:inline-flex}.zxJg-q_button{corner-shape:round;background:var(--dsw-specific-selector);width:28px;height:28px;color:var(--dsw-alias-label-primary);cursor:pointer;border:none;border-radius:999px;flex:none;place-items:center;display:grid}.zxJg-q_button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-solid)}.zxJg-q_button:disabled{opacity:.5;cursor:default}.zxJg-q_glyph{fill:currentColor;flex:none;width:16px;height:16px;overflow:hidden}.zxJg-q_popover{z-index:1100;box-sizing:border-box;max-width:min(260px,100vw - 32px);box-shadow:var(--dsw-elevation-prominent);color:var(--dsw-alias-label-primary);flex-direction:column;align-items:flex-start;gap:10px;padding:10px 12px;display:flex;position:fixed}.zxJg-q_popoverText{margin:0;font-size:13px;line-height:20px}";
1251
+ const tagId = "dsh-ui-fortifier/CompactContextButton.module.css";
1252
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
1253
+ const tag = document.createElement("style");
1254
+ tag.dataset.plugin = "dsh-ui-fortifier";
1255
+ tag.dataset.pluginCss = tagId;
1256
+ tag.textContent = css;
1257
+ document.head.appendChild(tag);
1258
+ }
1259
+ var CompactContextButton_module_css_default = {
1260
+ "button": "zxJg-q_button",
1261
+ "glyph": "zxJg-q_glyph",
1262
+ "popover": "zxJg-q_popover",
1263
+ "popoverText": "zxJg-q_popoverText",
1264
+ "root": "zxJg-q_root"
1265
+ };
1266
+ //#endregion
1267
+ //#region src/client/compact-button/CompactContextButton.tsx
1268
+ /**
1269
+ * compact-button 功能:输入框工具行左组右端的压缩上下文按钮。
1270
+ */
1271
+ /** 气泡与按钮之间的间距。 */
1272
+ const POPOVER_GAP = 8;
1273
+ /** 气泡与视口边缘保留的距离。 */
1274
+ const POPOVER_MARGIN = 12;
1275
+ const MEASURE_STYLE = {
1276
+ visibility: "hidden",
1277
+ left: 0,
1278
+ top: 0
1279
+ };
1280
+ /**
1281
+ * 压缩字形:官方图标库没有压缩语义的字形(`IconApiOutline14` 是终端提示符、
1282
+ * `IconEnhanceOutline16` 是四条段落横线,在此都会误读),故内联字形,与
1283
+ * `session-id-copy` 因官方无井号而内联 `HashGlyph` 同一做法。字形由用户提供。
1284
+ * @returns 装饰性内联图标(按钮已有 aria-label)。
1285
+ */ function CompressGlyph() {
1286
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
1287
+ className: CompactContextButton_module_css_default.glyph,
1288
+ viewBox: "0 0 1024 1024",
1289
+ "aria-hidden": "true",
1290
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M318.1248 840.533333l31.5264-99.8336a759.4624 759.4624 0 0 0 0-457.399466L318.1248 183.466667h332.7296l67.6416 67.6416a733.216 733.216 0 0 0-32.590933 526.184533L705.8752 840.533333H318.1248z m0-733.866666c-51.908267 0-88.866133 50.427733-73.2352 99.925333l31.5264 99.835733a682.658133 682.658133 0 0 1 0 411.144534l-31.5264 99.8336C229.258667 866.9056 266.216533 917.333333 318.1248 917.333333h387.7504c51.908267 0 88.866133-50.427733 73.2352-99.927466l-19.970133-63.240534a656.420267 656.420267 0 0 1 29.947733-472.744533 73.845333 73.845333 0 0 0-14.833067-83.165867l-69.0944-69.0944A76.8 76.8 0 0 0 650.8544 106.666667H318.1248zM152.123733 764.7168c-13.051733-5.437867-19.170133-20.413867-14.2656-33.674667a631.473067 631.473067 0 0 0 0-438.0864c-4.904533-13.2608 1.216-28.2368 14.2656-33.674666l15.752534-6.564267c13.051733-5.437867 28.087467 0.7232 33.045333 13.9648a699.746133 699.746133 0 0 1 0 490.634667c-4.957867 13.2416-19.9936 19.402667-33.045333 13.9648l-15.752534-6.564267z m733.538134-33.474133l-12.343467-29.6256a419.4688 419.4688 0 0 1 8.7744-342.293334c6.101333-12.7552 1.725867-28.311467-10.632533-35.178666l-14.920534-8.288c-12.3584-6.8672-28.010667-2.4384-34.2208 10.263466a487.741867 487.741867 0 0 0-12.017066 401.7536l12.343466 29.623467c5.437867 13.051733 20.426667 19.223467 33.4784 13.7856l15.752534-6.564267c13.051733-5.437867 19.223467-20.426667 13.7856-33.476266z" })
1291
+ });
1292
+ }
1293
+ /**
1294
+ * 渲染压缩上下文按钮:点击先弹出确认气泡,气泡内确认后才提交 `/compact` 指令;
1295
+ * 点气泡外或按 Escape 取消。回合运行中与提交等待期间按钮禁用。
1296
+ * @param props - 会话标准套件、本地化文案与注入的压缩回调。
1297
+ * @returns 工具行左组右端的图标按钮与确认气泡。
1298
+ */
1299
+ function CompactContextButton(props) {
1300
+ const running = props.useSession((snapshot) => snapshot.running);
1301
+ const [open, setOpen] = (0, react.useState)(false);
1302
+ const [pending, setPending] = (0, react.useState)(false);
1303
+ const rootRef = (0, react.useRef)(null);
1304
+ const panelRef = (0, react.useRef)(null);
1305
+ const alive = (0, react.useRef)(true);
1306
+ const position = (0, _deepseek_ai_dsh_client_ui_primitives.useAnchoredPosition)({
1307
+ open,
1308
+ anchorRef: rootRef,
1309
+ panelRef,
1310
+ side: "top",
1311
+ gap: POPOVER_GAP,
1312
+ margin: POPOVER_MARGIN
1313
+ });
1314
+ (0, _deepseek_ai_dsh_client_ui_primitives.useDismissOnOutsidePointer)(rootRef, open, setOpen, panelRef);
1315
+ (0, react.useEffect)(() => {
1316
+ alive.current = true;
1317
+ return () => {
1318
+ alive.current = false;
1319
+ };
1320
+ }, []);
1321
+ (0, react.useEffect)(() => {
1322
+ if (running) setOpen(false);
1323
+ }, [running]);
1324
+ (0, react.useEffect)(() => {
1325
+ if (!open) return;
1326
+ const onKeyDown = (event) => {
1327
+ if (event.key === "Escape") setOpen(false);
1328
+ };
1329
+ document.addEventListener("keydown", onKeyDown);
1330
+ return () => {
1331
+ document.removeEventListener("keydown", onKeyDown);
1332
+ };
1333
+ }, [open]);
1334
+ const compact = () => {
1335
+ if (pending) return;
1336
+ setOpen(false);
1337
+ setPending(true);
1338
+ const settle = () => {
1339
+ if (alive.current) setPending(false);
1340
+ };
1341
+ props.compact().then(settle, settle);
1342
+ };
1343
+ const disabled = running || pending;
1344
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1345
+ ref: rootRef,
1346
+ className: CompactContextButton_module_css_default.root,
1347
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
1348
+ label: props.t("compactButton.action"),
1349
+ side: "top",
1350
+ delayMs: 500,
1351
+ disabled: open,
1352
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1353
+ type: "button",
1354
+ className: CompactContextButton_module_css_default.button,
1355
+ "aria-label": props.t("compactButton.action"),
1356
+ "aria-haspopup": "dialog",
1357
+ "aria-expanded": open,
1358
+ disabled,
1359
+ onMouseDown: (event) => {
1360
+ event.preventDefault();
1361
+ },
1362
+ onClick: () => {
1363
+ setOpen((value) => !value);
1364
+ },
1365
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CompressGlyph, {})
1366
+ })
1367
+ }), open && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.MenuSurface, {
1368
+ ref: panelRef,
1369
+ compact: true,
1370
+ className: CompactContextButton_module_css_default.popover,
1371
+ role: "dialog",
1372
+ "aria-label": props.t("compactButton.confirmTitle"),
1373
+ style: position ?? MEASURE_STYLE,
1374
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1375
+ className: CompactContextButton_module_css_default.popoverText,
1376
+ children: props.t("compactButton.confirmText")
1377
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1378
+ variant: "primary",
1379
+ size: "sm",
1380
+ disabled,
1381
+ onClick: compact,
1382
+ children: props.t("compactButton.confirm")
1383
+ })]
1384
+ }), document.body)]
1385
+ });
1386
+ }
1387
+ //#endregion
1388
+ //#region src/client/compact-button/index.ts
1389
+ /**
1390
+ * 注册压缩上下文按钮到 conversation.input.left。
1391
+ * @param ctx - 需已提供 slots 与 remote.commands 的客户端上下文。
1392
+ * @returns 停止注入并卸载注册的 disposer。
1393
+ */
1394
+ function installCompactButton(ctx) {
1395
+ return ctx.slots.inject("conversation.input.left", () => ctx.slots.register({
1396
+ name: "conversation.input.left",
1397
+ id: "compact-button",
1398
+ order: 100,
1399
+ locale: "ui-fortifier",
1400
+ inject: (sessionId) => ({ compact: async () => {
1401
+ await ctx.remote.commands.execute(sessionId, "/compact", []);
1402
+ } })
1403
+ }, CompactContextButton));
1404
+ }
1405
+ //#endregion
1216
1406
  //#region src/client/index.ts
1217
- /** 功能模块清单:字段名(settings 开关 key) → 模块安装面。 */
1407
+ /** 功能模块清单:字段名(配置开关 key) → 模块安装面。 */
1218
1408
  const modules = {
1219
1409
  "provider-label": {
1220
1410
  module: installProviderLabel,
@@ -1235,43 +1425,53 @@ window.__ModuleLoader__.load({
1235
1425
  "session-id-copy": {
1236
1426
  module: installSessionIdCopy,
1237
1427
  dispose: void 0
1428
+ },
1429
+ "compact-button": {
1430
+ module: installCompactButton,
1431
+ dispose: void 0
1238
1432
  }
1239
1433
  };
1240
- /** 设置页开关行的显示顺序(按位置排序);不在表中的字段排在其后。 */
1434
+ /** 配置区开关行的显示顺序:按各模块在界面上的位置,从会话标题栏到输入行再到插件页。 */
1241
1435
  const MODULE_ORDER = [
1436
+ "session-id-copy",
1437
+ "compact-button",
1242
1438
  "provider-label",
1243
1439
  "model-picker",
1244
- "session-id-copy",
1245
1440
  "open-dsh-folder",
1246
1441
  "settings-frame"
1247
1442
  ];
1248
1443
  const inject = [
1249
1444
  "slots",
1250
1445
  "locale",
1251
- "settingsScope",
1446
+ "configForms",
1252
1447
  "modelDirectories",
1253
1448
  "connection",
1254
1449
  "sessions",
1255
1450
  "remote",
1256
- "remote.session"
1451
+ "remote.session",
1452
+ "remote.commands"
1257
1453
  ];
1258
1454
  /**
1259
- * 客户端插件体:注册设置页并据开关状态挂载各功能模块。
1260
- * @param ctx - 需已提供 slots/locale/settingsScope/modelDirectories/connection/sessions/remote 的客户端上下文。
1455
+ * 客户端插件体:注册插件页上的配置区并据开关状态挂载各功能模块。
1456
+ * @param ctx - 需已提供 slots/locale/configForms/modelDirectories/connection/sessions/remote 的客户端上下文。
1261
1457
  */
1262
1458
  function apply(ctx) {
1263
- const scope = installSettings(ctx, MODULE_ORDER);
1264
- for (const field of Object.keys(modules)) {
1459
+ const form = installSettings(ctx, MODULE_ORDER);
1460
+ const mounted = /* @__PURE__ */ new Map();
1461
+ const sync = (snapshot, field) => {
1462
+ const enabled = snapshot.value?.[field] ?? true;
1463
+ if (mounted.get(field) === enabled) return;
1464
+ mounted.set(field, enabled);
1265
1465
  const item = modules[field];
1266
- const sync = () => {
1267
- const enabled = scope.getSnapshot().value?.[field] ?? true;
1268
- item.dispose?.();
1269
- if (enabled) item.dispose = item.module(ctx);
1270
- else item.dispose = void 0;
1271
- };
1272
- scope.subscribe(sync);
1273
- sync();
1274
- }
1466
+ item.dispose?.();
1467
+ item.dispose = enabled ? item.module(ctx) : void 0;
1468
+ };
1469
+ const syncAll = () => {
1470
+ const snapshot = form.getSnapshot();
1471
+ for (const field of Object.keys(modules)) sync(snapshot, field);
1472
+ };
1473
+ form.subscribe(syncAll);
1474
+ syncAll();
1275
1475
  }
1276
1476
  //#endregion
1277
1477
  exports.MODULE_ORDER = MODULE_ORDER;
package/lib/index.js CHANGED
@@ -93,7 +93,7 @@ async function openWindowsFolderInForeground(path, signal) {
93
93
  windowsOpenScript(path)
94
94
  ], signal);
95
95
  }
96
- /** 用系统默认方式打开 .dsh 文件夹;无原生打开器时返回路径让 UI 展示。 */
96
+ /** 用系统默认方式打开 dshHome 文件夹;无原生打开器时返回路径让 UI 展示。 */
97
97
  let OpenDshFolderService = (() => {
98
98
  let _classSuper = TypertRemoteService;
99
99
  let _instanceExtraInitializers = [];
@@ -125,9 +125,9 @@ let OpenDshFolderService = (() => {
125
125
  __runInitializers(this, _instanceExtraInitializers);
126
126
  }
127
127
  /**
128
- * 打开 .dsh 文件夹。
128
+ * 打开 dshHome 文件夹。
129
129
  * @param signal - 调用方可取消信号。
130
- * @returns opened 为 false 时 path 为 .dsh 文件夹路径。
130
+ * @returns opened 为 false 时 path 为 dshHome 文件夹路径。
131
131
  */
132
132
  async openDshFolder(signal) {
133
133
  const home = resolveDshHome();
@@ -143,7 +143,7 @@ let OpenDshFolderService = (() => {
143
143
  path: home
144
144
  };
145
145
  } catch (error) {
146
- if (signal.aborted) throw new RemoteError("gateway/cancelled", "dsh folder open was aborted", {});
146
+ if (signal.aborted) throw new RemoteError("gateway/cancelled", "dshHome folder open was aborted", {});
147
147
  throw new RemoteError("gateway/internal", `path open failed: ${messageOf(error)}`, {}, { cause: error });
148
148
  }
149
149
  }
@@ -154,29 +154,30 @@ function messageOf(error) {
154
154
  }
155
155
  //#endregion
156
156
  //#region lib/types/index.js
157
- /** 功能开关的 settings 命名空间。 */
158
- const UI_FORTIFIER_SETTINGS_NAMESPACE = "ui-fortifier";
159
- /** 插件配置 schema。 */
157
+ /**
158
+ * 插件配置 schema。
159
+ *
160
+ * 不写成 `z<Config>`:`Config` 的字段是 `Volatile<T>`,而 volatile 字段的 schema 类型
161
+ * 处于 `"volatile-defined"` 模式,两者在双向方差下互不可赋值,加了注解反而编译不过
162
+ * (官方与已迁移的插件同样只写 `z.object`)。
163
+ */
160
164
  const Config = z.object({
161
- "provider-label": z.boolean().default(true),
162
- "open-dsh-folder": z.boolean().default(true),
163
- "settings-frame": z.boolean().default(true),
164
- "model-picker": z.boolean().default(true),
165
- "session-id-copy": z.boolean().default(true)
165
+ "provider-label": z.boolean().default(true).volatile(),
166
+ "open-dsh-folder": z.boolean().default(true).volatile(),
167
+ "settings-frame": z.boolean().default(true).volatile(),
168
+ "model-picker": z.boolean().default(true).volatile(),
169
+ "session-id-copy": z.boolean().default(true).volatile(),
170
+ "compact-button": z.boolean().default(true).volatile()
166
171
  });
167
172
  /**
168
- * 注册功能开关的 settings 命名空间并暴露打开 .dsh 文件夹的 Remote 服务。
169
- * @param ctx - 需已挂载 settings 服务的 Cordis 上下文。
170
- * @param config - 插件配置,作为 base 层。
173
+ * 挂载插件:暴露打开 dshHome 文件夹的 Remote 服务,并声明本插件自带配置区。
174
+ * @param ctx - 需已提供 reflect(Typert 网关)的 Cordis 上下文。
171
175
  */
172
- function apply(ctx, config) {
176
+ function apply(ctx) {
173
177
  new OpenDshFolderService(ctx);
174
178
  ctx.inject(["settings"], (settingsCtx) => {
175
- settingsCtx.settings.installSection(ctx, UI_FORTIFIER_SETTINGS_NAMESPACE, Config, config, {
176
- setSource: () => {},
177
- onChange: () => {}
178
- });
179
+ settingsCtx.effect(() => settingsCtx.settings.configure({ auto: false }, ctx.fiber));
179
180
  });
180
181
  }
181
182
  //#endregion
182
- export { Config, UI_FORTIFIER_SETTINGS_NAMESPACE, apply };
183
+ export { Config, apply };
@@ -0,0 +1,16 @@
1
+ import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
2
+ /** 注入面:提交一次压缩指令。 */
3
+ export interface CompactContextButtonInjected {
4
+ /** 提交 `/compact` 指令;指令生命周期由 host 写入会话流。 */
5
+ compact: () => Promise<void>;
6
+ }
7
+ /** 槽位 owner 参数、本地化文案与注入回调。 */
8
+ export type CompactContextButtonProps = PropsRuntime<'conversation.input.left'> & PropsLocale<'ui-fortifier'> & InjectFace<CompactContextButtonInjected>;
9
+ /**
10
+ * 渲染压缩上下文按钮:点击先弹出确认气泡,气泡内确认后才提交 `/compact` 指令;
11
+ * 点气泡外或按 Escape 取消。回合运行中与提交等待期间按钮禁用。
12
+ * @param props - 会话标准套件、本地化文案与注入的压缩回调。
13
+ * @returns 工具行左组右端的图标按钮与确认气泡。
14
+ */
15
+ export declare function CompactContextButton(props: CompactContextButtonProps): import("react").JSX.Element;
16
+ //# sourceMappingURL=CompactContextButton.d.ts.map
@@ -0,0 +1,11 @@
1
+ /**
2
+ * compact-button 功能:在输入框工具行左组右端注册压缩上下文按钮。
3
+ */
4
+ import type { Context as ClientContext } from '@deepseek-ai/cordis';
5
+ /**
6
+ * 注册压缩上下文按钮到 conversation.input.left。
7
+ * @param ctx - 需已提供 slots 与 remote.commands 的客户端上下文。
8
+ * @returns 停止注入并卸载注册的 disposer。
9
+ */
10
+ export declare function installCompactButton(ctx: ClientContext): () => void;
11
+ //# sourceMappingURL=index.d.ts.map
@@ -8,14 +8,14 @@ interface ModuleEntry {
8
8
  /** 当前注册的 disposer;归位后为 undefined。 */
9
9
  dispose: (() => void) | undefined;
10
10
  }
11
- /** 功能模块清单:字段名(settings 开关 key) → 模块安装面。 */
11
+ /** 功能模块清单:字段名(配置开关 key) → 模块安装面。 */
12
12
  export declare const modules: Record<keyof Config, ModuleEntry>;
13
- /** 设置页开关行的显示顺序(按位置排序);不在表中的字段排在其后。 */
13
+ /** 配置区开关行的显示顺序:按各模块在界面上的位置,从会话标题栏到输入行再到插件页。 */
14
14
  export declare const MODULE_ORDER: readonly (keyof Config)[];
15
15
  export declare const inject: string[];
16
16
  /**
17
- * 客户端插件体:注册设置页并据开关状态挂载各功能模块。
18
- * @param ctx - 需已提供 slots/locale/settingsScope/modelDirectories/connection/sessions/remote 的客户端上下文。
17
+ * 客户端插件体:注册插件页上的配置区并据开关状态挂载各功能模块。
18
+ * @param ctx - 需已提供 slots/locale/configForms/modelDirectories/connection/sessions/remote 的客户端上下文。
19
19
  */
20
20
  export declare function apply(ctx: Context): void;
21
21
  declare module '@deepseek-ai/dsh-client-ui-slots' {
@@ -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' | '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';
3
+ export type UiFortifierLocaleKey = 'nav' | 'config.provider-label' | 'config.open-dsh-folder' | 'config.settings-frame' | 'config.model-picker' | 'config.session-id-copy' | 'config.compact-button' | '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' | 'compactButton.action' | 'compactButton.confirmTitle' | 'compactButton.confirmText' | 'compactButton.confirm';
4
4
  /** 英文文案。 */
5
5
  export declare const en: Record<UiFortifierLocaleKey, string>;
6
6
  /** 中文文案。 */
@@ -2,15 +2,15 @@ import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-cli
2
2
  import type { ReactNode } from 'react';
3
3
  import type { ConnectionRpcResult } from '@deepseek-ai/dsh-client-connection/client';
4
4
  import type { DshFolderOpenValue } from '../../types.ts';
5
- /** 打开 .dsh 文件夹的注入面。 */
5
+ /** 打开 dshHome 文件夹的注入面。 */
6
6
  export interface OpenDshFolderInjected {
7
- /** 请求 Host 打开 .dsh 文件夹。 */
7
+ /** 请求 Host 打开 dshHome 文件夹。 */
8
8
  openDshFolder: () => Promise<ConnectionRpcResult<DshFolderOpenValue>>;
9
9
  }
10
10
  /** header 操作区的 owner 参数、本地化文案与注入回调。 */
11
11
  export type OpenDshFolderActionProps = PropsRuntime<'settings.action'> & PropsLocale<'ui-fortifier'> & InjectFace<OpenDshFolderInjected>;
12
12
  /**
13
- * 渲染「打开 .dsh 文件夹」操作,点击后请求 Host 打开 .dsh 目录。
13
+ * 渲染「打开 dshHome 文件夹」操作,点击后请求 Host 打开 dshHome 目录。
14
14
  * @param props - header owner 参数、本地化文案与注入的打开回调。
15
15
  * @returns 操作按钮及其结果提示。
16
16
  */
@@ -1,10 +1,10 @@
1
1
  /**
2
- * open-dsh-folder 功能:在设置页右上角「打开配置文件」旁增加「打开 .dsh 文件夹」。
2
+ * open-dsh-folder 功能:在设置页右上角「打开配置文件」旁增加「打开 dshHome 文件夹」。
3
3
  */
4
4
  import type { Context as ClientContext } from '@deepseek-ai/cordis';
5
5
  export type { OpenDshFolderActionProps, OpenDshFolderInjected } from './OpenDshFolderAction.tsx';
6
6
  /**
7
- * 注册「打开 .dsh 文件夹」到设置页右上角操作区,仅在回环连接下注册。
7
+ * 注册「打开 dshHome 文件夹」到设置页右上角操作区,仅在回环连接下注册。
8
8
  * @param ctx - 需已提供 slots/connection 的客户端上下文。
9
9
  * @returns 停止注入并卸载注册的 disposer。
10
10
  */
@@ -1,22 +1,27 @@
1
1
  import type { HostObservable, InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
2
+ import type { Config } from '../../types.ts';
2
3
  /** 一个功能开关行的状态。 */
3
4
  export interface FortifierToggleRow {
4
- /** 字段名(即 settings 的 key)。 */
5
- field: string;
5
+ /** 字段名(即配置的 key)。 */
6
+ field: keyof Config;
6
7
  /** 当前值。 */
7
8
  value: boolean;
8
9
  }
9
- /** 设置页渲染所需的面。 */
10
+ /** 配置区渲染所需的面。 */
10
11
  export interface FortifierPageInjected {
11
12
  hooks: {
12
13
  /** 所有功能开关的当前值(响应式)。 */
13
14
  toggles: HostObservable<readonly FortifierToggleRow[]>;
14
15
  };
15
- /** 写入一个开关值(user 层)。 */
16
- set: (field: string, value: boolean) => void;
16
+ /** 写入一个开关值(user 层);落定后 resolve。 */
17
+ set: (field: keyof Config, value: boolean) => Promise<boolean>;
17
18
  }
18
- /** 设置页 props。 */
19
- export type FortifierSettingsPageProps = PropsRuntime<'settings.section'> & PropsLocale<'ui-fortifier'> & InjectFace<FortifierPageInjected>;
20
- /** 设置页:读取配置项自动生成切换按钮行。 */
19
+ /** 配置区 props:挂在插件页本包详情页的 `plugins.bundle.config` 上。 */
20
+ export type FortifierSettingsPageProps = PropsRuntime<'plugins.bundle.config'> & PropsLocale<'ui-fortifier'> & InjectFace<FortifierPageInjected>;
21
+ /**
22
+ * 渲染六个功能开关。
23
+ * @param props - 组装后的插槽 props。
24
+ * @returns 插件页本包卡片上的配置区。
25
+ */
21
26
  export declare function FortifierSettingsPage(props: FortifierSettingsPageProps): import("react").JSX.Element;
22
27
  //# sourceMappingURL=FortifierSettingsPage.d.ts.map
@@ -1,14 +1,17 @@
1
+ /**
2
+ * 注册本插件的配置区(插件页上本包自己的卡片),并把入口配置的表单绑成本页的数据源。
3
+ */
1
4
  import type { Context as ClientContext } from '@deepseek-ai/cordis';
2
- import type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client';
5
+ import type { ConfigForm } from '@deepseek-ai/dsh-client-ui-settings/client';
3
6
  import type { Config } from '../../types.ts';
4
7
  export type { FortifierPageInjected, FortifierSettingsPageProps } from './FortifierSettingsPage.tsx';
5
- /** 本插件设置命名空间(与 host 侧一致)。 */
8
+ /** 本插件在 profile 里的入口 id,同时是设置契约的命名空间。 */
6
9
  export declare const UI_FORTIFIER = "ui-fortifier";
7
10
  /**
8
- * 注册设置页并绑定功能开关的 settings 作用域。
9
- * @param ctx - 需已提供 slots/locale/settingsScope 的客户端上下文。
10
- * @param moduleOrder - 功能模块字段的显示顺序(settings 开关 key)。
11
- * @returns 功能开关的 settings 绑定,供调用方控制子模块注册。
11
+ * 注册配置区并把入口配置的表单绑成本页的数据源。
12
+ * @param ctx - 需已提供 slots/locale/configForms 的客户端上下文。
13
+ * @param moduleOrder - 功能开关字段的显示顺序。
14
+ * @returns 本入口的表单,供调用方控制子模块注册。
12
15
  */
13
- export declare function installSettings(ctx: ClientContext, moduleOrder: readonly string[]): SettingsScope<Config>;
16
+ export declare function installSettings(ctx: ClientContext, moduleOrder: readonly (keyof Config)[]): ConfigForm<Config>;
14
17
  //# sourceMappingURL=index.d.ts.map
@@ -1,17 +1,62 @@
1
- import type { Context } from '@deepseek-ai/cordis';
1
+ /**
2
+ * dsh-ui-fortifier 的宿主半侧。
3
+ *
4
+ * 只做两件事:暴露「打开 dshHome 文件夹」的 Remote 服务,以及声明本插件自带配置区。
5
+ * 六个功能开关的值由浏览器半侧经设置表单读写(见 `src/client/settings/index.ts`),
6
+ * 宿主不读它们,因此 `apply` 不接配置参数。
7
+ *
8
+ * @module dsh-ui-fortifier
9
+ */
10
+ import type { Context, Volatile } from '@deepseek-ai/cordis';
2
11
  import z from '@deepseek-ai/schemastery';
3
- import type { Config as FortifierConfig } from './types.ts';
4
- /** 插件配置类型(定义见 `./types.ts`,与 Client 面共享)。 */
5
- export type Config = FortifierConfig;
6
12
  export type { DshFolderOpenValue } from './types.ts';
7
- /** 功能开关的 settings 命名空间。 */
8
- export declare const UI_FORTIFIER_SETTINGS_NAMESPACE = "ui-fortifier";
9
- /** 插件配置 schema。 */
10
- export declare const Config: z<FortifierConfig>;
11
13
  /**
12
- * 注册功能开关的 settings 命名空间并暴露打开 .dsh 文件夹的 Remote 服务。
13
- * @param ctx - 需已挂载 settings 服务的 Cordis 上下文。
14
- * @param config - 插件配置,作为 base 层。
14
+ * 插件配置。
15
+ *
16
+ * 六个字段都是 **volatile 引用**:设置表单只投影入口 `Config` 里声明为 `.volatile()`
17
+ * 的字段,写入由 Loader 经 `updateVolatile` 落回本入口的 profile patch。volatile 引用
18
+ * 原地更新,所以表单每次 `.get()` 都拿到当前值,不必重挂插件。
19
+ * 解引用之后的普通值见 [src/types.ts](./types.ts) 的 `Config`。
20
+ */
21
+ export interface Config {
22
+ /** 输入框显示当前提供商。 */
23
+ 'provider-label': Volatile<boolean>;
24
+ /** 插件页右上角显示打开 dshHome 文件夹按钮。 */
25
+ 'open-dsh-folder': Volatile<boolean>;
26
+ /** 设置面板可拖动位置与大小。 */
27
+ 'settings-frame': Volatile<boolean>;
28
+ /** 输入框右侧显示级联模型选择器(先选提供商、再选模型)。 */
29
+ 'model-picker': Volatile<boolean>;
30
+ /** 会话标题栏显示复制会话 ID 按钮。 */
31
+ 'session-id-copy': Volatile<boolean>;
32
+ /** 输入框工具行左组右端显示压缩上下文按钮。 */
33
+ 'compact-button': Volatile<boolean>;
34
+ }
35
+ /**
36
+ * 插件配置 schema。
37
+ *
38
+ * 不写成 `z<Config>`:`Config` 的字段是 `Volatile<T>`,而 volatile 字段的 schema 类型
39
+ * 处于 `"volatile-defined"` 模式,两者在双向方差下互不可赋值,加了注解反而编译不过
40
+ * (官方与已迁移的插件同样只写 `z.object`)。
41
+ */
42
+ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
43
+ 'provider-label': z<boolean, boolean, "volatile-defined">;
44
+ 'open-dsh-folder': z<boolean, boolean, "volatile-defined">;
45
+ 'settings-frame': z<boolean, boolean, "volatile-defined">;
46
+ 'model-picker': z<boolean, boolean, "volatile-defined">;
47
+ 'session-id-copy': z<boolean, boolean, "volatile-defined">;
48
+ 'compact-button': z<boolean, boolean, "volatile-defined">;
49
+ }>>, Schemastery.ObjectT<NoInfer<{
50
+ 'provider-label': z<boolean, boolean, "volatile-defined">;
51
+ 'open-dsh-folder': z<boolean, boolean, "volatile-defined">;
52
+ 'settings-frame': z<boolean, boolean, "volatile-defined">;
53
+ 'model-picker': z<boolean, boolean, "volatile-defined">;
54
+ 'session-id-copy': z<boolean, boolean, "volatile-defined">;
55
+ 'compact-button': z<boolean, boolean, "volatile-defined">;
56
+ }>>, "plain">;
57
+ /**
58
+ * 挂载插件:暴露打开 dshHome 文件夹的 Remote 服务,并声明本插件自带配置区。
59
+ * @param ctx - 需已提供 reflect(Typert 网关)的 Cordis 上下文。
15
60
  */
16
- export declare function apply(ctx: Context, config: FortifierConfig): void;
61
+ export declare function apply(ctx: Context): void;
17
62
  //# sourceMappingURL=index.d.ts.map
@@ -1,18 +1,18 @@
1
1
  /**
2
- * 打开本机 .dsh 文件夹的 Host Remote 服务。
2
+ * 打开本机 dshHome 文件夹的 Host Remote 服务。
3
3
  * 端点 `uiFortifier/openDshFolder` 由网关的 SRC 反射发现,无需 typert 生成器产物。
4
4
  */
5
5
  import { Context } from '@deepseek-ai/cordis';
6
6
  import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
7
7
  import type { DshFolderOpenValue } from './types.ts';
8
8
  export type { DshFolderOpenValue };
9
- /** 用系统默认方式打开 .dsh 文件夹;无原生打开器时返回路径让 UI 展示。 */
9
+ /** 用系统默认方式打开 dshHome 文件夹;无原生打开器时返回路径让 UI 展示。 */
10
10
  export declare class OpenDshFolderService extends TypertRemoteService {
11
11
  constructor(ctx: Context);
12
12
  /**
13
- * 打开 .dsh 文件夹。
13
+ * 打开 dshHome 文件夹。
14
14
  * @param signal - 调用方可取消信号。
15
- * @returns opened 为 false 时 path 为 .dsh 文件夹路径。
15
+ * @returns opened 为 false 时 path 为 dshHome 文件夹路径。
16
16
  */
17
17
  openDshFolder(signal: AbortSignal): Promise<DshFolderOpenValue>;
18
18
  }
@@ -1,25 +1,30 @@
1
1
  /**
2
2
  * 本包 Host/Client 两面共享的类型。
3
3
  * 仅类型、无运行时代码:双面工程各自编译本文件,无需跨面引用。
4
+ *
5
+ * 这里的 `Config` 是**解引用之后的普通值**,也就是配置区读写的那一份;Host 面的
6
+ * `Config` 六个字段都包在 volatile 引用里,见 [src/index.ts](./index.ts)。
4
7
  */
5
8
  /** 插件配置:功能开关状态(配置默认值)。 */
6
9
  export interface Config {
7
10
  /** 输入框显示当前提供商。 */
8
- 'provider-label': boolean;
9
- /** 设置页右上角显示打开 .dsh 文件夹按钮。 */
10
- 'open-dsh-folder': boolean;
11
+ readonly 'provider-label': boolean;
12
+ /** 插件页右上角显示打开 dshHome 文件夹按钮。 */
13
+ readonly 'open-dsh-folder': boolean;
11
14
  /** 设置面板可拖动位置与大小。 */
12
- 'settings-frame': boolean;
15
+ readonly 'settings-frame': boolean;
13
16
  /** 输入框右侧显示级联模型选择器(先选提供商、再选模型)。 */
14
- 'model-picker': boolean;
17
+ readonly 'model-picker': boolean;
15
18
  /** 会话标题栏显示复制会话 ID 按钮。 */
16
- 'session-id-copy': boolean;
19
+ readonly 'session-id-copy': boolean;
20
+ /** 输入框工具行左组右端显示压缩上下文按钮。 */
21
+ readonly 'compact-button': boolean;
17
22
  }
18
- /** 打开 .dsh 文件夹的返回值。 */
23
+ /** 打开 dshHome 文件夹的返回值。 */
19
24
  export interface DshFolderOpenValue {
20
25
  /** 是否已发起系统打开。 */
21
26
  readonly opened: boolean;
22
- /** .dsh 文件夹绝对路径(未打开时供 UI 展示)。 */
27
+ /** dshHome 文件夹绝对路径(未打开时供 UI 展示)。 */
23
28
  readonly path: string;
24
29
  }
25
30
  //# sourceMappingURL=types.d.ts.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-ui-fortifier",
3
3
  "description": "UI 强化",
4
- "version": "0.5.3",
4
+ "version": "0.7.0",
5
5
  "license": "MIT",
6
6
  "contributors": [
7
7
  "pgnqukezrdxmhjso"
@@ -44,34 +44,36 @@
44
44
  "lib/types/**/*.d.ts"
45
45
  ],
46
46
  "peerDependencies": {
47
- "@deepseek-ai/cordis": "^4.0.2",
48
- "@deepseek-ai/dsh-typert-protocol": "^0.1.5-rc.2",
49
- "@deepseek-ai/dsh-native-command": "^0.1.5-rc.2",
50
- "@deepseek-ai/dsh-home-paths": "^0.1.5-rc.2",
51
- "@deepseek-ai/schemastery": "^3.18.2"
47
+ "@deepseek-ai/cordis": "^4.0.4",
48
+ "@deepseek-ai/dsh-native-command": "^0.1.7-rc.2",
49
+ "@deepseek-ai/dsh-home-paths": "^0.1.7-rc.2",
50
+ "@deepseek-ai/dsh-typert-protocol": "^0.1.7-rc.2",
51
+ "@deepseek-ai/schemastery": "^3.18.4"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@types/react": "~18.3.1",
55
55
  "@types/react-dom": "~18.3.0",
56
56
  "react": "^18.2.0",
57
57
  "react-dom": "^18.2.0",
58
- "@deepseek-ai/dsh-api-remotes": "^0.1.5-rc.2",
59
- "@deepseek-ai/cordis": "^4.0.2",
60
- "@deepseek-ai/dsh-api-session-controller": "^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",
63
- "@deepseek-ai/dsh-client-ui-renderer": "^0.1.5-rc.2",
64
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.5-rc.2",
65
- "@deepseek-ai/dsh-client-ui-settings": "^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",
68
- "@deepseek-ai/dsh-native-command": "^0.1.5-rc.2",
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
- "@deepseek-ai/schemastery": "^3.18.2"
58
+ "@deepseek-ai/cordis": "^4.0.4",
59
+ "@deepseek-ai/dsh-client-ui-conversation": "^0.1.7-rc.2",
60
+ "@deepseek-ai/dsh-api-remotes": "^0.1.7-rc.2",
61
+ "@deepseek-ai/dsh-client-connection": "^0.1.7-rc.2",
62
+ "@deepseek-ai/dsh-client-ui-model-selection": "^0.1.7-rc.2",
63
+ "@deepseek-ai/dsh-api-session-controller": "^0.1.7-rc.2",
64
+ "@deepseek-ai/dsh-client-ui-plugin-manager": "^0.1.7-rc.2",
65
+ "@deepseek-ai/dsh-client-ui-renderer": "^0.1.7-rc.2",
66
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.7-rc.2",
67
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.7-rc.2",
68
+ "@deepseek-ai/dsh-client-ui-settings": "^0.1.7-rc.2",
69
+ "@deepseek-ai/dsh-client-ui-session": "^0.1.7-rc.2",
70
+ "@deepseek-ai/dsh-client-locale": "^0.1.7-rc.2",
71
+ "@deepseek-ai/dsh-home-paths": "^0.1.7-rc.2",
72
+ "@deepseek-ai/dsh-native-command": "^0.1.7-rc.2",
73
+ "@deepseek-ai/dsh-session": "^0.1.7-rc.2",
74
+ "@deepseek-ai/dsh-settings": "^0.1.7-rc.2",
75
+ "@deepseek-ai/schemastery": "^3.18.4",
76
+ "@deepseek-ai/dsh-typert-protocol": "^0.1.7-rc.2"
75
77
  },
76
78
  "dsh": {
77
79
  "bundle": {
@@ -84,6 +86,7 @@
84
86
  "@deepseek-ai/dsh-client-connection",
85
87
  "@deepseek-ai/dsh-client-ui-conversation",
86
88
  "@deepseek-ai/dsh-client-ui-model-selection",
89
+ "@deepseek-ai/dsh-client-ui-plugin-manager",
87
90
  "@deepseek-ai/dsh-client-ui-renderer",
88
91
  "@deepseek-ai/dsh-client-ui-settings",
89
92
  "@deepseek-ai/dsh-client-locale"
@@ -97,8 +100,9 @@
97
100
  "verify": "node ../dsh-pgnqukezrdxmhjso-base/verify.mjs",
98
101
  "pub": "pnpm run build && pnpm login --registry=https://registry.npmjs.org && pnpm publish --registry=https://registry.npmjs.org --no-git-checks",
99
102
  "pack": "pnpm run build && pnpm pack --pack-destination lib",
103
+ "link-test-i": "pnpm run build && (pnpm dsh plugin --profile pkgtest remove %npm_package_name% & pnpm dsh plugin --profile pkgtest add link:./)",
100
104
  "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%",
105
+ "npm-test-i": "pnpm dsh plugin --profile pkgtest remove %npm_package_name% & pnpm dsh plugin --profile pkgtest add --registry=https://registry.npmjs.org %npm_package_name%@%npm_package_version%",
102
106
  "test-create": "pnpm dsh --profile pkgtest --from-default-profile web",
103
107
  "test-start": "pnpm dsh --profile pkgtest --port 3081 --no-open"
104
108
  }