n8n-nodes-supermachine 0.6.1 → 0.6.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.
@@ -539,6 +539,17 @@ class Supermachine {
539
539
  else if (resource === 'image') {
540
540
  if (operation === 'generateV3') {
541
541
  const additionalFieldsV3 = this.getNodeParameter('additionalFieldsV3', i, {});
542
+ // Convert folderId string to number for API v3
543
+ let folderIdNumber = 0;
544
+ if (additionalFieldsV3.folderId) {
545
+ const folderIdValue = additionalFieldsV3.folderId;
546
+ if (typeof folderIdValue === 'string') {
547
+ folderIdNumber = parseInt(folderIdValue, 10) || 0;
548
+ }
549
+ else if (typeof folderIdValue === 'number') {
550
+ folderIdNumber = folderIdValue;
551
+ }
552
+ }
542
553
  const body = {
543
554
  prompt: this.getNodeParameter('promptV3', i),
544
555
  modelName: this.getNodeParameter('modelNameV3', i),
@@ -554,7 +565,7 @@ class Supermachine {
554
565
  faceEnhance: additionalFieldsV3.faceEnhance || false,
555
566
  imageStrength: additionalFieldsV3.imageStrength || null,
556
567
  initImage: additionalFieldsV3.initImage || '',
557
- folderId: additionalFieldsV3.folderId || 0,
568
+ folderId: folderIdNumber,
558
569
  homeUrl: 'https://supermachine.art',
559
570
  refImage: '',
560
571
  controlMode: 0,
@@ -1000,6 +1011,159 @@ class Supermachine {
1000
1011
  }
1001
1012
  });
1002
1013
  }
1014
+ else if (operation === 'upscale') {
1015
+ const imageSource = this.getNodeParameter('imageSource', i);
1016
+ const upscaler = this.getNodeParameter('upscaler', i);
1017
+ const factor = this.getNodeParameter('factor', i);
1018
+ const additionalOptions = this.getNodeParameter('additionalOptions', i, {});
1019
+ let imageUrl = '';
1020
+ // Step 1: Get image URL
1021
+ if (imageSource === 'imageId') {
1022
+ const imageId = this.getNodeParameter('imageId', i);
1023
+ const imageDetails = await this.helpers.requestWithAuthentication.call(this, 'supermachineApi', {
1024
+ method: 'GET',
1025
+ url: `https://dev.supermachine.art/v1/images/${imageId}`,
1026
+ json: true,
1027
+ });
1028
+ imageUrl = imageDetails.url;
1029
+ if (!imageUrl) {
1030
+ throw new Error(`Image ID ${imageId} does not have a valid URL`);
1031
+ }
1032
+ }
1033
+ else if (imageSource === 'imageUrl') {
1034
+ imageUrl = this.getNodeParameter('imageUrl', i);
1035
+ }
1036
+ // Step 2: Download image as binary
1037
+ const imageBuffer = await this.helpers.httpRequest({
1038
+ method: 'GET',
1039
+ url: imageUrl,
1040
+ encoding: 'arraybuffer',
1041
+ returnFullResponse: false,
1042
+ });
1043
+ // Step 3: Convert to base64
1044
+ const base64Image = Buffer.from(imageBuffer).toString('base64');
1045
+ let mimeType = 'image/jpeg';
1046
+ if (imageUrl.toLowerCase().includes('.png')) {
1047
+ mimeType = 'image/png';
1048
+ }
1049
+ else if (imageUrl.toLowerCase().includes('.webp')) {
1050
+ mimeType = 'image/webp';
1051
+ }
1052
+ const dataUri = `data:${mimeType};base64,${base64Image}`;
1053
+ // Step 4: Submit to upscale API
1054
+ const body = {
1055
+ image: dataUri,
1056
+ upscaler: upscaler,
1057
+ factor: factor,
1058
+ faceEnhance: additionalOptions.faceEnhance || false,
1059
+ };
1060
+ if (additionalOptions.folderId) {
1061
+ body.folderId = parseInt(additionalOptions.folderId, 10) || 0;
1062
+ }
1063
+ let upscaleResponse;
1064
+ try {
1065
+ upscaleResponse = await this.helpers.requestWithAuthentication.call(this, 'supermachineApi', {
1066
+ method: 'POST',
1067
+ url: 'https://dev.supermachine.art/v1/tools/upscale',
1068
+ json: true,
1069
+ body,
1070
+ });
1071
+ }
1072
+ catch (error) {
1073
+ throw new Error(`Failed to upscale image: ${error.message}`);
1074
+ }
1075
+ const batchId = upscaleResponse.batchId;
1076
+ if (!batchId) {
1077
+ if (upscaleResponse.url || upscaleResponse.imageUrl) {
1078
+ returnData.push({
1079
+ json: {
1080
+ success: true,
1081
+ imageUrl: upscaleResponse.url || upscaleResponse.imageUrl,
1082
+ imageId: upscaleResponse.id || upscaleResponse.imageId,
1083
+ creditsCost: upscaleResponse.creditsCost,
1084
+ creditsRemaining: upscaleResponse.creditsRemaining,
1085
+ upscaler,
1086
+ factor,
1087
+ faceEnhance: body.faceEnhance,
1088
+ processedAt: new Date().toISOString(),
1089
+ ...upscaleResponse,
1090
+ }
1091
+ });
1092
+ continue;
1093
+ }
1094
+ throw new Error('No batchId or result returned from upscale API');
1095
+ }
1096
+ const skipPolling = additionalOptions.skipPolling;
1097
+ if (skipPolling) {
1098
+ returnData.push({
1099
+ json: {
1100
+ batchId,
1101
+ tool: upscaleResponse.tool || 'upscale',
1102
+ creditsCost: upscaleResponse.creditsCost,
1103
+ creditsRemaining: upscaleResponse.creditsRemaining,
1104
+ upscaler,
1105
+ factor,
1106
+ faceEnhance: body.faceEnhance,
1107
+ status: 'submitted',
1108
+ message: 'Image upscaling submitted. Use List Images with this batchId to check status.',
1109
+ submittedAt: new Date().toISOString(),
1110
+ ...upscaleResponse,
1111
+ }
1112
+ });
1113
+ continue;
1114
+ }
1115
+ // Step 5: Poll for result
1116
+ const pollingInterval = additionalOptions.pollingInterval || 2;
1117
+ const maxPollingTime = additionalOptions.maxPollingTime || 120;
1118
+ const startTime = Date.now();
1119
+ let completedResult = null;
1120
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1121
+ while (!completedResult) {
1122
+ if ((Date.now() - startTime) / 1000 > maxPollingTime) {
1123
+ throw new Error(`Image upscaling timed out after ${maxPollingTime} seconds. Batch ID: ${batchId}`);
1124
+ }
1125
+ let pollResponse;
1126
+ try {
1127
+ pollResponse = await this.helpers.requestWithAuthentication.call(this, 'supermachineApi', {
1128
+ method: 'GET',
1129
+ url: `https://dev.supermachine.art/v1/images?batchId=${batchId}`,
1130
+ json: true,
1131
+ });
1132
+ }
1133
+ catch (error) {
1134
+ await new Promise((resolve) => setTimeout(resolve, pollingInterval * 1000));
1135
+ continue;
1136
+ }
1137
+ const pollItems = pollResponse.items;
1138
+ if (Array.isArray(pollItems) && pollItems.length > 0) {
1139
+ const firstItem = pollItems[0];
1140
+ const status = firstItem.status;
1141
+ if (status === 'completed') {
1142
+ completedResult = firstItem;
1143
+ break;
1144
+ }
1145
+ else if (status === 'failed') {
1146
+ throw new Error(`Image upscaling failed. Batch ID: ${batchId}. Error: ${firstItem.error || 'Unknown error'}`);
1147
+ }
1148
+ }
1149
+ await new Promise((resolve) => setTimeout(resolve, pollingInterval * 1000));
1150
+ }
1151
+ returnData.push({
1152
+ json: {
1153
+ batchId,
1154
+ success: true,
1155
+ operation: 'upscale',
1156
+ tool: 'upscale',
1157
+ upscaler,
1158
+ factor,
1159
+ faceEnhance: body.faceEnhance,
1160
+ creditsCost: upscaleResponse.creditsCost,
1161
+ creditsRemaining: upscaleResponse.creditsRemaining,
1162
+ processedAt: new Date().toISOString(),
1163
+ ...completedResult,
1164
+ }
1165
+ });
1166
+ }
1003
1167
  else {
1004
1168
  throw new Error(`⚠️ Operation "${operation}" is coming soon.`);
1005
1169
  }
@@ -547,11 +547,14 @@ exports.getImageFields = [
547
547
  description: 'Sampling method',
548
548
  },
549
549
  {
550
- displayName: 'Folder ID',
550
+ displayName: 'Move to Folder',
551
551
  name: 'folderId',
552
- type: 'number',
553
- default: 0,
554
- description: 'Folder ID to save image (0 = root)',
552
+ type: 'options',
553
+ typeOptions: {
554
+ loadOptionsMethod: 'getFolders',
555
+ },
556
+ default: '',
557
+ description: 'Move image to folder. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
555
558
  },
556
559
  {
557
560
  displayName: 'Face Enhance',
@@ -19,6 +19,12 @@ exports.getToolsOperations = [
19
19
  description: 'Remove background from an image',
20
20
  action: 'Remove background from image',
21
21
  },
22
+ {
23
+ name: 'Upscale',
24
+ value: 'upscale',
25
+ description: 'Upscale image resolution up to 4x',
26
+ action: 'Upscale image',
27
+ },
22
28
  {
23
29
  name: 'Image to Prompt (Coming Soon)',
24
30
  value: 'imageToPrompt',
@@ -31,12 +37,6 @@ exports.getToolsOperations = [
31
37
  description: 'Swap faces in images',
32
38
  action: 'Face swap',
33
39
  },
34
- {
35
- name: 'Upscale (Coming Soon)',
36
- value: 'upscale',
37
- description: 'Upscale image resolution up to 2x',
38
- action: 'Upscale image',
39
- },
40
40
  {
41
41
  name: 'Instruct Edit (Coming Soon)',
42
42
  value: 'instructEdit',
@@ -184,6 +184,174 @@ exports.getToolsFields = [
184
184
  ],
185
185
  },
186
186
  // ══════════════════════════════════════════════════════
187
+ // UPSCALE FIELDS
188
+ // ══════════════════════════════════════════════════════
189
+ {
190
+ displayName: 'Image Source',
191
+ name: 'imageSource',
192
+ type: 'options',
193
+ displayOptions: {
194
+ show: {
195
+ resource: ['tools'],
196
+ operation: ['upscale'],
197
+ },
198
+ },
199
+ options: [
200
+ {
201
+ name: 'Image ID',
202
+ value: 'imageId',
203
+ description: 'Use an image already in Supermachine by its ID',
204
+ },
205
+ {
206
+ name: 'Image URL',
207
+ value: 'imageUrl',
208
+ description: 'Use an external image URL',
209
+ },
210
+ ],
211
+ default: 'imageId',
212
+ description: 'Where to get the source image from',
213
+ },
214
+ {
215
+ displayName: 'Image ID',
216
+ name: 'imageId',
217
+ type: 'string',
218
+ displayOptions: {
219
+ show: {
220
+ resource: ['tools'],
221
+ operation: ['upscale'],
222
+ imageSource: ['imageId'],
223
+ },
224
+ },
225
+ required: true,
226
+ default: '',
227
+ placeholder: '12345',
228
+ description: 'The ID of the image to upscale',
229
+ },
230
+ {
231
+ displayName: 'Image URL',
232
+ name: 'imageUrl',
233
+ type: 'string',
234
+ displayOptions: {
235
+ show: {
236
+ resource: ['tools'],
237
+ operation: ['upscale'],
238
+ imageSource: ['imageUrl'],
239
+ },
240
+ },
241
+ required: true,
242
+ default: '',
243
+ placeholder: 'https://example.com/image.jpg',
244
+ description: 'The URL of the image to upscale',
245
+ },
246
+ {
247
+ displayName: 'Upscaler Model',
248
+ name: 'upscaler',
249
+ type: 'options',
250
+ displayOptions: {
251
+ show: {
252
+ resource: ['tools'],
253
+ operation: ['upscale'],
254
+ },
255
+ },
256
+ options: [
257
+ {
258
+ name: 'R-ESRGAN 4x+',
259
+ value: 'R-ESRGAN 4x+',
260
+ description: 'High quality 4x upscaling (recommended)',
261
+ },
262
+ {
263
+ name: 'R-ESRGAN 4x+ Anime',
264
+ value: 'R-ESRGAN 4x+ Anime',
265
+ description: 'Optimized for anime/cartoon images',
266
+ },
267
+ ],
268
+ default: 'R-ESRGAN 4x+',
269
+ required: true,
270
+ description: 'The upscaler model to use',
271
+ },
272
+ {
273
+ displayName: 'Upscale Factor',
274
+ name: 'factor',
275
+ type: 'number',
276
+ displayOptions: {
277
+ show: {
278
+ resource: ['tools'],
279
+ operation: ['upscale'],
280
+ },
281
+ },
282
+ default: 2,
283
+ typeOptions: {
284
+ minValue: 1,
285
+ maxValue: 4,
286
+ numberStepSize: 1,
287
+ },
288
+ required: true,
289
+ description: 'Upscale factor (1-4x). Higher values produce larger images.',
290
+ },
291
+ {
292
+ displayName: 'Additional Options',
293
+ name: 'additionalOptions',
294
+ type: 'collection',
295
+ placeholder: 'Add Option',
296
+ default: {},
297
+ displayOptions: {
298
+ show: {
299
+ resource: ['tools'],
300
+ operation: ['upscale'],
301
+ },
302
+ },
303
+ options: [
304
+ {
305
+ displayName: 'Face Enhance',
306
+ name: 'faceEnhance',
307
+ type: 'boolean',
308
+ default: false,
309
+ description: 'Whether to enhance faces in the image (uses GFPGAN)',
310
+ },
311
+ {
312
+ displayName: 'Save to Folder',
313
+ name: 'folderId',
314
+ type: 'options',
315
+ typeOptions: {
316
+ loadOptionsMethod: 'getFolders',
317
+ },
318
+ default: '',
319
+ description: 'Save the result to a specific folder. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
320
+ },
321
+ {
322
+ displayName: 'Skip Polling',
323
+ name: 'skipPolling',
324
+ type: 'boolean',
325
+ default: false,
326
+ description: 'Whether to skip waiting for the result and return immediately with batch ID',
327
+ },
328
+ {
329
+ displayName: 'Polling Interval (seconds)',
330
+ name: 'pollingInterval',
331
+ type: 'number',
332
+ default: 2,
333
+ description: 'How often to check if upscaling is complete (in seconds)',
334
+ displayOptions: {
335
+ show: {
336
+ skipPolling: [false],
337
+ },
338
+ },
339
+ },
340
+ {
341
+ displayName: 'Max Polling Time (seconds)',
342
+ name: 'maxPollingTime',
343
+ type: 'number',
344
+ default: 120,
345
+ description: 'Maximum time to wait for upscaling to complete (in seconds)',
346
+ displayOptions: {
347
+ show: {
348
+ skipPolling: [false],
349
+ },
350
+ },
351
+ },
352
+ ],
353
+ },
354
+ // ══════════════════════════════════════════════════════
187
355
  // COMING SOON NOTICE FOR OTHER OPERATIONS
188
356
  // ══════════════════════════════════════════════════════
189
357
  {
@@ -193,7 +361,7 @@ exports.getToolsFields = [
193
361
  displayOptions: {
194
362
  show: {
195
363
  resource: ['tools'],
196
- operation: ['imageToPrompt', 'faceSwap', 'upscale', 'instructEdit'],
364
+ operation: ['imageToPrompt', 'faceSwap', 'instructEdit'],
197
365
  },
198
366
  },
199
367
  default: '',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-supermachine",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
4
4
  "description": "n8n community node for Supermachine AI Image API — Generate images, manage models, LoRAs, and characters",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",