dsh-comfyui 0.3.0-beta.0
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/LICENSE +21 -0
- package/README.md +160 -0
- package/README.zh.md +160 -0
- package/client/client.js +3089 -0
- package/client/client.js.map +1 -0
- package/cordis.patch.yml +4 -0
- package/lib/analyze.d.ts +61 -0
- package/lib/analyze.js +96 -0
- package/lib/comfyui.d.ts +195 -0
- package/lib/comfyui.js +392 -0
- package/lib/config.d.ts +71 -0
- package/lib/config.js +28 -0
- package/lib/convert.d.ts +35 -0
- package/lib/convert.js +289 -0
- package/lib/host-hint.d.ts +29 -0
- package/lib/host-hint.js +73 -0
- package/lib/http.d.ts +15 -0
- package/lib/http.js +41 -0
- package/lib/index.d.ts +24 -0
- package/lib/index.js +344 -0
- package/lib/params.d.ts +63 -0
- package/lib/params.js +537 -0
- package/lib/progress.d.ts +27 -0
- package/lib/progress.js +98 -0
- package/lib/proxy.d.ts +13 -0
- package/lib/proxy.js +89 -0
- package/lib/queue.d.ts +50 -0
- package/lib/queue.js +103 -0
- package/lib/routes.d.ts +13 -0
- package/lib/routes.js +800 -0
- package/lib/skill.d.ts +12 -0
- package/lib/skill.js +72 -0
- package/lib/store.d.ts +120 -0
- package/lib/store.js +193 -0
- package/lib/templates.d.ts +36 -0
- package/lib/templates.js +84 -0
- package/lib/tools.d.ts +165 -0
- package/lib/tools.js +493 -0
- package/package.json +80 -0
package/lib/proxy.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { errorMessage, sendJson } from './http.js';
|
|
2
|
+
/**
|
|
3
|
+
* Mount the media proxy route on the host web server.
|
|
4
|
+
* @returns the disposer, or undefined when no web server is present.
|
|
5
|
+
*/
|
|
6
|
+
export function mountComfyUIProxy(ctx, runtime) {
|
|
7
|
+
const webServer = ctx.get('webServer');
|
|
8
|
+
if (webServer === undefined)
|
|
9
|
+
return undefined;
|
|
10
|
+
return webServer.register({
|
|
11
|
+
kind: 'exact',
|
|
12
|
+
path: '/comfyui/media',
|
|
13
|
+
handler: async (request, response) => {
|
|
14
|
+
runtime.hostHint.record(request);
|
|
15
|
+
if (request.method !== 'GET' && request.method !== 'HEAD') {
|
|
16
|
+
sendJson(response, 405, { error: 'method not allowed' });
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const url = new URL(request.url ?? '/', 'http://localhost');
|
|
20
|
+
const prompt = url.searchParams.get('prompt');
|
|
21
|
+
const node = url.searchParams.get('node');
|
|
22
|
+
const indexText = url.searchParams.get('index');
|
|
23
|
+
const file = url.searchParams.get('file');
|
|
24
|
+
if (file !== null) {
|
|
25
|
+
// Direct file lookup (e.g. job preview_output thumbnails).
|
|
26
|
+
const ref = { filename: file, subfolder: url.searchParams.get('subfolder') ?? '', type: url.searchParams.get('type') ?? 'output' };
|
|
27
|
+
try {
|
|
28
|
+
const client = runtime.createClient(await runtime.getApiKey());
|
|
29
|
+
const { bytes, contentType } = await client.fetchView(ref);
|
|
30
|
+
response.writeHead(200, {
|
|
31
|
+
'content-type': contentType,
|
|
32
|
+
'content-length': bytes.byteLength,
|
|
33
|
+
'cache-control': 'private, max-age=3600',
|
|
34
|
+
});
|
|
35
|
+
if (request.method === 'HEAD') {
|
|
36
|
+
response.end();
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
response.end(Buffer.from(bytes));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
sendJson(response, 502, { error: errorMessage(error) });
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (prompt === null || node === null || indexText === null) {
|
|
48
|
+
sendJson(response, 400, { error: 'prompt, node, and index query parameters are required (or file + subfolder + type)' });
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const index = Number(indexText);
|
|
52
|
+
if (!Number.isInteger(index) || index < 0) {
|
|
53
|
+
sendJson(response, 400, { error: 'index must be a non-negative integer' });
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const client = runtime.createClient(await runtime.getApiKey());
|
|
58
|
+
const entry = await client.getHistory(prompt);
|
|
59
|
+
const outputs = entry?.outputs ?? {};
|
|
60
|
+
const nodeOutput = outputs[node];
|
|
61
|
+
const collections = [
|
|
62
|
+
nodeOutput?.images,
|
|
63
|
+
nodeOutput?.videos,
|
|
64
|
+
nodeOutput?.gifs,
|
|
65
|
+
];
|
|
66
|
+
const ref = collections
|
|
67
|
+
.flatMap((items) => items ?? [])[index];
|
|
68
|
+
if (ref === undefined) {
|
|
69
|
+
sendJson(response, 404, { error: `no media item ${node}[${index}] for prompt ${prompt} (history may be evicted)` });
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const { bytes, contentType } = await client.fetchView(ref);
|
|
73
|
+
response.writeHead(200, {
|
|
74
|
+
'content-type': contentType,
|
|
75
|
+
'content-length': bytes.byteLength,
|
|
76
|
+
'cache-control': 'private, max-age=3600',
|
|
77
|
+
});
|
|
78
|
+
if (request.method === 'HEAD') {
|
|
79
|
+
response.end();
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
response.end(Buffer.from(bytes));
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
sendJson(response, 502, { error: errorMessage(error) });
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
}
|
package/lib/queue.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Queue tracking: remembers every prompt this plugin queued (from tools or
|
|
3
|
+
* the panel) so the panel can show "ours" in the ComfyUI queue and move
|
|
4
|
+
* completed runs into the asset index. Sweeps run on read (queue/assets
|
|
5
|
+
* routes), so no background timers leak into the fiber lifecycle.
|
|
6
|
+
*/
|
|
7
|
+
import { ComfyUIClient } from './comfyui.js';
|
|
8
|
+
import type { AssetRecord, ComfyUIStore, TrackedState } from './store.js';
|
|
9
|
+
/** A prompt this plugin queued. Kept after completion so the task center
|
|
10
|
+
* can still show its workflow name and "ours" marker. */
|
|
11
|
+
export interface QueuedRun {
|
|
12
|
+
promptId: string;
|
|
13
|
+
ts: string;
|
|
14
|
+
workflowName: string | null;
|
|
15
|
+
source: string;
|
|
16
|
+
}
|
|
17
|
+
/** Tracks queued prompts until they complete or vanish. */
|
|
18
|
+
export declare class QueueTracker {
|
|
19
|
+
private readonly persisted?;
|
|
20
|
+
private readonly runs;
|
|
21
|
+
/** Prompt ids already swept into the asset index, so sweep is idempotent. */
|
|
22
|
+
private readonly archived;
|
|
23
|
+
/**
|
|
24
|
+
* Optional durable backing: the tracked memory is persisted so completed
|
|
25
|
+
* runs still land in the asset index after a web-server restart.
|
|
26
|
+
*/
|
|
27
|
+
constructor(persisted?: {
|
|
28
|
+
load(): Promise<TrackedState>;
|
|
29
|
+
save(state: TrackedState): Promise<void>;
|
|
30
|
+
} | undefined);
|
|
31
|
+
/** Restore persisted runs/archived state; call once before tracking. */
|
|
32
|
+
init(): Promise<void>;
|
|
33
|
+
/** Fire-and-forget persistence; failures must not break queueing. */
|
|
34
|
+
private persistNow;
|
|
35
|
+
track(run: QueuedRun): void;
|
|
36
|
+
untrack(promptId: string): void;
|
|
37
|
+
get(promptId: string): QueuedRun | undefined;
|
|
38
|
+
list(): QueuedRun[];
|
|
39
|
+
/**
|
|
40
|
+
* Move completed tracked runs into the asset store. Runs already archived
|
|
41
|
+
* are skipped; failed runs stay tracked so their task rows keep a name.
|
|
42
|
+
* @returns the records newly appended.
|
|
43
|
+
*/
|
|
44
|
+
sweep(opts: {
|
|
45
|
+
client: ComfyUIClient;
|
|
46
|
+
store: ComfyUIStore;
|
|
47
|
+
maxItems: number;
|
|
48
|
+
proxyBase: string | undefined;
|
|
49
|
+
}): Promise<AssetRecord[]>;
|
|
50
|
+
}
|
package/lib/queue.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Queue tracking: remembers every prompt this plugin queued (from tools or
|
|
3
|
+
* the panel) so the panel can show "ours" in the ComfyUI queue and move
|
|
4
|
+
* completed runs into the asset index. Sweeps run on read (queue/assets
|
|
5
|
+
* routes), so no background timers leak into the fiber lifecycle.
|
|
6
|
+
*/
|
|
7
|
+
import { collectMedia, hasMedia } from './comfyui.js';
|
|
8
|
+
/** Upper bound on remembered runs; oldest are dropped beyond this. */
|
|
9
|
+
const MAX_TRACKED_RUNS = 500;
|
|
10
|
+
/** Tracks queued prompts until they complete or vanish. */
|
|
11
|
+
export class QueueTracker {
|
|
12
|
+
persisted;
|
|
13
|
+
runs = new Map();
|
|
14
|
+
/** Prompt ids already swept into the asset index, so sweep is idempotent. */
|
|
15
|
+
archived = new Set();
|
|
16
|
+
/**
|
|
17
|
+
* Optional durable backing: the tracked memory is persisted so completed
|
|
18
|
+
* runs still land in the asset index after a web-server restart.
|
|
19
|
+
*/
|
|
20
|
+
constructor(persisted) {
|
|
21
|
+
this.persisted = persisted;
|
|
22
|
+
}
|
|
23
|
+
/** Restore persisted runs/archived state; call once before tracking. */
|
|
24
|
+
async init() {
|
|
25
|
+
if (this.persisted === undefined)
|
|
26
|
+
return;
|
|
27
|
+
const state = await this.persisted.load();
|
|
28
|
+
for (const run of state.runs) {
|
|
29
|
+
this.runs.set(run.promptId, {
|
|
30
|
+
promptId: run.promptId,
|
|
31
|
+
ts: run.ts,
|
|
32
|
+
workflowName: run.workflowName,
|
|
33
|
+
source: run.source,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
for (const id of state.archived)
|
|
37
|
+
this.archived.add(id);
|
|
38
|
+
}
|
|
39
|
+
/** Fire-and-forget persistence; failures must not break queueing. */
|
|
40
|
+
persistNow() {
|
|
41
|
+
if (this.persisted === undefined)
|
|
42
|
+
return;
|
|
43
|
+
const state = {
|
|
44
|
+
runs: [...this.runs.values()],
|
|
45
|
+
archived: [...this.archived],
|
|
46
|
+
};
|
|
47
|
+
void this.persisted.save(state).catch(() => undefined);
|
|
48
|
+
}
|
|
49
|
+
track(run) {
|
|
50
|
+
this.runs.set(run.promptId, run);
|
|
51
|
+
this.persistNow();
|
|
52
|
+
}
|
|
53
|
+
untrack(promptId) {
|
|
54
|
+
this.runs.delete(promptId);
|
|
55
|
+
this.persistNow();
|
|
56
|
+
}
|
|
57
|
+
get(promptId) {
|
|
58
|
+
return this.runs.get(promptId);
|
|
59
|
+
}
|
|
60
|
+
list() {
|
|
61
|
+
return [...this.runs.values()];
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Move completed tracked runs into the asset store. Runs already archived
|
|
65
|
+
* are skipped; failed runs stay tracked so their task rows keep a name.
|
|
66
|
+
* @returns the records newly appended.
|
|
67
|
+
*/
|
|
68
|
+
async sweep(opts) {
|
|
69
|
+
const completed = [];
|
|
70
|
+
for (const run of [...this.runs.values()]) {
|
|
71
|
+
if (this.archived.has(run.promptId))
|
|
72
|
+
continue;
|
|
73
|
+
const entry = await opts.client.getHistory(run.promptId).catch(() => undefined);
|
|
74
|
+
if (entry === undefined)
|
|
75
|
+
continue;
|
|
76
|
+
if (isCompleted(entry)) {
|
|
77
|
+
const media = collectMedia({ promptId: run.promptId, entry, maxItems: opts.maxItems, proxyBase: opts.proxyBase });
|
|
78
|
+
const record = {
|
|
79
|
+
promptId: run.promptId,
|
|
80
|
+
ts: run.ts,
|
|
81
|
+
workflowName: run.workflowName,
|
|
82
|
+
source: run.source,
|
|
83
|
+
media,
|
|
84
|
+
};
|
|
85
|
+
await opts.store.appendAsset(record);
|
|
86
|
+
completed.push(record);
|
|
87
|
+
this.archived.add(run.promptId);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (this.runs.size > MAX_TRACKED_RUNS) {
|
|
91
|
+
const oldest = this.runs.keys().next().value;
|
|
92
|
+
if (oldest !== undefined)
|
|
93
|
+
this.runs.delete(oldest);
|
|
94
|
+
}
|
|
95
|
+
this.persistNow();
|
|
96
|
+
return completed;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function isCompleted(entry) {
|
|
100
|
+
return entry.status?.status_str === 'success'
|
|
101
|
+
|| entry.status?.completed === true
|
|
102
|
+
|| hasMedia(entry);
|
|
103
|
+
}
|
package/lib/routes.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-facing HTTP routes for dsh-comfyui: configuration (read/redacted,
|
|
3
|
+
* persist through the settings service), a connection probe, the workflow
|
|
4
|
+
* library (list/save/delete/run), the asset index, and the live ComfyUI
|
|
5
|
+
* queue view. Writes are same-origin-only.
|
|
6
|
+
*/
|
|
7
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
8
|
+
import type { ComfyUIRuntime } from './tools.js';
|
|
9
|
+
/**
|
|
10
|
+
* Mount every dsh-comfyui route on the host web server.
|
|
11
|
+
* @returns the disposer, or undefined when no web server is present.
|
|
12
|
+
*/
|
|
13
|
+
export declare function mountComfyUIRoutes(ctx: Context, runtime: ComfyUIRuntime): (() => void) | undefined;
|