Added Various API Functionality

This commit is contained in:
2025-05-10 16:23:32 -06:00
parent 6ff4170894
commit bce39d355f
14 changed files with 538 additions and 1 deletions

View File

@ -0,0 +1,143 @@
////////// PROJECT FILE SEPARATION LINE ////////// CODE AFTER THIS LINE ARE FROM: Node_Array_Index_Extractor.jsx
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 ArrayIndexExtractorNode = ({ id, data }) => {
const edges = useStore((state) => state.edges);
const { setNodes } = useReactFlow();
const [lineNumber, setLineNumber] = useState(data?.lineNumber || 1);
const [result, setResult] = useState("Line Does Not Exist");
const valueRef = useRef(result);
const handleLineNumberChange = (e) => {
const num = parseInt(e.target.value, 10);
const clamped = isNaN(num) ? 1 : Math.max(1, num);
setLineNumber(clamped);
setNodes((nds) =>
nds.map((n) =>
n.id === id ? { ...n, data: { ...n.data, lineNumber: clamped } } : n
)
);
};
useEffect(() => {
let intervalId = null;
let currentRate = window.BorealisUpdateRate;
const runNodeLogic = () => {
const inputEdge = edges.find((e) => e.target === id);
if (!inputEdge) {
valueRef.current = "Line Does Not Exist";
setResult("Line Does Not Exist");
window.BorealisValueBus[id] = "Line Does Not Exist";
return;
}
const upstreamValue = window.BorealisValueBus[inputEdge.source];
if (!Array.isArray(upstreamValue)) {
valueRef.current = "Line Does Not Exist";
setResult("Line Does Not Exist");
window.BorealisValueBus[id] = "Line Does Not Exist";
return;
}
const index = Math.max(0, lineNumber - 1); // Convert 1-based input to 0-based
const selected = upstreamValue[index] ?? "Line Does Not Exist";
if (selected !== valueRef.current) {
valueRef.current = selected;
setResult(selected);
window.BorealisValueBus[id] = selected;
}
};
intervalId = setInterval(runNodeLogic, currentRate);
const monitor = setInterval(() => {
const newRate = window.BorealisUpdateRate;
if (newRate !== currentRate) {
clearInterval(intervalId);
currentRate = newRate;
intervalId = setInterval(runNodeLogic, currentRate);
}
}, 300);
return () => {
clearInterval(intervalId);
clearInterval(monitor);
};
}, [id, edges, lineNumber]);
return (
<div className="borealis-node">
<Handle type="target" position={Position.Left} className="borealis-handle" />
<div className="borealis-node-header">Array Index Extractor</div>
<div className="borealis-node-content" style={{ fontSize: "9px" }}>
<div style={{ marginBottom: "6px", color: "#ccc" }}>
Output a Specific Array Index's Value
</div>
<label style={{ display: "block", marginBottom: "2px" }}>
Line Number (1 = First Line):
</label>
<input
type="number"
min="1"
step="1"
value={lineNumber}
onChange={handleLineNumberChange}
style={{
width: "100%",
fontSize: "9px",
background: "#1e1e1e",
color: "#ccc",
border: "1px solid #444",
borderRadius: "2px",
padding: "3px",
marginBottom: "6px"
}}
/>
<label style={{ display: "block", marginBottom: "2px" }}>
Output:
</label>
<input
type="text"
value={result}
disabled
style={{
width: "100%",
fontSize: "9px",
background: "#2a2a2a",
color: "#ccc",
border: "1px solid #444",
borderRadius: "2px",
padding: "3px"
}}
/>
</div>
<Handle type="source" position={Position.Right} className="borealis-handle" />
</div>
);
};
export default {
type: "ArrayIndexExtractor",
label: "Array Index Extractor",
description: `
Outputs a specific line from an upstream array (e.g., OCR multi-line output).
- User specifies the line number (1-based index)
- Outputs the value from that line if it exists
- If the index is out of bounds, outputs "Line Does Not Exist"
`.trim(),
content: "Output a Specific Array Index's Value",
component: ArrayIndexExtractorNode
};

View File

