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 +24 -0
- package/dist/credentials/SupermachineApi.credentials.js +36 -0
- package/dist/index.js +7 -0
- package/dist/nodes/Supermachine/Supermachine.node.js +477 -0
- package/dist/nodes/Supermachine/descriptions/AccountDescription.js +71 -0
- package/dist/nodes/Supermachine/descriptions/ImageDescription.js +355 -0
- package/dist/nodes/Supermachine/descriptions/ToolsDescription.js +239 -0
- package/dist/nodes/Supermachine/descriptions/index.js +19 -0
- package/dist/nodes/Supermachine/index.js +3 -0
- package/dist/nodes/Supermachine/supermachine.svg +132 -0
- package/package.json +32 -20
package/README.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# n8n-nodes-supermachine
|
|
2
|
+
|
|
3
|
+
[](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
|
+
];
|