dsh-comfyui 0.5.1 → 0.5.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.en.md +2 -2
- package/README.md +2 -2
- package/client/client.js +120 -22
- package/client/client.js.map +1 -1
- package/lib/analyze.js +24 -2
- package/lib/comfyui.d.ts +3 -0
- package/lib/comfyui.js +60 -5
- package/lib/config.d.ts +47 -31
- package/lib/config.js +59 -12
- package/lib/convert.d.ts +12 -0
- package/lib/convert.js +76 -10
- package/lib/index.d.ts +1 -1
- package/lib/index.js +43 -32
- package/lib/routes.js +14 -2
- package/lib/tools.js +20 -6
- package/package.json +3 -3
package/lib/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { homedir } from 'node:os';
|
|
2
2
|
import { isAbsolute, join } from 'node:path';
|
|
3
|
-
import { Config } from './config.js';
|
|
3
|
+
import { Config, resolveConfig } from './config.js';
|
|
4
4
|
import { ComfyUIClient, CLIENT_ID } from './comfyui.js';
|
|
5
5
|
import { ComfyUIStore } from './store.js';
|
|
6
6
|
import { QueueTracker } from './queue.js';
|
|
7
|
-
import { convertGraphToApi } from './convert.js';
|
|
7
|
+
import { convertGraphToApi, flattenDynamicCombos } from './convert.js';
|
|
8
8
|
import { analyzeGraph } from './analyze.js';
|
|
9
9
|
import { ProgressTracker } from './progress.js';
|
|
10
10
|
import { COMFYUI_SKILL } from './skill.js';
|
|
@@ -66,23 +66,10 @@ function defaultDataDir() {
|
|
|
66
66
|
* (defaults applied), then hands the resolved object to apply.
|
|
67
67
|
*/
|
|
68
68
|
export async function apply(ctx, entryConfig) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
timeoutMs: entryConfig.timeoutMs ?? 900_000,
|
|
74
|
-
pollIntervalMs: entryConfig.pollIntervalMs ?? 1_000,
|
|
75
|
-
maxMediaItems: entryConfig.maxMediaItems ?? 12,
|
|
76
|
-
maxMediaBytes: entryConfig.maxMediaBytes ?? 64 * 1024 * 1024,
|
|
77
|
-
dataDir: entryConfig.dataDir !== undefined && entryConfig.dataDir !== '' ? entryConfig.dataDir : defaultDataDir(),
|
|
78
|
-
maxAssets: entryConfig.maxAssets ?? 200,
|
|
79
|
-
skillsDir: entryConfig.skillsDir ?? '',
|
|
80
|
-
mediaHost: entryConfig.mediaHost ?? '',
|
|
81
|
-
outputDir: entryConfig.outputDir ?? '',
|
|
82
|
-
comfyuiDirs: Array.isArray(entryConfig.comfyuiDirs)
|
|
83
|
-
? entryConfig.comfyuiDirs.filter((dir) => typeof dir === 'string' && dir.trim() !== '')
|
|
84
|
-
: [],
|
|
85
|
-
};
|
|
69
|
+
// Volatile fields arrive as references on dsh 0.1.7+ (plain values on older
|
|
70
|
+
// hosts); `resolved` is the one plain object every consumer reads, refreshed
|
|
71
|
+
// in place when the Loader commits a settings-page change.
|
|
72
|
+
const resolved = resolveConfig(entryConfig, defaultDataDir());
|
|
86
73
|
const store = new ComfyUIStore(resolved.dataDir, resolved.maxAssets);
|
|
87
74
|
await store.init();
|
|
88
75
|
// Per-workflow skill packs default to `<dataDir>/skills`; `skillsDir` moves
|
|
@@ -113,11 +100,27 @@ export async function apply(ctx, entryConfig) {
|
|
|
113
100
|
// only broadcasts them to the submitting client. Node's global WebSocket
|
|
114
101
|
// (undici) cannot set auth headers, so a remote server behind an
|
|
115
102
|
// authenticating proxy simply shows no progress.
|
|
103
|
+
const progressUrl = () => resolved.baseUrl.replace(/^http:/, 'ws:').replace(/^https:/, 'wss:').replace(/\/$/, '') + `/ws?clientId=${CLIENT_ID}`;
|
|
104
|
+
let attachedUrl = progressUrl();
|
|
116
105
|
ctx.effect(() => {
|
|
117
|
-
|
|
118
|
-
progress.attach(wsUrl);
|
|
106
|
+
progress.attach(attachedUrl);
|
|
119
107
|
return () => progress.dispose();
|
|
120
108
|
}, 'dsh-comfyui: progress');
|
|
109
|
+
/** Pick up a new config in place; a new baseUrl re-points the progress socket. */
|
|
110
|
+
const refreshConfig = (next) => {
|
|
111
|
+
Object.assign(resolved, next);
|
|
112
|
+
const url = progressUrl();
|
|
113
|
+
if (url !== attachedUrl) {
|
|
114
|
+
attachedUrl = url;
|
|
115
|
+
progress.dispose();
|
|
116
|
+
progress.attach(url);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
// dsh 0.1.7+: a settings-page save commits volatile values into the same
|
|
120
|
+
// references without restarting the plugin, then notifies this fiber.
|
|
121
|
+
ctx.on('loader/volatile-update', (() => {
|
|
122
|
+
refreshConfig(resolveConfig(entryConfig, defaultDataDir()));
|
|
123
|
+
}));
|
|
121
124
|
const hostHint = createHostHint();
|
|
122
125
|
const runtime = {
|
|
123
126
|
getConfig: () => resolved,
|
|
@@ -164,7 +167,8 @@ export async function apply(ctx, entryConfig) {
|
|
|
164
167
|
},
|
|
165
168
|
queue: async (workflow, meta) => {
|
|
166
169
|
const client = runtime.createClient(await resolveApiKey(ctx, resolved.apiKeyEnv));
|
|
167
|
-
|
|
170
|
+
// Heals library entries saved with the pre-0.5.2 wrapped DynamicCombo shape (Issue #10).
|
|
171
|
+
let prompt = flattenDynamicCombos(workflow);
|
|
168
172
|
if (meta.parameters !== undefined && meta.parameters.length > 0) {
|
|
169
173
|
const objectInfo = await objectInfoCached(client);
|
|
170
174
|
const slots = await store.loadSlots();
|
|
@@ -322,18 +326,25 @@ export async function apply(ctx, entryConfig) {
|
|
|
322
326
|
let source = () => resolved;
|
|
323
327
|
ctx.inject(['settings'], (settingsCtx) => {
|
|
324
328
|
const settings = settingsCtx.settings;
|
|
325
|
-
if (typeof settings.installSection
|
|
326
|
-
|
|
329
|
+
if (typeof settings.installSection === 'function') {
|
|
330
|
+
// dsh 0.1.2–0.1.5: the section registry keeps its own copy of the values.
|
|
331
|
+
settings.installSection(ctx, COMFYUI_NS, Config, resolved, {
|
|
332
|
+
setSource: (current) => {
|
|
333
|
+
source = current;
|
|
334
|
+
},
|
|
335
|
+
onChange: () => {
|
|
336
|
+
refreshConfig(resolveConfig(source(), defaultDataDir()));
|
|
337
|
+
},
|
|
338
|
+
});
|
|
327
339
|
return;
|
|
328
340
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
});
|
|
341
|
+
// dsh 0.1.7+: forms are projected from the volatile Config fields and a
|
|
342
|
+
// save arrives through loader/volatile-update above. This plugin ships its
|
|
343
|
+
// own settings page, so opt out of any auto-generated one.
|
|
344
|
+
if (typeof settings.configure === 'function') {
|
|
345
|
+
const configure = settings.configure.bind(settings);
|
|
346
|
+
settingsCtx.effect(() => configure({ auto: false }, ctx.fiber), 'dsh-comfyui: settings page policy');
|
|
347
|
+
}
|
|
337
348
|
});
|
|
338
349
|
ctx.effect(() => {
|
|
339
350
|
const disposers = registerComfyUITools(ctx, runtime);
|
package/lib/routes.js
CHANGED
|
@@ -250,7 +250,14 @@ export function mountComfyUIRoutes(ctx, runtime) {
|
|
|
250
250
|
});
|
|
251
251
|
}
|
|
252
252
|
catch (error) {
|
|
253
|
-
|
|
253
|
+
// Name the probed address: a save that never persisted leaves the
|
|
254
|
+
// default 127.0.0.1:8188 in force, and a bare "fetch failed" reads
|
|
255
|
+
// like a network problem with the address the user typed (Issue #11).
|
|
256
|
+
sendJson(response, 200, {
|
|
257
|
+
ok: false,
|
|
258
|
+
error: `${errorMessage(error)}(探测地址 ${runtime.getConfig().baseUrl})`,
|
|
259
|
+
latencyMs: Date.now() - startedAt,
|
|
260
|
+
});
|
|
254
261
|
}
|
|
255
262
|
}),
|
|
256
263
|
}));
|
|
@@ -1428,7 +1435,12 @@ export function mountComfyUIRoutes(ctx, runtime) {
|
|
|
1428
1435
|
const client = runtime.createClient(await runtime.getApiKey());
|
|
1429
1436
|
const entry = await client.getHistory(promptId);
|
|
1430
1437
|
if (entry === undefined) {
|
|
1431
|
-
|
|
1438
|
+
// No history entry: still waiting in ComfyUI's queue, or evicted.
|
|
1439
|
+
// Tell the two apart so the background card keeps polling a long
|
|
1440
|
+
// queue instead of timing it out as "gone".
|
|
1441
|
+
const queue = await client.getQueue();
|
|
1442
|
+
const waiting = [...queue.queue_running, ...queue.queue_pending].some((item) => item.prompt_id === promptId);
|
|
1443
|
+
sendJson(response, 200, { ok: true, status: waiting ? 'queued' : 'unknown' });
|
|
1432
1444
|
return;
|
|
1433
1445
|
}
|
|
1434
1446
|
const statusStr = entry.status?.status_str;
|
package/lib/tools.js
CHANGED
|
@@ -263,7 +263,7 @@ function summarizeFields(fields, required) {
|
|
|
263
263
|
else if (typeof typeOrList === 'string') {
|
|
264
264
|
entry.type = typeOrList;
|
|
265
265
|
}
|
|
266
|
-
if (optionsRecord !== undefined &&
|
|
266
|
+
if (optionsRecord !== undefined && optionsRecord.default !== undefined) {
|
|
267
267
|
entry.default = optionsRecord.default;
|
|
268
268
|
}
|
|
269
269
|
out.push(entry);
|
|
@@ -307,16 +307,23 @@ function objectInfoDefinition(runtime) {
|
|
|
307
307
|
: entries.filter(([name]) => name.toLowerCase().includes(filter));
|
|
308
308
|
const nodes = filtered.slice(0, 60).map(([classType, def]) => ({
|
|
309
309
|
class_type: classType,
|
|
310
|
-
|
|
310
|
+
// Omitted rather than assigned undefined: a key holding undefined makes the
|
|
311
|
+
// whole result fail DSH's lossless-JSON validation. The render type already
|
|
312
|
+
// declares display_name optional.
|
|
313
|
+
...(def.display_name !== undefined ? { display_name: def.display_name } : {}),
|
|
311
314
|
description: (def.description ?? '').slice(0, 200),
|
|
312
315
|
required: summarizeFields(def.input?.required, true),
|
|
313
316
|
optional: summarizeFields(def.input?.optional, false),
|
|
314
317
|
}));
|
|
318
|
+
const hint = filtered.length > nodes.length
|
|
319
|
+
? `filter matched ${filtered.length} nodes, showing first ${nodes.length} — narrow the filter for more`
|
|
320
|
+
: undefined;
|
|
315
321
|
return {
|
|
316
322
|
total: entries.length,
|
|
317
323
|
shown: nodes.length,
|
|
318
324
|
filter: filter ?? null,
|
|
319
|
-
|
|
325
|
+
// Omitted rather than assigned undefined, same reason as display_name.
|
|
326
|
+
...(hint !== undefined ? { hint } : {}),
|
|
320
327
|
nodes,
|
|
321
328
|
};
|
|
322
329
|
},
|
|
@@ -409,12 +416,13 @@ function workflowDefinition(runtime, ctx) {
|
|
|
409
416
|
lines.push(` 技能包${skill.required ? '(运行前必读)' : ''}: ${skill.summary}${extra} — 运行前先 action: skill { id: "${workflow.id}" }`);
|
|
410
417
|
}
|
|
411
418
|
for (const param of workflow.parameters ?? []) {
|
|
412
|
-
const
|
|
419
|
+
const label = param.label ?? param.name;
|
|
420
|
+
const def = param.default === undefined ? '' : `,默认 ${typeof param.default === 'string' ? `"${param.default}"` : String(param.default)}`;
|
|
413
421
|
const options = Array.isArray(param.options) && param.options.length > 0 ? `,可选: ${param.options.join(' / ')}` : '';
|
|
414
422
|
const upload = param.upload !== undefined
|
|
415
423
|
? `,上传类型: ${param.upload}${param.upload === 'media' ? `(${param.subfolder ?? ''}/,空值=移除该参考位)` : ''}`
|
|
416
424
|
: '';
|
|
417
|
-
lines.push(` ${param.name}(${
|
|
425
|
+
lines.push(` ${param.name}(${label}${param.random === true ? ',随机' : ''}${def}${options}${upload})`);
|
|
418
426
|
}
|
|
419
427
|
}
|
|
420
428
|
const loadArea = data.loadArea;
|
|
@@ -506,7 +514,13 @@ function workflowDefinition(runtime, ctx) {
|
|
|
506
514
|
parameters: (parameters ?? []).map(({ name: pname, label, type, default: def, random, numberKind, options, upload }) => {
|
|
507
515
|
// DSH validates tool output as lossless JSON: JSON.stringify drops
|
|
508
516
|
// undefined keys, so omit optional fields instead of passing undefined.
|
|
509
|
-
const entry = { name: pname
|
|
517
|
+
const entry = { name: pname };
|
|
518
|
+
if (label !== undefined)
|
|
519
|
+
entry.label = label;
|
|
520
|
+
if (type !== undefined)
|
|
521
|
+
entry.type = type;
|
|
522
|
+
if (def !== undefined)
|
|
523
|
+
entry.default = def;
|
|
510
524
|
if (random !== undefined)
|
|
511
525
|
entry.random = random;
|
|
512
526
|
// Tells the model whether decimals are accepted; absent means the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-comfyui",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"description": "Let the DeepSeek Harness agent smartly drive a local or remote ComfyUI to generate anything, with workflow and asset management panels, per-workflow skill packs, a companion skill and a same-origin media proxy. / 让 DeepSeek Harness 的 Agent 智能驱动本地或远程 ComfyUI 生成任何内容。附带工作流、资产管理面板与技能包管理挂载。配套 skill 与同源媒体代理。",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -49,12 +49,12 @@
|
|
|
49
49
|
"node": ">=22.19"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@deepseek-ai/schemastery": "^3.18.
|
|
52
|
+
"@deepseek-ai/schemastery": "^3.18.4",
|
|
53
53
|
"fflate": "^0.8.3"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
56
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
57
|
-
"@deepseek-ai/dsh-settings": "^0.1.2-alpha.4"
|
|
57
|
+
"@deepseek-ai/dsh-settings": "^0.1.2-alpha.4 || ^0.1.5-alpha.1 || ^0.1.6-alpha.1 || ^0.1.7-alpha.1"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@deepseek-ai/cordis": "^4.0.1",
|