@ -0,0 +1,179 @@
////////// PROJECT FILE SEPARATION LINE ////////// CODE AFTER THIS LINE ARE FROM: <ProjectRoot>/Data/WebUI/src/nodes/Data Analysis & Manipulation/Node_JSON_Display.jsx
import React, { useEffect, useState, useRef, useCallback } from "react";
import { Handle, Position, useReactFlow, useStore } from "reactflow";
// For syntax highlighting, ensure prismjs is installed: npm install prismjs
import Prism from "prismjs";
import "prismjs/components/prism-json";
import "prismjs/themes/prism-okaidia.css";
const JSONPrettyDisplayNode = ({ id, data }) => {
const { setNodes } = useReactFlow();
const edges = useStore((state) => state.edges);
const containerRef = useRef(null);
const resizingRef = useRef(false);
const startPosRef = useRef({ x: 0, y: 0 });
const startDimRef = useRef({ width: 0, height: 0 });
const [jsonData, setJsonData] = useState(data?.jsonData || {});
const initW = parseInt(data?.width || "300", 10);
const initH = parseInt(data?.height || "150", 10);
const [dimensions, setDimensions] = useState({ width: initW, height: initH });
const jsonRef = useRef(jsonData);
const persistDimensions = useCallback(() => {
const w = `${Math.round(dimensions.width)}px`;
const h = `${Math.round(dimensions.height)}px`;
setNodes((nds) =>
nds.map((n) =>
n.id === id
? { ...n, data: { ...n.data, width: w, height: h } }
: n
)
);
}, [dimensions, id, setNodes]);
useEffect(() => {
const onMouseMove = (e) => {
if (!resizingRef.current) return;
const dx = e.clientX - startPosRef.current.x;
const dy = e.clientY - startPosRef.current.y;
setDimensions({
width: Math.max(100, startDimRef.current.width + dx),
height: Math.max(60, startDimRef.current.height + dy)
});
};
const onMouseUp = () => {
if (resizingRef.current) {
resizingRef.current = false;
persistDimensions();
}
};
window.addEventListener("mousemove", onMouseMove);
window.addEventListener("mouseup", onMouseUp);
return () => {
window.removeEventListener("mousemove", onMouseMove);
window.removeEventListener("mouseup", onMouseUp);
};
}, [persistDimensions]);
const onResizeMouseDown = (e) => {
e.stopPropagation();
resizingRef.current = true;
startPosRef.current = { x: e.clientX, y: e.clientY };
startDimRef.current = { ...dimensions };
};
useEffect(() => {
let rate = window.BorealisUpdateRate;
const tick = () => {
const edge = edges.find((e) => e.target === id);
if (edge && edge.source) {
const upstream = window.BorealisValueBus[edge.source];
if (typeof upstream === "object") {
if (JSON.stringify(upstream) !== JSON.stringify(jsonRef.current)) {
jsonRef.current = upstream;
setJsonData(upstream);
window.BorealisValueBus[id] = upstream;
setNodes((nds) =>
nds.map((n) =>
n.id === id ? { ...n, data: { ...n.data, jsonData: upstream } } : n
)
);
}
}
} else {
window.BorealisValueBus[id] = jsonRef.current;
}
};
const iv = setInterval(tick, rate);
const monitor = setInterval(() => {
if (window.BorealisUpdateRate !== rate) {
clearInterval(iv);
clearInterval(monitor);
}
}, 200);
return () => { clearInterval(iv); clearInterval(monitor); };
}, [id, edges, setNodes]);
// Generate highlighted HTML
const pretty = JSON.stringify(jsonData, null, 2);
const highlighted = Prism.highlight(pretty, Prism.languages.json, "json");
return (
<div
ref={containerRef}
className="borealis-node"
style={{
display: "flex",
flexDirection: "column",
width: dimensions.width,
height: dimensions.height,
overflow: "visible",
position: "relative",
boxSizing: "border-box"
}}
>
<Handle type="target" position={Position.Left} className="borealis-handle" />
<Handle type="source" position={Position.Right} className="borealis-handle" />
<div className="borealis-node-header">Display JSON Data</div>
<div
className="borealis-node-content"
style={{
flex: 1,
padding: "4px",
fontSize: "9px",
color: "#ccc",
display: "flex",
flexDirection: "column",
overflow: "hidden"
}}
>
<div style={{ marginBottom: "4px" }}>
Display prettified JSON from upstream.
</div>
<div
style={{
flex: 1,
width: "100%",
background: "#1e1e1e",
border: "1px solid #444",
borderRadius: "2px",
padding: "4px",
overflowY: "auto",
fontFamily: "monospace",
fontSize: "9px"
}}
>
<pre
dangerouslySetInnerHTML={{ __html: highlighted }}
style={{ margin: 0 }}
/>
</div>
</div>
<div
onMouseDown={onResizeMouseDown}
style={{
position: "absolute",
width: "20px",
height: "20px",
right: "-4px",
bottom: "-4px",
cursor: "nwse-resize",
background: "transparent",
zIndex: 10
}}
/>
</div>
);
};
export default {
type: "Node_JSON_Pretty_Display",
label: "Display JSON Data",
description: "Display upstream JSON object as prettified JSON with syntax highlighting.",
content: "Display prettified multi-line JSON from upstream node.",
component: JSONPrettyDisplayNode
};

