@soederpop/luca 0.0.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/CLAUDE.md +71 -0
- package/README.md +78 -0
- package/bun.lock +2928 -0
- package/bunfig.toml +3 -0
- package/commands/audit-docs.ts +740 -0
- package/commands/build-scaffolds.ts +154 -0
- package/commands/generate-api-docs.ts +114 -0
- package/commands/update-introspection.ts +67 -0
- package/docs/CLI.md +335 -0
- package/docs/README.md +88 -0
- package/docs/TABLE-OF-CONTENTS.md +157 -0
- package/docs/apis/clients/elevenlabs.md +84 -0
- package/docs/apis/clients/graph.md +56 -0
- package/docs/apis/clients/openai.md +69 -0
- package/docs/apis/clients/rest.md +41 -0
- package/docs/apis/clients/websocket.md +107 -0
- package/docs/apis/features/agi/assistant.md +471 -0
- package/docs/apis/features/agi/assistants-manager.md +154 -0
- package/docs/apis/features/agi/claude-code.md +602 -0
- package/docs/apis/features/agi/conversation-history.md +352 -0
- package/docs/apis/features/agi/conversation.md +333 -0
- package/docs/apis/features/agi/docs-reader.md +121 -0
- package/docs/apis/features/agi/openai-codex.md +318 -0
- package/docs/apis/features/agi/openapi.md +138 -0
- package/docs/apis/features/agi/semantic-search.md +387 -0
- package/docs/apis/features/agi/skills-library.md +216 -0
- package/docs/apis/features/node/container-link.md +133 -0
- package/docs/apis/features/node/content-db.md +313 -0
- package/docs/apis/features/node/disk-cache.md +379 -0
- package/docs/apis/features/node/dns.md +651 -0
- package/docs/apis/features/node/docker.md +705 -0
- package/docs/apis/features/node/downloader.md +81 -0
- package/docs/apis/features/node/esbuild.md +59 -0
- package/docs/apis/features/node/file-manager.md +182 -0
- package/docs/apis/features/node/fs.md +581 -0
- package/docs/apis/features/node/git.md +330 -0
- package/docs/apis/features/node/google-auth.md +174 -0
- package/docs/apis/features/node/google-calendar.md +187 -0
- package/docs/apis/features/node/google-docs.md +151 -0
- package/docs/apis/features/node/google-drive.md +225 -0
- package/docs/apis/features/node/google-sheets.md +179 -0
- package/docs/apis/features/node/grep.md +290 -0
- package/docs/apis/features/node/helpers.md +135 -0
- package/docs/apis/features/node/ink.md +334 -0
- package/docs/apis/features/node/ipc-socket.md +260 -0
- package/docs/apis/features/node/json-tree.md +86 -0
- package/docs/apis/features/node/launcher-app-command-listener.md +145 -0
- package/docs/apis/features/node/networking.md +281 -0
- package/docs/apis/features/node/nlp.md +133 -0
- package/docs/apis/features/node/opener.md +97 -0
- package/docs/apis/features/node/os.md +118 -0
- package/docs/apis/features/node/package-finder.md +402 -0
- package/docs/apis/features/node/postgres.md +212 -0
- package/docs/apis/features/node/proc.md +430 -0
- package/docs/apis/features/node/process-manager.md +210 -0
- package/docs/apis/features/node/python.md +278 -0
- package/docs/apis/features/node/repl.md +88 -0
- package/docs/apis/features/node/runpod.md +673 -0
- package/docs/apis/features/node/secure-shell.md +169 -0
- package/docs/apis/features/node/semantic-search.md +401 -0
- package/docs/apis/features/node/sqlite.md +211 -0
- package/docs/apis/features/node/telegram.md +254 -0
- package/docs/apis/features/node/tts.md +118 -0
- package/docs/apis/features/node/ui.md +703 -0
- package/docs/apis/features/node/vault.md +64 -0
- package/docs/apis/features/node/vm.md +84 -0
- package/docs/apis/features/node/window-manager.md +337 -0
- package/docs/apis/features/node/yaml-tree.md +85 -0
- package/docs/apis/features/node/yaml.md +176 -0
- package/docs/apis/features/web/asset-loader.md +47 -0
- package/docs/apis/features/web/container-link.md +133 -0
- package/docs/apis/features/web/esbuild.md +59 -0
- package/docs/apis/features/web/helpers.md +135 -0
- package/docs/apis/features/web/network.md +30 -0
- package/docs/apis/features/web/speech.md +55 -0
- package/docs/apis/features/web/vault.md +64 -0
- package/docs/apis/features/web/vm.md +84 -0
- package/docs/apis/features/web/voice.md +67 -0
- package/docs/apis/servers/express.md +127 -0
- package/docs/apis/servers/mcp.md +213 -0
- package/docs/apis/servers/websocket.md +99 -0
- package/docs/documentation-audit.md +134 -0
- package/docs/examples/content-db.md +77 -0
- package/docs/examples/disk-cache.md +83 -0
- package/docs/examples/docker.md +101 -0
- package/docs/examples/downloader.md +70 -0
- package/docs/examples/esbuild.md +80 -0
- package/docs/examples/file-manager.md +82 -0
- package/docs/examples/fs.md +83 -0
- package/docs/examples/git.md +85 -0
- package/docs/examples/google-auth.md +88 -0
- package/docs/examples/google-calendar.md +94 -0
- package/docs/examples/google-docs.md +82 -0
- package/docs/examples/google-drive.md +96 -0
- package/docs/examples/google-sheets.md +95 -0
- package/docs/examples/grep.md +85 -0
- package/docs/examples/ink-blocks.md +75 -0
- package/docs/examples/ink-renderer.md +41 -0
- package/docs/examples/ink.md +103 -0
- package/docs/examples/ipc-socket.md +103 -0
- package/docs/examples/json-tree.md +91 -0
- package/docs/examples/launcher-app-command-listener.md +120 -0
- package/docs/examples/networking.md +58 -0
- package/docs/examples/nlp.md +91 -0
- package/docs/examples/opener.md +78 -0
- package/docs/examples/os.md +72 -0
- package/docs/examples/package-finder.md +89 -0
- package/docs/examples/port-exposer.md +89 -0
- package/docs/examples/postgres.md +91 -0
- package/docs/examples/proc.md +81 -0
- package/docs/examples/process-manager.md +79 -0
- package/docs/examples/python.md +91 -0
- package/docs/examples/repl.md +93 -0
- package/docs/examples/runpod.md +119 -0
- package/docs/examples/secure-shell.md +92 -0
- package/docs/examples/sqlite.md +86 -0
- package/docs/examples/telegram.md +77 -0
- package/docs/examples/tts.md +86 -0
- package/docs/examples/ui.md +80 -0
- package/docs/examples/vault.md +70 -0
- package/docs/examples/vm.md +86 -0
- package/docs/examples/window-manager.md +125 -0
- package/docs/examples/yaml-tree.md +93 -0
- package/docs/examples/yaml.md +104 -0
- package/docs/ideas/class-registration-refactor-possibilities.md +197 -0
- package/docs/ideas/container-use-api.md +9 -0
- package/docs/ideas/easy-auth-for-express-servers-and-luca-serve.md +0 -0
- package/docs/ideas/feature-stacks.md +22 -0
- package/docs/ideas/luca-cli-self-sufficiency-demo.md +23 -0
- package/docs/ideas/mcp-design.md +9 -0
- package/docs/ideas/web-container-debugging-feature.md +13 -0
- package/docs/introspection-audit.md +49 -0
- package/docs/introspection.md +154 -0
- package/docs/mcp/readme.md +162 -0
- package/docs/models.ts +38 -0
- package/docs/philosophy.md +85 -0
- package/docs/principles.md +7 -0
- package/docs/prompts/audit-codebase-for-failures-to-use-the-container.md +34 -0
- package/docs/prompts/mcp-test-easy-command.md +27 -0
- package/docs/reports/assistant-bugs.md +38 -0
- package/docs/reports/attach-pattern-usage.md +18 -0
- package/docs/reports/code-audit-results.md +391 -0
- package/docs/reports/introspection-audit-tasks.md +378 -0
- package/docs/reports/luca-mcp-improvements.md +128 -0
- package/docs/scaffolds/client.md +140 -0
- package/docs/scaffolds/command.md +106 -0
- package/docs/scaffolds/endpoint.md +176 -0
- package/docs/scaffolds/feature.md +148 -0
- package/docs/scaffolds/server.md +187 -0
- package/docs/tasks/web-container-helper-discovery.md +71 -0
- package/docs/todos.md +1 -0
- package/docs/tutorials/01-getting-started.md +106 -0
- package/docs/tutorials/02-container.md +210 -0
- package/docs/tutorials/03-scripts.md +194 -0
- package/docs/tutorials/04-features-overview.md +196 -0
- package/docs/tutorials/05-state-and-events.md +171 -0
- package/docs/tutorials/06-servers.md +157 -0
- package/docs/tutorials/07-endpoints.md +198 -0
- package/docs/tutorials/08-commands.md +171 -0
- package/docs/tutorials/09-clients.md +162 -0
- package/docs/tutorials/10-creating-features.md +198 -0
- package/docs/tutorials/11-contentbase.md +191 -0
- package/docs/tutorials/12-assistants.md +215 -0
- package/docs/tutorials/13-introspection.md +147 -0
- package/docs/tutorials/14-type-system.md +174 -0
- package/docs/tutorials/15-project-patterns.md +222 -0
- package/docs/tutorials/16-google-features.md +534 -0
- package/docs/tutorials/17-tui-blocks.md +530 -0
- package/docs/tutorials/18-semantic-search.md +334 -0
- package/index.ts +1 -0
- package/luca.console.ts +9 -0
- package/main.py +6 -0
- package/package.json +154 -0
- package/pyproject.toml +7 -0
- package/scripts/animations/chrome-glitch.ts +55 -0
- package/scripts/animations/index.ts +16 -0
- package/scripts/animations/neon-pulse.ts +64 -0
- package/scripts/animations/types.ts +6 -0
- package/scripts/build-web.ts +28 -0
- package/scripts/examples/ask-luca-expert.ts +42 -0
- package/scripts/examples/assistant-questions.ts +12 -0
- package/scripts/examples/excalidraw-expert.ts +75 -0
- package/scripts/examples/expert-chat.ts +0 -0
- package/scripts/examples/file-manager.ts +14 -0
- package/scripts/examples/ideas.ts +12 -0
- package/scripts/examples/interactive-chat.ts +20 -0
- package/scripts/examples/openai-tool-calls.ts +113 -0
- package/scripts/examples/opening-a-web-browser.ts +5 -0
- package/scripts/examples/telegram-bot.ts +79 -0
- package/scripts/examples/telegram-ink-ui.ts +302 -0
- package/scripts/examples/using-assistant-with-mcp.ts +560 -0
- package/scripts/examples/using-claude-code.ts +10 -0
- package/scripts/examples/using-contentdb.ts +35 -0
- package/scripts/examples/using-conversations.ts +35 -0
- package/scripts/examples/using-disk-cache.ts +10 -0
- package/scripts/examples/using-docker-shell.ts +75 -0
- package/scripts/examples/using-elevenlabs.ts +25 -0
- package/scripts/examples/using-google-calendar.ts +57 -0
- package/scripts/examples/using-google-docs.ts +74 -0
- package/scripts/examples/using-google-drive.ts +74 -0
- package/scripts/examples/using-google-sheets.ts +89 -0
- package/scripts/examples/using-nlp.ts +55 -0
- package/scripts/examples/using-ollama.ts +10 -0
- package/scripts/examples/using-openai-codex.ts +23 -0
- package/scripts/examples/using-postgres.ts +55 -0
- package/scripts/examples/using-runpod.ts +32 -0
- package/scripts/examples/using-tts.ts +40 -0
- package/scripts/examples/vm-loading-esm-modules.ts +16 -0
- package/scripts/scaffold.ts +391 -0
- package/scripts/scratch.ts +15 -0
- package/scripts/test-command-listener.ts +123 -0
- package/scripts/test-window-manager-lifecycle.ts +86 -0
- package/scripts/test-window-manager.ts +43 -0
- package/scripts/update-introspection-data.ts +58 -0
- package/src/agi/README.md +14 -0
- package/src/agi/container.server.ts +114 -0
- package/src/agi/endpoints/ask.ts +60 -0
- package/src/agi/endpoints/conversations/[id].ts +45 -0
- package/src/agi/endpoints/conversations.ts +31 -0
- package/src/agi/endpoints/experts.ts +37 -0
- package/src/agi/features/assistant.ts +767 -0
- package/src/agi/features/assistants-manager.ts +260 -0
- package/src/agi/features/claude-code.ts +1111 -0
- package/src/agi/features/conversation-history.ts +497 -0
- package/src/agi/features/conversation.ts +799 -0
- package/src/agi/features/openai-codex.ts +631 -0
- package/src/agi/features/openapi.ts +438 -0
- package/src/agi/features/skills-library.ts +425 -0
- package/src/agi/index.ts +6 -0
- package/src/agi/lib/token-counter.ts +122 -0
- package/src/browser.ts +25 -0
- package/src/bus.ts +100 -0
- package/src/cli/cli.ts +70 -0
- package/src/client.ts +461 -0
- package/src/clients/civitai/index.ts +541 -0
- package/src/clients/client-template.ts +41 -0
- package/src/clients/comfyui/index.ts +597 -0
- package/src/clients/elevenlabs/index.ts +291 -0
- package/src/clients/openai/index.ts +451 -0
- package/src/clients/supabase/index.ts +366 -0
- package/src/command.ts +164 -0
- package/src/commands/chat.ts +182 -0
- package/src/commands/console.ts +192 -0
- package/src/commands/describe.ts +433 -0
- package/src/commands/eval.ts +116 -0
- package/src/commands/help.ts +214 -0
- package/src/commands/index.ts +14 -0
- package/src/commands/mcp.ts +64 -0
- package/src/commands/prompt.ts +807 -0
- package/src/commands/run.ts +257 -0
- package/src/commands/sandbox-mcp.ts +439 -0
- package/src/commands/scaffold.ts +79 -0
- package/src/commands/serve.ts +172 -0
- package/src/container.ts +781 -0
- package/src/endpoint.ts +340 -0
- package/src/feature.ts +75 -0
- package/src/hash-object.ts +97 -0
- package/src/helper.ts +543 -0
- package/src/introspection/generated.agi.ts +23388 -0
- package/src/introspection/generated.node.ts +18899 -0
- package/src/introspection/generated.web.ts +2021 -0
- package/src/introspection/index.ts +256 -0
- package/src/introspection/scan.ts +912 -0
- package/src/node/container.ts +354 -0
- package/src/node/feature.ts +13 -0
- package/src/node/features/container-link.ts +558 -0
- package/src/node/features/content-db.ts +475 -0
- package/src/node/features/disk-cache.ts +382 -0
- package/src/node/features/dns.ts +655 -0
- package/src/node/features/docker.ts +912 -0
- package/src/node/features/downloader.ts +92 -0
- package/src/node/features/esbuild.ts +68 -0
- package/src/node/features/file-manager.ts +357 -0
- package/src/node/features/fs.ts +534 -0
- package/src/node/features/git.ts +492 -0
- package/src/node/features/google-auth.ts +502 -0
- package/src/node/features/google-calendar.ts +300 -0
- package/src/node/features/google-docs.ts +404 -0
- package/src/node/features/google-drive.ts +339 -0
- package/src/node/features/google-sheets.ts +279 -0
- package/src/node/features/grep.ts +406 -0
- package/src/node/features/helpers.ts +374 -0
- package/src/node/features/ink.ts +490 -0
- package/src/node/features/ipc-socket.ts +459 -0
- package/src/node/features/json-tree.ts +188 -0
- package/src/node/features/launcher-app-command-listener.ts +388 -0
- package/src/node/features/networking.ts +925 -0
- package/src/node/features/nlp.ts +211 -0
- package/src/node/features/opener.ts +166 -0
- package/src/node/features/os.ts +157 -0
- package/src/node/features/package-finder.ts +539 -0
- package/src/node/features/port-exposer.ts +342 -0
- package/src/node/features/postgres.ts +273 -0
- package/src/node/features/proc.ts +502 -0
- package/src/node/features/process-manager.ts +542 -0
- package/src/node/features/python.ts +444 -0
- package/src/node/features/repl.ts +194 -0
- package/src/node/features/runpod.ts +802 -0
- package/src/node/features/secure-shell.ts +248 -0
- package/src/node/features/semantic-search.ts +924 -0
- package/src/node/features/sqlite.ts +289 -0
- package/src/node/features/telegram.ts +342 -0
- package/src/node/features/tts.ts +184 -0
- package/src/node/features/ui.ts +857 -0
- package/src/node/features/vault.ts +164 -0
- package/src/node/features/vm.ts +312 -0
- package/src/node/features/window-manager.ts +804 -0
- package/src/node/features/yaml-tree.ts +149 -0
- package/src/node/features/yaml.ts +132 -0
- package/src/node.ts +70 -0
- package/src/react/index.ts +175 -0
- package/src/registry.ts +199 -0
- package/src/scaffolds/generated.ts +1613 -0
- package/src/scaffolds/template.ts +37 -0
- package/src/schemas/base.ts +255 -0
- package/src/server.ts +135 -0
- package/src/servers/express.ts +209 -0
- package/src/servers/mcp.ts +805 -0
- package/src/servers/socket.ts +120 -0
- package/src/state.ts +101 -0
- package/src/web/clients/socket.ts +82 -0
- package/src/web/container.ts +74 -0
- package/src/web/extension.ts +30 -0
- package/src/web/feature.ts +12 -0
- package/src/web/features/asset-loader.ts +64 -0
- package/src/web/features/container-link.ts +385 -0
- package/src/web/features/esbuild.ts +79 -0
- package/src/web/features/helpers.ts +267 -0
- package/src/web/features/network.ts +61 -0
- package/src/web/features/speech.ts +87 -0
- package/src/web/features/vault.ts +189 -0
- package/src/web/features/vm.ts +78 -0
- package/src/web/features/voice-recognition.ts +129 -0
- package/src/web/shims/isomorphic-vm.ts +149 -0
- package/test/bus.test.ts +134 -0
- package/test/clients-servers.test.ts +216 -0
- package/test/container-link.test.ts +274 -0
- package/test/features.test.ts +160 -0
- package/test/integration.test.ts +787 -0
- package/test/node-container.test.ts +121 -0
- package/test/rate-limit.test.ts +272 -0
- package/test/semantic-search.test.ts +550 -0
- package/test/state.test.ts +121 -0
- package/test-integration/assistant.test.ts +138 -0
- package/test-integration/assistants-manager.test.ts +123 -0
- package/test-integration/claude-code.test.ts +98 -0
- package/test-integration/conversation-history.test.ts +205 -0
- package/test-integration/conversation.test.ts +137 -0
- package/test-integration/elevenlabs.test.ts +55 -0
- package/test-integration/google-services.test.ts +80 -0
- package/test-integration/helpers.ts +89 -0
- package/test-integration/openai-codex.test.ts +93 -0
- package/test-integration/runpod.test.ts +58 -0
- package/test-integration/server-endpoints.test.ts +97 -0
- package/test-integration/skills-library.test.ts +157 -0
- package/test-integration/telegram.test.ts +46 -0
- package/tsconfig.json +58 -0
- package/uv.lock +8 -0
|
@@ -0,0 +1,597 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ClientsInterface,
|
|
3
|
+
clients,
|
|
4
|
+
RestClient,
|
|
5
|
+
} from "@soederpop/luca/client";
|
|
6
|
+
import type { Container, ContainerContext } from "@soederpop/luca/container";
|
|
7
|
+
import { z } from 'zod'
|
|
8
|
+
import { ClientStateSchema, ClientOptionsSchema } from '@soederpop/luca/schemas/base.js'
|
|
9
|
+
|
|
10
|
+
declare module "@soederpop/luca/client" {
|
|
11
|
+
interface AvailableClients {
|
|
12
|
+
comfyui: typeof ComfyUIClient;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const ComfyUIClientStateSchema = ClientStateSchema.extend({
|
|
17
|
+
clientId: z.string().default('').describe('Unique client ID used for WebSocket session tracking'),
|
|
18
|
+
queueRemaining: z.number().default(0).describe('Number of prompts remaining in the queue'),
|
|
19
|
+
executing: z.string().nullable().default(null).describe('Prompt ID currently being executed, or null'),
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
export const ComfyUIClientOptionsSchema = ClientOptionsSchema.extend({
|
|
23
|
+
wsURL: z.string().optional().describe('Override the WebSocket URL (defaults to ws version of baseURL)'),
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
export type ComfyUIClientState = z.infer<typeof ComfyUIClientStateSchema>
|
|
27
|
+
export type ComfyUIClientOptions = z.infer<typeof ComfyUIClientOptionsSchema>
|
|
28
|
+
|
|
29
|
+
/** Maps a semantic input name to a specific node ID and field */
|
|
30
|
+
export type InputMapping = Record<string, { nodeId: string; field: string }>;
|
|
31
|
+
|
|
32
|
+
export type WorkflowRunOptions = {
|
|
33
|
+
/** Use polling instead of WebSocket for tracking execution */
|
|
34
|
+
poll?: boolean;
|
|
35
|
+
/** Polling interval in ms (default 1000) */
|
|
36
|
+
pollInterval?: number;
|
|
37
|
+
/** Named input mapping: semantic name -> { nodeId, field } */
|
|
38
|
+
inputMap?: InputMapping;
|
|
39
|
+
/** If provided, output images are downloaded to this directory */
|
|
40
|
+
outputDir?: string;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type WorkflowResult = {
|
|
44
|
+
promptId: string;
|
|
45
|
+
outputs: Record<string, any>;
|
|
46
|
+
images?: Array<{ filename: string; subfolder: string; type: string; localPath?: string }>;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* ComfyUI client — execute Stable Diffusion workflows via the ComfyUI API.
|
|
51
|
+
*
|
|
52
|
+
* Connects to a ComfyUI instance to queue prompts, track execution via WebSocket or polling,
|
|
53
|
+
* and download generated images. Supports both UI-format and API-format workflows with
|
|
54
|
+
* automatic conversion.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* const comfy = container.client('comfyui', { baseURL: 'http://localhost:8188' })
|
|
59
|
+
* const result = await comfy.runWorkflow(workflow, {
|
|
60
|
+
* '6': { text: 'a beautiful sunset' }
|
|
61
|
+
* })
|
|
62
|
+
* console.log(result.images)
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export class ComfyUIClient extends RestClient<ComfyUIClientState, ComfyUIClientOptions> {
|
|
66
|
+
static override shortcut = "clients.comfyui" as const;
|
|
67
|
+
static override description = "ComfyUI workflow execution client";
|
|
68
|
+
static override stateSchema = ComfyUIClientStateSchema;
|
|
69
|
+
static override optionsSchema = ComfyUIClientOptionsSchema;
|
|
70
|
+
|
|
71
|
+
private ws: WebSocket | null = null;
|
|
72
|
+
|
|
73
|
+
static override attach(container: Container & ClientsInterface, options?: any) {
|
|
74
|
+
container.clients.register("comfyui", ComfyUIClient);
|
|
75
|
+
return container;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
constructor(options: ComfyUIClientOptions, context: ContainerContext) {
|
|
79
|
+
super(
|
|
80
|
+
{
|
|
81
|
+
...options,
|
|
82
|
+
baseURL: options.baseURL || "http://127.0.0.1:8000",
|
|
83
|
+
json: options.json ?? true,
|
|
84
|
+
} as ComfyUIClientOptions,
|
|
85
|
+
context
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Initial state with a random client ID. */
|
|
90
|
+
override get initialState(): ComfyUIClientState {
|
|
91
|
+
return {
|
|
92
|
+
connected: false,
|
|
93
|
+
clientId: crypto.randomUUID(),
|
|
94
|
+
queueRemaining: 0,
|
|
95
|
+
executing: null,
|
|
96
|
+
} as ComfyUIClientState;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** The unique client ID used for WebSocket session tracking. */
|
|
100
|
+
get clientId(): string {
|
|
101
|
+
return this.state.get("clientId")!;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** The WebSocket URL derived from baseURL or overridden via options. */
|
|
105
|
+
get wsURL(): string {
|
|
106
|
+
if (this.options.wsURL) return this.options.wsURL;
|
|
107
|
+
return this.baseURL.replace(/^http/, "ws") + "/ws";
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
// Core API methods
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Queue a prompt (API-format workflow) for execution.
|
|
116
|
+
*
|
|
117
|
+
* @param prompt - The API-format workflow object
|
|
118
|
+
* @param clientId - Override the client ID for this request
|
|
119
|
+
* @returns The prompt ID and queue number
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```typescript
|
|
123
|
+
* const { prompt_id } = await comfy.queuePrompt(apiWorkflow)
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
async queuePrompt(prompt: Record<string, any>, clientId?: string): Promise<{ prompt_id: string; number: number }> {
|
|
127
|
+
return this.post("/prompt", {
|
|
128
|
+
prompt,
|
|
129
|
+
client_id: clientId ?? this.clientId,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Get the current prompt queue status.
|
|
135
|
+
*
|
|
136
|
+
* @returns Running and pending queue items
|
|
137
|
+
*/
|
|
138
|
+
async getQueue(): Promise<{ queue_running: any[]; queue_pending: any[] }> {
|
|
139
|
+
return this.get("/queue");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get execution history, optionally for a specific prompt.
|
|
144
|
+
*
|
|
145
|
+
* @param promptId - If provided, returns history for this prompt only
|
|
146
|
+
* @returns History records keyed by prompt ID
|
|
147
|
+
*/
|
|
148
|
+
async getHistory(promptId?: string): Promise<Record<string, any>> {
|
|
149
|
+
return this.get(promptId ? `/history/${promptId}` : "/history");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Get system stats including GPU memory and queue info.
|
|
154
|
+
*
|
|
155
|
+
* @returns System statistics
|
|
156
|
+
*/
|
|
157
|
+
async getSystemStats(): Promise<any> {
|
|
158
|
+
return this.get("/system_stats");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Get node type info with input/output schemas.
|
|
163
|
+
*
|
|
164
|
+
* @param nodeClass - If provided, returns info for this node type only
|
|
165
|
+
* @returns Object info keyed by node class name
|
|
166
|
+
*/
|
|
167
|
+
async getObjectInfo(nodeClass?: string): Promise<any> {
|
|
168
|
+
return this.get(nodeClass ? `/object_info/${nodeClass}` : "/object_info");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Interrupt the currently executing prompt. */
|
|
172
|
+
async interrupt(): Promise<void> {
|
|
173
|
+
await this.post("/interrupt", {});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* List available models, optionally filtered by type.
|
|
178
|
+
*
|
|
179
|
+
* @param type - Model type filter (e.g., 'checkpoints', 'loras')
|
|
180
|
+
* @returns Array of model file names
|
|
181
|
+
*/
|
|
182
|
+
async getModels(type?: string): Promise<string[]> {
|
|
183
|
+
return this.get(type ? `/models/${type}` : "/models");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** List available embedding models. */
|
|
187
|
+
async getEmbeddings(): Promise<string[]> {
|
|
188
|
+
return this.get("/embeddings");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Upload an image to ComfyUI's input directory.
|
|
193
|
+
*
|
|
194
|
+
* @param file - The image data as Buffer or Blob
|
|
195
|
+
* @param filename - File name for the upload
|
|
196
|
+
* @param opts - Upload options (subfolder, type, overwrite)
|
|
197
|
+
* @returns Upload result from ComfyUI
|
|
198
|
+
*/
|
|
199
|
+
async uploadImage(
|
|
200
|
+
file: Buffer | Blob,
|
|
201
|
+
filename: string,
|
|
202
|
+
opts: { subfolder?: string; type?: string; overwrite?: boolean } = {}
|
|
203
|
+
): Promise<any> {
|
|
204
|
+
const formData = new FormData();
|
|
205
|
+
const blob = file instanceof Blob ? file : new Blob([file as BlobPart]);
|
|
206
|
+
formData.append("image", blob, filename);
|
|
207
|
+
if (opts.subfolder) formData.append("subfolder", opts.subfolder);
|
|
208
|
+
if (opts.type) formData.append("type", opts.type);
|
|
209
|
+
if (opts.overwrite) formData.append("overwrite", "true");
|
|
210
|
+
|
|
211
|
+
return this.axios
|
|
212
|
+
.post("/upload/image", formData, {
|
|
213
|
+
headers: { "Content-Type": "multipart/form-data" },
|
|
214
|
+
})
|
|
215
|
+
.then((r) => r.data);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Download a generated image from ComfyUI as a Buffer.
|
|
220
|
+
*
|
|
221
|
+
* @param filename - The image filename
|
|
222
|
+
* @param subfolder - Subfolder within the output directory
|
|
223
|
+
* @param type - Image type ('output', 'input', 'temp')
|
|
224
|
+
* @returns The image data as a Buffer
|
|
225
|
+
*/
|
|
226
|
+
async viewImage(
|
|
227
|
+
filename: string,
|
|
228
|
+
subfolder = "",
|
|
229
|
+
type = "output"
|
|
230
|
+
): Promise<Buffer> {
|
|
231
|
+
const resp = await this.axios.get("/view", {
|
|
232
|
+
params: { filename, subfolder, type },
|
|
233
|
+
responseType: "arraybuffer",
|
|
234
|
+
});
|
|
235
|
+
return Buffer.from(resp.data);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ---------------------------------------------------------------------------
|
|
239
|
+
// WebSocket connection
|
|
240
|
+
// ---------------------------------------------------------------------------
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Open a WebSocket connection to ComfyUI for real-time execution tracking.
|
|
244
|
+
*
|
|
245
|
+
* Events emitted: `execution_start`, `executing`, `progress`, `executed`,
|
|
246
|
+
* `execution_cached`, `execution_error`, `execution_complete`.
|
|
247
|
+
*/
|
|
248
|
+
async connectWs(): Promise<void> {
|
|
249
|
+
if (this.ws) return;
|
|
250
|
+
|
|
251
|
+
const url = `${this.wsURL}?clientId=${this.clientId}`;
|
|
252
|
+
|
|
253
|
+
return new Promise<void>((resolve, reject) => {
|
|
254
|
+
const ws = new WebSocket(url);
|
|
255
|
+
|
|
256
|
+
ws.addEventListener("open", () => {
|
|
257
|
+
this.ws = ws;
|
|
258
|
+
this.state.set("connected", true);
|
|
259
|
+
resolve();
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
ws.addEventListener("error", (e) => {
|
|
263
|
+
reject(e);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
ws.addEventListener("close", () => {
|
|
267
|
+
this.ws = null;
|
|
268
|
+
this.state.set("connected", false);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
ws.addEventListener("message", (event) => {
|
|
272
|
+
try {
|
|
273
|
+
const msg = JSON.parse(typeof event.data === "string" ? event.data : event.data.toString());
|
|
274
|
+
this.handleWsMessage(msg);
|
|
275
|
+
} catch {
|
|
276
|
+
// binary data (e.g. preview images), ignore
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** Close the WebSocket connection. */
|
|
283
|
+
disconnectWs(): void {
|
|
284
|
+
if (this.ws) {
|
|
285
|
+
this.ws.close();
|
|
286
|
+
this.ws = null;
|
|
287
|
+
this.state.set("connected", false);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
private handleWsMessage(msg: { type: string; data: any }) {
|
|
292
|
+
switch (msg.type) {
|
|
293
|
+
case "status":
|
|
294
|
+
if (msg.data?.status?.exec_info) {
|
|
295
|
+
this.state.set("queueRemaining", msg.data.status.exec_info.queue_remaining);
|
|
296
|
+
}
|
|
297
|
+
break;
|
|
298
|
+
case "execution_start":
|
|
299
|
+
this.state.set("executing", msg.data.prompt_id);
|
|
300
|
+
this.emit("execution_start", { promptId: msg.data.prompt_id });
|
|
301
|
+
break;
|
|
302
|
+
case "executing":
|
|
303
|
+
if (msg.data.node === null) {
|
|
304
|
+
this.state.set("executing", null);
|
|
305
|
+
this.emit("execution_complete", { promptId: msg.data.prompt_id });
|
|
306
|
+
} else {
|
|
307
|
+
this.emit("executing", { node: msg.data.node, promptId: msg.data.prompt_id });
|
|
308
|
+
}
|
|
309
|
+
break;
|
|
310
|
+
case "progress":
|
|
311
|
+
this.emit("progress", {
|
|
312
|
+
node: msg.data.node,
|
|
313
|
+
value: msg.data.value,
|
|
314
|
+
max: msg.data.max,
|
|
315
|
+
promptId: msg.data.prompt_id,
|
|
316
|
+
});
|
|
317
|
+
break;
|
|
318
|
+
case "executed":
|
|
319
|
+
this.emit("executed", {
|
|
320
|
+
node: msg.data.node,
|
|
321
|
+
output: msg.data.output,
|
|
322
|
+
promptId: msg.data.prompt_id,
|
|
323
|
+
});
|
|
324
|
+
break;
|
|
325
|
+
case "execution_cached":
|
|
326
|
+
this.emit("execution_cached", {
|
|
327
|
+
nodes: msg.data.nodes,
|
|
328
|
+
promptId: msg.data.prompt_id,
|
|
329
|
+
});
|
|
330
|
+
break;
|
|
331
|
+
case "execution_error":
|
|
332
|
+
this.emit("execution_error", {
|
|
333
|
+
promptId: msg.data.prompt_id,
|
|
334
|
+
...msg.data,
|
|
335
|
+
});
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// ---------------------------------------------------------------------------
|
|
341
|
+
// Workflow format detection & conversion
|
|
342
|
+
// ---------------------------------------------------------------------------
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Detect whether a workflow object is in UI format (exported from the graph
|
|
346
|
+
* editor) or API format (flat node-id-keyed object with class_type).
|
|
347
|
+
*/
|
|
348
|
+
static isUIFormat(workflow: Record<string, any>): boolean {
|
|
349
|
+
return Array.isArray(workflow.nodes) && Array.isArray(workflow.links);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Convert a UI-format workflow to the API format that /prompt expects.
|
|
354
|
+
*
|
|
355
|
+
* Requires a running ComfyUI instance to fetch `object_info` so we can
|
|
356
|
+
* map positional `widgets_values` to their named input fields.
|
|
357
|
+
*
|
|
358
|
+
* If the workflow is already in API format, it's returned as-is.
|
|
359
|
+
*/
|
|
360
|
+
async toApiFormat(workflow: Record<string, any>): Promise<Record<string, any>> {
|
|
361
|
+
if (!ComfyUIClient.isUIFormat(workflow)) return workflow;
|
|
362
|
+
|
|
363
|
+
const nodes: any[] = workflow.nodes;
|
|
364
|
+
const links: any[] = workflow.links;
|
|
365
|
+
|
|
366
|
+
// Build a lookup: linkId -> { sourceNodeId, sourceSlot }
|
|
367
|
+
const linkMap = new Map<number, { sourceNodeId: string; sourceSlot: number }>();
|
|
368
|
+
for (const link of links) {
|
|
369
|
+
// link format: [linkId, sourceNodeId, sourceSlot, targetNodeId, targetSlot, type]
|
|
370
|
+
linkMap.set(link[0], { sourceNodeId: String(link[1]), sourceSlot: link[2] });
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Fetch object_info for all node types present in the workflow
|
|
374
|
+
const nodeTypes = [...new Set(nodes.map((n) => n.type))];
|
|
375
|
+
const objectInfo: Record<string, any> = {};
|
|
376
|
+
await Promise.all(
|
|
377
|
+
nodeTypes.map(async (type) => {
|
|
378
|
+
try {
|
|
379
|
+
const info = await this.getObjectInfo(type);
|
|
380
|
+
objectInfo[type] = info[type];
|
|
381
|
+
} catch {
|
|
382
|
+
// Node type not found on server — we'll do our best without it
|
|
383
|
+
}
|
|
384
|
+
})
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
const apiWorkflow: Record<string, any> = {};
|
|
388
|
+
|
|
389
|
+
for (const node of nodes) {
|
|
390
|
+
const nodeId = String(node.id);
|
|
391
|
+
const classType = node.type;
|
|
392
|
+
const inputs: Record<string, any> = {};
|
|
393
|
+
|
|
394
|
+
// Resolve connected inputs (from the node's input slots)
|
|
395
|
+
if (node.inputs) {
|
|
396
|
+
for (const input of node.inputs) {
|
|
397
|
+
if (input.link != null) {
|
|
398
|
+
const source = linkMap.get(input.link);
|
|
399
|
+
if (source) {
|
|
400
|
+
inputs[input.name] = [source.sourceNodeId, source.sourceSlot];
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Map widgets_values to named inputs using object_info
|
|
407
|
+
if (node.widgets_values && objectInfo[classType]) {
|
|
408
|
+
const info = objectInfo[classType];
|
|
409
|
+
const requiredInputs = info.input?.required ?? {};
|
|
410
|
+
const optionalInputs = info.input?.optional ?? {};
|
|
411
|
+
|
|
412
|
+
// Collect the widget input names in order (skip ones that are link-connected)
|
|
413
|
+
const connectedNames = new Set(
|
|
414
|
+
(node.inputs || []).filter((i: any) => i.link != null).map((i: any) => i.name)
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
const widgetNames: string[] = [];
|
|
418
|
+
for (const [name, config] of Object.entries(requiredInputs) as [string, any][]) {
|
|
419
|
+
if (connectedNames.has(name)) continue;
|
|
420
|
+
// Skip non-widget types (these are slot connections, not widgets)
|
|
421
|
+
const inputType = Array.isArray(config) ? config[0] : config;
|
|
422
|
+
if (typeof inputType === "string" && inputType === inputType.toUpperCase() && inputType.length > 1 && !Array.isArray(config[0])) continue;
|
|
423
|
+
widgetNames.push(name);
|
|
424
|
+
}
|
|
425
|
+
for (const [name, config] of Object.entries(optionalInputs) as [string, any][]) {
|
|
426
|
+
if (connectedNames.has(name)) continue;
|
|
427
|
+
const inputType = Array.isArray(config) ? config[0] : config;
|
|
428
|
+
if (typeof inputType === "string" && inputType === inputType.toUpperCase() && inputType.length > 1 && !Array.isArray(config[0])) continue;
|
|
429
|
+
widgetNames.push(name);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Assign values positionally
|
|
433
|
+
for (let i = 0; i < node.widgets_values.length && i < widgetNames.length; i++) {
|
|
434
|
+
inputs[widgetNames[i]!] = node.widgets_values[i];
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
apiWorkflow[nodeId] = { class_type: classType, inputs };
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return apiWorkflow;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// ---------------------------------------------------------------------------
|
|
445
|
+
// High-level workflow execution
|
|
446
|
+
// ---------------------------------------------------------------------------
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Run a ComfyUI workflow with optional runtime input overrides.
|
|
450
|
+
*
|
|
451
|
+
* Inputs can be provided in two forms:
|
|
452
|
+
*
|
|
453
|
+
* **Direct node mapping** (when no `inputMap` in options):
|
|
454
|
+
* ```
|
|
455
|
+
* { '3': { seed: 42 }, '6': { text: 'a cat' } }
|
|
456
|
+
* ```
|
|
457
|
+
*
|
|
458
|
+
* **Named inputs** (when `inputMap` is provided in options):
|
|
459
|
+
* ```
|
|
460
|
+
* inputs: { positive_prompt: 'a cat', seed: 42 }
|
|
461
|
+
* options.inputMap: {
|
|
462
|
+
* positive_prompt: { nodeId: '6', field: 'text' },
|
|
463
|
+
* seed: { nodeId: '3', field: 'seed' }
|
|
464
|
+
* }
|
|
465
|
+
* ```
|
|
466
|
+
*/
|
|
467
|
+
async runWorkflow(
|
|
468
|
+
workflow: Record<string, any>,
|
|
469
|
+
inputs?: Record<string, any>,
|
|
470
|
+
options: WorkflowRunOptions = {}
|
|
471
|
+
): Promise<WorkflowResult> {
|
|
472
|
+
// Auto-detect and convert UI format -> API format
|
|
473
|
+
const apiFormat = await this.toApiFormat(workflow);
|
|
474
|
+
const prompt = structuredClone(apiFormat);
|
|
475
|
+
|
|
476
|
+
// Apply inputs
|
|
477
|
+
if (inputs) {
|
|
478
|
+
if (options.inputMap) {
|
|
479
|
+
// Named input mode: resolve through the mapping
|
|
480
|
+
for (const [name, value] of Object.entries(inputs)) {
|
|
481
|
+
const mapping = options.inputMap[name];
|
|
482
|
+
if (!mapping) {
|
|
483
|
+
throw new Error(`No inputMap entry for "${name}". Available: ${Object.keys(options.inputMap).join(", ")}`);
|
|
484
|
+
}
|
|
485
|
+
if (!prompt[mapping.nodeId]) {
|
|
486
|
+
throw new Error(`Node "${mapping.nodeId}" not found in workflow (mapped from "${name}")`);
|
|
487
|
+
}
|
|
488
|
+
prompt[mapping.nodeId].inputs[mapping.field] = value;
|
|
489
|
+
}
|
|
490
|
+
} else {
|
|
491
|
+
// Direct node ID mapping
|
|
492
|
+
for (const [nodeId, fields] of Object.entries(inputs)) {
|
|
493
|
+
if (!prompt[nodeId]) {
|
|
494
|
+
throw new Error(`Node "${nodeId}" not found in workflow`);
|
|
495
|
+
}
|
|
496
|
+
if (typeof fields === "object" && fields !== null) {
|
|
497
|
+
Object.assign(prompt[nodeId].inputs, fields);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Queue the prompt
|
|
504
|
+
const { prompt_id: promptId } = await this.queuePrompt(prompt);
|
|
505
|
+
|
|
506
|
+
// Track execution
|
|
507
|
+
let outputs: Record<string, any>;
|
|
508
|
+
|
|
509
|
+
if (options.poll) {
|
|
510
|
+
outputs = await this.pollForCompletion(promptId, options.pollInterval ?? 1000);
|
|
511
|
+
} else {
|
|
512
|
+
outputs = await this.waitForCompletionWs(promptId);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Collect image outputs
|
|
516
|
+
const images: WorkflowResult["images"] = [];
|
|
517
|
+
for (const nodeOutputs of Object.values(outputs)) {
|
|
518
|
+
if (nodeOutputs.images) {
|
|
519
|
+
for (const img of nodeOutputs.images) {
|
|
520
|
+
images.push({
|
|
521
|
+
filename: img.filename,
|
|
522
|
+
subfolder: img.subfolder || "",
|
|
523
|
+
type: img.type || "output",
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Optionally download images to disk
|
|
530
|
+
if (options.outputDir && images.length) {
|
|
531
|
+
const { mkdir } = await import("fs/promises");
|
|
532
|
+
const { join } = await import("path");
|
|
533
|
+
await mkdir(options.outputDir, { recursive: true });
|
|
534
|
+
|
|
535
|
+
for (const img of images) {
|
|
536
|
+
const buf = await this.viewImage(img.filename, img.subfolder, img.type);
|
|
537
|
+
const localPath = join(options.outputDir, img.filename);
|
|
538
|
+
await Bun.write(localPath, buf);
|
|
539
|
+
img.localPath = localPath;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
return { promptId, outputs, images };
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
private async waitForCompletionWs(promptId: string): Promise<Record<string, any>> {
|
|
547
|
+
const needsConnect = !this.ws;
|
|
548
|
+
if (needsConnect) await this.connectWs();
|
|
549
|
+
|
|
550
|
+
return new Promise<Record<string, any>>((resolve, reject) => {
|
|
551
|
+
const onComplete = (data: any) => {
|
|
552
|
+
if (data.promptId === promptId) {
|
|
553
|
+
cleanup();
|
|
554
|
+
this.getHistory(promptId).then((history) => {
|
|
555
|
+
const entry = history[promptId];
|
|
556
|
+
resolve(entry?.outputs ?? {});
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
const onError = (data: any) => {
|
|
562
|
+
if (data.promptId === promptId) {
|
|
563
|
+
cleanup();
|
|
564
|
+
reject(new Error(data.exception_message || "Execution error"));
|
|
565
|
+
}
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
const cleanup = () => {
|
|
569
|
+
this.off("execution_complete", onComplete);
|
|
570
|
+
this.off("execution_error", onError);
|
|
571
|
+
if (needsConnect) this.disconnectWs();
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
this.on("execution_complete", onComplete);
|
|
575
|
+
this.on("execution_error", onError);
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
private async pollForCompletion(promptId: string, interval: number): Promise<Record<string, any>> {
|
|
580
|
+
while (true) {
|
|
581
|
+
const history = await this.getHistory(promptId);
|
|
582
|
+
const entry = history[promptId];
|
|
583
|
+
|
|
584
|
+
if (entry?.status?.completed || entry?.outputs) {
|
|
585
|
+
return entry.outputs ?? {};
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
if (entry?.status?.status_str === "error") {
|
|
589
|
+
throw new Error("Workflow execution failed");
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
export default clients.register("comfyui", ComfyUIClient);
|