diff --git a/.gitignore b/.gitignore
index f417792..6f5d4e3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
.vs/
-Borealis-Workflow-Automation-Tool/
\ No newline at end of file
+Borealis-Workflow-Automation-Tool/
+Borealis-API-Collector-Agent/
\ No newline at end of file
diff --git a/Data/Agent/api-collector-agent.py b/Data/Agent/api-collector-agent.py
new file mode 100644
index 0000000..44ecb9a
--- /dev/null
+++ b/Data/Agent/api-collector-agent.py
@@ -0,0 +1,205 @@
+import sys
+import uuid
+import time
+import json
+import base64
+import threading
+import requests
+from io import BytesIO
+import socket
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+from PIL import ImageGrab
+
+# ---------------- Configuration ----------------
+SERVER_URL = "http://localhost:5000"
+CHECKIN_ENDPOINT = f"{SERVER_URL}/api/agent/checkin"
+CONFIG_ENDPOINT = f"{SERVER_URL}/api/agent/config"
+DATA_POST_ENDPOINT = f"{SERVER_URL}/api/agent/data"
+
+HOSTNAME = socket.gethostname().lower()
+RANDOM_SUFFIX = uuid.uuid4().hex[:8]
+AGENT_ID = f"{HOSTNAME}-agent-{RANDOM_SUFFIX}"
+
+CONFIG_POLL_INTERVAL = 5
+
+# ---------------- State ----------------
+app_instance = None
+region_widget = None
+capture_thread_started = False
+current_interval = 1000
+config_ready = threading.Event()
+overlay_visible = True
+
+# ---------------- Signal Bridge ----------------
+class RegionLauncher(QtCore.QObject):
+ trigger = QtCore.pyqtSignal(int, int, int, int)
+
+ def __init__(self):
+ super().__init__()
+ self.trigger.connect(self.handle)
+
+ def handle(self, x, y, w, h):
+ launch_region(x, y, w, h)
+
+region_launcher = None
+
+# ---------------- Agent Networking ----------------
+def check_in():
+ try:
+ requests.post(CHECKIN_ENDPOINT, json={"agent_id": AGENT_ID, "hostname": HOSTNAME})
+ print(f"[INFO] Agent ID: {AGENT_ID}")
+ except Exception as e:
+ print(f"[ERROR] Check-in failed: {e}")
+
+def poll_for_config():
+ try:
+ res = requests.get(CONFIG_ENDPOINT, params={"agent_id": AGENT_ID})
+ if res.status_code == 200:
+ return res.json()
+ except Exception as e:
+ print(f"[ERROR] Config polling failed: {e}")
+ return None
+
+def send_image_data(image):
+ try:
+ buffer = BytesIO()
+ image.save(buffer, format="PNG")
+ encoded = base64.b64encode(buffer.getvalue()).decode("utf-8")
+
+ response = requests.post(DATA_POST_ENDPOINT, json={
+ "agent_id": AGENT_ID,
+ "type": "screenshot",
+ "image_base64": encoded
+ })
+
+ if response.status_code != 200:
+ print(f"[ERROR] Screenshot POST failed: {response.status_code} - {response.text}")
+ except Exception as e:
+ print(f"[ERROR] Failed to send image: {e}")
+
+# ---------------- Region Overlay ----------------
+class ScreenshotRegion(QtWidgets.QWidget):
+ def __init__(self, x=100, y=100, w=300, h=200):
+ super().__init__()
+ self.setGeometry(x, y, w, h)
+ self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint)
+ self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
+ self.drag_offset = None
+ self.resizing = False
+ self.resize_handle_size = 12
+ self.setVisible(True)
+
+ self.label = QtWidgets.QLabel(self)
+ self.label.setText(AGENT_ID)
+ self.label.setStyleSheet("color: lime; background: transparent; font-size: 10px;")
+ self.label.move(8, 4)
+
+ self.setMouseTracking(True)
+
+ def paintEvent(self, event):
+ painter = QtGui.QPainter(self)
+ painter.setRenderHint(QtGui.QPainter.Antialiasing)
+
+ # Transparent fill
+ painter.setBrush(QtCore.Qt.transparent)
+ painter.setPen(QtGui.QPen(QtGui.QColor(0, 255, 0), 2))
+ painter.drawRect(self.rect())
+
+ # Resize Handle Visual (Bottom-Right)
+ handle_rect = QtCore.QRect(
+ self.width() - self.resize_handle_size,
+ self.height() - self.resize_handle_size,
+ self.resize_handle_size,
+ self.resize_handle_size
+ )
+ painter.fillRect(handle_rect, QtGui.QColor(0, 255, 0))
+
+ def mousePressEvent(self, event):
+ if event.button() == QtCore.Qt.LeftButton:
+ if (event.pos().x() > self.width() - self.resize_handle_size and
+ event.pos().y() > self.height() - self.resize_handle_size):
+ self.resizing = True
+ else:
+ self.drag_offset = event.globalPos() - self.frameGeometry().topLeft()
+
+ def mouseMoveEvent(self, event):
+ if self.resizing:
+ new_width = max(event.pos().x(), 100)
+ new_height = max(event.pos().y(), 80)
+ self.resize(new_width, new_height)
+ elif event.buttons() & QtCore.Qt.LeftButton and self.drag_offset:
+ self.move(event.globalPos() - self.drag_offset)
+
+ def mouseReleaseEvent(self, event):
+ self.resizing = False
+ self.drag_offset = None
+
+ def get_geometry(self):
+ geo = self.geometry()
+ return geo.x(), geo.y(), geo.width(), geo.height()
+
+
+# ---------------- Threads ----------------
+def capture_loop():
+ global current_interval
+ print("[INFO] Screenshot capture loop started")
+ config_ready.wait()
+ while region_widget is None:
+ print("[WAIT] Waiting for region widget to initialize...")
+ time.sleep(0.2)
+
+ print(f"[INFO] Agent Capturing Region: x:{region_widget.x()} y:{region_widget.y()} w:{region_widget.width()} h:{region_widget.height()}")
+
+ while True:
+ if overlay_visible:
+ x, y, w, h = region_widget.get_geometry()
+ try:
+ img = ImageGrab.grab(bbox=(x, y, x + w, y + h))
+ send_image_data(img)
+ except Exception as e:
+ print(f"[ERROR] Screenshot error: {e}")
+ time.sleep(current_interval / 1000)
+
+def config_loop():
+ global region_widget, capture_thread_started, current_interval, overlay_visible
+ check_in()
+ while True:
+ config = poll_for_config()
+ if config and config.get("task") == "screenshot":
+ print("[PROVISIONING] Agent Provisioning Command Issued by Borealis")
+ x = config.get("x", 100)
+ y = config.get("y", 100)
+ w = config.get("w", 300)
+ h = config.get("h", 200)
+ current_interval = config.get("interval", 1000)
+ overlay_visible = config.get("visible", True)
+
+ print(f"[PROVISIONING] Agent Configured as \"Screenshot\" Collector w/ Polling Rate of <{current_interval/1000:.1f}s>")
+
+ if not region_widget:
+ region_launcher.trigger.emit(x, y, w, h)
+ elif region_widget:
+ region_widget.setVisible(overlay_visible)
+
+ if not capture_thread_started:
+ threading.Thread(target=capture_loop, daemon=True).start()
+ capture_thread_started = True
+
+ config_ready.set()
+ time.sleep(CONFIG_POLL_INTERVAL)
+
+def launch_region(x, y, w, h):
+ global region_widget
+ if region_widget:
+ return
+ print(f"[INFO] Agent Starting...")
+ region_widget = ScreenshotRegion(x, y, w, h)
+ region_widget.show()
+
+# ---------------- Main ----------------
+if __name__ == "__main__":
+ app_instance = QtWidgets.QApplication(sys.argv)
+ region_launcher = RegionLauncher()
+ threading.Thread(target=config_loop, daemon=True).start()
+ sys.exit(app_instance.exec_())
diff --git a/Data/Agent/requirements.txt b/Data/Agent/requirements.txt
new file mode 100644
index 0000000..883322d
--- /dev/null
+++ b/Data/Agent/requirements.txt
@@ -0,0 +1,3 @@
+requests
+PyQt5
+Pillow
diff --git a/Data/WebUI/src/App.jsx b/Data/WebUI/src/App.jsx
index 6fe5ad7..c27c82e 100644
--- a/Data/WebUI/src/App.jsx
+++ b/Data/WebUI/src/App.jsx
@@ -451,7 +451,7 @@ export default function App() {
}}
sx={{ color: "#58a6ff", borderColor: "#58a6ff", fontSize: "0.75rem", textTransform: "none", px: 1.5 }}
>
- Update Rate
+ Apply Rate
diff --git a/Data/WebUI/src/Borealis.css b/Data/WebUI/src/Borealis.css
index a868053..2ae9b27 100644
--- a/Data/WebUI/src/Borealis.css
+++ b/Data/WebUI/src/Borealis.css
@@ -61,4 +61,18 @@
background: #58a6ff;
width: 10px;
height: 10px;
-}
\ No newline at end of file
+}
+
+/* Global dark form inputs */
+input, select, button {
+ background-color: #2a2a2a;
+ color: #ccc;
+ border: 1px solid #444;
+ font-size: 10px;
+}
+
+/* Label / Dark Text styling */
+label {
+ color: #aaa;
+ font-size: 10px;
+}
diff --git a/Data/WebUI/src/nodes/Data Collection/API_Data_Collector.jsx b/Data/WebUI/src/nodes/Data Collection/API_Data_Collector.jsx
new file mode 100644
index 0000000..9cdd2ab
--- /dev/null
+++ b/Data/WebUI/src/nodes/Data Collection/API_Data_Collector.jsx
@@ -0,0 +1,177 @@
+import React, { useEffect, useState } from "react";
+import { Handle, Position, useReactFlow } from "reactflow";
+
+const APINode = ({ id, data }) => {
+ const { setNodes } = useReactFlow();
+ const [agents, setAgents] = useState([]);
+ const [selectedAgent, setSelectedAgent] = useState(data.agent_id || "");
+ const [selectedType, setSelectedType] = useState(data.data_type || "screenshot");
+ const [imageData, setImageData] = useState("");
+ const [intervalMs, setIntervalMs] = useState(data.interval || 1000);
+ const [paused, setPaused] = useState(false);
+ const [overlayVisible, setOverlayVisible] = useState(true);
+
+ // Refresh agents every 5s
+ useEffect(() => {
+ const fetchAgents = () => fetch("/api/agents").then(res => res.json()).then(setAgents);
+ fetchAgents();
+ const interval = setInterval(fetchAgents, 5000);
+ return () => clearInterval(interval);
+ }, []);
+
+ // Pull image if agent provisioned
+ useEffect(() => {
+ if (!selectedAgent || paused) return;
+ const interval = setInterval(() => {
+ fetch(`/api/agent/image?agent_id=${selectedAgent}`)
+ .then(res => res.json())
+ .then(json => {
+ if (json.image_base64) {
+ setImageData(json.image_base64);
+ window.BorealisValueBus = window.BorealisValueBus || {};
+ window.BorealisValueBus[id] = json.image_base64;
+ }
+ })
+ .catch(() => { });
+ }, intervalMs);
+ return () => clearInterval(interval);
+ }, [selectedAgent, id, paused, intervalMs]);
+
+ const provisionAgent = () => {
+ if (!selectedAgent) return;
+ fetch("/api/agent/provision", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ agent_id: selectedAgent,
+ x: 250,
+ y: 100,
+ w: 300,
+ h: 200,
+ interval: intervalMs,
+ visible: overlayVisible,
+ task: selectedType
+ })
+ }).then(() => {
+ setNodes(nds =>
+ nds.map(n => n.id === id
+ ? {
+ ...n,
+ data: {
+ ...n.data,
+ agent_id: selectedAgent,
+ data_type: selectedType,
+ interval: intervalMs
+ }
+ }
+ : n
+ )
+ );
+ });
+ };
+
+ const resetAgent = () => {
+ if (!selectedAgent) return;
+ fetch("/api/agent/reset", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ agent_id: selectedAgent })
+ }).then(() => {
+ setSelectedAgent("");
+ });
+ };
+
+ const toggleOverlay = () => {
+ const newVisibility = !overlayVisible;
+ setOverlayVisible(newVisibility);
+ if (selectedAgent) {
+ fetch("/api/agent/overlay_visibility", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ agent_id: selectedAgent, visible: newVisibility })
+ });
+ }
+ };
+
+ return (
+
+
+
API Data Collector
+
+
+
+
+
+
+
+
+
setIntervalMs(Number(e.target.value))}
+ style={{ width: "100%", fontSize: "9px", marginBottom: "6px" }}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default {
+ type: "API_Data_Collector",
+ label: "API Data Collector",
+ description: "Connects to a remote agent via API and collects data such as screenshots, OCR results, and more.",
+ content: "Publishes agent-collected data into the workflow ValueBus.",
+ component: APINode
+};
\ No newline at end of file
diff --git a/Data/WebUI/src/nodes/Image Processing/Image_Viewer.jsx b/Data/WebUI/src/nodes/Image Processing/Image_Viewer.jsx
new file mode 100644
index 0000000..e0197d4
--- /dev/null
+++ b/Data/WebUI/src/nodes/Image Processing/Image_Viewer.jsx
@@ -0,0 +1,60 @@
+import React, { useEffect, useState } from "react";
+import { Handle, Position, useReactFlow } from "reactflow";
+
+const ImageViewerNode = ({ id, data }) => {
+ const { getEdges } = useReactFlow();
+ const [imageBase64, setImageBase64] = useState("");
+ const [selectedType, setSelectedType] = useState("base64");
+
+ // Watch upstream value
+ useEffect(() => {
+ const interval = setInterval(() => {
+ const edges = getEdges();
+ const inputEdge = edges.find(e => e.target === id);
+ if (inputEdge) {
+ const sourceId = inputEdge.source;
+ const valueBus = window.BorealisValueBus || {};
+ const value = valueBus[sourceId];
+ if (typeof value === "string") {
+ setImageBase64(value);
+ }
+ }
+ }, 1000);
+ return () => clearInterval(interval);
+ }, [id, getEdges]);
+
+ return (
+
+
+
Image Viewer
+
+
+
+
+ {imageBase64 ? (
+

+ ) : (
+
Waiting for image...
+ )}
+
+
+ );
+};
+
+export default {
+ type: "Image_Viewer",
+ label: "Image Viewer",
+ description: "Displays base64 image pulled from ValueBus of upstream node.",
+ content: "Visual preview of base64 image",
+ component: ImageViewerNode
+};
diff --git a/Data/WebUI/src/nodes/Organization/Backdrop_Group_Box.jsx b/Data/WebUI/src/nodes/Organization/Backdrop_Group_Box.jsx
index b130685..549bb16 100644
--- a/Data/WebUI/src/nodes/Organization/Backdrop_Group_Box.jsx
+++ b/Data/WebUI/src/nodes/Organization/Backdrop_Group_Box.jsx
@@ -32,7 +32,8 @@ const BackdropGroupBoxNode = ({ id, data }) => {
}
}, [isEditing]);
- const handleTitleClick = () => {
+ const handleTitleClick = (e) => {
+ e.stopPropagation();
setIsEditing(true);
};
@@ -47,22 +48,32 @@ const BackdropGroupBoxNode = ({ id, data }) => {
};
return (
- {/* Prevent blocking other nodes */}
+
(
+ e.stopPropagation()}
+ onClick={(e) => e.stopPropagation()}
+ />
+ )}
+ onClick={(e) => e.stopPropagation()}
style={{
backgroundColor: "rgba(44, 44, 44, 0.5)",
border: "1px solid #3a3a3a",
borderRadius: "4px",
boxShadow: "0 0 5px rgba(88, 166, 255, 0.15)",
overflow: "hidden",
- position: "relative"
+ position: "relative",
+ zIndex: 0
}}
- onClick={(e) => e.stopPropagation()} // prevent drag on resize
>
{
value={title}
onChange={handleTitleChange}
onBlur={handleBlur}
+ onClick={(e) => e.stopPropagation()}
+ onMouseDown={(e) => e.stopPropagation()}
style={{
fontSize: "10px",
padding: "2px",
diff --git a/Data/server.py b/Data/server.py
index 424cff4..cfc79b8 100644
--- a/Data/server.py
+++ b/Data/server.py
@@ -1,7 +1,10 @@
-from flask import Flask, send_from_directory
+from flask import Flask, request, jsonify, send_from_directory
+import time
import os
-# Determine the absolute path for the React build folder
+# ---------------------------------------------
+# React Frontend Hosting Configuration
+# ---------------------------------------------
build_folder = os.path.join(os.getcwd(), "web-interface", "build")
if not os.path.exists(build_folder):
print("WARNING: web-interface build folder not found. Please build your React app.")
@@ -9,12 +12,103 @@ if not os.path.exists(build_folder):
app = Flask(__name__, static_folder=build_folder, static_url_path="/")
@app.route("/")
-def serve_frontend():
- """Serve the React app."""
+def serve_index():
index_path = os.path.join(build_folder, "index.html")
if os.path.exists(index_path):
- return send_from_directory(app.static_folder, "index.html")
+ return send_from_directory(build_folder, "index.html")
return "
Borealis React App Code Not Found
Please re-deploy Borealis Workflow Automation Tool
", 404
+# Wildcard route to serve React for sub-routes (e.g., /workflow)
+@app.route("/
")
+def serve_react_app(path):
+ full_path = os.path.join(build_folder, path)
+ if os.path.exists(full_path):
+ return send_from_directory(build_folder, path)
+ return send_from_directory(build_folder, "index.html")
+
+# ---------------------------------------------
+# Borealis Agent API Endpoints
+# ---------------------------------------------
+registered_agents = {}
+agent_configurations = {}
+latest_images = {}
+
+@app.route("/api/agent/checkin", methods=["POST"])
+def agent_checkin():
+ data = request.json
+ agent_id = data.get("agent_id")
+ hostname = data.get("hostname", "unknown")
+
+ registered_agents[agent_id] = {
+ "agent_id": agent_id,
+ "hostname": hostname,
+ "last_seen": time.time(),
+ "status": "orphaned" if agent_id not in agent_configurations else "provisioned"
+ }
+ return jsonify({"status": "ok"})
+
+@app.route("/api/agent/reset", methods=["POST"])
+def reset_agent():
+ agent_id = request.json.get("agent_id")
+ if agent_id in agents:
+ agents[agent_id]["status"] = "orphaned"
+ agents[agent_id]["config"] = None
+ latest_images.pop(agent_id, None)
+ return jsonify({"status": "reset"}), 200
+ return jsonify({"error": "Agent not found"}), 404
+
+@app.route("/api/agent/provision", methods=["POST"])
+def provision_agent():
+ data = request.json
+ agent_id = data.get("agent_id")
+ config = {
+ "task": "screenshot",
+ "x": data.get("x", 100),
+ "y": data.get("y", 100),
+ "w": data.get("w", 300),
+ "h": data.get("h", 200),
+ "interval": data.get("interval", 1000)
+ }
+ agent_configurations[agent_id] = config
+ if agent_id in registered_agents:
+ registered_agents[agent_id]["status"] = "provisioned"
+ return jsonify({"status": "provisioned"})
+
+@app.route("/api/agent/config")
+def get_agent_config():
+ agent_id = request.args.get("agent_id")
+ config = agent_configurations.get(agent_id)
+ return jsonify(config or {})
+
+@app.route("/api/agent/data", methods=["POST"])
+def agent_data():
+ data = request.json
+ agent_id = data.get("agent_id")
+ image = data.get("image_base64")
+
+ if not agent_id or not image:
+ return jsonify({"error": "Missing data"}), 400
+
+ latest_images[agent_id] = {
+ "image_base64": image,
+ "timestamp": time.time()
+ }
+ return jsonify({"status": "received"})
+
+@app.route("/api/agent/image")
+def get_latest_image():
+ agent_id = request.args.get("agent_id")
+ entry = latest_images.get(agent_id)
+ if entry:
+ return jsonify(entry)
+ return jsonify({"error": "No image"}), 404
+
+@app.route("/api/agents")
+def get_agents():
+ return jsonify(registered_agents)
+
+# ---------------------------------------------
+# Server Start
+# ---------------------------------------------
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000, debug=False)
diff --git a/Launch-API-Collector-Agent.ps1 b/Launch-API-Collector-Agent.ps1
new file mode 100644
index 0000000..38b35b5
--- /dev/null
+++ b/Launch-API-Collector-Agent.ps1
@@ -0,0 +1,86 @@
+# Launch-API-Collector-Agent.ps1
+# Run this script with:
+# Set-ExecutionPolicy Unrestricted -Scope Process; .\Launch-API-Collector-Agent.ps1
+
+# ---------------------- Initialization & Visuals ----------------------
+$symbols = @{
+ Success = [char]0x2705
+ Running = [char]0x23F3
+ Fail = [char]0x274C
+ Info = [char]0x2139
+}
+
+function Write-ProgressStep {
+ param (
+ [string]$Message,
+ [string]$Status = $symbols["Info"]
+ )
+ Write-Host "`r$Status $Message... " -NoNewline
+}
+
+function Run-Step {
+ param (
+ [string]$Message,
+ [scriptblock]$Script
+ )
+ Write-ProgressStep -Message $Message -Status "$($symbols.Running)"
+ try {
+ & $Script
+ if ($LASTEXITCODE -eq 0 -or $?) {
+ Write-Host "`r$($symbols.Success) $Message "
+ } else {
+ throw "Non-zero exit code"
+ }
+ } catch {
+ Write-Host "`r$($symbols.Fail) $Message - Failed: $_ " -ForegroundColor Red
+ exit 1
+ }
+}
+
+Clear-Host
+Write-Host "Deploying Borealis API Collector Agent..." -ForegroundColor Green
+Write-Host "===================================================================================="
+
+# ---------------------- Path Definitions ----------------------
+$venvFolder = "Borealis-API-Collector-Agent"
+$agentSourcePath = "Data\Agent\api-collector-agent.py"
+$agentRequirements = "Data\Agent\requirements.txt"
+$agentDestinationFolder = "$venvFolder\Agent"
+$agentDestinationFile = "$agentDestinationFolder\api-collector-agent.py"
+
+# ---------------------- Create Python Virtual Environment & Copy Agent ----------------------
+Run-Step "Create Virtual Python Environment for Collector Agent" {
+ if (!(Test-Path "$venvFolder\Scripts\Activate")) {
+ python -m venv $venvFolder | Out-Null
+ }
+
+ # Copy Agent Script
+ if (Test-Path $agentSourcePath) {
+ if (Test-Path $agentDestinationFolder) {
+ Remove-Item -Recurse -Force $agentDestinationFolder | Out-Null
+ }
+ New-Item -Path $agentDestinationFolder -ItemType Directory -Force | Out-Null
+ Copy-Item -Path $agentSourcePath -Destination $agentDestinationFile -Force
+ } else {
+ Write-Host "`r$($symbols.Info) Warning: Agent script not found at '$agentSourcePath', skipping copy." -ForegroundColor Yellow
+ }
+
+ . "$venvFolder\Scripts\Activate"
+}
+
+# ---------------------- Install Python Dependencies ----------------------
+Run-Step "Install Python Dependencies for Collector Agent" {
+ if (Test-Path $agentRequirements) {
+ pip install -q -r $agentRequirements 2>&1 | Out-Null
+ } else {
+ Write-Host "`r$($symbols.Info) Agent-specific requirements.txt not found at '$agentRequirements', skipping Python packages." -ForegroundColor Yellow
+ }
+}
+
+# ---------------------- Launch Agent ----------------------
+Push-Location $venvFolder
+Write-Host "`nLaunching Borealis API Collector Agent..." -ForegroundColor Green
+Write-Host "===================================================================================="
+Write-Host "$($symbols.Running) Starting Agent..." -NoNewline
+python "Agent\api-collector-agent.py"
+Pop-Location
diff --git a/Launch-Borealis.ps1 b/Launch-Borealis.ps1
index faf2038..e4186a2 100644
--- a/Launch-Borealis.ps1
+++ b/Launch-Borealis.ps1
@@ -54,15 +54,12 @@ $dataDestination = "$venvFolder\Borealis"
$customUIPath = "$dataSource\WebUI"
$webUIDestination = "$venvFolder\web-interface"
-# ---------------------- Create Python Virtual Environment ----------------------
-Run-Step "Create Virtual Python Environment" {
+# ---------------------- Create Python Virtual Environment & Prepare Borealis Files ----------------------
+Run-Step "Create Borealis Virtual Python Environment" {
if (!(Test-Path "$venvFolder\Scripts\Activate")) {
python -m venv $venvFolder | Out-Null
}
-}
-
-# ---------------------- Copy Server Data ----------------------
-Run-Step "Copy Borealis Server Data into Virtual Python Environment" {
+ # ---------------------- Copy Server Data ----------------------
if (Test-Path $dataSource) {
if (Test-Path $dataDestination) {
Remove-Item -Recurse -Force $dataDestination | Out-Null
@@ -72,31 +69,24 @@ Run-Step "Copy Borealis Server Data into Virtual Python Environment" {
} else {
Write-Host "`r$($symbols.Info) Warning: Data folder not found, skipping copy." -ForegroundColor Yellow
}
-}
-# ---------------------- React UI Deployment ----------------------
-Run-Step "Create a new ReactJS App in $webUIDestination" {
+ # ---------------------- React UI Deployment ----------------------
if (-not (Test-Path $webUIDestination)) {
npx create-react-app $webUIDestination | Out-Null
}
-}
-Run-Step "Overwrite ReactJS App Files with Borealis ReactJS Files" {
if (Test-Path $customUIPath) {
Copy-Item -Path "$customUIPath\*" -Destination $webUIDestination -Recurse -Force
} else {
Write-Host "`r$($symbols.Info) No custom UI found, using default React app." -ForegroundColor Yellow
}
-}
-Run-Step "Remove Existing ReactJS Build Folder (If Exists)" {
+ # Remove Pre-Existing ReactJS Build Folder (If one Exists)
if (Test-Path "$webUIDestination\build") {
Remove-Item -Path "$webUIDestination\build" -Recurse -Force
}
-}
-# ---------------------- Activate Python Virtual Environment ----------------------
-Run-Step "Activate Virtual Python Environment" {
+ # ---------------------- Activate Python Virtual Environment ----------------------
. "$venvFolder\Scripts\Activate"
}
@@ -110,39 +100,32 @@ Run-Step "Install Python Dependencies into Virtual Python Environment" {
}
# ---------------------- Build React App ----------------------
-Run-Step "ReactJS App: Install NPM" {
+Run-Step "ReactJS Web Frontend: Install Necessary NPM Packages" {
$packageJsonPath = Join-Path $webUIDestination "package.json"
if (Test-Path $packageJsonPath) {
Push-Location $webUIDestination
$env:npm_config_loglevel = "silent"
+
+ # Install NPM
npm install --silent --no-fund --audit=false 2>&1 | Out-Null
+
+ # Install React Resizable
+ npm install --silent react-resizable --no-fund --audit=false | Out-Null
+
+ # Install React Flow
+ npm install --silent reactflow --no-fund --audit=false | Out-Null
+
+ # Install Material UI Libraries
+ npm install --silent @mui/material @mui/icons-material @emotion/react @emotion/styled --no-fund --audit=false 2>&1 | Out-Null
+
Pop-Location
}
}
-Run-Step "ReactJS App: Install React Resizable" {
+Run-Step "ReactJS Web Frontend: Build App" {
Push-Location $webUIDestination
- npm install react-resizable --no-fund --audit=false | Out-Null
- Pop-Location
-}
-
-Run-Step "ReactJS App: Install React Flow" {
- Push-Location $webUIDestination
- npm install reactflow --no-fund --audit=false | Out-Null
- Pop-Location
-}
-
-Run-Step "ReactJS App: Install Material UI Libraries" {
- Push-Location $webUIDestination
- $env:npm_config_loglevel = "silent" # Force NPM to be completely silent
- npm install --silent @mui/material @mui/icons-material @emotion/react @emotion/styled --no-fund --audit=false 2>&1 | Out-Null
- Pop-Location
-}
-
-Run-Step "ReactJS App: Building App" {
- Push-Location $webUIDestination
- #npm run build | Out-Null
- npm run build
+ #npm run build | Out-Null # Suppress Compilation Output
+ npm run build # Enabled during Development
Pop-Location
}
@@ -150,7 +133,6 @@ Run-Step "ReactJS App: Building App" {
Push-Location $venvFolder
Write-Host "`nLaunching Borealis..." -ForegroundColor Green
Write-Host "===================================================================================="
-Write-Host "$($symbols.Running) Starting the Python Flask server..." -NoNewline
+Write-Host "$($symbols.Running) Starting Python Flask Server..." -NoNewline
python "Borealis\server.py"
-Write-Host "`r$($symbols.Success) Borealis Launched Successfully!"
Pop-Location
diff --git a/Workflows/Test.json b/Workflows/Test.json
deleted file mode 100644
index cfa8bc6..0000000
--- a/Workflows/Test.json
+++ /dev/null
@@ -1,80 +0,0 @@
-{
- "nodes": [
- {
- "id": "node-1743304886846",
- "type": "custom",
- "position": {
- "x": 214.63333129882812,
- "y": 146.66666666666666
- },
- "data": {
- "label": "Custom Node",
- "content": "Placeholder"
- },
- "width": 160,
- "height": 72
- },
- {
- "id": "node-1743304888268",
- "type": "custom",
- "position": {
- "x": 621.2999979654948,
- "y": 276.6666666666667
- },
- "data": {
- "label": "Custom Node",
- "content": "Placeholder"
- },
- "width": 160,
- "height": 72
- },
- {
- "id": "node-1743304891251",
- "type": "custom",
- "position": {
- "x": 814.6333312988281,
- "y": 65.33333333333334
- },
- "data": {
- "label": "Custom Node",
- "content": "Placeholder"
- },
- "width": 160,
- "height": 72,
- "selected": true,
- "positionAbsolute": {
- "x": 814.6333312988281,
- "y": 65.33333333333334
- },
- "dragging": false
- }
- ],
- "edges": [
- {
- "source": "node-1743304886846",
- "sourceHandle": null,
- "target": "node-1743304888268",
- "targetHandle": null,
- "type": "smoothstep",
- "animated": true,
- "style": {
- "strokeDasharray": "6 3",
- "stroke": "#58a6ff"
- },
- "id": "reactflow__edge-node-1743304886846-node-1743304888268"
- },
- {
- "source": "node-1743304886846",
- "sourceHandle": null,
- "target": "node-1743304891251",
- "targetHandle": null,
- "type": "smoothstep",
- "animated": true,
- "style": {
- "strokeDasharray": "6 3",
- "stroke": "#58a6ff"
- },
- "id": "reactflow__edge-node-1743304886846-node-1743304891251"
- }
- ]
-}
\ No newline at end of file
diff --git a/Workflows/workflow.json b/Workflows/workflow.json
deleted file mode 100644
index 50c65c1..0000000
--- a/Workflows/workflow.json
+++ /dev/null
@@ -1,86 +0,0 @@
-{
- "nodes": [
- {
- "id": "node-1743545354663",
- "type": "dataNode",
- "position": {
- "x": 177.26666259765625,
- "y": 122.66666666666667
- },
- "data": {
- "label": "Data Node",
- "content": "Placeholder Node"
- },
- "width": 160,
- "height": 63
- },
- {
- "id": "node-1743545357071",
- "type": "dataNode",
- "position": {
- "x": 506.59999593098956,
- "y": 232
- },
- "data": {
- "label": "Data Node",
- "content": "Placeholder Node"
- },
- "width": 160,
- "height": 63,
- "selected": true,
- "positionAbsolute": {
- "x": 506.59999593098956,
- "y": 232
- },
- "dragging": false
- },
- {
- "id": "node-1743545361694",
- "type": "ExportToCSVNode",
- "position": {
- "x": 187.2666625976562,
- "y": 316.66666666666663
- },
- "data": {
- "label": "Export to CSV",
- "content": "Placeholder Node"
- },
- "width": 160,
- "height": 63,
- "selected": false,
- "positionAbsolute": {
- "x": 187.2666625976562,
- "y": 316.66666666666663
- },
- "dragging": false
- }
- ],
- "edges": [
- {
- "source": "node-1743545354663",
- "sourceHandle": null,
- "target": "node-1743545357071",
- "targetHandle": null,
- "type": "smoothstep",
- "animated": true,
- "style": {
- "strokeDasharray": "6 3",
- "stroke": "#58a6ff"
- },
- "id": "reactflow__edge-node-1743545354663-node-1743545357071"
- },
- {
- "source": "node-1743545354663",
- "sourceHandle": null,
- "target": "node-1743545361694",
- "targetHandle": null,
- "type": "smoothstep",
- "animated": true,
- "style": {
- "strokeDasharray": "6 3",
- "stroke": "#58a6ff"
- },
- "id": "reactflow__edge-node-1743545354663-node-1743545361694"
- }
- ]
-}
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index f0b52a9..cc25e92 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,7 +3,7 @@ torch --index-url https://download.pytorch.org/whl/cu121
torchvision --index-url https://download.pytorch.org/whl/cu121
torchaudio --index-url https://download.pytorch.org/whl/cu121
-# Flask for API handling
+# Flask for API Hhandling
Flask
requests
@@ -13,9 +13,11 @@ qtpy
OdenGraphQt
PyQt5
-# Computer Vision & OCR dependencies
+# Computer Vision & OCR Dependencies
numpy # Numerical operations
opencv-python # Computer vision processing
pytesseract # OCR engine
easyocr # Deep-learning-based OCR
-Pillow # Image processing
\ No newline at end of file
+Pillow # Image processing
+
+# API Collector Agent Dependencies
\ No newline at end of file