dsh-comfyui-canvas 0.1.1 → 0.1.3
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 +20 -8
- package/README.zh.md +16 -6
- package/docs/PLAN-v0.1.2.md +23 -4
- package/docs/PLAN-v0.1.3.md +101 -0
- package/lib/client.js +120 -10
- package/lib/index.js +633 -40
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> [中文](README.zh.md) · English
|
|
4
4
|
|
|
5
5
|
[](LICENSE)
|
|
6
|
-
[](https://github.com/wbin0001/dsh-comfyui-canvas/releases)
|
|
7
7
|
[](https://github.com/wbin0001/dsh-comfyui-canvas)
|
|
8
8
|
[](https://github.com/DeepSeek-Harness/DSH)
|
|
9
9
|
[](https://github.com/comfyanonymous/ComfyUI)
|
|
@@ -19,6 +19,8 @@ Embed your **ComfyUI** (local or cloud) as a split-screen canvas tab inside [Dee
|
|
|
19
19
|
|
|
20
20
|
This package is the DSH-side plugin, and it ships the ComfyUI-side bridge node too. For headless/scale workloads it can be paired with the official ComfyUI MCP server — see [Canvas vs MCP](#canvas-vs-mcp--two-ways-to-drive-comfyui).
|
|
21
21
|
|
|
22
|
+
> **Where this plugin fits**: use it while **building / tuning a workflow on the live canvas** (the "IDE" role). For **unattended / batch / production runs**, hand the exported workflow to **Comfy CLI** (`comfy-cli run_workflow`) — it runs headlessly without a browser, which this canvas plugin deliberately does not do (the agent drives the canvas you are looking at; a closed browser means no runner). Export a workflow once with `comfyui_export_api`, then script it with the CLI at scale.
|
|
23
|
+
|
|
22
24
|
## What you get
|
|
23
25
|
|
|
24
26
|
| Surface | Description |
|
|
@@ -26,10 +28,13 @@ This package is the DSH-side plugin, and it ships the ComfyUI-side bridge node t
|
|
|
26
28
|
| **ComfyUI canvas tab** | A `ComfyUI` conversation view that embeds the ComfyUI frontend (local or cloud) side by side with the Chat rail. The iframe stays alive across tab switches (no reload). |
|
|
27
29
|
| **Visual canvas copilot** | The agent operates **the canvas you are looking at** — nodes appear, links wire, widgets change and runs trigger live on screen, so you watch every step instead of trusting an opaque JSON edit. Output images come back into the chat via `comfyui_get_outputs`. |
|
|
28
30
|
| **Canvas ops tools** | `comfyui_read_workflow`, `add_node`, `connect`, `set_param`, `remove_node`, `inject_text`, `load_workflow`, `run`, `debug` — build and fix workflows on the live canvas; `inject_text` writes conversation text straight into a node or a new wirable source. |
|
|
29
|
-
| **Production tools** | `comfyui_batch_run` sweeps a parameter matrix (seeds/prompts/strengths) in one go; `comfyui_get_outputs` pulls the resulting
|
|
30
|
-
| **
|
|
31
|
+
| **Production tools** | `comfyui_batch_run` sweeps a parameter matrix (seeds/prompts/strengths) in one go; `comfyui_get_outputs` pulls the resulting files back into the chat — images, videos, gifs, and audio — with optional `outputStem` auto-incrementing names (`stem.01.png`, never overwrites); `comfyui_attach_file` uploads any local file (image/audio/video/3D/text) into ComfyUI's input/ for the matching Load node; `comfyui_export_api` exports the live canvas as API-format workflow JSON for comfy-cli headless batch runs. |
|
|
32
|
+
| **Projects & traceability** | Downloads default to the project directory (Settings → 项目目录, default `<workspace>/projects`); every downloaded run appends `runs.json` (promptId / overrides / timestamp / files) so any output can be traced back to its parameters. Failed runs return a structured `executionError` (node id / node type / exception / message) instead of a raw JSON wall. |
|
|
33
|
+
| **Skills (SOPs)** | Built-in skills teach the agent the right order of operations: `comfyui-canvas-ops` (read → confirm → edit → run → fetch → self-check), `comfyui-admin-ops` (configure/launch/upgrade/node management), `comfyui-video-audio-ops` (video + voiceover/audio track), and `comfyui-dev-ops` (develop/debug custom nodes). Install the plugin and the skills ship with it — no extra setup. |
|
|
34
|
+
| **Upkeep tool** | `comfyui_upgrade` one-click updates the ComfyUI core and every git-backed custom node (concurrent, dirty-safe); `comfyui_config` reports the active connection, canvas focus, project directory, and a bridge-auth handshake check (`bridgeAuthEffective`). |
|
|
35
|
+
| **Node dev tools** | `comfyui_read_source` / `comfyui_edit_source` / `comfyui_reload` — read and edit custom-node source under custom_nodes/ and restart ComfyUI from the conversation, then verify on the canvas. |
|
|
31
36
|
| **Canvas focus mode** | The agent can tell (via `comfyui_config`) whether the browser is on the canvas tab for the current session, and focus on canvas work only then. Session-isolated. |
|
|
32
|
-
| **Settings page** | ComfyUI base URL / port / network mode / bridge token / launch command / rail width. Changes apply live. Customized nav icon with ComfyUI logo. |
|
|
37
|
+
| **Settings page** | ComfyUI base URL / port / network mode / bridge token / launch command / project directory / rail width. Changes apply live. Customized nav icon with ComfyUI logo. |
|
|
33
38
|
| **Rail polish** | Image previews inside the input box, a `+` button to attach local images (DSH's official attachment path), approval popup over the canvas (split layout), send button pinned to the panel corner. |
|
|
34
39
|
|
|
35
40
|
## Install
|
|
@@ -37,10 +42,16 @@ This package is the DSH-side plugin, and it ships the ComfyUI-side bridge node t
|
|
|
37
42
|
### 1. Install the DSH plugin
|
|
38
43
|
|
|
39
44
|
```bash
|
|
40
|
-
dsh plugin add
|
|
45
|
+
dsh plugin add dsh-comfyui-canvas
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Or from a GitHub checkout:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
dsh plugin add github:wbin0001/dsh-comfyui-canvas
|
|
41
52
|
```
|
|
42
53
|
|
|
43
|
-
|
|
54
|
+
Or from a local checkout:
|
|
44
55
|
|
|
45
56
|
```bash
|
|
46
57
|
# in your DSH profile
|
|
@@ -166,7 +177,7 @@ dsh-comfyui-canvas/
|
|
|
166
177
|
│ ├── __init__.py # /dsh-bridge/* HTTP routes on the ComfyUI server
|
|
167
178
|
│ └── entry/bridge.js # injected frontend: reports graph + runs commands
|
|
168
179
|
├── lib/
|
|
169
|
-
│ ├── index.js # DSH host:
|
|
180
|
+
│ ├── index.js # DSH host: 19 canvas tools + 4 built-in skills
|
|
170
181
|
│ └── client.js # DSH web: canvas tab / settings / rail polish
|
|
171
182
|
├── LICENSE
|
|
172
183
|
├── README.md
|
|
@@ -177,7 +188,8 @@ The **bridge** is the only ComfyUI-side dependency. It exposes `/dsh-bridge/work
|
|
|
177
188
|
|
|
178
189
|
## Known issues
|
|
179
190
|
|
|
180
|
-
|
|
191
|
+
- `comfyui_reload` currently supports **Windows only** (uses `netstat`/`taskkill`); on macOS/Linux it fails loud instead of pretending, restart ComfyUI manually there.
|
|
192
|
+
- The former "node previews missing after a run" is fixed in v0.1.1: removed the iframe's `referrerpolicy="no-referrer"` to match the native tab environment, and `bridge.js` now listens to ComfyUI's `executed` event to force a canvas redraw.
|
|
181
193
|
|
|
182
194
|
## License
|
|
183
195
|
|
package/README.zh.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> 中文 · [English](README.md)
|
|
4
4
|
|
|
5
5
|
[](LICENSE)
|
|
6
|
-
[](https://github.com/wbin0001/dsh-comfyui-canvas/releases)
|
|
7
7
|
[](https://github.com/wbin0001/dsh-comfyui-canvas)
|
|
8
8
|
[](https://github.com/DeepSeek-Harness/DSH)
|
|
9
9
|
[](https://github.com/comfyanonymous/ComfyUI)
|
|
@@ -28,11 +28,14 @@
|
|
|
28
28
|
| **画布标签页** | 对话里新增 `ComfyUI` 标签,左边画布、右边对话 rail 分屏。iframe 常驻不重载,切标签秒回。 |
|
|
29
29
|
| **可视化画布副驾** | agent 操作**你正在看的画布**——节点出现、连线接上、参数变化、运行触发,全部实时显示在屏幕上,每一步都看得见,而不是黑盒改 JSON。出图经 `comfyui_get_outputs` 直接带回对话。 |
|
|
30
30
|
| **画布操作工具** | `comfyui_read_workflow` / `add_node` / `connect` / `set_param` / `remove_node` / `inject_text` / `load_workflow` / `run` / `debug`——在活画布上搭建与修复工作流;`inject_text` 把对话文本一步注入为可连线节点。 |
|
|
31
|
-
| **生产工具** | `comfyui_batch_run` 一次扫参数矩阵(seed / prompt / 强度);`comfyui_get_outputs`
|
|
32
|
-
|
|
|
31
|
+
| **生产工具** | `comfyui_batch_run` 一次扫参数矩阵(seed / prompt / 强度);`comfyui_get_outputs` 把产物直接带回对话——图像、视频、GIF、音频都支持,可传 `outputStem` 自动编号(`stem.01.png`,永不覆盖);`comfyui_attach_file` 把本机任意文件(图片/音频/视频/3D/**文本**)上传进 ComfyUI `input/` 供对应 Load 节点使用;`comfyui_export_api` 把当前画布导出为 API 格式工作流,供 comfy-cli 无人值守批量。 |
|
|
32
|
+
| **项目目录与溯源** | 下载默认落「项目目录」(设置页可改,默认 `<工作区>/projects`);每次下载把 `runs.json`(promptId / overrides / 时间戳 / 文件)追加进项目目录,任意一张图都能追溯回它的生成参数。运行失败返回结构化 `executionError`(节点 id / 节点类型 / 异常类型 / 信息),不再是一堵 JSON 墙。 |
|
|
33
|
+
| **技能包(SOP)** | 内置技能教 agent 按正确顺序操作:`comfyui-canvas-ops`(读→确认→改→跑→取回→自检)、`comfyui-admin-ops`(配置/启动/升级/节点管理)、`comfyui-video-audio-ops`(视频+配音/音轨)、`comfyui-dev-ops`(开发/调试自定义节点)。装插件即自带技能,无需额外配置。 |
|
|
34
|
+
| **维护工具** | `comfyui_upgrade` 一键升级 ComfyUI 核心与全部 git 自定义节点(并发、跳过本地改过的仓库);`comfyui_config` 报告当前连接、画布专注状态、项目目录,以及**桥接鉴权握手检查**(`bridgeAuthEffective`)。 |
|
|
35
|
+
| **节点开发工具** | `comfyui_read_source` / `comfyui_edit_source` / `comfyui_reload`——对话里直接读写 `custom_nodes/` 下的节点源码并重启 ComfyUI,再到画布上验证。 |
|
|
33
36
|
| **画布专注模式(会话隔离)** | agent 通过 `comfyui_config` 感知当前会话是否在画布标签,只在画布场景专注画布操作,且**按会话隔离**——多个会话互不干扰。 |
|
|
34
37
|
| **ComfyUI 报错处理** | `debug` 校验工作流并高亮报错节点(纯校验,不触发执行),agent 帮你定位/修复画布错误。 |
|
|
35
|
-
| **设置页** | ComfyUI 地址 / 端口 / 网络模式 / 桥接 Token / 启动命令 / 右侧面板宽度,实时生效。导航栏已自定义为 ComfyUI logo 图标。 |
|
|
38
|
+
| **设置页** | ComfyUI 地址 / 端口 / 网络模式 / 桥接 Token / 启动命令 / 项目目录 / 右侧面板宽度,实时生效。导航栏已自定义为 ComfyUI logo 图标。 |
|
|
36
39
|
| **对话栏增强** | 图片预览并入输入框、`+` 号上传本地图片(走 DSH 官方附件通道)、画布上的授权弹窗、发送按钮贴右下角。 |
|
|
37
40
|
|
|
38
41
|
---
|
|
@@ -42,7 +45,13 @@
|
|
|
42
45
|
### 1. 安装 DSH 插件
|
|
43
46
|
|
|
44
47
|
```bash
|
|
45
|
-
dsh plugin add
|
|
48
|
+
dsh plugin add dsh-comfyui-canvas
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
或从 GitHub 安装:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
dsh plugin add github:wbin0001/dsh-comfyui-canvas
|
|
46
55
|
```
|
|
47
56
|
|
|
48
57
|
或本地安装:
|
|
@@ -201,7 +210,8 @@ dsh-comfyui-canvas/
|
|
|
201
210
|
|
|
202
211
|
## 已知问题
|
|
203
212
|
|
|
204
|
-
|
|
213
|
+
- `comfyui_reload` 目前**仅支持 Windows**(依赖 `netstat`/`taskkill`);macOS/Linux 上会明确报错而非假装成功,需手动重启 ComfyUI。
|
|
214
|
+
- 此前"画布运行后节点预览不显示"已在 v0.1.1 修复:移除 iframe 的 `referrerpolicy="no-referrer"` 消除环境差异,并在 `bridge.js` 监听 ComfyUI `executed` 事件强制重绘画布。
|
|
205
215
|
|
|
206
216
|
---
|
|
207
217
|
|
package/docs/PLAN-v0.1.2.md
CHANGED
|
@@ -249,10 +249,29 @@ step 12 发布(npm publish + awesome-dsh-plugin 收录信息若有变更)
|
|
|
249
249
|
### 设置导航图标(核心改动 §5.5)已落地
|
|
250
250
|
- 见 §5.5 完整记录;核心已还原、非侵入式图标生效、已推送
|
|
251
251
|
|
|
252
|
-
### npm publish
|
|
253
|
-
-
|
|
254
|
-
-
|
|
255
|
-
-
|
|
252
|
+
### npm publish 已完成(2026-09-02)
|
|
253
|
+
- ✅ `dsh-comfyui-canvas@0.1.1` 已发布到官方 registry(https://www.npmjs.com/package/dsh-comfyui-canvas)
|
|
254
|
+
- 包名未被占用;`files` 覆盖 LICENSE/README/lib/comfyui-bridge/screenshots.json/docs
|
|
255
|
+
- 安装命令已更新为 `dsh plugin add dsh-comfyui-canvas`(npm 方式)
|
|
256
|
+
- 发布 SOP:改版本号 → `npm config set registry https://registry.npmjs.org` → `npm publish`
|
|
257
|
+
- ⚠️ 注意:账户已开 2FA(安全密钥/Passkey);发布用 granular token(bypass 2FA,90 天有效,约 2026-12 到期)
|
|
258
|
+
|
|
259
|
+
### 通用文件传输 A1 + A2 已实现(2026-09-02,`a73ffd4`)
|
|
260
|
+
- ✅ `comfyui_attach_file`(host `lib/index.js`):任意文件(image/audio/video/3D)上传进 ComfyUI `input/`,`mediaTypeOf` 按扩展名推断,可选 `nodeId`+`widgetKey` 指向 Load 节点
|
|
261
|
+
- ✅ `comfyui_get_outputs` 泛化:扫描 `images/videos/gifs/audio` 四种 kind,每条带 `kind` + `/view` URL
|
|
262
|
+
- 已同步 node_modules 副本、已推送 GitHub
|
|
263
|
+
|
|
264
|
+
### 技能包 B 已实现(2026-09-02,`d1c3d07`)
|
|
265
|
+
- ✅ `ctx.skills.register` 内嵌三个 runtime 技能(随插件分发、零步骤):
|
|
266
|
+
- `comfyui-canvas-ops`(画布操作 SOP:读→确认→改→跑→取回→自检)
|
|
267
|
+
- `comfyui-admin-ops`(环境管理 SOP:配置/启动/升级/节点管理)
|
|
268
|
+
- `comfyui-video-audio-ops`(音画创作 SOP:视频+配音/音轨)
|
|
269
|
+
- `inject` 加 `skills`;技能内容中英双语、含安全约定
|
|
270
|
+
- 已同步 node_modules 副本、已推送 GitHub
|
|
271
|
+
|
|
272
|
+
### 剩余(见 §4 实施步骤)
|
|
273
|
+
- A3 画布会话框「+ 文件」通道(client 侧,硬前置:浏览器路径传递)
|
|
274
|
+
- C 节点开发调试(`read_source` / `edit_source` / `reload` + `comfyui-dev-ops` 技能)
|
|
256
275
|
|
|
257
276
|
---
|
|
258
277
|
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# dsh-comfyui-canvas v0.1.3 方案
|
|
2
|
+
|
|
3
|
+
> 目标:从「画布遥控器」进化为「可复现的创作工作台」——补齐运行溯源、输出不覆盖、项目目录三大能力(P0),加画布文件上传通道(A3,v0.1.2 搁置项)与失败结构化诊断(P1)。
|
|
4
|
+
> 依据:第三方评估报告(2026-09-02)的 6 项优化建议 + v0.1.2 收尾时搁置的 A3。
|
|
5
|
+
|
|
6
|
+
## 1. P0-1:运行溯源 `.runs.json`(~30 行)
|
|
7
|
+
|
|
8
|
+
**问题**:`comfyui_run` 返回 promptId 后,批量跑多个 seed/prompt 变体,无法追溯「哪张图对应什么参数」。
|
|
9
|
+
|
|
10
|
+
**方案**:host 端在每次 run/batch_run 时记录 `{ promptId, overrides, timestamp, status }` 到内存;`get_outputs` 下载时把记录 append 到 `downloadDir/<workflow_name>.runs.json`(无则新建)。
|
|
11
|
+
|
|
12
|
+
```json
|
|
13
|
+
[
|
|
14
|
+
{ "run": 1, "promptId": "abc123", "overrides": [{"nodeId":6,"key":"seed","value":42}],
|
|
15
|
+
"timestamp": "2026-09-02T22:00:00Z", "status": "success", "files": ["char_ref.01.png"] }
|
|
16
|
+
]
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**价值**:可复现是创作的命脉。agent 读 `.runs.json` 对比历史运行,用户追溯任意一张图的来源参数。
|
|
20
|
+
|
|
21
|
+
## 2. P0-2:输出不覆盖命名(~20 行)
|
|
22
|
+
|
|
23
|
+
**问题**:ComfyUI `output/` 用自带命名规则,批量跑后文件混乱、可能互相覆盖。
|
|
24
|
+
|
|
25
|
+
**方案**:`get_outputs` 下载时新增 `outputStem` 参数(可选)。扫描 `downloadDir` 已有 `<stem>.NN.<ext>` 文件,NN 自增不覆盖。
|
|
26
|
+
|
|
27
|
+
**价值**:批量跑 20 个变体后文件名直接反映顺序,永不覆盖。
|
|
28
|
+
|
|
29
|
+
## 3. P0-3:项目目录概念(~15 行)
|
|
30
|
+
|
|
31
|
+
**问题**:导出的 API JSON、下载的图片、溯源文件散落各处。
|
|
32
|
+
|
|
33
|
+
**方案**:ConfigSchema 新增 `projectDir` 字段。`get_outputs` 下载、`export_api` 导出、`.runs.json` 默认落在 `projectDir/<workflow_name>/`;未设时回退现有行为(不破坏兼容)。
|
|
34
|
+
|
|
35
|
+
**价值**:一个创作项目的所有资产(工作流/输出/溯源)聚在一起,agent 用文件工具管理整个项目。
|
|
36
|
+
|
|
37
|
+
## 4. A3:画布会话框「+ 文件」通道 —— 已砍(2026-09-03 决策)
|
|
38
|
+
|
|
39
|
+
**原内容**:画布会话框加「+ 文件」按钮,直接传本地文件进 ComfyUI `input/`。
|
|
40
|
+
|
|
41
|
+
**硬前置**:浏览器 `<input type="file">` 给的是 File 对象、不是绝对路径,而 `attach_file` 收 `path`——需先确认 client → host 传参通道(v0.1.2 方案开放问题 #1)。
|
|
42
|
+
|
|
43
|
+
**砍的原因**:A1(host 侧 `attach_file`)已让 agent 能传任意文件(含文本/提示词),A3 只是「手动点按钮直传」的锦上添花,浏览器路径传递是硬前置、收益边际低。**留 v0.2+ 或不做**。若有需要,降级方案:agent 引导用户给路径,无需 UI。
|
|
44
|
+
|
|
45
|
+
## 5. P1:运行失败结构化诊断(~40 行)
|
|
46
|
+
|
|
47
|
+
**问题**:`run` 失败只返回 HTTP 错误或 `node_errors`,无法定位具体节点异常。
|
|
48
|
+
|
|
49
|
+
**方案**:`run` 的 sendCommand 返回后,若 nodeErrors 非空,自动 GET `/history/{promptId}` 提取 `execution_error`,结构化返回:
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"promptId": "abc123", "success": false,
|
|
54
|
+
"nodeErrors": {"7": ["missing required input 'model'"]},
|
|
55
|
+
"executionError": { "nodeId": 7, "nodeType": "CheckpointLoaderSimple",
|
|
56
|
+
"exception": "FileNotFoundError", "message": "Checkpoint file not found: sd_xl_base.safetensors" }
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**价值**:agent 直接定位「节点 7 缺 checkpoint」并修复,不用看一堆 JSON。
|
|
61
|
+
|
|
62
|
+
## 6. 本版顺手修复
|
|
63
|
+
|
|
64
|
+
- ✅ `get_outputs` 无限超时(已修):history fetch 15s、文件下载 fetch 30s 超时(`AbortSignal.any([exec.signal, timeout])`)
|
|
65
|
+
- 桥接鉴权告警(设置页 + 画布激活,host 代理探测)——已实现(v0.1.2 后补,随 v0.1.3 发布)
|
|
66
|
+
- ✅ `attach_file` 文本/提示词上传(`d58d302`):mediaType 映射新增 txt/md/json/csv/yaml/yml/srt——提示词/批量提示词 JSON 等文本可直接进 ComfyUI `input/` 供文本类 Load 节点消费
|
|
67
|
+
|
|
68
|
+
### 文件上传支持矩阵(`attach_file`,2026-09-03)
|
|
69
|
+
|
|
70
|
+
| 类别 | 扩展名 | mediaType |
|
|
71
|
+
|---|---|---|
|
|
72
|
+
| 图片 | png / jpg / jpeg / webp / gif | image/* |
|
|
73
|
+
| 音频(含音乐) | wav / mp3 / ogg / flac / m4a / aac | audio/* |
|
|
74
|
+
| 视频 | mp4 / webm / mov / mkv / avi | video/* |
|
|
75
|
+
| 3D | glb / gltf / obj / fbx / stl | model/* |
|
|
76
|
+
| **文本/提示词** | txt / md / json / csv / yaml / yml / srt | text/plain、application/json、text/markdown、text/csv、application/yaml、application/x-subrip |
|
|
77
|
+
| 其他 | 任意 | application/octet-stream 透传 |
|
|
78
|
+
|
|
79
|
+
## 不做清单(范围控制)
|
|
80
|
+
|
|
81
|
+
- ❌ WebSocket 推送替代轮询(P2,改动大,收益中等,留 v0.2)
|
|
82
|
+
- ❌ 工作流链式组合(P1,单链封装 set_param+run,可用性待验证)
|
|
83
|
+
- ❌ src/ + 构建管线、CI、DOM 选择器集中管理(工程化,独立排期)
|
|
84
|
+
- ❌ `/dsh-bridge/report` 鉴权(localhost-only 已可接受,远期选项)
|
|
85
|
+
|
|
86
|
+
## 实施步骤
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
step 1 P0-1 `.runs.json`:host 记录 run/batch_run 元数据 → get_outputs 下载时 flush
|
|
90
|
+
step 2 P0-2 输出不覆盖命名:get_outputs 加 outputStem,NN 自增
|
|
91
|
+
step 3 P0-3 项目目录:ConfigSchema 加 projectDir → get_outputs/export_api 默认路径
|
|
92
|
+
step 4 P1 失败诊断:run 失败自动拉 /history/{pid} execution_error
|
|
93
|
+
step 5 A3 画布上传通道:探 client→host 传参通道;不可行则降级/砍
|
|
94
|
+
step 6 README 更新(P0 三项 + 新工具/参数说明)+ 版本号 0.1.2 → 0.1.3
|
|
95
|
+
step 7 npm run check → 同步三处 → 提交推送 → npm publish → GitHub Release
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## 参考
|
|
99
|
+
|
|
100
|
+
- 第三方评估报告(2026-09-02):6 项优化建议,P0 三项 ~65 行
|
|
101
|
+
- v0.1.2 方案 `docs/PLAN-v0.1.2.md` §4.5「剩余」:A3 移入 v0.1.3
|
package/lib/client.js
CHANGED
|
@@ -24,6 +24,9 @@ function canvasState() {
|
|
|
24
24
|
|
|
25
25
|
// 按会话更新 activeViewBySession 映射(读-改-写整张 map,settings 写入串行安全)。
|
|
26
26
|
// 只改自己 sessionId 的键,其它会话不受影响 → 画布模式感知是会话隔离的。
|
|
27
|
+
// 上限 MAX_SESSION_VIEWS:会话销毁时不会自动清理,若不封顶 map 会无限增长
|
|
28
|
+
// (WKB review 实测已 7 条)。写时裁剪到「当前会话 + 最近的 N-1 条」。
|
|
29
|
+
var MAX_SESSION_VIEWS = 50;
|
|
27
30
|
function setSessionView(sc, sessionId, mode) {
|
|
28
31
|
var snap = sc.getSnapshot ? sc.getSnapshot() : null;
|
|
29
32
|
var value = snap && snap.value ? snap.value : {};
|
|
@@ -33,6 +36,22 @@ function setSessionView(sc, sessionId, mode) {
|
|
|
33
36
|
for (var k in current) map[k] = current[k];
|
|
34
37
|
}
|
|
35
38
|
map[String(sessionId)] = mode;
|
|
39
|
+
var keys = Object.keys(map);
|
|
40
|
+
if (keys.length > MAX_SESSION_VIEWS) {
|
|
41
|
+
// Drop oldest keys (object string-key order = insertion order), always
|
|
42
|
+
// keeping the current session.
|
|
43
|
+
var keep = {};
|
|
44
|
+
var currentKey = String(sessionId);
|
|
45
|
+
var kept = 0;
|
|
46
|
+
for (var i = 0; i < keys.length; i++) {
|
|
47
|
+
var k = keys[i];
|
|
48
|
+
if (k === currentKey || kept < MAX_SESSION_VIEWS - 1) {
|
|
49
|
+
keep[k] = map[k];
|
|
50
|
+
kept++;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
map = keep;
|
|
54
|
+
}
|
|
36
55
|
sc.set("activeViewBySession", map).catch(function () {});
|
|
37
56
|
}
|
|
38
57
|
|
|
@@ -562,9 +581,10 @@ function ComfyUICanvasView(props) {
|
|
|
562
581
|
};
|
|
563
582
|
}, [sessionId]);
|
|
564
583
|
|
|
565
|
-
// ComfyUI
|
|
566
|
-
//
|
|
567
|
-
//
|
|
584
|
+
// ComfyUI 可达性检测:每隔几秒探一下。优先用正常 CORS 请求并检查 res.ok
|
|
585
|
+
// (能区分"在线"和"500 报错");若 DSH 页面 (127.0.0.1:3080) 跨域探测
|
|
586
|
+
// ComfyUI (127.0.0.1:8188) 被 CORS 拦截(抛 TypeError),回退到 mode:'no-cors'
|
|
587
|
+
// 的 opaque 可达性探测——服务可达即 resolve,不可达才 reject。
|
|
568
588
|
var onlineState = React.useState(null); // null=检测中, true=在线, false=离线
|
|
569
589
|
var online = onlineState[0];
|
|
570
590
|
var setOnline = onlineState[1];
|
|
@@ -574,17 +594,28 @@ function ComfyUICanvasView(props) {
|
|
|
574
594
|
// 同步顶栏状态圆点(初始检测中)。
|
|
575
595
|
updateCanvasStatus(null);
|
|
576
596
|
var probe = function () {
|
|
577
|
-
fetch(activeBase + "/system_stats", { method: "GET",
|
|
578
|
-
.then(function () {
|
|
597
|
+
fetch(activeBase + "/system_stats", { method: "GET", signal: AbortSignal.timeout(2500) })
|
|
598
|
+
.then(function (res) {
|
|
579
599
|
if (stopped) return;
|
|
580
|
-
setOnline(
|
|
581
|
-
setStarting(false);
|
|
582
|
-
updateCanvasStatus(
|
|
600
|
+
setOnline(res.ok); // 200-299 → 在线;4xx/5xx → 离线(服务在但报错)
|
|
601
|
+
setStarting(false);
|
|
602
|
+
updateCanvasStatus(res.ok);
|
|
583
603
|
})
|
|
584
604
|
.catch(function () {
|
|
585
605
|
if (stopped) return;
|
|
586
|
-
|
|
587
|
-
|
|
606
|
+
// CORS 拦截(TypeError)或网络错误 → 用 no-cors 回退判断端口是否可达
|
|
607
|
+
fetch(activeBase + "/system_stats", { method: "GET", mode: "no-cors", signal: AbortSignal.timeout(2500) })
|
|
608
|
+
.then(function () {
|
|
609
|
+
if (stopped) return;
|
|
610
|
+
setOnline(true);
|
|
611
|
+
setStarting(false);
|
|
612
|
+
updateCanvasStatus(true);
|
|
613
|
+
})
|
|
614
|
+
.catch(function () {
|
|
615
|
+
if (stopped) return;
|
|
616
|
+
setOnline(false);
|
|
617
|
+
updateCanvasStatus(false);
|
|
618
|
+
});
|
|
588
619
|
});
|
|
589
620
|
};
|
|
590
621
|
probe();
|
|
@@ -625,6 +656,34 @@ function ComfyUICanvasView(props) {
|
|
|
625
656
|
});
|
|
626
657
|
};
|
|
627
658
|
|
|
659
|
+
// 桥接鉴权握手(画布激活时按需触发,非启动路径):浏览器跨域读不到
|
|
660
|
+
// ComfyUI 状态码(CORS),所以向 host 请求一次代理探测——写
|
|
661
|
+
// probeRequested=true,host watcher 后台探测并把结果写回 bridgeProbe。
|
|
662
|
+
// 这里只负责发起 + 订阅展示,全程异步,不影响任何启动流程。
|
|
663
|
+
var probeState = React.useState(null); // {bridgeAuthEffective, bridgeAuthProbe, checkedAt}
|
|
664
|
+
var bridgeProbe = probeState[0];
|
|
665
|
+
var setBridgeProbe = probeState[1];
|
|
666
|
+
var tokenSetState = React.useState(false);
|
|
667
|
+
var tokenSet = tokenSetState[0];
|
|
668
|
+
var setTokenSet = tokenSetState[1];
|
|
669
|
+
React.useEffect(function () {
|
|
670
|
+
var sc = props.scope;
|
|
671
|
+
if (!sc) return;
|
|
672
|
+
if (typeof sc.set === "function") {
|
|
673
|
+
sc.set("probeRequested", true).catch(function () {});
|
|
674
|
+
}
|
|
675
|
+
if (typeof sc.subscribe !== "function" || typeof sc.getSnapshot !== "function") return;
|
|
676
|
+
var sync = function () {
|
|
677
|
+
var snap = sc.getSnapshot();
|
|
678
|
+
var v = snap && snap.value ? snap.value : {};
|
|
679
|
+
setBridgeProbe(v.bridgeProbe || null);
|
|
680
|
+
setTokenSet(!!(v.bridgeToken));
|
|
681
|
+
};
|
|
682
|
+
sync();
|
|
683
|
+
var unsub = sc.subscribe(sync);
|
|
684
|
+
return function () { if (typeof unsub === "function") unsub(); };
|
|
685
|
+
}, [sessionId]);
|
|
686
|
+
|
|
628
687
|
// 启动超时:host 用 launchCommand 拉起 ComfyUI 通常几十秒内完成;超过
|
|
629
688
|
// 45 秒仍未探测到在线就复位"正在启动…",允许用户重试,避免永久卡死。
|
|
630
689
|
React.useEffect(function () {
|
|
@@ -645,6 +704,20 @@ function ComfyUICanvasView(props) {
|
|
|
645
704
|
// 授权弹窗渲染在 host 内部(与控制条同级),absolute 定位锚定 host 的
|
|
646
705
|
// relative 边界 → 只盖画布视图区,不会挡住顶栏/右侧 rail。
|
|
647
706
|
var hostChildren = [control];
|
|
707
|
+
// 桥接鉴权告警(非启动路径,画布激活时按需展示):只有当 DSH 侧配了
|
|
708
|
+
// token、但桥接握手显示"错误 token 也能通过"时才亮红字——这是真正的
|
|
709
|
+
// 配置错位(ComfyUI 端没设 DSH_BRIDGE_TOKEN)。正常/未配 token 不打扰。
|
|
710
|
+
if (bridgeProbe && bridgeProbe.bridgeAuthEffective === false && tokenSet) {
|
|
711
|
+
hostChildren.push(React.createElement("div", {
|
|
712
|
+
style: {
|
|
713
|
+
position: "absolute", top: "8px", left: "50%", transform: "translateX(-50%)",
|
|
714
|
+
zIndex: 45, maxWidth: "86%",
|
|
715
|
+
background: "rgba(248,81,73,.12)", color: "#f85149",
|
|
716
|
+
border: "1px solid rgba(248,81,73,.4)", borderRadius: "6px",
|
|
717
|
+
padding: "6px 12px", fontSize: "12px", lineHeight: "1.5", textAlign: "center",
|
|
718
|
+
},
|
|
719
|
+
}, "⚠️ 桥接鉴权未生效:ComfyUI 端可能没设 DSH_BRIDGE_TOKEN。打开设置页 → ComfyUI 画布 查看详情。"));
|
|
720
|
+
}
|
|
648
721
|
if (approval) {
|
|
649
722
|
hostChildren.push(React.createElement(ComfyUIApprovalOverlay, { wait: approval }));
|
|
650
723
|
}
|
|
@@ -670,6 +743,7 @@ function ComfyUISettingsRow(props) {
|
|
|
670
743
|
var launchCommand = typeof value.launchCommand === "string" ? value.launchCommand : "";
|
|
671
744
|
var comfyuiDir = typeof value.comfyuiDir === "string" ? value.comfyuiDir : "";
|
|
672
745
|
var railWidth = value.railWidth != null ? Number(value.railWidth) : DEFAULT_RAIL;
|
|
746
|
+
var projectDir = typeof value.projectDir === "string" ? value.projectDir : "";
|
|
673
747
|
var writable = !snapshot || snapshot.writable !== false;
|
|
674
748
|
|
|
675
749
|
var inputStyle = {
|
|
@@ -687,6 +761,33 @@ function ComfyUISettingsRow(props) {
|
|
|
687
761
|
scope.set(field, next).catch(function () { /* recover handled by scope */ });
|
|
688
762
|
}
|
|
689
763
|
|
|
764
|
+
// 桥接鉴权握手(非启动路径):设置页打开 / token 变化时向 host 请求一次
|
|
765
|
+
// 探测(probeRequested=true → host 后台探测 → 结果写回 bridgeProbe)。
|
|
766
|
+
// host 代理是因为浏览器跨域读不到 ComfyUI 的状态码(CORS),而 host 端
|
|
767
|
+
// fetch 没有此限制。这里只负责"发起请求 + 展示结果",不阻塞任何启动流程。
|
|
768
|
+
// 输入 token 时防抖 600ms,避免每个字符触发一次探测。
|
|
769
|
+
React.useEffect(function () {
|
|
770
|
+
if (!bridgeToken) return;
|
|
771
|
+
var timer = setTimeout(function () {
|
|
772
|
+
if (scope && typeof scope.set === "function") {
|
|
773
|
+
scope.set("probeRequested", true).catch(function () {});
|
|
774
|
+
}
|
|
775
|
+
}, 600);
|
|
776
|
+
return function () { clearTimeout(timer); };
|
|
777
|
+
}, [bridgeToken]);
|
|
778
|
+
var probe = value.bridgeProbe || {};
|
|
779
|
+
var probeText = "";
|
|
780
|
+
var probeColor = "#9a9aa2";
|
|
781
|
+
if (probe.bridgeAuthEffective === true) {
|
|
782
|
+
probeText = "✅ 桥接鉴权已生效(错误 Token 会被拒绝)";
|
|
783
|
+
probeColor = "#3fb950";
|
|
784
|
+
} else if (probe.bridgeAuthEffective === false) {
|
|
785
|
+
probeText = bridgeToken
|
|
786
|
+
? "⚠️ 桥接鉴权未生效:ComfyUI 端可能没设 DSH_BRIDGE_TOKEN(检查启动器)"
|
|
787
|
+
: "ℹ️ 未配置桥接 Token —— 桥接不鉴权(ComfyUI 默认信任模型)";
|
|
788
|
+
probeColor = bridgeToken ? "#f85149" : "#d29922";
|
|
789
|
+
}
|
|
790
|
+
|
|
690
791
|
return React.createElement("div", { style: rowStyle },
|
|
691
792
|
React.createElement("div", { style: titleStyle }, "ComfyUI 画布"),
|
|
692
793
|
React.createElement("label", { style: labelStyle }, "ComfyUI 地址"),
|
|
@@ -730,12 +831,21 @@ function ComfyUISettingsRow(props) {
|
|
|
730
831
|
spellCheck: false, placeholder: "例如 C:\\path\\to\\ComfyUI",
|
|
731
832
|
onChange: function (e) { setField("comfyuiDir", e.target.value); },
|
|
732
833
|
}),
|
|
834
|
+
React.createElement("label", { style: labelStyle }, "项目目录(输出/导出/溯源默认落此;留空用 <工作区>/projects)"),
|
|
835
|
+
React.createElement("input", {
|
|
836
|
+
style: inputStyle, value: projectDir, disabled: !writable,
|
|
837
|
+
spellCheck: false, placeholder: "例如 D:\\AI-Projects(留空 = 工作区 projects/)",
|
|
838
|
+
onChange: function (e) { setField("projectDir", e.target.value); },
|
|
839
|
+
}),
|
|
733
840
|
React.createElement("label", { style: labelStyle }, "右侧面板宽度 (px)(画布宽度 = 总宽 − 右侧面板)"),
|
|
734
841
|
React.createElement("input", {
|
|
735
842
|
style: inputStyle, value: String(railWidth), disabled: !writable,
|
|
736
843
|
type: "number", min: 120, max: 1200, step: 10,
|
|
737
844
|
onChange: function (e) { setField("railWidth", Number(e.target.value)); },
|
|
738
845
|
}),
|
|
846
|
+
probeText ? React.createElement("div", {
|
|
847
|
+
style: { fontSize: "12px", color: probeColor, marginTop: "10px", lineHeight: "1.5" },
|
|
848
|
+
}, probeText) : null,
|
|
739
849
|
);
|
|
740
850
|
}
|
|
741
851
|
|