n8n-nodes-supermachine 0.7.0 → 0.7.1

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.
@@ -1379,8 +1379,164 @@ class Supermachine {
1379
1379
  }
1380
1380
  });
1381
1381
  }
1382
+ else if (operation === 'faceSwap') {
1383
+ const faceImageSource = this.getNodeParameter('faceImageSource', i);
1384
+ const targetImageSource = this.getNodeParameter('targetImageSource', i);
1385
+ const additionalOptions = this.getNodeParameter('additionalOptions', i, {});
1386
+ let faceImageUrl = '';
1387
+ let targetImageUrl = '';
1388
+ // Step 1: Get face image URL
1389
+ if (faceImageSource === 'imageId') {
1390
+ const faceImageId = this.getNodeParameter('faceImageId', i);
1391
+ const faceImageDetails = await this.helpers.requestWithAuthentication.call(this, 'supermachineApi', {
1392
+ method: 'GET',
1393
+ url: `https://dev.supermachine.art/v1/images/${faceImageId}`,
1394
+ json: true,
1395
+ });
1396
+ faceImageUrl = faceImageDetails.url;
1397
+ if (!faceImageUrl) {
1398
+ throw new Error(`Face image ID ${faceImageId} does not have a valid URL`);
1399
+ }
1400
+ }
1401
+ else if (faceImageSource === 'imageUrl') {
1402
+ faceImageUrl = this.getNodeParameter('faceImageUrl', i);
1403
+ }
1404
+ // Step 2: Get target image URL
1405
+ if (targetImageSource === 'imageId') {
1406
+ const targetImageId = this.getNodeParameter('targetImageId', i);
1407
+ const targetImageDetails = await this.helpers.requestWithAuthentication.call(this, 'supermachineApi', {
1408
+ method: 'GET',
1409
+ url: `https://dev.supermachine.art/v1/images/${targetImageId}`,
1410
+ json: true,
1411
+ });
1412
+ targetImageUrl = targetImageDetails.url;
1413
+ if (!targetImageUrl) {
1414
+ throw new Error(`Target image ID ${targetImageId} does not have a valid URL`);
1415
+ }
1416
+ }
1417
+ else if (targetImageSource === 'imageUrl') {
1418
+ targetImageUrl = this.getNodeParameter('targetImageUrl', i);
1419
+ }
1420
+ // Step 3: Download face image as binary
1421
+ const faceImageBuffer = await this.helpers.httpRequest({
1422
+ method: 'GET',
1423
+ url: faceImageUrl,
1424
+ encoding: 'arraybuffer',
1425
+ returnFullResponse: false,
1426
+ });
1427
+ // Step 4: Download target image as binary
1428
+ const targetImageBuffer = await this.helpers.httpRequest({
1429
+ method: 'GET',
1430
+ url: targetImageUrl,
1431
+ encoding: 'arraybuffer',
1432
+ returnFullResponse: false,
1433
+ });
1434
+ // Step 5: Convert both images to base64
1435
+ const base64FaceImage = Buffer.from(faceImageBuffer).toString('base64');
1436
+ const base64TargetImage = Buffer.from(targetImageBuffer).toString('base64');
1437
+ // Detect MIME types
1438
+ let faceMimeType = 'image/jpeg';
1439
+ if (faceImageUrl.toLowerCase().includes('.png')) {
1440
+ faceMimeType = 'image/png';
1441
+ }
1442
+ else if (faceImageUrl.toLowerCase().includes('.webp')) {
1443
+ faceMimeType = 'image/webp';
1444
+ }
1445
+ let targetMimeType = 'image/jpeg';
1446
+ if (targetImageUrl.toLowerCase().includes('.png')) {
1447
+ targetMimeType = 'image/png';
1448
+ }
1449
+ else if (targetImageUrl.toLowerCase().includes('.webp')) {
1450
+ targetMimeType = 'image/webp';
1451
+ }
1452
+ const faceDataUri = `data:${faceMimeType};base64,${base64FaceImage}`;
1453
+ const targetDataUri = `data:${targetMimeType};base64,${base64TargetImage}`;
1454
+ // Step 6: Submit to face-swap API
1455
+ const body = {
1456
+ faceImage: faceDataUri,
1457
+ targetImage: targetDataUri,
1458
+ };
1459
+ // Add folderId if provided
1460
+ if (additionalOptions.folderId) {
1461
+ body.folderId = additionalOptions.folderId;
1462
+ }
1463
+ let faceSwapResponse;
1464
+ try {
1465
+ faceSwapResponse = await this.helpers.requestWithAuthentication.call(this, 'supermachineApi', {
1466
+ method: 'POST',
1467
+ url: 'https://dev.supermachine.art/v1/tools/faceswap',
1468
+ json: true,
1469
+ body,
1470
+ });
1471
+ }
1472
+ catch (error) {
1473
+ throw new Error(`Failed to swap faces: ${error.message}`);
1474
+ }
1475
+ const batchId = faceSwapResponse.batchId;
1476
+ if (!batchId) {
1477
+ throw new Error('No batch ID returned from face-swap API');
1478
+ }
1479
+ // Step 7: Handle polling or return batch ID
1480
+ const skipPolling = additionalOptions.skipPolling;
1481
+ if (skipPolling) {
1482
+ returnData.push({
1483
+ json: {
1484
+ success: true,
1485
+ batchId,
1486
+ tool: 'faceswap',
1487
+ creditsCost: faceSwapResponse.creditsCost || 2,
1488
+ creditsRemaining: faceSwapResponse.creditsRemaining,
1489
+ operation: 'faceSwap',
1490
+ faceImage: faceImageUrl,
1491
+ targetImage: targetImageUrl,
1492
+ message: 'Face swap job submitted. Use "Get Batch Status" to check results.',
1493
+ ...faceSwapResponse,
1494
+ },
1495
+ });
1496
+ }
1497
+ else {
1498
+ // Poll for results
1499
+ const pollingInterval = additionalOptions.pollingInterval || 2;
1500
+ const maxPollingTime = additionalOptions.maxPollingTime || 120;
1501
+ const startTime = Date.now();
1502
+ let completed = false;
1503
+ let finalResult = null;
1504
+ while (!completed && (Date.now() - startTime) / 1000 < maxPollingTime) {
1505
+ await new Promise((resolve) => setTimeout(resolve, pollingInterval * 1000));
1506
+ const statusResponse = await this.helpers.requestWithAuthentication.call(this, 'supermachineApi', {
1507
+ method: 'GET',
1508
+ url: `https://dev.supermachine.art/v1/batches/${batchId}`,
1509
+ json: true,
1510
+ });
1511
+ const status = statusResponse.status;
1512
+ if (status === 'completed') {
1513
+ completed = true;
1514
+ finalResult = statusResponse;
1515
+ }
1516
+ else if (status === 'failed') {
1517
+ throw new Error(`Face swap failed: ${statusResponse.error || 'Unknown error'}`);
1518
+ }
1519
+ }
1520
+ if (!completed) {
1521
+ throw new Error(`Face swap timed out after ${maxPollingTime} seconds`);
1522
+ }
1523
+ returnData.push({
1524
+ json: {
1525
+ success: true,
1526
+ batchId,
1527
+ status: 'completed',
1528
+ operation: 'faceSwap',
1529
+ tool: 'faceswap',
1530
+ faceImage: faceImageUrl,
1531
+ targetImage: targetImageUrl,
1532
+ completedAt: new Date().toISOString(),
1533
+ ...finalResult,
1534
+ },
1535
+ });
1536
+ }
1537
+ }
1382
1538
  else {
1383
- throw new Error(`⚠️ Operation "${operation}" is coming soon.`);
1539
+ throw new Error(`⚠️ Operation "${operation}" is not yet implemented.`);
1384
1540
  }
