dsh-multimodal-node 0.0.0-stage → 0.2.2
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 +504 -2
- package/client.js +2817 -0
- package/cordis.patch.yml +4 -0
- package/icon.svg +9 -0
- package/index.js +960 -0
- package/lib/apikeys.js +250 -0
- package/lib/assets.js +234 -0
- package/lib/auth.js +315 -0
- package/lib/blender.js +600 -0
- package/lib/chain.js +161 -0
- package/lib/comfy.js +204 -0
- package/lib/config.js +377 -0
- package/lib/gateway.js +974 -0
- package/lib/http.js +119 -0
- package/lib/imagesize.js +92 -0
- package/lib/instances.js +554 -0
- package/lib/library.js +1209 -0
- package/lib/models.js +322 -0
- package/lib/nodeapi.js +491 -0
- package/lib/nodeclient.js +143 -0
- package/lib/pages.js +5788 -0
- package/lib/pool.js +591 -0
- package/lib/promptassist.js +1077 -0
- package/lib/promptlibrary.js +92 -0
- package/lib/remote.js +3348 -0
- package/lib/runindex.js +126 -0
- package/lib/seed.js +118 -0
- package/lib/tagdata.json +1 -0
- package/lib/tiers.js +283 -0
- package/lib/tools.js +230 -0
- package/lib/viewport.js +134 -0
- package/lib/workflows.js +472 -0
- package/lib/ws.js +106 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +60 -4
package/README.md
CHANGED
|
@@ -1,3 +1,505 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 06 · 多模态节点插件(dsh-multimodal-node)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> 一个插件,两种身份。服务节点开门并派发任务;用户节点不开门,连别人的服务节点。
|
|
4
|
+
> 状态:**v0.1.0 本地首版,未发布**。网页端沿用 03 号插件的控制台。
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 一、这个插件解决什么
|
|
9
|
+
|
|
10
|
+
03 号插件是「一台机器上多张卡,把任务排队分发出去」。它有一个隐含假设:**用的人和跑卡的机器是同一台**。
|
|
11
|
+
|
|
12
|
+
06 拆掉这个假设:
|
|
13
|
+
|
|
14
|
+
| | 谁 | 门 | 干什么 |
|
|
15
|
+
|---|---|---|---|
|
|
16
|
+
| **服务节点** | 有显卡的那台 | 开两个端口 | API 接入端口给程序(含 ComfyUI 协议与 `/node/*`);网页端口给人;另托管 ComfyUI 与 Blender 的实际端口 |
|
|
17
|
+
| **用户节点** | 想用算力的那台 | **一个端口都不开** | 填服务节点的公网地址 + apikey,在侧边栏拿工作流模板、看当前队列、看模型列表,产物拉回本机目录 |
|
|
18
|
+
|
|
19
|
+
模式是唯一的顶层开关。切换只换**监听行为与侧边栏**,不换数据目录——同一台机器先当服务节点、后来只当客户端,它的工作流库、运行索引、资产索引都还在。
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 二、界面在哪
|
|
24
|
+
|
|
25
|
+
| 位置 | 内容 |
|
|
26
|
+
|---|---|
|
|
27
|
+
| **设置页 ·「多模态节点」** | 工作模式(两张卡选择);服务节点模式 → 网页端口 + API 接入端口 + 网页账号管理(含 SFW/NSFW)+ apikey 管理(含 SFW/NSFW);用户节点模式 → 公网端口地址 + apikey + 本机产物目录;Blender 板块(可执行文件 + 实例列表) |
|
|
28
|
+
| **侧边栏 ·「多模态」** | 按模式渲染两套完全不同的页面,见下 |
|
|
29
|
+
|
|
30
|
+
### 侧边栏 · 用户节点
|
|
31
|
+
- **工作流模板**:服务节点的工作流库,按 apikey 分级过滤;能看到每个模板要几个提示词框、有几个图输入
|
|
32
|
+
- **当前队列**:服务节点任务池的实时快照
|
|
33
|
+
- **模型列表**:服务节点的模型清单(SFW 的 key 只拿到目录名)
|
|
34
|
+
- **本机产物文件夹**:从服务节点拉回来的图/视频落到哪,带缩略图
|
|
35
|
+
|
|
36
|
+
### 侧边栏 · 服务节点
|
|
37
|
+
- **ComfyUI 板块**:一个实例 = 一张卡 = 一个端口;可托管本机 bat(启动/停止/重启)、设并发、权重、自动拉起、退出时停止。
|
|
38
|
+
**模型列表就在这张卡片里**:填「模型文件夹地址」→ 探测 → 扫描,按目录分组列文件、可搜文件名(v0.2.0)
|
|
39
|
+
- **Blender 板块**:`bridge`(对方机器的 HTTP 端点)或 `spawn`(本机 `blender.exe` 命令行渲染);提交渲染、帧范围、进度、产物
|
|
40
|
+
- **队列板块**:动态/先后两种排序、插队、取消、释放显存、清空排队
|
|
41
|
+
|
|
42
|
+
板块页脚还有一段**额外扫描目录**:不在任何 ComfyUI 安装里的模型(另一块盘、NAS 映射盘)。
|
|
43
|
+
|
|
44
|
+
> v0.2.0 起模型列表不再是独立 tab。服务节点侧边栏只有三个板块:ComfyUI / Blender / 队列。
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 三、两套凭据,故意不合并
|
|
49
|
+
|
|
50
|
+
| | 给谁 | 形态 | 存法 |
|
|
51
|
+
|---|---|---|---|
|
|
52
|
+
| **网页账号** | 人 | 用户名 + 密码(scrypt) + 6 位动态配对码 | salt/hash |
|
|
53
|
+
| **apikey** | 程序 | 一个 `Bearer` 串,形如 `mmn_` 开头 | **只存 sha256**,明文只在创建时显示一次 |
|
|
54
|
+
|
|
55
|
+
分级(`sfw` / `nsfw`)两边都有,语义相同:**是上限不是偏好**。NSFW 的凭据能用 SFW 内容,反之不行。
|
|
56
|
+
|
|
57
|
+
apikey 的 SFW 在本插件里实际拦住三件事(都有测试锁住):
|
|
58
|
+
1. 提交成人工作流 → 403
|
|
59
|
+
2. 列出模型文件名 → 只给目录名
|
|
60
|
+
3. 取回成人产物 → 403
|
|
61
|
+
4. 释放显存(运维动作)→ 403
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 四、端口
|
|
66
|
+
|
|
67
|
+
| 端口 | 默认 | 谁用 | 路径 |
|
|
68
|
+
|---|---|---|---|
|
|
69
|
+
| API 接入 | `8287` | 脚本、别的 DSH、别的机器 | ComfyUI 原协议(`/prompt` `/queue` `/history` `/view` `/system_stats` `/object_info`)+ `/node/*` |
|
|
70
|
+
| 网页 | `8286` | 浏览器 | 03 号插件的控制台原样保留(登录、工作流、上传、画布桥接、资产、历史) |
|
|
71
|
+
|
|
72
|
+
`/node/*` 是 06 新增的用户节点接口:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
GET /node/ping 免凭据(只要它回 ok,否则「配好 key 才能确认服务活着」是死循环)
|
|
76
|
+
GET /node/templates 模板列表,按 key 的 tier 过滤
|
|
77
|
+
GET /node/models 模型清单,SFW 只给目录名
|
|
78
|
+
GET /node/queue 队列快照
|
|
79
|
+
GET /node/history[/:id] 历史
|
|
80
|
+
POST /node/pool 动作:dynamic / cancel / prioritize / free / clearQueue
|
|
81
|
+
POST /node/prompt 提交任务(按 tier 兜底)
|
|
82
|
+
GET /node/media 取产物(只认文件名,拒绝路径)
|
|
83
|
+
GET /node/blender 板块状态
|
|
84
|
+
POST /node/blender/render 提交渲染
|
|
85
|
+
POST /node/blender/cancel 取消
|
|
86
|
+
GET /node/blender/blends 浏览目录里的 .blend
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`/node/ping` 与 `/health` 的免凭据白名单是**一处定义、两处引用**(请求前 guard 与路由内部 authorize)。两处各写一份就会得到「gateway 放行、路由再拦一次」——表现为 `/node/ping` 莫名 401,且日志里一个字都没有。
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## 五、配置项
|
|
94
|
+
|
|
95
|
+
数据目录:`~/.dsh/data/dsh-multimodal-node/`
|
|
96
|
+
|
|
97
|
+
| 文件 | 作用 |
|
|
98
|
+
|---|---|
|
|
99
|
+
| `settings.json` | 全部设置(模式、端口、账号、apikey、实例、Blender、表头、知识库) |
|
|
100
|
+
| `workflows.json` | 工作流库 |
|
|
101
|
+
| `runs.json` | 运行索引(跨重启保留) |
|
|
102
|
+
| `assets.json` | 资产索引 |
|
|
103
|
+
| `prompts.json` | 提示词收藏(按账号) |
|
|
104
|
+
| `chains.json` | 工作链 |
|
|
105
|
+
|
|
106
|
+
产物目录:`~/.dsh/media/dsh-multimodal-node/`(可在设置页改;填相对路径会被忽略)
|
|
107
|
+
Blender 产物:上者的 `blender/` 子目录
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## 六、从 03 号插件继承了什么,改了什么
|
|
112
|
+
|
|
113
|
+
继承(`lib/` 下 24 个模块整体搬过来,一字未改的部分最多):
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
pool.js gateway.js remote.js pages.js library.js workflows.js
|
|
117
|
+
auth.js tiers.js tagdata.json promptassist.js promptlibrary.js
|
|
118
|
+
chain.js assets.js runindex.js seed.js viewport.js imagesize.js
|
|
119
|
+
comfy.js instances.js http.js ws.js
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
改动:
|
|
123
|
+
|
|
124
|
+
| 文件 | 改动 | 为什么 |
|
|
125
|
+
|---|---|---|
|
|
126
|
+
| `lib/config.js` | 重写:加 `mode` / `service` / `node` / `blender`;`SettingsStore` 加写盘串行链 | 模式与两套凭据是新概念;串行链是全插件一个 settings.json 的必然要求 |
|
|
127
|
+
| `lib/apikeys.js` | 新增 | apikey 是第二条凭据线 |
|
|
128
|
+
| `lib/blender.js` | 新增 | Blender 板块(bridge + spawn 两种托管、帧范围解析、`buildLaunch`) |
|
|
129
|
+
| `lib/models.js` | 新增:把 03 里散在 remote.js 的模型读取提出来 | 用户节点要读它,服务节点的模型板块也要读它,不该只能从控制台类上拿 |
|
|
130
|
+
| `lib/nodeapi.js` | 新增 | `/node/*` 路由 + 统一 API 闸门 |
|
|
131
|
+
| `lib/nodeclient.js` | 新增 | 用户节点侧客户端(错误分级:没填地址 / 连不上 / 401 三种分开说) |
|
|
132
|
+
| `lib/tools.js` | 重写为 `multimodal_node` | 面板每个动作 agent 都能做 |
|
|
133
|
+
| `lib/gateway.js` | 插入 `nodeRoutes` 分发 + `apiGuard` 闸门;兜底端口 8187→8287 | `/node/*` 必须在 ComfyUI 协议分支之前;否则被当未知路由转发给主实例 |
|
|
134
|
+
| `lib/remote.js` | 兜底端口 8186→8286 | 同上 |
|
|
135
|
+
| `index.js` | 重写 | 两套模式、两套监听、两个板块 |
|
|
136
|
+
| `client.js` | 重写 | 两套 UI + 设置页 |
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## 七、已修的三个真实故障(都是先被测试抓到的)
|
|
141
|
+
|
|
142
|
+
### 1. 并发写 settings.json → EPERM
|
|
143
|
+
|
|
144
|
+
一次带 apikey 的调用会同时改 `failures`、`lastUsedAt`、`sessions` 三处,各自 `rename` 到同一个目标。Windows 上直接 `EPERM operation not permitted`。
|
|
145
|
+
|
|
146
|
+
修法:`SettingsStore` 与 `ApiKeyStore` 各一条写链。**快照在入队时取**——等执行时才 `JSON.stringify(this.value)` 会让两次相邻 patch 都序列化出「最后一次」的内容,前一次改动静默丢失。
|
|
147
|
+
|
|
148
|
+
### 2. 读-改-写在队列外 → 新建的 key 消失
|
|
149
|
+
|
|
150
|
+
`create()` 原来是「先读 records、算好新数组、再入队写」。两次并发 create 各读一份 records,后写的把前一次整个覆盖。队列只保证写盘不撞车,保证不了「算的时候看到的是最新的」。
|
|
151
|
+
|
|
152
|
+
修法:读-改-写整体进队列(`create` / `update` / `remove` / `rotate` 四处)。
|
|
153
|
+
|
|
154
|
+
### 3. `enqueue` 吞错导致报错位置失真
|
|
155
|
+
|
|
156
|
+
第一版把「吃掉错误的链」直接返回,于是「重名」这种校验错误变成 `undefined`,调用方接着读字段才炸。修法:链与返回值是两个 promise。
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## 八、测试
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
node test/all.mjs
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
本机 node 不在 PATH 时:
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
ELECTRON_RUN_AS_NODE=1 "…/DeepSeek Harness.exe" test/all.mjs
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**243 条,全绿。** 覆盖:配置合并与清洗、apikey 全生命周期、API 闸门、并发写盘、并发建 key、Blender 帧解析/launch/实例规则/spawn 真进程/bridge 真 HTTP、模型磁盘读取、`/node/*` 真端口、客户端静态审计、清单完整性。
|
|
173
|
+
|
|
174
|
+
### 反向验证(变异测试)
|
|
175
|
+
|
|
176
|
+
源码套件用 `test/all.mjs`,UI 套件用 `test/render.mjs`(真的把组件树跑一遍,抓出 HTML 再断言)。
|
|
177
|
+
|
|
178
|
+
两条变异脚本把每个 bug 放回去,要求测试**变红**。源码 8/8 + UI 7/7 全部变红:
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
✓ SettingsStore.enqueue 不排队 → fail 1
|
|
182
|
+
✓ OPEN_PATHS 白名单在路由里失效 → fail 3
|
|
183
|
+
✓ buildLaunch 不认 .cmd → fail 4
|
|
184
|
+
✓ apikey 停用后仍然放行 → fail 1
|
|
185
|
+
✓ 模型清单收录 .py 配置 → fail 2
|
|
186
|
+
✓ 切模式后不动 mode 字段 → fail 2
|
|
187
|
+
✓ 帧范围步长不夹取 → fail 1
|
|
188
|
+
✓ 客户端把 key 拼进 URL → fail 1
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
UI 套件(`test/render.mjs`,89 条)的变异:
|
|
192
|
+
|
|
193
|
+
```
|
|
194
|
+
✓ M1 模式分支失效(用户节点也渲染服务节点页) → fail 11
|
|
195
|
+
✓ M2 设置页不按模式分支 → fail 11
|
|
196
|
+
✓ M3 用户节点队列取数回到 ?? 猜 → fail 3
|
|
197
|
+
✓ M4 未连接时不给提示 → fail 1
|
|
198
|
+
✓ M5 侧边栏入口不注册 → fail 1
|
|
199
|
+
✓ M6 设置页入口不注册 → fail 1
|
|
200
|
+
✓ M7 apikey 区不渲染 SFW/NSFW 分级 → fail 1
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
这一步抓出**四条假测试**(第一轮变异时仍全绿):
|
|
204
|
+
|
|
205
|
+
- 「串行写盘」原本断言的是文件完整性,而那条路径本来就只有一次写入;改成并发建 key 后才测得到。
|
|
206
|
+
- 「倒序范围被忽略」的守卫是不可达代码(`for` 条件本身已经挡住),断言永远为真;守卫已删除,换成「步长 0 必须夹到 1」——那个才是会让宿主挂死的输入。
|
|
207
|
+
- 「用户节点队列数字」原来只断言 `includes('排队 2')`,而 2 来自 probe 那条路,队列取数写错照样通过。改成「显式拉到 5 个排队、probe 说 2,页面必须显示 5」才测得到。
|
|
208
|
+
- 「注册项数 3」写成 `includes` 系(等价于只看名字存不存在),把 inject 的 slot 名改掉仍是 3。改成名字集合等值 + `injections` 逐字比对。
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## 九、本机安装与卸载 03(已完成)
|
|
213
|
+
|
|
214
|
+
### 已装状态(2026-11-13)
|
|
215
|
+
|
|
216
|
+
| 项 | 值 |
|
|
217
|
+
|---|---|
|
|
218
|
+
| 包 | `dsh-multimodal-node@0.1.0` |
|
|
219
|
+
| dist | `C:/DSH-Work/multimodal-node/dist-dsh-multimodal-node-0.1.0-982a9b65.tgz` |
|
|
220
|
+
| profile 依赖 | `file:C:/DSH-Work/multimodal-node/dist-dsh-multimodal-node-0.1.0-982a9b65.tgz` |
|
|
221
|
+
| node_modules | **36/36 文件逐字节 sha256 全等** |
|
|
222
|
+
| 已装副本验收 | 26/26(apply 可跑、13 条面板路由、真实端口中继、client 三处 slot) |
|
|
223
|
+
|
|
224
|
+
### 03 已卸载
|
|
225
|
+
|
|
226
|
+
| 位置 | 处理 |
|
|
227
|
+
|---|---|
|
|
228
|
+
| `dsh.profile.bundles` | 摘掉 `dsh-comfyui-gateway`(25 → 24 项) |
|
|
229
|
+
| `dependencies` | 删掉 03 的 tgz 依赖 |
|
|
230
|
+
| `node_modules/dsh-comfyui-gateway` | 移到 `C:/DSH-Work/multimodal-node/removed-03/`(**没删**,可回滚) |
|
|
231
|
+
| `pnpm-lock.yaml` | 定点删除 3 处 03 登记、插入 06;备份 `pnpm-lock.yaml.before-06-*.bak` |
|
|
232
|
+
| `node_modules/.modules.yaml` | `hoistedLocations` 里换掉 03、补上 06;备份 `.modules.yaml.bak` |
|
|
233
|
+
| 根 `cordis.patch.yml` | 本来就没有 03(它由包自带 patch 插入),无需处理 |
|
|
234
|
+
|
|
235
|
+
### 数据迁移(03 → 06)
|
|
236
|
+
|
|
237
|
+
03 的数据目录 `~/.dsh/data/dsh-comfyui-gateway/` **原样保留**,另拷一份到
|
|
238
|
+
`C:/DSH-Work/multimodal-node/backup-03-data-20261113-205710/`;06 的目录
|
|
239
|
+
`~/.dsh/data/dsh-multimodal-node/` 由迁移脚本生成:
|
|
240
|
+
|
|
241
|
+
| 文件 | 处理 |
|
|
242
|
+
|---|---|
|
|
243
|
+
| workflows.json / runs.json / assets.json / prompts.json / chains.json | 原样搬(形状没变) |
|
|
244
|
+
| settings.json | **合并**:03 的 listen / dynamic / probeMs / historyItems / stallWarnSec / promptHeads / promptKb / promptSkill / remote / instances 盖到 06 的默认值上 |
|
|
245
|
+
|
|
246
|
+
迁移自检 9/9:模式 service、API 端口 8187、网页端口 8186、2 个账号、secret 64 位保留、
|
|
247
|
+
2 个实例(p8188/p8189)、apikey 表空、Blender 1 个默认实例。
|
|
248
|
+
|
|
249
|
+
**06 沿用 03 的 8187/8186**:现有调用方(脚本、别的 DSH、手机书签)一行都不用改。
|
|
250
|
+
|
|
251
|
+
读取验收:6 条工作流(2 SFW / 4 NSFW)、84 条运行记录、2 个账号分级正确、1 条收藏提示词、0 条工作链。
|
|
252
|
+
|
|
253
|
+
### 生效条件
|
|
254
|
+
|
|
255
|
+
**必须完全退出 DSH 再打开。** 改动时 8186/8187 正被运行中的 DSH 宿主进程(pid 23840)占用,
|
|
256
|
+
宿主内存里仍是旧插件表——文件已改完,但不重启就还是 03 在跑、06 不出现。
|
|
257
|
+
|
|
258
|
+
回滚:把 `package.json.before-06-*.bak` 与 `pnpm-lock.yaml.before-06-*.bak` 放回,
|
|
259
|
+
再把 `removed-03/dsh-comfyui-gateway-0.7.52` 移回 `node_modules/dsh-comfyui-gateway`。
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## 十、pnpm 的两处既有问题(不是本次改动引入,未擅自修改)
|
|
264
|
+
|
|
265
|
+
想跑 `pnpm install` 让它自己重写 lock 时撞到两个**在我动手之前就存在**的问题:
|
|
266
|
+
|
|
267
|
+
### 1. minimumReleaseAge 拦下 dsh-mnemon-source-skills@0.5.12
|
|
268
|
+
|
|
269
|
+
- 本机有一份 `minimumReleaseAgeExclude` 白名单,逐版本列出放行的包
|
|
270
|
+
- `dsh-mnemon-source-skills` 只列到 `0.5.11`,`0.5.12` 不在表里
|
|
271
|
+
- 解掉它只需往表里加一行,但那是改用户的供应链策略,不在本次任务范围内
|
|
272
|
+
|
|
273
|
+
### 2. dsh-clash-subscription 的 package.json 与 lock 不一致
|
|
274
|
+
|
|
275
|
+
- `package.json` 写 `0.3.0`
|
|
276
|
+
- `pnpm-lock.yaml` 写 `0.2.3`
|
|
277
|
+
- registry 最新就是 `0.2.3`(`0.3.0` 不存在)
|
|
278
|
+
|
|
279
|
+
这是**改我之前就有**的状态(备份 `package.json.before-06-20261113-205656.bak` 里同样是 0.3.0)。
|
|
280
|
+
任何一次 `pnpm install` 都会因此失败,与 06 无关。
|
|
281
|
+
|
|
282
|
+
### 当前实际状态
|
|
283
|
+
|
|
284
|
+
磁盘与 `package.json` 一致(06 在、03 不在);`pnpm-lock.yaml` 也已改成一致。
|
|
285
|
+
但 `pnpm list` 仍报 03 —— 它读的是 `node_modules/.pnpm` 虚拟存储的旧视图。
|
|
286
|
+
|
|
287
|
+
DSH 启动时直接读 `package.json` 的 `dsh.profile.bundles` + `node_modules` 目录,
|
|
288
|
+
不依赖 `pnpm list` 的输出,所以**这不影响插件加载**。
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## 十一、这一版还没做的(下一步)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
- 网页端**暂时沿用** 03 号的控制台,没有按模式改(用户明确要求「先做一版」)
|
|
296
|
+
- 用户节点提交任务的完整链路(模板 → 填参数 → 提交 → 轮询 → 拉产物)只有接口,前端只到「看模板详情」
|
|
297
|
+
- Blender bridge 侧的 addon 没有一起做,只定义了 `/health` `/render` `/cancel` 三个契约
|
|
298
|
+
- 服务节点的 apikey 没有按工作流/IP 做更细的限额
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## 十二、v0.2.0:模型列表并入 ComfyUI 板块
|
|
303
|
+
|
|
304
|
+
### 改了什么
|
|
305
|
+
|
|
306
|
+
模型列表从「独立 tab」搬进 **ComfyUI 板块内**,并且变成**扫描本地模型文件夹**:
|
|
307
|
+
每个实例卡片里多一段「模型文件夹」,可以填地址、探测、扫描。
|
|
308
|
+
|
|
309
|
+
| 项 | v0.1.0 | v0.2.0 |
|
|
310
|
+
|---|---|---|
|
|
311
|
+
| 模型列表位置 | 独立的第四个 tab | **ComfyUI 板块内,每个实例卡片里** |
|
|
312
|
+
| 文件夹地址 | 混在实例表单里的一个输入框 | 模型区自己的地址栏 + 探测 + 保存 |
|
|
313
|
+
| 扫描范围 | 全部实例合并 | **按实例隔离**,另有「额外扫描目录」 |
|
|
314
|
+
| 填错目录 | 只报「读不到」 | 探测直接指出差在哪一层 |
|
|
315
|
+
|
|
316
|
+
服务节点侧边栏的 tab 从 4 个变 3 个:**ComfyUI 板块 / Blender 板块 / 队列板块**。
|
|
317
|
+
|
|
318
|
+
### 扫描根的三层来源
|
|
319
|
+
|
|
320
|
+
| 来源 | 谁设 | 进谁的清单 |
|
|
321
|
+
|---|---|---|
|
|
322
|
+
| 实例的「模型文件夹地址」 | 面板上那一栏 | 只进该实例 |
|
|
323
|
+
| 启动脚本推断(`<bat 目录>/ComfyUI/models`) | 自动,填了地址就不推断 | 只进该实例 |
|
|
324
|
+
| 额外扫描目录 | ComfyUI 板块底部 | 进总清单,不进任何实例 |
|
|
325
|
+
|
|
326
|
+
第三条是给「不在任何 ComfyUI 安装里的模型」用的:另一块盘、NAS 映射盘、整理到一半的下载目录。
|
|
327
|
+
它们不属于某张卡,所以**不能**算进实例清单——否则「GPU1 上到底有没有这个文件」这个判断会失效。
|
|
328
|
+
|
|
329
|
+
### 探测会说的四件事
|
|
330
|
+
|
|
331
|
+
`inspectModelDir()` 是独立的一步,因为它回答的问题和「扫描」不同:
|
|
332
|
+
|
|
333
|
+
| 情况 | 返回 |
|
|
334
|
+
|---|---|
|
|
335
|
+
| 目录不存在 | `{ ok:false, error:'目录不存在' }` |
|
|
336
|
+
| 没权限 | `{ ok:false, error:'没有读取权限' }` |
|
|
337
|
+
| 相对路径 | `{ ok:false, error:'需要绝对路径' }` |
|
|
338
|
+
| 填了便携版根 `E:\ComfyUI-aki-v3.2` | `hint: '这个目录下面有 ComfyUI,再往里一层才是 models'` |
|
|
339
|
+
| 填了 `…/ComfyUI` | `hint: '这个目录下面还有一层 models'` |
|
|
340
|
+
| 填对了 | `{ ok:true, folders:[…], hint:null }` |
|
|
341
|
+
|
|
342
|
+
「填高一层」是最常见的错误,两层的形态都要认:`…/ComfyUI` 与 `…/ComfyUI-aki-v3.2`。
|
|
343
|
+
|
|
344
|
+
### 实测
|
|
345
|
+
|
|
346
|
+
对本机真实目录 `E:\ComfyUI-aki-v3.2\ComfyUI\models`:
|
|
347
|
+
|
|
348
|
+
```
|
|
349
|
+
探测:ok | looksLikeModels | 39 个子目录
|
|
350
|
+
扫描(实例 p8188):15 个目录 / 60 个文件
|
|
351
|
+
checkpoints 3 个 cyberrealisticPony_v180Coreshift_pruned_fp16.safetensors …
|
|
352
|
+
loras 7 个 KRGRLv2-000003.safetensors …
|
|
353
|
+
controlnet 5 个
|
|
354
|
+
insightface 7 个 inswapper_128.onnx …
|
|
355
|
+
ultralytics 7 个 bbox/face_yolov8m.pt …
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### 这一版的测试
|
|
359
|
+
|
|
360
|
+
| 套件 | 结果 |
|
|
361
|
+
|---|---|
|
|
362
|
+
| 源码 `test/all.mjs` | **286**(新增 `model-scan` 一类 45 条) |
|
|
363
|
+
| UI 渲染 `test/render.mjs` | **95**(新增模型扫描区 6 条) |
|
|
364
|
+
| 端到端模型扫描 | **21 / 21**(真端口、真目录、真路由) |
|
|
365
|
+
| 变异 `test/mutate.mjs` | **22 / 22 全红** |
|
|
366
|
+
|
|
367
|
+
新增的 8 条模型相关断言各自抓什么:
|
|
368
|
+
|
|
369
|
+
```
|
|
370
|
+
✓ P1-5 模型扫描不按实例隔离(串卡) → fail 5
|
|
371
|
+
✓ P1-6 inspect 不提示填高了一层 → fail 1
|
|
372
|
+
✓ P1-7 相对模型路径被放行 → fail 1
|
|
373
|
+
✓ P1-8 额外扫描目录被算成实例的 → fail 1
|
|
374
|
+
✓ M8 模型扫描区从实例卡片里拿掉 → fail 1
|
|
375
|
+
✓ M9 额外扫描目录区拿掉 → fail 1
|
|
376
|
+
✓ M10 模型文件夹地址输入框被换成只读 span → fail 1
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### 又抓出四条真实缺陷
|
|
380
|
+
|
|
381
|
+
写这一版时,测试与变异各抓出两条:
|
|
382
|
+
|
|
383
|
+
1. **`inspectModelDir` 把 `models` 一起过滤掉了** —— `configs`/`datasets` 的过滤规则误伤了
|
|
384
|
+
`models` 本身,于是「填高一层」的提示永远不触发。过滤只该作用于模型清单,不该作用于
|
|
385
|
+
「这个地址对不对」的探测。
|
|
386
|
+
2. **`ServiceNodePage` 里残留了用户节点页的模板详情块** —— 一渲染就 `detail is not defined`。
|
|
387
|
+
静态审计抓不到,只有真跑组件树才发现。
|
|
388
|
+
3. **「有模型文件夹地址输入框」是假测试** —— 原来断言 `page.includes('models')`,
|
|
389
|
+
页面上任何一句话都能满足。M10 变异(input→span)照样通过后才改掉,现在按
|
|
390
|
+
`<input … placeholder="E:\\">` 的元素形状断言。
|
|
391
|
+
4. **删除「模型列表 tab」时切错了边界** —— 按第一个 `tab === 'models'` 匹配,而第一个匹配
|
|
392
|
+
在服务节点页里,结果一路切到用户节点页,把 `UserNodePage` 整个删掉了。
|
|
393
|
+
修复方式是从已装副本里把那段抽回来;教训是**按内容切分要先定位函数定义起点**。
|
|
394
|
+
|
|
395
|
+
### 装法(已装)
|
|
396
|
+
|
|
397
|
+
```
|
|
398
|
+
"dsh-multimodal-node": "file:C:/DSH-Work/multimodal-node/dist-dsh-multimodal-node-0.2.0-79ad9aa1.tgz"
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
已装副本 36/36 文件 sha256 全等,验收 19/19(含真实目录读到 60 个文件)。
|
|
402
|
+
**完全退出 DSH 再打开**才生效。
|
|
403
|
+
|
|
404
|
+
---
|
|
405
|
+
|
|
406
|
+
## 十三、独立性与依赖审计(2026-11-14)
|
|
407
|
+
|
|
408
|
+
**结论:是一个独立插件,不依赖任何其他 DSH 插件。**
|
|
409
|
+
|
|
410
|
+
它只依赖两样东西:**Node 内置模块** 和 **宿主(DSH 自己)提供的服务**。
|
|
411
|
+
宿主服务缺失时不崩、降级,端口照常起。
|
|
412
|
+
|
|
413
|
+
### 审计方法(五层,可复跑)
|
|
414
|
+
|
|
415
|
+
| 层 | 判据 | 命令 |
|
|
416
|
+
|---|---|---|
|
|
417
|
+
| 1 包级 | package.json 五个依赖字段 | `node test/deps.mjs` |
|
|
418
|
+
| 2 模块级 | 每个 import 的说明符 | 同上 |
|
|
419
|
+
| 3 宿主要求 | `ctx.get()` 拉了哪些服务、有无兜底 | 同上 |
|
|
420
|
+
| 4 实体依赖 | 有没有引用别的插件包名/数据目录 | 同上 |
|
|
421
|
+
| 5 运行态 | **装进只有宿主的空环境,能不能活** | `node test/isolation.mjs` |
|
|
422
|
+
|
|
423
|
+
第 5 层是唯一有决定性的。前四层只能证明「没写别人的包名」,
|
|
424
|
+
说不了「别人的服务不在时它会不会崩」。
|
|
425
|
+
|
|
426
|
+
### 第 1 层:包级依赖 = 0
|
|
427
|
+
|
|
428
|
+
```
|
|
429
|
+
dependencies : 无
|
|
430
|
+
devDependencies : 无
|
|
431
|
+
peerDependencies : 无
|
|
432
|
+
optionalDependencies : 无
|
|
433
|
+
bundledDependencies : 无
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
### 第 2 层:模块级依赖
|
|
437
|
+
|
|
438
|
+
```
|
|
439
|
+
静态 import 94 · 动态 import 4 · require 0
|
|
440
|
+
node: 内置模块 (10) : child_process, crypto, fs, fs/promises, http, net, os, path, stream, util
|
|
441
|
+
包内相对路径 (35) : 全部指向本包 — 已验证文件均存在
|
|
442
|
+
外部包 (0) : 无
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
94 + 4 = 98 个 import,**零外部包**。
|
|
446
|
+
|
|
447
|
+
> 扫描器有两处坑,都是实测撞出来的,写在 `test/_scan.mjs` 里:
|
|
448
|
+
> 1. 不能把字符串内容整段抹掉——`import x from './a.js'` 的说明符本身就是字符串,抹了全部 import 都消失;
|
|
449
|
+
> 2. 不能忽略 CRLF——行注释的终止判断被 `\r` 影响会让块注释起止错位,
|
|
450
|
+
> 于是注释里写着的 `import('/x')` 被误报成真依赖。最终实现是**逐行状态机**。
|
|
451
|
+
|
|
452
|
+
### 第 3 层:宿主要求(这些是宿主自己的,不是别的插件)
|
|
453
|
+
|
|
454
|
+
| 服务 | 用途 | 缺失时 |
|
|
455
|
+
|---|---|---|
|
|
456
|
+
| `webServer` | 挂 `/multimodal/*` 面板路由 | **不挂**,其它照常 |
|
|
457
|
+
| `tools` | 注册 `multimodal_node` 工具 | **不注册**,其它照常 |
|
|
458
|
+
| `llm` | 自然语言转提示词 | getter 延迟读取,降级到词典检索 |
|
|
459
|
+
| `agentDefaultModel` | 同上,取默认模型路由 | 同上 |
|
|
460
|
+
| `locale` | 中英文案 | 退回内置中文 |
|
|
461
|
+
|
|
462
|
+
四个都是 `if (xxx !== undefined)` 守卫,`llm` 更是做成 getter——注释里写着
|
|
463
|
+
「没挂 llm 的宿主不能崩」。
|
|
464
|
+
|
|
465
|
+
### 第 4 层:实体依赖 = 0
|
|
466
|
+
|
|
467
|
+
```
|
|
468
|
+
引用其他插件包名 : 无
|
|
469
|
+
引用其他插件数据目录 : 无
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
数据目录只有自己的 `~/.dsh/data/dsh-multimodal-node/`。
|
|
473
|
+
(03 号插件的目录只在迁移时读过一次,代码里不引用。)
|
|
474
|
+
|
|
475
|
+
客户端 `client.js` 只 `require('react')`——由宿主的 module loader 提供,不是 npm 依赖。
|
|
476
|
+
|
|
477
|
+
### 第 5 层:运行态(决定性证据)
|
|
478
|
+
|
|
479
|
+
`test/isolation.mjs`:四档宿主,每档用自己的随机端口,各跑「起 → 验 → 关 → 再起」。
|
|
480
|
+
|
|
481
|
+
| 档 | webServer | tools | llm | locale | 面板路由 | 工具 | 端口 | 热重载 | 抛错 |
|
|
482
|
+
|---|---|---|---|---|---|---|---|---|---|
|
|
483
|
+
| A 完整 | ✓ | ✓ | ✓ | ✓ | 14 | 1 | 起 | 能再起 | 否 |
|
|
484
|
+
| B 没有 tools/llm | ✓ | ✗ | ✗ | ✓ | 14 | 0 | 起 | 能再起 | 否 |
|
|
485
|
+
| C 没有 webServer | ✗ | ✓ | ✗ | ✓ | 0 | 1 | 起 | 能再起 | 否 |
|
|
486
|
+
| D **空宿主** | ✗ | ✗ | ✗ | ✗ | 0 | 0 | 起 | 能再起 | 否 |
|
|
487
|
+
|
|
488
|
+
**36/36 pass。** 关键行是 D 档:`ctx.get` 一律返回 `undefined`,
|
|
489
|
+
`apply()` 不抛、API 端口起得来、`/node/ping` 回 200、卸载后端口关得掉、再 apply 又能起。
|
|
490
|
+
|
|
491
|
+
> 「关得掉再起得来」是热重载契约。这一条在本轮抓出了一个**真实缺陷**:
|
|
492
|
+
> `server.close()` 只停止接受新连接,不会断开已存在的 keep-alive 连接,
|
|
493
|
+
> 于是回调迟迟不来、端口一直占着,紧接着的重新 listen 撞 EADDRINUSE。
|
|
494
|
+
> 修法见 `lib/http.js` 的 `closeServer()`:close 之后调 `closeAllConnections()`。
|
|
495
|
+
|
|
496
|
+
### 综合判断
|
|
497
|
+
|
|
498
|
+
| 问题 | 答案 |
|
|
499
|
+
|---|---|
|
|
500
|
+
| 需要先装别的插件吗? | **不需要。** 空宿主都能跑 |
|
|
501
|
+
| 需要 ComfyUI 才能启动吗? | **不需要。** 只是没有实例可派发 |
|
|
502
|
+
| 需要 Blender 吗? | **不需要。** Blender 板块可关闭 |
|
|
503
|
+
| 需要知识库插件吗? | **不需要。** 提示词助手降级 |
|
|
504
|
+
| 需要 `dsh-comfyui` 吗? | **不需要。** 两者无任何引用关系 |
|
|
505
|
+
| 需要改宿主源码吗? | **不需要。** 只用公开的 `ctx.get` / `ctx.effect` / `slots` |
|