n8n-nodes-pollinations-ai 1.0.0 → 1.1.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.
@@ -10,7 +10,7 @@ class Pollinations {
10
10
  group: ['transform'],
11
11
  version: 1,
12
12
  subtitle: '={{$parameter["operation"]}}',
13
- description: 'Generate images using Pollinations AI',
13
+ description: 'Generate images and text using Pollinations AI',
14
14
  defaults: {
15
15
  name: 'Pollinations',
16
16
  },
@@ -36,10 +36,17 @@ class Pollinations {
36
36
  description: 'Generate an image from a text prompt',
37
37
  action: 'Generate an image from a text prompt',
38
38
  },
39
+ {
40
+ name: 'Generate Text',
41
+ value: 'generateText',
42
+ description: 'Generate text from a prompt using AI',
43
+ action: 'Generate text from a prompt',
44
+ },
39
45
  ],
40
46
  default: 'generateImage',
41
47
  },
42
- // Prompt (Basic)
48
+ // ==================== GENERATE IMAGE ====================
49
+ // Prompt (Image)
43
50
  {
44
51
  displayName: 'Prompt',
45
52
  name: 'prompt',
@@ -56,7 +63,7 @@ class Pollinations {
56
63
  rows: 4,
57
64
  },
58
65
  },
59
- // Model (Basic)
66
+ // Model (Image) - Dynamic loading
60
67
  {
61
68
  displayName: 'Model',
62
69
  name: 'model',
@@ -67,46 +74,12 @@ class Pollinations {
67
74
  operation: ['generateImage'],
68
75
  },
69
76
  },
70
- options: [
71
- {
72
- name: 'Flux (Default)',
73
- value: 'flux',
74
- description: 'High quality image generation model',
75
- },
76
- {
77
- name: 'Turbo',
78
- value: 'turbo',
79
- description: 'Faster generation with good quality',
80
- },
81
- {
82
- name: 'GPT Image',
83
- value: 'gptimage',
84
- description: 'OpenAI DALL-E style generation',
85
- },
86
- {
87
- name: 'Kontext',
88
- value: 'kontext',
89
- description: 'Context-aware image generation (strict content filter)',
90
- },
91
- {
92
- name: 'Seedream',
93
- value: 'seedream',
94
- description: 'Dream-like artistic images',
95
- },
96
- {
97
- name: 'Nanobanana',
98
- value: 'nanobanana',
99
- description: 'Lightweight fast model',
100
- },
101
- {
102
- name: 'Nanobanana Pro',
103
- value: 'nanobanana-pro',
104
- description: 'Enhanced nanobanana model',
105
- },
106
- ],
77
+ typeOptions: {
78
+ loadOptionsMethod: 'getImageModels',
79
+ },
107
80
  description: 'The model to use for image generation',
108
81
  },
109
- // Advanced Options
82
+ // Advanced Options (Image)
110
83
  {
111
84
  displayName: 'Options',
112
85
  name: 'options',
@@ -171,8 +144,217 @@ class Pollinations {
171
144
  },
172
145
  ],
173
146
  },