View File

@ -0,0 +1,132 @@
////////// PROJECT FILE SEPARATION LINE ////////// CODE AFTER THIS LINE ARE FROM: <ProjectRoot>/Data/WebUI/src/nodes/Data Analysis & Manipulation/Node_JSON_Value_Extractor.jsx
import React, { useState, useEffect } from "react";
import { Handle, Position, useReactFlow } from "reactflow";
const JSONValueExtractorNode = ({ id, data }) => {
const { setNodes, getEdges } = useReactFlow();
const [keyName, setKeyName] = useState(data?.keyName || "");
const [value, setValue] = useState(data?.result || "");
const handleKeyChange = (e) => {
const newKey = e.target.value;
setKeyName(newKey);
setNodes((nds) =>
nds.map((n) =>
n.id === id
? { ...n, data: { ...n.data, keyName: newKey } }
: n
)
);
};
useEffect(() => {
let currentRate = window.BorealisUpdateRate;
let intervalId;
const runNodeLogic = () => {
const edges = getEdges();
const incoming = edges.filter((e) => e.target === id);
const sourceId = incoming[0]?.source;
let newValue = "Key Not Found";
if (sourceId && window.BorealisValueBus[sourceId] !== undefined) {
let upstream = window.BorealisValueBus[sourceId];
if (upstream && typeof upstream === "object" && keyName) {
const pathSegments = keyName.split(".");
let nodeVal = upstream;
for (let segment of pathSegments) {
if (
nodeVal != null &&
(typeof nodeVal === "object" || Array.isArray(nodeVal)) &&
segment in nodeVal
) {
nodeVal = nodeVal[segment];
} else {
nodeVal = undefined;
break;
}
}
if (nodeVal !== undefined) {
newValue = String(nodeVal);
}
}
}
if (newValue !== value) {
setValue(newValue);
window.BorealisValueBus[id] = newValue;
setNodes((nds) =>
nds.map((n) =>
n.id === id
? { ...n, data: { ...n.data, result: newValue } }
: n
)
);
}
};
runNodeLogic();
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);
};
}, [keyName, id, setNodes, getEdges, value]);
return (
<div className="borealis-node">
<div className="borealis-node-header">JSON Value Extractor</div>
<div className="borealis-node-content">
<label style={{ fontSize: "9px", display: "block", marginBottom: "4px" }}>
Key:
</label>
<input
type="text"
value={keyName}
onChange={handleKeyChange}
placeholder="e.g. name.en"
style={{
fontSize: "9px", padding: "4px", width: "100%",
background: "#1e1e1e", color: "#ccc",
border: "1px solid #444", borderRadius: "2px"
}}
/>
<label style={{ fontSize: "9px", display: "block", margin: "8px 0 4px" }}>
Value:
</label>
<textarea
readOnly
value={value}
rows={2}
style={{
fontSize: "9px", padding: "4px", width: "100%",
background: "#1e1e1e", color: "#ccc",
border: "1px solid #444", borderRadius: "2px",
resize: "none"
}}
/>
</div>
<Handle type="target" position={Position.Left} className="borealis-handle" />
<Handle type="source" position={Position.Right} className="borealis-handle" />
</div>
);
};
export default {
type: "JSON_Value_Extractor",
label: "JSON Value Extractor",
description: "Extract a nested value by dot-delimited path from upstream JSON data.",
content: "Provide a dot-separated key path (e.g. 'name.en'); outputs the extracted string or 'Key Not Found'.",
component: JSONValueExtractorNode
};

View File

