n8n-nodes-apexapi 0.1.1 → 0.1.3

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.
@@ -111,6 +111,34 @@ class ApexApi {
111
111
  default: '',
112
112
  required: true,
113
113
  },
114
+ {
115
+ displayName: 'Size',
116
+ name: 'imageSize',
117
+ type: 'options',
118
+ displayOptions: { show: { resource: ['image'] } },
119
+ options: [
120
+ { name: 'Square 1:1 — 1024×1024', value: '1024x1024' },
121
+ { name: 'Square 1:1 — 1080×1080 (Instagram post)', value: '1080x1080' },
122
+ { name: 'Portrait 9:16 — 1080×1920 (Story / Reel / TikTok)', value: '1080x1920' },
123
+ { name: 'Landscape 16:9 — 1920×1080 (YouTube / X)', value: '1920x1080' },
124
+ { name: 'Landscape 1.91:1 — 1200×630 (Facebook / link share)', value: '1200x630' },
125
+ { name: 'Portrait 4:5 — 1080×1350 (Instagram portrait)', value: '1080x1350' },
126
+ { name: 'Portrait 2:3 — 1024×1536', value: '1024x1536' },
127
+ { name: 'Landscape 3:2 — 1536×1024', value: '1536x1024' },
128
+ { name: 'Custom (WIDTHxHEIGHT)…', value: 'custom' },
129
+ ],
130
+ default: '1024x1024',
131
+ description: 'Output resolution / aspect ratio. Note: some models only support specific sizes. Max 4096 per side.',
132
+ },
133
+ {
134
+ displayName: 'Custom Size',
135
+ name: 'customSize',
136
+ type: 'string',
137
+ placeholder: '1024x1024',
138
+ displayOptions: { show: { resource: ['image'], imageSize: ['custom'] } },
139
+ default: '1024x1024',
140
+ description: 'Width×height in pixels, e.g. 1024x1024. Each side 1–4096.',
141
+ },
114
142
  {
115
143
  displayName: 'Options',
116
144
  name: 'options',
@@ -118,10 +146,7 @@ class ApexApi {
118
146
  placeholder: 'Add Option',
119
147
  displayOptions: { show: { resource: ['image'] } },
120
148
  default: {},
121
- options: [
122
- { displayName: 'Size', name: 'size', type: 'string', default: '1024x1024' },
123
- { displayName: 'Number of Images', name: 'n', type: 'number', default: 1 },
124
- ],
149
+ options: [{ displayName: 'Number of Images', name: 'n', type: 'number', default: 1 }],
125
150
  },
126
151
  // ---- Video ----
127
152
  {
@@ -182,7 +207,7 @@ class ApexApi {
182
207
  this.methods = {
183
208
  loadOptions: {
184
209
  async getChatModels() {
185
- return (0, loadOptions_1.loadModels)(this, 'text');
210
+ return (0, loadOptions_1.loadModels)(this, 'chat');
186
211
  },
187
212
  async getImageModels() {
188
213
  return (0, loadOptions_1.loadModels)(this, 'image');
@@ -217,15 +242,33 @@ class ApexApi {
217
242
  }
218
243
  else if (resource === 'image') {
219
244
  const options = this.getNodeParameter('options', i, {});
245
+ const sizeChoice = this.getNodeParameter('imageSize', i, '1024x1024');
246
+ const size = sizeChoice === 'custom'
247
+ ? this.getNodeParameter('customSize', i, '1024x1024')
248
+ : sizeChoice;
220
249
  const body = (0, image_1.buildImageBody)({
221
250
  model: this.getNodeParameter('model', i),
222
251
  prompt: this.getNodeParameter('prompt', i),
223
- size: options.size,
252
+ size,
224
253
  n: options.n,
225
254
  });
226
255
  const resp = await (0, transport_1.apexApiRequest)(ctx, 'POST', '/images/generations', body);
227
256
  const parsed = (0, image_1.parseImageResponse)(resp);
228
257
  json = { urls: parsed.urls, response: parsed.raw };
258
+ // Some providers (Vertex/Imagen) return inline base64 instead of a hosted
259
+ // link. Emit those as n8n binary so the next node can post/upload them
260
+ // directly — hosted-URL providers (fal/kie) just come through in `urls`.
261
+ const b64Images = parsed.images.filter((im) => im.base64);
262
+ if (b64Images.length > 0) {
263
+ const binary = {};
264
+ for (let k = 0; k < b64Images.length; k++) {
265
+ const im = b64Images[k];
266
+ const ext = (im.mimeType ?? 'image/png').split('/')[1] ?? 'png';
267
+ binary[k === 0 ? 'data' : `data${k}`] = await this.helpers.prepareBinaryData(Buffer.from(im.base64, 'base64'), `image_${k}.${ext}`, im.mimeType);
268
+ }
269
+ returnData.push({ json, binary, pairedItem: { item: i } });
270
+ continue;
271
+ }
229
272
  }
230
273
  else {
231
274
  // 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
  }
@@ -1,10 +1,31 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.loadModels = loadModels;
4
- const transport_1 = require("./transport");
4
+ const constants_1 = require("./constants");
5
5
  const models_1 = require("./models");
6
+ /**
7
+ * Load the model dropdown options for a resource. The `/v1/models` endpoint is
8
+ * PUBLIC, so this uses the unauthenticated request helper and works even before
9
+ * a credential is attached — that avoids n8n's "error fetching options" when the
10
+ * key hasn't been entered yet. The base URL comes from the credential when
11
+ * present, otherwise the default.
12
+ */
6
13
  async function loadModels(ctx, modelType) {
7
- const resp = await (0, transport_1.apexApiRequest)(ctx, 'GET', '/models');
14
+ let baseUrl = constants_1.DEFAULT_BASE_URL;
15
+ try {
16
+ const creds = await ctx.getCredentials(constants_1.CREDENTIAL_NAME);
17
+ if (creds && creds.baseUrl)
18
+ baseUrl = String(creds.baseUrl);
19
+ }
20
+ catch {
21
+ // No credential attached yet — /models is public, so fall back to the
22
+ // default base URL and load the list anyway.
23
+ }
24
+ baseUrl = baseUrl.replace(/\/+$/, '');
25
+ const http = ctx.helpers.httpRequest;
26
+ if (!http)
27
+ return [];
28
+ const resp = (await http({ method: 'GET', url: `${baseUrl}/models`, json: true }));
8
29
  const data = Array.isArray(resp?.data) ? resp.data : [];
9
30
  return (0, models_1.mapModelsToOptions)(data, modelType);
10
31
  }
@@ -3,8 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.mapModelsToOptions = mapModelsToOptions;
4
4
  /**
5
5
  * Map the `/v1/models` `data` array to n8n dropdown options. When `modelType`
6
- * is given, keep only models of that type (text/image/video); if that leaves
7
- * nothing, fall back to all models rather than showing an empty dropdown.
6
+ * is given, keep only models of that type (chat/image/video matching the live
7
+ * /v1/models `type` field); if that leaves nothing, fall back to all models
8
+ * rather than showing an empty dropdown.
8
9
  */
9
10
  function mapModelsToOptions(models, modelType) {
10
11
  const filtered = modelType ? models.filter((m) => m.type === modelType) : models;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-apexapi",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
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
  }