node-red-contrib-vectorprime 0.1.40 → 0.1.42
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/README.md +34 -47
- package/package.json +2 -2
- package/vectorprime.html +20 -81
- package/vectorprime.js +6 -12
package/README.md
CHANGED
|
@@ -1,70 +1,57 @@
|
|
|
1
1
|
# node-red-contrib-vectorprime
|
|
2
|
+
### VectorPrime — Decision Ranking for Node-RED
|
|
2
3
|
|
|
3
|
-
**
|
|
4
|
+
**node-red-contrib-vectorprime** lets you rank tasks, alerts, or actions inside Node-RED when multiple options compete.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
✅ Prioritize tasks
|
|
8
|
-
✅ Rank alerts by urgency
|
|
9
|
-
✅ Choose the best option from multiple choices
|
|
10
|
-
✅ Turn “too many choices” into a safe automation decision state
|
|
11
|
-
✅ Works inside Node-RED flows with 1 click
|
|
12
|
-
|
|
13
|
-
---
|
|
14
|
-
|
|
15
|
-
## What it does (in 1 sentence)
|
|
16
|
-
|
|
17
|
-
**Send a list of options → get them ranked best-to-worst with probabilities + metadata you can branch on.**
|
|
6
|
+
Instead of hard-coding rules, VectorPrime helps flows choose the **safest, most urgent, or most rational next step** automatically.
|
|
18
7
|
|
|
19
8
|
---
|
|
20
9
|
|
|
21
|
-
##
|
|
10
|
+
## 🧠 What this node does (plain English)
|
|
22
11
|
|
|
23
|
-
|
|
12
|
+
You send VectorPrime a list of options (tasks, actions, alerts). Each option can include simple metadata like urgency, impact, deadline, or effort.
|
|
24
13
|
|
|
25
|
-
VectorPrime
|
|
14
|
+
VectorPrime returns:
|
|
15
|
+
- A ranked list (best → worst)
|
|
16
|
+
- Probabilities / confidence signals (when provided)
|
|
17
|
+
- Metadata about the ranking
|
|
26
18
|
|
|
27
|
-
-
|
|
28
|
-
- “Which ticket should I do next?”
|
|
29
|
-
- “Which customer should get priority?”
|
|
30
|
-
- “Which action gives the best outcome with least risk?”
|
|
31
|
-
- “Which fix should I deploy first?”
|
|
32
|
-
|
|
33
|
-
Into:
|
|
34
|
-
|
|
35
|
-
✅ A **ranked result** when the input supports a winner
|
|
36
|
-
✅ Or a **tie / no-dominant outcome** (so your flow can branch safely)
|
|
19
|
+
This turns Node-RED into a **decision-aware automation engine**, not just `if/else`.
|
|
37
20
|
|
|
38
21
|
---
|
|
39
22
|
|
|
40
|
-
## Nodes
|
|
23
|
+
## 📦 Nodes included
|
|
41
24
|
|
|
42
25
|
- **VectorPrime Config**
|
|
43
|
-
Stores
|
|
26
|
+
Stores Base URL + API key securely using Node-RED credentials (not exported with flows).
|
|
44
27
|
|
|
45
|
-
- **VectorPrime
|
|
46
|
-
Sends options to VectorPrime
|
|
28
|
+
- **Rank Decision (VectorPrime)**
|
|
29
|
+
Sends options to the VectorPrime Rank API and returns ranked results.
|
|
47
30
|
|
|
48
31
|
---
|
|
49
32
|
|
|
50
|
-
##
|
|
33
|
+
## ⚡ Quick start (5 minutes)
|
|
51
34
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
35
|
+
1. Open Node-RED
|
|
36
|
+
2. Menu ☰ → **Manage Palette** → **Install**
|
|
37
|
+
3. Search for: **node-red-contrib-vectorprime**
|
|
38
|
+
4. Install
|
|
39
|
+
5. Drag **Rank Decision (VectorPrime)** into a flow
|
|
40
|
+
6. Create/select a **VectorPrime Config** and click **Get Free Key**
|
|
41
|
+
7. Inject one of the examples below into `msg.payload`
|
|
59
42
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
Open your Node-RED user directory:
|
|
43
|
+
---
|
|
63
44
|
|
|
64
|
-
|
|
65
|
-
- **macOS/Linux:** `~/.node-red`
|
|
45
|
+
## ✅ Input format (simple) — `items[]` (recommended)
|
|
66
46
|
|
|
67
|
-
|
|
47
|
+
The node will automatically convert `items[]` into the backend’s required `options[]` format.
|
|
68
48
|
|
|
69
|
-
```
|
|
70
|
-
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"items": [
|
|
52
|
+
{ "id": "payroll", "label": "Fix payroll outage", "urgency": 10, "impact": 10, "effort": 3 },
|
|
53
|
+
{ "id": "email", "label": "Reply to customer email", "urgency": 4, "impact": 5, "effort": 2 },
|
|
54
|
+
{ "id": "deploy", "label": "Ship small hotfix", "urgency": 7, "impact": 7, "effort": 4 }
|
|
55
|
+
],
|
|
56
|
+
"prompt": "Rank the best next action."
|
|
57
|
+
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-vectorprime",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.42",
|
|
4
4
|
"description": "Rank tasks, alerts, or actions inside Node-RED when multiple options compete. Choose the safest next step automatically instead of hard-coded rules.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Faisal Khan",
|
|
7
7
|
"homepage": "https://gitlab.com/faisalkhan3000/vectorprime-node-red-node",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
|
-
"url": "https://gitlab.com/faisalkhan3000/vectorprime-node-red-node.git"
|
|
10
|
+
"url": "git+https://gitlab.com/faisalkhan3000/vectorprime-node-red-node.git"
|
|
11
11
|
},
|
|
12
12
|
"bugs": {
|
|
13
13
|
"url": "https://gitlab.com/faisalkhan3000/vectorprime-node-red-node/-/issues"
|
package/vectorprime.html
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<!-- =========================
|
|
2
2
|
VectorPrime Config Template
|
|
3
|
+
WEBSITE-ONLY KEYS (no minting inside Node-RED)
|
|
3
4
|
========================= -->
|
|
4
5
|
<script type="text/x-red" data-template-name="vectorprime-config">
|
|
5
6
|
<div class="form-row">
|
|
@@ -9,26 +10,19 @@
|
|
|
9
10
|
<input type="text" id="node-config-input-baseUrl" placeholder="https://vectorprime-kernel-backend.onrender.com">
|
|
10
11
|
</div>
|
|
11
12
|
|
|
12
|
-
<!-- ✅ REQUIRED: email for /v1/billing/signup -->
|
|
13
|
-
<div class="form-row">
|
|
14
|
-
<label for="node-config-input-email">
|
|
15
|
-
<i class="fa fa-envelope"></i> Email
|
|
16
|
-
</label>
|
|
17
|
-
<input type="text" id="node-config-input-email" placeholder="you@example.com">
|
|
18
|
-
</div>
|
|
19
|
-
|
|
20
13
|
<div class="form-row">
|
|
21
14
|
<label for="node-config-input-apiKey">
|
|
22
15
|
<i class="fa fa-key"></i> API Key
|
|
23
16
|
</label>
|
|
24
|
-
<input type="password" id="node-config-input-apiKey" placeholder="
|
|
17
|
+
<input type="password" id="node-config-input-apiKey" placeholder="vp_live_...">
|
|
25
18
|
</div>
|
|
26
19
|
|
|
27
20
|
<div class="form-row">
|
|
28
|
-
<
|
|
29
|
-
Get
|
|
30
|
-
|
|
31
|
-
|
|
21
|
+
<div style="font-size:12px; color:#666; line-height:1.4;">
|
|
22
|
+
Get your API key from
|
|
23
|
+
<a href="https://vectorprime.tech" target="_blank" rel="noopener noreferrer">vectorprime.tech</a>,
|
|
24
|
+
then paste it above.
|
|
25
|
+
</div>
|
|
32
26
|
</div>
|
|
33
27
|
|
|
34
28
|
<div class="form-row">
|
|
@@ -50,8 +44,7 @@
|
|
|
50
44
|
RED.nodes.registerType("vectorprime-config", {
|
|
51
45
|
category: "config",
|
|
52
46
|
defaults: {
|
|
53
|
-
baseUrl: { value: "https://vectorprime-kernel-backend.onrender.com", required: true }
|
|
54
|
-
email: { value: "", required: true }
|
|
47
|
+
baseUrl: { value: "https://vectorprime-kernel-backend.onrender.com", required: true }
|
|
55
48
|
},
|
|
56
49
|
credentials: {
|
|
57
50
|
apiKey: { type: "password" }
|
|
@@ -60,65 +53,16 @@
|
|
|
60
53
|
return "VectorPrime Config";
|
|
61
54
|
},
|
|
62
55
|
oneditprepare: function () {
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const emailInput = document.getElementById("node-config-input-email");
|
|
74
|
-
const apiKeyInput = document.getElementById("node-config-input-apiKey");
|
|
75
|
-
|
|
76
|
-
const baseUrl = normalizeBaseUrl(baseUrlInput.value);
|
|
77
|
-
const email = ((emailInput && emailInput.value) ? emailInput.value : "").trim();
|
|
78
|
-
|
|
79
|
-
if (!baseUrl.startsWith("http")) {
|
|
80
|
-
statusEl.textContent = "❌ Base URL missing/invalid";
|
|
81
|
-
statusEl.style.color = "red";
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (!email || !email.includes("@")) {
|
|
86
|
-
statusEl.textContent = "❌ Enter a valid email";
|
|
87
|
-
statusEl.style.color = "red";
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// ✅ Call REAL backend endpoint that exists
|
|
92
|
-
const resp = await fetch(`${baseUrl}/v1/billing/signup`, {
|
|
93
|
-
method: "POST",
|
|
94
|
-
headers: { "Content-Type": "application/json" },
|
|
95
|
-
body: JSON.stringify({ email: email })
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
const text = await resp.text();
|
|
99
|
-
let data;
|
|
100
|
-
try { data = JSON.parse(text); } catch { data = { raw: text }; }
|
|
101
|
-
|
|
102
|
-
if (!resp.ok) {
|
|
103
|
-
statusEl.textContent = `❌ Failed (${resp.status}): ${data.message || data.error || data.detail || "unknown"}`;
|
|
104
|
-
statusEl.style.color = "red";
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (!data.api_key) {
|
|
109
|
-
statusEl.textContent = "❌ Backend did not return api_key";
|
|
110
|
-
statusEl.style.color = "red";
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
apiKeyInput.value = data.api_key;
|
|
115
|
-
statusEl.textContent = "✅ Free key generated + saved";
|
|
116
|
-
statusEl.style.color = "green";
|
|
117
|
-
} catch (err) {
|
|
118
|
-
statusEl.textContent = `❌ Error: ${err.message}`;
|
|
119
|
-
statusEl.style.color = "red";
|
|
120
|
-
}
|
|
121
|
-
};
|
|
56
|
+
const baseUrlInput = document.getElementById("node-config-input-baseUrl");
|
|
57
|
+
if (baseUrlInput && baseUrlInput.value) {
|
|
58
|
+
baseUrlInput.value = normalizeBaseUrl(baseUrlInput.value);
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
oneditsave: function () {
|
|
62
|
+
const baseUrlInput = document.getElementById("node-config-input-baseUrl");
|
|
63
|
+
if (baseUrlInput && baseUrlInput.value) {
|
|
64
|
+
baseUrlInput.value = normalizeBaseUrl(baseUrlInput.value);
|
|
65
|
+
}
|
|
122
66
|
}
|
|
123
67
|
});
|
|
124
68
|
})();
|
|
@@ -131,8 +75,8 @@
|
|
|
131
75
|
<h3>Quick Setup</h3>
|
|
132
76
|
<ol>
|
|
133
77
|
<li>Set <b>Base URL</b> (default is already correct)</li>
|
|
134
|
-
<li>
|
|
135
|
-
<li>
|
|
78
|
+
<li>Get your API key from <b>vectorprime.tech</b></li>
|
|
79
|
+
<li>Paste the key into <b>API Key</b></li>
|
|
136
80
|
<li>Click <b>Done</b></li>
|
|
137
81
|
</ol>
|
|
138
82
|
|
|
@@ -142,11 +86,6 @@
|
|
|
142
86
|
|
|
143
87
|
<h3>Recommended Base URL</h3>
|
|
144
88
|
<pre>https://vectorprime-kernel-backend.onrender.com</pre>
|
|
145
|
-
|
|
146
|
-
<h3>Tip</h3>
|
|
147
|
-
<p>
|
|
148
|
-
You can also paste your own paid key here if you want higher limits.
|
|
149
|
-
</p>
|
|
150
89
|
</script>
|
|
151
90
|
|
|
152
91
|
<hr />
|
package/vectorprime.js
CHANGED
|
@@ -25,10 +25,6 @@ module.exports = function (RED) {
|
|
|
25
25
|
return Number.isFinite(n) ? n : null;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
function almostEqual(a, b, eps = 1e-9) {
|
|
29
|
-
return Math.abs(a - b) <= eps;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
28
|
function isFlatProbabilities(probObj) {
|
|
33
29
|
if (!probObj || typeof probObj !== "object") return false;
|
|
34
30
|
const keys = Object.keys(probObj);
|
|
@@ -87,6 +83,7 @@ module.exports = function (RED) {
|
|
|
87
83
|
|
|
88
84
|
// -----------------------------
|
|
89
85
|
// Config Node (stores Base URL + API Key)
|
|
86
|
+
// WEBSITE-ONLY KEYS: no email, no "Get Free Key" minting
|
|
90
87
|
// -----------------------------
|
|
91
88
|
function VectorPrimeConfigNode(n) {
|
|
92
89
|
RED.nodes.createNode(this, n);
|
|
@@ -95,9 +92,8 @@ module.exports = function (RED) {
|
|
|
95
92
|
(n.baseUrl && String(n.baseUrl).trim()) ||
|
|
96
93
|
"https://vectorprime-kernel-backend.onrender.com";
|
|
97
94
|
|
|
98
|
-
// NOTE:
|
|
99
|
-
//
|
|
100
|
-
this.email = (n.email && String(n.email).trim()) || "";
|
|
95
|
+
// NOTE: apiKey is stored in credentials only.
|
|
96
|
+
// No email stored here anymore (website-only keys).
|
|
101
97
|
}
|
|
102
98
|
|
|
103
99
|
RED.nodes.registerType("vectorprime-config", VectorPrimeConfigNode, {
|
|
@@ -145,7 +141,7 @@ module.exports = function (RED) {
|
|
|
145
141
|
if (!storedKey) {
|
|
146
142
|
node.status({ fill: "red", shape: "ring", text: "missing api key" });
|
|
147
143
|
throw new Error(
|
|
148
|
-
"No API key.
|
|
144
|
+
"No API key. Get your key from vectorprime.tech and paste it into the VectorPrime Config."
|
|
149
145
|
);
|
|
150
146
|
}
|
|
151
147
|
|
|
@@ -214,15 +210,13 @@ module.exports = function (RED) {
|
|
|
214
210
|
}
|
|
215
211
|
|
|
216
212
|
// -----------------------------
|
|
217
|
-
// ✅
|
|
218
|
-
// This prevents users from seeing "random" / "input-order" ranking.
|
|
213
|
+
// ✅ If backend returns a tie/flat result, do deterministic local ranking
|
|
219
214
|
// -----------------------------
|
|
220
215
|
const tieBreak =
|
|
221
216
|
data && data.meta && typeof data.meta === "object"
|
|
222
217
|
? data.meta.tie_break
|
|
223
218
|
: null;
|
|
224
219
|
|
|
225
|
-
const hasRanking = Array.isArray(data && data.ranking);
|
|
226
220
|
const hasProb = data && data.probabilities;
|
|
227
221
|
|
|
228
222
|
const looksTied =
|
|
@@ -269,4 +263,4 @@ module.exports = function (RED) {
|
|
|
269
263
|
}
|
|
270
264
|
|
|
271
265
|
RED.nodes.registerType("vectorprime-rank", VectorPrimeRankNode);
|
|
272
|
-
};
|
|
266
|
+
};
|