@ -0,0 +1,259 @@
////////// PROJECT FILE SEPARATION LINE ////////// CODE AFTER THIS LINE ARE FROM: Node_OCR_Text_Extraction.jsx
import React, { useEffect, useState, useRef } from "react";
import { Handle, Position, useReactFlow, useStore } from "reactflow";
// Base64 comparison using hash (lightweight)
const getHashScore = (str = "") => {
let hash = 0;
for (let i = 0; i < str.length; i += 101) {
const char = str.charCodeAt(i);
hash = (hash << 5) - hash + char;
hash |= 0;
}
return Math.abs(hash);
};
if (!window.BorealisValueBus) window.BorealisValueBus = {};
if (!window.BorealisUpdateRate) window.BorealisUpdateRate = 100;
const OCRNode = ({ id, data }) => {
const edges = useStore((state) => state.edges);
const { setNodes } = useReactFlow();
const [ocrOutput, setOcrOutput] = useState("");
const [engine, setEngine] = useState(data?.engine || "None");
const [backend, setBackend] = useState(data?.backend || "CPU");
const [dataType, setDataType] = useState(data?.dataType || "Mixed");
const [customRateEnabled, setCustomRateEnabled] = useState(data?.customRateEnabled ?? true);
const [customRateMs, setCustomRateMs] = useState(data?.customRateMs || 1000);
const [changeThreshold, setChangeThreshold] = useState(data?.changeThreshold || 0);
const valueRef = useRef("");
const lastUsed = useRef({ engine: "", backend: "", dataType: "" });
const lastProcessedAt = useRef(0);
const lastImageHash = useRef(0);
// Sync updated settings back into node.data for persistence
useEffect(() => {
setNodes((nodes) =>
nodes.map((n) =>
n.id === id
? {
...n,
data: {
...n.data,
engine,
backend,
dataType,
customRateEnabled,
customRateMs,
changeThreshold
}
}
: n
)
);
}, [engine, backend, dataType, customRateEnabled, customRateMs, changeThreshold]);
const sendToOCRAPI = async (base64) => {
const cleanBase64 = base64.replace(/^data:image\/[a-zA-Z]+;base64,/, "");
try {
const response = await fetch("/api/ocr", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ image_base64: cleanBase64, engine, backend })
});
const result = await response.json();
return response.ok && Array.isArray(result.lines)
? result.lines
: [`[ERROR] ${result.error || "Invalid OCR response."}`];
} catch (err) {
return [`[ERROR] OCR API request failed: ${err.message}`];
}
};
const filterLines = (lines) => {
if (dataType === "Numerical") {
return lines.map(line => line.replace(/[^\d.%\s]/g, '').replace(/\s+/g, ' ').trim()).filter(Boolean);
}
if (dataType === "String") {
return lines.map(line => line.replace(/[^a-zA-Z\s]/g, '').replace(/\s+/g, ' ').trim()).filter(Boolean);
}
return lines;
};
useEffect(() => {
let intervalId = null;
let currentRate = window.BorealisUpdateRate || 100;
const runNodeLogic = async () => {
const inputEdge = edges.find((e) => e.target === id);
if (!inputEdge) {
window.BorealisValueBus[id] = [];
setOcrOutput("");
return;
}
const upstreamValue = window.BorealisValueBus[inputEdge.source] || "";
const now = Date.now();
const effectiveRate = customRateEnabled ? customRateMs : window.BorealisUpdateRate || 100;
const configChanged =
lastUsed.current.engine !== engine ||
lastUsed.current.backend !== backend ||
lastUsed.current.dataType !== dataType;
const upstreamHash = getHashScore(upstreamValue);
const hashDelta = Math.abs(upstreamHash - lastImageHash.current);
const hashThreshold = (changeThreshold / 100) * 1000000000;
const imageChanged = hashDelta > hashThreshold;
if (!configChanged && (!imageChanged || (now - lastProcessedAt.current < effectiveRate))) return;
lastUsed.current = { engine, backend, dataType };
lastProcessedAt.current = now;
lastImageHash.current = upstreamHash;
valueRef.current = upstreamValue;
const lines = await sendToOCRAPI(upstreamValue);
const filtered = filterLines(lines);
setOcrOutput(filtered.join("\n"));
window.BorealisValueBus[id] = filtered;
};
intervalId = setInterval(runNodeLogic, currentRate);
const monitor = setInterval(() => {
const newRate = window.BorealisUpdateRate || 100;
if (newRate !== currentRate) {
clearInterval(intervalId);
intervalId = setInterval(runNodeLogic, newRate);
currentRate = newRate;
}
}, 300);
return () => {
clearInterval(intervalId);
clearInterval(monitor);
};
}, [id, engine, backend, dataType, customRateEnabled, customRateMs, changeThreshold, edges]);
return (
<div className="borealis-node" style={{ minWidth: "200px" }}>
<Handle type="target" position={Position.Left} className="borealis-handle" />
<div className="borealis-node-header">OCR-Based Text Extraction</div>
<div className="borealis-node-content">
<div style={{ fontSize: "9px", marginBottom: "8px", color: "#ccc" }}>
Extract Multi-Line Text from Upstream Image Node
</div>
<label style={labelStyle}>OCR Engine:</label>
<select value={engine} onChange={(e) => setEngine(e.target.value)} style={dropdownStyle}>
<option value="None">None</option>
<option value="TesseractOCR">TesseractOCR</option>
<option value="EasyOCR">EasyOCR</option>
</select>
<label style={labelStyle}>Compute:</label>
<select value={backend} onChange={(e) => setBackend(e.target.value)} style={dropdownStyle} disabled={engine === "None"}>
<option value="CPU">CPU</option>
<option value="GPU">GPU</option>
</select>
<label style={labelStyle}>Data Type:</label>
<select value={dataType} onChange={(e) => setDataType(e.target.value)} style={dropdownStyle}>
<option value="Mixed">Mixed Data</option>
<option value="Numerical">Numerical Data</option>
<option value="String">String Data</option>
</select>
<label style={labelStyle}>Custom API Rate-Limit (ms):</label>
<div style={{ display: "flex", alignItems: "center", marginBottom: "8px" }}>
<input
type="checkbox"
checked={customRateEnabled}
onChange={(e) => setCustomRateEnabled(e.target.checked)}
style={{ marginRight: "8px" }}
/>
<input
type="number"
min="100"
step="100"
value={customRateMs}
onChange={(e) => setCustomRateMs(Number(e.target.value))}
disabled={!customRateEnabled}
style={numberInputStyle}
/>
</div>
<label style={labelStyle}>Change Detection Sensitivity Threshold:</label>
<input
type="number"
min="0"
max="100"
step="1"
value={changeThreshold}
onChange={(e) => setChangeThreshold(Number(e.target.value))}
style={numberInputStyle}
/>
<label style={labelStyle}>OCR Output:</label>
<textarea
readOnly
value={ocrOutput}
rows={6}
style={{
width: "100%",
fontSize: "9px",
background: "#1e1e1e",
color: "#ccc",
border: "1px solid #444",
borderRadius: "2px",
padding: "4px",
resize: "vertical"
}}
/>
</div>
<Handle type="source" position={Position.Right} className="borealis-handle" />
</div>
);
};
const labelStyle = {
fontSize: "9px",
display: "block",
marginTop: "6px",
marginBottom: "2px"
};
const dropdownStyle = {
width: "100%",
fontSize: "9px",
padding: "4px",
background: "#1e1e1e",
color: "#ccc",
border: "1px solid #444",
borderRadius: "2px"
};
const numberInputStyle = {
width: "80px",
fontSize: "9px",
background: "#1e1e1e",
color: "#ccc",
border: "1px solid #444",
borderRadius: "2px",
padding: "2px 4px",
marginBottom: "8px"
};
export default {
type: "OCR_Text_Extraction",
label: "OCR Text Extraction",
description: `Extract text from upstream image using backend OCR engine via API. Includes rate limiting and sensitivity detection for smart processing.`,
content: "Extract Multi-Line Text from Upstream Image Node",
component: OCRNode
};

