n8n-nodes-commandos-image 0.1.8 → 0.1.9

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.
@@ -1,3 +1,7 @@
1
+ /**
2
+ * Version 0.1.9: Forced lightweight formats (JPEG/JPG) for ALL models.
3
+ * Removed the Output Format option from the UI to avoid confusion.
4
+ */
1
5
  import type { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
2
6
  export declare class CommandosImage implements INodeType {
3
7
  description: INodeTypeDescription;
@@ -15,9 +15,6 @@ const resolveGenerationUrl = (resolvedModel) => {
15
15
  }
16
16
  return GENERATION_URL_DEFAULT;
17
17
  };
18
- /**
19
- * Maps standard ratios to specific model requirements
20
- */
21
18
  const mapRatio = (model, ratio) => {
22
19
  if (model.includes('seedream')) {
23
20
  if (ratio === '2:3')
@@ -31,7 +28,6 @@ const buildGenerationRequest = ({ model, prompt, ratio, references, quality = 'b
31
28
  const hasReferences = references.length > 0;
32
29
  const requestedModel = model;
33
30
  let resolvedModel = model;
34
- // Model Resolution
35
31
  if (requestedModel === 'flux-pro') {
36
32
  resolvedModel = hasReferences ? 'flux-2/pro-image-to-image' : 'flux-2/pro-text-to-image';
37
33
  }
@@ -44,12 +40,8 @@ const buildGenerationRequest = ({ model, prompt, ratio, references, quality = 'b
44
40
  else if (requestedModel === 'seedream') {
45
41
  resolvedModel = hasReferences ? 'seedream/4.5-edit' : 'seedream/4.5';
46
42
  }
47
- // Fallback for MJ or generic seedream
48
43
  if (!hasReferences && (requestedModel === 'seedream' || requestedModel === 'midjourney')) {
49
- if (requestedModel === 'seedream') {
50
- // Handled above for 4.5
51
- }
52
- else if (requestedModel === 'midjourney') {
44
+ if (requestedModel === 'midjourney') {
53
45
  resolvedModel = 'midjourney';
54
46
  }
55
47
  }
@@ -59,7 +51,7 @@ const buildGenerationRequest = ({ model, prompt, ratio, references, quality = 'b
59
51
  filesUrl: references,
60
52
  prompt,
61
53
  size: ratio,
62
- callBackUrl: 'https://api.comandos.ai/internal/kie/callback', // Fixed to internal
54
+ callBackUrl: 'https://api.comandos.ai/internal/kie/callback',
63
55
  fallbackModel: 'FLUX_MAX',
64
56
  };
65
57
  }
@@ -70,6 +62,7 @@ const buildGenerationRequest = ({ model, prompt, ratio, references, quality = 'b
70
62
  prompt,
71
63
  aspect_ratio: ratio,
72
64
  resolution,
65
+ output_format: 'jpeg', // Force jpeg
73
66
  ...(hasReferences ? { input_urls: references } : {}),
74
67
  },
75
68
  };
@@ -81,6 +74,7 @@ const buildGenerationRequest = ({ model, prompt, ratio, references, quality = 'b
81
74
  prompt,
82
75
  aspect_ratio: mapRatio(resolvedModel, ratio),
83
76
  quality,
77
+ output_format: 'jpeg', // Force jpeg
84
78
  ...(hasReferences ? { image_urls: references } : {}),
85
79
  },
86
80
  };
@@ -92,7 +86,7 @@ const buildGenerationRequest = ({ model, prompt, ratio, references, quality = 'b
92
86
  prompt,
93
87
  aspect_ratio: ratio,
94
88
  resolution,
95
- output_format: 'jpg', // ALWAYS JPG for Nano Banana Pro (lightweight)
89
+ output_format: 'jpg', // Force jpg for pro
96
90
  ...(hasReferences ? { image_input: references } : {}),
97
91
  },
98
92
  };
@@ -103,7 +97,7 @@ const buildGenerationRequest = ({ model, prompt, ratio, references, quality = 'b
103
97
  input: {
104
98
  prompt,
105
99
  image_size: ratio,
106
- output_format: 'jpeg', // ALWAYS JPEG for Nano Banana (lightweight)
100
+ output_format: 'jpeg', // Force jpeg
107
101
  ...(hasReferences ? { image_urls: references } : {}),
108
102
  },
109
103
  };
@@ -119,13 +113,13 @@ const buildGenerationRequest = ({ model, prompt, ratio, references, quality = 'b
119
113
  };
120
114
  }
121
115
  else {
122
- // Default fallback (v4 edit style)
123
116
  body = {
124
117
  model: requestedModel === 'gpt4o-image' ? 'gpt-4o' : 'bytedance/seedream-v4-edit',
125
118
  input: {
126
119
  image_urls: references,
127
120
  prompt,
128
121
  image_size: mapRatio('seedream', ratio),
122
+ output_format: 'jpeg', // Default jpeg
129
123
  },
130
124
  };
131
125
  }
@@ -145,7 +139,6 @@ const extractReferences = (raw, model) => {
145
139
  }
146
140
  const collection = raw;
147
141
  const items = Array.isArray(collection.reference) ? collection.reference : [];
148
- // Dynamic limit based on model docs
149
142
  let limit = 8;
150
143
  if (model === 'seedream')
151
144
  limit = 14;
@@ -185,14 +178,8 @@ class CommandosImage {
185
178
  type: 'options',
186
179
  noDataExpression: true,
187
180
  options: [
188
- {
189
- name: 'Create Task',
190
- value: 'create',
191
- },
192
- {
193
- name: 'Check Status',
194
- value: 'status',
195
- },
181
+ { name: 'Create Task', value: 'create' },
182
+ { name: 'Check Status', value: 'status' },
196
183
  ],
197
184
  default: 'create',
198
185
  },
@@ -209,25 +196,15 @@ class CommandosImage {
209
196
  { name: 'Midjourney', value: 'midjourney' },
210
197
  ],
211
198
  default: 'seedream',
212
- displayOptions: {
213
- show: {
214
- operation: ['create'],
215
- },
216
- },
199
+ displayOptions: { show: { operation: ['create'] } },
217
200
  },
218
201
  {
219
202
  displayName: 'Prompt',
220
203
  name: 'prompt',
221
204
  type: 'string',
222
205
  default: '',
223
- typeOptions: {
224
- rows: 4,
225
- },
226
- displayOptions: {
227
- show: {
228
- operation: ['create'],
229
- },
230
- },
206
+ typeOptions: { rows: 4 },
207
+ displayOptions: { show: { operation: ['create'] } },
231
208
  },
232
209
  {
233
210
  displayName: 'Ratio',
@@ -245,11 +222,7 @@ class CommandosImage {
245
222
  { name: '21:9', value: '21:9' },
246
223
  ],
247
224
  default: '2:3',
248
- displayOptions: {
249
- show: {
250
- operation: ['create'],
251
- },
252
- },
225
+ displayOptions: { show: { operation: ['create'] } },
253
226
  },
254
227
  {
255
228
  displayName: 'Quality',
@@ -290,9 +263,7 @@ class CommandosImage {
290
263
  name: 'references',
291
264
  type: 'fixedCollection',
292
265
  default: {},
293
- typeOptions: {
294
- multipleValues: true,
295
- },
266
+ typeOptions: { multipleValues: true },
296
267
  options: [
297
268
  {
298
269
  name: 'reference',
@@ -307,22 +278,14 @@ class CommandosImage {
307
278
  ],
308
279
  },
309
280
  ],
310
- displayOptions: {
311
- show: {
312
- operation: ['create'],
313
- },
314
- },
281
+ displayOptions: { show: { operation: ['create'] } },
315
282
  },
316
283
  {
317
284
  displayName: 'Task ID',
318
285
  name: 'taskId',
319
286
  type: 'string',
320
287
  default: '',
321
- displayOptions: {
322
- show: {
323
- operation: ['status'],
324
- },
325
- },
288
+ displayOptions: { show: { operation: ['status'] } },
326
289
  },
327
290
  ],
328
291
  };
@@ -352,17 +315,7 @@ class CommandosImage {
352
315
  if (!prompt) {
353
316
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Prompt is required', { itemIndex: i });
354
317
  }
355
- const request = buildGenerationRequest({
356
- model,
357
- prompt,
358
- ratio,
359
- references,
360
- quality,
361
- resolution
362
- });
363
- if (!request.url) {
364
- throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'URL генерации не настроен. Проверь переменные окружения COMMANDOS_IMAGE_URL_DEFAULT/COMMANDOS_IMAGE_URL_GPT4O/COMMANDOS_IMAGE_URL_MJ', { itemIndex: i });
365
- }
318
+ const request = buildGenerationRequest({ model, prompt, ratio, references, quality, resolution });
366
319
  const payload = {
367
320
  url: request.url,
368
321
  body: request.body,
@@ -371,7 +324,7 @@ class CommandosImage {
371
324
  ratio: request.ratio,
372
325
  references: request.references,
373
326
  hasReferences: request.hasReferences,
374
- _v: '0.1.8'
327
+ _v: '0.1.9'
375
328
  };
376
329
  const response = await this.helpers.request({
377
330
  method: 'POST',
@@ -380,10 +333,7 @@ class CommandosImage {
380
333
  'Content-Type': 'application/json',
381
334
  'X-License-Key': licenseKey,
382
335
  },
383
- body: {
384
- process_type: 'IMAGE_GENERATION',
385
- payload,
386
- },
336
+ body: { process_type: 'IMAGE_GENERATION', payload },
387
337
  json: true,
388
338
  });
389
339
  results.push({ json: response });
@@ -396,18 +346,15 @@ class CommandosImage {
396
346
  const response = await this.helpers.request({
397
347
  method: 'GET',
398
348
  url: `${COMMANDOS_API_URL}/tasks/${encodeURIComponent(taskId)}`,
399
- headers: {
400
- 'X-License-Key': licenseKey,
401
- },
349
+ headers: { 'X-License-Key': licenseKey },
402
350
  json: true,
403
351
  });
404
352
  results.push({ json: response });
405
353
  }
406
354
  }
407
355
  catch (error) {
408
- if (error instanceof n8n_workflow_1.NodeOperationError) {
356
+ if (error instanceof n8n_workflow_1.NodeOperationError)
409
357
  throw error;
410
- }
411
358
  const message = error instanceof Error ? error.message : 'Request failed';
412
359
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), message, { itemIndex: i });
413
360
  }
@@ -1,9 +1,13 @@
1
+ /**
2
+ * Version 0.1.9: Forced lightweight formats (JPEG/JPG) for ALL models.
3
+ * Removed the Output Format option from the UI to avoid confusion.
4
+ */
1
5
  import type {
2
- IDataObject,
3
- IExecuteFunctions,
4
- INodeExecutionData,
5
- INodeType,
6
- INodeTypeDescription,
6
+ IDataObject,
7
+ IExecuteFunctions,
8
+ INodeExecutionData,
9
+ INodeType,
10
+ INodeTypeDescription,
7
11
  } from 'n8n-workflow';
8
12
  import { NodeOperationError } from 'n8n-workflow';
9
13
 
@@ -13,448 +17,390 @@ const GENERATION_URL_GPT4O = String(process.env.COMMANDOS_IMAGE_URL_GPT4O || '')
13
17
  const GENERATION_URL_MJ = String(process.env.COMMANDOS_IMAGE_URL_MJ || '').trim();
14
18
 
15
19
  type BuildParams = {
16
- model: string;
17
- prompt: string;
18
- ratio: string;
19
- references: string[];
20
- quality?: string;
21
- resolution?: string;
20
+ model: string;
21
+ prompt: string;
22
+ ratio: string;
23
+ references: string[];
24
+ quality?: string;
25
+ resolution?: string;
22
26
  };
23
27
 
24
28
  type BuildResult = {
25
- url: string;
26
- body: Record<string, unknown>;
27
- requestedModel: string;
28
- resolvedModel: string;
29
- ratio: string;
30
- references: string[];
31
- hasReferences: boolean;
29
+ url: string;
30
+ body: Record<string, unknown>;
31
+ requestedModel: string;
32
+ resolvedModel: string;
33
+ ratio: string;
34
+ references: string[];
35
+ hasReferences: boolean;
32
36
  };
33
37
 
34
38
  const resolveGenerationUrl = (resolvedModel: string): string => {
35
- if (resolvedModel === 'gpt4o-image') {
36
- return GENERATION_URL_GPT4O || GENERATION_URL_DEFAULT;
37
- }
38
- if (resolvedModel === 'midjourney') {
39
- return GENERATION_URL_MJ || GENERATION_URL_DEFAULT;
40
- }
41
- return GENERATION_URL_DEFAULT;
39
+ if (resolvedModel === 'gpt4o-image') {
40
+ return GENERATION_URL_GPT4O || GENERATION_URL_DEFAULT;
41
+ }
42
+ if (resolvedModel === 'midjourney') {
43
+ return GENERATION_URL_MJ || GENERATION_URL_DEFAULT;
44
+ }
45
+ return GENERATION_URL_DEFAULT;
42
46
  };
43
47
 
44
- /**
45
- * Maps standard ratios to specific model requirements
46
- */
47
48
  const mapRatio = (model: string, ratio: string): string => {
48
- if (model.includes('seedream')) {
49
- if (ratio === '2:3') return 'portrait_3_2';
50
- if (ratio === '3:2') return 'landscape_2_3';
51
- }
52
- return ratio;
49
+ if (model.includes('seedream')) {
50
+ if (ratio === '2:3') return 'portrait_3_2';
51
+ if (ratio === '3:2') return 'landscape_2_3';
52
+ }
53
+ return ratio;
53
54
  };
54
55
 
55
56
  const buildGenerationRequest = ({
56
- model,
57
- prompt,
58
- ratio,
59
- references,
60
- quality = 'basic',
61
- resolution = '1K',
57
+ model,
58
+ prompt,
59
+ ratio,
60
+ references,
61
+ quality = 'basic',
62
+ resolution = '1K',
62
63
  }: BuildParams): BuildResult => {
63
- const hasReferences = references.length > 0;
64
- const requestedModel = model;
65
- let resolvedModel = model;
64
+ const hasReferences = references.length > 0;
65
+ const requestedModel = model;
66
+ let resolvedModel = model;
66
67
 
67
- // Model Resolution
68
- if (requestedModel === 'flux-pro') {
69
- resolvedModel = hasReferences ? 'flux-2/pro-image-to-image' : 'flux-2/pro-text-to-image';
70
- } else if (requestedModel === 'nano-banana') {
71
- resolvedModel = hasReferences ? 'google/nano-banana-edit' : 'google/nano-banana';
72
- } else if (requestedModel === 'nano-banana-pro') {
73
- resolvedModel = 'nano-banana-pro';
74
- } else if (requestedModel === 'seedream') {
75
- resolvedModel = hasReferences ? 'seedream/4.5-edit' : 'seedream/4.5';
76
- }
68
+ if (requestedModel === 'flux-pro') {
69
+ resolvedModel = hasReferences ? 'flux-2/pro-image-to-image' : 'flux-2/pro-text-to-image';
70
+ } else if (requestedModel === 'nano-banana') {
71
+ resolvedModel = hasReferences ? 'google/nano-banana-edit' : 'google/nano-banana';
72
+ } else if (requestedModel === 'nano-banana-pro') {
73
+ resolvedModel = 'nano-banana-pro';
74
+ } else if (requestedModel === 'seedream') {
75
+ resolvedModel = hasReferences ? 'seedream/4.5-edit' : 'seedream/4.5';
76
+ }
77
77
 
78
- // Fallback for MJ or generic seedream
79
- if (!hasReferences && (requestedModel === 'seedream' || requestedModel === 'midjourney')) {
80
- if (requestedModel === 'seedream') {
81
- // Handled above for 4.5
82
- } else if (requestedModel === 'midjourney') {
83
- resolvedModel = 'midjourney';
78
+ if (!hasReferences && (requestedModel === 'seedream' || requestedModel === 'midjourney')) {
79
+ if (requestedModel === 'midjourney') {
80
+ resolvedModel = 'midjourney';
81
+ }
84
82
  }
85
- }
86
83
 
87
- let body: Record<string, unknown> = {};
84
+ let body: Record<string, unknown> = {};
88
85
 
89
- if (resolvedModel === 'gpt4o-image') {
90
- body = {
91
- filesUrl: references,
92
- prompt,
93
- size: ratio,
94
- callBackUrl: 'https://api.comandos.ai/internal/kie/callback', // Fixed to internal
95
- fallbackModel: 'FLUX_MAX',
96
- };
97
- } else if (resolvedModel.includes('flux-2/')) {
98
- body = {
99
- model: resolvedModel,
100
- input: {
101
- prompt,
102
- aspect_ratio: ratio,
103
- resolution,
104
- ...(hasReferences ? { input_urls: references } : {}),
105
- },
106
- };
107
- } else if (resolvedModel === 'seedream/4.5' || resolvedModel === 'seedream/4.5-edit') {
108
- body = {
109
- model: resolvedModel,
110
- input: {
111
- prompt,
112
- aspect_ratio: mapRatio(resolvedModel, ratio),
113
- quality,
114
- ...(hasReferences ? { image_urls: references } : {}),
115
- },
116
- };
117
- } else if (resolvedModel === 'nano-banana-pro') {
118
- body = {
119
- model: resolvedModel,
120
- input: {
121
- prompt,
122
- aspect_ratio: ratio,
123
- resolution,
124
- output_format: 'jpg', // ALWAYS JPG for Nano Banana Pro (lightweight)
125
- ...(hasReferences ? { image_input: references } : {}),
126
- },
127
- };
128
- } else if (resolvedModel.includes('google/nano-banana')) {
129
- body = {
130
- model: resolvedModel,
131
- input: {
132
- prompt,
133
- image_size: ratio,
134
- output_format: 'jpeg', // ALWAYS JPEG for Nano Banana (lightweight)
135
- ...(hasReferences ? { image_urls: references } : {}),
136
- },
137
- };
138
- } else if (resolvedModel === 'midjourney') {
139
- body = {
140
- taskType: hasReferences ? 'mj_img2img' : 'mj_txt2img',
141
- speed: 'fast',
142
- prompt,
143
- fileUrls: references,
144
- aspectRatio: ratio,
145
- enableTranslation: true,
146
- };
147
- } else {
148
- // Default fallback (v4 edit style)
149
- body = {
150
- model: requestedModel === 'gpt4o-image' ? 'gpt-4o' : 'bytedance/seedream-v4-edit',
151
- input: {
152
- image_urls: references,
153
- prompt,
154
- image_size: mapRatio('seedream', ratio),
155
- },
156
- };
157
- }
86
+ if (resolvedModel === 'gpt4o-image') {
87
+ body = {
88
+ filesUrl: references,
89
+ prompt,
90
+ size: ratio,
91
+ callBackUrl: 'https://api.comandos.ai/internal/kie/callback',
92
+ fallbackModel: 'FLUX_MAX',
93
+ };
94
+ } else if (resolvedModel.includes('flux-2/')) {
95
+ body = {
96
+ model: resolvedModel,
97
+ input: {
98
+ prompt,
99
+ aspect_ratio: ratio,
100
+ resolution,
101
+ output_format: 'jpeg', // Force jpeg
102
+ ...(hasReferences ? { input_urls: references } : {}),
103
+ },
104
+ };
105
+ } else if (resolvedModel === 'seedream/4.5' || resolvedModel === 'seedream/4.5-edit') {
106
+ body = {
107
+ model: resolvedModel,
108
+ input: {
109
+ prompt,
110
+ aspect_ratio: mapRatio(resolvedModel, ratio),
111
+ quality,
112
+ output_format: 'jpeg', // Force jpeg
113
+ ...(hasReferences ? { image_urls: references } : {}),
114
+ },
115
+ };
116
+ } else if (resolvedModel === 'nano-banana-pro') {
117
+ body = {
118
+ model: resolvedModel,
119
+ input: {
120
+ prompt,
121
+ aspect_ratio: ratio,
122
+ resolution,
123
+ output_format: 'jpg', // Force jpg for pro
124
+ ...(hasReferences ? { image_input: references } : {}),
125
+ },
126
+ };
127
+ } else if (resolvedModel.includes('google/nano-banana')) {
128
+ body = {
129
+ model: resolvedModel,
130
+ input: {
131
+ prompt,
132
+ image_size: ratio,
133
+ output_format: 'jpeg', // Force jpeg
134
+ ...(hasReferences ? { image_urls: references } : {}),
135
+ },
136
+ };
137
+ } else if (resolvedModel === 'midjourney') {
138
+ body = {
139
+ taskType: hasReferences ? 'mj_img2img' : 'mj_txt2img',
140
+ speed: 'fast',
141
+ prompt,
142
+ fileUrls: references,
143
+ aspectRatio: ratio,
144
+ enableTranslation: true,
145
+ };
146
+ } else {
147
+ body = {
148
+ model: requestedModel === 'gpt4o-image' ? 'gpt-4o' : 'bytedance/seedream-v4-edit',
149
+ input: {
150
+ image_urls: references,
151
+ prompt,
152
+ image_size: mapRatio('seedream', ratio),
153
+ output_format: 'jpeg', // Default jpeg
154
+ },
155
+ };
156
+ }
158
157
 
159
- return {
160
- url: resolveGenerationUrl(resolvedModel),
161
- body,
162
- requestedModel,
163
- resolvedModel,
164
- ratio,
165
- references,
166
- hasReferences,
167
- };
158
+ return {
159
+ url: resolveGenerationUrl(resolvedModel),
160
+ body,
161
+ requestedModel,
162
+ resolvedModel,
163
+ ratio,
164
+ references,
165
+ hasReferences,
166
+ };
168
167
  };
169
168
 
170
169
  const extractReferences = (raw: unknown, model: string): string[] => {
171
- if (!raw || typeof raw !== 'object') {
172
- return [];
173
- }
174
- const collection = raw as { reference?: Array<{ url?: string }> };
175
- const items = Array.isArray(collection.reference) ? collection.reference : [];
176
-
177
- // Dynamic limit based on model docs
178
- let limit = 8;
179
- if (model === 'seedream') limit = 14;
180
- if (model === 'gpt4o-image') limit = 16;
181
- if (model.includes('nano-banana')) limit = 10;
170
+ if (!raw || typeof raw !== 'object') {
171
+ return [];
172
+ }
173
+ const collection = raw as { reference?: Array<{ url?: string }> };
174
+ const items = Array.isArray(collection.reference) ? collection.reference : [];
182
175
 
183
- return items
184
- .map((entry) => String(entry?.url || '').trim())
185
- .filter((value) => value.length > 0 && /^https?:\/\//i.test(value))
186
- .slice(0, limit);
176
+ let limit = 8;
177
+ if (model === 'seedream') limit = 14;
178
+ if (model === 'gpt4o-image') limit = 16;
179
+ if (model.includes('nano-banana')) limit = 10;
180
+
181
+ return items
182
+ .map((entry) => String(entry?.url || '').trim())
183
+ .filter((value) => value.length > 0 && /^https?:\/\//i.test(value))
184
+ .slice(0, limit);
187
185
  };
188
186
 
189
187
  export class CommandosImage implements INodeType {
190
- description: INodeTypeDescription = {
191
- displayName: 'Commandos Image',
192
- name: 'commandosImage',
193
- group: ['transform'],
194
- version: 1,
195
- description: 'Create image tasks in Commandos API',
196
- defaults: {
197
- name: 'Commandos Image',
198
- },
199
- icon: 'file:Image.png',
200
- inputs: ['main'],
201
- outputs: ['main'],
202
- credentials: [
203
- {
204
- name: 'commandosApi',
205
- required: true,
206
- },
207
- ],
208
- properties: [
209
- {
210
- displayName: 'Operation',
211
- name: 'operation',
212
- type: 'options',
213
- noDataExpression: true,
214
- options: [
215
- {
216
- name: 'Create Task',
217
- value: 'create',
218
- },
219
- {
220
- name: 'Check Status',
221
- value: 'status',
222
- },
223
- ],
224
- default: 'create',
225
- },
226
- {
227
- displayName: 'Model',
228
- name: 'model',
229
- type: 'options',
230
- options: [
231
- { name: 'Seedream 4.5', value: 'seedream' },
232
- { name: 'Flux Pro', value: 'flux-pro' },
233
- { name: 'GPT-4o Image', value: 'gpt4o-image' },
234
- { name: 'Nano Banana', value: 'nano-banana' },
235
- { name: 'Nano Banana Pro', value: 'nano-banana-pro' },
236
- { name: 'Midjourney', value: 'midjourney' },
237
- ],
238
- default: 'seedream',
239
- displayOptions: {
240
- show: {
241
- operation: ['create'],
242
- },
188
+ description: INodeTypeDescription = {
189
+ displayName: 'Commandos Image',
190
+ name: 'commandosImage',
191
+ group: ['transform'],
192
+ version: 1,
193
+ description: 'Create image tasks in Commandos API',
194
+ defaults: {
195
+ name: 'Commandos Image',
243
196
  },
244
- },
245
- {
246
- displayName: 'Prompt',
247
- name: 'prompt',
248
- type: 'string',
249
- default: '',
250
- typeOptions: {
251
- rows: 4,
252
- },
253
- displayOptions: {
254
- show: {
255
- operation: ['create'],
256
- },
257
- },
258
- },
259
- {
260
- displayName: 'Ratio',
261
- name: 'ratio',
262
- type: 'options',
263
- options: [
264
- { name: '1:1', value: '1:1' },
265
- { name: '2:3', value: '2:3' },
266
- { name: '3:2', value: '3:2' },
267
- { name: '4:5', value: '4:5' },
268
- { name: '16:9', value: '16:9' },
269
- { name: '9:16', value: '9:16' },
270
- { name: '4:3', value: '4:3' },
271
- { name: '3:4', value: '3:4' },
272
- { name: '21:9', value: '21:9' },
273
- ],
274
- default: '2:3',
275
- displayOptions: {
276
- show: {
277
- operation: ['create'],
278
- },
279
- },
280
- },
281
- {
282
- displayName: 'Quality',
283
- name: 'quality',
284
- type: 'options',
285
- options: [
286
- { name: 'Basic (2K)', value: 'basic' },
287
- { name: 'High (4K)', value: 'high' },
288
- { name: 'Medium (Balanced)', value: 'medium' },
289
- ],
290
- default: 'basic',
291
- displayOptions: {
292
- show: {
293
- operation: ['create'],
294
- model: ['seedream', 'gpt4o-image', 'flux-pro'],
295
- },
296
- },
297
- },
298
- {
299
- displayName: 'Resolution',
300
- name: 'resolution',
301
- type: 'options',
302
- options: [
303
- { name: '1K', value: '1K' },
304
- { name: '2K', value: '2K' },
305
- { name: '4K', value: '4K' },
197
+ icon: 'file:Image.png',
198
+ inputs: ['main'],
199
+ outputs: ['main'],
200
+ credentials: [
201
+ {
202
+ name: 'commandosApi',
203
+ required: true,
204
+ },
306
205
  ],
307
- default: '1K',
308
- displayOptions: {
309
- show: {
310
- operation: ['create'],
311
- model: ['flux-pro', 'nano-banana-pro'],
312
- },
313
- },
314
- },
315
- {
316
- displayName: 'References',
317
- name: 'references',
318
- type: 'fixedCollection',
319
- default: {},
320
- typeOptions: {
321
- multipleValues: true,
322
- },
323
- options: [
324
- {
325
- name: 'reference',
326
- displayName: 'Reference',
327
- values: [
328
- {
329
- displayName: 'Reference URL',
330
- name: 'url',
206
+ properties: [
207
+ {
208
+ displayName: 'Operation',
209
+ name: 'operation',
210
+ type: 'options',
211
+ noDataExpression: true,
212
+ options: [
213
+ { name: 'Create Task', value: 'create' },
214
+ { name: 'Check Status', value: 'status' },
215
+ ],
216
+ default: 'create',
217
+ },
218
+ {
219
+ displayName: 'Model',
220
+ name: 'model',
221
+ type: 'options',
222
+ options: [
223
+ { name: 'Seedream 4.5', value: 'seedream' },
224
+ { name: 'Flux Pro', value: 'flux-pro' },
225
+ { name: 'GPT-4o Image', value: 'gpt4o-image' },
226
+ { name: 'Nano Banana', value: 'nano-banana' },
227
+ { name: 'Nano Banana Pro', value: 'nano-banana-pro' },
228
+ { name: 'Midjourney', value: 'midjourney' },
229
+ ],
230
+ default: 'seedream',
231
+ displayOptions: { show: { operation: ['create'] } },
232
+ },
233
+ {
234
+ displayName: 'Prompt',
235
+ name: 'prompt',
236
+ type: 'string',
237
+ default: '',
238
+ typeOptions: { rows: 4 },
239
+ displayOptions: { show: { operation: ['create'] } },
240
+ },
241
+ {
242
+ displayName: 'Ratio',
243
+ name: 'ratio',
244
+ type: 'options',
245
+ options: [
246
+ { name: '1:1', value: '1:1' },
247
+ { name: '2:3', value: '2:3' },
248
+ { name: '3:2', value: '3:2' },
249
+ { name: '4:5', value: '4:5' },
250
+ { name: '16:9', value: '16:9' },
251
+ { name: '9:16', value: '9:16' },
252
+ { name: '4:3', value: '4:3' },
253
+ { name: '3:4', value: '3:4' },
254
+ { name: '21:9', value: '21:9' },
255
+ ],
256
+ default: '2:3',
257
+ displayOptions: { show: { operation: ['create'] } },
258
+ },
259
+ {
260
+ displayName: 'Quality',
261
+ name: 'quality',
262
+ type: 'options',
263
+ options: [
264
+ { name: 'Basic (2K)', value: 'basic' },
265
+ { name: 'High (4K)', value: 'high' },
266
+ { name: 'Medium (Balanced)', value: 'medium' },
267
+ ],
268
+ default: 'basic',
269
+ displayOptions: {
270
+ show: {
271
+ operation: ['create'],
272
+ model: ['seedream', 'gpt4o-image', 'flux-pro'],
273
+ },
274
+ },
275
+ },
276
+ {
277
+ displayName: 'Resolution',
278
+ name: 'resolution',
279
+ type: 'options',
280
+ options: [
281
+ { name: '1K', value: '1K' },
282
+ { name: '2K', value: '2K' },
283
+ { name: '4K', value: '4K' },
284
+ ],
285
+ default: '1K',
286
+ displayOptions: {
287
+ show: {
288
+ operation: ['create'],
289
+ model: ['flux-pro', 'nano-banana-pro'],
290
+ },
291
+ },
292
+ },
293
+ {
294
+ displayName: 'References',
295
+ name: 'references',
296
+ type: 'fixedCollection',
297
+ default: {},
298
+ typeOptions: { multipleValues: true },
299
+ options: [
300
+ {
301
+ name: 'reference',
302
+ displayName: 'Reference',
303
+ values: [
304
+ {
305
+ displayName: 'Reference URL',
306
+ name: 'url',
307
+ type: 'string',
308
+ default: '',
309
+ },
310
+ ],
311
+ },
312
+ ],
313
+ displayOptions: { show: { operation: ['create'] } },
314
+ },
315
+ {
316
+ displayName: 'Task ID',
317
+ name: 'taskId',
331
318
  type: 'string',
332
319
  default: '',
333
- },
334
- ],
335
- },
320
+ displayOptions: { show: { operation: ['status'] } },
321
+ },
336
322
  ],
337
- displayOptions: {
338
- show: {
339
- operation: ['create'],
340
- },
341
- },
342
- },
343
- {
344
- displayName: 'Task ID',
345
- name: 'taskId',
346
- type: 'string',
347
- default: '',
348
- displayOptions: {
349
- show: {
350
- operation: ['status'],
351
- },
352
- },
353
- },
354
- ],
355
- };
323
+ };
356
324
 
357
- async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
358
- const items = this.getInputData();
359
- const operation = this.getNodeParameter('operation', 0) as string;
360
- const credentials = await this.getCredentials('commandosApi');
361
- const licenseKey = String(credentials.licenseKey || '').trim();
325
+ async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
326
+ const items = this.getInputData();
327
+ const operation = this.getNodeParameter('operation', 0) as string;
328
+ const credentials = await this.getCredentials('commandosApi');
329
+ const licenseKey = String(credentials.licenseKey || '').trim();
362
330
 
363
- if (!licenseKey) {
364
- throw new NodeOperationError(this.getNode(), 'License key is required');
365
- }
331
+ if (!licenseKey) {
332
+ throw new NodeOperationError(this.getNode(), 'License key is required');
333
+ }
366
334
 
367
- const results: INodeExecutionData[] = [];
335
+ const results: INodeExecutionData[] = [];
368
336
 
369
- for (let i = 0; i < items.length; i += 1) {
370
- try {
371
- if (operation === 'create') {
372
- const model = String(this.getNodeParameter('model', i));
373
- const prompt = String(this.getNodeParameter('prompt', i) || '').trim();
374
- const ratio = String(this.getNodeParameter('ratio', i));
375
- const references = extractReferences(this.getNodeParameter('references', i, {}), model);
337
+ for (let i = 0; i < items.length; i += 1) {
338
+ try {
339
+ if (operation === 'create') {
340
+ const model = String(this.getNodeParameter('model', i));
341
+ const prompt = String(this.getNodeParameter('prompt', i) || '').trim();
342
+ const ratio = String(this.getNodeParameter('ratio', i));
343
+ const references = extractReferences(this.getNodeParameter('references', i, {}), model);
376
344
 
377
- const quality = ['seedream', 'gpt4o-image', 'flux-pro'].includes(model)
378
- ? String(this.getNodeParameter('quality', i, 'basic'))
379
- : 'basic';
345
+ const quality = ['seedream', 'gpt4o-image', 'flux-pro'].includes(model)
346
+ ? String(this.getNodeParameter('quality', i, 'basic'))
347
+ : 'basic';
380
348
 
381
- const resolution = ['flux-pro', 'nano-banana-pro'].includes(model)
382
- ? String(this.getNodeParameter('resolution', i, '1K'))
383
- : '1K';
349
+ const resolution = ['flux-pro', 'nano-banana-pro'].includes(model)
350
+ ? String(this.getNodeParameter('resolution', i, '1K'))
351
+ : '1K';
384
352
 
385
- if (!prompt) {
386
- throw new NodeOperationError(this.getNode(), 'Prompt is required', { itemIndex: i });
387
- }
353
+ if (!prompt) {
354
+ throw new NodeOperationError(this.getNode(), 'Prompt is required', { itemIndex: i });
355
+ }
388
356
 
389
- const request = buildGenerationRequest({
390
- model,
391
- prompt,
392
- ratio,
393
- references,
394
- quality,
395
- resolution
396
- });
397
-
398
- if (!request.url) {
399
- throw new NodeOperationError(
400
- this.getNode(),
401
- 'URL генерации не настроен. Проверь переменные окружения COMMANDOS_IMAGE_URL_DEFAULT/COMMANDOS_IMAGE_URL_GPT4O/COMMANDOS_IMAGE_URL_MJ',
402
- { itemIndex: i },
403
- );
404
- }
357
+ const request = buildGenerationRequest({ model, prompt, ratio, references, quality, resolution });
405
358
 
406
- const payload = {
407
- url: request.url,
408
- body: request.body,
409
- requestedModel: request.requestedModel,
410
- resolvedModel: request.resolvedModel,
411
- ratio: request.ratio,
412
- references: request.references,
413
- hasReferences: request.hasReferences,
414
- _v: '0.1.8'
415
- };
359
+ const payload = {
360
+ url: request.url,
361
+ body: request.body,
362
+ requestedModel: request.requestedModel,
363
+ resolvedModel: request.resolvedModel,
364
+ ratio: request.ratio,
365
+ references: request.references,
366
+ hasReferences: request.hasReferences,
367
+ _v: '0.1.9'
368
+ };
416
369
 
417
- const response = await this.helpers.request({
418
- method: 'POST',
419
- url: `${COMMANDOS_API_URL}/tasks`,
420
- headers: {
421
- 'Content-Type': 'application/json',
422
- 'X-License-Key': licenseKey,
423
- },
424
- body: {
425
- process_type: 'IMAGE_GENERATION',
426
- payload,
427
- },
428
- json: true,
429
- });
370
+ const response = await this.helpers.request({
371
+ method: 'POST',
372
+ url: `${COMMANDOS_API_URL}/tasks`,
373
+ headers: {
374
+ 'Content-Type': 'application/json',
375
+ 'X-License-Key': licenseKey,
376
+ },
377
+ body: { process_type: 'IMAGE_GENERATION', payload },
378
+ json: true,
379
+ });
430
380
 
431
- results.push({ json: response as IDataObject });
432
- } else if (operation === 'status') {
433
- const taskId = String(this.getNodeParameter('taskId', i) || '').trim();
434
- if (!taskId) {
435
- throw new NodeOperationError(this.getNode(), 'Task ID is required', { itemIndex: i });
436
- }
381
+ results.push({ json: response as IDataObject });
382
+ } else if (operation === 'status') {
383
+ const taskId = String(this.getNodeParameter('taskId', i) || '').trim();
384
+ if (!taskId) {
385
+ throw new NodeOperationError(this.getNode(), 'Task ID is required', { itemIndex: i });
386
+ }
437
387
 
438
- const response = await this.helpers.request({
439
- method: 'GET',
440
- url: `${COMMANDOS_API_URL}/tasks/${encodeURIComponent(taskId)}`,
441
- headers: {
442
- 'X-License-Key': licenseKey,
443
- },
444
- json: true,
445
- });
388
+ const response = await this.helpers.request({
389
+ method: 'GET',
390
+ url: `${COMMANDOS_API_URL}/tasks/${encodeURIComponent(taskId)}`,
391
+ headers: { 'X-License-Key': licenseKey },
392
+ json: true,
393
+ });
446
394
 
447
- results.push({ json: response as IDataObject });
395
+ results.push({ json: response as IDataObject });
396
+ }
397
+ } catch (error) {
398
+ if (error instanceof NodeOperationError) throw error;
399
+ const message = error instanceof Error ? error.message : 'Request failed';
400
+ throw new NodeOperationError(this.getNode(), message, { itemIndex: i });
401
+ }
448
402
  }
449
- } catch (error) {
450
- if (error instanceof NodeOperationError) {
451
- throw error;
452
- }
453
- const message = error instanceof Error ? error.message : 'Request failed';
454
- throw new NodeOperationError(this.getNode(), message, { itemIndex: i });
455
- }
456
- }
457
403
 
458
- return [results];
459
- }
404
+ return [results];
405
+ }
460
406
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-commandos-image",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "Commandos Image custom node",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",