mirror of
https://github.com/bunny-lab-io/Borealis.git
synced 2025-07-27 06:58:28 -06:00
- 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)
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
|||||||
.vs/
|
.vs/
|
||||||
Borealis-Workflow-Automation-Tool/
|
Borealis-Workflow-Automation-Tool/
|
||||||
|
Borealis-API-Collector-Agent/
|
205
Data/Agent/api-collector-agent.py
Normal file
205
Data/Agent/api-collector-agent.py
Normal file
@ -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_())
|
3
Data/Agent/requirements.txt
Normal file
3
Data/Agent/requirements.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
requests
|
||||||
|
PyQt5
|
||||||
|
Pillow
|
@ -451,7 +451,7 @@ export default function App() {
|
|||||||
}}
|
}}
|
||||||
sx={{ color: "#58a6ff", borderColor: "#58a6ff", fontSize: "0.75rem", textTransform: "none", px: 1.5 }}
|
sx={{ color: "#58a6ff", borderColor: "#58a6ff", fontSize: "0.75rem", textTransform: "none", px: 1.5 }}
|
||||||
>
|
>
|
||||||
Update Rate
|
Apply Rate
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -62,3 +62,17 @@
|
|||||||
width: 10px;
|
width: 10px;
|
||||||
height: 10px;
|
height: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
177
Data/WebUI/src/nodes/Data Collection/API_Data_Collector.jsx
Normal file
177
Data/WebUI/src/nodes/Data Collection/API_Data_Collector.jsx
Normal file
@ -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 (
|
||||||
|
<div className="borealis-node">
|
||||||
|
<Handle type="source" position={Position.Right} className="borealis-handle" />
|
||||||
|
<div className="borealis-node-header">API Data Collector</div>
|
||||||
|
<div className="borealis-node-content">
|
||||||
|
<label style={{ fontSize: "10px" }}>Agent:</label>
|
||||||
|
<select
|
||||||
|
value={selectedAgent}
|
||||||
|
onChange={(e) => setSelectedAgent(e.target.value)}
|
||||||
|
style={{ width: "100%", fontSize: "9px", marginBottom: "6px" }}
|
||||||
|
>
|
||||||
|
<option value="">-- Select --</option>
|
||||||
|
{Object.entries(agents).map(([id, info]) => (
|
||||||
|
<option key={id} value={id} disabled={info.status === "provisioned"}>
|
||||||
|
{id} {info.status === "provisioned" ? "(Adopted)" : ""}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<label style={{ fontSize: "10px" }}>Data Type:</label>
|
||||||
|
<select
|
||||||
|
value={selectedType}
|
||||||
|
onChange={(e) => setSelectedType(e.target.value)}
|
||||||
|
style={{ width: "100%", fontSize: "9px", marginBottom: "6px" }}
|
||||||
|
>
|
||||||
|
<option value="screenshot">Screenshot Region</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<label style={{ fontSize: "10px" }}>Update Rate (ms):</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min="100"
|
||||||
|
step="100"
|
||||||
|
value={intervalMs}
|
||||||
|
onChange={(e) => setIntervalMs(Number(e.target.value))}
|
||||||
|
style={{ width: "100%", fontSize: "9px", marginBottom: "6px" }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: "6px" }}>
|
||||||
|
<label style={{ fontSize: "10px" }}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={paused}
|
||||||
|
onChange={() => setPaused(!paused)}
|
||||||
|
style={{ marginRight: "4px" }}
|
||||||
|
/>
|
||||||
|
Pause Data Collection
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ display: "flex", gap: "4px", flexWrap: "wrap" }}>
|
||||||
|
<button
|
||||||
|
style={{ flex: 1, fontSize: "9px" }}
|
||||||
|
onClick={provisionAgent}
|
||||||
|
>
|
||||||
|
Provision
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
style={{ flex: 1, fontSize: "9px" }}
|
||||||
|
onClick={resetAgent}
|
||||||
|
>
|
||||||
|
Reset Agent
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
style={{ flex: 1, fontSize: "9px" }}
|
||||||
|
onClick={toggleOverlay}
|
||||||
|
>
|
||||||
|
{overlayVisible ? "Hide Overlay" : "Show Overlay"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
60
Data/WebUI/src/nodes/Image Processing/Image_Viewer.jsx
Normal file
60
Data/WebUI/src/nodes/Image Processing/Image_Viewer.jsx
Normal file
@ -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 (
|
||||||
|
<div className="borealis-node">
|
||||||
|
<Handle type="target" position={Position.Left} className="borealis-handle" />
|
||||||
|
<div className="borealis-node-header">Image Viewer</div>
|
||||||
|
<div className="borealis-node-content">
|
||||||
|
<label style={{ fontSize: "10px" }}>Data Type:</label>
|
||||||
|
<select
|
||||||
|
value={selectedType}
|
||||||
|
onChange={(e) => setSelectedType(e.target.value)}
|
||||||
|
style={{ width: "100%", fontSize: "9px", marginBottom: "6px" }}
|
||||||
|
>
|
||||||
|
<option value="base64">Base64 Encoded Image</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
{imageBase64 ? (
|
||||||
|
<img
|
||||||
|
src={`data:image/png;base64,${imageBase64}`}
|
||||||
|
alt="Live"
|
||||||
|
style={{ width: "100%", border: "1px solid #333", marginTop: "6px" }}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div style={{ fontSize: "9px", color: "#888" }}>Waiting for image...</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
@ -32,7 +32,8 @@ const BackdropGroupBoxNode = ({ id, data }) => {
|
|||||||
}
|
}
|
||||||
}, [isEditing]);
|
}, [isEditing]);
|
||||||
|
|
||||||
const handleTitleClick = () => {
|
const handleTitleClick = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
setIsEditing(true);
|
setIsEditing(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -47,22 +48,32 @@ const BackdropGroupBoxNode = ({ id, data }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ pointerEvents: "auto", zIndex: -1 }}> {/* Prevent blocking other nodes */}
|
<div style={{ pointerEvents: "auto" }}>
|
||||||
<ResizableBox
|
<ResizableBox
|
||||||
width={200}
|
width={200}
|
||||||
height={120}
|
height={120}
|
||||||
minConstraints={[120, 80]}
|
minConstraints={[120, 80]}
|
||||||
maxConstraints={[600, 600]}
|
maxConstraints={[600, 600]}
|
||||||
resizeHandles={["se"]}
|
resizeHandles={["se"]}
|
||||||
|
className="borealis-node"
|
||||||
|
handle={(h) => (
|
||||||
|
<span
|
||||||
|
className={`react-resizable-handle react-resizable-handle-${h}`}
|
||||||
|
style={{ pointerEvents: "auto" }}
|
||||||
|
onMouseDown={(e) => e.stopPropagation()}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: "rgba(44, 44, 44, 0.5)",
|
backgroundColor: "rgba(44, 44, 44, 0.5)",
|
||||||
border: "1px solid #3a3a3a",
|
border: "1px solid #3a3a3a",
|
||||||
borderRadius: "4px",
|
borderRadius: "4px",
|
||||||
boxShadow: "0 0 5px rgba(88, 166, 255, 0.15)",
|
boxShadow: "0 0 5px rgba(88, 166, 255, 0.15)",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
position: "relative"
|
position: "relative",
|
||||||
|
zIndex: 0
|
||||||
}}
|
}}
|
||||||
onClick={(e) => e.stopPropagation()} // prevent drag on resize
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
onClick={handleTitleClick}
|
onClick={handleTitleClick}
|
||||||
@ -82,6 +93,8 @@ const BackdropGroupBoxNode = ({ id, data }) => {
|
|||||||
value={title}
|
value={title}
|
||||||
onChange={handleTitleChange}
|
onChange={handleTitleChange}
|
||||||
onBlur={handleBlur}
|
onBlur={handleBlur}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
onMouseDown={(e) => e.stopPropagation()}
|
||||||
style={{
|
style={{
|
||||||
fontSize: "10px",
|
fontSize: "10px",
|
||||||
padding: "2px",
|
padding: "2px",
|
||||||
|
104
Data/server.py
104
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
|
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")
|
build_folder = os.path.join(os.getcwd(), "web-interface", "build")
|
||||||
if not os.path.exists(build_folder):
|
if not os.path.exists(build_folder):
|
||||||
print("WARNING: web-interface build folder not found. Please build your React app.")
|
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 = Flask(__name__, static_folder=build_folder, static_url_path="/")
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def serve_frontend():
|
def serve_index():
|
||||||
"""Serve the React app."""
|
|
||||||
index_path = os.path.join(build_folder, "index.html")
|
index_path = os.path.join(build_folder, "index.html")
|
||||||
if os.path.exists(index_path):
|
if os.path.exists(index_path):
|
||||||
return send_from_directory(app.static_folder, "index.html")
|
return send_from_directory(build_folder, "index.html")
|
||||||
return "<h1>Borealis React App Code Not Found</h1><p>Please re-deploy Borealis Workflow Automation Tool</p>", 404
|
return "<h1>Borealis React App Code Not Found</h1><p>Please re-deploy Borealis Workflow Automation Tool</p>", 404
|
||||||
|
|
||||||
|
# Wildcard route to serve React for sub-routes (e.g., /workflow)
|
||||||
|
@app.route("/<path:path>")
|
||||||
|
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__":
|
if __name__ == "__main__":
|
||||||
app.run(host="0.0.0.0", port=5000, debug=False)
|
app.run(host="0.0.0.0", port=5000, debug=False)
|
||||||
|
86
Launch-API-Collector-Agent.ps1
Normal file
86
Launch-API-Collector-Agent.ps1
Normal file
@ -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
|
@ -54,15 +54,12 @@ $dataDestination = "$venvFolder\Borealis"
|
|||||||
$customUIPath = "$dataSource\WebUI"
|
$customUIPath = "$dataSource\WebUI"
|
||||||
$webUIDestination = "$venvFolder\web-interface"
|
$webUIDestination = "$venvFolder\web-interface"
|
||||||
|
|
||||||
# ---------------------- Create Python Virtual Environment ----------------------
|
# ---------------------- Create Python Virtual Environment & Prepare Borealis Files ----------------------
|
||||||
Run-Step "Create Virtual Python Environment" {
|
Run-Step "Create Borealis Virtual Python Environment" {
|
||||||
if (!(Test-Path "$venvFolder\Scripts\Activate")) {
|
if (!(Test-Path "$venvFolder\Scripts\Activate")) {
|
||||||
python -m venv $venvFolder | Out-Null
|
python -m venv $venvFolder | Out-Null
|
||||||
}
|
}
|
||||||
}
|
# ---------------------- Copy Server Data ----------------------
|
||||||
|
|
||||||
# ---------------------- Copy Server Data ----------------------
|
|
||||||
Run-Step "Copy Borealis Server Data into Virtual Python Environment" {
|
|
||||||
if (Test-Path $dataSource) {
|
if (Test-Path $dataSource) {
|
||||||
if (Test-Path $dataDestination) {
|
if (Test-Path $dataDestination) {
|
||||||
Remove-Item -Recurse -Force $dataDestination | Out-Null
|
Remove-Item -Recurse -Force $dataDestination | Out-Null
|
||||||
@ -72,31 +69,24 @@ Run-Step "Copy Borealis Server Data into Virtual Python Environment" {
|
|||||||
} else {
|
} else {
|
||||||
Write-Host "`r$($symbols.Info) Warning: Data folder not found, skipping copy." -ForegroundColor Yellow
|
Write-Host "`r$($symbols.Info) Warning: Data folder not found, skipping copy." -ForegroundColor Yellow
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
# ---------------------- React UI Deployment ----------------------
|
# ---------------------- React UI Deployment ----------------------
|
||||||
Run-Step "Create a new ReactJS App in $webUIDestination" {
|
|
||||||
if (-not (Test-Path $webUIDestination)) {
|
if (-not (Test-Path $webUIDestination)) {
|
||||||
npx create-react-app $webUIDestination | Out-Null
|
npx create-react-app $webUIDestination | Out-Null
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Run-Step "Overwrite ReactJS App Files with Borealis ReactJS Files" {
|
|
||||||
if (Test-Path $customUIPath) {
|
if (Test-Path $customUIPath) {
|
||||||
Copy-Item -Path "$customUIPath\*" -Destination $webUIDestination -Recurse -Force
|
Copy-Item -Path "$customUIPath\*" -Destination $webUIDestination -Recurse -Force
|
||||||
} else {
|
} else {
|
||||||
Write-Host "`r$($symbols.Info) No custom UI found, using default React app." -ForegroundColor Yellow
|
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") {
|
if (Test-Path "$webUIDestination\build") {
|
||||||
Remove-Item -Path "$webUIDestination\build" -Recurse -Force
|
Remove-Item -Path "$webUIDestination\build" -Recurse -Force
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
# ---------------------- Activate Python Virtual Environment ----------------------
|
# ---------------------- Activate Python Virtual Environment ----------------------
|
||||||
Run-Step "Activate Virtual Python Environment" {
|
|
||||||
. "$venvFolder\Scripts\Activate"
|
. "$venvFolder\Scripts\Activate"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,39 +100,32 @@ Run-Step "Install Python Dependencies into Virtual Python Environment" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# ---------------------- Build React App ----------------------
|
# ---------------------- Build React App ----------------------
|
||||||
Run-Step "ReactJS App: Install NPM" {
|
Run-Step "ReactJS Web Frontend: Install Necessary NPM Packages" {
|
||||||
$packageJsonPath = Join-Path $webUIDestination "package.json"
|
$packageJsonPath = Join-Path $webUIDestination "package.json"
|
||||||
if (Test-Path $packageJsonPath) {
|
if (Test-Path $packageJsonPath) {
|
||||||
Push-Location $webUIDestination
|
Push-Location $webUIDestination
|
||||||
$env:npm_config_loglevel = "silent"
|
$env:npm_config_loglevel = "silent"
|
||||||
|
|
||||||
|
# Install NPM
|
||||||
npm install --silent --no-fund --audit=false 2>&1 | Out-Null
|
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
|
Pop-Location
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Run-Step "ReactJS App: Install React Resizable" {
|
Run-Step "ReactJS Web Frontend: Build App" {
|
||||||
Push-Location $webUIDestination
|
Push-Location $webUIDestination
|
||||||
npm install react-resizable --no-fund --audit=false | Out-Null
|
#npm run build | Out-Null # Suppress Compilation Output
|
||||||
Pop-Location
|
npm run build # Enabled during Development
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
Pop-Location
|
Pop-Location
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,7 +133,6 @@ Run-Step "ReactJS App: Building App" {
|
|||||||
Push-Location $venvFolder
|
Push-Location $venvFolder
|
||||||
Write-Host "`nLaunching Borealis..." -ForegroundColor Green
|
Write-Host "`nLaunching Borealis..." -ForegroundColor Green
|
||||||
Write-Host "===================================================================================="
|
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"
|
python "Borealis\server.py"
|
||||||
Write-Host "`r$($symbols.Success) Borealis Launched Successfully!"
|
|
||||||
Pop-Location
|
Pop-Location
|
||||||
|
@ -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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
@ -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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
@ -3,7 +3,7 @@ torch --index-url https://download.pytorch.org/whl/cu121
|
|||||||
torchvision --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
|
torchaudio --index-url https://download.pytorch.org/whl/cu121
|
||||||
|
|
||||||
# Flask for API handling
|
# Flask for API Hhandling
|
||||||
Flask
|
Flask
|
||||||
requests
|
requests
|
||||||
|
|
||||||
@ -13,9 +13,11 @@ qtpy
|
|||||||
OdenGraphQt
|
OdenGraphQt
|
||||||
PyQt5
|
PyQt5
|
||||||
|
|
||||||
# Computer Vision & OCR dependencies
|
# Computer Vision & OCR Dependencies
|
||||||
numpy # Numerical operations
|
numpy # Numerical operations
|
||||||
opencv-python # Computer vision processing
|
opencv-python # Computer vision processing
|
||||||
pytesseract # OCR engine
|
pytesseract # OCR engine
|
||||||
easyocr # Deep-learning-based OCR
|
easyocr # Deep-learning-based OCR
|
||||||
Pillow # Image processing
|
Pillow # Image processing
|
||||||
|
|
||||||
|
# API Collector Agent Dependencies
|
Reference in New Issue
Block a user