mirror of
https://github.com/bunny-lab-io/Borealis.git
synced 2025-09-11 05:08:43 -06:00
Merge pull request #20 from bunny-lab-io/codex/update-workflow-management-features
Enable workflow import/export and rename
This commit is contained in:
@@ -175,6 +175,86 @@ export default function App() {
|
||||
setRenameDialogOpen(false);
|
||||
};
|
||||
|
||||
const handleExportFlow = useCallback(() => {
|
||||
const tab = tabs.find((t) => t.id === activeTabId);
|
||||
if (!tab) return;
|
||||
const payload = {
|
||||
tab_name: tab.tab_name,
|
||||
nodes: tab.nodes,
|
||||
edges: tab.edges
|
||||
};
|
||||
const fileName = `${tab.tab_name || "workflow"}.json`;
|
||||
const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = fileName;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}, [tabs, activeTabId]);
|
||||
|
||||
const handleImportFlow = useCallback(() => {
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.value = null;
|
||||
fileInputRef.current.click();
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onFileInputChange = useCallback(
|
||||
(e) => {
|
||||
const file = e.target.files && e.target.files[0];
|
||||
if (!file) return;
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
try {
|
||||
const data = JSON.parse(reader.result);
|
||||
const newId = "flow_" + Date.now();
|
||||
setTabs((prev) => [
|
||||
...prev,
|
||||
{
|
||||
id: newId,
|
||||
tab_name:
|
||||
data.tab_name || data.name || file.name.replace(/\.json$/i, ""),
|
||||
nodes: data.nodes || [],
|
||||
edges: data.edges || []
|
||||
}
|
||||
]);
|
||||
setActiveTabId(newId);
|
||||
setCurrentPage("workflow-editor");
|
||||
} catch (err) {
|
||||
console.error("Failed to import workflow:", err);
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
e.target.value = "";
|
||||
},
|
||||
[setTabs]
|
||||
);
|
||||
|
||||
const handleSaveFlow = useCallback(async () => {
|
||||
const tab = tabs.find((t) => t.id === activeTabId);
|
||||
if (!tab) return;
|
||||
const name = window.prompt("Enter workflow name", tab.tab_name || "workflow");
|
||||
if (!name) return;
|
||||
const payload = {
|
||||
name,
|
||||
workflow: {
|
||||
tab_name: tab.tab_name,
|
||||
nodes: tab.nodes,
|
||||
edges: tab.edges
|
||||
}
|
||||
};
|
||||
try {
|
||||
await fetch("/api/storage/save_workflow", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("Failed to save workflow:", err);
|
||||
}
|
||||
}, [tabs, activeTabId]);
|
||||
|
||||
const renderMainContent = () => {
|
||||
switch (currentPage) {
|
||||
case "devices":
|
||||
@@ -230,11 +310,12 @@ export default function App() {
|
||||
<Box sx={{ display: "flex", flexGrow: 1, overflow: "hidden" }}>
|
||||
<NodeSidebar
|
||||
categorizedNodes={categorizedNodes}
|
||||
handleExportFlow={() => {}}
|
||||
handleImportFlow={() => {}}
|
||||
handleOpenCloseAllDialog={() => {}}
|
||||
handleExportFlow={handleExportFlow}
|
||||
handleImportFlow={handleImportFlow}
|
||||
handleSaveFlow={handleSaveFlow}
|
||||
handleOpenCloseAllDialog={() => setConfirmCloseOpen(true)}
|
||||
fileInputRef={fileInputRef}
|
||||
onFileInputChange={() => {}}
|
||||
onFileInputChange={onFileInputChange}
|
||||
/>
|
||||
<Box sx={{ display: "flex", flexDirection: "column", flexGrow: 1, overflow: "hidden" }}>
|
||||
<FlowTabs
|
||||
|
@@ -95,6 +95,43 @@ export function RenameTabDialog({ open, value, onChange, onCancel, onSave }) {
|
||||
);
|
||||
}
|
||||
|
||||
export function RenameWorkflowDialog({ open, value, onChange, onCancel, onSave }) {
|
||||
return (
|
||||
<Dialog open={open} onClose={onCancel} PaperProps={{ sx: { bgcolor: "#121212", color: "#fff" } }}>
|
||||
<DialogTitle>Rename Workflow</DialogTitle>
|
||||
<DialogContent>
|
||||
<TextField
|
||||
autoFocus
|
||||
margin="dense"
|
||||
label="Workflow Name"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
sx={{
|
||||
"& .MuiOutlinedInput-root": {
|
||||
backgroundColor: "#2a2a2a",
|
||||
color: "#ccc",
|
||||
"& fieldset": {
|
||||
borderColor: "#444"
|
||||
},
|
||||
"&:hover fieldset": {
|
||||
borderColor: "#666"
|
||||
}
|
||||
},
|
||||
label: { color: "#aaa" },
|
||||
mt: 1
|
||||
}}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onCancel} sx={{ color: "#58a6ff" }}>Cancel</Button>
|
||||
<Button onClick={onSave} sx={{ color: "#58a6ff" }}>Save</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export function DeleteDeviceDialog({ open, onCancel, onConfirm }) {
|
||||
return (
|
||||
<Dialog open={open} onClose={onCancel} PaperProps={{ sx: { bgcolor: "#121212", color: "#fff" } }}>
|
||||
|
@@ -12,6 +12,7 @@ import {
|
||||
} from "@mui/material";
|
||||
import {
|
||||
ExpandMore as ExpandMoreIcon,
|
||||
SaveAlt as SaveAltIcon,
|
||||
Save as SaveIcon,
|
||||
FileOpen as FileOpenIcon,
|
||||
DeleteForever as DeleteForeverIcon,
|
||||
@@ -25,6 +26,7 @@ export default function NodeSidebar({
|
||||
categorizedNodes,
|
||||
handleExportFlow,
|
||||
handleImportFlow,
|
||||
handleSaveFlow,
|
||||
handleOpenCloseAllDialog,
|
||||
fileInputRef,
|
||||
onFileInputChange
|
||||
@@ -72,10 +74,15 @@ export default function NodeSidebar({
|
||||
</AccordionSummary>
|
||||
<AccordionDetails sx={{ p: 0, bgcolor: "#232323" }}>
|
||||
<Tooltip title="Export Current Tab to a JSON File" placement="right" arrow>
|
||||
<Button fullWidth startIcon={<SaveIcon />} onClick={handleExportFlow} sx={buttonStyle}>
|
||||
<Button fullWidth startIcon={<SaveAltIcon />} onClick={handleExportFlow} sx={buttonStyle}>
|
||||
Export Current Flow
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title="Save Current Flow to Workflows Folder" placement="right" arrow>
|
||||
<Button fullWidth startIcon={<SaveIcon />} onClick={handleSaveFlow} sx={buttonStyle}>
|
||||
Save Workflow
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title="Import JSON File into New Flow Tab" placement="right" arrow>
|
||||
<Button fullWidth startIcon={<FileOpenIcon />} onClick={handleImportFlow} sx={buttonStyle}>
|
||||
Import Flow
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useMemo, useEffect } from "react";
|
||||
import React, { useState, useMemo, useEffect, useCallback } from "react";
|
||||
import {
|
||||
Paper,
|
||||
Box,
|
||||
@@ -9,9 +9,13 @@ import {
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableSortLabel
|
||||
TableSortLabel,
|
||||
IconButton,
|
||||
Menu,
|
||||
MenuItem
|
||||
} from "@mui/material";
|
||||
import { PlayCircle as PlayCircleIcon } from "@mui/icons-material";
|
||||
import { PlayCircle as PlayCircleIcon, MoreVert as MoreVertIcon } from "@mui/icons-material";
|
||||
import { RenameWorkflowDialog } from "./Dialogs";
|
||||
|
||||
function formatDateTime(dateString) {
|
||||
if (!dateString) return "";
|
||||
@@ -31,34 +35,34 @@ export default function WorkflowList({ onOpenWorkflow }) {
|
||||
const [rows, setRows] = useState([]);
|
||||
const [orderBy, setOrderBy] = useState("name");
|
||||
const [order, setOrder] = useState("asc");
|
||||
const [menuAnchor, setMenuAnchor] = useState(null);
|
||||
const [selected, setSelected] = useState(null);
|
||||
const [renameOpen, setRenameOpen] = useState(false);
|
||||
const [renameValue, setRenameValue] = useState("");
|
||||
|
||||
const loadRows = useCallback(async () => {
|
||||
try {
|
||||
const resp = await fetch("/api/storage/load_workflows");
|
||||
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
||||
const data = await resp.json();
|
||||
const mapped = (data.workflows || []).map((w) => ({
|
||||
...w,
|
||||
name: w.tab_name && w.tab_name.trim() ? w.tab_name.trim() : w.file_name,
|
||||
description: "",
|
||||
category: "",
|
||||
lastEdited: w.last_edited,
|
||||
lastEditedEpoch: w.last_edited_epoch
|
||||
}));
|
||||
setRows(mapped);
|
||||
} catch (err) {
|
||||
console.error("Failed to load workflows:", err);
|
||||
setRows([]);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let alive = true;
|
||||
(async () => {
|
||||
try {
|
||||
const resp = await fetch("/api/storage/load_workflows");
|
||||
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
||||
const data = await resp.json();
|
||||
if (!alive) return;
|
||||
|
||||
const mapped = (data.workflows || []).map(w => ({
|
||||
...w,
|
||||
name: w.tab_name && w.tab_name.trim() ? w.tab_name.trim() : w.file_name,
|
||||
description: "",
|
||||
category: "",
|
||||
lastEdited: w.last_edited,
|
||||
lastEditedEpoch: w.last_edited_epoch
|
||||
}));
|
||||
setRows(mapped);
|
||||
} catch (err) {
|
||||
console.error("Failed to load workflows:", err);
|
||||
setRows([]);
|
||||
}
|
||||
})();
|
||||
return () => {
|
||||
alive = false;
|
||||
};
|
||||
}, []);
|
||||
loadRows();
|
||||
}, [loadRows]);
|
||||
|
||||
const handleSort = (col) => {
|
||||
if (orderBy === col) setOrder(order === "asc" ? "desc" : "asc");
|
||||
@@ -94,6 +98,41 @@ export default function WorkflowList({ onOpenWorkflow }) {
|
||||
}
|
||||
};
|
||||
|
||||
const openMenu = (e, row) => {
|
||||
e.stopPropagation();
|
||||
setMenuAnchor(e.currentTarget);
|
||||
setSelected(row);
|
||||
};
|
||||
|
||||
const closeMenu = () => setMenuAnchor(null);
|
||||
|
||||
const startRename = () => {
|
||||
closeMenu();
|
||||
if (selected) {
|
||||
const initial = selected.tab_name && selected.tab_name.trim().length > 0
|
||||
? selected.tab_name.trim()
|
||||
: selected.file_name.replace(/\.json$/i, "");
|
||||
setRenameValue(initial);
|
||||
setRenameOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRenameSave = async () => {
|
||||
if (!selected) return;
|
||||
try {
|
||||
await fetch("/api/storage/rename_workflow", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ path: selected.rel_path, new_name: renameValue })
|
||||
});
|
||||
await loadRows();
|
||||
} catch (err) {
|
||||
console.error("Failed to rename workflow:", err);
|
||||
}
|
||||
setRenameOpen(false);
|
||||
setSelected(null);
|
||||
};
|
||||
|
||||
const renderNameCell = (r) => {
|
||||
const hasPrefix = r.breadcrumb_prefix && r.breadcrumb_prefix.length > 0;
|
||||
const primary = r.tab_name && r.tab_name.trim().length > 0 ? r.tab_name.trim() : r.file_name;
|
||||
@@ -179,6 +218,7 @@ export default function WorkflowList({ onOpenWorkflow }) {
|
||||
Last Edited
|
||||
</TableSortLabel>
|
||||
</TableCell>
|
||||
<TableCell />
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
@@ -193,17 +233,41 @@ export default function WorkflowList({ onOpenWorkflow }) {
|
||||
<TableCell>{r.description}</TableCell>
|
||||
<TableCell>{r.category}</TableCell>
|
||||
<TableCell>{formatDateTime(r.lastEdited)}</TableCell>
|
||||
<TableCell align="right" onClick={(e) => e.stopPropagation()}>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={(e) => openMenu(e, r)}
|
||||
sx={{ color: "#ccc" }}
|
||||
>
|
||||
<MoreVertIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
{sorted.length === 0 && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4} sx={{ color: "#888" }}>
|
||||
<TableCell colSpan={5} sx={{ color: "#888" }}>
|
||||
No workflows found.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<Menu
|
||||
anchorEl={menuAnchor}
|
||||
open={Boolean(menuAnchor)}
|
||||
onClose={closeMenu}
|
||||
PaperProps={{ sx: { bgcolor: "#1e1e1e", color: "#fff", fontSize: "13px" } }}
|
||||
>
|
||||
<MenuItem onClick={startRename}>Rename</MenuItem>
|
||||
</Menu>
|
||||
<RenameWorkflowDialog
|
||||
open={renameOpen}
|
||||
value={renameValue}
|
||||
onChange={setRenameValue}
|
||||
onCancel={() => setRenameOpen(false)}
|
||||
onSave={handleRenameSave}
|
||||
/>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
@@ -186,6 +186,63 @@ def load_workflow():
|
||||
obj = _safe_read_json(abs_path)
|
||||
return jsonify(obj)
|
||||
|
||||
|
||||
@app.route("/api/storage/save_workflow", methods=["POST"])
|
||||
def save_workflow():
|
||||
data = request.get_json(silent=True) or {}
|
||||
name = (data.get("name") or "").strip()
|
||||
workflow = data.get("workflow")
|
||||
if not name or not isinstance(workflow, dict):
|
||||
return jsonify({"error": "Invalid payload"}), 400
|
||||
|
||||
if not name.lower().endswith(".json"):
|
||||
name += ".json"
|
||||
workflows_root = os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), "..", "..", "Workflows")
|
||||
)
|
||||
os.makedirs(workflows_root, exist_ok=True)
|
||||
safe_name = os.path.basename(name)
|
||||
abs_path = os.path.join(workflows_root, safe_name)
|
||||
try:
|
||||
with open(abs_path, "w", encoding="utf-8") as fh:
|
||||
json.dump(workflow, fh, indent=2)
|
||||
return jsonify({"status": "ok", "file_name": safe_name})
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
|
||||
@app.route("/api/storage/rename_workflow", methods=["POST"])
|
||||
def rename_workflow():
|
||||
data = request.get_json(silent=True) or {}
|
||||
rel_path = (data.get("path") or "").strip()
|
||||
new_name = (data.get("new_name") or "").strip()
|
||||
workflows_root = os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), "..", "..", "Workflows")
|
||||
)
|
||||
old_abs = os.path.abspath(os.path.join(workflows_root, rel_path))
|
||||
if not old_abs.startswith(workflows_root) or not os.path.isfile(old_abs):
|
||||
return jsonify({"error": "Workflow not found"}), 404
|
||||
if not new_name:
|
||||
return jsonify({"error": "Invalid new_name"}), 400
|
||||
if not new_name.lower().endswith(".json"):
|
||||
new_name += ".json"
|
||||
new_abs = os.path.join(os.path.dirname(old_abs), os.path.basename(new_name))
|
||||
base_name = os.path.splitext(os.path.basename(new_abs))[0]
|
||||
try:
|
||||
os.rename(old_abs, new_abs)
|
||||
obj = _safe_read_json(new_abs)
|
||||
for k in ["tabName", "tab_name", "name", "title"]:
|
||||
if k in obj:
|
||||
obj[k] = base_name
|
||||
if "tab_name" not in obj:
|
||||
obj["tab_name"] = base_name
|
||||
with open(new_abs, "w", encoding="utf-8") as fh:
|
||||
json.dump(obj, fh, indent=2)
|
||||
rel_new = os.path.relpath(new_abs, workflows_root).replace(os.sep, "/")
|
||||
return jsonify({"status": "ok", "rel_path": rel_new})
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
# ---------------------------------------------
|
||||
# Borealis Agent API Endpoints
|
||||
# ---------------------------------------------
|
||||
|
106
Workflows/Examples/API Requests/Value Parser.json
Normal file
106
Workflows/Examples/API Requests/Value Parser.json
Normal file
@@ -0,0 +1,106 @@
|
||||
{
|
||||
"tab_name": "Value Parser",
|
||||
"nodes": [
|
||||
{
|
||||
"data": {
|
||||
"body": "",
|
||||
"content": "Fetch JSON from HTTP or remote API endpoint, with CORS proxy option.",
|
||||
"intervalSec": "10",
|
||||
"label": "API Request",
|
||||
"result": "{\n \"status\": \"ok\"\n}",
|
||||
"url": "http://localhost:5000/health",
|
||||
"useProxy": "true"
|
||||
},
|
||||
"dragHandle": ".borealis-node-header",
|
||||
"dragging": false,
|
||||
"height": 124,
|
||||
"id": "node-1754799747658",
|
||||
"position": {
|
||||
"x": 27.333333333333314,
|
||||
"y": 28
|
||||
},
|
||||
"positionAbsolute": {
|
||||
"x": 27.333333333333314,
|
||||
"y": 28
|
||||
},
|
||||
"selected": false,
|
||||
"type": "API_Request",
|
||||
"width": 160
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"content": "Display prettified multi-line JSON from upstream node.",
|
||||
"jsonData": {
|
||||
"status": "ok"
|
||||
},
|
||||
"label": "Display JSON Data"
|
||||
},
|
||||
"dragHandle": ".borealis-node-header",
|
||||
"dragging": false,
|
||||
"height": 150,
|
||||
"id": "node-1754799750393",
|
||||
"position": {
|
||||
"x": 245.33333333333326,
|
||||
"y": 28
|
||||
},
|
||||
"positionAbsolute": {
|
||||
"x": 245.33333333333326,
|
||||
"y": 28
|
||||
},
|
||||
"selected": false,
|
||||
"type": "Node_JSON_Pretty_Display",
|
||||
"width": 260
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"content": "Provide a dot-separated key path (e.g. 'name.en'); outputs the extracted string or 'Key Not Found'.",
|
||||
"keyName": "status",
|
||||
"label": "JSON Value Extractor",
|
||||
"result": "ok"
|
||||
},
|
||||
"dragHandle": ".borealis-node-header",
|
||||
"dragging": false,
|
||||
"height": 145,
|
||||
"id": "node-1754799751513",
|
||||
"position": {
|
||||
"x": 559.3333333333333,
|
||||
"y": 28
|
||||
},
|
||||
"positionAbsolute": {
|
||||
"x": 559.3333333333333,
|
||||
"y": 28
|
||||
},
|
||||
"selected": false,
|
||||
"type": "JSON_Value_Extractor",
|
||||
"width": 160
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"animated": true,
|
||||
"id": "reactflow__edge-node-1754799747658-node-1754799750393",
|
||||
"source": "node-1754799747658",
|
||||
"sourceHandle": null,
|
||||
"style": {
|
||||
"stroke": "#58a6ff",
|
||||
"strokeDasharray": "6 3"
|
||||
},
|
||||
"target": "node-1754799750393",
|
||||
"targetHandle": null,
|
||||
"type": "bezier"
|
||||
},
|
||||
{
|
||||
"animated": true,
|
||||
"id": "reactflow__edge-node-1754799750393-node-1754799751513",
|
||||
"source": "node-1754799750393",
|
||||
"sourceHandle": null,
|
||||
"style": {
|
||||
"stroke": "#58a6ff",
|
||||
"strokeDasharray": "6 3"
|
||||
},
|
||||
"target": "node-1754799751513",
|
||||
"targetHandle": null,
|
||||
"type": "bezier"
|
||||
}
|
||||
]
|
||||
}
|
113
Workflows/Examples/Basic/Logic Comparison.json
Normal file
113
Workflows/Examples/Basic/Logic Comparison.json
Normal file
@@ -0,0 +1,113 @@
|
||||
{
|
||||
"tab_name": "Logic Comparison",
|
||||
"nodes": [
|
||||
{
|
||||
"data": {
|
||||
"content": "Store a String or Number",
|
||||
"label": "String Input A",
|
||||
"value": "Bunny"
|
||||
},
|
||||
"dragHandle": ".borealis-node-header",
|
||||
"dragging": false,
|
||||
"height": 67,
|
||||
"id": "node-1754800111049",
|
||||
"position": {
|
||||
"x": 19.333333333333314,
|
||||
"y": 20.666666666666657
|
||||
},
|
||||
"positionAbsolute": {
|
||||
"x": 19.333333333333314,
|
||||
"y": 20.666666666666657
|
||||
},
|
||||
"selected": false,
|
||||
"type": "DataNode",
|
||||
"width": 160
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"accentColor": "#ff8c00",
|
||||
"content": "Store a String or Number",
|
||||
"label": "String Input B",
|
||||
"value": "Bunny"
|
||||
},
|
||||
"dragHandle": ".borealis-node-header",
|
||||
"dragging": false,
|
||||
"height": 67,
|
||||
"id": "node-1754800112609",
|
||||
"position": {
|
||||
"x": 19.33333333333337,
|
||||
"y": 121.33333333333337
|
||||
},
|
||||
"positionAbsolute": {
|
||||
"x": 19.33333333333337,
|
||||
"y": 121.33333333333337
|
||||
},
|
||||
"selected": false,
|
||||
"style": {
|
||||
"--borealis-accent": "#ff8c00",
|
||||
"--borealis-accent-dark": "#b36200",
|
||||
"--borealis-title": "#ff8c00"
|
||||
},
|
||||
"type": "DataNode",
|
||||
"width": 160
|
||||
},
|
||||
{
|
||||
"id": "node-1754800323495",
|
||||
"type": "ComparisonNode",
|
||||
"position": {
|
||||
"x": 271.3333333333333,
|
||||
"y": 69.33333333333333
|
||||
},
|
||||
"data": {
|
||||
"label": "Does A and B Match?",
|
||||
"content": "Compare A and B using Logic, with new range operator.",
|
||||
"value": "1",
|
||||
"accentColor": "#c0ff00",
|
||||
"inputType": "String",
|
||||
"operator": "Equal (==)"
|
||||
},
|
||||
"dragHandle": ".borealis-node-header",
|
||||
"width": 160,
|
||||
"height": 67,
|
||||
"positionAbsolute": {
|
||||
"x": 271.3333333333333,
|
||||
"y": 69.33333333333333
|
||||
},
|
||||
"selected": false,
|
||||
"dragging": false,
|
||||
"style": {
|
||||
"--borealis-accent": "#c0ff00",
|
||||
"--borealis-accent-dark": "#86b300",
|
||||
"--borealis-title": "#c0ff00"
|
||||
}
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"source": "node-1754800111049",
|
||||
"sourceHandle": null,
|
||||
"target": "node-1754800323495",
|
||||
"targetHandle": "a",
|
||||
"type": "bezier",
|
||||
"animated": true,
|
||||
"style": {
|
||||
"strokeDasharray": "6 3",
|
||||
"stroke": "#58a6ff"
|
||||
},
|
||||
"id": "reactflow__edge-node-1754800111049-node-1754800323495a"
|
||||
},
|
||||
{
|
||||
"source": "node-1754800112609",
|
||||
"sourceHandle": null,
|
||||
"target": "node-1754800323495",
|
||||
"targetHandle": "b",
|
||||
"type": "bezier",
|
||||
"animated": true,
|
||||
"style": {
|
||||
"strokeDasharray": "6 3",
|
||||
"stroke": "#58a6ff"
|
||||
},
|
||||
"id": "reactflow__edge-node-1754800112609-node-1754800323495b"
|
||||
}
|
||||
]
|
||||
}
|
147
Workflows/Examples/Basic/Math Operations.json
Normal file
147
Workflows/Examples/Basic/Math Operations.json
Normal file
@@ -0,0 +1,147 @@
|
||||
{
|
||||
"tab_name": "Math Operations",
|
||||
"nodes": [
|
||||
{
|
||||
"id": "node-1754800111049",
|
||||
"type": "DataNode",
|
||||
"position": {
|
||||
"x": 19.333333333333314,
|
||||
"y": 20.666666666666657
|
||||
},
|
||||
"data": {
|
||||
"label": "Number Input A",
|
||||
"content": "Store a String or Number",
|
||||
"value": "5"
|
||||
},
|
||||
"dragHandle": ".borealis-node-header",
|
||||
"width": 160,
|
||||
"height": 67,
|
||||
"selected": false,
|
||||
"positionAbsolute": {
|
||||
"x": 19.333333333333314,
|
||||
"y": 20.666666666666657
|
||||
},
|
||||
"dragging": false
|
||||
},
|
||||
{
|
||||
"id": "node-1754800112609",
|
||||
"type": "DataNode",
|
||||
"position": {
|
||||
"x": 19.33333333333337,
|
||||
"y": 121.33333333333337
|
||||
},
|
||||
"data": {
|
||||
"label": "Number Input B",
|
||||
"content": "Store a String or Number",
|
||||
"accentColor": "#ff8c00",
|
||||
"value": "3"
|
||||
},
|
||||
"dragHandle": ".borealis-node-header",
|
||||
"width": 160,
|
||||
"height": 67,
|
||||
"selected": false,
|
||||
"positionAbsolute": {
|
||||
"x": 19.33333333333337,
|
||||
"y": 121.33333333333337
|
||||
},
|
||||
"dragging": false,
|
||||
"style": {
|
||||
"--borealis-accent": "#ff8c00",
|
||||
"--borealis-accent-dark": "#b36200",
|
||||
"--borealis-title": "#ff8c00"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "node-1754800119705",
|
||||
"type": "MathNode",
|
||||
"position": {
|
||||
"x": 276.66666666666663,
|
||||
"y": 69.33333333333334
|
||||
},
|
||||
"data": {
|
||||
"label": "Multiply A x B",
|
||||
"content": "Perform Math Operations",
|
||||
"value": "15",
|
||||
"operator": "Multiply",
|
||||
"accentColor": "#00d18c"
|
||||
},
|
||||
"dragHandle": ".borealis-node-header",
|
||||
"width": 160,
|
||||
"height": 67,
|
||||
"positionAbsolute": {
|
||||
"x": 276.66666666666663,
|
||||
"y": 69.33333333333334
|
||||
},
|
||||
"selected": false,
|
||||
"dragging": false,
|
||||
"style": {
|
||||
"--borealis-accent": "#00d18c",
|
||||
"--borealis-accent-dark": "#009262",
|
||||
"--borealis-title": "#00d18c"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "node-1754800128555",
|
||||
"type": "DataNode",
|
||||
"position": {
|
||||
"x": 517.3333333333334,
|
||||
"y": 69.33333333333333
|
||||
},
|
||||
"data": {
|
||||
"label": "Usable Result",
|
||||
"content": "Store a String or Number",
|
||||
"value": "15"
|
||||
},
|
||||
"dragHandle": ".borealis-node-header",
|
||||
"width": 160,
|
||||
"height": 67,
|
||||
"positionAbsolute": {
|
||||
"x": 517.3333333333334,
|
||||
"y": 69.33333333333333
|
||||
},
|
||||
"selected": true,
|
||||
"dragging": false
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"source": "node-1754800111049",
|
||||
"sourceHandle": null,
|
||||
"target": "node-1754800119705",
|
||||
"targetHandle": "a",
|
||||
"type": "bezier",
|
||||
"animated": true,
|
||||
"style": {
|
||||
"strokeDasharray": "6 3",
|
||||
"stroke": "#58a6ff"
|
||||
},
|
||||
"id": "reactflow__edge-node-1754800111049-node-1754800119705a"
|
||||
},
|
||||
{
|
||||
"source": "node-1754800112609",
|
||||
"sourceHandle": null,
|
||||
"target": "node-1754800119705",
|
||||
"targetHandle": "b",
|
||||
"type": "bezier",
|
||||
"animated": true,
|
||||
"style": {
|
||||
"strokeDasharray": "6 3",
|
||||
"stroke": "#58a6ff"
|
||||
},
|
||||
"id": "reactflow__edge-node-1754800112609-node-1754800119705b"
|
||||
},
|
||||
{
|
||||
"source": "node-1754800119705",
|
||||
"sourceHandle": null,
|
||||
"target": "node-1754800128555",
|
||||
"targetHandle": null,
|
||||
"type": "bezier",
|
||||
"animated": true,
|
||||
"style": {
|
||||
"strokeDasharray": "6 3",
|
||||
"stroke": "#58a6ff"
|
||||
},
|
||||
"id": "reactflow__edge-node-1754800119705-node-1754800128555"
|
||||
}
|
||||
]
|
||||
}
|
276
Workflows/Examples/OCR/Text Recognition.json
Normal file
276
Workflows/Examples/OCR/Text Recognition.json
Normal file
@@ -0,0 +1,276 @@
|
||||
{
|
||||
"tab_name": "Text Recognition",
|
||||
"nodes": [
|
||||
{
|
||||
"id": "node-1754800498085",
|
||||
"type": "Borealis_Agent",
|
||||
"position": {
|
||||
"x": 25.999999999999943,
|
||||
"y": 24.000000000000014
|
||||
},
|
||||
"data": {
|
||||
"label": "Borealis Agent",
|
||||
"content": "Select and manage an Agent with dynamic roles",
|
||||
"agent_id": ""
|
||||
},
|
||||
"dragHandle": ".borealis-node-header",
|
||||
"positionAbsolute": {
|
||||
"x": 25.999999999999943,
|
||||
"y": 24.000000000000014
|
||||
},
|
||||
"width": 205,
|
||||
"height": 146,
|
||||
"selected": false,
|
||||
"dragging": false
|
||||
},
|
||||
{
|
||||
"id": "node-1754800514571",
|
||||
"type": "Agent_Role_Screenshot",
|
||||
"position": {
|
||||
"x": 278,
|
||||
"y": 24
|
||||
},
|
||||
"data": {
|
||||
"label": "Agent Role: Screenshot",
|
||||
"content": "Capture screenshot region via agent",
|
||||
"interval": "1000",
|
||||
"x": "250",
|
||||
"y": "100",
|
||||
"w": "300",
|
||||
"h": "200",
|
||||
"visible": "true",
|
||||
"alias": ""
|
||||
},
|
||||
"dragHandle": ".borealis-node-header",
|
||||
"width": 166,
|
||||
"height": 115,
|
||||
"selected": false,
|
||||
"positionAbsolute": {
|
||||
"x": 278,
|
||||
"y": 24
|
||||
},
|
||||
"dragging": false
|
||||
},
|
||||
{
|
||||
"id": "node-1754800556810",
|
||||
"type": "Image_Viewer",
|
||||
"position": {
|
||||
"x": 507.33333333333326,
|
||||
"y": 24
|
||||
},
|
||||
"data": {
|
||||
"label": "Raw Image Viewer",
|
||||
"content": "Visual preview of base64 image with zoom overlay."
|
||||
},
|
||||
"dragHandle": ".borealis-node-header",
|
||||
"width": 160,
|
||||
"height": 69,
|
||||
"selected": false,
|
||||
"positionAbsolute": {
|
||||
"x": 507.33333333333326,
|
||||
"y": 24
|
||||
},
|
||||
"dragging": false
|
||||
},
|
||||
{
|
||||
"id": "node-1754800584420",
|
||||
"type": "BWThresholdNode",
|
||||
"position": {
|
||||
"x": 507.33333333333337,
|
||||
"y": 110
|
||||
},
|
||||
"data": {
|
||||
"label": "BW Threshold",
|
||||
"content": "Applies black & white threshold to base64 image input."
|
||||
},
|
||||
"dragHandle": ".borealis-node-header",
|
||||
"width": 160,
|
||||
"height": 96,
|
||||
"selected": false,
|
||||
"positionAbsolute": {
|
||||
"x": 507.33333333333337,
|
||||
"y": 110
|
||||
},
|
||||
"dragging": false
|
||||
},
|
||||
{
|
||||
"id": "node-1754800597090",
|
||||
"type": "OCR_Text_Extraction",
|
||||
"position": {
|
||||
"x": 46.800008138020814,
|
||||
"y": 280.00000000000006
|
||||
},
|
||||
"data": {
|
||||
"label": "OCR Text Extraction",
|
||||
"content": "Extract Multi-Line Text from Upstream Image Node",
|
||||
"engine": "EasyOCR",
|
||||
"backend": "GPU",
|
||||
"dataType": "Mixed",
|
||||
"customRateEnabled": "true",
|
||||
"customRateMs": "1000",
|
||||
"changeThreshold": "0"
|
||||
},
|
||||
"dragHandle": ".borealis-node-header",
|
||||
"width": 231,
|
||||
"height": 160,
|
||||
"selected": false,
|
||||
"positionAbsolute": {
|
||||
"x": 46.800008138020814,
|
||||
"y": 280.00000000000006
|
||||
},
|
||||
"dragging": false
|
||||
},
|
||||
{
|
||||
"id": "node-1754800680302",
|
||||
"type": "ArrayIndexExtractor",
|
||||
"position": {
|
||||
"x": 497.3333333333333,
|
||||
"y": 280
|
||||
},
|
||||
"data": {
|
||||
"label": "Array Index Extractor",
|
||||
"content": "Output a Specific Array Index's Value",
|
||||
"lineNumber": "1"
|
||||
},
|
||||
"dragHandle": ".borealis-node-header",
|
||||
"width": 210,
|
||||
"height": 121,
|
||||
"selected": false,
|
||||
"positionAbsolute": {
|
||||
"x": 497.3333333333333,
|
||||
"y": 280
|
||||
},
|
||||
"dragging": false
|
||||
},
|
||||
{
|
||||
"id": "node-1754800736215",
|
||||
"type": "DataNode",
|
||||
"position": {
|
||||
"x": 328.6666666666665,
|
||||
"y": 568.6666666666666
|
||||
},
|
||||
"data": {
|
||||
"label": "String / Number",
|
||||
"content": "Store a String or Number",
|
||||
"value": "[ERROR] No base64 image data provided."
|
||||
},
|
||||
"dragHandle": ".borealis-node-header",
|
||||
"width": 226,
|
||||
"height": 67,
|
||||
"selected": true,
|
||||
"positionAbsolute": {
|
||||
"x": 328.6666666666665,
|
||||
"y": 568.6666666666666
|
||||
},
|
||||
"dragging": false
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"source": "node-1754800498085",
|
||||
"sourceHandle": "provisioner",
|
||||
"target": "node-1754800514571",
|
||||
"targetHandle": null,
|
||||
"type": "smoothstep",
|
||||
"animated": true,
|
||||
"style": {
|
||||
"strokeDasharray": "6 3",
|
||||
"stroke": "#58a6ff"
|
||||
},
|
||||
"id": "reactflow__edge-node-1754800498085provisioner-node-1754800514571",
|
||||
"label": "Capture the Screen",
|
||||
"labelBgStyle": {
|
||||
"fill": "#000000"
|
||||
},
|
||||
"labelStyle": {
|
||||
"fill": "#58a6ff"
|
||||
}
|
||||
},
|
||||
{
|
||||
"source": "node-1754800514571",
|
||||
"sourceHandle": null,
|
||||
"target": "node-1754800556810",
|
||||
"targetHandle": null,
|
||||
"type": "bezier",
|
||||
"animated": true,
|
||||
"style": {
|
||||
"strokeDasharray": "6 3",
|
||||
"stroke": "#58a6ff"
|
||||
},
|
||||
"id": "reactflow__edge-node-1754800514571-node-1754800556810"
|
||||
},
|
||||
{
|
||||
"source": "node-1754800514571",
|
||||
"sourceHandle": null,
|
||||
"target": "node-1754800584420",
|
||||
"targetHandle": null,
|
||||
"type": "bezier",
|
||||
"animated": true,
|
||||
"style": {
|
||||
"strokeDasharray": "6 3",
|
||||
"stroke": "#58a6ff"
|
||||
},
|
||||
"id": "reactflow__edge-node-1754800514571-node-1754800584420"
|
||||
},
|
||||
{
|
||||
"source": "node-1754800584420",
|
||||
"sourceHandle": null,
|
||||
"target": "node-1754800597090",
|
||||
"targetHandle": null,
|
||||
"type": "smoothstep",
|
||||
"animated": true,
|
||||
"style": {
|
||||
"strokeDasharray": "6 3",
|
||||
"stroke": "#00d18c"
|
||||
},
|
||||
"id": "reactflow__edge-node-1754800584420-node-1754800597090",
|
||||
"label": "Process Agent Screenshot into Text",
|
||||
"labelBgStyle": {
|
||||
"fill": "#000000"
|
||||
},
|
||||
"labelStyle": {
|
||||
"fill": "#00d18c"
|
||||
}
|
||||
},
|
||||
{
|
||||
"source": "node-1754800597090",
|
||||
"sourceHandle": null,
|
||||
"target": "node-1754800680302",
|
||||
"targetHandle": null,
|
||||
"type": "straight",
|
||||
"animated": true,
|
||||
"style": {
|
||||
"strokeDasharray": "6 3",
|
||||
"stroke": "#00d18c"
|
||||
},
|
||||
"id": "reactflow__edge-node-1754800597090-node-1754800680302",
|
||||
"label": "Extract a Specific Line of Text",
|
||||
"labelBgStyle": {
|
||||
"fill": "#000000"
|
||||
},
|
||||
"labelStyle": {
|
||||
"fill": "#00d18c"
|
||||
}
|
||||
},
|
||||
{
|
||||
"source": "node-1754800680302",
|
||||
"sourceHandle": null,
|
||||
"target": "node-1754800736215",
|
||||
"targetHandle": null,
|
||||
"type": "smoothstep",
|
||||
"animated": true,
|
||||
"style": {
|
||||
"strokeDasharray": "6 3",
|
||||
"stroke": "#ff8c00"
|
||||
},
|
||||
"id": "reactflow__edge-node-1754800680302-node-1754800736215",
|
||||
"label": "Do something with the result",
|
||||
"labelStyle": {
|
||||
"fill": "#ff8c00"
|
||||
},
|
||||
"labelBgStyle": {
|
||||
"fill": "#000000"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@@ -565,5 +565,5 @@
|
||||
}
|
||||
}
|
||||
],
|
||||
"tab_name": "Flyff Character Status"
|
||||
"tab_name": "Character Status Breakdown"
|
||||
}
|
270
Workflows/Games/Flyff Universe/Chat Text Search Alerter.json
Normal file
270
Workflows/Games/Flyff Universe/Chat Text Search Alerter.json
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user