n8n-nodes-pollinations-ai 1.1.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.
@@ -5,6 +5,7 @@ export declare class Pollinations implements INodeType {
5
5
  loadOptions: {
6
6
  getImageModels(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
7
7
  getTextModels(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
8
+ getImageModelsWithReferenceSupport(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
8
9
  };
9
10
  };
10
11
  execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Pollinations = void 0;
4
+ const n8n_workflow_1 = require("n8n-workflow");
4
5
  class Pollinations {
5
6
  constructor() {
6
7
  this.description = {
@@ -36,6 +37,12 @@ class Pollinations {
36
37
  description: 'Generate an image from a text prompt',
37
38
  action: 'Generate an image from a text prompt',
38
39
  },
40
+ {
41
+ name: 'Generate with Reference',
42
+ value: 'generateImageWithReference',
43
+ description: 'Generate an image using a reference image',
44
+ action: 'Generate an image using a reference image',
45
+ },
39
46
  {
40
47
  name: 'Generate Text',
41
48
  value: 'generateText',
@@ -144,6 +151,120 @@ class Pollinations {
144
151
  },
145
152
  ],
146
153
  },
154
+ // ==================== GENERATE IMAGE WITH REFERENCE ====================
155
+ // Prompt (Reference)
156
+ {
157
+ displayName: 'Prompt',
158
+ name: 'referencePrompt',
159
+ type: 'string',
160
+ default: '',
161
+ required: true,
162
+ displayOptions: {
163
+ show: {
164
+ operation: ['generateImageWithReference'],
165
+ },
166
+ },
167
+ description: 'The text prompt describing how to transform or use the reference image',
168
+ typeOptions: {
169
+ rows: 4,
170
+ },
171
+ },
172
+ // Reference Image URL (Required)
173
+ {
174
+ displayName: 'Reference Image URL',
175
+ name: 'referenceImage',
176
+ type: 'string',
177
+ default: '',
178
+ required: true,
179
+ displayOptions: {
180
+ show: {
181
+ operation: ['generateImageWithReference'],
182
+ },
183
+ },
184
+ placeholder: 'https://example.com/image.jpg',
185
+ description: 'URL of the reference image. Must be publicly accessible.',
186
+ },
187
+ // Model (Reference) - Dynamic loading with image input support
188
+ {
189
+ displayName: 'Model',
190
+ name: 'referenceModel',
191
+ type: 'options',
192
+ default: 'kontext',
193
+ displayOptions: {
194
+ show: {
195
+ operation: ['generateImageWithReference'],
196
+ },
197
+ },
198
+ typeOptions: {
199
+ loadOptionsMethod: 'getImageModelsWithReferenceSupport',
200
+ },
201
+ description: 'The model to use. Only models supporting image input are shown.',
202
+ },
203
+ // Advanced Options (Reference)
204
+ {
205
+ displayName: 'Options',
206
+ name: 'referenceOptions',
207
+ type: 'collection',
208
+ placeholder: 'Add Option',
209
+ default: {},
210
+ displayOptions: {
211
+ show: {
212
+ operation: ['generateImageWithReference'],
213
+ },
214
+ },
215
+ options: [
216
+ {
217
+ displayName: 'Width',
218
+ name: 'width',
219
+ type: 'number',
220
+ default: 1024,
221
+ description: 'Width of the generated image in pixels',
222
+ typeOptions: {
223
+ minValue: 64,
224
+ maxValue: 2048,
225
+ },
226
+ },
227
+ {
228
+ displayName: 'Height',
229
+ name: 'height',
230
+ type: 'number',
231
+ default: 1024,
232
+ description: 'Height of the generated image in pixels',
233
+ typeOptions: {
234
+ minValue: 64,
235
+ maxValue: 2048,
236
+ },
237
+ },
238
+ {
239
+ displayName: 'Seed',
240
+ name: 'seed',
241
+ type: 'number',
242
+ default: 0,
243
+ description: 'Seed for reproducible generation. Use 0 for random.',
244
+ },
245
+ {
246
+ displayName: 'No Logo',
247
+ name: 'nologo',
248
+ type: 'boolean',
249
+ default: false,
250
+ description: 'Whether to remove the Pollinations watermark',
251
+ },
252
+ {
253
+ displayName: 'Enhance Prompt',
254
+ name: 'enhance',
255
+ type: 'boolean',
256
+ default: false,
257
+ description: 'Whether to automatically enhance the prompt for better results',
258
+ },
259
+ {
260
+ displayName: 'Safe Mode',
261
+ name: 'safe',
262
+ type: 'boolean',
263
+ default: false,
264
+ description: 'Whether to enable content safety filter',
265
+ },
266
+ ],
267
+ },
147
268
  // ==================== GENERATE TEXT ====================
148
269
  // Prompt (Text)
149
270
  {
@@ -353,6 +474,55 @@ class Pollinations {
353
474
  ];
354
475
  }
355
476
  },
477
+ async getImageModelsWithReferenceSupport() {
478
+ try {
479
+ const credentials = await this.getCredentials('pollinationsApi');
480
+ const apiKey = credentials.apiKey;
481
+ const response = await this.helpers.httpRequest({
482
+ method: 'GET',
483
+ url: 'https://gen.pollinations.ai/image/models',
484
+ headers: {
485
+ Authorization: `Bearer ${apiKey}`,
486
+ },
487
+ });
488
+ if (Array.isArray(response)) {
489
+ // Filter only image models that support image input (for reference/transformation)
490
+ const imageModels = response.filter((model) => model.output_modalities?.includes('image') &&
491
+ !model.output_modalities?.includes('video') &&
492
+ model.input_modalities?.includes('image'));
493
+ return imageModels.map((model) => {
494
+ let displayName = model.description || model.name;
495
+ // Add pricing info if available
496
+ if (model.pricing?.completionImageTokens) {
497
+ const imagesPerPollen = Math.floor(1 / model.pricing.completionImageTokens);
498
+ displayName += ` (~${imagesPerPollen.toLocaleString()} img/$)`;
499
+ }
500
+ return {
501
+ name: displayName,
502
+ value: model.name,
503
+ };
504
+ });
505
+ }
506
+ // Fallback if API fails
507
+ return [
508
+ { name: 'FLUX.1 Kontext', value: 'kontext' },
509
+ { name: 'NanoBanana', value: 'nanobanana' },
510
+ { name: 'NanoBanana Pro', value: 'nanobanana-pro' },
511
+ { name: 'Seedream 4.0', value: 'seedream' },
512
+ { name: 'GPT Image 1 Mini', value: 'gptimage' },
513
+ ];
514
+ }
515
+ catch {
516
+ // Fallback if API fails
517
+ return [
518
+ { name: 'FLUX.1 Kontext', value: 'kontext' },
519
+ { name: 'NanoBanana', value: 'nanobanana' },
520
+ { name: 'NanoBanana Pro', value: 'nanobanana-pro' },
521
+ { name: 'Seedream 4.0', value: 'seedream' },
522
+ { name: 'GPT Image 1 Mini', value: 'gptimage' },
523
+ ];
524
+ }
525
+ },
356
526
  },
357
527
  };
358
528
  }
@@ -516,6 +686,94 @@ class Pollinations {
516
686
  json: metadata,
517
687
  });
