@foresthubai/workflow-builder 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -661
- package/NOTICE +16 -16
- package/README.md +110 -93
- package/dist/components/ui/command.d.ts +2 -2
- package/dist/components/ui/input.d.ts +1 -1
- package/dist/components/ui/resizable.d.ts +1 -1
- package/dist/components/ui/textarea.d.ts +1 -1
- package/dist/graph/BaseNode.js +10 -10
- package/dist/graph/reactFlowRegistry.d.ts.map +1 -1
- package/dist/lib/utils.d.ts +3 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +6 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/toolbars/CanvasTabsToolbar.d.ts +11 -0
- package/dist/toolbars/CanvasTabsToolbar.d.ts.map +1 -0
- package/dist/toolbars/CanvasTabsToolbar.js +101 -0
- package/dist/toolbars/CanvasTabsToolbar.js.map +1 -0
- package/package.json +2 -2
- package/src/BuilderLayout.tsx +345 -345
- package/src/Canvas.tsx +261 -261
- package/src/CanvasEditor.tsx +142 -142
- package/src/CanvasTabsToolbar.tsx +176 -176
- package/src/RightConfigPanel.tsx +266 -266
- package/src/WorkflowBuilder.tsx +412 -412
- package/src/cn.ts +6 -6
- package/src/components/ui/add-button.tsx +39 -39
- package/src/components/ui/alert-dialog.tsx +141 -141
- package/src/components/ui/alert.tsx +59 -59
- package/src/components/ui/badge.tsx +36 -36
- package/src/components/ui/button.tsx +85 -85
- package/src/components/ui/card.tsx +79 -79
- package/src/components/ui/checkbox.tsx +28 -28
- package/src/components/ui/collapsible.tsx +9 -9
- package/src/components/ui/command.tsx +153 -153
- package/src/components/ui/delete-button.tsx +23 -23
- package/src/components/ui/dialog.tsx +125 -125
- package/src/components/ui/dropdown-menu.tsx +198 -198
- package/src/components/ui/input.tsx +55 -55
- package/src/components/ui/label.tsx +24 -24
- package/src/components/ui/readonly-banner.tsx +15 -15
- package/src/components/ui/resizable.tsx +43 -43
- package/src/components/ui/scroll-area.tsx +102 -102
- package/src/components/ui/select.tsx +160 -160
- package/src/components/ui/separator.tsx +29 -29
- package/src/components/ui/switch.tsx +27 -27
- package/src/components/ui/textarea.tsx +51 -51
- package/src/components/ui/toast.tsx +127 -127
- package/src/components/ui/toaster.tsx +33 -33
- package/src/components/ui/toggle-group.tsx +59 -59
- package/src/components/ui/toggle.tsx +43 -43
- package/src/components/ui/tooltip.tsx +32 -32
- package/src/dialogs/NodePickerDialog.tsx +84 -84
- package/src/dialogs/ValidationDialog.tsx +184 -184
- package/src/graph/BaseNode.tsx +557 -557
- package/src/graph/CustomEdge.tsx +185 -185
- package/src/graph/CustomNode.tsx +16 -16
- package/src/graph/FunctionCallNode.tsx +30 -30
- package/src/graph/PortHandle.tsx +189 -189
- package/src/graph/reactFlowRegistry.ts +26 -26
- package/src/hooks/use-toast.ts +125 -125
- package/src/hooks/useAvailableVariables.ts +20 -20
- package/src/hooks/useCanvasHistory.ts +22 -22
- package/src/hooks/useCanvasTabs.ts +168 -168
- package/src/hooks/useFunctionDiagnosticsSync.ts +40 -40
- package/src/hooks/useFunctionRegistry.ts +26 -26
- package/src/hooks/useFunctions.ts +44 -44
- package/src/hooks/useGraph.ts +161 -161
- package/src/hooks/useNodeDefinitions.ts +82 -82
- package/src/hooks/useParamErrors.ts +26 -26
- package/src/hooks/useResolvedTheme.ts +30 -30
- package/src/hooks/useResourceDiagnosticsSync.ts +58 -58
- package/src/hooks/useSuppressThemeTransition.ts +79 -79
- package/src/hooks/useWorkflowSerialization.ts +127 -127
- package/src/i18n/index.ts +53 -53
- package/src/i18n/locales/de.json +501 -501
- package/src/i18n/locales/en.json +557 -557
- package/src/index.ts +27 -27
- package/src/inputs/ExpressionInput.tsx +297 -297
- package/src/inputs/ParameterEditor.tsx +515 -515
- package/src/inputs/PortSection.tsx +144 -144
- package/src/panels/BuilderSidebar.tsx +301 -301
- package/src/panels/ChannelConfigPanel.tsx +49 -49
- package/src/panels/ChannelsPanel.tsx +28 -28
- package/src/panels/DebugConsolePanel.tsx +73 -73
- package/src/panels/DebugContextPanel.tsx +77 -77
- package/src/panels/DebugExternalIOPanel.tsx +180 -180
- package/src/panels/DiagnosticsPanel.tsx +170 -170
- package/src/panels/EdgeConfigPanel.tsx +104 -104
- package/src/panels/FunctionConfigPanel.tsx +179 -179
- package/src/panels/FunctionListPanel.tsx +45 -45
- package/src/panels/MemoryConfigPanel.tsx +55 -55
- package/src/panels/MemoryPanel.tsx +40 -40
- package/src/panels/ModelConfigPanel.tsx +41 -41
- package/src/panels/ModelsPanel.tsx +36 -36
- package/src/panels/NodeConfigPanel.tsx +630 -630
- package/src/panels/NodeLibrary.tsx +288 -288
- package/src/panels/ResourceConfigPanel.tsx +132 -132
- package/src/panels/ResourceListPanel.tsx +113 -113
- package/src/panels/VariableConfigPanel.tsx +161 -161
- package/src/panels/VariablesPanel.tsx +145 -145
- package/src/stores/canvasStore.test.ts +44 -44
- package/src/stores/canvasStore.ts +245 -245
- package/src/stores/debugStore.ts +74 -74
- package/src/stores/diagnosticsStore.ts +130 -130
- package/src/stores/editorStore.ts +202 -202
- package/src/styles/index.css +526 -526
- package/src/utils/categoryConstants.ts +26 -26
- package/src/utils/channelOperations.ts +86 -86
- package/src/utils/connectionRules.ts +137 -137
- package/src/utils/functionOperations.ts +179 -179
- package/src/utils/graphOperations.ts +550 -550
- package/src/utils/history.ts +207 -207
- package/src/utils/memoryOperations.ts +57 -57
- package/src/utils/migrateFunctionNodes.ts +107 -107
- package/src/utils/modelOperations.ts +55 -55
- package/src/utils/paramDisplay.ts +71 -71
- package/src/utils/resourceHelpers.ts +32 -32
- package/src/utils/translation.ts +28 -28
- package/src/utils/variableOperations.ts +75 -75
- package/tailwind-preset.ts +166 -166
package/src/graph/CustomEdge.tsx
CHANGED
|
@@ -1,185 +1,185 @@
|
|
|
1
|
-
import { Tooltip, TooltipContent, TooltipTrigger } from "../components/ui/tooltip";
|
|
2
|
-
import { BaseEdge, EdgeLabelRenderer, getBezierPath, Position, useEdges } from "@xyflow/react";
|
|
3
|
-
import { AlertTriangle } from "lucide-react";
|
|
4
|
-
import { EdgeData, isControlFlow, isToolFlow, type EdgeType } from "@foresthubai/workflow-core/edge";
|
|
5
|
-
import { useEffect, useMemo } from "react";
|
|
6
|
-
import { useAvailableVariables } from "../hooks/useAvailableVariables";
|
|
7
|
-
import { useDiagnosticsStore } from "../stores/diagnosticsStore";
|
|
8
|
-
import { useEditorStore } from "../stores/editorStore";
|
|
9
|
-
import { isReadOnly } from "../WorkflowBuilder";
|
|
10
|
-
import { computeEdgeDiagnostics } from "@foresthubai/workflow-core/diagnostics";
|
|
11
|
-
|
|
12
|
-
const EDGE_BASE_COLOR = "hsl(var(--edge-default))";
|
|
13
|
-
const AGENT_COLOR = "hsl(var(--node-agent))";
|
|
14
|
-
const ERROR_COLOR = "hsl(var(--destructive))";
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Source/target stroke colors per edge type. Plain control and tool edges use
|
|
18
|
-
* the neutral edge base on both ends. Edges that touch an Agent pick up the
|
|
19
|
-
* agent color on that side, so the SVG gradient makes the edge visually
|
|
20
|
-
* announce its agent endpoint (e.g. agentTask fades base → agent toward the
|
|
21
|
-
* target Agent).
|
|
22
|
-
*/
|
|
23
|
-
function endColors(type: EdgeType): { source: string; target: string } {
|
|
24
|
-
switch (type) {
|
|
25
|
-
case "control":
|
|
26
|
-
case "tool":
|
|
27
|
-
return { source: EDGE_BASE_COLOR, target: EDGE_BASE_COLOR };
|
|
28
|
-
case "agentTask":
|
|
29
|
-
return { source: EDGE_BASE_COLOR, target: AGENT_COLOR };
|
|
30
|
-
case "agentChoice":
|
|
31
|
-
return { source: AGENT_COLOR, target: EDGE_BASE_COLOR };
|
|
32
|
-
case "agentDelegate":
|
|
33
|
-
return { source: AGENT_COLOR, target: AGENT_COLOR };
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export const CustomEdge = ({
|
|
38
|
-
id,
|
|
39
|
-
source,
|
|
40
|
-
type,
|
|
41
|
-
sourceX,
|
|
42
|
-
sourceY,
|
|
43
|
-
targetX,
|
|
44
|
-
targetY,
|
|
45
|
-
data,
|
|
46
|
-
selected,
|
|
47
|
-
}: {
|
|
48
|
-
id: string;
|
|
49
|
-
source: string;
|
|
50
|
-
type: string;
|
|
51
|
-
sourceX: number;
|
|
52
|
-
sourceY: number;
|
|
53
|
-
targetX: number;
|
|
54
|
-
targetY: number;
|
|
55
|
-
data?: EdgeData;
|
|
56
|
-
selected?: boolean;
|
|
57
|
-
}) => {
|
|
58
|
-
const isHighlighted = selected ?? false;
|
|
59
|
-
const isPreview = useEditorStore((s) => isReadOnly(s.builderMode));
|
|
60
|
-
const edgeType = (type ?? "control") as EdgeType;
|
|
61
|
-
const edges = useEdges();
|
|
62
|
-
const activeCanvasId = useEditorStore.getState().activeCanvasId;
|
|
63
|
-
const { lookup: availableVariables } = useAvailableVariables(activeCanvasId);
|
|
64
|
-
|
|
65
|
-
const sourceControlEdgeCount = edges.filter((e) => e.source === source && isControlFlow(e.type as EdgeType)).length;
|
|
66
|
-
|
|
67
|
-
// Compute edge diagnostics via extracted pure function
|
|
68
|
-
const diagnostics = useMemo(
|
|
69
|
-
() =>
|
|
70
|
-
computeEdgeDiagnostics({
|
|
71
|
-
canvasId: activeCanvasId,
|
|
72
|
-
edgeId: id,
|
|
73
|
-
edgeType,
|
|
74
|
-
edgeData: data,
|
|
75
|
-
availableVariables,
|
|
76
|
-
sourceControlEdgeCount,
|
|
77
|
-
}),
|
|
78
|
-
[edgeType, sourceControlEdgeCount, data, availableVariables, activeCanvasId, id],
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
const hasErrors = diagnostics.length > 0;
|
|
82
|
-
|
|
83
|
-
// Write diagnostics to store (cleanup on unmount; validateAllCanvases handles full-project)
|
|
84
|
-
const setEdgeDiagnostics = useDiagnosticsStore((s) => s.setEdgeDiagnostics);
|
|
85
|
-
const clearEdgeDiagnostics = useDiagnosticsStore((s) => s.clearEdgeDiagnostics);
|
|
86
|
-
useEffect(() => {
|
|
87
|
-
if (isPreview) return;
|
|
88
|
-
setEdgeDiagnostics(id, diagnostics);
|
|
89
|
-
return () => clearEdgeDiagnostics(id);
|
|
90
|
-
}, [id, diagnostics, setEdgeDiagnostics, clearEdgeDiagnostics, isPreview]);
|
|
91
|
-
|
|
92
|
-
const getEdgePath = () => {
|
|
93
|
-
if (isToolFlow(edgeType)) {
|
|
94
|
-
// Tool ports are diamonds, and React Flow anchors the wire at the handle
|
|
95
|
-
// box's edge midpoint — i.e. the diamond's pointed tip — so a wire meeting
|
|
96
|
-
// it looks pinched/detached. Pull each end into its node (tool output sits
|
|
97
|
-
// on a node's bottom, tool input on the next node's top) so the wire
|
|
98
|
-
// overlaps the diamond body (hidden under the z-20 port) and reads solid.
|
|
99
|
-
const OVERLAP = 3;
|
|
100
|
-
return getBezierPath({
|
|
101
|
-
sourceX,
|
|
102
|
-
sourceY: sourceY - OVERLAP,
|
|
103
|
-
targetX,
|
|
104
|
-
targetY: targetY + OVERLAP,
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
// Control flow: ends have horizontal slope
|
|
108
|
-
const OVERLAP = 2;
|
|
109
|
-
return getBezierPath({
|
|
110
|
-
sourceX: sourceX - OVERLAP,
|
|
111
|
-
sourceY,
|
|
112
|
-
sourcePosition: Position.Right,
|
|
113
|
-
targetX: targetX + OVERLAP,
|
|
114
|
-
targetY,
|
|
115
|
-
targetPosition: Position.Left,
|
|
116
|
-
});
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
const [edgePath, labelX, labelY] = getEdgePath();
|
|
120
|
-
const colors = hasErrors ? { source: ERROR_COLOR, target: ERROR_COLOR } : endColors(edgeType);
|
|
121
|
-
const strokeWidth = 3;
|
|
122
|
-
const gradientId = `edge-gradient-${id}`;
|
|
123
|
-
|
|
124
|
-
// Glow effect for highlighted edges — uniform high-contrast color (or
|
|
125
|
-
// destructive when the edge itself is errored).
|
|
126
|
-
const glowColor = hasErrors ? ERROR_COLOR : "hsl(var(--selection-glow))";
|
|
127
|
-
const glowFilter = isHighlighted ? `drop-shadow(0 0 8px ${glowColor}) drop-shadow(0 0 16px ${glowColor})` : undefined;
|
|
128
|
-
|
|
129
|
-
return (
|
|
130
|
-
<>
|
|
131
|
-
<g style={{ filter: glowFilter }}>
|
|
132
|
-
<defs>
|
|
133
|
-
{/* gradientUnits=userSpaceOnUse anchors the stops to the edge's actual
|
|
134
|
-
source/target coordinates, so the fade follows the wire regardless
|
|
135
|
-
of its bounding box (which is what objectBoundingBox would use). */}
|
|
136
|
-
<linearGradient
|
|
137
|
-
id={gradientId}
|
|
138
|
-
gradientUnits="userSpaceOnUse"
|
|
139
|
-
x1={sourceX}
|
|
140
|
-
y1={sourceY}
|
|
141
|
-
x2={targetX}
|
|
142
|
-
y2={targetY}
|
|
143
|
-
>
|
|
144
|
-
<stop offset="0%" stopColor={colors.source} />
|
|
145
|
-
<stop offset="100%" stopColor={colors.target} />
|
|
146
|
-
</linearGradient>
|
|
147
|
-
</defs>
|
|
148
|
-
<BaseEdge
|
|
149
|
-
id={id}
|
|
150
|
-
path={edgePath}
|
|
151
|
-
style={{
|
|
152
|
-
stroke: `url(#${gradientId})`,
|
|
153
|
-
strokeWidth,
|
|
154
|
-
strokeDasharray: "none",
|
|
155
|
-
}}
|
|
156
|
-
/>
|
|
157
|
-
</g>
|
|
158
|
-
<EdgeLabelRenderer>
|
|
159
|
-
<div
|
|
160
|
-
style={{
|
|
161
|
-
position: "absolute",
|
|
162
|
-
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
|
|
163
|
-
pointerEvents: "all",
|
|
164
|
-
}}
|
|
165
|
-
className="flex flex-col items-center gap-0.5"
|
|
166
|
-
>
|
|
167
|
-
{hasErrors && (
|
|
168
|
-
<Tooltip delayDuration={300}>
|
|
169
|
-
<TooltipTrigger asChild>
|
|
170
|
-
<div className="cursor-help">
|
|
171
|
-
<AlertTriangle className="h-5 w-5 text-destructive fill-background" />
|
|
172
|
-
</div>
|
|
173
|
-
</TooltipTrigger>
|
|
174
|
-
<TooltipContent side="top" className="bg-destructive text-destructive-foreground text-xs px-2 py-1">
|
|
175
|
-
{diagnostics[0]?.message ?? "This edge has errors"}
|
|
176
|
-
</TooltipContent>
|
|
177
|
-
</Tooltip>
|
|
178
|
-
)}
|
|
179
|
-
</div>
|
|
180
|
-
</EdgeLabelRenderer>
|
|
181
|
-
</>
|
|
182
|
-
);
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
export default CustomEdge;
|
|
1
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "../components/ui/tooltip";
|
|
2
|
+
import { BaseEdge, EdgeLabelRenderer, getBezierPath, Position, useEdges } from "@xyflow/react";
|
|
3
|
+
import { AlertTriangle } from "lucide-react";
|
|
4
|
+
import { EdgeData, isControlFlow, isToolFlow, type EdgeType } from "@foresthubai/workflow-core/edge";
|
|
5
|
+
import { useEffect, useMemo } from "react";
|
|
6
|
+
import { useAvailableVariables } from "../hooks/useAvailableVariables";
|
|
7
|
+
import { useDiagnosticsStore } from "../stores/diagnosticsStore";
|
|
8
|
+
import { useEditorStore } from "../stores/editorStore";
|
|
9
|
+
import { isReadOnly } from "../WorkflowBuilder";
|
|
10
|
+
import { computeEdgeDiagnostics } from "@foresthubai/workflow-core/diagnostics";
|
|
11
|
+
|
|
12
|
+
const EDGE_BASE_COLOR = "hsl(var(--edge-default))";
|
|
13
|
+
const AGENT_COLOR = "hsl(var(--node-agent))";
|
|
14
|
+
const ERROR_COLOR = "hsl(var(--destructive))";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Source/target stroke colors per edge type. Plain control and tool edges use
|
|
18
|
+
* the neutral edge base on both ends. Edges that touch an Agent pick up the
|
|
19
|
+
* agent color on that side, so the SVG gradient makes the edge visually
|
|
20
|
+
* announce its agent endpoint (e.g. agentTask fades base → agent toward the
|
|
21
|
+
* target Agent).
|
|
22
|
+
*/
|
|
23
|
+
function endColors(type: EdgeType): { source: string; target: string } {
|
|
24
|
+
switch (type) {
|
|
25
|
+
case "control":
|
|
26
|
+
case "tool":
|
|
27
|
+
return { source: EDGE_BASE_COLOR, target: EDGE_BASE_COLOR };
|
|
28
|
+
case "agentTask":
|
|
29
|
+
return { source: EDGE_BASE_COLOR, target: AGENT_COLOR };
|
|
30
|
+
case "agentChoice":
|
|
31
|
+
return { source: AGENT_COLOR, target: EDGE_BASE_COLOR };
|
|
32
|
+
case "agentDelegate":
|
|
33
|
+
return { source: AGENT_COLOR, target: AGENT_COLOR };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const CustomEdge = ({
|
|
38
|
+
id,
|
|
39
|
+
source,
|
|
40
|
+
type,
|
|
41
|
+
sourceX,
|
|
42
|
+
sourceY,
|
|
43
|
+
targetX,
|
|
44
|
+
targetY,
|
|
45
|
+
data,
|
|
46
|
+
selected,
|
|
47
|
+
}: {
|
|
48
|
+
id: string;
|
|
49
|
+
source: string;
|
|
50
|
+
type: string;
|
|
51
|
+
sourceX: number;
|
|
52
|
+
sourceY: number;
|
|
53
|
+
targetX: number;
|
|
54
|
+
targetY: number;
|
|
55
|
+
data?: EdgeData;
|
|
56
|
+
selected?: boolean;
|
|
57
|
+
}) => {
|
|
58
|
+
const isHighlighted = selected ?? false;
|
|
59
|
+
const isPreview = useEditorStore((s) => isReadOnly(s.builderMode));
|
|
60
|
+
const edgeType = (type ?? "control") as EdgeType;
|
|
61
|
+
const edges = useEdges();
|
|
62
|
+
const activeCanvasId = useEditorStore.getState().activeCanvasId;
|
|
63
|
+
const { lookup: availableVariables } = useAvailableVariables(activeCanvasId);
|
|
64
|
+
|
|
65
|
+
const sourceControlEdgeCount = edges.filter((e) => e.source === source && isControlFlow(e.type as EdgeType)).length;
|
|
66
|
+
|
|
67
|
+
// Compute edge diagnostics via extracted pure function
|
|
68
|
+
const diagnostics = useMemo(
|
|
69
|
+
() =>
|
|
70
|
+
computeEdgeDiagnostics({
|
|
71
|
+
canvasId: activeCanvasId,
|
|
72
|
+
edgeId: id,
|
|
73
|
+
edgeType,
|
|
74
|
+
edgeData: data,
|
|
75
|
+
availableVariables,
|
|
76
|
+
sourceControlEdgeCount,
|
|
77
|
+
}),
|
|
78
|
+
[edgeType, sourceControlEdgeCount, data, availableVariables, activeCanvasId, id],
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const hasErrors = diagnostics.length > 0;
|
|
82
|
+
|
|
83
|
+
// Write diagnostics to store (cleanup on unmount; validateAllCanvases handles full-project)
|
|
84
|
+
const setEdgeDiagnostics = useDiagnosticsStore((s) => s.setEdgeDiagnostics);
|
|
85
|
+
const clearEdgeDiagnostics = useDiagnosticsStore((s) => s.clearEdgeDiagnostics);
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
if (isPreview) return;
|
|
88
|
+
setEdgeDiagnostics(id, diagnostics);
|
|
89
|
+
return () => clearEdgeDiagnostics(id);
|
|
90
|
+
}, [id, diagnostics, setEdgeDiagnostics, clearEdgeDiagnostics, isPreview]);
|
|
91
|
+
|
|
92
|
+
const getEdgePath = () => {
|
|
93
|
+
if (isToolFlow(edgeType)) {
|
|
94
|
+
// Tool ports are diamonds, and React Flow anchors the wire at the handle
|
|
95
|
+
// box's edge midpoint — i.e. the diamond's pointed tip — so a wire meeting
|
|
96
|
+
// it looks pinched/detached. Pull each end into its node (tool output sits
|
|
97
|
+
// on a node's bottom, tool input on the next node's top) so the wire
|
|
98
|
+
// overlaps the diamond body (hidden under the z-20 port) and reads solid.
|
|
99
|
+
const OVERLAP = 3;
|
|
100
|
+
return getBezierPath({
|
|
101
|
+
sourceX,
|
|
102
|
+
sourceY: sourceY - OVERLAP,
|
|
103
|
+
targetX,
|
|
104
|
+
targetY: targetY + OVERLAP,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
// Control flow: ends have horizontal slope
|
|
108
|
+
const OVERLAP = 2;
|
|
109
|
+
return getBezierPath({
|
|
110
|
+
sourceX: sourceX - OVERLAP,
|
|
111
|
+
sourceY,
|
|
112
|
+
sourcePosition: Position.Right,
|
|
113
|
+
targetX: targetX + OVERLAP,
|
|
114
|
+
targetY,
|
|
115
|
+
targetPosition: Position.Left,
|
|
116
|
+
});
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const [edgePath, labelX, labelY] = getEdgePath();
|
|
120
|
+
const colors = hasErrors ? { source: ERROR_COLOR, target: ERROR_COLOR } : endColors(edgeType);
|
|
121
|
+
const strokeWidth = 3;
|
|
122
|
+
const gradientId = `edge-gradient-${id}`;
|
|
123
|
+
|
|
124
|
+
// Glow effect for highlighted edges — uniform high-contrast color (or
|
|
125
|
+
// destructive when the edge itself is errored).
|
|
126
|
+
const glowColor = hasErrors ? ERROR_COLOR : "hsl(var(--selection-glow))";
|
|
127
|
+
const glowFilter = isHighlighted ? `drop-shadow(0 0 8px ${glowColor}) drop-shadow(0 0 16px ${glowColor})` : undefined;
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<>
|
|
131
|
+
<g style={{ filter: glowFilter }}>
|
|
132
|
+
<defs>
|
|
133
|
+
{/* gradientUnits=userSpaceOnUse anchors the stops to the edge's actual
|
|
134
|
+
source/target coordinates, so the fade follows the wire regardless
|
|
135
|
+
of its bounding box (which is what objectBoundingBox would use). */}
|
|
136
|
+
<linearGradient
|
|
137
|
+
id={gradientId}
|
|
138
|
+
gradientUnits="userSpaceOnUse"
|
|
139
|
+
x1={sourceX}
|
|
140
|
+
y1={sourceY}
|
|
141
|
+
x2={targetX}
|
|
142
|
+
y2={targetY}
|
|
143
|
+
>
|
|
144
|
+
<stop offset="0%" stopColor={colors.source} />
|
|
145
|
+
<stop offset="100%" stopColor={colors.target} />
|
|
146
|
+
</linearGradient>
|
|
147
|
+
</defs>
|
|
148
|
+
<BaseEdge
|
|
149
|
+
id={id}
|
|
150
|
+
path={edgePath}
|
|
151
|
+
style={{
|
|
152
|
+
stroke: `url(#${gradientId})`,
|
|
153
|
+
strokeWidth,
|
|
154
|
+
strokeDasharray: "none",
|
|
155
|
+
}}
|
|
156
|
+
/>
|
|
157
|
+
</g>
|
|
158
|
+
<EdgeLabelRenderer>
|
|
159
|
+
<div
|
|
160
|
+
style={{
|
|
161
|
+
position: "absolute",
|
|
162
|
+
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
|
|
163
|
+
pointerEvents: "all",
|
|
164
|
+
}}
|
|
165
|
+
className="flex flex-col items-center gap-0.5"
|
|
166
|
+
>
|
|
167
|
+
{hasErrors && (
|
|
168
|
+
<Tooltip delayDuration={300}>
|
|
169
|
+
<TooltipTrigger asChild>
|
|
170
|
+
<div className="cursor-help">
|
|
171
|
+
<AlertTriangle className="h-5 w-5 text-destructive fill-background" />
|
|
172
|
+
</div>
|
|
173
|
+
</TooltipTrigger>
|
|
174
|
+
<TooltipContent side="top" className="bg-destructive text-destructive-foreground text-xs px-2 py-1">
|
|
175
|
+
{diagnostics[0]?.message ?? "This edge has errors"}
|
|
176
|
+
</TooltipContent>
|
|
177
|
+
</Tooltip>
|
|
178
|
+
)}
|
|
179
|
+
</div>
|
|
180
|
+
</EdgeLabelRenderer>
|
|
181
|
+
</>
|
|
182
|
+
);
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
export default CustomEdge;
|
package/src/graph/CustomNode.tsx
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { NodeData, NodeRegistry } from "@foresthubai/workflow-core/node";
|
|
2
|
-
import { NodeProps } from "@xyflow/react";
|
|
3
|
-
import { memo, useMemo } from "react";
|
|
4
|
-
import { BaseNode } from "./BaseNode";
|
|
5
|
-
|
|
6
|
-
// Standard node component for all non-FunctionCall nodes
|
|
7
|
-
// Simply resolves the node definition from the registry and delegates to BaseNode
|
|
8
|
-
export const CustomNode = memo((props: NodeProps) => {
|
|
9
|
-
const nodeData = props.data as NodeData;
|
|
10
|
-
|
|
11
|
-
// Get node definition from static registry
|
|
12
|
-
const nodeDefinition = useMemo(() => {
|
|
13
|
-
return NodeRegistry.getByType(nodeData.type);
|
|
14
|
-
}, [nodeData.type]);
|
|
15
|
-
return <BaseNode {...props} nodeDefinition={nodeDefinition} />;
|
|
16
|
-
});
|
|
1
|
+
import { NodeData, NodeRegistry } from "@foresthubai/workflow-core/node";
|
|
2
|
+
import { NodeProps } from "@xyflow/react";
|
|
3
|
+
import { memo, useMemo } from "react";
|
|
4
|
+
import { BaseNode } from "./BaseNode";
|
|
5
|
+
|
|
6
|
+
// Standard node component for all non-FunctionCall nodes
|
|
7
|
+
// Simply resolves the node definition from the registry and delegates to BaseNode
|
|
8
|
+
export const CustomNode = memo((props: NodeProps) => {
|
|
9
|
+
const nodeData = props.data as NodeData;
|
|
10
|
+
|
|
11
|
+
// Get node definition from static registry
|
|
12
|
+
const nodeDefinition = useMemo(() => {
|
|
13
|
+
return NodeRegistry.getByType(nodeData.type);
|
|
14
|
+
}, [nodeData.type]);
|
|
15
|
+
return <BaseNode {...props} nodeDefinition={nodeDefinition} />;
|
|
16
|
+
});
|
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
import { FunctionCallNode as DomainFunctionCallNode } from "@foresthubai/workflow-core/node";
|
|
2
|
-
import { NodeProps } from "@xyflow/react";
|
|
3
|
-
import { memo, useMemo } from "react";
|
|
4
|
-
import { buildFunctionNodeDef } from "../hooks/useNodeDefinitions";
|
|
5
|
-
import { useFunctionRegistry } from "../hooks/useFunctionRegistry";
|
|
6
|
-
import { BaseNode } from "./BaseNode";
|
|
7
|
-
|
|
8
|
-
// Specialized node component for FunctionCall nodes
|
|
9
|
-
// Renders from node's stored data (usually up-to-date via auto-migration,
|
|
10
|
-
// but may be stale after undo)
|
|
11
|
-
export const FunctionCallNode = memo((props: NodeProps) => {
|
|
12
|
-
const nodeData = props.data as DomainFunctionCallNode;
|
|
13
|
-
|
|
14
|
-
// Get function info for staleness check and live label
|
|
15
|
-
const { getFunction } = useFunctionRegistry();
|
|
16
|
-
const registryFunctionInfo = getFunction(nodeData.functionInfo.id);
|
|
17
|
-
|
|
18
|
-
const isDeleted = !registryFunctionInfo;
|
|
19
|
-
const isStale = registryFunctionInfo
|
|
20
|
-
? nodeData.functionInfo.version !== registryFunctionInfo.version
|
|
21
|
-
: false;
|
|
22
|
-
|
|
23
|
-
// Build node definition from node's stored functionInfo, using registry name for label
|
|
24
|
-
const nodeDefinition = useMemo(() => {
|
|
25
|
-
const name = registryFunctionInfo?.name ?? nodeData.functionInfo.name;
|
|
26
|
-
return buildFunctionNodeDef({ ...nodeData.functionInfo, name });
|
|
27
|
-
}, [nodeData.functionInfo, registryFunctionInfo?.name]);
|
|
28
|
-
|
|
29
|
-
return <BaseNode {...props} nodeDefinition={nodeDefinition} isStale={isStale} isDeleted={isDeleted} />;
|
|
30
|
-
});
|
|
1
|
+
import { FunctionCallNode as DomainFunctionCallNode } from "@foresthubai/workflow-core/node";
|
|
2
|
+
import { NodeProps } from "@xyflow/react";
|
|
3
|
+
import { memo, useMemo } from "react";
|
|
4
|
+
import { buildFunctionNodeDef } from "../hooks/useNodeDefinitions";
|
|
5
|
+
import { useFunctionRegistry } from "../hooks/useFunctionRegistry";
|
|
6
|
+
import { BaseNode } from "./BaseNode";
|
|
7
|
+
|
|
8
|
+
// Specialized node component for FunctionCall nodes
|
|
9
|
+
// Renders from node's stored data (usually up-to-date via auto-migration,
|
|
10
|
+
// but may be stale after undo)
|
|
11
|
+
export const FunctionCallNode = memo((props: NodeProps) => {
|
|
12
|
+
const nodeData = props.data as DomainFunctionCallNode;
|
|
13
|
+
|
|
14
|
+
// Get function info for staleness check and live label
|
|
15
|
+
const { getFunction } = useFunctionRegistry();
|
|
16
|
+
const registryFunctionInfo = getFunction(nodeData.functionInfo.id);
|
|
17
|
+
|
|
18
|
+
const isDeleted = !registryFunctionInfo;
|
|
19
|
+
const isStale = registryFunctionInfo
|
|
20
|
+
? nodeData.functionInfo.version !== registryFunctionInfo.version
|
|
21
|
+
: false;
|
|
22
|
+
|
|
23
|
+
// Build node definition from node's stored functionInfo, using registry name for label
|
|
24
|
+
const nodeDefinition = useMemo(() => {
|
|
25
|
+
const name = registryFunctionInfo?.name ?? nodeData.functionInfo.name;
|
|
26
|
+
return buildFunctionNodeDef({ ...nodeData.functionInfo, name });
|
|
27
|
+
}, [nodeData.functionInfo, registryFunctionInfo?.name]);
|
|
28
|
+
|
|
29
|
+
return <BaseNode {...props} nodeDefinition={nodeDefinition} isStale={isStale} isDeleted={isDeleted} />;
|
|
30
|
+
});
|