1385
1541
  }
1386
1542
  }
@@ -38,7 +38,7 @@ exports.getToolsOperations = [
38
38
  action: 'Image to prompt',
39
39
  },
40
40
  {
41
- name: 'Face Swap (Coming Soon)',
41
+ name: 'Face Swap',
42
42
  value: 'faceSwap',
43
43
  description: 'Swap faces in images',
44
44
  action: 'Face swap',
@@ -260,8 +260,8 @@ exports.getToolsFields = [
260
260
  description: 'High quality 4x upscaling (recommended)',
261
261
  },
262
262
  {
263
- name: 'R-ESRGAN 4x+ Anime',
264
- value: 'R-ESRGAN 4x+ Anime',
263
+ name: 'R-ESRGAN 4x+ Anime6B',
264
+ value: 'R-ESRGAN 4x+ Anime6B',
265
265
  description: 'Optimized for anime/cartoon images',
266
266
  },
267
267
  ],
@@ -546,19 +546,176 @@ exports.getToolsFields = [
546
546
  description: 'The URL of the image to analyze',
547
547
  },
548
548
  // ══════════════════════════════════════════════════════
549
- // COMING SOON NOTICE FOR OTHER OPERATIONS
549
+ // FACE SWAP FIELDS
550
550
  // ══════════════════════════════════════════════════════
