node-red-contrib-vectorprime 0.1.3 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-vectorprime",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "VectorPrime Decision Kernel node for Node-RED",
5
5
  "main": "vectorprime.js",
6
6
  "license": "MIT",
package/vectorprime.html CHANGED
@@ -28,87 +28,86 @@
28
28
  </script>
29
29
 
30
30
  <script type="text/javascript">
31
- (function () {
32
- function normalizeBaseUrl(url) {
33
- url = (url || "").trim();
34
- if (!url) return "";
35
- // Remove trailing slash
36
- if (url.endsWith("/")) url = url.slice(0, -1);
37
- return url;
38
- }
39
-
40
- RED.nodes.registerType("vectorprime-config", {
41
- category: "config",
42
- defaults: {
43
- baseUrl: { value: "https://vectorprime-kernel-backend.onrender.com", required: true }
44
- },
45
- credentials: {
46
- apiKey: { type: "password" }
47
- },
48
- label: function () {
49
- return "VectorPrime Config";
50
- },
51
- oneditprepare: function () {
52
- const statusEl = document.getElementById("vp-key-status");
53
- const btn = document.getElementById("vp-get-free-key");
54
-
55
- btn.onclick = async function (e) {
56
- e.preventDefault();
57
- statusEl.textContent = "Requesting free key...";
58
- statusEl.style.color = "#444";
59
-
60
- try {
61
- const baseUrlInput = document.getElementById("node-config-input-baseUrl");
62
- const apiKeyInput = document.getElementById("node-config-input-apiKey");
63
-
64
- const baseUrl = normalizeBaseUrl(baseUrlInput.value);
65
-
66
- if (!baseUrl.startsWith("http")) {
67
- statusEl.textContent = "❌ Base URL missing/invalid";
68
- statusEl.style.color = "red";
69
- return;
70
- }
71
-
72
- // Call backend directly from the editor
73
- const resp = await fetch(`${baseUrl}/v1/keys/free`, {
74
- method: "POST",
75
- headers: { "Content-Type": "application/json" },
76
- body: JSON.stringify({ source: "node-red" })
77
- });
78
-
79
- const text = await resp.text();
80
- let data;
81
- try { data = JSON.parse(text); } catch { data = { raw: text }; }
82
-
83
- if (!resp.ok) {
84
- statusEl.textContent = `❌ Failed (${resp.status}): ${data.message || data.error || "unknown"}`;
85
- statusEl.style.color = "red";
86
- return;
87
- }
88
-
89
- if (!data.api_key) {
90
- statusEl.textContent = "❌ Backend did not return api_key";
91
- statusEl.style.color = "red";
92
- return;
93
- }
94
-
95
- apiKeyInput.value = data.api_key;
96
- statusEl.textContent = "✅ Free key generated + saved";
97
- statusEl.style.color = "green";
98
- } catch (err) {
99
- statusEl.textContent = `❌ Error: ${err.message}`;
100
- statusEl.style.color = "red";
101
- }
102
- };
31
+ (function () {
32
+ function normalizeBaseUrl(url) {
33
+ url = (url || "").trim();
34
+ if (!url) return "";
35
+ if (url.endsWith("/")) url = url.slice(0, -1);
36
+ return url;
37
+ }
38
+
39
+ RED.nodes.registerType("vectorprime-config", {
40
+ category: "config",
41
+ defaults: {
42
+ baseUrl: { value: "https://vectorprime-kernel-backend.onrender.com", required: true }
43
+ },
44
+ credentials: {
45
+ apiKey: { type: "password" }
46
+ },
47
+ label: function () {
48
+ return "VectorPrime Config";
49
+ },
50
+ oneditprepare: function () {
51
+ const statusEl = document.getElementById("vp-key-status");
52
+ const btn = document.getElementById("vp-get-free-key");
53
+
54
+ btn.onclick = async function (e) {
55
+ e.preventDefault();
56
+ statusEl.textContent = "Requesting free key...";
57
+ statusEl.style.color = "#444";
58
+
59
+ try {
60
+ const baseUrlInput = document.getElementById("node-config-input-baseUrl");
61
+ const apiKeyInput = document.getElementById("node-config-input-apiKey");
62
+
63
+ const baseUrl = normalizeBaseUrl(baseUrlInput.value);
64
+
65
+ if (!baseUrl.startsWith("http")) {
66
+ statusEl.textContent = "❌ Base URL missing/invalid";
67
+ statusEl.style.color = "red";
68
+ return;
103
69
  }
104
- });
105
- })();
70
+
71
+ // Call backend directly from the editor
72
+ const resp = await fetch(`${baseUrl}/v1/keys/free`, {
73
+ method: "POST",
74
+ headers: { "Content-Type": "application/json" },
75
+ body: JSON.stringify({ source: "node-red" })
76
+ });
77
+
78
+ const text = await resp.text();
79
+ let data;
80
+ try { data = JSON.parse(text); } catch { data = { raw: text }; }
81
+
82
+ if (!resp.ok) {
83
+ statusEl.textContent = `❌ Failed (${resp.status}): ${data.message || data.error || "unknown"}`;
84
+ statusEl.style.color = "red";
85
+ return;
86
+ }
87
+
88
+ if (!data.api_key) {
89
+ statusEl.textContent = "❌ Backend did not return api_key";
90
+ statusEl.style.color = "red";
91
+ return;
92
+ }
93
+
94
+ apiKeyInput.value = data.api_key;
95
+ statusEl.textContent = "✅ Free key generated + saved";
96
+ statusEl.style.color = "green";
97
+ } catch (err) {
98
+ statusEl.textContent = `❌ Error: ${err.message}`;
99
+ statusEl.style.color = "red";
100
+ }
101
+ };
102
+ }
103
+ });
104
+ })();
106
105
  </script>
107
106
 
108
107
  <script type="text/html" data-template-name="vectorprime-rank">
109
108
  <div class="form-row">
110
109
  <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
111
- <input type="text" id="node-input-name" placeholder="VectorPrime Rank">
110
+ <input type="text" id="node-input-name" placeholder="Rank Decision (VectorPrime)">
112
111
  </div>
113
112
 
114
113
  <div class="form-row">
@@ -123,25 +122,57 @@
123
122
 
124
123
  <div class="form-row">
125
124
  <div style="font-size:12px; color:#666;">
126
- This node sends <code>msg.payload</code> to VectorPrime and returns the response in <code>msg.payload</code>.
125
+ ✅ <b>1-second summary:</b> Send a decision prompt + options get ranking + probabilities back.
127
126
  </div>
128
127
  </div>
129
128
  </script>
130
129
 
131
130
  <script type="text/javascript">
132
- RED.nodes.registerType("vectorprime-rank", {
133
- category: "VectorPrime",
134
- color: "#2563EB", // ✅ FIX: readable (NOT black)
135
- defaults: {
136
- name: { value: "" },
137
- config: { type: "vectorprime-config", required: true },
138
- endpoint: { value: "/v1/kernel/rank", required: true }
139
- },
140
- inputs: 1,
141
- outputs: 1,
142
- icon: "font-awesome/fa-bolt",
143
- label: function () {
144
- return this.name || "VectorPrime Rank";
145
- }
146
- });
147
- </script>
131
+ RED.nodes.registerType("vectorprime-rank", {
132
+ // ✅ REQUIRED: Change category to Network
133
+ category: "network",
134
+
135
+ // Easy to see, not black
136
+ color: "#2563EB",
137
+
138
+ defaults: {
139
+ name: { value: "" },
140
+ config: { type: "vectorprime-config", required: true },
141
+ endpoint: { value: "/v1/kernel/rank", required: true }
142
+ },
143
+
144
+ inputs: 1,
145
+ outputs: 1,
146
+ icon: "font-awesome/fa-bolt",
147
+
148
+ // ✅ REQUIRED: Make it instantly understandable in palette
149
+ paletteLabel: "Rank Decision (VectorPrime)",
150
+
151
+ label: function () {
152
+ return this.name || "Rank Decision (VectorPrime)";
153
+ },
154
+
155
+ outputLabels: ["ranking + probabilities"]
156
+ });
157
+ </script>
158
+
159
+ <script type="text/markdown" data-help-name="vectorprime-rank">
160
+ # Rank Decision (VectorPrime)
161
+
162
+ **What it does (1 second):**
163
+ Send a decision prompt + options → VectorPrime returns **ranking + probabilities**.
164
+
165
+ ## Input: `msg.payload`
166
+ Example:
167
+
168
+ ```json
169
+ {
170
+ "decision_id": "node-red-demo-001",
171
+ "prompt": "Pick one.",
172
+ "options": [
173
+ { "id": "a", "label": "A" },
174
+ { "id": "b", "label": "B" }
175
+ ],
176
+ "engine": "classical",
177
+ "seed": 123
178
+ }
package/vectorprime.js CHANGED
@@ -1,6 +1,7 @@
1
1
  module.exports = function (RED) {
2
+ // ✅ Works with node-fetch v2 or v3
2
3
  const fetch = (...args) =>
3
- import("node-fetch").then(({ default: fetch }) => fetch(...args));
4
+ import("node-fetch").then((mod) => (mod.default ? mod.default(...args) : mod(...args)));
4
5
 
5
6
  function normalizeBaseUrl(url) {
6
7
  url = (url || "").trim();
@@ -11,6 +12,7 @@ module.exports = function (RED) {
11
12
 
12
13
  function normalizeEndpoint(path) {
13
14
  path = (path || "").trim();
15
+ if (!path) return "/v1/kernel/rank";
14
16
  if (!path.startsWith("/")) path = "/" + path;
15
17
  return path;
16
18
  }
@@ -38,15 +40,19 @@ module.exports = function (RED) {
38
40
  node.name = config.name;
39
41
  node.endpoint = config.endpoint || "/v1/kernel/rank";
40
42
 
41
- const cfg = RED.nodes.getNode(config.config);
42
-
43
43
  node.on("input", async function (msg, send, done) {
44
+ send = send || function () { node.send.apply(node, arguments); };
45
+
44
46
  try {
47
+ const cfg = RED.nodes.getNode(config.config);
45
48
  if (!cfg) {
46
49
  node.status({ fill: "red", shape: "ring", text: "missing config" });
47
- throw new Error(
48
- "VectorPrime config missing. Open node settings and set Config."
49
- );
50
+ msg.payload = {
51
+ error: "VectorPrime config missing. Open node settings and set Config.",
52
+ };
53
+ send(msg);
54
+ if (done) done();
55
+ return;
50
56
  }
51
57
 
52
58
  const baseUrl = normalizeBaseUrl(cfg.baseUrl);
@@ -54,10 +60,16 @@ module.exports = function (RED) {
54
60
 
55
61
  if (!baseUrl.startsWith("http")) {
56
62
  node.status({ fill: "red", shape: "ring", text: "invalid base url" });
57
- throw new Error(`Base URL missing/invalid: ${baseUrl}`);
63
+ msg.payload = { error: `Base URL missing/invalid: ${baseUrl}` };
64
+ send(msg);
65
+ if (done) done();
66
+ return;
58
67
  }
59
68
 
69
+ // ✅ Ensure headers object exists
60
70
  msg.headers = msg.headers || {};
71
+
72
+ // ✅ Allow incoming Authorization override (advanced users)
61
73
  const incomingAuth =
62
74
  msg.headers.authorization ||
63
75
  msg.headers.Authorization ||
@@ -68,17 +80,21 @@ module.exports = function (RED) {
68
80
  ? String(cfg.credentials.apiKey).trim()
69
81
  : "";
70
82
 
71
- // If user didn't provide Authorization, use stored key
83
+ // If user didn't provide Authorization, use stored key from config credentials
72
84
  if (!incomingAuth) {
73
85
  if (!storedKey) {
74
86
  node.status({ fill: "red", shape: "ring", text: "missing api key" });
75
- throw new Error(
76
- "No API key. Open VectorPrime Config and click 'Get Free Key'."
77
- );
87
+ msg.payload = {
88
+ error: "No API key. Open VectorPrime Config and click 'Get Free Key'.",
89
+ };
90
+ send(msg);
91
+ if (done) done();
92
+ return;
78
93
  }
79
94
  msg.headers.Authorization = `Bearer ${storedKey}`;
80
95
  }
81
96
 
97
+ // ✅ Force JSON
82
98
  msg.headers["Content-Type"] = "application/json";
83
99
 
84
100
  const payload = msg.payload || {};
@@ -99,7 +115,7 @@ module.exports = function (RED) {
99
115
  data = { raw: text };
100
116
  }
101
117
 
102
- // If free tier limit hit → return upgrade url inside Node-RED
118
+ // Free tier limit hit → return upgrade url inside Node-RED
103
119
  if (!resp.ok) {
104
120
  const upgradeUrl = data.upgrade_url || data.checkout_url || null;
105
121
 
@@ -112,24 +128,26 @@ module.exports = function (RED) {
112
128
  msg.payload = data;
113
129
  msg.vectorprime = { upgrade_url: upgradeUrl };
114
130
  send(msg);
115
- done();
131
+ if (done) done();
116
132
  return;
117
133
  }
118
134
 
119
135
  node.status({ fill: "red", shape: "ring", text: `error ${resp.status}` });
120
136
  msg.payload = data;
121
137
  send(msg);
122
- done();
138
+ if (done) done();
123
139
  return;
124
140
  }
125
141
 
126
142
  node.status({ fill: "green", shape: "dot", text: "ok" });
127
143
  msg.payload = data;
128
144
  send(msg);
129
- done();
145
+ if (done) done();
130
146
  } catch (err) {
131
147
  node.status({ fill: "red", shape: "ring", text: "failed" });
132
- done(err);
148
+ msg.payload = { error: err.message || String(err) };
149
+ send(msg);
150
+ if (done) done(err);
133
151
  }
134
152
  });
135
153
  }