Merge pull request #20 from bunny-lab-io/codex/update-workflow-management-features

Enable workflow import/export and rename
This commit is contained in:
2025-08-09 22:46:53 -06:00
committed by GitHub
11 changed files with 1194 additions and 36 deletions

View File

@@ -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

View File

@@ -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" } }}>

View File

@@ -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

View File

@@ -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,17 +35,17 @@ 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("");
useEffect(() => {
let alive = true;
(async () => {
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();
if (!alive) return;
const mapped = (data.workflows || []).map(w => ({
const mapped = (data.workflows || []).map((w) => ({
...w,
name: w.tab_name && w.tab_name.trim() ? w.tab_name.trim() : w.file_name,
description: "",
@@ -54,12 +58,12 @@ export default function WorkflowList({ onOpenWorkflow }) {
console.error("Failed to load workflows:", err);
setRows([]);
}
})();
return () => {
alive = false;
};
}, []);
useEffect(() => {
loadRows();
}, [loadRows]);
const handleSort = (col) => {
if (orderBy === col) setOrder(order === "asc" ? "desc" : "asc");
else {
@@ -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>
);
}

View File

@@ -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
# ---------------------------------------------

View 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"
}
]
}

View 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"
}
]
}

View 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"
}
]
}

View 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"
}
}
]
}

View File

@@ -565,5 +565,5 @@
}
}
],
"tab_name": "Flyff Character Status"
"tab_name": "Character Status Breakdown"
}

File diff suppressed because one or more lines are too long