View File

@ -0,0 +1,201 @@
////////// PROJECT FILE SEPARATION LINE ////////// CODE AFTER THIS LINE ARE FROM: <ProjectRoot>/Data/WebUI/src/nodes/Data Manipulation/Node_Regex_Replace.jsx
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 RegexReplaceNode = ({ id, data }) => {
const edges = useStore((state) => state.edges);
const { setNodes } = useReactFlow();
const [pattern, setPattern] = useState(data?.pattern || "");
const [replacement, setReplacement] = useState(data?.replacement || "");
const [flags, setFlags] = useState(data?.flags || "g");
const [enabled, setEnabled] = useState(data?.enabled ?? true);
const [result, setResult] = useState("");
const [original, setOriginal] = useState("");
const valueRef = useRef("");
const updateNodeData = (key, val) => {
setNodes(nds =>
nds.map(n =>
n.id === id ? { ...n, data: { ...n.data, [key]: val } } : n
)
);
};
useEffect(() => {
let intervalId = null;
let currentRate = window.BorealisUpdateRate;
const runNodeLogic = () => {
const inputEdge = edges.find((e) => e.target === id);
const inputValue = inputEdge
? window.BorealisValueBus[inputEdge.source] || ""
: "";
setOriginal(inputValue);
let newVal = inputValue;
try {
if (enabled && pattern) {
const regex = new RegExp(pattern, flags);
let safeReplacement = replacement.trim();
if (
safeReplacement.startsWith('"') &&
safeReplacement.endsWith('"')
) {
safeReplacement = safeReplacement.slice(1, -1);
}
newVal = inputValue.replace(regex, safeReplacement);
}
} catch (err) {
newVal = `[Error] ${err.message}`;
}
if (newVal !== valueRef.current) {
valueRef.current = newVal;
setResult(newVal);
window.BorealisValueBus[id] = newVal;
}
};
intervalId = setInterval(runNodeLogic, currentRate);
const monitor = setInterval(() => {
const newRate = window.BorealisUpdateRate;
if (newRate !== currentRate) {
clearInterval(intervalId);
intervalId = setInterval(runNodeLogic, newRate);
currentRate = newRate;
}
}, 300);
return () => {
clearInterval(intervalId);
clearInterval(monitor);
};
}, [id, edges, pattern, replacement, flags, enabled]);
return (
<div className="borealis-node">
<Handle type="target" position={Position.Left} className="borealis-handle" />
<div className="borealis-node-header">Regex Replace</div>
<div className="borealis-node-content">
<div style={{ marginBottom: "6px", fontSize: "9px", color: "#ccc" }}>
Perform regex replacement on upstream string
</div>
<label>Regex Pattern:</label>
<input
type="text"
value={pattern}
onChange={(e) => {
setPattern(e.target.value);
updateNodeData("pattern", e.target.value);
}}
placeholder="e.g. \\d+"
style={inputStyle}
/>
<label>Replacement:</label>
<input
type="text"
value={replacement}
onChange={(e) => {
setReplacement(e.target.value);
updateNodeData("replacement", e.target.value);
}}
placeholder="e.g. $1"
style={inputStyle}
/>
<label>Regex Flags:</label>
<input
type="text"
value={flags}
onChange={(e) => {
setFlags(e.target.value);
updateNodeData("flags", e.target.value);
}}
placeholder="e.g. gi"
style={inputStyle}
/>
<div style={{ margin: "6px 0" }}>
<label>
<input
type="checkbox"
checked={enabled}
onChange={(e) => {
setEnabled(e.target.checked);
updateNodeData("enabled", e.target.checked);
}}
style={{ marginRight: "6px" }}
/>
Enable Replacement
</label>
</div>
<label>Original Input:</label>
<textarea
readOnly
value={original}
rows={2}
style={textAreaStyle}
/>
<label>Output:</label>
<textarea
readOnly
value={result}
rows={2}
style={textAreaStyle}
/>
</div>
<Handle type="source" position={Position.Right} className="borealis-handle" />
</div>
);
};
const inputStyle = {
width: "100%",
fontSize: "9px",
background: "#1e1e1e",
color: "#ccc",
border: "1px solid #444",
borderRadius: "2px",
padding: "3px",
marginBottom: "6px"
};
const textAreaStyle = {
width: "100%",
fontSize: "9px",
background: "#2a2a2a",
color: "#ccc",
border: "1px solid #444",
borderRadius: "2px",
padding: "3px",
resize: "vertical",
marginBottom: "6px"
};
export default {
type: "RegexReplace",
label: "Regex Replacer",
description: `
Enhanced Regex Replacer:
- Add regex flags (g, i, m, etc)
- Live preview of input vs output
- Optional enable toggle for replacement logic
`.trim(),
content: "Perform regex replacement on upstream string",
component: RegexReplaceNode
};

