From a75c472c98cf50f590d6d326cd17eb49ebb3a443 Mon Sep 17 00:00:00 2001 From: Nicole Rappe Date: Fri, 4 Apr 2025 03:23:01 -0600 Subject: [PATCH] - Implemented Agent-based Data Collection Nodes - Added More Dark Theming throughout Borealis - Added API Data Collector Node - Added Image Viewer Node - Added Agent Deployment Script (Powershell) --- .gitignore | 3 +- Data/Agent/api-collector-agent.py | 205 ++++++++++++++++++ Data/Agent/requirements.txt | 3 + Data/WebUI/src/App.jsx | 2 +- Data/WebUI/src/Borealis.css | 16 +- .../Data Collection/API_Data_Collector.jsx | 177 +++++++++++++++ .../nodes/Image Processing/Image_Viewer.jsx | 60 +++++ .../nodes/Organization/Backdrop_Group_Box.jsx | 21 +- Data/server.py | 104 ++++++++- Launch-API-Collector-Agent.ps1 | 86 ++++++++ Launch-Borealis.ps1 | 64 ++---- Workflows/Test.json | 80 ------- Workflows/workflow.json | 86 -------- requirements.txt | 8 +- 14 files changed, 693 insertions(+), 222 deletions(-) create mode 100644 Data/Agent/api-collector-agent.py create mode 100644 Data/Agent/requirements.txt create mode 100644 Data/WebUI/src/nodes/Data Collection/API_Data_Collector.jsx create mode 100644 Data/WebUI/src/nodes/Image Processing/Image_Viewer.jsx create mode 100644 Launch-API-Collector-Agent.ps1 delete mode 100644 Workflows/Test.json delete mode 100644 Workflows/workflow.json 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 ? ( + Live + ) : ( +
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