551
551
  {
552
- displayName: 'Coming Soon',
553
- name: 'comingSoonNotice',
554
- type: 'notice',
552
+ displayName: 'Face Image Source',
553
+ name: 'faceImageSource',
554
+ type: 'options',
555
+ displayOptions: {
556
+ show: {
557
+ resource: ['tools'],
558
+ operation: ['faceSwap'],
559
+ },
560
+ },
561
+ options: [
562
+ {
563
+ name: 'Image ID',
564
+ value: 'imageId',
565
+ description: 'Use an image already in Supermachine by its ID',
566
+ },
567
+ {
568
+ name: 'Image URL',
569
+ value: 'imageUrl',
570
+ description: 'Use an external image URL',
571
+ },
572
+ ],
573
+ default: 'imageId',
574
+ description: 'Where to get the face image from (the face you want to use)',
575
+ },
576
+ {
577
+ displayName: 'Face Image ID',
578
+ name: 'faceImageId',
579
+ type: 'string',
580
+ displayOptions: {
581
+ show: {
582
+ resource: ['tools'],
583
+ operation: ['faceSwap'],
584
+ faceImageSource: ['imageId'],
585
+ },
586
+ },
587
+ required: true,
588
+ default: '',
589
+ placeholder: '12345',
590
+ description: 'The ID of the face image to use',
591
+ },
592
+ {
593
+ displayName: 'Face Image URL',
594
+ name: 'faceImageUrl',
595
+ type: 'string',
596
+ displayOptions: {
597
+ show: {
598
+ resource: ['tools'],
599
+ operation: ['faceSwap'],
600
+ faceImageSource: ['imageUrl'],
601
+ },
602
+ },
603
+ required: true,
604
+ default: '',
605
+ placeholder: 'https://example.com/face.jpg',
606
+ description: 'The URL of the face image to use',
607
+ },
608
+ {
609
+ displayName: 'Target Image Source',
610
+ name: 'targetImageSource',
611
+ type: 'options',
612
+ displayOptions: {
613
+ show: {
614
+ resource: ['tools'],
615
+ operation: ['faceSwap'],
616
+ },
617
+ },
618
+ options: [
619
+ {
620
+ name: 'Image ID',
621
+ value: 'imageId',
622
+ description: 'Use an image already in Supermachine by its ID',
623
+ },
624
+ {
625
+ name: 'Image URL',
626
+ value: 'imageUrl',
627
+ description: 'Use an external image URL',
628
+ },
629
+ ],
630
+ default: 'imageId',
631
+ description: 'Where to get the target image from (the image where face will be swapped)',
632
+ },
633
+ {
634
+ displayName: 'Target Image ID',
635
+ name: 'targetImageId',
636
+ type: 'string',
555
637
  displayOptions: {
556
638
  show: {
557
639
  resource: ['tools'],
558
640
  operation: ['faceSwap'],
641
+ targetImageSource: ['imageId'],
559
642
  },
560
643
  },
644
+ required: true,
561
645
  default: '',
562
- description: '⚠️ This tool operation is coming soon. It will be available in a future update when Supermachine API provides official endpoints.',
646
+ placeholder: '67890',
647
+ description: 'The ID of the target image',
648
+ },
649
+ {
650
+ displayName: 'Target Image URL',
651
+ name: 'targetImageUrl',
652
+ type: 'string',
653
+ displayOptions: {
654
+ show: {
655
+ resource: ['tools'],
656
+ operation: ['faceSwap'],
657
+ targetImageSource: ['imageUrl'],
658
+ },
659
+ },
660
+ required: true,
661
+ default: '',
662
+ placeholder: 'https://example.com/target.jpg',
663
+ description: 'The URL of the target image',
664
+ },
665
+ {
666
+ displayName: 'Additional Options',
667
+ name: 'additionalOptions',
668
+ type: 'collection',
669
+ placeholder: 'Add Option',
670
+ default: {},
671
+ displayOptions: {
672
+ show: {
673
+ resource: ['tools'],
674
+ operation: ['faceSwap'],
675
+ },
676
+ },
677
+ options: [
678
+ {
679
+ displayName: 'Save to Folder',
680
+ name: 'folderId',
681
+ type: 'options',
682
+ typeOptions: {
683
+ loadOptionsMethod: 'getFolders',
684
+ },
685
+ default: '',
686
+ 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>.',
687
+ },
688
+ {
689
+ displayName: 'Skip Polling',
690
+ name: 'skipPolling',
691
+ type: 'boolean',
692
+ default: false,
693
+ description: 'Whether to skip waiting for the result and return immediately with batch ID',
694
+ },
695
+ {
696
+ displayName: 'Polling Interval (seconds)',
697
+ name: 'pollingInterval',
698
+ type: 'number',
699
+ default: 2,
700
+ description: 'How often to check if face swap is complete (in seconds)',
701
+ displayOptions: {
702
+ show: {
703
+ skipPolling: [false],
704
+ },
705
+ },
706
+ },
707
+ {
708
+ displayName: 'Max Polling Time (seconds)',
709
+ name: 'maxPollingTime',
710
+ type: 'number',
711
+ default: 120,
712
+ description: 'Maximum time to wait for face swap to complete (in seconds)',
713
+ displayOptions: {
714
+ show: {
715
+ skipPolling: [false],
716
+ },
717
+ },
718
+ },
719
+ ],
563
720
  },
564
721
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-supermachine",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
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",