dsh-model-groups 0.3.8 → 0.3.9
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/CHANGELOG.md +56 -0
- package/README.md +11 -1
- package/docs/maintenance.md +20 -0
- package/lib/client.js +25 -3
- package/native/pi-ai.patch.json +385 -4
- package/native/settings-models.patch.json +533 -1
- package/package.json +2 -1
- package/scripts/settings-reorder-patch.mjs +49 -0
- package/scripts/settings-reorder.mjs +170 -0
- package/src/hierarchy.js +23 -1
- package/src/settings-page.js +1 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# 更新记录
|
|
2
|
+
|
|
3
|
+
完整版本列表与安装包:[GitHub Releases](https://github.com/betsunninda9-lgtm/dsh-model-groups/releases)。
|
|
4
|
+
|
|
5
|
+
## 0.3.9 · 2026-11-14
|
|
6
|
+
|
|
7
|
+
### 新增
|
|
8
|
+
|
|
9
|
+
- 网页设置页支持在同一供应商内调整密钥分组顺序、在组内调整模型顺序。手机长按三横线手柄约 400 毫秒后拖动,电脑也可以操作。
|
|
10
|
+
- 拖动时显示蓝色插入线和“松手后放在这里”,列表边缘支持自动滚动;手柄也支持上下方向键。
|
|
11
|
+
- 排序沿用原表单的保存按钮;未保存的输入、展开状态和密钥组身份会保留。保存后,聊天模型菜单使用相同顺序。
|
|
12
|
+
- 插件设置页分别显示前端和后端版本,方便确认更新是否已加载。
|
|
13
|
+
|
|
14
|
+
### 修复
|
|
15
|
+
|
|
16
|
+
- 修复测试版中密钥分组排序保存后,退出设置又恢复原顺序的问题。现在保存明确的顺序值,确保运行中的配置、重新打开的设置页和模型选择列表一致。
|
|
17
|
+
- 保留旧版补丁的升级与还原配方,支持从 0.3.8 和本次测试版直接更新。
|
|
18
|
+
|
|
19
|
+
### 更新步骤
|
|
20
|
+
|
|
21
|
+
1. 在原 profile 更新插件,无需先卸载:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
dsh plugin --profile web add dsh-model-groups@0.3.9 --registry=https://registry.npmjs.org/
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
2. 重启原 DSH 引擎并刷新网页,确认插件前端、后端均为 `0.3.9`。
|
|
28
|
+
3. 在 **设置 → 模型分组** 执行 **更新多密钥补丁**。本次同时更新设置页和后端补丁。
|
|
29
|
+
4. 再次重启引擎并刷新网页。拖动排序后,点击原表单保存按钮。
|
|
30
|
+
|
|
31
|
+
仍支持 DSH `0.2.2-rc.1`、Node.js 22 或更新版本。本次修改的是网页界面。
|
|
32
|
+
|
|
33
|
+
### 验证
|
|
34
|
+
|
|
35
|
+
- beta.2 完整隔离环境回归:298 项,287 通过,0 失败,11 个历史或环境条件跳过。
|
|
36
|
+
- 三个已知后端构建验证真实配置更新、YAML 保存重读及模型菜单排序;18 个升级与还原场景通过。
|
|
37
|
+
- 用户已实机验收 beta.2 的排序修复;正式版沿用该实现。
|
|
38
|
+
|
|
39
|
+
## 0.3.8 · 2026-11-12
|
|
40
|
+
|
|
41
|
+
- 修复 Anthropic 协议在结尾带 `/v1` 的中转地址上重复拼接 `/v1/v1/messages` 的问题;模型探测和实际请求使用相同规则。
|
|
42
|
+
- 安装后需更新多密钥补丁,重启 DSH 引擎并刷新网页。
|
|
43
|
+
- [完整发布说明](https://github.com/betsunninda9-lgtm/dsh-model-groups/releases/tag/v0.3.8)。
|
|
44
|
+
|
|
45
|
+
## 0.3.7 · 2026-11-11
|
|
46
|
+
|
|
47
|
+
- 补充图文配置与安全关闭、卸载指南,简化设置页的重启和刷新提示。
|
|
48
|
+
- [完整发布说明](https://github.com/betsunninda9-lgtm/dsh-model-groups/releases/tag/v0.3.7)。
|
|
49
|
+
|
|
50
|
+
## 0.3.6 · 2026-11-11
|
|
51
|
+
|
|
52
|
+
- 多密钥配置支持分别设置 API 协议,继续支持跟随供应商。
|
|
53
|
+
- 完善旧版本直接升级、预检诊断和前后端版本一致性保护。
|
|
54
|
+
- [完整发布说明](https://github.com/betsunninda9-lgtm/dsh-model-groups/releases/tag/v0.3.6)。
|
|
55
|
+
|
|
56
|
+
更早的正式版与历史预览版请查看 [全部发布记录](https://github.com/betsunninda9-lgtm/dsh-model-groups/releases)。
|
package/README.md
CHANGED
|
@@ -4,6 +4,16 @@
|
|
|
4
4
|
|
|
5
5
|
支持 **DSH `0.2.2-rc.1`**、**Node.js 22 或更新版本**。本插件适用于 DSH 网页端,不替换原生 App 的模型菜单。
|
|
6
6
|
|
|
7
|
+
## 本次更新 · 0.3.9
|
|
8
|
+
|
|
9
|
+
- **手机也能排序**:在设置页长按三横线手柄约 400 毫秒,即可调整密钥分组及组内模型顺序;蓝色插入线会提示落点,靠近边缘可自动滚动。
|
|
10
|
+
- **保存后顺序保留**:使用原表单保存按钮,退出再进入设置、打开聊天模型菜单都沿用已保存顺序。未保存的输入和展开状态会保留。
|
|
11
|
+
- **修复测试版排序回退**,并分别显示插件前端、后端版本,方便核对更新。
|
|
12
|
+
|
|
13
|
+
升级到本版后,需要在 **设置 → 模型分组** 更新多密钥补丁,再重启 DSH 引擎并刷新网页。
|
|
14
|
+
|
|
15
|
+
查看 [每次更新的内容](CHANGELOG.md) · [GitHub 发布记录与安装包](https://github.com/betsunninda9-lgtm/dsh-model-groups/releases)。
|
|
16
|
+
|
|
7
17
|
## 安装
|
|
8
18
|
|
|
9
19
|
在运行 DSH 的环境里执行(如果你使用的 DSH 配置不是 `web`,把命令中的 `web` 换成你的配置名):
|
|
@@ -12,7 +22,7 @@
|
|
|
12
22
|
dsh plugin --profile web add dsh-model-groups@latest --registry=https://registry.npmjs.org/
|
|
13
23
|
```
|
|
14
24
|
|
|
15
|
-
如果 npm 尚未同步到你需要的版本,可以把 `dsh-model-groups@latest` 换成 [GitHub 0.3.
|
|
25
|
+
如果 npm 尚未同步到你需要的版本,可以把 `dsh-model-groups@latest` 换成 [GitHub 0.3.9 正式版安装包](https://github.com/betsunninda9-lgtm/dsh-model-groups/releases/download/v0.3.9/dsh-model-groups-0.3.9.tgz) 的下载地址。安装后,**重启正在使用的 DSH 引擎,再刷新网页**。只想给现有模型分组?装好就可以用,不需要启用多密钥。
|
|
16
26
|
|
|
17
27
|
## 在对话中选模型
|
|
18
28
|
|
package/docs/maintenance.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
日常安装和使用见 [README](../README.md)。
|
|
4
4
|
|
|
5
|
+
## GitHub 自动检查
|
|
6
|
+
|
|
7
|
+
向 `main` 提交代码或创建以 `main` 为目标的修改申请后,GitHub 的 **Actions → Automatic checks** 会自动检查:
|
|
8
|
+
|
|
9
|
+
- 已提交的网页产物是否与源码一致(先检查,再打包,避免打包自动修复后漏报)。
|
|
10
|
+
- 仓库现有的自动测试是否通过;完整获取 Git 历史,以运行历史补丁清单验证。
|
|
11
|
+
- 能否正常预览生成安装包,以及打包后网页产物是否仍与提交一致。
|
|
12
|
+
|
|
13
|
+
使用 Node.js 22 和只读仓库权限,不发布 npm、不安装 DSH、不应用补丁。当前项目没有依赖和锁文件,因此无需安装依赖,也不使用 `npm ci`。以后新增依赖时,须提交锁文件并同步增加依赖安装步骤。
|
|
14
|
+
|
|
15
|
+
检查详情中的红色步骤表示失败,可展开查看原因;绿色表示本次配置的检查通过。没有真实 DSH 程序文件的集成测试会明确跳过,不能视为已经通过。宿主补丁的真实升级、还原和手机窄屏触摸仍按下文单独验证。首次检查流程合并后,也可在 Actions 页点击 **Run workflow** 手动运行。
|
|
16
|
+
|
|
5
17
|
## 本地验证
|
|
6
18
|
|
|
7
19
|
需要 Node.js 22 或更新版本。修改网页端源码后,重新生成并提交客户端产物:
|
|
@@ -28,6 +40,14 @@ node scripts/settings-models-collapse.mjs --source /path/to/pristine-client.js
|
|
|
28
40
|
node scripts/settings-models-collapse.mjs --source /path/to/pristine-client.js --check
|
|
29
41
|
```
|
|
30
42
|
|
|
43
|
+
## 触摸排序的验证
|
|
44
|
+
|
|
45
|
+
设置页密钥分组和模型行共用 `scripts/settings-reorder.mjs` 的指针手势,补丁生成器将其嵌入宿主设置页。长按约 400 毫秒后,以蓝色插入线和「松手后放在这里」提示落点;松手只更新草稿,仍由原表单提交。分组移动时写入每组的 `sortOrder` 数值,模型沿用数组顺序。不能只依赖对象键顺序:宿主配置加载器的相等比较忽略对象键的排列,单纯重排键会被视为未变化。设置和聊天菜单按同一份已保存配置排列,不保存另一份本地排序。
|
|
46
|
+
|
|
47
|
+
自动测试覆盖长按与防误触、松手坐标、同列表边界、边缘滚动、取消及卸载清理、编辑行身份与输入草稿、只排序的改动提示、失败重试以及选择菜单顺序。发布前仍需在真实手机浏览器核对:短按手柄不移动,手柄以外可滚动;长按拖动折叠/展开分组和模型;长列表边缘滚动;取消后原顺序不变;点击现有保存按钮后,聊天菜单及刷新后的设置顺序一致。模拟事件测试不代表实机触摸已验证。
|
|
48
|
+
|
|
49
|
+
旧 0.3.8 与 0.3.9-beta.1 的设置补丁,以及 0.3.9-beta.1 的后端补丁,保存在测试夹具中并作为可升级、可还原的历史变体保留。完整程序生成还会验证正向和反向变换均逐字匹配,防止替换片段互相干扰。
|
|
50
|
+
|
|
31
51
|
## 诊断与恢复
|
|
32
52
|
|
|
33
53
|
只读诊断(请替换实际安装路径):
|
package/lib/client.js
CHANGED
|
@@ -87,6 +87,17 @@ function decodeKeyModelId(model) {
|
|
|
87
87
|
} catch { return undefined; }
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
/** Explicit ranks survive host equality checks, schema parsing, and reloads.
|
|
91
|
+
* Unranked legacy/new entries retain their existing relative order at the end.
|
|
92
|
+
*/
|
|
93
|
+
function orderedKeyEntries(entries) {
|
|
94
|
+
const rank = entry => Number.isSafeInteger(entry?.sortOrder) && entry.sortOrder >= 0 ? entry.sortOrder : Infinity;
|
|
95
|
+
return Object.entries(entries ?? {}).sort(([, left], [, right]) => {
|
|
96
|
+
const a = rank(left), b = rank(right);
|
|
97
|
+
return a === b ? 0 : a < b ? -1 : 1;
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
90
101
|
function keyEntryGroupKey(provider, entryId) {
|
|
91
102
|
return `@key/${encodeURIComponent(provider)}/${encodeURIComponent(entryId)}`;
|
|
92
103
|
}
|
|
@@ -119,7 +130,7 @@ function buildModelHierarchy(groups, providers, preferences, current = null) {
|
|
|
119
130
|
}
|
|
120
131
|
const byEntry = new Map();
|
|
121
132
|
const configured = profile?.keyEntries && typeof profile.keyEntries === 'object' ? profile.keyEntries : {};
|
|
122
|
-
for (const entryId of
|
|
133
|
+
for (const [entryId] of orderedKeyEntries(configured)) {
|
|
123
134
|
if (KEY_ENTRY_ID.test(entryId) && !forbiddenEntry(entryId)) byEntry.set(entryId, []);
|
|
124
135
|
}
|
|
125
136
|
for (const model of aliases) {
|
|
@@ -133,6 +144,17 @@ function buildModelHierarchy(groups, providers, preferences, current = null) {
|
|
|
133
144
|
const children = [...byEntry].filter(([, models]) => models.length > 0).map(([entryId, models]) => {
|
|
134
145
|
const key = keyEntryGroupKey(group.id, entryId);
|
|
135
146
|
const active = root.current && selectedEntry === entryId;
|
|
147
|
+
// Catalog refreshes may use a different order from the saved editor rows.
|
|
148
|
+
// Empty lists inherit catalog order; unconfigured models stay stable last.
|
|
149
|
+
const configuredModels = Object.hasOwn(configured, entryId) ? configured[entryId]?.models : undefined;
|
|
150
|
+
if (Array.isArray(configuredModels) && configuredModels.length > 0) {
|
|
151
|
+
const order = new Map();
|
|
152
|
+
for (const model of configuredModels) {
|
|
153
|
+
if (typeof model?.id === 'string' && !order.has(model.id)) order.set(model.id, order.size);
|
|
154
|
+
}
|
|
155
|
+
const rank = model => order.get(decodeKeyModelId(model.id).modelId) ?? order.size;
|
|
156
|
+
models.sort((left, right) => rank(left) - rank(right));
|
|
157
|
+
}
|
|
136
158
|
return { ...group, key, entryId, name: keyEntryName(providers, group.id, entryId), models, current: active,
|
|
137
159
|
expanded: Object.hasOwn(preferences, key) ? preferences[key] : active };
|
|
138
160
|
});
|
|
@@ -1270,7 +1292,7 @@ function installSettingsPageStyles() {
|
|
|
1270
1292
|
document.head.appendChild(style);
|
|
1271
1293
|
}
|
|
1272
1294
|
|
|
1273
|
-
const MODEL_GROUPS_CLIENT_VERSION = "0.3.
|
|
1295
|
+
const MODEL_GROUPS_CLIENT_VERSION = "0.3.9";
|
|
1274
1296
|
// Public program metadata only. Never include paths, source text, configuration,
|
|
1275
1297
|
// credentials, environment variables, or arbitrary exception messages.
|
|
1276
1298
|
const PATCH_IDS = ['pi-ai.patch.json', 'settings-models.patch.json', 'llm-typert.host.patch.json', 'llm-typert.remote-client.patch.json', 'api-remotes.patch.json'];
|
|
@@ -1510,7 +1532,7 @@ function ModelGroupsSettingsPage() {
|
|
|
1510
1532
|
return h('section', { className: 'mg-maintenance', 'aria-label': '模型分组设置', 'aria-busy': state.busy },
|
|
1511
1533
|
h('h2', null, '多密钥管理'),
|
|
1512
1534
|
h('p', null, '在这里启用、更新或关闭多密钥。如果只想按供应商浏览模型,无需启用多密钥。'),
|
|
1513
|
-
|
|
1535
|
+
h('p', { className: 'mg-maintenance-help' }, `网页插件 ${MODEL_GROUPS_CLIENT_VERSION} · 后端插件 ${snapshot?.pluginVersion ?? '检查中'} · DSH ${snapshot?.compatibility?.hostVersion ?? '版本未确认'}`),
|
|
1514
1536
|
h('section', { className: 'mg-maintenance-card', 'aria-label': '当前状态' },
|
|
1515
1537
|
h('div', { className: 'mg-maintenance-row' }, h('h3', null, '当前状态'), h('button', { type: 'button', disabled: state.busy, onClick: refresh }, state.busy ? '处理中…' : '刷新检查')),
|
|
1516
1538
|
h('p', { role: 'status', 'aria-live': 'polite', className: 'mg-maintenance-help' }, state.busy ? '正在检查或执行维护,请勿关闭或重复操作。' : snapshot ? statusLabel : '尚无可验证状态,请刷新检查。'),
|