mirror of
https://github.com/bunny-lab-io/Borealis.git
synced 2025-07-27 17:38:28 -06:00
Overhauled Deployment Structure
This commit is contained in:
192
Data/Server/WebUI/src/nodes/General Purpose/Node_Data.jsx
Normal file
192
Data/Server/WebUI/src/nodes/General Purpose/Node_Data.jsx
Normal file
@ -0,0 +1,192 @@
|
||||
////////// PROJECT FILE SEPARATION LINE ////////// CODE AFTER THIS LINE ARE FROM: <ProjectRoot>/Data/WebUI/src/nodes/General Purpose/Node_Data.jsx
|
||||
|
||||
/**
|
||||
* ============================================
|
||||
* Borealis - Standard Live Data Node Template
|
||||
* ============================================
|
||||
*
|
||||
* COMPONENT ROLE:
|
||||
* This component defines a "data conduit" node that can accept input,
|
||||
* process/override it with local logic, and forward the output on a timed basis.
|
||||
*
|
||||
* It serves as the core behavior model for other nodes that rely on live propagation.
|
||||
* Clone and extend this file to create nodes with specialized logic.
|
||||
*
|
||||
* CORE CONCEPTS:
|
||||
* - Uses a centralized shared memory (window.BorealisValueBus) for value sharing
|
||||
* - Synchronizes with upstream nodes based on ReactFlow edges
|
||||
* - Emits to downstream nodes by updating its own BorealisValueBus[id] value
|
||||
* - Controlled by a global polling timer (window.BorealisUpdateRate)
|
||||
*
|
||||
* LIFECYCLE SUMMARY:
|
||||
* - onMount: initializes logic loop and sync monitor
|
||||
* - onUpdate: watches edges and global rate, reconfigures as needed
|
||||
* - onUnmount: cleans up all timers
|
||||
*
|
||||
* DATA FLOW OVERVIEW:
|
||||
* - INPUT: if a left-edge (target) is connected, disables manual editing
|
||||
* - OUTPUT: propagates renderValue to downstream nodes via right-edge (source)
|
||||
*
|
||||
* STRUCTURE:
|
||||
* - Node UI includes:
|
||||
* * Label (from data.label)
|
||||
* * Body description (from data.content)
|
||||
* * Input textbox (disabled if input is connected)
|
||||
*
|
||||
* HOW TO EXTEND:
|
||||
* - For transformations, insert logic into runNodeLogic()
|
||||
* - To validate or restrict input types, modify handleManualInput()
|
||||
* - For side-effects or external API calls, add hooks inside runNodeLogic()
|
||||
*/
|
||||
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { Handle, Position, useReactFlow, useStore } from "reactflow";
|
||||
|
||||
// Global Shared Bus for Node Data Propagation
|
||||
if (!window.BorealisValueBus) {
|
||||
window.BorealisValueBus = {};
|
||||
}
|
||||
|
||||
// Global Update Rate (ms) for All Data Nodes
|
||||
if (!window.BorealisUpdateRate) {
|
||||
window.BorealisUpdateRate = 100;
|
||||
}
|
||||
|
||||
const DataNode = ({ id, data }) => {
|
||||
const { setNodes } = useReactFlow();
|
||||
const edges = useStore(state => state.edges);
|
||||
|
||||
const [renderValue, setRenderValue] = useState(data?.value || "");
|
||||
const valueRef = useRef(renderValue);
|
||||
|
||||
// Manual input handler (disabled if connected to input)
|
||||
const handleManualInput = (e) => {
|
||||
const newValue = e.target.value;
|
||||
|
||||
// TODO: Add input validation/sanitization here if needed
|
||||
valueRef.current = newValue;
|
||||
setRenderValue(newValue);
|
||||
|
||||
window.BorealisValueBus[id] = newValue;
|
||||
|
||||
setNodes(nds =>
|
||||
nds.map(n =>
|
||||
n.id === id
|
||||
? { ...n, data: { ...n.data, value: newValue } }
|
||||
: n
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let currentRate = window.BorealisUpdateRate || 100;
|
||||
let intervalId = null;
|
||||
|
||||
const runNodeLogic = () => {
|
||||
const inputEdge = edges.find(e => e?.target === id);
|
||||
const hasInput = Boolean(inputEdge);
|
||||
|
||||
if (hasInput && inputEdge.source) {
|
||||
const upstreamValue = window.BorealisValueBus[inputEdge.source] ?? "";
|
||||
|
||||
// TODO: Insert custom transform logic here (e.g., parseInt, apply formula)
|
||||
|
||||
if (upstreamValue !== valueRef.current) {
|
||||
valueRef.current = upstreamValue;
|
||||
setRenderValue(upstreamValue);
|
||||
window.BorealisValueBus[id] = upstreamValue;
|
||||
|
||||
setNodes(nds =>
|
||||
nds.map(n =>
|
||||
n.id === id
|
||||
? { ...n, data: { ...n.data, value: upstreamValue } }
|
||||
: n
|
||||
)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// OUTPUT BROADCAST: emits to downstream via shared memory
|
||||
window.BorealisValueBus[id] = valueRef.current;
|
||||
}
|
||||
};
|
||||
|
||||
const startInterval = () => {
|
||||
intervalId = setInterval(runNodeLogic, currentRate);
|
||||
};
|
||||
|
||||
startInterval();
|
||||
|
||||
// Monitor for global update rate changes
|
||||
const monitor = setInterval(() => {
|
||||
const newRate = window.BorealisUpdateRate || 100;
|
||||
if (newRate !== currentRate) {
|
||||
currentRate = newRate;
|
||||
clearInterval(intervalId);
|
||||
startInterval();
|
||||
}
|
||||
}, 250);
|
||||
|
||||
return () => {
|
||||
clearInterval(intervalId);
|
||||
clearInterval(monitor);
|
||||
};
|
||||
}, [id, setNodes, edges]);
|
||||
|
||||
const inputEdge = edges.find(e => e?.target === id);
|
||||
const hasInput = Boolean(inputEdge);
|
||||
const upstreamId = inputEdge?.source || "";
|
||||
const upstreamValue = window.BorealisValueBus[upstreamId] || "";
|
||||
|
||||
return (
|
||||
<div className="borealis-node">
|
||||
<Handle type="target" position={Position.Left} className="borealis-handle" />
|
||||
|
||||
<div className="borealis-node-header">
|
||||
{data?.label || "Data Node"}
|
||||
</div>
|
||||
|
||||
<div className="borealis-node-content">
|
||||
{/* Description visible in node body */}
|
||||
<div style={{ marginBottom: "8px", fontSize: "9px", color: "#ccc" }}>
|
||||
{data?.content || "Foundational node for live value propagation."}
|
||||
</div>
|
||||
|
||||
<label style={{ fontSize: "9px", display: "block", marginBottom: "4px" }}>
|
||||
Value:
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={renderValue}
|
||||
onChange={handleManualInput}
|
||||
disabled={hasInput}
|
||||
style={{
|
||||
fontSize: "9px",
|
||||
padding: "4px",
|
||||
background: hasInput ? "#2a2a2a" : "#1e1e1e",
|
||||
color: "#ccc",
|
||||
border: "1px solid #444",
|
||||
borderRadius: "2px",
|
||||
width: "100%"
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Handle type="source" position={Position.Right} className="borealis-handle" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default {
|
||||
type: "DataNode", // REQUIRED: unique identifier for the node type
|
||||
label: "String / Number",
|
||||
description: `
|
||||
Foundational Data Node
|
||||
|
||||
- Accepts input from another node
|
||||
- If no input is connected, allows user-defined value
|
||||
- Pushes value to downstream nodes every X ms
|
||||
- Uses BorealisValueBus to communicate with other nodes
|
||||
`.trim(),
|
||||
content: "Store a String or Number",
|
||||
component: DataNode
|
||||
};
|
@ -0,0 +1,175 @@
|
||||
////////// PROJECT FILE SEPARATION LINE ////////// CODE AFTER THIS LINE ARE FROM: <ProjectRoot>/Data/WebUI/src/nodes/General Purpose/Node_Logical_Operators.jsx
|
||||
|
||||
/**
|
||||
* ==============================================
|
||||
* Borealis - Comparison Node (Logic Evaluation)
|
||||
* ==============================================
|
||||
*
|
||||
* COMPONENT ROLE:
|
||||
* This node takes two input values and evaluates them using a selected comparison operator.
|
||||
* It returns 1 (true) or 0 (false) depending on the result of the comparison.
|
||||
*
|
||||
* FEATURES:
|
||||
* - Dropdown to select input type: "Number" or "String"
|
||||
* - Dropdown to select comparison operator: ==, !=, >, <, >=, <=
|
||||
* - Dynamically disables numeric-only operators for string inputs
|
||||
* - Automatically resets operator to == when switching to String
|
||||
* - Supports summing multiple inputs per side (A, B)
|
||||
* - For "String" mode: concatenates inputs in connection order
|
||||
* - Uses BorealisValueBus for input/output
|
||||
* - Controlled by global update timer
|
||||
*
|
||||
* STRUCTURE:
|
||||
* - Label and Description
|
||||
* - Input A (top-left) and Input B (middle-left)
|
||||
* - Output (right edge) result: 1 (true) or 0 (false)
|
||||
* - Operator dropdown and Input Type dropdown
|
||||
*/
|
||||
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { Handle, Position, useReactFlow, useStore } from "reactflow";
|
||||
|
||||
if (!window.BorealisValueBus) window.BorealisValueBus = {};
|
||||
if (!window.BorealisUpdateRate) window.BorealisUpdateRate = 100;
|
||||
|
||||
const ComparisonNode = ({ id, data }) => {
|
||||
const { setNodes } = useReactFlow();
|
||||
const edges = useStore(state => state.edges);
|
||||
|
||||
const [inputType, setInputType] = useState(data?.inputType || "Number");
|
||||
const [operator, setOperator] = useState(data?.operator || "Equal (==)");
|
||||
const [renderValue, setRenderValue] = useState("0");
|
||||
const valueRef = useRef("0");
|
||||
|
||||
useEffect(() => {
|
||||
if (inputType === "String" && !["Equal (==)", "Not Equal (!=)"].includes(operator)) {
|
||||
setOperator("Equal (==)");
|
||||
}
|
||||
}, [inputType]);
|
||||
|
||||
useEffect(() => {
|
||||
let currentRate = window.BorealisUpdateRate;
|
||||
let intervalId = null;
|
||||
|
||||
const runNodeLogic = () => {
|
||||
const edgeInputsA = edges.filter(e => e?.target === id && e.targetHandle === "a");
|
||||
const edgeInputsB = edges.filter(e => e?.target === id && e.targetHandle === "b");
|
||||
|
||||
const extractValues = (edgeList) => {
|
||||
const values = edgeList.map(e => window.BorealisValueBus[e.source]).filter(v => v !== undefined);
|
||||
if (inputType === "Number") {
|
||||
return values.reduce((sum, v) => sum + (parseFloat(v) || 0), 0);
|
||||
}
|
||||
return values.join("");
|
||||
};
|
||||
|
||||
const a = extractValues(edgeInputsA);
|
||||
const b = extractValues(edgeInputsB);
|
||||
|
||||
const resultMap = {
|
||||
"Equal (==)": a === b,
|
||||
"Not Equal (!=)": a !== b,
|
||||
"Greater Than (>)": a > b,
|
||||
"Less Than (<)": a < b,
|
||||
"Greater Than or Equal (>=)": a >= b,
|
||||
"Less Than or Equal (<=)": a <= b
|
||||
};
|
||||
|
||||
const result = resultMap[operator] ? "1" : "0";
|
||||
|
||||
valueRef.current = result;
|
||||
setRenderValue(result);
|
||||
window.BorealisValueBus[id] = result;
|
||||
|
||||
setNodes(nds =>
|
||||
nds.map(n =>
|
||||
n.id === id ? { ...n, data: { ...n.data, value: result, inputType, operator } } : n
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
intervalId = setInterval(runNodeLogic, currentRate);
|
||||
|
||||
const monitor = setInterval(() => {
|
||||
const newRate = window.BorealisUpdateRate;
|
||||
if (newRate !== currentRate) {
|
||||
clearInterval(intervalId);
|
||||
currentRate = newRate;
|
||||
intervalId = setInterval(runNodeLogic, currentRate);
|
||||
}
|
||||
}, 250);
|
||||
|
||||
return () => {
|
||||
clearInterval(intervalId);
|
||||
clearInterval(monitor);
|
||||
};
|
||||
}, [id, edges, inputType, operator, setNodes]);
|
||||
|
||||
return (
|
||||
<div className="borealis-node">
|
||||
<div style={{ position: "absolute", left: -16, top: 12, fontSize: "8px", color: "#ccc" }}>A</div>
|
||||
<div style={{ position: "absolute", left: -16, top: 50, fontSize: "8px", color: "#ccc" }}>B</div>
|
||||
<Handle type="target" position={Position.Left} id="a" style={{ top: 12 }} className="borealis-handle" />
|
||||
<Handle type="target" position={Position.Left} id="b" style={{ top: 50 }} className="borealis-handle" />
|
||||
|
||||
<div className="borealis-node-header">
|
||||
{data?.label || "Comparison Node"}
|
||||
</div>
|
||||
|
||||
<div className="borealis-node-content">
|
||||
<div style={{ marginBottom: "6px", fontSize: "9px", color: "#ccc" }}>
|
||||
{data?.content || "Evaluates A vs B and outputs 1 (true) or 0 (false)."}
|
||||
</div>
|
||||
|
||||
<label style={{ fontSize: "9px" }}>Input Type:</label>
|
||||
<select value={inputType} onChange={(e) => setInputType(e.target.value)} style={dropdownStyle}>
|
||||
<option value="Number">Number</option>
|
||||
<option value="String">String</option>
|
||||
</select>
|
||||
|
||||
<label style={{ fontSize: "9px", marginTop: "6px" }}>Operator:</label>
|
||||
<select value={operator} onChange={(e) => setOperator(e.target.value)} style={dropdownStyle}>
|
||||
<option>Equal (==)</option>
|
||||
<option>Not Equal (!=)</option>
|
||||
<option disabled={inputType === "String"}>Greater Than (>)</option>
|
||||
<option disabled={inputType === "String"}>Less Than (<)</option>
|
||||
<option disabled={inputType === "String"}>Greater Than or Equal (>=)</option>
|
||||
<option disabled={inputType === "String"}>Less Than or Equal (<=)</option>
|
||||
</select>
|
||||
|
||||
<div style={{ marginTop: "8px", fontSize: "9px" }}>Result: {renderValue}</div>
|
||||
</div>
|
||||
|
||||
<Handle type="source" position={Position.Right} className="borealis-handle" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const dropdownStyle = {
|
||||
width: "100%",
|
||||
fontSize: "9px",
|
||||
padding: "4px",
|
||||
background: "#1e1e1e",
|
||||
color: "#ccc",
|
||||
border: "1px solid #444",
|
||||
borderRadius: "2px",
|
||||
marginBottom: "4px"
|
||||
};
|
||||
|
||||
export default {
|
||||
type: "ComparisonNode",
|
||||
label: "Logic Comparison",
|
||||
description: `
|
||||
Compare Two Inputs (A vs B)
|
||||
|
||||
- Uses configurable operator
|
||||
- Supports numeric and string comparison
|
||||
- Aggregates multiple inputs by summing (Number) or joining (String in connection order)
|
||||
- Only == and != are valid for String mode
|
||||
- Automatically resets operator when switching to String mode
|
||||
- Outputs 1 (true) or 0 (false) into BorealisValueBus
|
||||
- Live-updates based on global timer
|
||||
`.trim(),
|
||||
content: "Compare A and B using Logic",
|
||||
component: ComparisonNode
|
||||
};
|
@ -0,0 +1,179 @@
|
||||
////////// PROJECT FILE SEPARATION LINE ////////// CODE AFTER THIS LINE ARE FROM: <ProjectRoot>/Data/WebUI/src/nodes/General Purpose/Node_Math_Operations.jsx
|
||||
|
||||
/**
|
||||
* ============================================
|
||||
* Borealis - Math Operation Node (Multi-Input A/B)
|
||||
* ============================================
|
||||
*
|
||||
* COMPONENT ROLE:
|
||||
* Performs live math operations on *two grouped input sets* (A and B).
|
||||
*
|
||||
* FUNCTIONALITY:
|
||||
* - Inputs connected to Handle A are summed
|
||||
* - Inputs connected to Handle B are summed
|
||||
* - Math operation is applied as: A <operator> B
|
||||
* - Result pushed via BorealisValueBus[id]
|
||||
*
|
||||
* SUPPORTED OPERATORS:
|
||||
* - Add, Subtract, Multiply, Divide, Average
|
||||
*/
|
||||
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { Handle, Position, useReactFlow, useStore } from "reactflow";
|
||||
|
||||
if (!window.BorealisValueBus) window.BorealisValueBus = {};
|
||||
if (!window.BorealisUpdateRate) window.BorealisUpdateRate = 100;
|
||||
|
||||
const MathNode = ({ id, data }) => {
|
||||
const { setNodes } = useReactFlow();
|
||||
const edges = useStore(state => state.edges);
|
||||
|
||||
const [operator, setOperator] = useState(data?.operator || "Add");
|
||||
const [result, setResult] = useState("0");
|
||||
const resultRef = useRef(0);
|
||||
|
||||
useEffect(() => {
|
||||
let intervalId = null;
|
||||
let currentRate = window.BorealisUpdateRate;
|
||||
|
||||
const runLogic = () => {
|
||||
const inputsA = edges.filter(e => e.target === id && e.targetHandle === "a");
|
||||
const inputsB = edges.filter(e => e.target === id && e.targetHandle === "b");
|
||||
|
||||
const sum = (list) =>
|
||||
list.map(e => parseFloat(window.BorealisValueBus[e.source]) || 0).reduce((a, b) => a + b, 0);
|
||||
|
||||
const valA = sum(inputsA);
|
||||
const valB = sum(inputsB);
|
||||
|
||||
let value = 0;
|
||||
switch (operator) {
|
||||
case "Add":
|
||||
value = valA + valB;
|
||||
break;
|
||||
case "Subtract":
|
||||
value = valA - valB;
|
||||
break;
|
||||
case "Multiply":
|
||||
value = valA * valB;
|
||||
break;
|
||||
case "Divide":
|
||||
value = valB !== 0 ? valA / valB : 0;
|
||||
break;
|
||||
case "Average":
|
||||
const totalInputs = inputsA.length + inputsB.length;
|
||||
const totalSum = valA + valB;
|
||||
value = totalInputs > 0 ? totalSum / totalInputs : 0;
|
||||
break;
|
||||
}
|
||||
|
||||
resultRef.current = value;
|
||||
setResult(value.toString());
|
||||
window.BorealisValueBus[id] = value.toString();
|
||||
|
||||
setNodes(nds =>
|
||||
nds.map(n =>
|
||||
n.id === id ? { ...n, data: { ...n.data, operator, value: value.toString() } } : n
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
intervalId = setInterval(runLogic, currentRate);
|
||||
|
||||
const monitor = setInterval(() => {
|
||||
const newRate = window.BorealisUpdateRate;
|
||||
if (newRate !== currentRate) {
|
||||
clearInterval(intervalId);
|
||||
currentRate = newRate;
|
||||
intervalId = setInterval(runLogic, currentRate);
|
||||
}
|
||||
}, 250);
|
||||
|
||||
return () => {
|
||||
clearInterval(intervalId);
|
||||
clearInterval(monitor);
|
||||
};
|
||||
}, [id, operator, edges, setNodes]);
|
||||
|
||||
return (
|
||||
<div className="borealis-node">
|
||||
<div style={{ position: "absolute", left: -16, top: 12, fontSize: "8px", color: "#ccc" }}>A</div>
|
||||
<div style={{ position: "absolute", left: -16, top: 50, fontSize: "8px", color: "#ccc" }}>B</div>
|
||||
<Handle type="target" position={Position.Left} id="a" style={{ top: 12 }} className="borealis-handle" />
|
||||
<Handle type="target" position={Position.Left} id="b" style={{ top: 50 }} className="borealis-handle" />
|
||||
|
||||
<div className="borealis-node-header">
|
||||
{data?.label || "Math Operation"}
|
||||
</div>
|
||||
|
||||
<div className="borealis-node-content">
|
||||
<div style={{ marginBottom: "8px", fontSize: "9px", color: "#ccc" }}>
|
||||
Aggregates A and B inputs then performs operation.
|
||||
</div>
|
||||
|
||||
<label style={{ fontSize: "9px", display: "block", marginBottom: "4px" }}>
|
||||
Operator:
|
||||
</label>
|
||||
<select
|
||||
value={operator}
|
||||
onChange={(e) => setOperator(e.target.value)}
|
||||
style={dropdownStyle}
|
||||
>
|
||||
<option value="Add">Add</option>
|
||||
<option value="Subtract">Subtract</option>
|
||||
<option value="Multiply">Multiply</option>
|
||||
<option value="Divide">Divide</option>
|
||||
<option value="Average">Average</option>
|
||||
</select>
|
||||
|
||||
<label style={{ fontSize: "9px", display: "block", marginBottom: "4px" }}>
|
||||
Result:
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={result}
|
||||
disabled
|
||||
style={resultBoxStyle}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Handle type="source" position={Position.Right} className="borealis-handle" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const dropdownStyle = {
|
||||
fontSize: "9px",
|
||||
padding: "4px",
|
||||
background: "#1e1e1e",
|
||||
color: "#ccc",
|
||||
border: "1px solid #444",
|
||||
borderRadius: "2px",
|
||||
width: "100%",
|
||||
marginBottom: "8px"
|
||||
};
|
||||
|
||||
const resultBoxStyle = {
|
||||
fontSize: "9px",
|
||||
padding: "4px",
|
||||
background: "#2a2a2a",
|
||||
color: "#ccc",
|
||||
border: "1px solid #444",
|
||||
borderRadius: "2px",
|
||||
width: "100%"
|
||||
};
|
||||
|
||||
export default {
|
||||
type: "MathNode",
|
||||
label: "Math Operation",
|
||||
description: `
|
||||
Perform Math on Aggregated Inputs
|
||||
|
||||
- A and B groups are independently summed
|
||||
- Performs: Add, Subtract, Multiply, Divide, or Average
|
||||
- Result = A <op> B
|
||||
- Emits result via BorealisValueBus every update tick
|
||||
`.trim(),
|
||||
content: "Perform Math Operations",
|
||||
component: MathNode
|
||||
};
|
Reference in New Issue
Block a user