n8n-nodes-pollinations-ai 1.2.1 → 1.3.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.
@@ -1,912 +0,0 @@
1
- import {
2
- IExecuteFunctions,
3
- ILoadOptionsFunctions,
4
- INodeExecutionData,
5
- INodePropertyOptions,
6
- INodeType,
7
- INodeTypeDescription,
8
- NodeOperationError,
9
- } from 'n8n-workflow';
10
-
11
- export class Pollinations implements INodeType {
12
- description: INodeTypeDescription = {
13
- displayName: 'Pollinations',
14
- name: 'pollinations',
15
- icon: 'file:pollinations.svg',
16
- group: ['transform'],
17
- version: 1,
18
- subtitle: '={{$parameter["operation"]}}',
19
- description: 'Generate images and text using Pollinations AI',
20
- defaults: {
21
- name: 'Pollinations',
22
- },
23
- inputs: ['main'],
24
- outputs: ['main'],
25
- credentials: [
26
- {
27
- name: 'pollinationsApi',
28
- required: true,
29
- },
30
- ],
31
- properties: [
32
- // Operation
33
- {
34
- displayName: 'Operation',
35
- name: 'operation',
36
- type: 'options',
37
- noDataExpression: true,
38
- options: [
39
- {
40
- name: 'Generate Image',
41
- value: 'generateImage',
42
- description: 'Generate an image from a text prompt',
43
- action: 'Generate an image from a text prompt',
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
- },
57
- ],
58
- default: 'generateImage',
59
- },
60
-
61
- // ==================== GENERATE IMAGE ====================
62
-
63
- // Prompt (Image)
64
- {
65
- displayName: 'Prompt',
66
- name: 'prompt',
67
- type: 'string',
68
- default: '',
69
- required: true,
70
- displayOptions: {
71
- show: {
72
- operation: ['generateImage'],
73
- },
74
- },
75
- description: 'The text prompt to generate the image from',
76
- typeOptions: {
77
- rows: 4,
78
- },
79
- },
80
-
81
- // Model (Image) - Dynamic loading
82
- {
83
- displayName: 'Model',
84
- name: 'model',
85
- type: 'options',
86
- default: 'flux',
87
- displayOptions: {
88
- show: {
89
- operation: ['generateImage'],
90
- },
91
- },
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'],
108
- },
109
- },
110
- options: [
111
- {
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
- },
121
- },
122
- {
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
- },
132
- },
133
- {
134
- displayName: 'Seed',
135
- name: 'seed',
136
- type: 'number',
137
- default: 0,
138
- description: 'Seed for reproducible generation. Use 0 for random.',
139
- },
140
- {
141
- displayName: 'No Logo',
142
- name: 'nologo',
143
- type: 'boolean',
144
- default: false,
145
- description: 'Whether to remove the Pollinations watermark',
146
- },
147
- {
148
- displayName: 'Enhance Prompt',
149
- name: 'enhance',
150
- type: 'boolean',
151
- default: false,
152
- description: 'Whether to automatically enhance the prompt for better results',
153
- },
154
- {
155
- displayName: 'Safe Mode',
156
- name: 'safe',
157
- type: 'boolean',
158
- default: false,
159
- description: 'Whether to enable content safety filter',
160
- },
161
- ],
162
- },
163
-
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)
218
- {
219
- displayName: 'Options',
220
- name: 'referenceOptions',
221
- type: 'collection',
222
- placeholder: 'Add Option',
223
- default: {},
224
- displayOptions: {
225
- show: {
226
- operation: ['generateImageWithReference'],
227
- },
228
- },
229
- options: [
230
- {
231
- displayName: 'Width',
232
- name: 'width',
233
- type: 'number',
234
- default: 1024,
235
- description: 'Width of the generated image in pixels',
236
- typeOptions: {
237
- minValue: 64,
238
- maxValue: 2048,
239
- },
240
- },
241
- {
242
- displayName: 'Height',
243
- name: 'height',
244
- type: 'number',
245
- default: 1024,
246
- description: 'Height of the generated image in pixels',
247
- typeOptions: {
248
- minValue: 64,
249
- maxValue: 2048,
250
- },
251
- },
252
- {
253
- displayName: 'Seed',
254
- name: 'seed',
255
- type: 'number',
256
- default: 0,
257
- description: 'Seed for reproducible generation. Use 0 for random.',
258
- },
259
- {
260
- displayName: 'No Logo',
261
- name: 'nologo',
262
- type: 'boolean',
263
- default: false,
264
- description: 'Whether to remove the Pollinations watermark',
265
- },
266
- {
267
- displayName: 'Enhance Prompt',
268
- name: 'enhance',
269
- type: 'boolean',
270
- default: false,
271
- description: 'Whether to automatically enhance the prompt for better results',
272
- },
273
- {
274
- displayName: 'Safe Mode',
275
- name: 'safe',
276
- type: 'boolean',
277
- default: false,
278
- description: 'Whether to enable content safety filter',
279
- },
280
- ],
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
- },
387
- ],
388
- };
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
-
597
- async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
598
- const items = this.getInputData();
599
- const returnData: INodeExecutionData[] = [];
600
-
601
- for (let i = 0; i < items.length; i++) {
602
- const operation = this.getNodeParameter('operation', i) as string;
603
-
604
- if (operation === 'generateImage') {
605
- const prompt = this.getNodeParameter('prompt', i) as string;
606
- const model = this.getNodeParameter('model', i) as string;
607
- const options = this.getNodeParameter('options', i, {}) as {
608
- width?: number;
609
- height?: number;
610
- seed?: number;
611
- nologo?: boolean;
612
- enhance?: boolean;
613
- safe?: boolean;
614
- };
615
-
616
- // Get credentials
617
- const credentials = await this.getCredentials('pollinationsApi');
618
- const apiKey = credentials.apiKey as string;
619
-
620
- // Build query parameters
621
- const queryParams: Record<string, string> = {
622
- model,
623
- };
624
-
625
- if (options.width) {
626
- queryParams.width = options.width.toString();
627
- }
628
- if (options.height) {
629
- queryParams.height = options.height.toString();
630
- }
631
- if (options.seed) {
632
- queryParams.seed = options.seed.toString();
633
- }
634
- if (options.nologo) {
635
- queryParams.nologo = 'true';
636
- }
637
- if (options.enhance) {
638
- queryParams.enhance = 'true';
639
- }
640
- if (options.safe) {
641
- queryParams.safe = 'true';
642
- }
643
-
644
- // Build the URL
645
- const baseUrl = 'https://gen.pollinations.ai/image';
646
- const encodedPrompt = encodeURIComponent(prompt);
647
- const queryString = new URLSearchParams(queryParams).toString();
648
- const fullUrl = `${baseUrl}/${encodedPrompt}?${queryString}`;
649
-
650
- // Record start time
651
- const startTime = Date.now();
652
-
653
- // Make the request with authentication
654
- const response = await this.helpers.httpRequest({
655
- method: 'GET',
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
- },
864
- encoding: 'arraybuffer',
865
- returnFullResponse: true,
866
- });
867
-
868
- // Calculate duration
869
- const duration = Date.now() - startTime;
870
-
871
- // Prepare binary data
872
- const binaryData = await this.helpers.prepareBinaryData(
873
- Buffer.from(response.body as ArrayBuffer),
874
- 'image.png',
875
- 'image/png',
876
- );
877
-
878
- // Build metadata for debugging
879
- const metadata = {
880
- request: {
881
- url: fullUrl,
882
- prompt,
883
- model,
884
- referenceImage,
885
- width: options.width || 1024,
886
- height: options.height || 1024,
887
- seed: options.seed || null,
888
- nologo: options.nologo || false,
889
- enhance: options.enhance || false,
890
- safe: options.safe || false,
891
- },
892
- response: {
893
- statusCode: response.statusCode,
894
- contentType: response.headers?.['content-type'] || 'image/png',
895
- contentLength: response.headers?.['content-length'] || null,
896
- duration: `${duration}ms`,
897
- },
898
- timestamp: new Date().toISOString(),
899
- };
900
-
901
- returnData.push({
902
- json: metadata,
903
- binary: {
904
- data: binaryData,
905
- },
906
- });
907
- }
908
- }
909
-
910
- return [returnData];
911
- }
912
- }