n8n-nodes-comfyui-all 2.4.1 → 2.4.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/dist/nodes/ComfyUiTool/ComfyUiTool.node.d.ts +59 -34
- package/dist/nodes/ComfyUiTool/ComfyUiTool.node.d.ts.map +1 -1
- package/dist/nodes/ComfyUiTool/ComfyUiTool.node.js +213 -146
- package/dist/nodes/ComfyUiTool/ComfyUiTool.node.js.map +1 -1
- package/dist/nodes/agentToolHelpers.d.ts +32 -51
- package/dist/nodes/agentToolHelpers.d.ts.map +1 -1
- package/dist/nodes/agentToolHelpers.js +57 -183
- package/dist/nodes/agentToolHelpers.js.map +1 -1
- package/dist/nodes/types.d.ts +0 -38
- package/dist/nodes/types.d.ts.map +1 -1
- package/dist/nodes/workflowConfig.d.ts +27 -13
- package/dist/nodes/workflowConfig.d.ts.map +1 -1
- package/dist/nodes/workflowConfig.js +57 -148
- package/dist/nodes/workflowConfig.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ComfyUI Tool -
|
|
2
|
+
* ComfyUI Tool - Simplified workflow execution node for AI Agents
|
|
3
3
|
*
|
|
4
|
-
* This
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* This is a simplified node designed for AI Agent usage. It executes
|
|
5
|
+
* ComfyUI workflows provided by the user without making assumptions
|
|
6
|
+
* about workflow structure or node types.
|
|
7
7
|
*/
|
|
8
8
|
import { IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
|
|
9
9
|
export declare class ComfyUiTool {
|
|
@@ -28,22 +28,13 @@ export declare class ComfyUiTool {
|
|
|
28
28
|
subtitle: string;
|
|
29
29
|
notes: string[];
|
|
30
30
|
inputSample: {
|
|
31
|
-
|
|
31
|
+
workflowJson: string;
|
|
32
32
|
};
|
|
33
33
|
outputSample: {
|
|
34
34
|
json: {
|
|
35
35
|
success: boolean;
|
|
36
36
|
imageCount: number;
|
|
37
37
|
imageUrls: string[];
|
|
38
|
-
prompt: string;
|
|
39
|
-
mode: string;
|
|
40
|
-
parameters: {
|
|
41
|
-
width: number;
|
|
42
|
-
height: number;
|
|
43
|
-
steps: number;
|
|
44
|
-
cfg: number;
|
|
45
|
-
seed: number;
|
|
46
|
-
};
|
|
47
38
|
};
|
|
48
39
|
};
|
|
49
40
|
properties: ({
|
|
@@ -53,9 +44,26 @@ export declare class ComfyUiTool {
|
|
|
53
44
|
required: boolean;
|
|
54
45
|
default: string;
|
|
55
46
|
description: string;
|
|
47
|
+
typeOptions?: undefined;
|
|
48
|
+
placeholder?: undefined;
|
|
49
|
+
hint?: undefined;
|
|
50
|
+
minValue?: undefined;
|
|
51
|
+
maxValue?: undefined;
|
|
52
|
+
options?: undefined;
|
|
53
|
+
} | {
|
|
54
|
+
displayName: string;
|
|
55
|
+
name: string;
|
|
56
|
+
type: string;
|
|
57
|
+
typeOptions: {
|
|
58
|
+
rows: number;
|
|
59
|
+
};
|
|
60
|
+
required: boolean;
|
|
61
|
+
default: string;
|
|
62
|
+
description: string;
|
|
63
|
+
placeholder: string;
|
|
64
|
+
hint: string;
|
|
56
65
|
minValue?: undefined;
|
|
57
66
|
maxValue?: undefined;
|
|
58
|
-
placeholder?: undefined;
|
|
59
67
|
options?: undefined;
|
|
60
68
|
} | {
|
|
61
69
|
displayName: string;
|
|
@@ -66,7 +74,9 @@ export declare class ComfyUiTool {
|
|
|
66
74
|
minValue: number;
|
|
67
75
|
maxValue: number;
|
|
68
76
|
required?: undefined;
|
|
77
|
+
typeOptions?: undefined;
|
|
69
78
|
placeholder?: undefined;
|
|
79
|
+
hint?: undefined;
|
|
70
80
|
options?: undefined;
|
|
71
81
|
} | {
|
|
72
82
|
displayName: string;
|
|
@@ -76,6 +86,8 @@ export declare class ComfyUiTool {
|
|
|
76
86
|
description: string;
|
|
77
87
|
placeholder: string;
|
|
78
88
|
required?: undefined;
|
|
89
|
+
typeOptions?: undefined;
|
|
90
|
+
hint?: undefined;
|
|
79
91
|
minValue?: undefined;
|
|
80
92
|
maxValue?: undefined;
|
|
81
93
|
options?: undefined;
|
|
@@ -85,29 +97,42 @@ export declare class ComfyUiTool {
|
|
|
85
97
|
type: string;
|
|
86
98
|
placeholder: string;
|
|
87
99
|
default: {};
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
name: string;
|
|
91
|
-
type: string;
|
|
92
|
-
typeOptions: {
|
|
93
|
-
rows: number;
|
|
94
|
-
};
|
|
95
|
-
default: string;
|
|
96
|
-
description: string;
|
|
97
|
-
minValue?: undefined;
|
|
98
|
-
maxValue?: undefined;
|
|
99
|
-
} | {
|
|
100
|
+
description: string;
|
|
101
|
+
options: {
|
|
100
102
|
displayName: string;
|
|
101
103
|
name: string;
|
|
102
104
|
type: string;
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
105
|
+
placeholder: string;
|
|
106
|
+
default: {};
|
|
107
|
+
options: ({
|
|
108
|
+
displayName: string;
|
|
109
|
+
name: string;
|
|
110
|
+
type: string;
|
|
111
|
+
default: string;
|
|
112
|
+
description: string;
|
|
113
|
+
placeholder: string;
|
|
114
|
+
required?: undefined;
|
|
115
|
+
} | {
|
|
116
|
+
displayName: string;
|
|
117
|
+
name: string;
|
|
118
|
+
type: string;
|
|
119
|
+
required: boolean;
|
|
120
|
+
default: string;
|
|
121
|
+
description: string;
|
|
122
|
+
placeholder: string;
|
|
123
|
+
} | {
|
|
124
|
+
displayName: string;
|
|
125
|
+
name: string;
|
|
126
|
+
type: string;
|
|
127
|
+
default: string;
|
|
128
|
+
description: string;
|
|
129
|
+
placeholder?: undefined;
|
|
130
|
+
required?: undefined;
|
|
131
|
+
})[];
|
|
132
|
+
}[];
|
|
109
133
|
required?: undefined;
|
|
110
|
-
|
|
134
|
+
typeOptions?: undefined;
|
|
135
|
+
hint?: undefined;
|
|
111
136
|
minValue?: undefined;
|
|
112
137
|
maxValue?: undefined;
|
|
113
138
|
})[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ComfyUiTool.node.d.ts","sourceRoot":"","sources":["../../../nodes/ComfyUiTool/ComfyUiTool.node.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAEnB,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"ComfyUiTool.node.d.ts","sourceRoot":"","sources":["../../../nodes/ComfyUiTool/ComfyUiTool.node.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAEnB,MAAM,cAAc,CAAC;AActB,qBAAa,WAAW;IACtB;;OAEG;IACH,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MA2HT;IAEF;;OAEG;IACG,OAAO,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC;CA+MxE"}
|
|
@@ -1,11 +1,44 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* ComfyUI Tool -
|
|
3
|
+
* ComfyUI Tool - Simplified workflow execution node for AI Agents
|
|
4
4
|
*
|
|
5
|
-
* This
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* This is a simplified node designed for AI Agent usage. It executes
|
|
6
|
+
* ComfyUI workflows provided by the user without making assumptions
|
|
7
|
+
* about workflow structure or node types.
|
|
8
8
|
*/
|
|
9
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
21
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
22
|
+
}) : function(o, v) {
|
|
23
|
+
o["default"] = v;
|
|
24
|
+
});
|
|
25
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
26
|
+
var ownKeys = function(o) {
|
|
27
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
28
|
+
var ar = [];
|
|
29
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
30
|
+
return ar;
|
|
31
|
+
};
|
|
32
|
+
return ownKeys(o);
|
|
33
|
+
};
|
|
34
|
+
return function (mod) {
|
|
35
|
+
if (mod && mod.__esModule) return mod;
|
|
36
|
+
var result = {};
|
|
37
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
38
|
+
__setModuleDefault(result, mod);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
9
42
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
43
|
exports.ComfyUiTool = void 0;
|
|
11
44
|
const n8n_workflow_1 = require("n8n-workflow");
|
|
@@ -27,38 +60,28 @@ class ComfyUiTool {
|
|
|
27
60
|
group: ['transform'],
|
|
28
61
|
version: [1],
|
|
29
62
|
defaultVersion: 1,
|
|
30
|
-
description: '
|
|
63
|
+
description: 'Execute ComfyUI workflows. Requires a workflow JSON exported from ComfyUI.',
|
|
31
64
|
defaults: {
|
|
32
65
|
name: 'ComfyUI Tool',
|
|
33
66
|
},
|
|
34
67
|
usableAsTool: true,
|
|
35
68
|
inputs: ['main'],
|
|
36
69
|
outputs: ['main'],
|
|
37
|
-
subtitle: '
|
|
70
|
+
subtitle: 'ComfyUI Workflow Executor',
|
|
38
71
|
notes: [
|
|
39
|
-
'
|
|
40
|
-
'
|
|
41
|
-
'
|
|
42
|
-
'
|
|
43
|
-
'Examples: "A beautiful sunset, size:1024x768, steps:30"',
|
|
72
|
+
'You must provide a ComfyUI workflow JSON (API Format)',
|
|
73
|
+
'In ComfyUI: Design your workflow → Click "Save (API Format)" → Copy JSON → Paste here',
|
|
74
|
+
'Works with ANY ComfyUI workflow: text-to-image, image-to-image, video generation, etc.',
|
|
75
|
+
'Configure parameters directly in your workflow JSON or use parameter overrides',
|
|
44
76
|
],
|
|
45
77
|
inputSample: {
|
|
46
|
-
|
|
78
|
+
workflowJson: '{ "3": { "inputs": {}, "class_type": "KSampler" } }',
|
|
47
79
|
},
|
|
48
80
|
outputSample: {
|
|
49
81
|
json: {
|
|
50
82
|
success: true,
|
|
51
83
|
imageCount: 1,
|
|
52
84
|
imageUrls: ['http://127.0.0.1:8188/view?filename=ComfyUI_00001.png'],
|
|
53
|
-
prompt: 'A beautiful landscape painting',
|
|
54
|
-
mode: 'text-to-image',
|
|
55
|
-
parameters: {
|
|
56
|
-
width: 512,
|
|
57
|
-
height: 512,
|
|
58
|
-
steps: 20,
|
|
59
|
-
cfg: 8,
|
|
60
|
-
seed: 123456789,
|
|
61
|
-
},
|
|
62
85
|
},
|
|
63
86
|
},
|
|
64
87
|
properties: [
|
|
@@ -70,75 +93,84 @@ class ComfyUiTool {
|
|
|
70
93
|
default: 'http://127.0.0.1:8188',
|
|
71
94
|
description: 'URL of ComfyUI server',
|
|
72
95
|
},
|
|
96
|
+
{
|
|
97
|
+
displayName: 'Workflow JSON',
|
|
98
|
+
name: 'workflowJson',
|
|
99
|
+
type: 'string',
|
|
100
|
+
typeOptions: {
|
|
101
|
+
rows: 20,
|
|
102
|
+
},
|
|
103
|
+
required: true,
|
|
104
|
+
default: '',
|
|
105
|
+
description: 'Paste your ComfyUI workflow JSON (API Format). Required field - no built-in templates.',
|
|
106
|
+
placeholder: 'Paste your ComfyUI workflow JSON (API Format) here...\n\n{\n "3": {\n "inputs": {\n "seed": 123456789,\n ...\n },\n "class_type": "KSampler"\n }\n}',
|
|
107
|
+
hint: 'Required: Export your workflow from ComfyUI in API format and paste it here.',
|
|
108
|
+
},
|
|
73
109
|
{
|
|
74
110
|
displayName: 'Timeout (Seconds)',
|
|
75
111
|
name: 'timeout',
|
|
76
112
|
type: 'number',
|
|
77
|
-
default:
|
|
78
|
-
description: 'Maximum time to wait for
|
|
113
|
+
default: 300,
|
|
114
|
+
description: 'Maximum time to wait for workflow execution (in seconds). Default: 300 (5 minutes).',
|
|
79
115
|
minValue: 10,
|
|
80
|
-
maxValue:
|
|
116
|
+
maxValue: 3600,
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
displayName: 'Load Image Node ID',
|
|
120
|
+
name: 'loadImageNodeId',
|
|
121
|
+
type: 'string',
|
|
122
|
+
default: '',
|
|
123
|
+
description: 'Optional: Node ID of LoadImage node if your workflow uses input images. Leave empty if not using image input.',
|
|
124
|
+
placeholder: 'e.g., 5, 12, load_image_1',
|
|
81
125
|
},
|
|
82
126
|
{
|
|
83
|
-
displayName: '
|
|
84
|
-
name: '
|
|
127
|
+
displayName: 'Image Data (Base64 or Path)',
|
|
128
|
+
name: 'imageData',
|
|
85
129
|
type: 'string',
|
|
86
|
-
default: '
|
|
87
|
-
description: '
|
|
88
|
-
placeholder: '
|
|
130
|
+
default: '',
|
|
131
|
+
description: 'Optional: Image data as base64 string (with or without data:image/... prefix) or file path. Use this when passing images from AI Agents or as text parameters.',
|
|
132
|
+
placeholder: 'data:image/png;base64,iVBORw0KG... or /tmp/image.png',
|
|
89
133
|
},
|
|
90
134
|
{
|
|
91
|
-
displayName: '
|
|
92
|
-
name: '
|
|
93
|
-
type: '
|
|
94
|
-
placeholder: 'Add
|
|
135
|
+
displayName: 'Parameter Overrides',
|
|
136
|
+
name: 'parameterOverrides',
|
|
137
|
+
type: 'fixedCollection',
|
|
138
|
+
placeholder: 'Add Parameter Override',
|
|
95
139
|
default: {},
|
|
140
|
+
description: 'Override specific node parameters at runtime. Useful for dynamic values from input data.',
|
|
96
141
|
options: [
|
|
97
142
|
{
|
|
98
|
-
displayName: '
|
|
99
|
-
name: '
|
|
100
|
-
type: '
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
default: 20,
|
|
130
|
-
description: 'Default sampling steps when not specified in query',
|
|
131
|
-
minValue: 1,
|
|
132
|
-
maxValue: 150,
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
displayName: 'Default Width',
|
|
136
|
-
name: 'defaultWidth',
|
|
137
|
-
type: 'number',
|
|
138
|
-
default: 512,
|
|
139
|
-
description: 'Default image width when not specified in query',
|
|
140
|
-
minValue: 64,
|
|
141
|
-
maxValue: 4096,
|
|
143
|
+
displayName: 'Parameter Override',
|
|
144
|
+
name: 'parameterOverride',
|
|
145
|
+
type: 'collection',
|
|
146
|
+
placeholder: 'Add Override',
|
|
147
|
+
default: {},
|
|
148
|
+
options: [
|
|
149
|
+
{
|
|
150
|
+
displayName: 'Node ID',
|
|
151
|
+
name: 'nodeId',
|
|
152
|
+
type: 'string',
|
|
153
|
+
default: '',
|
|
154
|
+
description: 'The node ID to update (e.g., "3", "6", "15")',
|
|
155
|
+
placeholder: 'Node ID from workflow JSON',
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
displayName: 'Parameter Path',
|
|
159
|
+
name: 'paramPath',
|
|
160
|
+
type: 'string',
|
|
161
|
+
required: true,
|
|
162
|
+
default: '',
|
|
163
|
+
description: 'Path to the parameter (e.g., "inputs.text", "inputs.seed", "inputs.steps")',
|
|
164
|
+
placeholder: 'inputs.parameter_name',
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
displayName: 'Value',
|
|
168
|
+
name: 'value',
|
|
169
|
+
type: 'string',
|
|
170
|
+
default: '',
|
|
171
|
+
description: 'The new value. Can be a static value or an expression (e.g., "{{ $JSON.prompt }}").',
|
|
172
|
+
},
|
|
173
|
+
],
|
|
142
174
|
},
|
|
143
175
|
],
|
|
144
176
|
},
|
|
@@ -152,58 +184,35 @@ class ComfyUiTool {
|
|
|
152
184
|
const logger = (0, logger_1.createLogger)(this.logger);
|
|
153
185
|
// Get node parameters
|
|
154
186
|
const comfyUiUrl = this.getNodeParameter('comfyUiUrl', 0);
|
|
187
|
+
const workflowJson = this.getNodeParameter('workflowJson', 0);
|
|
155
188
|
const timeout = this.getNodeParameter('timeout', 0);
|
|
156
|
-
const
|
|
157
|
-
const
|
|
158
|
-
const
|
|
189
|
+
const loadImageNodeId = this.getNodeParameter('loadImageNodeId', 0);
|
|
190
|
+
const imageData = this.getNodeParameter('imageData', 0);
|
|
191
|
+
const parameterOverrides = this.getNodeParameter('parameterOverrides', 0);
|
|
159
192
|
// Validate URL
|
|
160
193
|
if (!(0, validation_1.validateUrl)(comfyUiUrl)) {
|
|
161
194
|
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Invalid ComfyUI URL. Must be a valid HTTP/HTTPS URL.');
|
|
162
195
|
}
|
|
196
|
+
// Parse and validate workflow
|
|
197
|
+
let workflow;
|
|
198
|
+
try {
|
|
199
|
+
workflow = (0, workflowConfig_1.parseWorkflow)(workflowJson);
|
|
200
|
+
const validation = (0, validation_1.validateComfyUIWorkflow)(workflowJson);
|
|
201
|
+
if (!validation.valid && validation.error) {
|
|
202
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Invalid workflow: ${validation.error}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
if (error instanceof n8n_workflow_1.NodeOperationError) {
|
|
207
|
+
throw error;
|
|
208
|
+
}
|
|
209
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to parse workflow JSON. Please ensure it is valid ComfyUI API format.\nError: ${error instanceof Error ? error.message : String(error)}`);
|
|
210
|
+
}
|
|
211
|
+
logger.info('Processing ComfyUI workflow execution request', { comfyUiUrl, timeout });
|
|
163
212
|
// Get input data
|
|
164
213
|
const inputData = this.getInputData();
|
|
165
|
-
if (!inputData || inputData.length === 0) {
|
|
166
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'No input data provided. Please provide a query text.');
|
|
167
|
-
}
|
|
168
|
-
// Check if input contains binary image data
|
|
169
214
|
const hasInputImage = (0, agentToolHelpers_1.hasBinaryData)(inputData);
|
|
170
215
|
let uploadedImageFilename = null;
|
|
171
|
-
// Get query from input
|
|
172
|
-
const firstItem = inputData[0];
|
|
173
|
-
const query = firstItem.json.query ||
|
|
174
|
-
firstItem.json.text ||
|
|
175
|
-
firstItem.json.prompt ||
|
|
176
|
-
'';
|
|
177
|
-
if (!query || query.trim().length === 0) {
|
|
178
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'No query found in input. Please provide a "query", "text", or "prompt" field with a description of the image you want to generate.');
|
|
179
|
-
}
|
|
180
|
-
// Determine mode based on input
|
|
181
|
-
const mode = hasInputImage ? 'image-to-image' : 'text-to-image';
|
|
182
|
-
logger.info('Processing image generation request', { query, comfyUiUrl, timeout, mode });
|
|
183
|
-
// Parse the query to extract parameters
|
|
184
|
-
const params = (0, agentToolHelpers_1.parseInput)(query, {
|
|
185
|
-
negativePrompt: defaultNegativePrompt,
|
|
186
|
-
width: defaultWidth,
|
|
187
|
-
height: defaultHeight,
|
|
188
|
-
steps: defaultSteps,
|
|
189
|
-
cfg: defaultCfg,
|
|
190
|
-
});
|
|
191
|
-
logger.debug('Parsed parameters', {
|
|
192
|
-
prompt: params.prompt,
|
|
193
|
-
width: params.width,
|
|
194
|
-
height: params.height,
|
|
195
|
-
steps: params.steps,
|
|
196
|
-
cfg: params.cfg,
|
|
197
|
-
seed: params.seed,
|
|
198
|
-
});
|
|
199
|
-
// Get workflow template
|
|
200
|
-
let workflowTemplate;
|
|
201
|
-
if (customWorkflow && customWorkflow.trim().length > 0) {
|
|
202
|
-
workflowTemplate = (0, validation_1.safeJsonParse)(customWorkflow, 'Custom workflow JSON');
|
|
203
|
-
}
|
|
204
|
-
else {
|
|
205
|
-
workflowTemplate = (0, workflowConfig_1.getWorkflowTemplate)({ mode });
|
|
206
|
-
}
|
|
207
216
|
// Create ComfyUI client
|
|
208
217
|
const client = new ComfyUiClient_1.ComfyUIClient({
|
|
209
218
|
baseUrl: comfyUiUrl,
|
|
@@ -212,9 +221,8 @@ class ComfyUiTool {
|
|
|
212
221
|
logger,
|
|
213
222
|
});
|
|
214
223
|
try {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
if (hasInputImage) {
|
|
224
|
+
// If input contains image and LoadImage node ID is specified, upload it
|
|
225
|
+
if (hasInputImage && loadImageNodeId) {
|
|
218
226
|
const binaryKey = (0, agentToolHelpers_1.getFirstBinaryKey)(inputData);
|
|
219
227
|
if (!binaryKey) {
|
|
220
228
|
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Binary data found but no binary key detected.');
|
|
@@ -226,51 +234,110 @@ class ComfyUiTool {
|
|
|
226
234
|
logger.info('Uploading input image to ComfyUI', {
|
|
227
235
|
fileName: binaryData.fileName,
|
|
228
236
|
mimeType: binaryData.mimeType,
|
|
237
|
+
nodeId: loadImageNodeId,
|
|
229
238
|
});
|
|
230
239
|
// Convert base64 to buffer
|
|
231
240
|
const buffer = Buffer.from(binaryData.data, 'base64');
|
|
232
241
|
// Upload image to ComfyUI
|
|
233
242
|
uploadedImageFilename = await client.uploadImage(buffer, binaryData.fileName || 'input_image.png');
|
|
234
243
|
logger.info('Successfully uploaded input image', { filename: uploadedImageFilename });
|
|
244
|
+
// Update workflow with uploaded image filename
|
|
245
|
+
workflow = (0, agentToolHelpers_1.updateNodeParameter)(workflow, loadImageNodeId, 'inputs.image', uploadedImageFilename);
|
|
235
246
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
247
|
+
else if (imageData && loadImageNodeId) {
|
|
248
|
+
// Handle image data passed as base64 string or file path (for AI Agents)
|
|
249
|
+
logger.info('Processing image data from parameter', { dataLength: imageData.length, nodeId: loadImageNodeId });
|
|
250
|
+
let buffer;
|
|
251
|
+
let filename;
|
|
252
|
+
if (imageData.startsWith('data:image')) {
|
|
253
|
+
// Data URL format: data:image/png;base64,iVBORw0KG...
|
|
254
|
+
const [mimeType, base64Data] = imageData.split(',');
|
|
255
|
+
if (!base64Data) {
|
|
256
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Invalid data URL format. Expected: data:image/<type>;base64,<data>');
|
|
257
|
+
}
|
|
258
|
+
buffer = Buffer.from(base64Data, 'base64');
|
|
259
|
+
const extension = mimeType.split('/')[1]?.split(';')[0] || 'png';
|
|
260
|
+
filename = `agent_image.${extension}`;
|
|
261
|
+
logger.info('Decoded image from data URL', { mimeType: mimeType.split(';')[0], size: buffer.length });
|
|
262
|
+
}
|
|
263
|
+
else if (imageData.startsWith('/') || imageData.startsWith('./') || imageData.startsWith('../')) {
|
|
264
|
+
// File path
|
|
265
|
+
const fs = await Promise.resolve().then(() => __importStar(require('fs/promises')));
|
|
266
|
+
try {
|
|
267
|
+
buffer = await fs.readFile(imageData);
|
|
268
|
+
filename = imageData.split('/').pop() || 'agent_image.png';
|
|
269
|
+
logger.info('Read image from file', { path: imageData, size: buffer.length });
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to read image file from path "${imageData}": ${error instanceof Error ? error.message : String(error)}`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
// Plain base64 string (without data:image/... prefix)
|
|
277
|
+
try {
|
|
278
|
+
buffer = Buffer.from(imageData, 'base64');
|
|
279
|
+
filename = 'agent_image.png';
|
|
280
|
+
logger.info('Decoded image from base64 string', { size: buffer.length });
|
|
281
|
+
}
|
|
282
|
+
catch (error) {
|
|
283
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to decode base64 image data. Make sure the data is valid base64 or a data URL (data:image/...;base64,...). Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
// Validate buffer
|
|
287
|
+
if (!buffer || buffer.length === 0) {
|
|
288
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Decoded image data is empty. Please check the image data.');
|
|
289
|
+
}
|
|
290
|
+
// Upload image to ComfyUI
|
|
291
|
+
uploadedImageFilename = await client.uploadImage(buffer, filename);
|
|
292
|
+
logger.info('Successfully uploaded image from parameter', { filename: uploadedImageFilename });
|
|
293
|
+
// Update workflow with uploaded image filename
|
|
294
|
+
workflow = (0, agentToolHelpers_1.updateNodeParameter)(workflow, loadImageNodeId, 'inputs.image', uploadedImageFilename);
|
|
241
295
|
}
|
|
296
|
+
// Apply parameter overrides if provided
|
|
297
|
+
if (parameterOverrides?.parameterOverride && parameterOverrides.parameterOverride.length > 0) {
|
|
298
|
+
logger.info('Applying parameter overrides', {
|
|
299
|
+
count: parameterOverrides.parameterOverride.length,
|
|
300
|
+
});
|
|
301
|
+
for (const override of parameterOverrides.parameterOverride) {
|
|
302
|
+
const { nodeId, paramPath, value } = override;
|
|
303
|
+
// Evaluate expressions if needed
|
|
304
|
+
let evaluatedValue = value;
|
|
305
|
+
if (value && value.includes('{{') && value.includes('}}')) {
|
|
306
|
+
try {
|
|
307
|
+
evaluatedValue = this.evaluateExpression(value, 0);
|
|
308
|
+
logger.debug('Evaluated expression', { nodeId, paramPath, result: evaluatedValue });
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
logger.warn(`Failed to evaluate expression for ${nodeId}.${paramPath}, using raw value`, { error });
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
workflow = (0, agentToolHelpers_1.updateNodeParameter)(workflow, nodeId, paramPath, evaluatedValue);
|
|
315
|
+
logger.debug('Applied parameter override', { nodeId, paramPath, value: evaluatedValue });
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
logger.info('Starting ComfyUI workflow execution');
|
|
242
319
|
// Execute workflow
|
|
243
320
|
const result = await client.executeWorkflow(workflow);
|
|
244
321
|
if (!result.success) {
|
|
245
322
|
logger.error('Workflow execution failed', result.error);
|
|
246
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to
|
|
323
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to execute workflow: ${result.error}`);
|
|
247
324
|
}
|
|
248
|
-
//
|
|
325
|
+
// Build URLs
|
|
249
326
|
const imageUrls = result.images ? result.images.map(img => `${comfyUiUrl}${img}`) : [];
|
|
250
327
|
const videoUrls = result.videos ? result.videos.map(vid => `${comfyUiUrl}${vid}`) : [];
|
|
251
|
-
const
|
|
328
|
+
const outputJson = {
|
|
252
329
|
success: true,
|
|
253
330
|
imageUrls,
|
|
254
331
|
videoUrls,
|
|
255
332
|
imageCount: result.images?.length || 0,
|
|
256
333
|
videoCount: result.videos?.length || 0,
|
|
257
|
-
prompt: params.prompt,
|
|
258
|
-
mode: mode,
|
|
259
|
-
parameters: {
|
|
260
|
-
width: params.width,
|
|
261
|
-
height: params.height,
|
|
262
|
-
steps: params.steps,
|
|
263
|
-
cfg: params.cfg,
|
|
264
|
-
seed: params.seed,
|
|
265
|
-
negative_prompt: params.negative_prompt,
|
|
266
|
-
},
|
|
267
334
|
};
|
|
268
|
-
logger.info('
|
|
269
|
-
imageCount:
|
|
270
|
-
|
|
335
|
+
logger.info('Workflow execution completed successfully', {
|
|
336
|
+
imageCount: outputJson.imageCount,
|
|
337
|
+
videoCount: outputJson.videoCount,
|
|
271
338
|
});
|
|
272
|
-
// Return result (no binary data for AI Agent)
|
|
273
|
-
return [this.helpers.constructExecutionMetaData([{ json:
|
|
339
|
+
// Return result (no binary data for AI Agent - URLs only)
|
|
340
|
+
return [this.helpers.constructExecutionMetaData([{ json: outputJson }], { itemData: { item: 0 } })];
|
|
274
341
|
}
|
|
275
342
|
catch (error) {
|
|
276
343
|
logger.error('Error during workflow execution', error);
|