dsh-effort-menu 1.0.0 → 1.1.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
@@ -3,7 +3,7 @@
3
3
  A replacement model menu for the [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) composer: pick a model and set its reasoning effort in the same popup.
4
4
 
5
5
  [![MIT license](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
6
- [![DSH 0.2.2-rc.1 tested](https://img.shields.io/badge/DSH-0.2.2--rc.1%20tested-4d6bfe)](#compatibility)
6
+ [![DSH >=0.2.2-rc.1](https://img.shields.io/badge/DSH-%3E%3D0.2.2--rc.1-4d6bfe)](#compatibility)
7
7
 
8
8
  English | [简体中文](./README.zh-CN.md)
9
9
 
@@ -35,23 +35,27 @@ Hovering the provider row opens a flyout with every provider's models.
35
35
 
36
36
  ## Install
37
37
 
38
- Requires a DeepSeek Harness build that ships the `dsh-client-ui-conversation`, `dsh-client-ui-model-selection` and `dsh-client-locale` client packages (see [Compatibility](#compatibility)).
38
+ Requires DSH `0.2.2-rc.1` or newer, with a build that ships the `dsh-client-ui-conversation`, `dsh-client-ui-model-selection` and `dsh-client-locale` client packages (see [Compatibility](#compatibility)).
39
39
 
40
40
  ### Web
41
41
 
42
42
  With a CLI-managed profile, one command installs it into `web`.
43
43
 
44
44
  ```sh
45
- dsh plugin --profile web add github:xuanfenglajiao/dsh-effort-menu
45
+ dsh plugin --profile web add dsh-effort-menu
46
46
  ```
47
47
 
48
48
  There is no build step. The browser half is the committed `client.js`. Restart `dsh web`, or reload the page.
49
49
 
50
50
  ### Desktop
51
51
 
52
- The `desktop` profile is managed by the app, so `dsh plugin` refuses it. Clone the repository, then open **Plugin Manager** and install the checkout directory.
52
+ The `desktop` profile is managed by the app. Fully quit the app before using the CLI, or keep it running and install the checkout directory through **Plugin Manager**.
53
53
 
54
54
  ```sh
55
+ # CLI install, with the desktop app fully quit
56
+ dsh plugin --profile desktop add dsh-effort-menu
57
+
58
+ # Or clone it and point Plugin Manager at the directory
55
59
  git clone https://github.com/xuanfenglajiao/dsh-effort-menu.git
56
60
  ```
57
61
 
@@ -65,7 +69,7 @@ If you would rather not use the CLI, edit the profile's own `package.json`.
65
69
 
66
70
  ```json
67
71
  "dependencies": {
68
- "dsh-effort-menu": "github:xuanfenglajiao/dsh-effort-menu"
72
+ "dsh-effort-menu": "^1.0.0"
69
73
  }
70
74
  ```
71
75
 
@@ -81,7 +85,7 @@ Run `pnpm install` in that directory and restart `dsh web`.
81
85
 
82
86
  ```json
83
87
  "dependencies": {
84
- "dsh-effort-menu": "github:xuanfenglajiao/dsh-effort-menu"
88
+ "dsh-effort-menu": "^1.0.0"
85
89
  }
86
90
  ```
87
91
 
@@ -91,7 +95,7 @@ Run `pnpm install` and restart the app.
91
95
 
92
96
  ## Compatibility
93
97
 
94
- - Developed and tested on DeepSeek Harness Desktop 44.7.0 (DSH `0.2.2-rc.1`) on Windows 11. Earlier DSH lines are untested.
98
+ - Supports DSH `0.2.2-rc.1` and newer. Developed and tested on DeepSeek Harness Desktop 44.7.0 (DSH `0.2.2-rc.1`) on Windows 11; earlier DSH versions are not supported.
95
99
  - The current model must expose `reasoning.efforts` for the slider to have anything to show. Otherwise the menu says the model provides no levels.
96
100
  - Renders nothing in subagent sessions.
97
101
 
package/README.zh-CN.md CHANGED
@@ -3,7 +3,7 @@
3
3
  给 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 输入框换一个模型菜单,选模型和调思考强度在同一个弹层里完成。
4
4
 
5
5
  [![MIT license](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
6
- [![DSH 0.2.2-rc.1 tested](https://img.shields.io/badge/DSH-0.2.2--rc.1%20tested-4d6bfe)](#兼容性)
6
+ [![DSH >=0.2.2-rc.1](https://img.shields.io/badge/DSH-%3E%3D0.2.2--rc.1-4d6bfe)](#兼容性)
7
7
 
8
8
  [English](./README.md) | 简体中文
9
9
 
@@ -35,23 +35,27 @@
35
35
 
36
36
  ## 安装
37
37
 
38
- 需要所用的 DeepSeek Harness 自带 `dsh-client-ui-conversation`、`dsh-client-ui-model-selection` 和 `dsh-client-locale` 三个客户端包(见[兼容性](#兼容性))。
38
+ 需要 DSH `0.2.2-rc.1` 或更新版本,并自带 `dsh-client-ui-conversation`、`dsh-client-ui-model-selection` 和 `dsh-client-locale` 三个客户端包(见[兼容性](#兼容性))。
39
39
 
40
40
  ### Web 端
41
41
 
42
42
  用 CLI 管理 profile 时,一条命令装进 `web`。
43
43
 
44
44
  ```sh
45
- dsh plugin --profile web add github:xuanfenglajiao/dsh-effort-menu
45
+ dsh plugin --profile web add dsh-effort-menu
46
46
  ```
47
47
 
48
48
  本插件没有构建步骤,浏览器端就是仓库里提交的 `client.js`。装完重启 `dsh web`,或者刷新页面。
49
49
 
50
50
  ### 桌面端
51
51
 
52
- `desktop` profile 由应用独占管理,`dsh plugin` 会直接拒绝。先把仓库克隆到本地,再打开 **Plugin Manager**,选择安装这个目录。
52
+ `desktop` profile 由应用管理。先完全退出桌面应用,再用 CLI 安装;或者不退出,打开应用内的 **Plugin Manager** 选择安装这个目录。
53
53
 
54
54
  ```sh
55
+ # CLI 安装,需要先完全退出桌面应用
56
+ dsh plugin --profile desktop add dsh-effort-menu
57
+
58
+ # 或者克隆下来,在 Plugin Manager 里选择这个目录
55
59
  git clone https://github.com/xuanfenglajiao/dsh-effort-menu.git
56
60
  ```
57
61
 
@@ -65,7 +69,7 @@ git clone https://github.com/xuanfenglajiao/dsh-effort-menu.git
65
69
 
66
70
  ```json
67
71
  "dependencies": {
68
- "dsh-effort-menu": "github:xuanfenglajiao/dsh-effort-menu"
72
+ "dsh-effort-menu": "^1.0.0"
69
73
  }
70
74
  ```
71
75
 
@@ -81,7 +85,7 @@ git clone https://github.com/xuanfenglajiao/dsh-effort-menu.git
81
85
 
82
86
  ```json
83
87
  "dependencies": {
84
- "dsh-effort-menu": "github:xuanfenglajiao/dsh-effort-menu"
88
+ "dsh-effort-menu": "^1.0.0"
85
89
  }
86
90
  ```
87
91
 
@@ -91,7 +95,7 @@ git clone https://github.com/xuanfenglajiao/dsh-effort-menu.git
91
95
 
92
96
  ## 兼容性
93
97
 
94
- - 开发和测试用的是 DeepSeek Harness Desktop 44.7.0(DSH `0.2.2-rc.1`),Windows 11。更早的 DSH 版本没有测过。
98
+ - 支持 DSH `0.2.2-rc.1` 及更新版本。开发和测试用的是 DeepSeek Harness Desktop 44.7.0(DSH `0.2.2-rc.1`),Windows 11;更早的 DSH 版本不受支持。
95
99
  - 当前模型必须公布 `reasoning.efforts`,滑杆才有内容可显示。否则菜单会提示该模型没有可选档位。
96
100
  - 在子代理会话中不渲染任何内容。
97
101
 
package/client.js CHANGED
@@ -22,6 +22,7 @@ window.__ModuleLoader__.load({
22
22
  'menu.recent': '最近使用',
23
23
  'menu.allModels': '全部模型',
24
24
  'menu.effort': '思考强度',
25
+ 'menu.manageModels': '管理模型',
25
26
  'provider.account': 'DeepSeek 账号',
26
27
  'trigger.fallback': '请选择模型',
27
28
  'trigger.selectAria': '请选择模型',
@@ -44,6 +45,7 @@ window.__ModuleLoader__.load({
44
45
  'menu.recent': 'Recently used',
45
46
  'menu.allModels': 'All models',
46
47
  'menu.effort': 'Thinking intensity',
48
+ 'menu.manageModels': 'Manage models',
47
49
  'provider.account': 'DeepSeek Account',
48
50
  'trigger.fallback': 'Select model',
49
51
  'trigger.selectAria': 'Select model',
@@ -87,7 +89,7 @@ window.__ModuleLoader__.load({
87
89
  .es-providerChevron{width:14px;height:14px;flex:none;color:var(--dsw-alias-label-caption,var(--dsw-alias-label-secondary));transition:transform .18s ease}
88
90
  .es-flyout{box-sizing:border-box;position:fixed;z-index:1101;width:280px;max-width:calc(100vw - 24px);max-height:min(480px,calc(100vh - 24px));display:flex;flex-direction:column;padding:6px;border-radius:12px;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-specific-input-major,var(--dsw-alias-bg-overlay));box-shadow:var(--dsw-elevation-prominent,0 18px 48px rgba(0,0,0,.45));color:var(--dsw-alias-label-primary);font-size:var(--dsh-content-font-size,13px);line-height:calc(20px + var(--dsh-content-font-delta,0px));overflow:hidden}
89
91
  .es-flyout:focus{outline:none}
90
- .es-flyoutList{display:flex;flex-direction:column;min-height:0;overflow-x:hidden;overflow-y:auto;overscroll-behavior:contain}
92
+ .es-flyoutList{display:flex;flex-direction:column;min-height:0;flex:1;overflow-x:hidden;overflow-y:auto;overscroll-behavior:contain}
91
93
  .es-sectionLabel{flex:none;padding:7px 8px 3px;color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary));font-size:calc(11px + var(--dsh-content-font-delta-secondary,0px));line-height:16px;font-weight:500}
92
94
  .es-modelGroup{display:flex;flex-direction:column}
93
95
  .es-groupHeading{flex:none;padding:6px 8px 2px;color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary));font-size:calc(11px + var(--dsh-content-font-delta-secondary,0px));line-height:16px;font-weight:500}
@@ -102,6 +104,13 @@ window.__ModuleLoader__.load({
102
104
  .es-check{flex:0 0 14px;display:grid;place-items:center;color:var(--dsw-alias-label-primary)}
103
105
  .es-effortDivider{border-top:1px solid var(--dsw-alias-border-l1);margin:8px 0 0;flex:none}
104
106
  .es-effortBlock{padding:8px 2px 2px;border-radius:10px;flex:none}
107
+ .es-footerDivider{border-top:1px solid var(--dsw-alias-border-l1);margin:8px 0 0;flex:none}
108
+ .es-footer{padding:4px 0 0;flex:none}
109
+ .es-manageRow{box-sizing:border-box;width:100%;height:calc(36px + var(--dsh-content-font-delta,0px));display:flex;align-items:center;gap:8px;padding:0 8px;margin:0;border:0;border-radius:8px;background:0 0;color:inherit;font:inherit;text-align:left;cursor:pointer;outline:none;transition:background-color .16s ease}
110
+ @media (hover:hover){.es-manageRow:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}}
111
+ .es-manageRow:focus-visible{background:var(--dsw-alias-interactive-bg-hover);box-shadow:inset 0 0 0 1px var(--dsw-alias-border-l2)}
112
+ .es-manageRow:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}
113
+ .es-manageLabel{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-size:var(--dsh-content-font-size,13px);line-height:calc(20px + var(--dsh-content-font-delta,0px))}
105
114
  .es-effortHeader{display:flex;align-items:center;gap:8px;padding:2px 6px 0}
106
115
  .es-effortLabel{color:var(--dsw-alias-label-primary);flex:none;font-size:var(--dsh-content-font-size,13px);line-height:calc(20px + var(--dsh-content-font-delta,0px))}
107
116
  .es-effortValue{color:var(--dsw-alias-label-primary);font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-size:var(--dsh-content-font-size,13px);line-height:calc(20px + var(--dsh-content-font-delta,0px))}
@@ -212,6 +221,98 @@ window.__ModuleLoader__.load({
212
221
  );
213
222
  }
214
223
 
224
+ /**
225
+ * Click the Models row in an open Settings dialog. The row index comes from the
226
+ * live `settings.section` ledger (the shell sorts its nav by `order`), with a
227
+ * localized label fallback for shells whose registry is not exposed here.
228
+ */
229
+ function selectModelsSection(scope, panel) {
230
+ let index = -1;
231
+ try {
232
+ const sections = scope.slots
233
+ .entries('settings.section')
234
+ .map((entry) => ({
235
+ id: entry.options === undefined || entry.options.id === undefined ? '' : entry.options.id,
236
+ order: entry.options === undefined || entry.options.order === undefined ? 0 : entry.options.order,
237
+ }))
238
+ .sort((left, right) => left.order - right.order);
239
+ index = sections.findIndex((section) => section.id === 'models');
240
+ } catch (_error) {
241
+ index = -1;
242
+ }
243
+ const nav = panel.querySelector('nav');
244
+ const buttons = nav === null ? [] : Array.from(nav.querySelectorAll('button'));
245
+ let target = index >= 0 && index < buttons.length ? buttons[index] : null;
246
+ if (target === null) {
247
+ const label = /^(模型|Models?|モデル|모델|Модели|Modèles|Modelle|Modelos|Modelli)$/i;
248
+ target = buttons.find((button) => label.test((button.textContent || '').trim())) || null;
249
+ }
250
+ if (target !== null) target.click();
251
+ }
252
+
253
+ /**
254
+ * Open Settings on its Models section. The settings shell keeps its open
255
+ * state in the store registered on `sidebar.settings`, so the exact live
256
+ * instance is reachable through the slot ledger. If that route is absent,
257
+ * invoke the shipped `settings.open` command and click the Models row in the
258
+ * rendered dialog instead.
259
+ */
260
+ function openModelsSettings(scope) {
261
+ const existing = document.querySelector('[data-shortcut-modal="settings"]');
262
+ if (existing !== null) {
263
+ selectModelsSection(scope, existing);
264
+ return;
265
+ }
266
+ let opened = false;
267
+ try {
268
+ const entries =
269
+ typeof scope.slots.entriesOfSlot === 'function'
270
+ ? scope.slots.entriesOfSlot('sidebar.settings')
271
+ : scope.slots.entries('sidebar.settings');
272
+ const entry = entries.find(
273
+ (candidate) =>
274
+ candidate !== undefined &&
275
+ candidate !== null &&
276
+ candidate.store !== undefined &&
277
+ typeof candidate.store.create === 'function',
278
+ );
279
+ const instance = entry === undefined ? undefined : entry.store.create();
280
+ if (
281
+ instance !== undefined &&
282
+ instance !== null &&
283
+ instance.actions !== undefined &&
284
+ typeof instance.actions.openSection === 'function'
285
+ ) {
286
+ instance.actions.openSection('models');
287
+ opened = true;
288
+ }
289
+ } catch (_error) {
290
+ opened = false;
291
+ }
292
+ if (!opened) {
293
+ const shortcuts = scope.shortcuts;
294
+ const registry = shortcuts === undefined || shortcuts === null ? undefined : shortcuts.registry;
295
+ if (registry !== undefined && registry !== null && typeof registry.invoke === 'function') {
296
+ registry.invoke('settings.open', { region: 'page', modal: null, target: null });
297
+ }
298
+ }
299
+ // The dialog is portaled on the next React commit; retry briefly when the
300
+ // shell store route missed and the command opened the dialog asynchronously.
301
+ let attempts = 0;
302
+ const settle = () => {
303
+ const panel = document.querySelector('[data-shortcut-modal="settings"]');
304
+ if (panel === null) {
305
+ if (attempts < 40) {
306
+ attempts += 1;
307
+ window.setTimeout(settle, 50);
308
+ }
309
+ return;
310
+ }
311
+ selectModelsSection(scope, panel);
312
+ };
313
+ window.setTimeout(settle, 30);
314
+ }
315
+
215
316
  const RECENT_KEY = 'dsh-effort-menu/recent-models';
216
317
  /** The menu lists at most this many recent models, the current one included. */
217
318
  const RECENT_VISIBLE = 3;
@@ -576,7 +677,7 @@ window.__ModuleLoader__.load({
576
677
  * @param props - owner share (`locked`), the injected directory face, and locale `t`.
577
678
  * @returns the trigger button plus, while open, a portaled menu.
578
679
  */
579
- function EffortSeat({ locked, available, directory, load, select, resolveText, t }) {
680
+ function EffortSeat({ locked, available, directory, load, select, resolveText, t, manageModels }) {
580
681
  const state = React.useSyncExternalStore(
581
682
  (callback) => directory.subscribe(callback),
582
683
  () => directory.getSnapshot(),
@@ -1159,7 +1260,7 @@ window.__ModuleLoader__.load({
1159
1260
  return;
1160
1261
  }
1161
1262
  if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp') return;
1162
- const rows = Array.from(event.currentTarget.querySelectorAll('.es-modelRow')).filter((row) => !row.disabled);
1263
+ const rows = Array.from(event.currentTarget.querySelectorAll('.es-modelRow, .es-manageRow')).filter((row) => !row.disabled);
1163
1264
  if (rows.length === 0) return;
1164
1265
  event.preventDefault();
1165
1266
  const at = rows.indexOf(document.activeElement);
@@ -1169,6 +1270,23 @@ window.__ModuleLoader__.load({
1169
1270
  },
1170
1271
  },
1171
1272
  h('div', { className: 'es-flyoutList' }, allGroups),
1273
+ h('div', { className: 'es-footerDivider' }),
1274
+ h(
1275
+ 'div',
1276
+ { className: 'es-footer' },
1277
+ h(
1278
+ 'button',
1279
+ {
1280
+ type: 'button',
1281
+ className: 'es-manageRow',
1282
+ onClick: () => {
1283
+ close(false);
1284
+ if (typeof manageModels === 'function') manageModels();
1285
+ },
1286
+ },
1287
+ h('span', { className: 'es-manageLabel' }, t('menu.manageModels')),
1288
+ ),
1289
+ ),
1172
1290
  );
1173
1291
 
1174
1292
  return h(
@@ -1203,14 +1321,14 @@ window.__ModuleLoader__.load({
1203
1321
  return currentModel === null && state.current !== null && state.current !== undefined;
1204
1322
  }
1205
1323
 
1206
- const inject = ['locale', 'slots', 'modelDirectories', 'sessions'];
1324
+ const inject = ['locale', 'slots', 'modelDirectories', 'sessions', 'shortcuts'];
1207
1325
 
1208
1326
  /**
1209
1327
  * Mount the replacement seat over the shipped per-session model directory.
1210
1328
  * @param ctx - client root context.
1211
1329
  */
1212
1330
  function apply(ctx) {
1213
- ctx.inject(['locale', 'slots', 'modelDirectories', 'sessions'], (scope) => {
1331
+ ctx.inject(['locale', 'slots', 'modelDirectories', 'sessions', 'shortcuts'], (scope) => {
1214
1332
  scope.effect(() => scope.locale.register(NS, { zh, en }), 'effort-menu: dictionaries');
1215
1333
  const models = scope.modelDirectories;
1216
1334
  const sessions = scope.sessions;
@@ -1231,6 +1349,7 @@ window.__ModuleLoader__.load({
1231
1349
  },
1232
1350
  resolveText: (text) => scope.locale.resolveText(text),
1233
1351
  select: (selection) => (available ? directory.select(selection) : Promise.resolve(undefined)),
1352
+ manageModels: () => openModelsSettings(scope),
1234
1353
  };
1235
1354
  },
1236
1355
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-effort-menu",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Replaces the composer's model menu with a recent-models list, an all-providers flyout, and a draggable reasoning-effort slider.",
5
5
  "keywords": [
6
6
  "dsh-plugin",
@@ -56,6 +56,7 @@
56
56
  "immediately": true,
57
57
  "inject": [
58
58
  "@deepseek-ai/dsh-client-locale",
59
+ "@deepseek-ai/dsh-client-shortcuts",
59
60
  "@deepseek-ai/dsh-client-ui-conversation",
60
61
  "@deepseek-ai/dsh-client-ui-model-selection"
61
62
  ]