147
+ // ==================== GENERATE TEXT ====================
148
+ // Prompt (Text)
149
+ {
150
+ displayName: 'Prompt',
151
+ name: 'textPrompt',
152
+ type: 'string',
153
+ default: '',
154
+ required: true,
155
+ displayOptions: {
156
+ show: {
157
+ operation: ['generateText'],
158
+ },
159
+ },
160
+ description: 'The text prompt or question for the AI model',
161
+ typeOptions: {
162
+ rows: 4,
163
+ },
164
+ },
165
+ // Model (Text) - Dynamic loading
166
+ {
167
+ displayName: 'Model',
168
+ name: 'textModel',
169
+ type: 'options',
170
+ default: 'openai',
171
+ displayOptions: {
172
+ show: {
173
+ operation: ['generateText'],
174
+ },
175
+ },
176
+ typeOptions: {
177
+ loadOptionsMethod: 'getTextModels',
178
+ },
179
+ description: 'The AI model to use for text generation',
180
+ },
181
+ // System Prompt (Text)
182
+ {
183
+ displayName: 'System Prompt',
184
+ name: 'systemPrompt',
185
+ type: 'string',
186
+ default: '',
187
+ displayOptions: {
188
+ show: {
189
+ operation: ['generateText'],
190
+ },
191
+ },
192
+ description: 'Instructions that define the AI behavior and context',
193
+ placeholder: 'You are a helpful assistant that responds concisely...',
194
+ typeOptions: {
195
+ rows: 3,
196
+ },
197
+ },
198
+ // Temperature (Text)
199
+ {
200
+ displayName: 'Temperature',
201
+ name: 'temperature',
202
+ type: 'number',
203
+ default: 0.7,
204
+ displayOptions: {
205
+ show: {
206
+ operation: ['generateText'],
207
+ },
208
+ },
209
+ description: 'Controls creativity: 0.0 = strict/deterministic, 2.0 = very creative',
210
+ typeOptions: {
211
+ minValue: 0,
212
+ maxValue: 2,
213
+ numberPrecision: 1,
214
+ },
215
+ },
216
+ // Advanced Options (Text)
217
+ {
218
+ displayName: 'Options',
219
+ name: 'textOptions',
220
+ type: 'collection',
221
+ placeholder: 'Add Option',
222
+ default: {},
223
+ displayOptions: {
224
+ show: {
225
+ operation: ['generateText'],
226
+ },
227
+ },
228
+ options: [
229
+ {
230
+ displayName: 'JSON Response',
231
+ name: 'jsonMode',
232
+ type: 'boolean',
233
+ default: false,
234
+ description: 'Whether to force the response in JSON format. Not supported by all models.',
235
+ },
236
+ {
237
+ displayName: 'Seed',
238
+ name: 'seed',
239
+ type: 'number',
240
+ default: -1,
241
+ description: 'Seed for reproducible results. Use -1 for random.',
242
+ },
243
+ ],
244
+ },
174
245
  ],
175
246
  };
