n8n-nodes-apexapi 0.1.2 → 0.1.4

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.
@@ -117,12 +117,23 @@ class ApexApi {
117
117
  type: 'options',
118
118
  displayOptions: { show: { resource: ['image'] } },
119
119
  options: [
120
- { name: 'Square 1:1 (1024×1024)', value: '1024x1024' },
121
- { name: 'Landscape16:9 (1344×768)', value: '1344x768' },
122
- { name: 'Portrait 9:16 (768×1344)', value: '768x1344' },
120
+ { name: 'Square 1:1 1024×1024', value: '1024x1024' },
121
+ { name: 'Square 1:1 1080×1080', value: '1080x1080' },
122
+ { name: 'Portrait 9:16 — 1080×1920', value: '1080x1920' },
123
+ { name: 'Landscape 16:9 — 1920×1080', value: '1920x1080' },
124
+ { name: 'Custom (WIDTHxHEIGHT)…', value: 'custom' },
123
125
  ],
124
126
  default: '1024x1024',
125
- description: 'Output resolution / aspect ratio. Note: some models only support specific sizes.',
127
+ description: 'Output size / aspect ratio. Works on every model the API adapts it per provider (exact pixels for fal/flux, nearest aspect ratio for Imagen/Gemini/GPT-Image).',
128
+ },
129
+ {
130
+ displayName: 'Custom Size',
131
+ name: 'customSize',
132
+ type: 'string',
133
+ placeholder: '1024x1024',
134
+ displayOptions: { show: { resource: ['image'], imageSize: ['custom'] } },
135
+ default: '1024x1024',
136
+ description: 'Width×height in pixels, e.g. 1024x1024. Each side 1–4096.',
126
137
  },
127
138
  {
128
139
  displayName: 'Options',
@@ -227,15 +238,33 @@ class ApexApi {
227
238
  }
228
239
  else if (resource === 'image') {
229
240
  const options = this.getNodeParameter('options', i, {});
241
+ const sizeChoice = this.getNodeParameter('imageSize', i, '1024x1024');
242
+ const size = sizeChoice === 'custom'
243
+ ? this.getNodeParameter('customSize', i, '1024x1024')
244
+ : sizeChoice;
230
245
  const body = (0, image_1.buildImageBody)({
231
246
  model: this.getNodeParameter('model', i),
232
247
  prompt: this.getNodeParameter('prompt', i),
233
- size: this.getNodeParameter('imageSize', i, '1024x1024'),
248
+ size,
234
249
  n: options.n,
235
250
  });
236
251
  const resp = await (0, transport_1.apexApiRequest)(ctx, 'POST', '/images/generations', body);
237
252
  const parsed = (0, image_1.parseImageResponse)(resp);
238
253
  json = { urls: parsed.urls, response: parsed.raw };
254
+ // Some providers (Vertex/Imagen) return inline base64 instead of a hosted
255
+ // link. Emit those as n8n binary so the next node can post/upload them
256
+ // directly — hosted-URL providers (fal/kie) just come through in `urls`.
257
+ const b64Images = parsed.images.filter((im) => im.base64);
258
+ if (b64Images.length > 0) {
259
+ const binary = {};
260
+ for (let k = 0; k < b64Images.length; k++) {
261
+ const im = b64Images[k];
262
+ const ext = (im.mimeType ?? 'image/png').split('/')[1] ?? 'png';
263
+ binary[k === 0 ? 'data' : `data${k}`] = await this.helpers.prepareBinaryData(Buffer.from(im.base64, 'base64'), `image_${k}.${ext}`, im.mimeType);
264
+ }
265
+ returnData.push({ json, binary, pairedItem: { item: i } });
266
+ continue;
267
+ }
239
268
  }
240
269
  else {
241
270
  // video
@@ -10,11 +10,29 @@ function buildImageBody(params) {
10
10
  body.n = params.n;
11
11
  return body;
12
12
  }
13
+ const DATA_URL_RE = /^data:([^;,]+);base64,(.+)$/;
14
+ /**
15
+ * Normalise the `/v1/images/generations` response. Providers return images two
16
+ * ways: a hosted URL (fal/kie) or inline base64 (Vertex/Imagen `data:` URLs, or
17
+ * `b64_json`). `urls` holds only real links; `images` holds every image with the
18
+ * base64 decoded out of `data:` URLs so the node can emit it as n8n binary.
19
+ */
13
20
  function parseImageResponse(resp) {
14
21
  const r = resp;
15
22
  const data = Array.isArray(r?.data) ? r.data : [];
16
- const urls = data
17
- .map((d) => d?.url)
18
- .filter((u) => typeof u === 'string');
19
- return { urls, raw: resp };
23
+ const images = [];
24
+ for (const d of data) {
25
+ if (typeof d?.url === 'string') {
26
+ const m = d.url.match(DATA_URL_RE);
27
+ if (m)
28
+ images.push({ base64: m[2], mimeType: m[1] });
29
+ else
30
+ images.push({ url: d.url });
31
+ }
32
+ else if (typeof d?.b64_json === 'string') {
33
+ images.push({ base64: d.b64_json, mimeType: 'image/png' });
34
+ }
35
+ }
36
+ const urls = images.map((i) => i.url).filter((u) => typeof u === 'string');
37
+ return { urls, images, raw: resp };
20
38
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-apexapi",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "n8n community node for ApexApi — call 14 AI providers (OpenAI, Anthropic, Google, Mistral, and more) through one OpenAI-compatible API.",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",
@@ -46,6 +46,7 @@
46
46
  "devDependencies": {
47
47
  "n8n-workflow": "^1.82.0",
48
48
  "typescript": "^5.9.3",
49
- "vitest": "^3.2.4"
49
+ "vitest": "^3.2.4",
50
+ "@types/node": "^22.0.0"
50
51
  }
51
52
  }