n8n-nodes-supermachine 0.6.2 → 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.
@@ -1011,6 +1011,159 @@ class Supermachine {
1011
1011
  }
1012
1012
  });
1013
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
+ }
1014
1167
  else {
1015
1168
  throw new Error(`⚠️ Operation "${operation}" is coming soon.`);
1016
1169
  }
@@ -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.2",
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",