518
688
  }
689
+ if (operation === 'generateImageWithReference') {
690
+ const prompt = this.getNodeParameter('referencePrompt', i);
691
+ const referenceImage = this.getNodeParameter('referenceImage', i);
692
+ const model = this.getNodeParameter('referenceModel', i);
693
+ const options = this.getNodeParameter('referenceOptions', i, {});
694
+ // Validate reference image URL
695
+ try {
696
+ new URL(referenceImage);
697
+ }
698
+ catch {
699
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Invalid reference image URL: "${referenceImage}"`, { itemIndex: i });
700
+ }
701
+ // Get credentials
702
+ const credentials = await this.getCredentials('pollinationsApi');
703
+ const apiKey = credentials.apiKey;
704
+ // Build query parameters
705
+ const queryParams = {
706
+ model,
707
+ image: referenceImage,
708
+ };
709
+ if (options.width) {
710
+ queryParams.width = options.width.toString();
711
+ }
712
+ if (options.height) {
713
+ queryParams.height = options.height.toString();
714
+ }
715
+ if (options.seed) {
716
+ queryParams.seed = options.seed.toString();
717
+ }
718
+ if (options.nologo) {
719
+ queryParams.nologo = 'true';
720
+ }
721
+ if (options.enhance) {
722
+ queryParams.enhance = 'true';
723
+ }
724
+ if (options.safe) {
725
+ queryParams.safe = 'true';
726
+ }
727
+ // Build the URL
728
+ const baseUrl = 'https://gen.pollinations.ai/image';
729
+ const encodedPrompt = encodeURIComponent(prompt);
730
+ const queryString = new URLSearchParams(queryParams).toString();
731
+ const fullUrl = `${baseUrl}/${encodedPrompt}?${queryString}`;
732
+ // Record start time
733
+ const startTime = Date.now();
734
+ // Make the request with authentication
735
+ const response = await this.helpers.httpRequest({
736
+ method: 'GET',
737
+ url: fullUrl,
738
+ headers: {
739
+ Authorization: `Bearer ${apiKey}`,
740
+ },
741
+ encoding: 'arraybuffer',
742
+ returnFullResponse: true,
743
+ });
744
+ // Calculate duration
745
+ const duration = Date.now() - startTime;
746
+ // Prepare binary data
747
+ const binaryData = await this.helpers.prepareBinaryData(Buffer.from(response.body), 'image.png', 'image/png');
748
+ // Build metadata for debugging
749
+ const metadata = {
750
+ request: {
751
+ url: fullUrl,
752
+ prompt,
753
+ model,
754
+ referenceImage,
755
+ width: options.width || 1024,
756
+ height: options.height || 1024,
757
+ seed: options.seed || null,
758
+ nologo: options.nologo || false,
759
+ enhance: options.enhance || false,
760
+ safe: options.safe || false,
761
+ },
762
+ response: {
763
+ statusCode: response.statusCode,
764
+ contentType: response.headers?.['content-type'] || 'image/png',
765
+ contentLength: response.headers?.['content-length'] || null,
766
+ duration: `${duration}ms`,
767
+ },
768
+ timestamp: new Date().toISOString(),
769
+ };
770
+ returnData.push({
771
+ json: metadata,
772
+ binary: {
773
+ data: binaryData,
774
+ },
775
+ });
776
+ }
519
777
  }
520
778
  return [returnData];
521
779
  }
@@ -5,6 +5,7 @@ import {
5
5
  INodePropertyOptions,
6
6
  INodeType,
7
7
  INodeTypeDescription,
8
+ NodeOperationError,
8
9
  } from 'n8n-workflow';
9
10
 
10
11
  export class Pollinations implements INodeType {
@@ -41,6 +42,12 @@ export class Pollinations implements INodeType {
41
42
  description: 'Generate an image from a text prompt',
42
43
  action: 'Generate an image from a text prompt',
43
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
+ },
44
51
  {
45
52
  name: 'Generate Text',
46
53
  value: 'generateText',
@@ -154,6 +161,125 @@ export class Pollinations implements INodeType {
154
161
  ],
155
162
  },
156
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
+
157
283
  // ==================== GENERATE TEXT ====================
158
284
 
159
285
  // Prompt (Text)
@@ -399,6 +525,72 @@ export class Pollinations implements INodeType {
399
525
  ];
400
526
  }
401
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
+ },
402
594
  },
403
595
  };
404
596
 
@@ -599,6 +791,120 @@ export class Pollinations implements INodeType {
599
791
  json: metadata,
600
792
  });
601
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
+ }
602
908
  }
603
909
 
604
910
  return [returnData];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-pollinations-ai",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "n8n community node for Pollinations AI - image generation and chat models",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",