247
+ this.methods = {
248
+ loadOptions: {
249
+ async getImageModels() {
250
+ try {
251
+ const credentials = await this.getCredentials('pollinationsApi');
252
+ const apiKey = credentials.apiKey;
253
+ const response = await this.helpers.httpRequest({
254
+ method: 'GET',
255
+ url: 'https://gen.pollinations.ai/image/models',
256
+ headers: {
257
+ Authorization: `Bearer ${apiKey}`,
258
+ },
259
+ });
260
+ if (Array.isArray(response)) {
261
+ // Filter only image models (exclude video models)
262
+ const imageModels = response.filter((model) => model.output_modalities?.includes('image') &&
263
+ !model.output_modalities?.includes('video'));
264
+ return imageModels.map((model) => {
265
+ let displayName = model.description || model.name;
266
+ // Add pricing info if available
267
+ if (model.pricing?.completionImageTokens) {
268
+ const imagesPerPollen = Math.floor(1 / model.pricing.completionImageTokens);
269
+ displayName += ` (~${imagesPerPollen.toLocaleString()} img/$)`;
270
+ }
271
+ return {
272
+ name: displayName,
273
+ value: model.name,
274
+ };
275
+ });
276
+ }
277
+ // Fallback if API fails
278
+ return [
279
+ { name: 'Flux Schnell', value: 'flux' },
280
+ { name: 'SDXL Turbo', value: 'turbo' },
281
+ { name: 'GPT Image 1 Mini', value: 'gptimage' },
282
+ { name: 'FLUX.1 Kontext', value: 'kontext' },
283
+ { name: 'Seedream 4.0', value: 'seedream' },
284
+ { name: 'NanoBanana', value: 'nanobanana' },
285
+ { name: 'NanoBanana Pro', value: 'nanobanana-pro' },
286
+ ];
287
+ }
288
+ catch {
289
+ // Fallback if API fails
290
+ return [
291
+ { name: 'Flux Schnell', value: 'flux' },
292
+ { name: 'SDXL Turbo', value: 'turbo' },
293
+ { name: 'GPT Image 1 Mini', value: 'gptimage' },
294
+ { name: 'FLUX.1 Kontext', value: 'kontext' },
295
+ { name: 'Seedream 4.0', value: 'seedream' },
296
+ ];
297
+ }
298
+ },
299
+ async getTextModels() {
300
+ try {
301
+ const credentials = await this.getCredentials('pollinationsApi');
302
+ const apiKey = credentials.apiKey;
303
+ const response = await this.helpers.httpRequest({
304
+ method: 'GET',
305
+ url: 'https://gen.pollinations.ai/text/models',
306
+ headers: {
307
+ Authorization: `Bearer ${apiKey}`,
308
+ },
309
+ });
310
+ if (Array.isArray(response)) {
311
+ // Filter only text models (exclude image/video models)
312
+ const textModels = response.filter((model) => model.output_modalities?.includes('text') &&
313
+ !model.output_modalities?.includes('image') &&
314
+ !model.output_modalities?.includes('video'));
315
+ return textModels.map((model) => {
316
+ let displayName = model.description || model.name;
317
+ // Add pricing info if available (responses per pollen)
318
+ if (model.pricing?.completionTextTokens) {
319
+ const responsesPerPollen = Math.floor(1 / model.pricing.completionTextTokens);
320
+ displayName += ` (~${responsesPerPollen.toLocaleString()} resp/$)`;
321
+ }
322
+ return {
323
+ name: displayName,
324
+ value: model.name,
325
+ };
326
+ });
327
+ }
328
+ // Fallback if API fails
329
+ return [
330
+ { name: 'OpenAI GPT-4o Mini', value: 'openai' },
331
+ { name: 'OpenAI GPT-4o Mini (Fast)', value: 'openai-fast' },
332
+ { name: 'OpenAI GPT-4o (Large)', value: 'openai-large' },
333
+ { name: 'Claude Sonnet 3.5', value: 'claude' },
334
+ { name: 'Claude (Fast)', value: 'claude-fast' },
335
+ { name: 'Claude (Large)', value: 'claude-large' },
336
+ { name: 'Gemini', value: 'gemini' },
337
+ { name: 'Gemini (Fast)', value: 'gemini-fast' },
338
+ { name: 'Gemini (Large)', value: 'gemini-large' },
339
+ { name: 'DeepSeek V3', value: 'deepseek' },
340
+ { name: 'Mistral', value: 'mistral' },
341
+ { name: 'Grok', value: 'grok' },
342
+ ];
343
+ }
344
+ catch {
345
+ // Fallback if API fails
346
+ return [
347
+ { name: 'OpenAI GPT-4o Mini', value: 'openai' },
348
+ { name: 'OpenAI GPT-4o Mini (Fast)', value: 'openai-fast' },
349
+ { name: 'OpenAI GPT-4o (Large)', value: 'openai-large' },
350
+ { name: 'Claude Sonnet 3.5', value: 'claude' },
351
+ { name: 'Mistral', value: 'mistral' },
352
+ { name: 'DeepSeek V3', value: 'deepseek' },
353
+ ];
354
+ }
355
+ },
356
+ },
357
+ };
176
358
  }
177
359
  async execute() {
178
360
  const items = this.getInputData();
@@ -183,6 +365,9 @@ class Pollinations {
183
365
  const prompt = this.getNodeParameter('prompt', i);
184
366
  const model = this.getNodeParameter('model', i);
185
367
  const options = this.getNodeParameter('options', i, {});
368
+ // Get credentials
369
+ const credentials = await this.getCredentials('pollinationsApi');
370
+ const apiKey = credentials.apiKey;
186
371
  // Build query parameters
187
372
  const queryParams = {
188
373
  model,
@@ -206,16 +391,19 @@ class Pollinations {
206
391
  queryParams.safe = 'true';
207
392
  }
208
393
  // Build the URL
209
- const baseUrl = 'https://image.pollinations.ai/prompt';
394
+ const baseUrl = 'https://gen.pollinations.ai/image';
210
395
  const encodedPrompt = encodeURIComponent(prompt);
211
396
  const queryString = new URLSearchParams(queryParams).toString();
212
397
  const fullUrl = `${baseUrl}/${encodedPrompt}?${queryString}`;
213
398
  // Record start time
214
399
  const startTime = Date.now();
215
- // Make the request
400
+ // Make the request with authentication
216
401
  const response = await this.helpers.httpRequest({
217
402
  method: 'GET',
218
403
  url: fullUrl,
404
+ headers: {
405
+ Authorization: `Bearer ${apiKey}`,
406
+ },
219
407
  encoding: 'arraybuffer',
220
408
  returnFullResponse: true,
221
409
  });
@@ -251,6 +439,83 @@ class Pollinations {
251
439
  },
252
440
  });
253
441
  }
