@nimoxie/dsh-model-organizer 0.5.11 → 0.5.19
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/DEVELOPMENT.md +71 -55
- package/README.md +97 -93
- package/lib/client.js +436 -126
- package/package.json +2 -2
package/DEVELOPMENT.md
CHANGED
|
@@ -133,10 +133,28 @@ pi-ai 的 profile 与 `modelFields` **都没有 enabled/disabled 字段**,所
|
|
|
133
133
|
|
|
134
134
|
---
|
|
135
135
|
|
|
136
|
-
##
|
|
136
|
+
## 设置页的 UI 约定(都是实机截图打回来的)
|
|
137
137
|
|
|
138
|
-
|
|
139
|
-
|
|
138
|
+
### 基础两条(0.4.0)
|
|
139
|
+
|
|
140
|
+
1. **控件必须 `boxSizing: "border-box"`**:`width: "100%"` 配默认 content-box 时,宽度是 100% 再加 padding 与边框,输入框会顶出设置列(实机截图里 API 地址那栏)。凡会被拉伸的输入框/选择器/按钮都显式声明 border-box。
|
|
141
|
+
2. **不要用原生 `<select>`**:下拉列表由平台绘制,本主题下是白底浅字(截图里「完全看不清」)。协议选择改用三个自绘的 radio 分段按钮(`pageSegments`/`pageSegment`),完整标签放 `title` 与下方提示行。
|
|
142
|
+
|
|
143
|
+
### 0.5.12:按钮的尺寸、对齐、颜色与文案
|
|
144
|
+
|
|
145
|
+
- **尺寸**:三颗行内图标键(供应商删除、模型测试、模型删除)共用 `ACTION_BOX`(19×17、圆角 6、`border-l1`)。高度**刻意等于启用开关的 17**——早期 26×22 被截图指出「比开关高一头」;宽度按截图在 20 → 22 → 21 → 17 → 19 之间收敛定稿,图标 13px。
|
|
146
|
+
- **对齐**:详情头部是 `align-items: baseline`。图标键与开关都没有文字基线(前者首个 flex 项是 SVG,后者是带 2px padding + 1px 边框的圆点盒),各自按自己的基线对,盒子同高却差 1–2px。两者都加 `alignSelf: "center"`:文字仍按基线排,控件按中线互相对齐。
|
|
147
|
+
- **颜色**:删除键 = `DELETE_ACTION`(共享盒子 + 红框、透明底、红图标);测试连接键 = `TEST_ACTION`(只覆盖 `background` 为普通按钮的浅底)。结论色只留在图标上(`.dsh-mo-act-ok/-miss/-danger`),不染边框。
|
|
148
|
+
- **文案**:悬停只回答「这个按钮做什么 / 现在什么状态」,一行说完;实现细节留在本文档。冒烟测试钉「提示 ≤16 字、结论 ≤18 字」,防止长段落回流。
|
|
149
|
+
|
|
150
|
+
### 按钮悬停:规则为什么只能写在注入样式表里
|
|
151
|
+
|
|
152
|
+
页面与四个弹窗的按钮**静止态是内联样式**(`S.pageButton` 等),而内联样式赢过普通 `:hover`——直接写 `.foo:hover{background:…}` 会静默失效(与 0.4.0「内联背景盖掉官方 hover」是同一个坑)。做法:
|
|
153
|
+
|
|
154
|
+
- 悬停/按下/禁用三态写在 `ensureOwnStyles()` 注入的样式表里,且用 `!important`(只用于这三个状态,静止态仍由内联样式负责);
|
|
155
|
+
- 作用域 `:where(page、panel、四个弹窗的 button)`:`:where()` 优先级为 0,变体规则(主要操作保持强调色、危险操作泛红、旁白按钮提亮文字)才能稳定压过通用规则;
|
|
156
|
+
- **开关(`role="switch"`)显式排除**:它的底色就是开/关状态,悬停改底色会被误读成状态切换,只加 `box-shadow` 光环;
|
|
157
|
+
- 关闭键(`data-mo$="-close"`)悬停时把图标提到 `label-primary`,否则只变底色看不出来。
|
|
140
158
|
|
|
141
159
|
## 已知的非插件告警:permission preset "auto"(会连带「权限选择消失」和「工具通道断」)
|
|
142
160
|
|
|
@@ -172,7 +190,7 @@ Error: permission: cannot restore preset "auto" without its active integration
|
|
|
172
190
|
|
|
173
191
|
**症状**:在「添加模型提供商」弹窗里按 Tab,焦点跳到弹窗背后的设置页。
|
|
174
192
|
|
|
175
|
-
|
|
193
|
+
**原因**:每个弹窗都用 `ReactDOM.createPortal` 挂到 `document.body`,在文档顺序里排在整页**之后**。两张脸:① Tab 从卡片最后一个控件继续往前走,浏览器绕回文档开头(也就是设置页的导航);② 打开瞬间焦点没落进弹窗(宿主壳自己的 focus 流程会把刚插入的 portal 的 `focus()` 抢走),于是第一次 Tab 直接从页面上的按钮继续走。
|
|
176
194
|
|
|
177
195
|
**0.5.1 为什么没修好**:第一版只在卡片上挂 `onKeyDown`,且**只在边界**(第一个/最后一个)才 `preventDefault`。中间步骤仍交给浏览器自己走;只要焦点不在卡片里(情形②),或者边界判定没命中,浏览器就照旧走到页面上去。**教训:不能让浏览器"帮我们走一步",必须整段接管。**
|
|
178
196
|
|
|
@@ -238,81 +256,74 @@ Error: permission: cannot restore preset "auto" without its active integration
|
|
|
238
256
|
|
|
239
257
|
**教训**:凡是「问宿主网关」的调用,参数要和已经跑通的那条路径逐项对一遍;冒烟测试里固定断言 `request.baseURL`,防止再退化。
|
|
240
258
|
|
|
241
|
-
### 0.5.6–0.5.
|
|
242
|
-
|
|
243
|
-
那两版在"按钮多大"上来回改:`56×20 → 32×20 → 36×22 → 20×17`,期间还把 emoji `🗑` 换成手绘 SVG 的 `TrashIcon`,越修越不像界面里的东西。
|
|
259
|
+
### 0.5.6–0.5.11:行内图标键的四次返工(教训)
|
|
244
260
|
|
|
245
|
-
|
|
261
|
+
- **别自己画图标**:emoji `🗑` 的墨迹远小于字号,手绘 SVG 又和界面里的东西不像;改用 DSH 自带 primitives(0.5.8,98 个图标),`kitIcon()` 取不到时退回同尺寸文字 glyph,无头测试仍能跑。
|
|
262
|
+
- **尺寸别靠眼估**:盒子在 `56×20 → 32×20 → 36×22 → 20×17` 之间反复;真正的判据是「与相邻控件同高」(开关 17),最终统一 19×17(见 0.5.12)。
|
|
263
|
+
- **「转圈看不见」不是动画没写**:本地网关 50ms 就答完,React 会把 busy 与结论合并进同一帧,屏幕上从未出现转圈帧。修法:点击回调里直接给 DOM 加 `dsh-mo-act-busy` 并起 CSS 动画(`startSpinNow`),结论至少 450ms 后才落状态;动画作用在 `svg` 上,按钮尺寸不变。
|
|
264
|
+
- **测试连接失败先查参数**:`discoverModels()` 对目录外路由要求 `baseURL`,只发 `{ provider }` 会被宿主拒绝(0.5.5 的真因);凡是问宿主的调用,参数与已经跑通的那条路径逐项对齐。
|
|
246
265
|
|
|
247
|
-
|
|
248
|
-
2. **别自己画图标**:DSH 有现成的图标库(见 0.5.8),手绘 SVG 只会和界面其它按钮不是一套。
|
|
266
|
+
### 0.5.13–0.5.14:新建弹窗里的模型改成「清单 + 勾选」
|
|
249
267
|
|
|
250
|
-
|
|
268
|
+
**问题**:0.5.3 为了让「必须列出模型」这条宿主规则可满足,把「从网关获取」做成了**一次性把网关返回的所有 id 灌进输入框**。这和官方交互相反(官方是弹窗勾选、点添加才落盘),用户也没得选。
|
|
251
269
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
**背景**:0.5.7 手绘了一个垃圾桶 `<svg>`,虽然尺寸对了,但和界面其它按钮不是一套东西。DSH 其实自带图标库:
|
|
270
|
+
**做法**:
|
|
255
271
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
272
|
+
1. `addDialog.models` 从字符串改成**草稿数组**(每项 `{id, context, maxTokens, vision}`),另加 `modelInput` 只承载手工输入;
|
|
273
|
+
2. 手工添加:`parseModelIds(modelInput)` → `addModelsToDraft()` 去空去重后追加,输入框清空;
|
|
274
|
+
3. 「获取可用模型」:用对话框里的草稿(`baseURL` + `api` + 一次性 `apiKey`)调 `discoverModels`,候选塞进**同一个 picker**,只是多带一个 `target: "draft"`;
|
|
275
|
+
4. `fetch-adopt` 分叉:`target === "draft"` → 只回填弹窗清单(**不写盘**);否则维持 `writeModels(draftRows.concat(chosen))`;
|
|
276
|
+
5. 清单在弹窗里逐行展示(id + 容量/视觉 chips + 🗑 移除),空清单显示提示;
|
|
277
|
+
6. `createProvider()` 改用 `modelsFromDraft()` 统一校验(空 ID / 重复 / 容量非法都在写盘前拒),profile 写成 `{ api, models: built.models }`。
|
|
261
278
|
|
|
262
|
-
|
|
279
|
+
**为什么复用 picker**:同一个弹窗、同一套搜索/全选/勾选,draft 与 saved 的唯一差别是「采纳之后去哪」,用 `target` 区分,避免第二套弹窗各写一份逻辑。
|
|
263
280
|
|
|
264
|
-
|
|
281
|
+
**0.5.14(同一处的排版)**:手工输入框原来用 `pageInputWide`(`width: 100%`),在 `pageKeyRow`(`flex-wrap: wrap`)里会把「添加」挤到下一行。改成新的 `pageInlineRow`(`nowrap`)+ `pageInputGrow`(`flex: 1 1 auto; width: auto`):输入框自适应,按钮稳定在右侧。冒烟断言 `flexWrap === "nowrap"` 且输入框 `width === "auto"`。
|
|
265
282
|
|
|
266
|
-
|
|
267
|
-
2. 三个按钮都换成**无边框 ghost 图标按钮**(`S.pageIconAction` 26×22、`S.pageIconActionHead` 22×17),hover 底色与配色全部交给注入的样式表:`.dsh-mo-act` / `.dsh-mo-act-danger` / `.dsh-mo-act-ok` / `.dsh-mo-act-miss`,图标靠 `currentColor` 继承颜色;
|
|
268
|
-
3. 测试按钮的**图标就是结论**:`▶` → `⟳` → `✓` / `⚠` / `✗`(同一 14px,按钮盒子不变)。删除按钮是 `IconTrashOutlineRegular`,常红;
|
|
269
|
-
4. 供应商详情那颗高度仍是 17(等于启用开关),模型行两颗同为 26×22。
|
|
283
|
+
**顺带**:删掉 0.5.8 之后就不再使用的 `page.testOkShort / testMissingShort / testFailShort`(zh/en 同步,157/157)。
|
|
270
284
|
|
|
271
|
-
|
|
285
|
+
**回归**:冒烟断言 —— 「获取可用模型」必须弹出 picker(`fetch-dialog`);「添加所选」后清单出现该 id 且**仍未写盘**;逐个移除后创建被拒且零写盘;手工输入 `glm-4.6, qwen-max` 后清单两项、创建时同一次 mutate 带 `models`。
|
|
272
286
|
|
|
273
|
-
### 0.5.9:测试按钮的图标语义 + 转圈动画
|
|
274
287
|
|
|
275
|
-
|
|
288
|
+
### 0.5.15:内置目录回退 + 第三方预设
|
|
276
289
|
|
|
277
|
-
|
|
290
|
+
**A. 目录回退(为什么必须先做)**:本页的模型一直只来自配置里的 `providers.<route>.models`。而用预设(或官方编辑器)建出来的内置供应商,profile 是**空对象**,模型在适配器的内置目录里 → 本页会显示「没有模型」。
|
|
278
291
|
|
|
279
|
-
|
|
280
|
-
@keyframes dsh-mo-spin{from{transform:rotate(0)}to{transform:rotate(360deg)}}
|
|
281
|
-
.dsh-mo-act-busy svg{animation:dsh-mo-spin .9s linear infinite}
|
|
282
|
-
```
|
|
292
|
+
修法:选中一条 `models` 为空的 route 时,用 `discoverModels(namespace, { provider })` 读目录(目录供应商**零网络**),把结果作为**只读行**渲染(`page-model-inherited` + 一行提示),保留绿点与「测试连接」,不参与拖拽、不能打开详情弹窗;`catalogRows` 按 route 缓存,切回来不重复请求。
|
|
283
293
|
|
|
284
|
-
|
|
294
|
+
**B. 第三方预设**:新建弹窗加了「添加方式」两个选项(仅在存在可添加的预设时出现):
|
|
285
295
|
|
|
286
|
-
|
|
296
|
+
- 可添加预设 = `listConfigurableProviders()` 里 `declared === true` 且 `settingsPath` 非空、`settingsNs` 就是本命名空间、且 `providers[id]` 还没配置的条目(和官方那个下拉同源);
|
|
297
|
+
- 选预设 → 只需「显示名(可选)+ 密钥(可选)」;写盘是 **`providers.<id> = {}`**(有密钥时再加 `apiKeyEnv`),**不写 models** —— 地址/协议/模型由目录提供,这也是它不会撞上 `resolves no models` 的原因;
|
|
298
|
+
- 预设弹窗是**独立的第二个分支**(`data-mo-mode="catalog"`),共用一个 `addModeTabs()`;自定义那条分支原样不动,避免在同一段 JSX 里塞两套条件。
|
|
287
299
|
|
|
288
|
-
|
|
300
|
+
**顺带**:`page.baseUrlRequired` 与 `page.modelsFieldHint` 里残留的「从网关获取」都改成「获取可用模型」(按钮改名时漏了这两处,实机截图发现的)。
|
|
289
301
|
|
|
290
|
-
|
|
302
|
+
**0.5.16**:「添加方式」那行改成**始终渲染**。之前在没有可添加预设(内置目录里的供应商都配置过了)时整行 `return null`,实机上看起来就是「这个功能不存在」。现在第三方那格变灰、`title` 说明原因,自定义始终可选。冒烟补了这条:预设耗尽时仍有两个模式选项、catalog 侧 `disabled` 且带说明、custom 侧可用。
|
|
291
303
|
|
|
292
|
-
|
|
304
|
+
**0.5.17:`declared` 的语义我弄反了**(实机反馈「第三方那格一直是灰的」)。目录条目里的 `declared` 是**「用户手工声明」**,不是「由目录声明」:
|
|
293
305
|
|
|
294
|
-
|
|
306
|
+
```js
|
|
307
|
+
// dsh-llm-pi-ai/lib/index.js directoryEntries()
|
|
308
|
+
declared: installed === void 0 // 目录里没有这个 provider 才是 true
|
|
309
|
+
```
|
|
295
310
|
|
|
296
|
-
|
|
311
|
+
所以「可添加预设」必须取 `declared === false` 的那批(目录自带、尚未配置),我写成 `!== true` → 恒为空。改成 `if (entry.declared === true) continue;` 后正常。**教训:字段名读起来像什么不重要,去读生成它的那行代码。**(本页原有的「官方目录 / 自定义供应商」分组其实一直是对的:`entry.declared === true ? "custom" : "catalog"`。)
|
|
297
312
|
|
|
298
|
-
|
|
313
|
+
**0.5.18:一个弹窗,两套内容**(实机反馈「又出来个弹窗」)。0.5.15 为了少改 JSX,把预设做成了**第二个 portal**(切换模式 = 换一个弹窗),视觉上就是「弹窗关掉又冒出一个」。现在合并成一个 `DialogShell`:`addDialog.mode` 只决定内容 —— 预设块用三元插在 `addModeTabs()` 之后,自定义那四个字段用 `addDialog.mode === "catalog" ? null : h(...)` 前缀挡住(前缀不影响数组元素结构),显示名/密钥两栏共用;提交按钮在 catalog 模式且未选预设时禁用。
|
|
299
314
|
|
|
300
|
-
|
|
315
|
+
**下拉「全白」**:原生 `<select>` 的弹出层跟系统主题走,深色界面里就是白底。补了 `.dsh-mo-select` 规则(深色底 + 前景色 + 边框),并在控件上写 `colorScheme: "dark"`,让 Chromium 用深色渲染那个 popup 与 `option` 列表。
|
|
316
|
+
**回归**:冒烟断言 —— 新建弹窗有两个模式选项;预设下拉列出未配置的 `kimi`;选它创建 = 一次 mutate、只设 `providers/kimi`、**不带 models**、弹窗关闭;`providers.bare = {}` 的路由渲染出目录里的只读模型行 + 提示,且没有可编辑行。
|
|
301
317
|
|
|
302
|
-
### 0.5.11:立刻开转、开没开都转
|
|
303
318
|
|
|
304
|
-
|
|
319
|
+
### 0.5.19:弹窗可拖动、点外面不关
|
|
305
320
|
|
|
306
|
-
|
|
321
|
+
**可拖动**:`DialogShell` 自己持有一个 `offset {x,y}` 状态,卡片渲染成 `transform: translate(...)`;`onMouseDown` 挂在卡片上,但只认**带 `data-mo-drag` 的头部条**(`S.pickHead`),并且 `closest("button, a, input, select, textarea, [data-mo-no-drag]")` 命中时直接放弃 —— 所以点 ✕、点输入框都不会挪窗。拖动过程用 document 上的 `mousemove`/`mouseup` 捕获,鼠标移出卡片也不会断。
|
|
307
322
|
|
|
308
|
-
|
|
309
|
-
node.classList.add("dsh-mo-act-busy"); // 让 CSS 规则接手
|
|
310
|
-
node.querySelector("svg").style.animation = "dsh-mo-spin .9s linear infinite"; // 当场起转
|
|
311
|
-
```
|
|
323
|
+
**点外面不关**:四个弹窗的遮罩层原来的 `onMouseDown: if (target === currentTarget) close()` 全部删掉。关窗只剩三条路:✕、取消、`Esc`(`Esc` 已经在 DialogShell 的 document 捕获里处理)。理由:这些弹窗里有填了一半的表单,误点空白直接丢弃代价太大;需要看被挡住的内容时,现在可以直接把窗拖开。
|
|
312
324
|
|
|
313
|
-
|
|
325
|
+
**回归**:冒烟断言 —— 遮罩层不再有 `onMouseDown`;每个弹窗的头部条唯一且 `cursor: "move"`;卡片带 `onMouseDown` 且初始 `transform: translate(0px, 0px)`。
|
|
314
326
|
|
|
315
|
-
**回归**:点击时传一个假的 `currentTarget`(`classList.add` + `querySelector`),断言**同步**加了 `dsh-mo-act-busy` 且拿到了 `dsh-mo-spin` 的动画;再加上「520ms 后必须落到非 busy 结论」与「busy 图标是 loading」两条。
|
|
316
327
|
|
|
317
328
|
## 测试
|
|
318
329
|
|
|
@@ -324,14 +335,13 @@ node test/smoke.mjs # 或 npm test
|
|
|
324
335
|
|
|
325
336
|
为什么值得留着:座位渲染时抛错会被**静默弃权**(官方 UI 悄悄回填),在浏览器里只看到「面板不见了」。这是提交前唯一能撞出这类错误的手段;改座位注册或页面结构后先跑它。
|
|
326
337
|
|
|
327
|
-
0.5.1–0.5.
|
|
338
|
+
0.5.1–0.5.12 新增的覆盖面:
|
|
328
339
|
|
|
329
340
|
- **弹窗焦点**:卡片契约(`role=dialog` + `aria-modal` + `tabIndex -1`)、`data-mo-focus` 唯一、Tab/Shift+Tab **手动步进**与两端循环、焦点在卡片外时回拉、隐藏控件被跳过、Escape 关窗;
|
|
330
341
|
- **新建供应商**:缺模型时**零写盘**并给文案、`从网关获取` 会填 id、一次 mutate 同时带 `models` 与 `apiKeyEnv`;
|
|
331
342
|
- **删除**:供应商删除必须**先确认**(取消不写盘)、确认后 `unset providers.<route>` + 清派生凭据、模型删除写回剩余列表;
|
|
332
343
|
- **测试连接**:请求必须带 `baseURL`、点击**同帧**起转(`dsh-mo-act-busy` + `dsh-mo-spin`)、busy 图标是 `loading`、≥450ms 后落到结论、结论把图标换成 `check`/`warn`;
|
|
333
|
-
-
|
|
334
|
-
|
|
344
|
+
- **图标与布局**:图标来自 primitives 桩(断言 `data-icon`);三颗图标键同一个盒子(19×17);删除键红框、测试连接键浅底;悬停规则、开关光环与基线对齐(`alignSelf: center`);悬停文案 ≤16 字、结论 ≤18 字。
|
|
335
345
|
|
|
336
346
|
### 0.4.0 验证范围
|
|
337
347
|
|
|
@@ -374,10 +384,16 @@ node test/smoke.mjs # 或 npm test
|
|
|
374
384
|
```sh
|
|
375
385
|
# npm 不允许覆盖已发布的版本号:先升版本,再提交,最后发布
|
|
376
386
|
npm version patch # 或手改 package.json 的 version
|
|
377
|
-
git add -A
|
|
387
|
+
git add -A
|
|
388
|
+
git commit -m "..."
|
|
389
|
+
git push origin main
|
|
390
|
+
git tag v0.5.12 # 与 package.json 的 version 对齐
|
|
391
|
+
git push origin v0.5.12
|
|
378
392
|
npm publish
|
|
379
393
|
```
|
|
380
394
|
|
|
395
|
+
发布前先 `npm whoami`:`401 Unauthorized` 说明 token 已失效(例如重置过 2FA),重新登录或按下一段换 token 再发。
|
|
396
|
+
|
|
381
397
|
发布需要带 **Bypass 2FA** 的 Granular Access Token(npm 2025-11 起只支持 granular token):npmjs.com → 头像 → Access Tokens → Generate New Token → Packages 选 **All Packages + Read and write (publish and stage)**、**Organizations 选 No access**、勾 **Bypass two-factor authentication**;然后 `npm config set //registry.npmjs.org/:_authToken=npm_xxx`(写进用户级 `.npmrc`,不要放进仓库)。
|
|
382
398
|
|
|
383
399
|
包是 scoped 名(`@nimoxie/dsh-model-organizer`,用户作用域),`publishConfig.access: "public"` 已写进 package.json,`npm publish` 不用再带参数。老的无作用域名 `dsh-model-organizer` 已停更,记得发一次 `npm deprecate dsh-model-organizer "已迁移到 @nimoxie/dsh-model-organizer"` 留路标。
|
package/README.md
CHANGED
|
@@ -1,94 +1,98 @@
|
|
|
1
|
-
# dsh-model-organizer
|
|
2
|
-
|
|
3
|
-
给 DSH(DeepSeek Harness)换个更好用的模型选择器:**按供应商分组、可折叠、可拖拽排序**。
|
|
4
|
-
|
|
5
|
-
## 它解决什么问题
|
|
6
|
-
|
|
7
|
-
官方那个模型下拉是一个扁平长列表——供应商只是粘性小标题,模型多的时候要滚很久,也没法按自己的习惯排序。这个插件把它改成两级:**根层先选「模型」或「推理等级」,模型层里按供应商分组**,供应商和模型的顺序都能直接拖。
|
|
8
|
-
|
|
9
|
-
## 功能
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
- 模型多于 4
|
|
15
|
-
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
>
|
|
61
|
-
>
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
1
|
+
# dsh-model-organizer
|
|
2
|
+
|
|
3
|
+
给 DSH(DeepSeek Harness)换个更好用的模型选择器:**按供应商分组、可折叠、可拖拽排序**。
|
|
4
|
+
|
|
5
|
+
## 它解决什么问题
|
|
6
|
+
|
|
7
|
+
官方那个模型下拉是一个扁平长列表——供应商只是粘性小标题,模型多的时候要滚很久,也没法按自己的习惯排序。这个插件把它改成两级:**根层先选「模型」或「推理等级」,模型层里按供应商分组**,供应商和模型的顺序都能直接拖。
|
|
8
|
+
|
|
9
|
+
## 功能
|
|
10
|
+
|
|
11
|
+
### 聊天框的模型菜单
|
|
12
|
+
- 根层与官方一致:「模型」「推理等级」两个入口(「推理等级」常驻,模型未声明等级时置灰)
|
|
13
|
+
- 「模型」按供应商分组、可折叠,顺序与设置页浮窗一致;当前模型加粗
|
|
14
|
+
- 模型多于 4 个时出现官方同款的「搜索模型」框
|
|
15
|
+
- 外观完全沿用官方菜单(同一表面、同一套图标、同一毛玻璃背景)
|
|
16
|
+
|
|
17
|
+
### 设置页「模型」:本插件接管
|
|
18
|
+
- 设置里**始终只有「模型」一行**,点开就是本插件的页;每次打开设置都默认回到本页
|
|
19
|
+
- 交还官方页:本页标题右侧的「官方模型页」= 这一次打开期间交还;官方页右下角浮窗里的「模型管理页」= 再接管回来
|
|
20
|
+
- 左栏:置顶只读的「官方(内置)」路由(点击切回官方页)+ 自建供应商(分「官方目录 / 自定义供应商」,可拖动排序,顺序只记本机)。内置目录里的供应商**没有在配置里列模型**,它们的模型由目录提供,所以在这一页是**只读展示**(可测试连接,不能编辑)
|
|
21
|
+
- 右栏:API 地址、API 格式(三种协议)改动即时保存;密钥写入凭据库(读不回明文,可清除)
|
|
22
|
+
- 模型列表一行一个:**整行可拖动排序**,**单击打开「模型详情」**——ID、显示名、上下文窗口、最大输出、输入模态、推理等级、启用/删除;保存立即写回 DSH 配置
|
|
23
|
+
- 「获取可用模型」:调官方发现接口(目录里已有的供应商不联网,自己加的网关才问 `GET {baseURL}/models`),弹窗可搜索、全选,**勾选后点「添加所选」才写回**
|
|
24
|
+
- 左栏末尾虚线框「**+ 添加模型提供商**」(同一个弹窗里切换内容):顶部「**添加方式**」两种 —— **第三方模型提供商**(从 DSH 内置目录里挑一个,只填密钥就够,地址/协议/模型都由目录提供;如果目录里的供应商都已经添加过,这格会置灰并说明)或 **自定义模型 API**(路由 ID、显示名、地址、格式、密钥,再选出一份模型清单);路由 ID 校验非空/字符集/重名,配置与密钥引用一次写入
|
|
25
|
+
- 删除供应商:详情右上角垃圾桶,先确认;删配置并清理本页派生的密钥引用(别处配置的密钥不动),官方路由没有这个按钮
|
|
26
|
+
- 每行的**绿点就是启用开关**:亮=出现在本插件菜单,暗=从本插件菜单移除;**DSH 配置原样保留**,状态存本机
|
|
27
|
+
|
|
28
|
+
### 排序、按钮与细节
|
|
29
|
+
- 设置页右下角浮窗:**直接拖动**供应商行/模型行排序;模型顺序写进 DSH 配置(同一份配置的所有设备共用),供应商顺序与浮窗位置只记在当前浏览器
|
|
30
|
+
- 页面上所有按钮(含所有弹窗)都有**悬停反馈**:普通按钮提亮、主要操作保持强调色、危险操作泛红、开关用光环(不改开/关底色,避免被误读为状态变化)
|
|
31
|
+
- 所有弹窗(选模型、添加提供商、模型详情、删除确认)都能**按住标题栏拖动**;**点弹窗外的空白不会关闭**,关闭方式是右上角 ✕、底部「取消」或 `Esc`(避免误点把填了一半的表单扔掉)
|
|
32
|
+
- 模型行两颗**带边框的图标键**:链接图标 = **测试连接**(问网关该模型在不在它的列表里,结论把图标换成 ✓/⚠/✗ 并变色,悬停看原因;点击同帧开始转圈)、垃圾桶 = **删除**(图标常红);两者同一尺寸,转圈与尺寸都不随结论变化
|
|
33
|
+
- 悬停文案只说明「这个按钮做什么 / 现在什么状态」,不写实现细节
|
|
34
|
+
|
|
35
|
+
## 安装
|
|
36
|
+
|
|
37
|
+
装到哪个环境,就用对应的方法:
|
|
38
|
+
|
|
39
|
+
**浏览器版(`web`,CLI 管理)**
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
dsh plugin --profile web add @nimoxie/dsh-model-organizer
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
装完重启服务(关掉再 `dsh web`)。`--profile` 后面是目标 profile 名:浏览器版是 `web`,你自己另外创建的 profile 填对应的名字。
|
|
46
|
+
|
|
47
|
+
**桌面客户端**
|
|
48
|
+
|
|
49
|
+
桌面 profile 由客户端独占管理,**不要用上面的 CLI 命令**。请在客户端的「插件管理」页添加本包(包名 `@nimoxie/dsh-model-organizer`)。
|
|
50
|
+
|
|
51
|
+
卸载(CLI 环境):
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
dsh plugin --profile <profile> remove @nimoxie/dsh-model-organizer
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
开发用:从 GitHub 或本地目录装
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
dsh plugin --profile <profile> add github:NimoXie15/dsh-model-organizer
|
|
61
|
+
dsh plugin --profile <profile> add link:/absolute/path/to/dsh-model-organizer
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
> **兼容性**:已在 DSH `0.1.7`、`0.2.0-rc.1`(web)、`0.2.0-rc.2`、`0.2.1-rc.2`(桌面端)上核验,`0.1.5` 起都可用。
|
|
65
|
+
>
|
|
66
|
+
> 升级插件后如果界面没变化,按 **Ctrl+Shift+R** 强刷一次(浏览器可能还在用缓存的旧版本)。
|
|
67
|
+
|
|
68
|
+
## 想让「推理等级」可用,需要模型自己声明
|
|
69
|
+
|
|
70
|
+
「推理等级」入口常驻菜单根层;只有模型声明了等级,它才可点、才会在按钮上显示当前等级。
|
|
71
|
+
|
|
72
|
+
0.5.0 起在「模型详情」弹窗里直接设置:推理等级选「继承」(沿用该模型已有的声明)、「不支持」,或「自定义」——自定义时逐行添加等级,**键 = 菜单里显示的等级,值 = 选中时发给模型的 API 参数**(只有 `off` 可以留空)。
|
|
73
|
+
|
|
74
|
+
也可以直接在 profile 配置里给这个模型声明(0.2.x 在 `cordis.patch.yml`,0.1.x 在 `settings.yaml`):
|
|
75
|
+
|
|
76
|
+
```yaml
|
|
77
|
+
# 该供应商的 models[] 条目(0.2.x 在 profile 的 cordis.patch.yml;0.1.x 在 settings.yaml)
|
|
78
|
+
- id: <你的模型 ID>
|
|
79
|
+
reasoningEfforts: # 键=菜单里显示的等级,值=选中时发给模型的 API 参数;只有 off 可以留空
|
|
80
|
+
off:
|
|
81
|
+
high: high
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## 已知限制
|
|
85
|
+
|
|
82
86
|
- 「测试连接」验证的是**地址 / 密钥 / 网关可达性,以及这个模型 ID 是否在网关返回的列表里** —— 它不会真的发一条消息给模型(DSH 的远程接口没有这个能力,客户端也读不回密钥)。所以「✓」只代表这条路由和这个 ID 是通的,不等于这个模型一定能补全
|
|
83
|
-
- 设置页右下角的排序浮窗**只列你自己添加的供应商**(写进配置的那些),官方路由**根本不进这个浮窗**;想在左栏看到官方路由,看顶部只读的「官方(内置)」分组
|
|
84
|
-
- 浮窗里排出的供应商顺序会**镜像**到官方模型页的卡片上;官方卡片(DeepSeek 官方等)被刻意保持在最前面
|
|
85
|
-
- 供应商顺序、浮窗位置只存在当前浏览器;模型顺序存在 DSH 的用户配置里,同一份 DSH
|
|
86
|
-
- 不修改 DSH 本体,只通过官方提供的扩展点接入
|
|
87
|
-
|
|
88
|
-
## 许可
|
|
89
|
-
|
|
90
|
-
MIT
|
|
91
|
-
|
|
92
|
-
---
|
|
93
|
-
|
|
94
|
-
想自己改这个插件、或者想了解它内部怎么实现的,见 [DEVELOPMENT.md](./DEVELOPMENT.md)。
|
|
87
|
+
- 设置页右下角的排序浮窗**只列你自己添加的供应商**(写进配置的那些),官方路由**根本不进这个浮窗**;想在左栏看到官方路由,看顶部只读的「官方(内置)」分组
|
|
88
|
+
- 浮窗里排出的供应商顺序会**镜像**到官方模型页的卡片上;官方卡片(DeepSeek 官方等)被刻意保持在最前面
|
|
89
|
+
- 供应商顺序、浮窗位置只存在当前浏览器;模型顺序存在 DSH 的用户配置里,同一份 DSH 配置的所有设备共用——两台机器各装一份 DSH 则互不相通
|
|
90
|
+
- 不修改 DSH 本体,只通过官方提供的扩展点接入
|
|
91
|
+
|
|
92
|
+
## 许可
|
|
93
|
+
|
|
94
|
+
MIT
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
想自己改这个插件、或者想了解它内部怎么实现的,见 [DEVELOPMENT.md](./DEVELOPMENT.md)。
|