@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/RightConfigPanel.tsx
CHANGED
|
@@ -1,266 +1,266 @@
|
|
|
1
|
-
import { useCallback, useMemo, type ReactNode } from "react";
|
|
2
|
-
import type { NodeData, NodeDefinition } from "@foresthubai/workflow-core/node";
|
|
3
|
-
import type { EdgeData, EdgeType } from "@foresthubai/workflow-core/edge";
|
|
4
|
-
import { isControlFlow } from "@foresthubai/workflow-core/edge";
|
|
5
|
-
import type { NodeCategory as NodeCategoryEnum } from "@foresthubai/workflow-core/node";
|
|
6
|
-
|
|
7
|
-
import { ScrollArea } from "./components/ui/scroll-area";
|
|
8
|
-
import { cn } from "./cn";
|
|
9
|
-
import { ChannelConfigPanel } from "./panels/ChannelConfigPanel";
|
|
10
|
-
import { DebugExternalIOPanel } from "./panels/DebugExternalIOPanel";
|
|
11
|
-
import { EdgeConfigPanel } from "./panels/EdgeConfigPanel";
|
|
12
|
-
import { FunctionConfigPanel } from "./panels/FunctionConfigPanel";
|
|
13
|
-
import { MemoryConfigPanel } from "./panels/MemoryConfigPanel";
|
|
14
|
-
import { ModelConfigPanel } from "./panels/ModelConfigPanel";
|
|
15
|
-
import { NodeConfigPanel } from "./panels/NodeConfigPanel";
|
|
16
|
-
import { VariableConfigPanel } from "./panels/VariableConfigPanel";
|
|
17
|
-
import { getOrCreateCanvasStore } from "./stores/canvasStore";
|
|
18
|
-
import { useEditorStore } from "./stores/editorStore";
|
|
19
|
-
import { declaredVarKey } from "@foresthubai/workflow-core/variable";
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Right-side selection-routed config panel.
|
|
23
|
-
*
|
|
24
|
-
* Reads the current selection from editorStore (project-wide) and the
|
|
25
|
-
* selected node/edge from the active canvas store, then renders the
|
|
26
|
-
* appropriate config component. Receives graph mutation handlers from
|
|
27
|
-
* BuilderLayout — it never touches the canvas store directly for writes.
|
|
28
|
-
*
|
|
29
|
-
* Hidden while the user is mid selection-drag to avoid flicker.
|
|
30
|
-
*/
|
|
31
|
-
export interface RightConfigPanelProps {
|
|
32
|
-
canvasId: string;
|
|
33
|
-
isDebugMode: boolean;
|
|
34
|
-
selectionDrag: boolean;
|
|
35
|
-
|
|
36
|
-
// Lookups
|
|
37
|
-
getNodeDef: (node: NodeData) => NodeDefinition | undefined;
|
|
38
|
-
|
|
39
|
-
// Mutation handlers (live in BuilderLayout, bound to active canvas)
|
|
40
|
-
onNodeUpdate: (nodeId: string, updates: Partial<NodeData>) => void;
|
|
41
|
-
onNodeDelete: (nodeId: string) => void;
|
|
42
|
-
onEdgeUpdate: (edgeId: string, updates: Partial<EdgeData>) => void;
|
|
43
|
-
onEdgeDelete: (edgeId: string) => void;
|
|
44
|
-
onClearSelection: () => void;
|
|
45
|
-
|
|
46
|
-
// Embedder-fulfilled
|
|
47
|
-
onTestNode?: (nodeId: string) => void;
|
|
48
|
-
onDebugStep?: (nodeId?: string) => void;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export const RightConfigPanel = ({
|
|
52
|
-
canvasId,
|
|
53
|
-
isDebugMode,
|
|
54
|
-
selectionDrag,
|
|
55
|
-
getNodeDef,
|
|
56
|
-
onNodeUpdate,
|
|
57
|
-
onNodeDelete,
|
|
58
|
-
onEdgeUpdate,
|
|
59
|
-
onEdgeDelete,
|
|
60
|
-
onClearSelection,
|
|
61
|
-
onTestNode,
|
|
62
|
-
onDebugStep,
|
|
63
|
-
}: RightConfigPanelProps) => {
|
|
64
|
-
const selection = useEditorStore((s) => s.selection);
|
|
65
|
-
const clearSelection = useEditorStore((s) => s.clearSelection);
|
|
66
|
-
const channels = useEditorStore((s) => s.channels);
|
|
67
|
-
const memory = useEditorStore((s) => s.memory);
|
|
68
|
-
const models = useEditorStore((s) => s.models);
|
|
69
|
-
const functions = useEditorStore((s) => s.functions);
|
|
70
|
-
|
|
71
|
-
const useStore = getOrCreateCanvasStore(canvasId);
|
|
72
|
-
|
|
73
|
-
const selectedNode = useStore(
|
|
74
|
-
useCallback(
|
|
75
|
-
(s) => {
|
|
76
|
-
if (selection.kind !== "graph" || selection.nodeIds.length !== 1) return null;
|
|
77
|
-
const node = s.nodes.find((n) => n.id === selection.nodeIds[0]);
|
|
78
|
-
return node?.data ?? null;
|
|
79
|
-
},
|
|
80
|
-
[selection],
|
|
81
|
-
),
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
const selectedVariable = useStore(
|
|
85
|
-
useCallback(
|
|
86
|
-
(s) => {
|
|
87
|
-
if (selection.kind !== "variable") return null;
|
|
88
|
-
const v = s.variables[declaredVarKey(selection.uid)];
|
|
89
|
-
return v && v.kind === "declared" ? v : null;
|
|
90
|
-
},
|
|
91
|
-
[selection],
|
|
92
|
-
),
|
|
93
|
-
);
|
|
94
|
-
|
|
95
|
-
const selectedEdgeRaw = useStore(
|
|
96
|
-
useCallback(
|
|
97
|
-
(s) => {
|
|
98
|
-
// Edge panel shows only for a lone edge (a node selection takes priority).
|
|
99
|
-
if (selection.kind !== "graph" || selection.edgeIds.length !== 1 || selection.nodeIds.length > 0) return null;
|
|
100
|
-
return s.edges.find((e) => e.id === selection.edgeIds[0]) ?? null;
|
|
101
|
-
},
|
|
102
|
-
[selection],
|
|
103
|
-
),
|
|
104
|
-
);
|
|
105
|
-
const selectedEdge = selectedEdgeRaw
|
|
106
|
-
? {
|
|
107
|
-
id: selectedEdgeRaw.id,
|
|
108
|
-
source: selectedEdgeRaw.source,
|
|
109
|
-
type: (selectedEdgeRaw.type ?? "control") as EdgeType,
|
|
110
|
-
data: (selectedEdgeRaw.data ?? {}) as EdgeData,
|
|
111
|
-
}
|
|
112
|
-
: null;
|
|
113
|
-
|
|
114
|
-
const sourceControlEdgeCount = useStore(
|
|
115
|
-
useCallback(
|
|
116
|
-
(s) => {
|
|
117
|
-
if (!selectedEdge) return 0;
|
|
118
|
-
return s.edges.filter((e) => e.source === selectedEdge.source && isControlFlow(e.type as EdgeType)).length;
|
|
119
|
-
},
|
|
120
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
121
|
-
[selectedEdge?.source],
|
|
122
|
-
),
|
|
123
|
-
);
|
|
124
|
-
|
|
125
|
-
const selectedChannel = useMemo(
|
|
126
|
-
() => (selection.kind === "channel" ? (Object.values(channels).find((v) => v.id === selection.id) ?? null) : null),
|
|
127
|
-
[selection, channels],
|
|
128
|
-
);
|
|
129
|
-
|
|
130
|
-
const selectedMemory = useMemo(
|
|
131
|
-
() => (selection.kind === "memory" ? (Object.values(memory).find((m) => m.id === selection.id) ?? null) : null),
|
|
132
|
-
[selection, memory],
|
|
133
|
-
);
|
|
134
|
-
|
|
135
|
-
const selectedModel = useMemo(
|
|
136
|
-
() => (selection.kind === "model" ? (Object.values(models).find((m) => m.id === selection.id) ?? null) : null),
|
|
137
|
-
[selection, models],
|
|
138
|
-
);
|
|
139
|
-
|
|
140
|
-
const selectedFunction = useMemo(
|
|
141
|
-
() => (selection.kind === "function" ? (functions[selection.id] ?? null) : null),
|
|
142
|
-
[selection, functions],
|
|
143
|
-
);
|
|
144
|
-
|
|
145
|
-
const getNodeCategory = useCallback(
|
|
146
|
-
(node: NodeData) => getNodeDef(node)?.category as NodeCategoryEnum | undefined,
|
|
147
|
-
[getNodeDef],
|
|
148
|
-
);
|
|
149
|
-
|
|
150
|
-
const handleTestNode = useCallback((nodeId: string) => onTestNode?.(nodeId), [onTestNode]);
|
|
151
|
-
|
|
152
|
-
if (selectionDrag) return null;
|
|
153
|
-
|
|
154
|
-
if (isDebugMode) {
|
|
155
|
-
if (!selectedNode) return null;
|
|
156
|
-
return (
|
|
157
|
-
<Shell bg="bg-background" pad>
|
|
158
|
-
<DebugExternalIOPanel
|
|
159
|
-
canvasId={canvasId}
|
|
160
|
-
onStep={onDebugStep ?? (() => {})}
|
|
161
|
-
getNodeCategory={getNodeCategory}
|
|
162
|
-
/>
|
|
163
|
-
</Shell>
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (selectedNode) {
|
|
168
|
-
return (
|
|
169
|
-
<Shell>
|
|
170
|
-
<NodeConfigPanel
|
|
171
|
-
canvasId={canvasId}
|
|
172
|
-
selectedNode={selectedNode}
|
|
173
|
-
onNodeUpdate={onNodeUpdate}
|
|
174
|
-
onNodeDelete={onNodeDelete}
|
|
175
|
-
onClose={onClearSelection}
|
|
176
|
-
onOpenTest={handleTestNode}
|
|
177
|
-
getNodeDef={getNodeDef}
|
|
178
|
-
/>
|
|
179
|
-
</Shell>
|
|
180
|
-
);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
if (selectedEdge) {
|
|
184
|
-
return (
|
|
185
|
-
<Shell>
|
|
186
|
-
<EdgeConfigPanel
|
|
187
|
-
canvasId={canvasId}
|
|
188
|
-
edgeId={selectedEdge.id}
|
|
189
|
-
edgeType={selectedEdge.type}
|
|
190
|
-
edgeData={selectedEdge.data}
|
|
191
|
-
sourceControlEdgeCount={sourceControlEdgeCount}
|
|
192
|
-
onEdgeUpdate={onEdgeUpdate}
|
|
193
|
-
onEdgeDelete={onEdgeDelete}
|
|
194
|
-
onClose={onClearSelection}
|
|
195
|
-
/>
|
|
196
|
-
</Shell>
|
|
197
|
-
);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (selectedVariable) {
|
|
201
|
-
return (
|
|
202
|
-
<Shell>
|
|
203
|
-
<VariableConfigPanel
|
|
204
|
-
canvasId={canvasId}
|
|
205
|
-
variable={selectedVariable}
|
|
206
|
-
onClose={clearSelection}
|
|
207
|
-
/>
|
|
208
|
-
</Shell>
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
if (selectedChannel) {
|
|
213
|
-
return (
|
|
214
|
-
<Shell>
|
|
215
|
-
<ChannelConfigPanel channel={selectedChannel} onClose={clearSelection} />
|
|
216
|
-
</Shell>
|
|
217
|
-
);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (selectedMemory) {
|
|
221
|
-
return (
|
|
222
|
-
<Shell>
|
|
223
|
-
<MemoryConfigPanel memory={selectedMemory} onClose={clearSelection} />
|
|
224
|
-
</Shell>
|
|
225
|
-
);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
if (selectedModel) {
|
|
229
|
-
return (
|
|
230
|
-
<Shell>
|
|
231
|
-
<ModelConfigPanel model={selectedModel} onClose={clearSelection} />
|
|
232
|
-
</Shell>
|
|
233
|
-
);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
if (selectedFunction) {
|
|
237
|
-
return (
|
|
238
|
-
<Shell>
|
|
239
|
-
<FunctionConfigPanel func={selectedFunction} onClose={clearSelection} />
|
|
240
|
-
</Shell>
|
|
241
|
-
);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
return null;
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Right-panel chrome — fixed-width column with the bordered card background
|
|
249
|
-
* and an overlay scrollbar. Extracted because all eight selection branches
|
|
250
|
-
* render the same shell; keeping them in lockstep by hand was an obvious
|
|
251
|
-
* drift risk. The debug variant overrides the surface and adds inner padding
|
|
252
|
-
* (other variants pad inside their own ConfigPanel).
|
|
253
|
-
*/
|
|
254
|
-
const Shell = ({
|
|
255
|
-
bg = "bg-card",
|
|
256
|
-
pad = false,
|
|
257
|
-
children,
|
|
258
|
-
}: {
|
|
259
|
-
bg?: string;
|
|
260
|
-
pad?: boolean;
|
|
261
|
-
children: ReactNode;
|
|
262
|
-
}) => (
|
|
263
|
-
<ScrollArea className={cn("w-80 border-l border-border", bg)} viewportClassName={pad ? "p-3" : undefined}>
|
|
264
|
-
{children}
|
|
265
|
-
</ScrollArea>
|
|
266
|
-
);
|
|
1
|
+
import { useCallback, useMemo, type ReactNode } from "react";
|
|
2
|
+
import type { NodeData, NodeDefinition } from "@foresthubai/workflow-core/node";
|
|
3
|
+
import type { EdgeData, EdgeType } from "@foresthubai/workflow-core/edge";
|
|
4
|
+
import { isControlFlow } from "@foresthubai/workflow-core/edge";
|
|
5
|
+
import type { NodeCategory as NodeCategoryEnum } from "@foresthubai/workflow-core/node";
|
|
6
|
+
|
|
7
|
+
import { ScrollArea } from "./components/ui/scroll-area";
|
|
8
|
+
import { cn } from "./cn";
|
|
9
|
+
import { ChannelConfigPanel } from "./panels/ChannelConfigPanel";
|
|
10
|
+
import { DebugExternalIOPanel } from "./panels/DebugExternalIOPanel";
|
|
11
|
+
import { EdgeConfigPanel } from "./panels/EdgeConfigPanel";
|
|
12
|
+
import { FunctionConfigPanel } from "./panels/FunctionConfigPanel";
|
|
13
|
+
import { MemoryConfigPanel } from "./panels/MemoryConfigPanel";
|
|
14
|
+
import { ModelConfigPanel } from "./panels/ModelConfigPanel";
|
|
15
|
+
import { NodeConfigPanel } from "./panels/NodeConfigPanel";
|
|
16
|
+
import { VariableConfigPanel } from "./panels/VariableConfigPanel";
|
|
17
|
+
import { getOrCreateCanvasStore } from "./stores/canvasStore";
|
|
18
|
+
import { useEditorStore } from "./stores/editorStore";
|
|
19
|
+
import { declaredVarKey } from "@foresthubai/workflow-core/variable";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Right-side selection-routed config panel.
|
|
23
|
+
*
|
|
24
|
+
* Reads the current selection from editorStore (project-wide) and the
|
|
25
|
+
* selected node/edge from the active canvas store, then renders the
|
|
26
|
+
* appropriate config component. Receives graph mutation handlers from
|
|
27
|
+
* BuilderLayout — it never touches the canvas store directly for writes.
|
|
28
|
+
*
|
|
29
|
+
* Hidden while the user is mid selection-drag to avoid flicker.
|
|
30
|
+
*/
|
|
31
|
+
export interface RightConfigPanelProps {
|
|
32
|
+
canvasId: string;
|
|
33
|
+
isDebugMode: boolean;
|
|
34
|
+
selectionDrag: boolean;
|
|
35
|
+
|
|
36
|
+
// Lookups
|
|
37
|
+
getNodeDef: (node: NodeData) => NodeDefinition | undefined;
|
|
38
|
+
|
|
39
|
+
// Mutation handlers (live in BuilderLayout, bound to active canvas)
|
|
40
|
+
onNodeUpdate: (nodeId: string, updates: Partial<NodeData>) => void;
|
|
41
|
+
onNodeDelete: (nodeId: string) => void;
|
|
42
|
+
onEdgeUpdate: (edgeId: string, updates: Partial<EdgeData>) => void;
|
|
43
|
+
onEdgeDelete: (edgeId: string) => void;
|
|
44
|
+
onClearSelection: () => void;
|
|
45
|
+
|
|
46
|
+
// Embedder-fulfilled
|
|
47
|
+
onTestNode?: (nodeId: string) => void;
|
|
48
|
+
onDebugStep?: (nodeId?: string) => void;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const RightConfigPanel = ({
|
|
52
|
+
canvasId,
|
|
53
|
+
isDebugMode,
|
|
54
|
+
selectionDrag,
|
|
55
|
+
getNodeDef,
|
|
56
|
+
onNodeUpdate,
|
|
57
|
+
onNodeDelete,
|
|
58
|
+
onEdgeUpdate,
|
|
59
|
+
onEdgeDelete,
|
|
60
|
+
onClearSelection,
|
|
61
|
+
onTestNode,
|
|
62
|
+
onDebugStep,
|
|
63
|
+
}: RightConfigPanelProps) => {
|
|
64
|
+
const selection = useEditorStore((s) => s.selection);
|
|
65
|
+
const clearSelection = useEditorStore((s) => s.clearSelection);
|
|
66
|
+
const channels = useEditorStore((s) => s.channels);
|
|
67
|
+
const memory = useEditorStore((s) => s.memory);
|
|
68
|
+
const models = useEditorStore((s) => s.models);
|
|
69
|
+
const functions = useEditorStore((s) => s.functions);
|
|
70
|
+
|
|
71
|
+
const useStore = getOrCreateCanvasStore(canvasId);
|
|
72
|
+
|
|
73
|
+
const selectedNode = useStore(
|
|
74
|
+
useCallback(
|
|
75
|
+
(s) => {
|
|
76
|
+
if (selection.kind !== "graph" || selection.nodeIds.length !== 1) return null;
|
|
77
|
+
const node = s.nodes.find((n) => n.id === selection.nodeIds[0]);
|
|
78
|
+
return node?.data ?? null;
|
|
79
|
+
},
|
|
80
|
+
[selection],
|
|
81
|
+
),
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const selectedVariable = useStore(
|
|
85
|
+
useCallback(
|
|
86
|
+
(s) => {
|
|
87
|
+
if (selection.kind !== "variable") return null;
|
|
88
|
+
const v = s.variables[declaredVarKey(selection.uid)];
|
|
89
|
+
return v && v.kind === "declared" ? v : null;
|
|
90
|
+
},
|
|
91
|
+
[selection],
|
|
92
|
+
),
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const selectedEdgeRaw = useStore(
|
|
96
|
+
useCallback(
|
|
97
|
+
(s) => {
|
|
98
|
+
// Edge panel shows only for a lone edge (a node selection takes priority).
|
|
99
|
+
if (selection.kind !== "graph" || selection.edgeIds.length !== 1 || selection.nodeIds.length > 0) return null;
|
|
100
|
+
return s.edges.find((e) => e.id === selection.edgeIds[0]) ?? null;
|
|
101
|
+
},
|
|
102
|
+
[selection],
|
|
103
|
+
),
|
|
104
|
+
);
|
|
105
|
+
const selectedEdge = selectedEdgeRaw
|
|
106
|
+
? {
|
|
107
|
+
id: selectedEdgeRaw.id,
|
|
108
|
+
source: selectedEdgeRaw.source,
|
|
109
|
+
type: (selectedEdgeRaw.type ?? "control") as EdgeType,
|
|
110
|
+
data: (selectedEdgeRaw.data ?? {}) as EdgeData,
|
|
111
|
+
}
|
|
112
|
+
: null;
|
|
113
|
+
|
|
114
|
+
const sourceControlEdgeCount = useStore(
|
|
115
|
+
useCallback(
|
|
116
|
+
(s) => {
|
|
117
|
+
if (!selectedEdge) return 0;
|
|
118
|
+
return s.edges.filter((e) => e.source === selectedEdge.source && isControlFlow(e.type as EdgeType)).length;
|
|
119
|
+
},
|
|
120
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
121
|
+
[selectedEdge?.source],
|
|
122
|
+
),
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const selectedChannel = useMemo(
|
|
126
|
+
() => (selection.kind === "channel" ? (Object.values(channels).find((v) => v.id === selection.id) ?? null) : null),
|
|
127
|
+
[selection, channels],
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const selectedMemory = useMemo(
|
|
131
|
+
() => (selection.kind === "memory" ? (Object.values(memory).find((m) => m.id === selection.id) ?? null) : null),
|
|
132
|
+
[selection, memory],
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
const selectedModel = useMemo(
|
|
136
|
+
() => (selection.kind === "model" ? (Object.values(models).find((m) => m.id === selection.id) ?? null) : null),
|
|
137
|
+
[selection, models],
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
const selectedFunction = useMemo(
|
|
141
|
+
() => (selection.kind === "function" ? (functions[selection.id] ?? null) : null),
|
|
142
|
+
[selection, functions],
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
const getNodeCategory = useCallback(
|
|
146
|
+
(node: NodeData) => getNodeDef(node)?.category as NodeCategoryEnum | undefined,
|
|
147
|
+
[getNodeDef],
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const handleTestNode = useCallback((nodeId: string) => onTestNode?.(nodeId), [onTestNode]);
|
|
151
|
+
|
|
152
|
+
if (selectionDrag) return null;
|
|
153
|
+
|
|
154
|
+
if (isDebugMode) {
|
|
155
|
+
if (!selectedNode) return null;
|
|
156
|
+
return (
|
|
157
|
+
<Shell bg="bg-background" pad>
|
|
158
|
+
<DebugExternalIOPanel
|
|
159
|
+
canvasId={canvasId}
|
|
160
|
+
onStep={onDebugStep ?? (() => {})}
|
|
161
|
+
getNodeCategory={getNodeCategory}
|
|
162
|
+
/>
|
|
163
|
+
</Shell>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (selectedNode) {
|
|
168
|
+
return (
|
|
169
|
+
<Shell>
|
|
170
|
+
<NodeConfigPanel
|
|
171
|
+
canvasId={canvasId}
|
|
172
|
+
selectedNode={selectedNode}
|
|
173
|
+
onNodeUpdate={onNodeUpdate}
|
|
174
|
+
onNodeDelete={onNodeDelete}
|
|
175
|
+
onClose={onClearSelection}
|
|
176
|
+
onOpenTest={handleTestNode}
|
|
177
|
+
getNodeDef={getNodeDef}
|
|
178
|
+
/>
|
|
179
|
+
</Shell>
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (selectedEdge) {
|
|
184
|
+
return (
|
|
185
|
+
<Shell>
|
|
186
|
+
<EdgeConfigPanel
|
|
187
|
+
canvasId={canvasId}
|
|
188
|
+
edgeId={selectedEdge.id}
|
|
189
|
+
edgeType={selectedEdge.type}
|
|
190
|
+
edgeData={selectedEdge.data}
|
|
191
|
+
sourceControlEdgeCount={sourceControlEdgeCount}
|
|
192
|
+
onEdgeUpdate={onEdgeUpdate}
|
|
193
|
+
onEdgeDelete={onEdgeDelete}
|
|
194
|
+
onClose={onClearSelection}
|
|
195
|
+
/>
|
|
196
|
+
</Shell>
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (selectedVariable) {
|
|
201
|
+
return (
|
|
202
|
+
<Shell>
|
|
203
|
+
<VariableConfigPanel
|
|
204
|
+
canvasId={canvasId}
|
|
205
|
+
variable={selectedVariable}
|
|
206
|
+
onClose={clearSelection}
|
|
207
|
+
/>
|
|
208
|
+
</Shell>
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (selectedChannel) {
|
|
213
|
+
return (
|
|
214
|
+
<Shell>
|
|
215
|
+
<ChannelConfigPanel channel={selectedChannel} onClose={clearSelection} />
|
|
216
|
+
</Shell>
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (selectedMemory) {
|
|
221
|
+
return (
|
|
222
|
+
<Shell>
|
|
223
|
+
<MemoryConfigPanel memory={selectedMemory} onClose={clearSelection} />
|
|
224
|
+
</Shell>
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (selectedModel) {
|
|
229
|
+
return (
|
|
230
|
+
<Shell>
|
|
231
|
+
<ModelConfigPanel model={selectedModel} onClose={clearSelection} />
|
|
232
|
+
</Shell>
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (selectedFunction) {
|
|
237
|
+
return (
|
|
238
|
+
<Shell>
|
|
239
|
+
<FunctionConfigPanel func={selectedFunction} onClose={clearSelection} />
|
|
240
|
+
</Shell>
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return null;
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Right-panel chrome — fixed-width column with the bordered card background
|
|
249
|
+
* and an overlay scrollbar. Extracted because all eight selection branches
|
|
250
|
+
* render the same shell; keeping them in lockstep by hand was an obvious
|
|
251
|
+
* drift risk. The debug variant overrides the surface and adds inner padding
|
|
252
|
+
* (other variants pad inside their own ConfigPanel).
|
|
253
|
+
*/
|
|
254
|
+
const Shell = ({
|
|
255
|
+
bg = "bg-card",
|
|
256
|
+
pad = false,
|
|
257
|
+
children,
|
|
258
|
+
}: {
|
|
259
|
+
bg?: string;
|
|
260
|
+
pad?: boolean;
|
|
261
|
+
children: ReactNode;
|
|
262
|
+
}) => (
|
|
263
|
+
<ScrollArea className={cn("w-80 border-l border-border", bg)} viewportClassName={pad ? "p-3" : undefined}>
|
|
264
|
+
{children}
|
|
265
|
+
</ScrollArea>
|
|
266
|
+
);
|