442
+ if (operation === 'generateText') {
443
+ const prompt = this.getNodeParameter('textPrompt', i);
444
+ const model = this.getNodeParameter('textModel', i);
445
+ const systemPrompt = this.getNodeParameter('systemPrompt', i, '');
446
+ const temperature = this.getNodeParameter('temperature', i);
447
+ const textOptions = this.getNodeParameter('textOptions', i, {});
448
+ const jsonMode = textOptions.jsonMode || false;
449
+ // Get credentials
450
+ const credentials = await this.getCredentials('pollinationsApi');
451
+ const apiKey = credentials.apiKey;
452
+ // Build query parameters
453
+ const queryParams = {
454
+ model,
455
+ temperature: temperature.toString(),
456
+ };
457
+ if (systemPrompt) {
458
+ queryParams.system = systemPrompt;
459
+ }
460
+ if (textOptions.seed !== undefined && textOptions.seed !== -1) {
461
+ queryParams.seed = textOptions.seed.toString();
462
+ }
463
+ if (jsonMode) {
464
+ queryParams.json = 'true';
465
+ }
466
+ // Build the URL
467
+ const baseUrl = 'https://gen.pollinations.ai/text';
468
+ const encodedPrompt = encodeURIComponent(prompt);
469
+ const queryString = new URLSearchParams(queryParams).toString();
470
+ const fullUrl = `${baseUrl}/${encodedPrompt}?${queryString}`;
471
+ // Record start time
472
+ const startTime = Date.now();
473
+ // Make the request with authentication
474
+ const response = await this.helpers.httpRequest({
475
+ method: 'GET',
476
+ url: fullUrl,
477
+ headers: {
478
+ Authorization: `Bearer ${apiKey}`,
479
+ },
480
+ returnFullResponse: true,
481
+ });
482
+ // Calculate duration
483
+ const duration = Date.now() - startTime;
484
+ // Parse response text
485
+ const text = response.body;
486
+ let parsedJson = null;
487
+ // If JSON mode, try to parse the response
488
+ if (jsonMode) {
489
+ try {
490
+ parsedJson = JSON.parse(text);
491
+ }
492
+ catch {
493
+ // Keep as string if parsing fails
494
+ }
495
+ }
496
+ // Build metadata for debugging
497
+ const metadata = {
498
+ text: parsedJson || text,
499
+ request: {
500
+ url: fullUrl,
501
+ prompt,
502
+ model,
503
+ system: systemPrompt || null,
504
+ temperature,
505
+ seed: textOptions.seed !== -1 ? textOptions.seed : null,
506
+ jsonMode: jsonMode,
507
+ },
508
+ response: {
509
+ statusCode: response.statusCode,
510
+ contentType: response.headers?.['content-type'] || 'text/plain',
511
+ duration: `${duration}ms`,
512
+ },
513
+ timestamp: new Date().toISOString(),
514
+ };
515
+ returnData.push({
516
+ json: metadata,
517
+ });
518
+ }
254
519
  }
255
520
  return [returnData];
256
521
  }