View File

@ -0,0 +1,124 @@
////////// PROJECT FILE SEPARATION LINE ////////// CODE AFTER THIS LINE ARE FROM: <ProjectRoot>/Data/WebUI/src/nodes/Data Analysis/Node_Regex_Search.jsx
import React, { useEffect, useState, useRef } from "react";
import { Handle, Position, useReactFlow, useStore } from "reactflow";
if (!window.BorealisValueBus) window.BorealisValueBus = {};
if (!window.BorealisUpdateRate) window.BorealisUpdateRate = 100;
const RegexSearchNode = ({ id, data }) => {
const edges = useStore((state) => state.edges);
const { setNodes } = useReactFlow();
const [pattern, setPattern] = useState(data?.pattern || "");
const [flags, setFlags] = useState(data?.flags || "i");
const valueRef = useRef("0");
const updateNodeData = (key, val) => {
setNodes(nds =>
nds.map(n =>
n.id === id ? { ...n, data: { ...n.data, [key]: val } } : n
)
);
};
useEffect(() => {
let intervalId = null;
let currentRate = window.BorealisUpdateRate;
const runNodeLogic = () => {
const inputEdge = edges.find((e) => e.target === id);
const inputVal = inputEdge ? window.BorealisValueBus[inputEdge.source] || "" : "";
let matched = false;
try {
if (pattern) {
const regex = new RegExp(pattern, flags);
matched = regex.test(inputVal);
}
} catch {
matched = false;
}
const result = matched ? "1" : "0";
if (result !== valueRef.current) {
valueRef.current = result;
window.BorealisValueBus[id] = result;
}
};
intervalId = setInterval(runNodeLogic, currentRate);
const monitor = setInterval(() => {
const newRate = window.BorealisUpdateRate;
if (newRate !== currentRate) {
clearInterval(intervalId);
intervalId = setInterval(runNodeLogic, newRate);
currentRate = newRate;
}
}, 300);
return () => {
clearInterval(intervalId);
clearInterval(monitor);
};
}, [id, edges, pattern, flags]);
return (
<div className="borealis-node">
<Handle type="target" position={Position.Left} className="borealis-handle" />
<div className="borealis-node-header">Regex Search</div>
<div className="borealis-node-content">
<label>Regex Pattern:</label>
<input
type="text"
value={pattern}
onChange={(e) => {
setPattern(e.target.value);
updateNodeData("pattern", e.target.value);
}}
placeholder="e.g. World"
style={inputStyle}
/>
<label>Regex Flags:</label>
<input
type="text"
value={flags}
onChange={(e) => {
setFlags(e.target.value);
updateNodeData("flags", e.target.value);
}}
placeholder="e.g. i"
style={inputStyle}
/>
</div>
<Handle type="source" position={Position.Right} className="borealis-handle" />
</div>
);
};
const inputStyle = {
width: "100%",
fontSize: "9px",
background: "#1e1e1e",
color: "#ccc",
border: "1px solid #444",
borderRadius: "2px",
padding: "3px",
marginBottom: "6px"
};
export default {
type: "RegexSearch",
label: "Regex Search",
description: `
Minimal RegEx Matcher:
- Accepts pattern and flags
- Outputs "1" if match is found, else "0"
- No visual output display
`.trim(),
content: "Outputs '1' if regex matches input, otherwise '0'",
component: RegexSearchNode
};

