dsh-model-organizer 0.3.2 → 0.3.4
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/DEVELOPING.md +40 -85
- package/README.md +7 -5
- package/lib/client.js +121 -74
- package/package.json +1 -1
package/DEVELOPING.md
CHANGED
|
@@ -1,108 +1,63 @@
|
|
|
1
1
|
# 开发笔记
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
面向改这个插件的人。用户文档在 [README.md](./README.md)。
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 升级 DSH 后座位凭空消失?先查服务名
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
`profile bundle "..." declares no dsh.bundle in its package.json`。
|
|
7
|
+
cordis 的 `ctx.inject([...])` **只要有一个服务永不出现,回调就永远不执行** —— 不报错、不打日志。所以服务改名会让整个座位静默消失。
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
少一个 → 渲染时抛 `cannot get property "remote.session" without inject` → **条目被静默弃权(abdicate)并回退到内置 UI**,界面无任何报错。
|
|
12
|
-
调用 `remote.settings.mutate` 还必须显式声明 **`remote.settings`**。
|
|
9
|
+
已发生一次:0.1.7 把客户端设置镜像服务 `settingsScope` 改名为 `configForms`(提供者仍是 `@deepseek-ai/dsh-client-ui-settings`,`describe()` 返回同一个 mirror)。设置页浮窗因此消失,而输入框菜单照常(它注入的 `slots/modelDirectories/sessions` 没变)。
|
|
13
10
|
|
|
14
|
-
|
|
11
|
+
`apply()` 里同时注入两个名字,一次性标志保证只挂载一次:
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
6. **边框必须用 longhand**(`borderWidth/borderStyle/borderColor`)。用 shorthand `border` 再叠加 `borderColor` 时,React 会把 shorthand 展开成 longhand;回退时移除 `borderColor`,`border-color` 就落回 **currentColor** —— 表现为「拖拽结束后高亮边框一直不消失」。
|
|
21
|
-
|
|
22
|
-
7. **拖拽会把源行留在焦点上**。行加 `tabIndex:-1`、drop/dragend 时 `blur()`;并且**不要给行画 `:focus-visible` 背景**(官方 `option` 类自带一条,会变成「拖完还留一块灰底」)。
|
|
23
|
-
|
|
24
|
-
8. **原生拖拽会让文字变成可拖对象**(浏览器弹「松开鼠标即可搜索」)。解决:`-webkit-user-drag:none` 加在**行的内容**上,不要加在行本身(加在行上会让行也拖不动)。这条是从侧边栏会话行的实现里学来的。
|
|
25
|
-
|
|
26
|
-
9. **primitive 图标不转发 inline `style`**:需要旋转/变色请传 `className`(本插件为此注入一张极小的自有样式表)。
|
|
27
|
-
|
|
28
|
-
10. **数组顺序可控,record 键顺序不可控**:`providers` 是 record,宿主要规范化键顺序,整体 `set` 与 `unset`+`set` 都实测无效 —— 所以供应商顺序只能存本机偏好。
|
|
29
|
-
|
|
30
|
-
11. **面板里用到的每个局部变量都要真的声明**:曾在 `ProviderModelOrder` 里用了 `C`(官方类名映射)却没定义 → `ReferenceError` → 条目再次被静默弃权,表现为「面板整个不见了」。
|
|
31
|
-
|
|
32
|
-
12. 浏览器半只需 `react` / `react-dom` / `@deepseek-ai/dsh-client-ui-primitives`(都在 shell 的 PLATFORM_MODULES 种子里),无需 `dsh.client.external`。
|
|
33
|
-
|
|
34
|
-
## 兼容性与鲁棒性(已核查)
|
|
35
|
-
|
|
36
|
-
### 已发现并修复的真实冲突
|
|
37
|
-
|
|
38
|
-
**`settings.models.provider-card` 被 `@linxin666/dsh-client-ui-model-capabilities` 占用。**
|
|
39
|
-
该座位是 **keyed**,key 由官方页面固定派发为供应商的 settings namespace(`llm-pi-ai`);而该插件已经用同一个 key 注册了「模型能力」面板。SlotCore 对 keyed 座位**一个 key 只保留一个占用者**,且同 key 同优先级会直接抛错 —— 它用 `try/catch` 吞掉异常,所以**本插件早期版本一直在静默压制它的「模型能力」面板**(实测:让出该座位后,每个供应商卡片立刻出现「模型能力」)。
|
|
40
|
-
|
|
41
|
-
**修复**:本插件不再占用该座位,模型排序改由自己拥有的 `settings.models.footer`(list 座位,`id: model-organizer-provider-order`)承载 —— list 座位按 id 去重,天然可与该插件的 `ui-model-capabilities` 共存。
|
|
42
|
-
|
|
43
|
-
### 其它已做的加固
|
|
44
|
-
|
|
45
|
-
| 风险 | 处理 |
|
|
46
|
-
| :-- | :-- |
|
|
47
|
-
| 官方 CSS module 哈希变化 / 被别的模块误命中 | 运行时解析前缀,并用**只有该模块才有的第二个类**(`_optionCopy`)校验;缓存与样式表数量绑定;全部失败则退回内置回退样式 |
|
|
48
|
-
| 座位契约变更(prop 改名等) | **不拦截**:让渲染抛错 → SlotCore 弃权该条目 → 官方内置组件自动回填 |
|
|
49
|
-
| 座位被重命名 / 移除 | `slots.inject` 永不触发 → 对应面板静默消失,其它功能不受影响 |
|
|
50
|
-
| 单个座位注册失败 | 每个座位注册独立 `guard`,失败只记一条 warn,**不会拖垮整个插件** |
|
|
51
|
-
| 写入被拒绝 / ops 契约变化 | `mutate` 的同步抛错与 promise 拒绝都有捕获,并在表头显示失败原因 |
|
|
52
|
-
| 本机偏好 | `localStorage` key 带命名空间,读写都有 try/catch |
|
|
53
|
-
| 注入的样式表 | `<style id="dsh-model-organizer-style">`,类名统一 `dsh-mo-*` 前缀,注入幂等 |
|
|
54
|
-
| 服务端半边 | 只有空 `apply()`,不注册任何宿主能力 |
|
|
55
|
-
|
|
56
|
-
### 仍然存在的风险(无法在插件侧消除)
|
|
13
|
+
```js
|
|
14
|
+
ctx.inject(["slots", "configForms", "remote", "remote.settings"], (s) => mountOrderPanel(s, s.configForms.describe()));
|
|
15
|
+
ctx.inject(["slots", "settingsScope", "remote", "remote.settings"], (s) => mountOrderPanel(s, s.settingsScope.describe()));
|
|
16
|
+
```
|
|
57
17
|
|
|
58
|
-
|
|
59
|
-
- **官方模型页若改变 keyed 派发约定**,依赖该座位的插件会失效 —— 本插件已不依赖它。
|
|
60
|
-
- **官方 CSS module 若改名**(例如删掉 `optionCopy`),校验会失败并退回回退样式:功能可用、外观降级。
|
|
61
|
-
- **卡片排序**是本插件唯一会改官方 DOM 的地方(给官方列表设 `display:flex` + 给 `li` 设 CSS `order`,不移动节点)。若 DSH 改那棵 DOM,该功能会静默降级,其余功能不受影响。
|
|
18
|
+
**规律**:升级后座位不见了,第一件事是去新版 `dsh-client-ui-*` 包里 grep 自己注入的每个服务名。
|
|
62
19
|
|
|
63
|
-
##
|
|
20
|
+
## 座位的失败模式(决定了代码结构)
|
|
64
21
|
|
|
65
|
-
|
|
22
|
+
- **条目渲染时抛错 → 被静默弃权,官方内置 UI 自动回填**,界面上没有任何提示。
|
|
23
|
+
- **座位注册本身失败 → 该座位静默消失**,其它功能不受影响。
|
|
24
|
+
- 所以:每个座位注册各套一层 `guard`(失败只记 warn,不拖垮插件);组件外套 `PanelBoundary`(把抛错变成页面上的一行红字,而不是"凭空消失")。
|
|
25
|
+
- **顶层 `inject` 必须列全座位 standardProps 会读的每个服务**(本插件:`locale, slots, sessions, remote, remote.session, remote.settings`)。少一个 → 渲染抛 `cannot get property "..." without inject` → 弃权。用 `remote.settings.mutate` 必须显式声明 `remote.settings`。
|
|
26
|
+
- `single` 座位同优先级注册会**直接抛错**;运行时会为非 chain 座位自动分配优先级,后注册者胜出。
|
|
27
|
+
- 未声明的局部变量(`ReferenceError`)同样会导致弃权,表现就是"整个面板不见了"。
|
|
66
28
|
|
|
67
|
-
|
|
29
|
+
## 与官方模块的耦合
|
|
68
30
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
dsh
|
|
31
|
+
- **官方 CSS-module 类名靠运行时解析**:锚定只有该模块才有的类名,并用第二个类名(如 `_optionCopy`)校验;解析失败退回内置回退样式(功能可用、外观降级)。
|
|
32
|
+
- **0.1.7-rc.1 的构建丢了官方菜单规则的两条属性**:线上生效的 `_7KE1Ra_menu` 里既没有 `background` 也没有 `border-radius`(源码里是 `var(--dsw-specific-menu)` 和 `16px`),所以**官方菜单本身表现为全透明+直角**。本插件不依赖那条规则:运行时从 `document.body` 解析 `--dsw-specific-menu` 内联补上背景,并补 `var(--dsw-radius-lg)` 圆角。升级后可重新检查这条规则是否已修复。
|
|
33
|
+
- **浮层必须 `ReactDOM.createPortal` 到 `document.body`**:座位在设置页很深的子树里,`position: fixed` 会被困在那个子树的堆叠上下文,被别的插件的悬浮组件盖住 —— 提高 z-index 也没用。
|
|
34
|
+
- **不要占用 `settings.models.provider-card`**:它是 keyed 座位,key 是供应商的 settings namespace,已被 `@linxin666/dsh-client-ui-model-capabilities` 占用;keyed 座位一个 key 只留一个占用者,同 key 同优先级注册会抛错(而且对方用 try/catch 吞掉,等于静默压制它)。本插件只用自己的 `settings.models.footer`(list 座位,按 id 去重)。
|
|
73
35
|
|
|
74
|
-
|
|
75
|
-
setx EGO_LINUX_HEADLESS 1
|
|
76
|
-
```
|
|
36
|
+
## 开发环境
|
|
77
37
|
|
|
78
|
-
-
|
|
79
|
-
|
|
80
|
-
-
|
|
81
|
-
-
|
|
38
|
+
- **`file:` 依赖是打包快照,`link:` 才是符号链接。** profile 里写 `"file:../../../dsh-model-organizer"` 时 pnpm 把目录打包**复制**进 store,DSH 加载的是 `profiles/<p>/node_modules/.../lib/client.js` 那份副本 —— 改仓库**不生效**。
|
|
39
|
+
排查:grep 那份副本的 `BUILD` 常量,和仓库对比。
|
|
40
|
+
修法:`dsh plugin --profile web remove dsh-model-organizer && dsh plugin --profile web add link:./dsh-model-organizer`
|
|
41
|
+
- **改完要 Ctrl+Shift+R**:客户端 bundle 以 `Cache-Control: immutable, max-age=31536000` 下发,且 URL 不随代码变化,普通刷新一直用缓存。
|
|
42
|
+
- **浏览器半只需 `react` / `react-dom` / `@deepseek-ai/dsh-client-ui-primitives`**(都在 shell 的 PLATFORM_MODULES 种子里),不需要 `dsh.client.external`。
|
|
43
|
+
- profile bundle 必须声明 `dsh.bundle.patch`(指向 `cordis.patch.yml`),否则 dsh 启动即报 `declares no dsh.bundle`。
|
|
82
44
|
|
|
83
|
-
|
|
45
|
+
## 写 UI 时踩过的坑
|
|
84
46
|
|
|
85
|
-
|
|
47
|
+
- **边框必须用 longhand**(`borderWidth/borderStyle/borderColor`)。用 shorthand `border` 再叠加 `borderColor` 时 React 会展开 shorthand;回退时移除 `borderColor`,`border-color` 落回 `currentColor` → "拖拽结束后高亮边框一直不消失"。
|
|
48
|
+
- **拖拽会把源行留在焦点上**:行加 `tabIndex:-1`,drop/dragend 时 `blur()`;并且不要给行画 `:focus-visible` 背景(官方 `option` 类自带一条 → "拖完还留一块灰底")。
|
|
49
|
+
- **原生拖拽会让文字变成可拖对象**(浏览器弹「松开鼠标即可搜索」):`-webkit-user-drag:none` 加在**行的内容**上,不要加在行本身(加在行上会让行也拖不动)。
|
|
50
|
+
- **drop 会在行与容器上各触发一次**(冒泡),需加锁,否则重复写入。
|
|
51
|
+
- **primitive 图标不转发 inline `style`**:需要旋转/变色请传 `className`。
|
|
52
|
+
- **`providers` 是 record,键顺序由宿主规范化**:整体 `set` 与 `unset`+`set` 都实测无效,所以供应商顺序只能存 `localStorage`(模型顺序是数组,可以写进文档)。
|
|
86
53
|
|
|
87
|
-
##
|
|
54
|
+
## 发版
|
|
88
55
|
|
|
89
56
|
```sh
|
|
90
|
-
#
|
|
91
|
-
#
|
|
92
|
-
# 2. 提交并推送
|
|
57
|
+
# npm 不允许覆盖已发布的版本号:先升版本,再提交,最后发布
|
|
58
|
+
npm version patch # 或手改 package.json 的 version
|
|
93
59
|
git add -A && git commit -m "..." && git push
|
|
94
|
-
# 3. 发布
|
|
95
60
|
npm publish
|
|
96
61
|
```
|
|
97
62
|
|
|
98
|
-
发布需要带 **Bypass 2FA** 的 Granular Access Token(npm 2025-11 起只支持 granular token
|
|
99
|
-
npmjs.com → 头像 → Access Tokens → Generate New Token → Packages 选 **All Packages + Read and write (publish and stage)**、**Organizations 选 No access**、勾 **Bypass two-factor authentication**;然后
|
|
100
|
-
`npm config set //registry.npmjs.org/:_authToken=npm_xxx`(写进用户级 `.npmrc`,不要放进仓库)。
|
|
101
|
-
|
|
102
|
-
## 附:关于「(modlens vision)」供应商分组
|
|
103
|
-
|
|
104
|
-
这些是 **`@liustack/modlens` 自动生成的「视觉变体」路由**,不是配置重复项:
|
|
105
|
-
|
|
106
|
-
- 机制:modlens 扫描已注册 provider,找出上游元数据声明为 `text` 且不含 image 的模型(默认按 `deepseek` / `glm` / `mimo` 前缀族匹配),为它们注册一条**声明支持图片输入**的伴随路由,界面名加 `(modlens vision)`。
|
|
107
|
-
- 用途:选中该分组下的模型后,粘贴/拖拽图片会走 DSH 原生附件流程,在调用(纯文本)模型前把图片转成证据文本。不选这个分组时,纯文本模型无法接收图片。
|
|
108
|
-
- 调整:编辑 `$DSH_HOME/profiles/<profile>/cordis.patch.yml` 里的 `modlens` 配置(`families` / `discover`),整条关掉用 `visionProvider: false`。视觉引擎配置在 `~/.modlens/config.json`。
|
|
63
|
+
发布需要带 **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`,不要放进仓库)。
|
package/README.md
CHANGED
|
@@ -36,7 +36,9 @@ dsh plugin --profile web add github:NimoXie15/dsh-model-organizer
|
|
|
36
36
|
dsh plugin --profile web add link:/absolute/path/to/dsh-model-organizer
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
>
|
|
39
|
+
> **兼容性**:已在 DSH `0.1.7` 上实测,`0.1.5` 起都可用(0.1.7 调整了一处内部接口,本插件已做兼容处理)。
|
|
40
|
+
>
|
|
41
|
+
> 升级插件后如果界面没变化,按 **Ctrl+Shift+R** 强刷一次(浏览器可能还在用缓存的旧版本)。
|
|
40
42
|
|
|
41
43
|
## 截图
|
|
42
44
|
|
|
@@ -56,9 +58,9 @@ dsh plugin --profile web add link:/absolute/path/to/dsh-model-organizer
|
|
|
56
58
|
|
|
57
59
|
## 已知限制
|
|
58
60
|
|
|
59
|
-
-
|
|
60
|
-
-
|
|
61
|
-
-
|
|
61
|
+
- 只对「设置 → 模型」里自己添加的供应商生效,**DeepSeek 官方供应商不在其中**
|
|
62
|
+
- 供应商顺序、浮窗位置只保存在本机浏览器,不跨设备同步;模型顺序写在设置里,会跟着走
|
|
63
|
+
- 不修改 DSH 本体,只通过官方提供的扩展点接入
|
|
62
64
|
|
|
63
65
|
## 许可
|
|
64
66
|
|
|
@@ -66,4 +68,4 @@ MIT
|
|
|
66
68
|
|
|
67
69
|
---
|
|
68
70
|
|
|
69
|
-
|
|
71
|
+
想自己改这个插件、或者想了解它内部怎么实现的,见 [DEVELOPING.md](./DEVELOPING.md)。
|
package/lib/client.js
CHANGED
|
@@ -41,7 +41,7 @@ window.__ModuleLoader__.load({
|
|
|
41
41
|
"order.unsaved": "有未保存改动",
|
|
42
42
|
"order.readonly": "当前环境不可写入该设置",
|
|
43
43
|
"providers.title": "供应商与模型顺序",
|
|
44
|
-
"providers.hint": "
|
|
44
|
+
"providers.hint": "点击收起/展开,长按拖拽调整",
|
|
45
45
|
"providers.expand": "展开顺序面板",
|
|
46
46
|
"providers.collapse": "收起顺序面板",
|
|
47
47
|
"providers.saved": "已保存 ✓",
|
|
@@ -66,7 +66,7 @@ window.__ModuleLoader__.load({
|
|
|
66
66
|
"order.unsaved": "Unsaved changes",
|
|
67
67
|
"order.readonly": "This settings document is read-only here",
|
|
68
68
|
"providers.title": "Provider and model order",
|
|
69
|
-
"providers.hint": "
|
|
69
|
+
"providers.hint": "Click to collapse/expand, press and drag to reorder",
|
|
70
70
|
"providers.expand": "Expand the order panel",
|
|
71
71
|
"providers.collapse": "Collapse the order panel",
|
|
72
72
|
"providers.saved": "Saved ✓",
|
|
@@ -90,7 +90,7 @@ window.__ModuleLoader__.load({
|
|
|
90
90
|
code changes, so a browser can keep running an old build until a hard reload.
|
|
91
91
|
Kept OFF the UI (it is not user-facing): read it as
|
|
92
92
|
`window.__dshModelOrganizerBuild` when a stale build is suspected. */
|
|
93
|
-
const BUILD = "
|
|
93
|
+
const BUILD = "b24";
|
|
94
94
|
|
|
95
95
|
let officialClasses = null;
|
|
96
96
|
let officialClassesSheets = -1;
|
|
@@ -265,11 +265,16 @@ window.__ModuleLoader__.load({
|
|
|
265
265
|
/* Floating presentation: the panel sits in the corner of the Models page so
|
|
266
266
|
provider/API configuration and reordering are visible at the same time. */
|
|
267
267
|
floatPanel: {
|
|
268
|
-
position: "fixed", right: 20, bottom: 20, zIndex:
|
|
268
|
+
position: "fixed", right: 20, bottom: 20, zIndex: 10000, width: 380, maxWidth: "calc(100vw - 40px)",
|
|
269
269
|
display: "flex", flexDirection: "column", padding: 4,
|
|
270
270
|
border: "1px solid var(--dsw-alias-border-l1)", borderRadius: 14,
|
|
271
|
-
|
|
272
|
-
|
|
271
|
+
/* The theme's menu token is an OPAQUE colour (a color-mix), so using it as-is
|
|
272
|
+
makes this panel a solid box and the blur below has nothing to show. Mixing
|
|
273
|
+
it back toward transparent restores the frosted look in the theme's colour. */
|
|
274
|
+
background: "color-mix(in srgb, var(--dsw-specific-menu, #26262a) 50%, transparent)",
|
|
275
|
+
backdropFilter: "var(--dsw-menu-backdrop-filter, blur(28px) saturate(1.6))",
|
|
276
|
+
boxShadow: "var(--dsw-elevation-prominent, 0 10px 30px rgba(0,0,0,.34))",
|
|
277
|
+
color: "var(--dsw-alias-label-primary)"
|
|
273
278
|
},
|
|
274
279
|
/* The shipped `cell` sets min-width:100%; the head must stay inside the panel. */
|
|
275
280
|
panelHeadCell: { display: "flex", alignItems: "center", gap: 6, flex: "1 1 auto", minWidth: 0 },
|
|
@@ -853,9 +858,26 @@ window.__ModuleLoader__.load({
|
|
|
853
858
|
ref: menuRef,
|
|
854
859
|
id: idRef.current + "-menu",
|
|
855
860
|
className: C !== null ? C.menu : undefined,
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
861
|
+
/* The shipped `_menu` rule asks for var(--dsw-specific-menu), but that lookup
|
|
862
|
+
comes out transparent on this node while the same token is defined on <body>.
|
|
863
|
+
Resolving it here and setting it inline is what makes the menu look like the
|
|
864
|
+
shipped one instead of a see-through pane. */
|
|
865
|
+
style: Object.assign({
|
|
866
|
+
background: (function () {
|
|
867
|
+
try {
|
|
868
|
+
const v = getComputedStyle(document.body).getPropertyValue("--dsw-specific-menu").trim();
|
|
869
|
+
if (v.length > 0) return v;
|
|
870
|
+
} catch (e) {}
|
|
871
|
+
return "#26262a";
|
|
872
|
+
})(),
|
|
873
|
+
/* This build ships _7KE1Ra_menu without its background or radius (the source
|
|
874
|
+
declares both), so the shipped menu renders as a square transparent pane.
|
|
875
|
+
Both are restored here from the theme's own tokens. */
|
|
876
|
+
borderRadius: "var(--dsw-radius-lg, 16px)"
|
|
877
|
+
},
|
|
878
|
+
C !== null
|
|
879
|
+
? (menuPos !== null ? menuPos : MEASURE_STYLE)
|
|
880
|
+
: Object.assign({}, S.fallbackMenu, menuPos !== null ? menuPos : MEASURE_STYLE)),
|
|
859
881
|
role: "menu",
|
|
860
882
|
"aria-label": t("composer.title"),
|
|
861
883
|
"aria-busy": loading || busy
|
|
@@ -1105,6 +1127,42 @@ window.__ModuleLoader__.load({
|
|
|
1105
1127
|
return known.concat(rest);
|
|
1106
1128
|
}
|
|
1107
1129
|
|
|
1130
|
+
/* A seat entry that throws is silently abdicated, so "my panel is missing" and
|
|
1131
|
+
"my panel threw" look identical from the page. This boundary turns the throw
|
|
1132
|
+
into one visible line, so the failure can be reported instead of guessed. */
|
|
1133
|
+
class PanelBoundary extends React.Component {
|
|
1134
|
+
constructor(props) {
|
|
1135
|
+
super(props);
|
|
1136
|
+
this.state = { err: null };
|
|
1137
|
+
}
|
|
1138
|
+
static getDerivedStateFromError(err) {
|
|
1139
|
+
return { err: err };
|
|
1140
|
+
}
|
|
1141
|
+
render() {
|
|
1142
|
+
if (this.state.err !== null) {
|
|
1143
|
+
const message = String((this.state.err && this.state.err.message) || this.state.err);
|
|
1144
|
+
return h("div", {
|
|
1145
|
+
"data-mo": "panel-error",
|
|
1146
|
+
style: {
|
|
1147
|
+
margin: "8px 0", padding: "6px 10px", borderRadius: 8, fontSize: 12, lineHeight: "18px",
|
|
1148
|
+
whiteSpace: "pre-wrap",
|
|
1149
|
+
border: "1px solid var(--dsw-alias-state-error-primary, #f85149)",
|
|
1150
|
+
color: "var(--dsw-alias-state-error-primary, #f85149)"
|
|
1151
|
+
}
|
|
1152
|
+
}, "dsh-model-organizer 渲染失败:" + message);
|
|
1153
|
+
}
|
|
1154
|
+
return this.props.children;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
function ProviderOrderPanelGuarded(props) {
|
|
1159
|
+
return h(PanelBoundary, null, h(ProviderOrderPanel, props));
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
function ProviderGroupedModelSelectGuarded(props) {
|
|
1163
|
+
return h(PanelBoundary, null, h(ProviderGroupedModelSelect, props));
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1108
1166
|
function ProviderOrderPanel(props) {
|
|
1109
1167
|
const C = resolveOfficialClasses();
|
|
1110
1168
|
ensureOwnStyles();
|
|
@@ -1164,21 +1222,9 @@ window.__ModuleLoader__.load({
|
|
|
1164
1222
|
const idsKey = ids.join("|");
|
|
1165
1223
|
const dirty = order !== null && order.join("|") !== sourceKey;
|
|
1166
1224
|
|
|
1167
|
-
/*
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
next frame and once more shortly after. */
|
|
1171
|
-
useEffect(function () {
|
|
1172
|
-
const list = idsKey.length === 0 ? [] : idsKey.split("|");
|
|
1173
|
-
applyCardOrder(display, list);
|
|
1174
|
-
const frame = requestAnimationFrame(function () { applyCardOrder(display, list); });
|
|
1175
|
-
const timer = setTimeout(function () { applyCardOrder(display, list); }, 400);
|
|
1176
|
-
return function () {
|
|
1177
|
-
cancelAnimationFrame(frame);
|
|
1178
|
-
clearTimeout(timer);
|
|
1179
|
-
};
|
|
1180
|
-
}, [sourceKey, idsKey]);
|
|
1181
|
-
|
|
1225
|
+
/* The shipped provider cards are left exactly as React renders them. Ordering used
|
|
1226
|
+
to be mirrored onto them with inline `order`; that is gone — this plugin adds a
|
|
1227
|
+
floating panel to the Models page and nothing else. */
|
|
1182
1228
|
function commit(next) {
|
|
1183
1229
|
if (next.join("|") === baseIds.join("|")) { setOrder(null); return; }
|
|
1184
1230
|
writeProviderOrder(next);
|
|
@@ -1352,56 +1398,19 @@ window.__ModuleLoader__.load({
|
|
|
1352
1398
|
: Object.assign({}, S.floatPanel, { left: panelPos.left, top: panelPos.top, right: "auto", bottom: "auto" });
|
|
1353
1399
|
/* Original layout (header first, list below) with a TOP-anchored panel: the
|
|
1354
1400
|
header never moves and collapsing pulls the bottom edge up. */
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1401
|
+
/* Portalled to <body>: the footer seat lives deep inside the settings page, so a
|
|
1402
|
+
fixed panel there is trapped in that subtree's stacking context and any other
|
|
1403
|
+
plugin's floating widget paints over it. At body level the z-index is global. */
|
|
1404
|
+
return ReactDOM.createPortal(
|
|
1405
|
+
h("div", { ref: panelRef, style: panelStyle, "data-mo": "panel" },
|
|
1406
|
+
head,
|
|
1407
|
+
panelOpen ? h("div", { style: S.floatBody }, list) : null
|
|
1408
|
+
),
|
|
1409
|
+
document.body
|
|
1358
1410
|
);
|
|
1359
1411
|
}
|
|
1360
1412
|
|
|
1361
1413
|
/* -------------------------------- helpers --------------------------------- */
|
|
1362
|
-
/* The Models page renders one <li> per provider inside its own <ul>. The host
|
|
1363
|
-
owns that order (the settings document cannot express it — verified), so the
|
|
1364
|
-
stored preference is applied as CSS `order` on a flex column: the DOM is left
|
|
1365
|
-
exactly as React rendered it, only the visual sequence changes. Cards that do
|
|
1366
|
-
not correspond to a settings provider (catalog-only rows) stay pinned first. */
|
|
1367
|
-
function applyCardOrder(providers, ids) {
|
|
1368
|
-
try {
|
|
1369
|
-
const names = [];
|
|
1370
|
-
for (const id in providers) {
|
|
1371
|
-
const dn = providers[id] ? providers[id].displayName : undefined;
|
|
1372
|
-
if (typeof dn === "string" && dn.length > 0) names.push([id, dn]);
|
|
1373
|
-
}
|
|
1374
|
-
if (names.length === 0) return;
|
|
1375
|
-
names.sort(function (a, b) { return b[1].length - a[1].length; });
|
|
1376
|
-
|
|
1377
|
-
let list = null;
|
|
1378
|
-
let best = 0;
|
|
1379
|
-
const lists = document.querySelectorAll("ul");
|
|
1380
|
-
for (let i = 0; i < lists.length; i++) {
|
|
1381
|
-
const items = lists[i].children;
|
|
1382
|
-
let hits = 0;
|
|
1383
|
-
for (let j = 0; j < items.length; j++) {
|
|
1384
|
-
const text = items[j].textContent || "";
|
|
1385
|
-
for (let k = 0; k < names.length; k++) if (text.indexOf(names[k][1]) !== -1) { hits++; break; }
|
|
1386
|
-
}
|
|
1387
|
-
if (hits >= 2 && hits > best) { best = hits; list = lists[i]; }
|
|
1388
|
-
}
|
|
1389
|
-
if (list === null) return;
|
|
1390
|
-
|
|
1391
|
-
list.style.display = "flex";
|
|
1392
|
-
list.style.flexDirection = "column";
|
|
1393
|
-
const rank = {};
|
|
1394
|
-
for (let i = 0; i < ids.length; i++) rank[ids[i]] = i;
|
|
1395
|
-
const items = list.children;
|
|
1396
|
-
for (let i = 0; i < items.length; i++) {
|
|
1397
|
-
const text = items[i].textContent || "";
|
|
1398
|
-
let matched;
|
|
1399
|
-
for (let k = 0; k < names.length; k++) if (text.indexOf(names[k][1]) !== -1) { matched = names[k][0]; break; }
|
|
1400
|
-
const r = matched === undefined ? undefined : rank[matched];
|
|
1401
|
-
items[i].style.order = String(r === undefined ? -1 : r);
|
|
1402
|
-
}
|
|
1403
|
-
} catch (e) {}
|
|
1404
|
-
}
|
|
1405
1414
|
|
|
1406
1415
|
function namespaceView(view, namespace) {
|
|
1407
1416
|
if (!view || !view.namespaces) return null;
|
|
@@ -1450,6 +1459,18 @@ window.__ModuleLoader__.load({
|
|
|
1450
1459
|
|
|
1451
1460
|
function apply(ctx) {
|
|
1452
1461
|
try { window.__dshModelOrganizerBuild = BUILD; } catch (e) {}
|
|
1462
|
+
/* A DOM marker proving this bundle actually applied, independent of any seat.
|
|
1463
|
+
Seat registration is silent by design (a throw abdicates the entry), so
|
|
1464
|
+
"is the new code even loaded?" is otherwise unanswerable from the page. */
|
|
1465
|
+
try {
|
|
1466
|
+
if (document.body !== null && document.querySelector('[data-mo="boot"]') === null) {
|
|
1467
|
+
const boot = document.createElement("div");
|
|
1468
|
+
boot.setAttribute("data-mo", "boot");
|
|
1469
|
+
boot.setAttribute("data-mo-build", BUILD);
|
|
1470
|
+
boot.hidden = true;
|
|
1471
|
+
document.body.appendChild(boot);
|
|
1472
|
+
}
|
|
1473
|
+
} catch (e) {}
|
|
1453
1474
|
ctx.effect(function () { return ctx.locale.register(NS, { zh: zh, en: en }); }, "dsh-model-organizer: dictionaries");
|
|
1454
1475
|
const t = ctx.locale.bind(NS);
|
|
1455
1476
|
|
|
@@ -1477,12 +1498,31 @@ window.__ModuleLoader__.load({
|
|
|
1477
1498
|
t: t
|
|
1478
1499
|
};
|
|
1479
1500
|
}
|
|
1480
|
-
},
|
|
1501
|
+
}, ProviderGroupedModelSelectGuarded); });
|
|
1481
1502
|
});
|
|
1482
1503
|
});
|
|
1483
1504
|
|
|
1484
|
-
|
|
1485
|
-
|
|
1505
|
+
/* The settings mirror moved homes: DSH 0.1.7 exposes it as `configForms`
|
|
1506
|
+
(provided by ui-settings); 0.1.5/0.1.6 exposed the same object as
|
|
1507
|
+
`settingsScope`. A cordis `ctx.inject` whose service never appears simply
|
|
1508
|
+
never runs, so injecting only the old name made this whole seat vanish
|
|
1509
|
+
without a single error once DSH renamed it. Inject both names and mount
|
|
1510
|
+
once — whichever resolves first wins, the other stays dormant. */
|
|
1511
|
+
let orderPanelMounted = false;
|
|
1512
|
+
|
|
1513
|
+
function mountOrderPanel(scope, mirror) {
|
|
1514
|
+
if (orderPanelMounted) return;
|
|
1515
|
+
orderPanelMounted = true;
|
|
1516
|
+
/* Mark that the service resolved and the panel is being mounted — this
|
|
1517
|
+
separates "the seat never rendered" from "the inject never fired". */
|
|
1518
|
+
try {
|
|
1519
|
+
if (document.body !== null && document.querySelector('[data-mo="panel-mounted"]') === null) {
|
|
1520
|
+
const mark = document.createElement("div");
|
|
1521
|
+
mark.setAttribute("data-mo", "panel-mounted");
|
|
1522
|
+
mark.hidden = true;
|
|
1523
|
+
document.body.appendChild(mark);
|
|
1524
|
+
}
|
|
1525
|
+
} catch (e) {}
|
|
1486
1526
|
mirror.ensure();
|
|
1487
1527
|
const mutate = function (ops, revision) { return scope.remote.settings.mutate(PI_AI_NS, ops, revision); };
|
|
1488
1528
|
|
|
@@ -1504,9 +1544,16 @@ window.__ModuleLoader__.load({
|
|
|
1504
1544
|
id: "model-organizer-provider-order",
|
|
1505
1545
|
order: 100,
|
|
1506
1546
|
inject: face
|
|
1507
|
-
},
|
|
1547
|
+
}, ProviderOrderPanelGuarded);
|
|
1508
1548
|
});
|
|
1509
1549
|
});
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
ctx.inject(["slots", "configForms", "remote", "remote.settings"], function (scope) {
|
|
1553
|
+
mountOrderPanel(scope, scope.configForms.describe());
|
|
1554
|
+
});
|
|
1555
|
+
ctx.inject(["slots", "settingsScope", "remote", "remote.settings"], function (scope) {
|
|
1556
|
+
mountOrderPanel(scope, scope.settingsScope.describe());
|
|
1510
1557
|
});
|
|
1511
1558
|
}
|
|
1512
1559
|
|