n8n-nodes-supermachine 0.1.1 → 0.1.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.
package/README.md ADDED
@@ -0,0 +1,24 @@
1
+ # n8n-nodes-supermachine
2
+
3
+ [![npm version](https://badge.fury.io/js/n8n-nodes-supermachine.svg)](https://www.npmjs.com/package/n8n-nodes-supermachine)
4
+
5
+ This is an n8n community node for the [Supermachine AI Image API](https://supermachine.art). It lets you generate AI images, manage models, LoRAs, and characters in your n8n workflows.
6
+
7
+ [n8n](https://n8n.io/) is a [fair-code licensed](https://docs.n8n.io/reference/license/) workflow automation platform.
8
+
9
+ ## Installation
10
+
11
+ Follow the [installation guide](https://docs.n8n.io/integrations/community-nodes/installation/) in the n8n community nodes documentation.
12
+
13
+ ### Via n8n UI
14
+
15
+ 1. Go to **Settings** > **Community Nodes**
16
+ 2. Select **Install**
17
+ 3. Enter `n8n-nodes-supermachine` in **Enter npm package name**
18
+ 4. Click **Install**
19
+ 5. Restart n8n
20
+
21
+ ### Via CLI
22
+
23
+ ```bash
24
+ npm install n8n-nodes-supermachine
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SupermachineApi = void 0;
4
+ class SupermachineApi {
5
+ name = 'supermachineApi';
6
+ displayName = 'Supermachine API';
7
+ documentationUrl = 'https://knowledge.supermachine.art/help/accessing-the-supermachine-api';
8
+ properties = [
9
+ {
10
+ displayName: 'API Key',
11
+ name: 'apiKey',
12
+ type: 'string',
13
+ typeOptions: { password: true },
14
+ default: '',
15
+ required: true,
16
+ placeholder: 'sk-xxxxxxxxxxxxxxxxxxxxxx',
17
+ description: 'Your Supermachine API key. Get it from <a href="https://supermachine.art/settings/api" target="_blank">Supermachine Settings</a>',
18
+ },
19
+ ];
20
+ authenticate = {
21
+ type: 'generic',
22
+ properties: {
23
+ headers: {
24
+ 'Authorization': '=Bearer {{$credentials.apiKey}}',
25
+ },
26
+ },
27
+ };
28
+ test = {
29
+ request: {
30
+ baseURL: 'https://dev.supermachine.art',
31
+ url: '/v1/user',
32
+ method: 'GET',
33
+ },
34
+ };
35
+ }
36
+ exports.SupermachineApi = SupermachineApi;
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.credentials = exports.nodes = void 0;
4
+ const Supermachine_node_1 = require("./nodes/Supermachine/Supermachine.node");
5
+ const SupermachineApi_credentials_1 = require("./credentials/SupermachineApi.credentials");
6
+ exports.nodes = [Supermachine_node_1.Supermachine];
7
+ exports.credentials = [SupermachineApi_credentials_1.SupermachineApi];
@@ -0,0 +1,477 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Supermachine = void 0;
4
+ const descriptions_1 = require("./descriptions");
5
+ class Supermachine {
6
+ description = {
7
+ displayName: 'Supermachine',
8
+ name: 'supermachine',
9
+ icon: 'file:supermachine.svg',
10
+ group: ['transform'],
11
+ version: 1,
12
+ subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
13
+ description: 'Interact with Supermachine AI Image API',
14
+ defaults: { name: 'Supermachine' },
15
+ inputs: ['main'],
16
+ outputs: ['main'],
17
+ credentials: [{ name: 'supermachineApi', required: true }],
18
+ properties: [
19
+ {
20
+ displayName: 'Resource',
21
+ name: 'resource',
22
+ type: 'options',
23
+ noDataExpression: true,
24
+ options: [
25
+ { name: 'Account', value: 'account' },
26
+ { name: 'Image', value: 'image' },
27
+ { name: 'Tools', value: 'tools' },
28
+ ],
29
+ default: 'image',
30
+ },
31
+ ...descriptions_1.getAccountOperations,
32
+ ...descriptions_1.getAccountFields,
33
+ ...descriptions_1.getImageOperations,
34
+ ...descriptions_1.getImageFields,
35
+ ...descriptions_1.getToolsOperations,
36
+ ...descriptions_1.getToolsFields,
37
+ ],
38
+ };
39
+ methods = {
40
+ loadOptions: {
41
+ // ═══════════════════════════════════════════════════════════
42
+ // Load danh sách Models
43
+ // ═══════════════════════════════════════════════════════════
44
+ async getModels() {
45
+ const credentials = await this.getCredentials('supermachineApi');
46
+ const apiKey = credentials.apiKey;
47
+ try {
48
+ const response = await this.helpers.httpRequest({
49
+ method: 'GET',
50
+ url: 'https://dev.supermachine.art/v1/models',
51
+ headers: {
52
+ 'Authorization': `Bearer ${apiKey}`,
53
+ 'Content-Type': 'application/json',
54
+ },
55
+ json: true,
56
+ });
57
+ const items = response.items;
58
+ if (!Array.isArray(items) || items.length === 0) {
59
+ return [{ name: 'No models available', value: '' }];
60
+ }
61
+ return items.map((model) => ({
62
+ name: model.title,
63
+ value: model.title,
64
+ description: `${model.slug} (ID: ${model.id})`,
65
+ }));
66
+ }
67
+ catch (error) {
68
+ console.error('Supermachine getModels error:', error);
69
+ return [{ name: `⚠️ Error: ${error.message}`, value: '' }];
70
+ }
71
+ },
72
+ // ═══════════════════════════════════════════════════════════
73
+ // Load danh sách LoRAs theo modelId được chọn
74
+ // ═══════════════════════════════════════════════════════════
75
+ async getLoras() {
76
+ const credentials = await this.getCredentials('supermachineApi');
77
+ const apiKey = credentials.apiKey;
78
+ const modelTitle = this.getNodeParameter('model');
79
+ if (!modelTitle) {
80
+ return [{
81
+ name: '⚠️ Please select a model first',
82
+ value: '',
83
+ description: 'LoRAs depend on the selected model'
84
+ }];
85
+ }
86
+ try {
87
+ const modelsResponse = await this.helpers.httpRequest({
88
+ method: 'GET',
89
+ url: 'https://dev.supermachine.art/v1/models',
90
+ headers: {
91
+ 'Authorization': `Bearer ${apiKey}`,
92
+ 'Content-Type': 'application/json',
93
+ },
94
+ json: true,
95
+ });
96
+ const models = modelsResponse.items;
97
+ const selectedModel = models.find((m) => m.title === modelTitle);
98
+ if (!selectedModel) {
99
+ return [{
100
+ name: `⚠️ Model "${modelTitle}" not found`,
101
+ value: '',
102
+ description: 'Try refreshing the page'
103
+ }];
104
+ }
105
+ const modelId = selectedModel.id;
106
+ const lorasResponse = await this.helpers.httpRequest({
107
+ method: 'GET',
108
+ url: `https://dev.supermachine.art/v1/loras?modelId=${modelId}`,
109
+ headers: {
110
+ 'Authorization': `Bearer ${apiKey}`,
111
+ 'Content-Type': 'application/json',
112
+ },
113
+ json: true,
114
+ });
115
+ const items = lorasResponse.items;
116
+ if (!Array.isArray(items) || items.length === 0) {
117
+ return [{
118
+ name: 'No LoRAs available for this model',
119
+ value: '',
120
+ description: `Model "${modelTitle}" doesn't support LoRAs`
121
+ }];
122
+ }
123
+ return items.map((lora) => {
124
+ const triggerWords = lora.triggerWords || [];
125
+ const triggerHint = triggerWords.length > 0
126
+ ? ` | Trigger: ${triggerWords.join(', ')}`
127
+ : '';
128
+ return {
129
+ name: lora.name,
130
+ value: String(lora.id),
131
+ description: `${lora.slug}${triggerHint}`,
132
+ };
133
+ });
134
+ }
135
+ catch (error) {
136
+ console.error('Supermachine getLoras error:', error);
137
+ return [{
138
+ name: `⚠️ Error loading LoRAs: ${error.message}`,
139
+ value: '',
140
+ description: 'Check your API key and network connection'
141
+ }];
142
+ }
143
+ },
144
+ // ═══════════════════════════════════════════════════════════
145
+ // Load danh sách Character Categories
146
+ // ═══════════════════════════════════════════════════════════
147
+ async getCharacterCategories() {
148
+ const credentials = await this.getCredentials('supermachineApi');
149
+ const apiKey = credentials.apiKey;
150
+ try {
151
+ const response = await this.helpers.httpRequest({
152
+ method: 'GET',
153
+ url: 'https://dev.supermachine.art/v1/characters/categories',
154
+ headers: {
155
+ 'Authorization': `Bearer ${apiKey}`,
156
+ 'Content-Type': 'application/json',
157
+ },
158
+ json: true,
159
+ });
160
+ const items = response.items;
161
+ if (!Array.isArray(items) || items.length === 0) {
162
+ return [{
163
+ name: 'No categories available',
164
+ value: '',
165
+ description: 'Character categories may not be enabled'
166
+ }];
167
+ }
168
+ return items.map((category) => ({
169
+ name: category.name,
170
+ value: String(category.id),
171
+ description: `Category ID: ${category.id}`,
172
+ }));
173
+ }
174
+ catch (error) {
175
+ console.error('Supermachine getCharacterCategories error:', error);
176
+ return [{
177
+ name: `⚠️ Error: ${error.message}`,
178
+ value: '',
179
+ description: 'Check your API key and permissions'
180
+ }];
181
+ }
182
+ },
183
+ // ═══════════════════════════════════════════════════════════
184
+ // Load danh sách Characters (phụ thuộc category + model)
185
+ // ═══════════════════════════════════════════════════════════
186
+ async getCharacters() {
187
+ const credentials = await this.getCredentials('supermachineApi');
188
+ const apiKey = credentials.apiKey;
189
+ const additionalFields = this.getCurrentNodeParameter('additionalFields');
190
+ const categoryId = additionalFields?.characterCategory;
191
+ const modelTitle = this.getNodeParameter('model');
192
+ if (!categoryId) {
193
+ return [{
194
+ name: '⚠️ Please select a Character Category first',
195
+ value: '',
196
+ description: 'Characters depend on the selected category'
197
+ }];
198
+ }
199
+ if (!modelTitle) {
200
+ return [{
201
+ name: '⚠️ Please select a Model first',
202
+ value: '',
203
+ description: 'Characters depend on the selected model'
204
+ }];
205
+ }
206
+ try {
207
+ const modelsResponse = await this.helpers.httpRequest({
208
+ method: 'GET',
209
+ url: 'https://dev.supermachine.art/v1/models',
210
+ headers: {
211
+ 'Authorization': `Bearer ${apiKey}`,
212
+ 'Content-Type': 'application/json',
213
+ },
214
+ json: true,
215
+ });
216
+ const models = modelsResponse.items;
217
+ const selectedModel = models.find((m) => m.title === modelTitle);
218
+ if (!selectedModel) {
219
+ return [{
220
+ name: `⚠️ Model "${modelTitle}" not found`,
221
+ value: '',
222
+ description: 'Try refreshing the page'
223
+ }];
224
+ }
225
+ const modelId = selectedModel.id;
226
+ const charactersResponse = await this.helpers.httpRequest({
227
+ method: 'GET',
228
+ url: `https://dev.supermachine.art/v1/characters?categoryId=${categoryId}&modelId=${modelId}`,
229
+ headers: {
230
+ 'Authorization': `Bearer ${apiKey}`,
231
+ 'Content-Type': 'application/json',
232
+ },
233
+ json: true,
234
+ });
235
+ const items = charactersResponse.items;
236
+ if (!Array.isArray(items) || items.length === 0) {
237
+ return [{
238
+ name: 'No characters available',
239
+ value: '',
240
+ description: `No characters found for this category and model combination`
241
+ }];
242
+ }
243
+ return items.map((character) => {
244
+ const embedCode = character.embedCode;
245
+ const embedHint = embedCode ? ` | Use: "${embedCode}"` : '';
246
+ return {
247
+ name: character.name,
248
+ value: String(character.id),
249
+ description: `${character.slug}${embedHint}`,
250
+ };
251
+ });
252
+ }
253
+ catch (error) {
254
+ console.error('Supermachine getCharacters error:', error);
255
+ return [{
256
+ name: `⚠️ Error loading characters: ${error.message}`,
257
+ value: '',
258
+ description: 'Check your API key and network connection'
259
+ }];
260
+ }
261
+ },
262
+ },
263
+ };
264
+ async execute() {
265
+ const items = this.getInputData();
266
+ const returnData = [];
267
+ for (let i = 0; i < items.length; i++) {
268
+ try {
269
+ const resource = this.getNodeParameter('resource', i);
270
+ const operation = this.getNodeParameter('operation', i);
271
+ let requestOptions = {
272
+ method: 'GET',
273
+ url: '',
274
+ json: true,
275
+ };
276
+ // ══════════════════════════════════════════════════════
277
+ // ACCOUNT OPERATIONS
278
+ // ══════════════════════════════════════════════════════
279
+ if (resource === 'account') {
280
+ if (operation === 'getProfile') {
281
+ requestOptions.url = 'https://dev.supermachine.art/v1/user';
282
+ }
283
+ else if (operation === 'listModels') {
284
+ requestOptions.url = 'https://dev.supermachine.art/v1/models';
285
+ if (!this.getNodeParameter('returnAll', i, false)) {
286
+ requestOptions.qs = {
287
+ perPage: this.getNodeParameter('limit', i, 20),
288
+ page: 1,
289
+ };
290
+ }
291
+ }
292
+ else if (operation === 'getModelDetail') {
293
+ const modelTitle = this.getNodeParameter('modelId', i);
294
+ const modelsResponse = await this.helpers.requestWithAuthentication.call(this, 'supermachineApi', { method: 'GET', url: 'https://dev.supermachine.art/v1/models', json: true });
295
+ const models = modelsResponse.items;
296
+ const model = models.find((m) => m.title === modelTitle);
297
+ if (!model) {
298
+ throw new Error(`Model "${modelTitle}" not found`);
299
+ }
300
+ requestOptions.url = `https://dev.supermachine.art/v1/models/gems/id/${model.id}`;
301
+ }
302
+ else if (operation === 'listModelCategories') {
303
+ requestOptions.url = 'https://dev.supermachine.art/v1/models/categories';
304
+ }
305
+ else if (operation === 'listLoras') {
306
+ const modelTitle = this.getNodeParameter('modelId', i);
307
+ const modelsResponse = await this.helpers.requestWithAuthentication.call(this, 'supermachineApi', { method: 'GET', url: 'https://dev.supermachine.art/v1/models', json: true });
308
+ const models = modelsResponse.items;
309
+ const model = models.find((m) => m.title === modelTitle);
310
+ if (!model) {
311
+ throw new Error(`Model "${modelTitle}" not found`);
312
+ }
313
+ requestOptions.url = `https://dev.supermachine.art/v1/loras?modelId=${model.id}`;
314
+ }
315
+ else if (operation === 'listLoraCategories') {
316
+ requestOptions.url = 'https://dev.supermachine.art/v1/loras/categories';
317
+ }
318
+ else if (operation === 'listCharacterCategories') {
319
+ requestOptions.url = 'https://dev.supermachine.art/v1/characters/categories';
320
+ }
321
+ }
322
+ // ══════════════════════════════════════════════════════
323
+ // IMAGE OPERATIONS
324
+ // ══════════════════════════════════════════════════════
325
+ else if (resource === 'image') {
326
+ if (operation === 'generate') {
327
+ // BƯỚC 1: Start generation
328
+ const additionalFields = this.getNodeParameter('additionalFields', i, {});
329
+ const body = {
330
+ prompt: this.getNodeParameter('prompt', i),
331
+ model: this.getNodeParameter('model', i),
332
+ width: this.getNodeParameter('width', i, 1024),
333
+ height: this.getNodeParameter('height', i, 768),
334
+ };
335
+ // Add optional fields
336
+ if (additionalFields.negativePrompt) {
337
+ body.negativePrompt = additionalFields.negativePrompt;
338
+ }
339
+ if (additionalFields.numberResults) {
340
+ body.numberResults = additionalFields.numberResults;
341
+ }
342
+ if (additionalFields.outputFormat) {
343
+ body.outputFormat = additionalFields.outputFormat;
344
+ }
345
+ if (additionalFields.seed) {
346
+ body.seed = additionalFields.seed;
347
+ }
348
+ if (additionalFields.steps) {
349
+ body.steps = additionalFields.steps;
350
+ }
351
+ if (additionalFields.cfgScale) {
352
+ body.cfgScale = additionalFields.cfgScale;
353
+ }
354
+ // Add LoRAs
355
+ if (additionalFields.loras) {
356
+ const lorasData = additionalFields.loras;
357
+ const loraValues = lorasData.loraValues;
358
+ if (Array.isArray(loraValues) && loraValues.length > 0) {
359
+ body.loras = loraValues.map((lora) => ({
360
+ id: lora.loraId,
361
+ strength: lora.strength || 1,
362
+ }));
363
+ }
364
+ }
365
+ // Add character
366
+ if (additionalFields.characterId) {
367
+ body.characterId = additionalFields.characterId;
368
+ }
369
+ // Add img2img
370
+ if (additionalFields.img2img) {
371
+ const img2imgData = additionalFields.img2img;
372
+ const img2imgValues = img2imgData.img2imgValues;
373
+ if (Array.isArray(img2imgValues) && img2imgValues.length > 0) {
374
+ const img2img = img2imgValues[0];
375
+ if (img2img.sourceImageUrl) {
376
+ body.sourceImageUrl = img2img.sourceImageUrl;
377
+ body.denoisingStrength = img2img.denoisingStrength || 0.75;
378
+ }
379
+ }
380
+ }
381
+ // Start generation
382
+ const generateResponse = await this.helpers.requestWithAuthentication.call(this, 'supermachineApi', {
383
+ method: 'POST',
384
+ url: 'https://dev.supermachine.art/v1/generate',
385
+ json: true,
386
+ body,
387
+ });
388
+ const batchId = generateResponse.batchId;
389
+ if (!batchId) {
390
+ throw new Error('No batchId returned from generate API');
391
+ }
392
+ // BƯỚC 2: Poll for completion
393
+ const pollingInterval = additionalFields.pollingInterval || 2;
394
+ const maxPollingTime = additionalFields.maxPollingTime || 120;
395
+ const startTime = Date.now();
396
+ let completedImage = null;
397
+ while (!completedImage) {
398
+ // Check timeout
399
+ if ((Date.now() - startTime) / 1000 > maxPollingTime) {
400
+ throw new Error(`Image generation timed out after ${maxPollingTime} seconds. BatchId: ${batchId}`);
401
+ }
402
+ // Wait before polling
403
+ await new Promise((resolve) => setTimeout(resolve, pollingInterval * 1000));
404
+ // Poll status
405
+ const pollResponse = await this.helpers.requestWithAuthentication.call(this, 'supermachineApi', {
406
+ method: 'GET',
407
+ url: `https://dev.supermachine.art/v1/images?batchId=${batchId}`,
408
+ json: true,
409
+ });
410
+ const items = pollResponse.items;
411
+ if (Array.isArray(items) && items.length > 0) {
412
+ const firstItem = items[0];
413
+ const status = firstItem.status;
414
+ if (status === 'completed') {
415
+ completedImage = firstItem;
416
+ break;
417
+ }
418
+ else if (status === 'failed') {
419
+ throw new Error(`Image generation failed. BatchId: ${batchId}. Error: ${firstItem.error || 'Unknown error'}`);
420
+ }
421
+ // If status is 'pending' or 'processing', continue polling
422
+ }
423
+ }
424
+ // Return completed image data
425
+ returnData.push({
426
+ json: {
427
+ batchId,
428
+ ...completedImage,
429
+ }
430
+ });
431
+ }
432
+ else if (operation === 'listImages') {
433
+ const batchId = this.getNodeParameter('batchId', i);
434
+ requestOptions.url = `https://dev.supermachine.art/v1/images?batchId=${batchId}`;
435
+ if (!this.getNodeParameter('returnAll', i, false)) {
436
+ requestOptions.qs = {
437
+ limit: this.getNodeParameter('limit', i, 20),
438
+ page: this.getNodeParameter('page', i, 1),
439
+ };
440
+ }
441
+ const responseData = await this.helpers.requestWithAuthentication.call(this, 'supermachineApi', requestOptions);
442
+ returnData.push({ json: responseData });
443
+ }
444
+ }
445
+ // ══════════════════════════════════════════════════════
446
+ // TOOLS OPERATIONS
447
+ // ══════════════════════════════════════════════════════
448
+ else if (resource === 'tools') {
449
+ throw new Error('Tools operations chưa có endpoints chính thức từ Supermachine API docs. Vui lòng check documentation.');
450
+ }
451
+ const responseData = await this.helpers.requestWithAuthentication.call(this, 'supermachineApi', requestOptions);
452
+ let jsonData;
453
+ if (operation === 'listModels' || operation === 'listImages' || operation === 'listLoras') {
454
+ jsonData = responseData;
455
+ }
456
+ else {
457
+ jsonData = responseData.items || responseData;
458
+ }
459
+ returnData.push({ json: jsonData });
460
+ }
461
+ catch (error) {
462
+ if (this.continueOnFail()) {
463
+ returnData.push({
464
+ json: {
465
+ error: error.message,
466
+ stack: error.stack,
467
+ }
468
+ });
469
+ continue;
470
+ }
471
+ throw error;
472
+ }
473
+ }
474
+ return [returnData];
475
+ }
476
+ }
477
+ exports.Supermachine = Supermachine;
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getAccountFields = exports.getAccountOperations = void 0;
4
+ exports.getAccountOperations = [
5
+ {
6
+ displayName: 'Operation',
7
+ name: 'operation',
8
+ type: 'options',
9
+ noDataExpression: true,
10
+ displayOptions: {
11
+ show: {
12
+ resource: ['account'],
13
+ },
14
+ },
15
+ options: [
16
+ {
17
+ name: 'Get Profile',
18
+ value: 'getProfile',
19
+ description: 'Lấy thông tin tài khoản và credit còn lại',
20
+ action: 'Get account profile',
21
+ },
22
+ {
23
+ name: 'List Models',
24
+ value: 'listModels',
25
+ description: 'Liệt kê tất cả models có sẵn',
26
+ action: 'List available models',
27
+ },
28
+ {
29
+ name: 'Get Model Categories',
30
+ value: 'listModelCategories',
31
+ description: 'Lấy danh mục models',
32
+ action: 'List model categories',
33
+ },
34
+ ],
35
+ default: 'getProfile',
36
+ },
37
+ ];
38
+ exports.getAccountFields = [
39
+ // Get Profile - không cần fields
40
+ {
41
+ displayName: 'Return All',
42
+ name: 'returnAll',
43
+ type: 'boolean',
44
+ displayOptions: {
45
+ show: {
46
+ resource: ['account'],
47
+ operation: ['listModels', 'listModelCategories'],
48
+ },
49
+ },
50
+ default: false,
51
+ description: 'Trả về tất cả kết quả hay chỉ giới hạn',
52
+ },
53
+ {
54
+ displayName: 'Limit',
55
+ name: 'limit',
56
+ type: 'number',
57
+ typeOptions: {
58
+ minValue: 1,
59
+ maxValue: 100,
60
+ },
61
+ displayOptions: {
62
+ show: {
63
+ resource: ['account'],
64
+ operation: ['listModels', 'listModelCategories'],
65
+ returnAll: [false],
66
+ },
67
+ },
68
+ default: 20,
69
+ description: 'Số lượng items trả về mỗi trang',
70
+ },
71
+ ];