n8n-nodes-pollinations-ai 1.0.0 → 1.2.0
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 +82 -74
- package/credentials/PollinationsApi.credentials.ts +11 -2
- package/dist/credentials/PollinationsApi.credentials.d.ts +2 -1
- package/dist/credentials/PollinationsApi.credentials.js +8 -2
- package/dist/nodes/Pollinations/Pollinations.node.d.ts +8 -1
- package/dist/nodes/Pollinations/Pollinations.node.js +555 -32
- package/dist/nodes/Pollinations/PollinationsChatModel.node.d.ts +10 -0
- package/dist/nodes/Pollinations/PollinationsChatModel.node.js +219 -0
- package/dist/nodes/Pollinations/pollinations.svg +1 -1
- package/nodes/Pollinations/Pollinations.node.json +2 -5
- package/nodes/Pollinations/Pollinations.node.ts +658 -32
- package/nodes/Pollinations/PollinationsChatModel.node.json +17 -0
- package/nodes/Pollinations/PollinationsChatModel.node.ts +247 -0
- package/nodes/Pollinations/pollinations.svg +1 -1
- package/package.json +12 -4
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
IExecuteFunctions,
|
|
3
|
+
ILoadOptionsFunctions,
|
|
3
4
|
INodeExecutionData,
|
|
5
|
+
INodePropertyOptions,
|
|
4
6
|
INodeType,
|
|
5
7
|
INodeTypeDescription,
|
|
8
|
+
NodeOperationError,
|
|
6
9
|
} from 'n8n-workflow';
|
|
7
10
|
|
|
8
11
|
export class Pollinations implements INodeType {
|
|
@@ -13,7 +16,7 @@ export class Pollinations implements INodeType {
|
|
|
13
16
|
group: ['transform'],
|
|
14
17
|
version: 1,
|
|
15
18
|
subtitle: '={{$parameter["operation"]}}',
|
|
16
|
-
description: 'Generate images using Pollinations AI',
|
|
19
|
+
description: 'Generate images and text using Pollinations AI',
|
|
17
20
|
defaults: {
|
|
18
21
|
name: 'Pollinations',
|
|
19
22
|
},
|
|
@@ -39,11 +42,25 @@ export class Pollinations implements INodeType {
|
|
|
39
42
|
description: 'Generate an image from a text prompt',
|
|
40
43
|
action: 'Generate an image from a text prompt',
|
|
41
44
|
},
|
|
45
|
+
{
|
|
46
|
+
name: 'Generate with Reference',
|
|
47
|
+
value: 'generateImageWithReference',
|
|
48
|
+
description: 'Generate an image using a reference image',
|
|
49
|
+
action: 'Generate an image using a reference image',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: 'Generate Text',
|
|
53
|
+
value: 'generateText',
|
|
54
|
+
description: 'Generate text from a prompt using AI',
|
|
55
|
+
action: 'Generate text from a prompt',
|
|
56
|
+
},
|
|
42
57
|
],
|
|
43
58
|
default: 'generateImage',
|
|
44
59
|
},
|
|
45
60
|
|
|
46
|
-
//
|
|
61
|
+
// ==================== GENERATE IMAGE ====================
|
|
62
|
+
|
|
63
|
+
// Prompt (Image)
|
|
47
64
|
{
|
|
48
65
|
displayName: 'Prompt',
|
|
49
66
|
name: 'prompt',
|
|
@@ -61,7 +78,7 @@ export class Pollinations implements INodeType {
|
|
|
61
78
|
},
|
|
62
79
|
},
|
|
63
80
|
|
|
64
|
-
// Model (
|
|
81
|
+
// Model (Image) - Dynamic loading
|
|
65
82
|
{
|
|
66
83
|
displayName: 'Model',
|
|
67
84
|
name: 'model',
|
|
@@ -72,56 +89,141 @@ export class Pollinations implements INodeType {
|
|
|
72
89
|
operation: ['generateImage'],
|
|
73
90
|
},
|
|
74
91
|
},
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
92
|
+
typeOptions: {
|
|
93
|
+
loadOptionsMethod: 'getImageModels',
|
|
94
|
+
},
|
|
95
|
+
description: 'The model to use for image generation',
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
// Advanced Options (Image)
|
|
99
|
+
{
|
|
100
|
+
displayName: 'Options',
|
|
101
|
+
name: 'options',
|
|
102
|
+
type: 'collection',
|
|
103
|
+
placeholder: 'Add Option',
|
|
104
|
+
default: {},
|
|
105
|
+
displayOptions: {
|
|
106
|
+
show: {
|
|
107
|
+
operation: ['generateImage'],
|
|
80
108
|
},
|
|
109
|
+
},
|
|
110
|
+
options: [
|
|
81
111
|
{
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
112
|
+
displayName: 'Width',
|
|
113
|
+
name: 'width',
|
|
114
|
+
type: 'number',
|
|
115
|
+
default: 1024,
|
|
116
|
+
description: 'Width of the generated image in pixels',
|
|
117
|
+
typeOptions: {
|
|
118
|
+
minValue: 64,
|
|
119
|
+
maxValue: 2048,
|
|
120
|
+
},
|
|
85
121
|
},
|
|
86
122
|
{
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
123
|
+
displayName: 'Height',
|
|
124
|
+
name: 'height',
|
|
125
|
+
type: 'number',
|
|
126
|
+
default: 1024,
|
|
127
|
+
description: 'Height of the generated image in pixels',
|
|
128
|
+
typeOptions: {
|
|
129
|
+
minValue: 64,
|
|
130
|
+
maxValue: 2048,
|
|
131
|
+
},
|
|
90
132
|
},
|
|
91
133
|
{
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
134
|
+
displayName: 'Seed',
|
|
135
|
+
name: 'seed',
|
|
136
|
+
type: 'number',
|
|
137
|
+
default: 0,
|
|
138
|
+
description: 'Seed for reproducible generation. Use 0 for random.',
|
|
95
139
|
},
|
|
96
140
|
{
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
141
|
+
displayName: 'No Logo',
|
|
142
|
+
name: 'nologo',
|
|
143
|
+
type: 'boolean',
|
|
144
|
+
default: false,
|
|
145
|
+
description: 'Whether to remove the Pollinations watermark',
|
|
100
146
|
},
|
|
101
147
|
{
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
148
|
+
displayName: 'Enhance Prompt',
|
|
149
|
+
name: 'enhance',
|
|
150
|
+
type: 'boolean',
|
|
151
|
+
default: false,
|
|
152
|
+
description: 'Whether to automatically enhance the prompt for better results',
|
|
105
153
|
},
|
|
106
154
|
{
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
155
|
+
displayName: 'Safe Mode',
|
|
156
|
+
name: 'safe',
|
|
157
|
+
type: 'boolean',
|
|
158
|
+
default: false,
|
|
159
|
+
description: 'Whether to enable content safety filter',
|
|
110
160
|
},
|
|
111
161
|
],
|
|
112
|
-
description: 'The model to use for image generation',
|
|
113
162
|
},
|
|
114
163
|
|
|
115
|
-
//
|
|
164
|
+
// ==================== GENERATE IMAGE WITH REFERENCE ====================
|
|
165
|
+
|
|
166
|
+
// Prompt (Reference)
|
|
167
|
+
{
|
|
168
|
+
displayName: 'Prompt',
|
|
169
|
+
name: 'referencePrompt',
|
|
170
|
+
type: 'string',
|
|
171
|
+
default: '',
|
|
172
|
+
required: true,
|
|
173
|
+
displayOptions: {
|
|
174
|
+
show: {
|
|
175
|
+
operation: ['generateImageWithReference'],
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
description: 'The text prompt describing how to transform or use the reference image',
|
|
179
|
+
typeOptions: {
|
|
180
|
+
rows: 4,
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
// Reference Image URL (Required)
|
|
185
|
+
{
|
|
186
|
+
displayName: 'Reference Image URL',
|
|
187
|
+
name: 'referenceImage',
|
|
188
|
+
type: 'string',
|
|
189
|
+
default: '',
|
|
190
|
+
required: true,
|
|
191
|
+
displayOptions: {
|
|
192
|
+
show: {
|
|
193
|
+
operation: ['generateImageWithReference'],
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
placeholder: 'https://example.com/image.jpg',
|
|
197
|
+
description: 'URL of the reference image. Must be publicly accessible.',
|
|
198
|
+
},
|
|
199
|
+
|
|
200
|
+
// Model (Reference) - Dynamic loading with image input support
|
|
201
|
+
{
|
|
202
|
+
displayName: 'Model',
|
|
203
|
+
name: 'referenceModel',
|
|
204
|
+
type: 'options',
|
|
205
|
+
default: 'kontext',
|
|
206
|
+
displayOptions: {
|
|
207
|
+
show: {
|
|
208
|
+
operation: ['generateImageWithReference'],
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
typeOptions: {
|
|
212
|
+
loadOptionsMethod: 'getImageModelsWithReferenceSupport',
|
|
213
|
+
},
|
|
214
|
+
description: 'The model to use. Only models supporting image input are shown.',
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
// Advanced Options (Reference)
|
|
116
218
|
{
|
|
117
219
|
displayName: 'Options',
|
|
118
|
-
name: '
|
|
220
|
+
name: 'referenceOptions',
|
|
119
221
|
type: 'collection',
|
|
120
222
|
placeholder: 'Add Option',
|
|
121
223
|
default: {},
|
|
122
224
|
displayOptions: {
|
|
123
225
|
show: {
|
|
124
|
-
operation: ['
|
|
226
|
+
operation: ['generateImageWithReference'],
|
|
125
227
|
},
|
|
126
228
|
},
|
|
127
229
|
options: [
|
|
@@ -177,9 +279,321 @@ export class Pollinations implements INodeType {
|
|
|
177
279
|
},
|
|
178
280
|
],
|
|
179
281
|
},
|
|
282
|
+
|
|
283
|
+
// ==================== GENERATE TEXT ====================
|
|
284
|
+
|
|
285
|
+
// Prompt (Text)
|
|
286
|
+
{
|
|
287
|
+
displayName: 'Prompt',
|
|
288
|
+
name: 'textPrompt',
|
|
289
|
+
type: 'string',
|
|
290
|
+
default: '',
|
|
291
|
+
required: true,
|
|
292
|
+
displayOptions: {
|
|
293
|
+
show: {
|
|
294
|
+
operation: ['generateText'],
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
description: 'The text prompt or question for the AI model',
|
|
298
|
+
typeOptions: {
|
|
299
|
+
rows: 4,
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
|
|
303
|
+
// Model (Text) - Dynamic loading
|
|
304
|
+
{
|
|
305
|
+
displayName: 'Model',
|
|
306
|
+
name: 'textModel',
|
|
307
|
+
type: 'options',
|
|
308
|
+
default: 'openai',
|
|
309
|
+
displayOptions: {
|
|
310
|
+
show: {
|
|
311
|
+
operation: ['generateText'],
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
typeOptions: {
|
|
315
|
+
loadOptionsMethod: 'getTextModels',
|
|
316
|
+
},
|
|
317
|
+
description: 'The AI model to use for text generation',
|
|
318
|
+
},
|
|
319
|
+
|
|
320
|
+
// System Prompt (Text)
|
|
321
|
+
{
|
|
322
|
+
displayName: 'System Prompt',
|
|
323
|
+
name: 'systemPrompt',
|
|
324
|
+
type: 'string',
|
|
325
|
+
default: '',
|
|
326
|
+
displayOptions: {
|
|
327
|
+
show: {
|
|
328
|
+
operation: ['generateText'],
|
|
329
|
+
},
|
|
330
|
+
},
|
|
331
|
+
description: 'Instructions that define the AI behavior and context',
|
|
332
|
+
placeholder: 'You are a helpful assistant that responds concisely...',
|
|
333
|
+
typeOptions: {
|
|
334
|
+
rows: 3,
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
|
|
338
|
+
// Temperature (Text)
|
|
339
|
+
{
|
|
340
|
+
displayName: 'Temperature',
|
|
341
|
+
name: 'temperature',
|
|
342
|
+
type: 'number',
|
|
343
|
+
default: 0.7,
|
|
344
|
+
displayOptions: {
|
|
345
|
+
show: {
|
|
346
|
+
operation: ['generateText'],
|
|
347
|
+
},
|
|
348
|
+
},
|
|
349
|
+
description: 'Controls creativity: 0.0 = strict/deterministic, 2.0 = very creative',
|
|
350
|
+
typeOptions: {
|
|
351
|
+
minValue: 0,
|
|
352
|
+
maxValue: 2,
|
|
353
|
+
numberPrecision: 1,
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
|
|
357
|
+
// Advanced Options (Text)
|
|
358
|
+
{
|
|
359
|
+
displayName: 'Options',
|
|
360
|
+
name: 'textOptions',
|
|
361
|
+
type: 'collection',
|
|
362
|
+
placeholder: 'Add Option',
|
|
363
|
+
default: {},
|
|
364
|
+
displayOptions: {
|
|
365
|
+
show: {
|
|
366
|
+
operation: ['generateText'],
|
|
367
|
+
},
|
|
368
|
+
},
|
|
369
|
+
options: [
|
|
370
|
+
{
|
|
371
|
+
displayName: 'JSON Response',
|
|
372
|
+
name: 'jsonMode',
|
|
373
|
+
type: 'boolean',
|
|
374
|
+
default: false,
|
|
375
|
+
description:
|
|
376
|
+
'Whether to force the response in JSON format. Not supported by all models.',
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
displayName: 'Seed',
|
|
380
|
+
name: 'seed',
|
|
381
|
+
type: 'number',
|
|
382
|
+
default: -1,
|
|
383
|
+
description: 'Seed for reproducible results. Use -1 for random.',
|
|
384
|
+
},
|
|
385
|
+
],
|
|
386
|
+
},
|
|
180
387
|
],
|
|
181
388
|
};
|
|
182
389
|
|
|
390
|
+
methods = {
|
|
391
|
+
loadOptions: {
|
|
392
|
+
async getImageModels(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
|
393
|
+
try {
|
|
394
|
+
const credentials = await this.getCredentials('pollinationsApi');
|
|
395
|
+
const apiKey = credentials.apiKey as string;
|
|
396
|
+
|
|
397
|
+
const response = await this.helpers.httpRequest({
|
|
398
|
+
method: 'GET',
|
|
399
|
+
url: 'https://gen.pollinations.ai/image/models',
|
|
400
|
+
headers: {
|
|
401
|
+
Authorization: `Bearer ${apiKey}`,
|
|
402
|
+
},
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
if (Array.isArray(response)) {
|
|
406
|
+
// Filter only image models (exclude video models)
|
|
407
|
+
const imageModels = response.filter(
|
|
408
|
+
(model: { output_modalities?: string[] }) =>
|
|
409
|
+
model.output_modalities?.includes('image') &&
|
|
410
|
+
!model.output_modalities?.includes('video'),
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
return imageModels.map(
|
|
414
|
+
(model: {
|
|
415
|
+
name: string;
|
|
416
|
+
description: string;
|
|
417
|
+
pricing?: { completionImageTokens?: number };
|
|
418
|
+
}) => {
|
|
419
|
+
let displayName = model.description || model.name;
|
|
420
|
+
|
|
421
|
+
// Add pricing info if available
|
|
422
|
+
if (model.pricing?.completionImageTokens) {
|
|
423
|
+
const imagesPerPollen = Math.floor(1 / model.pricing.completionImageTokens);
|
|
424
|
+
displayName += ` (~${imagesPerPollen.toLocaleString()} img/$)`;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return {
|
|
428
|
+
name: displayName,
|
|
429
|
+
value: model.name,
|
|
430
|
+
};
|
|
431
|
+
},
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Fallback if API fails
|
|
436
|
+
return [
|
|
437
|
+
{ name: 'Flux Schnell', value: 'flux' },
|
|
438
|
+
{ name: 'SDXL Turbo', value: 'turbo' },
|
|
439
|
+
{ name: 'GPT Image 1 Mini', value: 'gptimage' },
|
|
440
|
+
{ name: 'FLUX.1 Kontext', value: 'kontext' },
|
|
441
|
+
{ name: 'Seedream 4.0', value: 'seedream' },
|
|
442
|
+
{ name: 'NanoBanana', value: 'nanobanana' },
|
|
443
|
+
{ name: 'NanoBanana Pro', value: 'nanobanana-pro' },
|
|
444
|
+
];
|
|
445
|
+
} catch {
|
|
446
|
+
// Fallback if API fails
|
|
447
|
+
return [
|
|
448
|
+
{ name: 'Flux Schnell', value: 'flux' },
|
|
449
|
+
{ name: 'SDXL Turbo', value: 'turbo' },
|
|
450
|
+
{ name: 'GPT Image 1 Mini', value: 'gptimage' },
|
|
451
|
+
{ name: 'FLUX.1 Kontext', value: 'kontext' },
|
|
452
|
+
{ name: 'Seedream 4.0', value: 'seedream' },
|
|
453
|
+
];
|
|
454
|
+
}
|
|
455
|
+
},
|
|
456
|
+
|
|
457
|
+
async getTextModels(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
|
458
|
+
try {
|
|
459
|
+
const credentials = await this.getCredentials('pollinationsApi');
|
|
460
|
+
const apiKey = credentials.apiKey as string;
|
|
461
|
+
|
|
462
|
+
const response = await this.helpers.httpRequest({
|
|
463
|
+
method: 'GET',
|
|
464
|
+
url: 'https://gen.pollinations.ai/text/models',
|
|
465
|
+
headers: {
|
|
466
|
+
Authorization: `Bearer ${apiKey}`,
|
|
467
|
+
},
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
if (Array.isArray(response)) {
|
|
471
|
+
// Filter only text models (exclude image/video models)
|
|
472
|
+
const textModels = response.filter(
|
|
473
|
+
(model: { output_modalities?: string[] }) =>
|
|
474
|
+
model.output_modalities?.includes('text') &&
|
|
475
|
+
!model.output_modalities?.includes('image') &&
|
|
476
|
+
!model.output_modalities?.includes('video'),
|
|
477
|
+
);
|
|
478
|
+
|
|
479
|
+
return textModels.map(
|
|
480
|
+
(model: {
|
|
481
|
+
name: string;
|
|
482
|
+
description: string;
|
|
483
|
+
pricing?: { completionTextTokens?: number };
|
|
484
|
+
}) => {
|
|
485
|
+
let displayName = model.description || model.name;
|
|
486
|
+
|
|
487
|
+
// Add pricing info if available (responses per pollen)
|
|
488
|
+
if (model.pricing?.completionTextTokens) {
|
|
489
|
+
const responsesPerPollen = Math.floor(1 / model.pricing.completionTextTokens);
|
|
490
|
+
displayName += ` (~${responsesPerPollen.toLocaleString()} resp/$)`;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
return {
|
|
494
|
+
name: displayName,
|
|
495
|
+
value: model.name,
|
|
496
|
+
};
|
|
497
|
+
},
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Fallback if API fails
|
|
502
|
+
return [
|
|
503
|
+
{ name: 'OpenAI GPT-4o Mini', value: 'openai' },
|
|
504
|
+
{ name: 'OpenAI GPT-4o Mini (Fast)', value: 'openai-fast' },
|
|
505
|
+
{ name: 'OpenAI GPT-4o (Large)', value: 'openai-large' },
|
|
506
|
+
{ name: 'Claude Sonnet 3.5', value: 'claude' },
|
|
507
|
+
{ name: 'Claude (Fast)', value: 'claude-fast' },
|
|
508
|
+
{ name: 'Claude (Large)', value: 'claude-large' },
|
|
509
|
+
{ name: 'Gemini', value: 'gemini' },
|
|
510
|
+
{ name: 'Gemini (Fast)', value: 'gemini-fast' },
|
|
511
|
+
{ name: 'Gemini (Large)', value: 'gemini-large' },
|
|
512
|
+
{ name: 'DeepSeek V3', value: 'deepseek' },
|
|
513
|
+
{ name: 'Mistral', value: 'mistral' },
|
|
514
|
+
{ name: 'Grok', value: 'grok' },
|
|
515
|
+
];
|
|
516
|
+
} catch {
|
|
517
|
+
// Fallback if API fails
|
|
518
|
+
return [
|
|
519
|
+
{ name: 'OpenAI GPT-4o Mini', value: 'openai' },
|
|
520
|
+
{ name: 'OpenAI GPT-4o Mini (Fast)', value: 'openai-fast' },
|
|
521
|
+
{ name: 'OpenAI GPT-4o (Large)', value: 'openai-large' },
|
|
522
|
+
{ name: 'Claude Sonnet 3.5', value: 'claude' },
|
|
523
|
+
{ name: 'Mistral', value: 'mistral' },
|
|
524
|
+
{ name: 'DeepSeek V3', value: 'deepseek' },
|
|
525
|
+
];
|
|
526
|
+
}
|
|
527
|
+
},
|
|
528
|
+
|
|
529
|
+
async getImageModelsWithReferenceSupport(
|
|
530
|
+
this: ILoadOptionsFunctions,
|
|
531
|
+
): Promise<INodePropertyOptions[]> {
|
|
532
|
+
try {
|
|
533
|
+
const credentials = await this.getCredentials('pollinationsApi');
|
|
534
|
+
const apiKey = credentials.apiKey as string;
|
|
535
|
+
|
|
536
|
+
const response = await this.helpers.httpRequest({
|
|
537
|
+
method: 'GET',
|
|
538
|
+
url: 'https://gen.pollinations.ai/image/models',
|
|
539
|
+
headers: {
|
|
540
|
+
Authorization: `Bearer ${apiKey}`,
|
|
541
|
+
},
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
if (Array.isArray(response)) {
|
|
545
|
+
// Filter only image models that support image input (for reference/transformation)
|
|
546
|
+
const imageModels = response.filter(
|
|
547
|
+
(model: { output_modalities?: string[]; input_modalities?: string[] }) =>
|
|
548
|
+
model.output_modalities?.includes('image') &&
|
|
549
|
+
!model.output_modalities?.includes('video') &&
|
|
550
|
+
model.input_modalities?.includes('image'),
|
|
551
|
+
);
|
|
552
|
+
|
|
553
|
+
return imageModels.map(
|
|
554
|
+
(model: {
|
|
555
|
+
name: string;
|
|
556
|
+
description: string;
|
|
557
|
+
pricing?: { completionImageTokens?: number };
|
|
558
|
+
}) => {
|
|
559
|
+
let displayName = model.description || model.name;
|
|
560
|
+
|
|
561
|
+
// Add pricing info if available
|
|
562
|
+
if (model.pricing?.completionImageTokens) {
|
|
563
|
+
const imagesPerPollen = Math.floor(1 / model.pricing.completionImageTokens);
|
|
564
|
+
displayName += ` (~${imagesPerPollen.toLocaleString()} img/$)`;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
return {
|
|
568
|
+
name: displayName,
|
|
569
|
+
value: model.name,
|
|
570
|
+
};
|
|
571
|
+
},
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// Fallback if API fails
|
|
576
|
+
return [
|
|
577
|
+
{ name: 'FLUX.1 Kontext', value: 'kontext' },
|
|
578
|
+
{ name: 'NanoBanana', value: 'nanobanana' },
|
|
579
|
+
{ name: 'NanoBanana Pro', value: 'nanobanana-pro' },
|
|
580
|
+
{ name: 'Seedream 4.0', value: 'seedream' },
|
|
581
|
+
{ name: 'GPT Image 1 Mini', value: 'gptimage' },
|
|
582
|
+
];
|
|
583
|
+
} catch {
|
|
584
|
+
// Fallback if API fails
|
|
585
|
+
return [
|
|
586
|
+
{ name: 'FLUX.1 Kontext', value: 'kontext' },
|
|
587
|
+
{ name: 'NanoBanana', value: 'nanobanana' },
|
|
588
|
+
{ name: 'NanoBanana Pro', value: 'nanobanana-pro' },
|
|
589
|
+
{ name: 'Seedream 4.0', value: 'seedream' },
|
|
590
|
+
{ name: 'GPT Image 1 Mini', value: 'gptimage' },
|
|
591
|
+
];
|
|
592
|
+
}
|
|
593
|
+
},
|
|
594
|
+
},
|
|
595
|
+
};
|
|
596
|
+
|
|
183
597
|
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
|
184
598
|
const items = this.getInputData();
|
|
185
599
|
const returnData: INodeExecutionData[] = [];
|
|
@@ -199,6 +613,10 @@ export class Pollinations implements INodeType {
|
|
|
199
613
|
safe?: boolean;
|
|
200
614
|
};
|
|
201
615
|
|
|
616
|
+
// Get credentials
|
|
617
|
+
const credentials = await this.getCredentials('pollinationsApi');
|
|
618
|
+
const apiKey = credentials.apiKey as string;
|
|
619
|
+
|
|
202
620
|
// Build query parameters
|
|
203
621
|
const queryParams: Record<string, string> = {
|
|
204
622
|
model,
|
|
@@ -224,7 +642,7 @@ export class Pollinations implements INodeType {
|
|
|
224
642
|
}
|
|
225
643
|
|
|
226
644
|
// Build the URL
|
|
227
|
-
const baseUrl = 'https://
|
|
645
|
+
const baseUrl = 'https://gen.pollinations.ai/image';
|
|
228
646
|
const encodedPrompt = encodeURIComponent(prompt);
|
|
229
647
|
const queryString = new URLSearchParams(queryParams).toString();
|
|
230
648
|
const fullUrl = `${baseUrl}/${encodedPrompt}?${queryString}`;
|
|
@@ -232,10 +650,217 @@ export class Pollinations implements INodeType {
|
|
|
232
650
|
// Record start time
|
|
233
651
|
const startTime = Date.now();
|
|
234
652
|
|
|
235
|
-
// Make the request
|
|
653
|
+
// Make the request with authentication
|
|
236
654
|
const response = await this.helpers.httpRequest({
|
|
237
655
|
method: 'GET',
|
|
238
656
|
url: fullUrl,
|
|
657
|
+
headers: {
|
|
658
|
+
Authorization: `Bearer ${apiKey}`,
|
|
659
|
+
},
|
|
660
|
+
encoding: 'arraybuffer',
|
|
661
|
+
returnFullResponse: true,
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
// Calculate duration
|
|
665
|
+
const duration = Date.now() - startTime;
|
|
666
|
+
|
|
667
|
+
// Prepare binary data
|
|
668
|
+
const binaryData = await this.helpers.prepareBinaryData(
|
|
669
|
+
Buffer.from(response.body as ArrayBuffer),
|
|
670
|
+
'image.png',
|
|
671
|
+
'image/png',
|
|
672
|
+
);
|
|
673
|
+
|
|
674
|
+
// Build metadata for debugging
|
|
675
|
+
const metadata = {
|
|
676
|
+
request: {
|
|
677
|
+
url: fullUrl,
|
|
678
|
+
prompt,
|
|
679
|
+
model,
|
|
680
|
+
width: options.width || 1024,
|
|
681
|
+
height: options.height || 1024,
|
|
682
|
+
seed: options.seed || null,
|
|
683
|
+
nologo: options.nologo || false,
|
|
684
|
+
enhance: options.enhance || false,
|
|
685
|
+
safe: options.safe || false,
|
|
686
|
+
},
|
|
687
|
+
response: {
|
|
688
|
+
statusCode: response.statusCode,
|
|
689
|
+
contentType: response.headers?.['content-type'] || 'image/png',
|
|
690
|
+
contentLength: response.headers?.['content-length'] || null,
|
|
691
|
+
duration: `${duration}ms`,
|
|
692
|
+
},
|
|
693
|
+
timestamp: new Date().toISOString(),
|
|
694
|
+
};
|
|
695
|
+
|
|
696
|
+
returnData.push({
|
|
697
|
+
json: metadata,
|
|
698
|
+
binary: {
|
|
699
|
+
data: binaryData,
|
|
700
|
+
},
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
if (operation === 'generateText') {
|
|
705
|
+
const prompt = this.getNodeParameter('textPrompt', i) as string;
|
|
706
|
+
const model = this.getNodeParameter('textModel', i) as string;
|
|
707
|
+
const systemPrompt = this.getNodeParameter('systemPrompt', i, '') as string;
|
|
708
|
+
const temperature = this.getNodeParameter('temperature', i) as number;
|
|
709
|
+
const textOptions = this.getNodeParameter('textOptions', i, {}) as {
|
|
710
|
+
jsonMode?: boolean;
|
|
711
|
+
seed?: number;
|
|
712
|
+
};
|
|
713
|
+
const jsonMode = textOptions.jsonMode || false;
|
|
714
|
+
|
|
715
|
+
// Get credentials
|
|
716
|
+
const credentials = await this.getCredentials('pollinationsApi');
|
|
717
|
+
const apiKey = credentials.apiKey as string;
|
|
718
|
+
|
|
719
|
+
// Build query parameters
|
|
720
|
+
const queryParams: Record<string, string> = {
|
|
721
|
+
model,
|
|
722
|
+
temperature: temperature.toString(),
|
|
723
|
+
};
|
|
724
|
+
|
|
725
|
+
if (systemPrompt) {
|
|
726
|
+
queryParams.system = systemPrompt;
|
|
727
|
+
}
|
|
728
|
+
if (textOptions.seed !== undefined && textOptions.seed !== -1) {
|
|
729
|
+
queryParams.seed = textOptions.seed.toString();
|
|
730
|
+
}
|
|
731
|
+
if (jsonMode) {
|
|
732
|
+
queryParams.json = 'true';
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// Build the URL
|
|
736
|
+
const baseUrl = 'https://gen.pollinations.ai/text';
|
|
737
|
+
const encodedPrompt = encodeURIComponent(prompt);
|
|
738
|
+
const queryString = new URLSearchParams(queryParams).toString();
|
|
739
|
+
const fullUrl = `${baseUrl}/${encodedPrompt}?${queryString}`;
|
|
740
|
+
|
|
741
|
+
// Record start time
|
|
742
|
+
const startTime = Date.now();
|
|
743
|
+
|
|
744
|
+
// Make the request with authentication
|
|
745
|
+
const response = await this.helpers.httpRequest({
|
|
746
|
+
method: 'GET',
|
|
747
|
+
url: fullUrl,
|
|
748
|
+
headers: {
|
|
749
|
+
Authorization: `Bearer ${apiKey}`,
|
|
750
|
+
},
|
|
751
|
+
returnFullResponse: true,
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
// Calculate duration
|
|
755
|
+
const duration = Date.now() - startTime;
|
|
756
|
+
|
|
757
|
+
// Parse response text
|
|
758
|
+
const text = response.body as string;
|
|
759
|
+
let parsedJson = null;
|
|
760
|
+
|
|
761
|
+
// If JSON mode, try to parse the response
|
|
762
|
+
if (jsonMode) {
|
|
763
|
+
try {
|
|
764
|
+
parsedJson = JSON.parse(text);
|
|
765
|
+
} catch {
|
|
766
|
+
// Keep as string if parsing fails
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// Build metadata for debugging
|
|
771
|
+
const metadata = {
|
|
772
|
+
text: parsedJson || text,
|
|
773
|
+
request: {
|
|
774
|
+
url: fullUrl,
|
|
775
|
+
prompt,
|
|
776
|
+
model,
|
|
777
|
+
system: systemPrompt || null,
|
|
778
|
+
temperature,
|
|
779
|
+
seed: textOptions.seed !== -1 ? textOptions.seed : null,
|
|
780
|
+
jsonMode: jsonMode,
|
|
781
|
+
},
|
|
782
|
+
response: {
|
|
783
|
+
statusCode: response.statusCode,
|
|
784
|
+
contentType: response.headers?.['content-type'] || 'text/plain',
|
|
785
|
+
duration: `${duration}ms`,
|
|
786
|
+
},
|
|
787
|
+
timestamp: new Date().toISOString(),
|
|
788
|
+
};
|
|
789
|
+
|
|
790
|
+
returnData.push({
|
|
791
|
+
json: metadata,
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
if (operation === 'generateImageWithReference') {
|
|
796
|
+
const prompt = this.getNodeParameter('referencePrompt', i) as string;
|
|
797
|
+
const referenceImage = this.getNodeParameter('referenceImage', i) as string;
|
|
798
|
+
const model = this.getNodeParameter('referenceModel', i) as string;
|
|
799
|
+
const options = this.getNodeParameter('referenceOptions', i, {}) as {
|
|
800
|
+
width?: number;
|
|
801
|
+
height?: number;
|
|
802
|
+
seed?: number;
|
|
803
|
+
nologo?: boolean;
|
|
804
|
+
enhance?: boolean;
|
|
805
|
+
safe?: boolean;
|
|
806
|
+
};
|
|
807
|
+
|
|
808
|
+
// Validate reference image URL
|
|
809
|
+
try {
|
|
810
|
+
new URL(referenceImage);
|
|
811
|
+
} catch {
|
|
812
|
+
throw new NodeOperationError(
|
|
813
|
+
this.getNode(),
|
|
814
|
+
`Invalid reference image URL: "${referenceImage}"`,
|
|
815
|
+
{ itemIndex: i },
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// Get credentials
|
|
820
|
+
const credentials = await this.getCredentials('pollinationsApi');
|
|
821
|
+
const apiKey = credentials.apiKey as string;
|
|
822
|
+
|
|
823
|
+
// Build query parameters
|
|
824
|
+
const queryParams: Record<string, string> = {
|
|
825
|
+
model,
|
|
826
|
+
image: referenceImage,
|
|
827
|
+
};
|
|
828
|
+
|
|
829
|
+
if (options.width) {
|
|
830
|
+
queryParams.width = options.width.toString();
|
|
831
|
+
}
|
|
832
|
+
if (options.height) {
|
|
833
|
+
queryParams.height = options.height.toString();
|
|
834
|
+
}
|
|
835
|
+
if (options.seed) {
|
|
836
|
+
queryParams.seed = options.seed.toString();
|
|
837
|
+
}
|
|
838
|
+
if (options.nologo) {
|
|
839
|
+
queryParams.nologo = 'true';
|
|
840
|
+
}
|
|
841
|
+
if (options.enhance) {
|
|
842
|
+
queryParams.enhance = 'true';
|
|
843
|
+
}
|
|
844
|
+
if (options.safe) {
|
|
845
|
+
queryParams.safe = 'true';
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// Build the URL
|
|
849
|
+
const baseUrl = 'https://gen.pollinations.ai/image';
|
|
850
|
+
const encodedPrompt = encodeURIComponent(prompt);
|
|
851
|
+
const queryString = new URLSearchParams(queryParams).toString();
|
|
852
|
+
const fullUrl = `${baseUrl}/${encodedPrompt}?${queryString}`;
|
|
853
|
+
|
|
854
|
+
// Record start time
|
|
855
|
+
const startTime = Date.now();
|
|
856
|
+
|
|
857
|
+
// Make the request with authentication
|
|
858
|
+
const response = await this.helpers.httpRequest({
|
|
859
|
+
method: 'GET',
|
|
860
|
+
url: fullUrl,
|
|
861
|
+
headers: {
|
|
862
|
+
Authorization: `Bearer ${apiKey}`,
|
|
863
|
+
},
|
|
239
864
|
encoding: 'arraybuffer',
|
|
240
865
|
returnFullResponse: true,
|
|
241
866
|
});
|
|
@@ -256,6 +881,7 @@ export class Pollinations implements INodeType {
|
|
|
256
881
|
url: fullUrl,
|
|
257
882
|
prompt,
|
|
258
883
|
model,
|
|
884
|
+
referenceImage,
|
|
259
885
|
width: options.width || 1024,
|
|
260
886
|
height: options.height || 1024,
|
|
261
887
|
seed: options.seed || null,
|