diff --git a/Data/WebUI/src/components/FlowEditor.jsx b/Data/WebUI/src/components/FlowEditor.jsx
new file mode 100644
index 0000000..0de34ca
--- /dev/null
+++ b/Data/WebUI/src/components/FlowEditor.jsx
@@ -0,0 +1,46 @@
+import React, { useState, useEffect, useCallback } from "react";
+import ReactFlow, {
+ addEdge,
+ Controls,
+ Background,
+} from "reactflow";
+import "reactflow/dist/style.css";
+
+const fetchNodes = async () => {
+ const response = await fetch("/api/workflow");
+ return response.json();
+};
+
+const saveWorkflow = async (workflow) => {
+ await fetch("/api/workflow", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(workflow),
+ });
+};
+
+export default function FlowEditor() {
+ const [elements, setElements] = useState([]);
+
+ useEffect(() => {
+ fetchNodes().then((data) => setElements([...data.nodes, ...data.edges]));
+ }, []);
+
+ const onConnect = useCallback(
+ (params) => {
+ const newEdge = { id: `e${params.source}-${params.target}`, ...params };
+ setElements((els) => [...els, newEdge]);
+ saveWorkflow({ nodes: elements.filter(e => e.type), edges: [...elements.filter(e => !e.type), newEdge] });
+ },
+ [elements]
+ );
+
+ return (
+
+
+
+
+
+
+ );
+}
diff --git a/Data/server.py b/Data/server.py
index e484666..04db7ee 100644
--- a/Data/server.py
+++ b/Data/server.py
@@ -2,32 +2,34 @@ from flask import Flask, send_from_directory, jsonify, request, abort
import os
import importlib
import inspect
+import uuid
from OdenGraphQt import BaseNode
# Determine the absolute path for the React build folder
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 run your React build process to generate 'web-interface/build'.")
+ print("WARNING: web-interface build folder not found. Please build your React app.")
app = Flask(__name__, static_folder=build_folder, static_url_path="/")
# Directory where nodes are stored
NODES_PACKAGE = "Nodes"
-# In-memory workflow storage (a simple scaffold)
+# In-memory workflow storage
workflow_data = {
"nodes": [],
- "connections": []
+ "edges": [] # Store connections separately
}
def import_nodes_from_folder(package_name):
+ """Dynamically import node classes from the given package and list them."""
nodes_by_category = {}
package = importlib.import_module(package_name)
package_path = package.__path__[0]
for root, _, files in os.walk(package_path):
rel_path = os.path.relpath(root, package_path).replace(os.sep, ".")
- module_prefix = f"{package_name}.{rel_path}" if rel_path != '.' else package_name
+ module_prefix = f"{package_name}.{rel_path}" if rel_path != "." else package_name
category_name = os.path.basename(root)
for file in files:
@@ -36,9 +38,7 @@ def import_nodes_from_folder(package_name):
try:
module = importlib.import_module(module_name)
for name, obj in inspect.getmembers(module, inspect.isclass):
- # Filter out only actual node classes you define:
- if (issubclass(obj, BaseNode)
- and obj.__module__ == module.__name__):
+ if issubclass(obj, BaseNode) and obj.__module__ == module.__name__:
if category_name not in nodes_by_category:
nodes_by_category[category_name] = []
nodes_by_category[category_name].append(obj.NODE_NAME)
@@ -49,68 +49,83 @@ def import_nodes_from_folder(package_name):
@app.route("/")
def serve_frontend():
+ """Serve the React app."""
index_path = os.path.join(build_folder, "index.html")
if os.path.exists(index_path):
return send_from_directory(app.static_folder, "index.html")
- else:
- return "React App Not Found
Please build the web-interface application.
", 404
+ return "React App Not Found
Please build the web-interface application.
", 404
@app.route("/api/nodes", methods=["GET"])
def get_available_nodes():
- """Returns a list of available node categories and node types."""
+ """Return available node types."""
nodes = import_nodes_from_folder(NODES_PACKAGE)
return jsonify(nodes)
-@app.route("/api/workflow", methods=["GET"])
-def get_workflow():
- """Returns the current workflow data."""
- return jsonify(workflow_data)
-
-@app.route("/api/workflow", methods=["POST"])
-def save_workflow():
- """Saves the workflow data (nodes and connections) sent from the UI."""
+@app.route("/api/workflow", methods=["GET", "POST"])
+def handle_workflow():
+ """Retrieve or update the workflow."""
global workflow_data
- data = request.get_json()
- if not data:
- abort(400, "Invalid workflow data")
- workflow_data = data
- return jsonify({"status": "success", "workflow": workflow_data})
+ if request.method == "GET":
+ return jsonify(workflow_data)
+ elif request.method == "POST":
+ data = request.get_json()
+ if not data:
+ abort(400, "Invalid workflow data")
+ workflow_data = data
+ return jsonify({"status": "success", "workflow": workflow_data})
@app.route("/api/node", methods=["POST"])
def create_node():
- """Creates a new node. Expects JSON with 'nodeType' and optionally 'position' and 'properties'."""
+ """Create a new node with a unique UUID."""
data = request.get_json()
if not data or "nodeType" not in data:
abort(400, "Invalid node data")
+
+ node_id = str(uuid.uuid4()) # Generate a unique ID
node = {
- "id": len(workflow_data["nodes"]) + 1, # simple incremental ID
+ "id": node_id,
"type": data["nodeType"],
- "position": data.get("position", {"x": 0, "y": 0}),
+ "position": data.get("position", {"x": 100, "y": 100}),
"properties": data.get("properties", {})
}
workflow_data["nodes"].append(node)
return jsonify({"status": "success", "node": node})
-@app.route("/api/node/", methods=["DELETE"])
-def delete_node(node_id):
- """Deletes the node with the given ID."""
+@app.route("/api/node/", methods=["PUT", "DELETE"])
+def modify_node(node_id):
+ """Update or delete a node."""
global workflow_data
- nodes = workflow_data["nodes"]
- workflow_data["nodes"] = [n for n in nodes if n["id"] != node_id]
- return jsonify({"status": "success", "deletedNode": node_id})
+ if request.method == "PUT":
+ data = request.get_json()
+ for node in workflow_data["nodes"]:
+ if node["id"] == node_id:
+ node["position"] = data.get("position", node["position"])
+ node["properties"] = data.get("properties", node["properties"])
+ return jsonify({"status": "success", "node": node})
+ abort(404, "Node not found")
-@app.route("/api/node/", methods=["PUT"])
-def update_node(node_id):
- """Updates an existing node's position or properties."""
+ elif request.method == "DELETE":
+ workflow_data["nodes"] = [n for n in workflow_data["nodes"] if n["id"] != node_id]
+ return jsonify({"status": "success", "deletedNode": node_id})
+
+@app.route("/api/edge", methods=["POST"])
+def create_edge():
+ """Create a new connection (edge) between nodes."""
data = request.get_json()
- if not data:
- abort(400, "Invalid node data")
- for node in workflow_data["nodes"]:
- if node["id"] == node_id:
- node["position"] = data.get("position", node["position"])
- node["properties"] = data.get("properties", node["properties"])
- return jsonify({"status": "success", "node": node})
- abort(404, "Node not found")
+ if not data or "source" not in data or "target" not in data:
+ abort(400, "Invalid edge data")
+
+ edge_id = str(uuid.uuid4())
+ edge = {"id": edge_id, "source": data["source"], "target": data["target"]}
+ workflow_data["edges"].append(edge)
+ return jsonify({"status": "success", "edge": edge})
+
+@app.route("/api/edge/", methods=["DELETE"])
+def delete_edge(edge_id):
+ """Delete an edge by ID."""
+ global workflow_data
+ workflow_data["edges"] = [e for e in workflow_data["edges"] if e["id"] != edge_id]
+ return jsonify({"status": "success", "deletedEdge": edge_id})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000, debug=True)
diff --git a/Launch-Borealis.ps1 b/Launch-Borealis.ps1
index 4770780..22b4522 100644
--- a/Launch-Borealis.ps1
+++ b/Launch-Borealis.ps1
@@ -69,7 +69,12 @@ if (-not (Test-Path $buildFolder)) {
if (Test-Path $packageJsonPath) {
Write-Output "React UI build not found in '$webUIDestination'. Installing dependencies and building the React app..."
Push-Location $webUIDestination
- npm install
+ npm install --no-fund --audit=false
+
+ # Ensure react-flow-renderer is installed
+ Write-Output "Installing React Flow..."
+ npm install reactflow --no-fund --audit=false
+
npm run build
Pop-Location
}