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