View File

@ -0,0 +1,190 @@
////////// PROJECT FILE SEPARATION LINE ////////// CODE AFTER THIS LINE ARE FROM: <ProjectRoot>/nodes/Data Analysis/Node_TextArray_Display.jsx
/**
* Display Multi-Line Array Node
* --------------------------------------------------
* A node to display upstream multi-line text arrays.
* Has one input edge on left and passthrough output on right.
* Custom drag-resize handle for width & height adjustments.
* Inner textarea scrolls vertically; container overflow visible.
*/
import React, { useEffect, useState, useRef, useCallback } from "react";
import { Handle, Position, useReactFlow, useStore } from "reactflow";
const TextArrayDisplayNode = ({ id, data }) => {
const { setNodes } = useReactFlow();
const edges = useStore((state) => state.edges);
const containerRef = useRef(null);
const resizingRef = useRef(false);
const startPosRef = useRef({ x: 0, y: 0 });
const startDimRef = useRef({ width: 0, height: 0 });
// Initialize lines and dimensions
const [lines, setLines] = useState(data?.lines || []);
const linesRef = useRef(lines);
const initW = parseInt(data?.width || "300", 10);
const initH = parseInt(data?.height || "150", 10);
const [dimensions, setDimensions] = useState({ width: initW, height: initH });
// Persist dimensions to node data
const persistDimensions = useCallback(() => {
const w = `${Math.round(dimensions.width)}px`;
const h = `${Math.round(dimensions.height)}px`;
setNodes((nds) =>
nds.map((n) =>
n.id === id
? { ...n, data: { ...n.data, width: w, height: h } }
: n
)
);
}, [dimensions, id, setNodes]);
// Mouse handlers for custom resize
useEffect(() => {
const onMouseMove = (e) => {
if (!resizingRef.current) return;
const dx = e.clientX - startPosRef.current.x;
const dy = e.clientY - startPosRef.current.y;
setDimensions({
width: Math.max(100, startDimRef.current.width + dx),
height: Math.max(60, startDimRef.current.height + dy)
});
};
const onMouseUp = () => {
if (resizingRef.current) {
resizingRef.current = false;
persistDimensions();
}
};
window.addEventListener("mousemove", onMouseMove);
window.addEventListener("mouseup", onMouseUp);
return () => {
window.removeEventListener("mousemove", onMouseMove);
window.removeEventListener("mouseup", onMouseUp);
};
}, [persistDimensions]);
// Start drag
const onResizeMouseDown = (e) => {
e.stopPropagation();
resizingRef.current = true;
startPosRef.current = { x: e.clientX, y: e.clientY };
startDimRef.current = { ...dimensions };
};
// Polling for upstream data
useEffect(() => {
let rate = window.BorealisUpdateRate;
const tick = () => {
const edge = edges.find((e) => e.target === id);
if (edge && edge.source) {
const arr = window.BorealisValueBus[edge.source] || [];
if (JSON.stringify(arr) !== JSON.stringify(linesRef.current)) {
linesRef.current = arr;
setLines(arr);
window.BorealisValueBus[id] = arr;
setNodes((nds) =>
nds.map((n) =>
n.id === id ? { ...n, data: { ...n.data, lines: arr } } : n
)
);
}
} else {
window.BorealisValueBus[id] = linesRef.current;
}
};
const iv = setInterval(tick, rate);
const monitor = setInterval(() => {
if (window.BorealisUpdateRate !== rate) {
clearInterval(iv);
clearInterval(monitor);
}
}, 200);
return () => { clearInterval(iv); clearInterval(monitor); };
}, [id, edges, setNodes]);
return (
<div
ref={containerRef}
className="borealis-node"
style={{
display: "flex",
flexDirection: "column",
width: dimensions.width,
height: dimensions.height,
overflow: "visible",
position: "relative",
boxSizing: "border-box"
}}
>
{/* Connectors */}
<Handle type="target" position={Position.Left} className="borealis-handle" />
<Handle type="source" position={Position.Right} className="borealis-handle" />
{/* Header */}
<div className="borealis-node-header">
{data?.label || "Display Multi-Line Array"}
</div>
{/* Content */}
<div
className="borealis-node-content"
style={{
flex: 1,
padding: "4px",
fontSize: "9px",
color: "#ccc",
display: "flex",
flexDirection: "column",
overflow: "hidden"
}}
>
<div style={{ marginBottom: "4px" }}>
{data?.content || "Display upstream multi-line text arrays."}
</div>
<label style={{ marginBottom: "4px" }}>Upstream Text Data:</label>
<textarea
value={lines.join("\n")}
readOnly
style={{
flex: 1,
width: "100%",
fontSize: "9px",
background: "#1e1e1e",
color: "#ccc",
border: "1px solid #444",
borderRadius: "2px",
padding: "4px",
resize: "none",
overflowY: "auto",
boxSizing: "border-box"
}}
/>
</div>
{/* Invisible drag-resize handle */}
<div
onMouseDown={onResizeMouseDown}
style={{
position: "absolute",
width: "20px",
height: "20px",
right: "-4px",
bottom: "-4px",
cursor: "nwse-resize",
background: "transparent",
zIndex: 10
}}
/>
</div>
);
};
// Export node metadata
export default {
type: "Node_TextArray_Display",
label: "Display Multi-Line Array",
description: "Display upstream multi-line text arrays.",
content: "Display upstream multi-line text arrays.",
component: TextArrayDisplayNode
};