@@ -0,0 +1,10 @@
1
+ import type { ILoadOptionsFunctions, INodePropertyOptions, INodeType, INodeTypeDescription, ISupplyDataFunctions, SupplyData } from 'n8n-workflow';
2
+ export declare class PollinationsChatModel implements INodeType {
3
+ description: INodeTypeDescription;
4
+ methods: {
5
+ loadOptions: {
6
+ getChatModels(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
7
+ };
8
+ };
9
+ supplyData(this: ISupplyDataFunctions): Promise<SupplyData>;
10
+ }
@@ -0,0 +1,219 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PollinationsChatModel = void 0;
4
+ const openai_1 = require("@langchain/openai");
5
+ class PollinationsChatModel {
6
+ constructor() {
7
+ this.description = {
8
+ displayName: 'Pollinations Chat Model',
9
+ name: 'pollinationsChatModel',
10
+ icon: 'file:pollinations.svg',
11
+ group: ['transform'],
12
+ version: 1,
13
+ description: 'Use Pollinations AI chat models with AI Agents and LLM Chains',
14
+ defaults: {
15
+ name: 'Pollinations Chat Model',
16
+ },
17
+ codex: {
18
+ categories: ['AI'],
19
+ subcategories: {
20
+ AI: ['Language Models', 'Chat Models'],
21
+ },
22
+ resources: {
23
+ primaryDocumentation: [
24
+ {
25
+ url: 'https://enter.pollinations.ai/api/docs',
26
+ },
27
+ ],
28
+ },
29
+ },
30
+ // Sub-node: no main inputs, output is ai_languageModel
31
+ inputs: [],
32
+ outputs: ['ai_languageModel'],
33
+ outputNames: ['Model'],
34
+ credentials: [
35
+ {
36
+ name: 'pollinationsApi',
37
+ required: true,
38
+ },
39
+ ],
40
+ properties: [
41
+ // Model - dynamic loading
42
+ {
43
+ displayName: 'Model',
44
+ name: 'model',
45
+ type: 'options',
46
+ default: 'openai',
47
+ typeOptions: {
48
+ loadOptionsMethod: 'getChatModels',
49
+ },
50
+ description: 'The model to use for chat completions',
51
+ },
52
+ // Temperature
53
+ {
54
+ displayName: 'Temperature',
55
+ name: 'temperature',
56
+ type: 'number',
57
+ default: 1,
58
+ typeOptions: {
59
+ minValue: 0,
60
+ maxValue: 2,
61
+ numberPrecision: 1,
62
+ },
63
+ description: 'Controls randomness: 0 = deterministic, 2 = very creative',
64
+ },
65
+ // Options collection
66
+ {
67
+ displayName: 'Options',
68
+ name: 'options',
69
+ type: 'collection',
70
+ placeholder: 'Add Option',
71
+ default: {},
72
+ options: [
73
+ {
74
+ displayName: 'Max Tokens',
75
+ name: 'maxTokens',
76
+ type: 'number',
77
+ default: 0,
78
+ description: 'Maximum tokens in response. 0 uses model default.',
79
+ typeOptions: {
80
+ minValue: 0,
81
+ },
82
+ },
83
+ {
84
+ displayName: 'Top P',
85
+ name: 'topP',
86
+ type: 'number',
87
+ default: 1,
88
+ typeOptions: {
89
+ minValue: 0,
90
+ maxValue: 1,
91
+ numberPrecision: 2,
92
+ },
93
+ description: 'Nucleus sampling: consider tokens with top_p probability mass',
94
+ },
95
+ {
96
+ displayName: 'Frequency Penalty',
97
+ name: 'frequencyPenalty',
98
+ type: 'number',
99
+ default: 0,
100
+ typeOptions: {
101
+ minValue: -2,
102
+ maxValue: 2,
103
+ numberPrecision: 1,
104
+ },
105
+ description: 'Reduce repetition of token sequences. Higher values decrease repetition.',
106
+ },
107
+ {
108
+ displayName: 'Presence Penalty',
109
+ name: 'presencePenalty',
110
+ type: 'number',
111
+ default: 0,
112
+ typeOptions: {
113
+ minValue: -2,
114
+ maxValue: 2,
115
+ numberPrecision: 1,
116
+ },
117
+ description: 'Increase likelihood of new topics. Higher values encourage novelty.',
118
+ },
119
+ {
120
+ displayName: 'Timeout',
121
+ name: 'timeout',
122
+ type: 'number',
123
+ default: 60000,
124
+ typeOptions: {
125
+ minValue: 1000,
126
+ },
127
+ description: 'Request timeout in milliseconds',
128
+ },
129
+ ],
130
+ },
131
+ ],
132
+ };
133
+ this.methods = {
134
+ loadOptions: {
135
+ async getChatModels() {
136
+ try {
137
+ const credentials = await this.getCredentials('pollinationsApi');
138
+ const apiKey = credentials.apiKey;
139
+ const response = await this.helpers.httpRequest({
140
+ method: 'GET',
141
+ url: 'https://gen.pollinations.ai/text/models',
142
+ headers: {
143
+ Authorization: `Bearer ${apiKey}`,
144
+ },
145
+ });
146
+ if (Array.isArray(response)) {
147
+ // Filter only text models (exclude image/video models)
148
+ const textModels = response.filter((model) => model.output_modalities?.includes('text') &&
149
+ !model.output_modalities?.includes('image') &&
150
+ !model.output_modalities?.includes('video'));
151
+ return textModels.map((model) => {
152
+ let displayName = model.description || model.name;
153
+ // Add pricing info if available (responses per pollen)
154
+ if (model.pricing?.completionTextTokens) {
155
+ const responsesPerPollen = Math.floor(1 / model.pricing.completionTextTokens);
156
+ displayName += ` (~${responsesPerPollen.toLocaleString()} resp/$)`;
157
+ }
158
+ return {
159
+ name: displayName,
160
+ value: model.name,
161
+ };
162
+ });
163
+ }
164
+ // Fallback if API fails
165
+ return [
166
+ { name: 'OpenAI GPT-4o Mini', value: 'openai' },
167
+ { name: 'OpenAI GPT-4o Mini (Fast)', value: 'openai-fast' },
168
+ { name: 'OpenAI GPT-4o (Large)', value: 'openai-large' },
169
+ { name: 'Claude Sonnet 3.5', value: 'claude' },
170
+ { name: 'Claude (Fast)', value: 'claude-fast' },
171
+ { name: 'Claude (Large)', value: 'claude-large' },
172
+ { name: 'Gemini', value: 'gemini' },
173
+ { name: 'Gemini (Fast)', value: 'gemini-fast' },
174
+ { name: 'Gemini (Large)', value: 'gemini-large' },
175
+ { name: 'DeepSeek V3', value: 'deepseek' },
176
+ { name: 'Mistral', value: 'mistral' },
177
+ { name: 'Grok', value: 'grok' },
178
+ ];
179
+ }
180
+ catch {
181
+ // Fallback if API fails
182
+ return [
183
+ { name: 'OpenAI GPT-4o Mini', value: 'openai' },
184
+ { name: 'OpenAI GPT-4o Mini (Fast)', value: 'openai-fast' },
185
+ { name: 'OpenAI GPT-4o (Large)', value: 'openai-large' },
186
+ { name: 'Claude Sonnet 3.5', value: 'claude' },
187
+ { name: 'Mistral', value: 'mistral' },
188
+ { name: 'DeepSeek V3', value: 'deepseek' },
189
+ ];
190
+ }
191
+ },
192
+ },
193
+ };
194
+ }
195
+ async supplyData() {
196
+ const credentials = await this.getCredentials('pollinationsApi');
197
+ const apiKey = credentials.apiKey;
198
+ const model = this.getNodeParameter('model', 0);
199
+ const temperature = this.getNodeParameter('temperature', 0);
200
+ const options = this.getNodeParameter('options', 0, {});
201
+ const chatModel = new openai_1.ChatOpenAI({
202
+ model,
203
+ temperature,
204
+ maxTokens: options.maxTokens || undefined,
205
+ topP: options.topP,
206
+ frequencyPenalty: options.frequencyPenalty,
207
+ presencePenalty: options.presencePenalty,
208
+ timeout: options.timeout,
209
+ configuration: {
210
+ baseURL: 'https://gen.pollinations.ai/v1',
211
+ },
212
+ apiKey,
213
+ });
214
+ return {
215
+ response: chatModel,
216
+ };
217
+ }
218
+ }
219
+ exports.PollinationsChatModel = PollinationsChatModel;