@iblai/web-containers 1.51.1 → 1.52.1

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.
Files changed (111) hide show
  1. package/dist/cjs/components/analytics/analytics-report-download.js +10 -4
  2. package/dist/cjs/components/analytics/analytics-report-download.js.map +1 -1
  3. package/dist/cjs/components/analytics/analytics-reports.js +12 -57
  4. package/dist/cjs/components/analytics/analytics-reports.js.map +1 -1
  5. package/dist/cjs/components/analytics/use-reports.js +4 -2
  6. package/dist/cjs/components/analytics/use-reports.js.map +1 -1
  7. package/dist/cjs/components/chat/loading-message.js +13 -0
  8. package/dist/cjs/components/chat/loading-message.js.map +1 -0
  9. package/dist/cjs/components/mentors/mentor-selection-grid.js +4 -2
  10. package/dist/cjs/components/mentors/mentor-selection-grid.js.map +1 -1
  11. package/dist/cjs/components/modals/edit-mentor-modal/tabs/mcp-tab/connector-management-content.js +9 -5
  12. package/dist/cjs/components/modals/edit-mentor-modal/tabs/mcp-tab/connector-management-content.js.map +1 -1
  13. package/dist/cjs/components/profile/chat-history/use-chat-history-export.js +8 -2
  14. package/dist/cjs/components/profile/chat-history/use-chat-history-export.js.map +1 -1
  15. package/dist/cjs/components/profile/invite/courses-tab.js +3 -4
  16. package/dist/cjs/components/profile/invite/courses-tab.js.map +1 -1
  17. package/dist/cjs/components/profile/invite/programs-tab.js +3 -4
  18. package/dist/cjs/components/profile/invite/programs-tab.js.map +1 -1
  19. package/dist/cjs/components/profile/invite/users-tab.js +3 -4
  20. package/dist/cjs/components/profile/invite/users-tab.js.map +1 -1
  21. package/dist/cjs/components/workflows/action-tooltip.js +11 -0
  22. package/dist/cjs/components/workflows/action-tooltip.js.map +1 -0
  23. package/dist/cjs/components/workflows/node-config-panel.js +576 -0
  24. package/dist/cjs/components/workflows/node-config-panel.js.map +1 -0
  25. package/dist/cjs/components/workflows/tool-dialogs.js +95 -60
  26. package/dist/cjs/components/workflows/tool-dialogs.js.map +1 -1
  27. package/dist/cjs/components/workflows/workflow-canvas.js +1028 -0
  28. package/dist/cjs/components/workflows/workflow-canvas.js.map +1 -0
  29. package/dist/cjs/components/workflows/workflow-definition.js +369 -0
  30. package/dist/cjs/components/workflows/workflow-definition.js.map +1 -0
  31. package/dist/cjs/components/workflows/workflow-editor.js +523 -0
  32. package/dist/cjs/components/workflows/workflow-editor.js.map +1 -0
  33. package/dist/cjs/components/workflows/workflow-preview-chat.js +57 -0
  34. package/dist/cjs/components/workflows/workflow-preview-chat.js.map +1 -0
  35. package/dist/cjs/components/workflows/workflow-sidebar.js +2 -2
  36. package/dist/cjs/components/workflows/workflow-sidebar.js.map +1 -1
  37. package/dist/cjs/components/workflows/workflows-list.js +115 -0
  38. package/dist/cjs/components/workflows/workflows-list.js.map +1 -0
  39. package/dist/cjs/i18n/messages/en.json.js +207 -18
  40. package/dist/cjs/i18n/messages/en.json.js.map +1 -1
  41. package/dist/cjs/i18n/messages/es.json.js +201 -12
  42. package/dist/cjs/i18n/messages/es.json.js.map +1 -1
  43. package/dist/cjs/i18n/messages/fr.json.js +201 -12
  44. package/dist/cjs/i18n/messages/fr.json.js.map +1 -1
  45. package/dist/cjs/i18n/messages/zh.json.js +201 -12
  46. package/dist/cjs/i18n/messages/zh.json.js.map +1 -1
  47. package/dist/cjs/index.js +17 -0
  48. package/dist/cjs/index.js.map +1 -1
  49. package/dist/cjs/next/index.js +10 -0
  50. package/dist/cjs/next/index.js.map +1 -1
  51. package/dist/cjs/web-containers/tsconfig.tsbuildinfo +1 -1
  52. package/dist/esm/_virtual/index2.js +2 -2
  53. package/dist/esm/_virtual/index3.js +2 -2
  54. package/dist/esm/components/analytics/analytics-report-download.js +10 -4
  55. package/dist/esm/components/analytics/analytics-report-download.js.map +1 -1
  56. package/dist/esm/components/analytics/analytics-reports.js +11 -56
  57. package/dist/esm/components/analytics/analytics-reports.js.map +1 -1
  58. package/dist/esm/components/analytics/use-reports.js +5 -3
  59. package/dist/esm/components/analytics/use-reports.js.map +1 -1
  60. package/dist/esm/components/chat/loading-message.js +11 -0
  61. package/dist/esm/components/chat/loading-message.js.map +1 -0
  62. package/dist/esm/components/mentors/mentor-selection-grid.js +4 -2
  63. package/dist/esm/components/mentors/mentor-selection-grid.js.map +1 -1
  64. package/dist/esm/components/modals/edit-mentor-modal/tabs/mcp-tab/connector-management-content.js +9 -5
  65. package/dist/esm/components/modals/edit-mentor-modal/tabs/mcp-tab/connector-management-content.js.map +1 -1
  66. package/dist/esm/components/profile/chat-history/use-chat-history-export.js +8 -2
  67. package/dist/esm/components/profile/chat-history/use-chat-history-export.js.map +1 -1
  68. package/dist/esm/components/profile/invite/courses-tab.js +3 -4
  69. package/dist/esm/components/profile/invite/courses-tab.js.map +1 -1
  70. package/dist/esm/components/profile/invite/programs-tab.js +3 -4
  71. package/dist/esm/components/profile/invite/programs-tab.js.map +1 -1
  72. package/dist/esm/components/profile/invite/users-tab.js +3 -4
  73. package/dist/esm/components/profile/invite/users-tab.js.map +1 -1
  74. package/dist/esm/components/workflows/action-tooltip.js +9 -0
  75. package/dist/esm/components/workflows/action-tooltip.js.map +1 -0
  76. package/dist/esm/components/workflows/node-config-panel.js +574 -0
  77. package/dist/esm/components/workflows/node-config-panel.js.map +1 -0
  78. package/dist/esm/components/workflows/tool-dialogs.js +96 -61
  79. package/dist/esm/components/workflows/tool-dialogs.js.map +1 -1
  80. package/dist/esm/components/workflows/workflow-canvas.js +1026 -0
  81. package/dist/esm/components/workflows/workflow-canvas.js.map +1 -0
  82. package/dist/esm/components/workflows/workflow-definition.js +352 -0
  83. package/dist/esm/components/workflows/workflow-definition.js.map +1 -0
  84. package/dist/esm/components/workflows/workflow-editor.js +521 -0
  85. package/dist/esm/components/workflows/workflow-editor.js.map +1 -0
  86. package/dist/esm/components/workflows/workflow-preview-chat.js +55 -0
  87. package/dist/esm/components/workflows/workflow-preview-chat.js.map +1 -0
  88. package/dist/esm/components/workflows/workflow-sidebar.js +2 -2
  89. package/dist/esm/components/workflows/workflow-sidebar.js.map +1 -1
  90. package/dist/esm/components/workflows/workflows-list.js +113 -0
  91. package/dist/esm/components/workflows/workflows-list.js.map +1 -0
  92. package/dist/esm/i18n/messages/en.json.js +203 -19
  93. package/dist/esm/i18n/messages/en.json.js.map +1 -1
  94. package/dist/esm/i18n/messages/es.json.js +197 -13
  95. package/dist/esm/i18n/messages/es.json.js.map +1 -1
  96. package/dist/esm/i18n/messages/fr.json.js +197 -13
  97. package/dist/esm/i18n/messages/fr.json.js.map +1 -1
  98. package/dist/esm/i18n/messages/zh.json.js +197 -13
  99. package/dist/esm/i18n/messages/zh.json.js.map +1 -1
  100. package/dist/esm/index.js +1 -0
  101. package/dist/esm/index.js.map +1 -1
  102. package/dist/esm/next/index.js +5 -0
  103. package/dist/esm/next/index.js.map +1 -1
  104. package/dist/esm/node_modules/.pnpm/css-mediaquery@0.1.2/node_modules/css-mediaquery/index.js +1 -1
  105. package/dist/esm/node_modules/.pnpm/matchmediaquery@0.4.2/node_modules/matchmediaquery/index.js +1 -1
  106. package/dist/esm/node_modules/.pnpm/prop-types@15.8.1/node_modules/prop-types/index.js +1 -1
  107. package/dist/esm/web-containers/tsconfig.tsbuildinfo +1 -1
  108. package/dist/index.d.ts +164 -19
  109. package/dist/next/index.d.ts +215 -3
  110. package/dist/package.json +1 -1
  111. package/package.json +4 -4
@@ -0,0 +1,1026 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { useState, useRef, useEffect, useCallback, useMemo } from 'react';
3
+ import { useLazyGetMentorSettingsQuery } from '@iblai/data-layer';
4
+ import { useT } from '../../i18n/context.js';
5
+ import '../../packages/i18n/dist/index.esm.js';
6
+ import { MentorSelectionGrid } from '../mentors/mentor-selection-grid.js';
7
+ import { Button } from '../ui/button.js';
8
+ import { Dialog, DialogContent, DialogHeader, DialogTitle } from '../ui/dialog.js';
9
+ import { Textarea } from '../ui/textarea.js';
10
+ import { ActionTooltip } from './action-tooltip.js';
11
+ import { NodeConfigPanel } from './node-config-panel.js';
12
+ import { canReceiveConnection, edgeIdFor, collectWorkflowVariables, collectVariableUsages, WHILE_LOOP_HANDLE, WHILE_DONE_HANDLE, getConditionCount, reindexConditionEdges, getConditions } from './workflow-definition.js';
13
+ import X from '../../node_modules/.pnpm/lucide-react@0.528.0_react@19.1.0/node_modules/lucide-react/dist/esm/icons/x.js';
14
+ import Trash2 from '../../node_modules/.pnpm/lucide-react@0.528.0_react@19.1.0/node_modules/lucide-react/dist/esm/icons/trash-2.js';
15
+ import Play from '../../node_modules/.pnpm/lucide-react@0.528.0_react@19.1.0/node_modules/lucide-react/dist/esm/icons/play.js';
16
+ import RefreshCw from '../../node_modules/.pnpm/lucide-react@0.528.0_react@19.1.0/node_modules/lucide-react/dist/esm/icons/refresh-cw.js';
17
+ import GitBranch from '../../node_modules/.pnpm/lucide-react@0.528.0_react@19.1.0/node_modules/lucide-react/dist/esm/icons/git-branch.js';
18
+ import ThumbsUp from '../../node_modules/.pnpm/lucide-react@0.528.0_react@19.1.0/node_modules/lucide-react/dist/esm/icons/thumbs-up.js';
19
+ import Bot from '../../node_modules/.pnpm/lucide-react@0.528.0_react@19.1.0/node_modules/lucide-react/dist/esm/icons/bot.js';
20
+ import Pencil from '../../node_modules/.pnpm/lucide-react@0.528.0_react@19.1.0/node_modules/lucide-react/dist/esm/icons/pencil.js';
21
+ import Hand from '../../node_modules/.pnpm/lucide-react@0.528.0_react@19.1.0/node_modules/lucide-react/dist/esm/icons/hand.js';
22
+ import MousePointer from '../../node_modules/.pnpm/lucide-react@0.528.0_react@19.1.0/node_modules/lucide-react/dist/esm/icons/mouse-pointer.js';
23
+ import Undo from '../../node_modules/.pnpm/lucide-react@0.528.0_react@19.1.0/node_modules/lucide-react/dist/esm/icons/undo.js';
24
+ import Redo from '../../node_modules/.pnpm/lucide-react@0.528.0_react@19.1.0/node_modules/lucide-react/dist/esm/icons/redo.js';
25
+ import ZoomOut from '../../node_modules/.pnpm/lucide-react@0.528.0_react@19.1.0/node_modules/lucide-react/dist/esm/icons/zoom-out.js';
26
+ import ZoomIn from '../../node_modules/.pnpm/lucide-react@0.528.0_react@19.1.0/node_modules/lucide-react/dist/esm/icons/zoom-in.js';
27
+
28
+ const DEFAULT_MENTOR_LABELS = new Set([
29
+ 'my agent',
30
+ 'agent',
31
+ 'agent node',
32
+ 'my mentor',
33
+ 'mentor',
34
+ 'mentor node',
35
+ ]);
36
+ const isDefaultMentorLabel = (label) => {
37
+ if (!label)
38
+ return true;
39
+ return DEFAULT_MENTOR_LABELS.has(label.trim().toLowerCase());
40
+ };
41
+ // API may use 'conditional' as the node type key for if-else nodes
42
+ const isConditionalType = (type) => type === 'if-else' || type === 'conditional';
43
+ // Cards with one labelled row per outgoing branch.
44
+ const isBranchingType = (type) => isConditionalType(type) || type === 'while' || type === 'user-approval';
45
+ // Branch rows sit under a 68px header, 44px tall with an 8px gap.
46
+ const branchRowCenter = (index) => 90 + index * 52;
47
+ const branchCardHeight = (rows) => 76 + rows * 52;
48
+ // Rendered card sizes, for placing new steps and for box selection.
49
+ const nodeSize = (node) => {
50
+ if (isConditionalType(node.type)) {
51
+ const conditions = node.data ? getConditionCount(node.data) : 1;
52
+ return { width: 280, height: branchCardHeight(conditions + 1) };
53
+ }
54
+ if (isBranchingType(node.type)) {
55
+ return { width: 280, height: branchCardHeight(2) };
56
+ }
57
+ if (node.type === 'note') {
58
+ return { width: node.width || 200, height: node.height || 120 };
59
+ }
60
+ return { width: 140, height: 50 };
61
+ };
62
+ const SIDE_HANDLES = new Set(['left', 'right', 'top', 'bottom']);
63
+ // If/Else rows, While Loop/Done and Approve/Reject, as opposed to a plain
64
+ // step's side points.
65
+ const isBranchHandle = (handle) => !SIDE_HANDLES.has(handle);
66
+ // The only steps the backend runs that write to step_results.
67
+ const STEPS_WITH_RESULTS = new Set(['mentor', 'transform', 'set-state', 'while']);
68
+ const NODE_PANEL_SELECTOR = '[data-testid="workflow-node-panel"]';
69
+ // The floating zoom/undo toolbar covers the bottom of the canvas.
70
+ const TOOLBAR_CLEARANCE = 80;
71
+ const isDeletableNode = (node) => node.type !== 'start' && node.deletable !== false;
72
+ // Helper to strip UI-only properties from nodes for comparison/saving
73
+ function stripUiProperties(nodes) {
74
+ return nodes.map(({ selected, dragging, hidden, ...rest }) => rest);
75
+ }
76
+ // Helper to strip UI-only properties from edges for comparison/saving
77
+ function stripEdgeUiProperties(edges) {
78
+ return edges.map(({ selected, hidden, ...rest }) => rest);
79
+ }
80
+ // Default nodes for new workflows
81
+ const DEFAULT_NODES = [
82
+ {
83
+ id: 'start',
84
+ type: 'start',
85
+ position: { x: 300, y: 250 },
86
+ data: { label: 'Start' },
87
+ draggable: true,
88
+ selectable: true,
89
+ connectable: true,
90
+ },
91
+ {
92
+ id: 'mentor-1',
93
+ type: 'mentor',
94
+ position: { x: 550, y: 250 },
95
+ data: { label: 'My agent', subtitle: 'Agent', color: 'chart-1' },
96
+ draggable: true,
97
+ selectable: true,
98
+ connectable: true,
99
+ },
100
+ ];
101
+ const DEFAULT_EDGES = [
102
+ {
103
+ id: 'e-start-mentor-1',
104
+ source: 'start',
105
+ target: 'mentor-1',
106
+ sourceHandle: 'right',
107
+ targetHandle: 'left',
108
+ },
109
+ ];
110
+ function WorkflowCanvas({ onDraggedItem: _onDraggedItem, onClickedItem, previewMode = false, initialNodes, initialEdges, initialViewport, onStateChange, org, defaultMentorId, username, onOpenAgentSettings, agentSettings, }) {
111
+ var _a;
112
+ const t = useT();
113
+ // Use initialNodes if provided (from API), otherwise use defaults for new workflows
114
+ const [nodes, setNodes] = useState(initialNodes && initialNodes.length > 0 ? initialNodes : DEFAULT_NODES);
115
+ const [edges, setEdges] = useState(initialEdges ? initialEdges : DEFAULT_EDGES);
116
+ const [tool, setTool] = useState('hand');
117
+ const [zoom, setZoom] = useState((initialViewport === null || initialViewport === void 0 ? void 0 : initialViewport.zoom) || 1);
118
+ const [history, setHistory] = useState([
119
+ { nodes: [...nodes], edges: [...edges] },
120
+ ]);
121
+ const [historyIndex, setHistoryIndex] = useState(0);
122
+ // Saves can run after an await (mentor prefill); a closed-over index would
123
+ // cut off entries added in the meantime.
124
+ const historyIndexRef = useRef(0);
125
+ const [draggedNode, setDraggedNode] = useState(null);
126
+ const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
127
+ const [isPanning, setIsPanning] = useState(false);
128
+ const [panOffset, setPanOffset] = useState(initialViewport ? { x: initialViewport.x, y: initialViewport.y } : { x: 0, y: 0 });
129
+ const [panStart, setPanStart] = useState({ x: 0, y: 0 });
130
+ const [hoveredNode, setHoveredNode] = useState(null);
131
+ const [connectingFrom, setConnectingFrom] = useState(null);
132
+ const [tempConnectionPos, setTempConnectionPos] = useState(null);
133
+ const [editingNote, setEditingNote] = useState(null);
134
+ const [resizingNote, setResizingNote] = useState(null);
135
+ const [selectionBox, setSelectionBox] = useState(null);
136
+ const [selectedNodeForConfig, setSelectedNodeForConfig] = useState(null);
137
+ const [selectedEdgeId, setSelectedEdgeId] = useState(null);
138
+ const [hoveredEdgeId, setHoveredEdgeId] = useState(null);
139
+ const processedClickRef = useRef(null);
140
+ const [showMentorModal, setShowMentorModal] = useState(false);
141
+ const [mentorSearchQuery, setMentorSearchQuery] = useState('');
142
+ const [activeMentorNodeId, setActiveMentorNodeId] = useState(null);
143
+ const rootRef = useRef(null);
144
+ // False once the user clicks into a settings panel or a dialog, so a
145
+ // Delete meant for a dropdown or button there never removes the step.
146
+ const canvasHasPointerRef = useRef(true);
147
+ const canvasRef = useRef(null);
148
+ // Read by callbacks that run after an await (mentor prefill), which must
149
+ // not write back a stale copy of the graph.
150
+ const nodesRef = useRef(nodes);
151
+ const edgesRef = useRef(edges);
152
+ nodesRef.current = nodes;
153
+ edgesRef.current = edges;
154
+ const dragStartRef = useRef(null);
155
+ const dragMovedRef = useRef(false);
156
+ const suppressMentorClickRef = useRef(false);
157
+ // Track if we've loaded initial data from props
158
+ const hasLoadedInitialData = useRef(false);
159
+ // Track last emitted state to avoid duplicate onStateChange calls
160
+ const lastEmittedStateRef = useRef(null);
161
+ const [fetchMentorSettings] = useLazyGetMentorSettingsQuery();
162
+ // Sync nodes and edges when initialNodes/initialEdges change
163
+ // This handles both preview mode updates and initial data load
164
+ useEffect(() => {
165
+ // In preview mode, always sync when props change
166
+ if (previewMode) {
167
+ if (initialNodes && initialNodes.length > 0) {
168
+ setNodes(initialNodes);
169
+ }
170
+ if (initialEdges) {
171
+ setEdges(initialEdges);
172
+ }
173
+ return;
174
+ }
175
+ // In edit mode, only sync on first load (when data comes from API)
176
+ if (!hasLoadedInitialData.current && initialNodes && initialNodes.length > 0) {
177
+ setNodes(initialNodes);
178
+ if (initialEdges) {
179
+ setEdges(initialEdges);
180
+ }
181
+ // Reset history with loaded data
182
+ setHistory([{ nodes: initialNodes, edges: initialEdges || [] }]);
183
+ historyIndexRef.current = 0;
184
+ setHistoryIndex(0);
185
+ hasLoadedInitialData.current = true;
186
+ }
187
+ }, [previewMode, initialNodes, initialEdges]);
188
+ useEffect(() => {
189
+ if (previewMode && initialViewport) {
190
+ setZoom(initialViewport.zoom);
191
+ setPanOffset({ x: initialViewport.x, y: initialViewport.y });
192
+ }
193
+ }, [previewMode, initialViewport]);
194
+ const saveToHistory = useCallback((newNodes, newEdges) => {
195
+ const index = historyIndexRef.current;
196
+ setHistory((prev) => {
197
+ const newHistory = prev.slice(0, index + 1);
198
+ newHistory.push({ nodes: [...newNodes], edges: [...newEdges] });
199
+ return newHistory.slice(-50); // Keep last 50 states
200
+ });
201
+ historyIndexRef.current = Math.min(index + 1, 49);
202
+ setHistoryIndex(historyIndexRef.current);
203
+ }, []);
204
+ const restoreHistory = (newIndex) => {
205
+ historyIndexRef.current = newIndex;
206
+ setHistoryIndex(newIndex);
207
+ setNodes(history[newIndex].nodes);
208
+ setEdges(history[newIndex].edges);
209
+ setSelectedEdgeId(null);
210
+ setHoveredEdgeId(null);
211
+ };
212
+ const handleUndo = () => {
213
+ if (historyIndex > 0)
214
+ restoreHistory(historyIndex - 1);
215
+ };
216
+ const handleRedo = () => {
217
+ if (historyIndex < history.length - 1)
218
+ restoreHistory(historyIndex + 1);
219
+ };
220
+ const handleZoomIn = () => {
221
+ setZoom((prev) => Math.min(prev + 0.1, 2));
222
+ };
223
+ const handleZoomOut = () => {
224
+ setZoom((prev) => Math.max(prev - 0.1, 0.5));
225
+ };
226
+ const handleUpdateNode = useCallback((nodeId, updates) => {
227
+ const target = nodesRef.current.find((node) => node.id === nodeId);
228
+ if (!target)
229
+ return;
230
+ const shouldSyncMentorId = target.type === 'mentor' &&
231
+ updates.entry_mentor_id !== undefined &&
232
+ updates.mentor_id === undefined;
233
+ const normalizedUpdates = shouldSyncMentorId
234
+ ? { ...updates, mentor_id: updates.entry_mentor_id }
235
+ : updates;
236
+ const applyUpdate = (node) => node.id === nodeId ? { ...node, data: { ...node.data, ...normalizedUpdates } } : node;
237
+ const { conditions } = updates;
238
+ const moveBranchEdges = (current) => isConditionalType(target.type) && conditions
239
+ ? reindexConditionEdges(current, nodeId, getConditions(target.data), conditions)
240
+ : current;
241
+ const newNodes = nodesRef.current.map(applyUpdate);
242
+ const newEdges = moveBranchEdges(edgesRef.current);
243
+ nodesRef.current = newNodes;
244
+ edgesRef.current = newEdges;
245
+ setNodes((prev) => prev.map(applyUpdate));
246
+ setEdges(moveBranchEdges);
247
+ saveToHistory(newNodes, newEdges);
248
+ }, [saveToHistory]);
249
+ const prefillMentorNode = useCallback(async (nodeId, mentorId, existingData) => {
250
+ if (!mentorId || !org || !username)
251
+ return;
252
+ try {
253
+ const settings = (await fetchMentorSettings({
254
+ mentor: mentorId,
255
+ org,
256
+ }).unwrap());
257
+ const mentorLabel = settings.display_name || settings.mentor_name;
258
+ const mentorInstructions = settings.system_prompt;
259
+ const mentorModel = settings.llm_name;
260
+ const updates = {};
261
+ if (mentorLabel && isDefaultMentorLabel(existingData === null || existingData === void 0 ? void 0 : existingData.label)) {
262
+ updates.label = mentorLabel;
263
+ }
264
+ if (mentorInstructions && !(existingData === null || existingData === void 0 ? void 0 : existingData.instructions)) {
265
+ updates.instructions = mentorInstructions;
266
+ }
267
+ if (mentorModel && !(existingData === null || existingData === void 0 ? void 0 : existingData.model)) {
268
+ updates.model = mentorModel;
269
+ }
270
+ if (Object.keys(updates).length > 0) {
271
+ handleUpdateNode(nodeId, updates);
272
+ }
273
+ }
274
+ catch (error) {
275
+ console.error('Failed to prefill mentor node data:', error);
276
+ }
277
+ }, [fetchMentorSettings, handleUpdateNode, org, username]);
278
+ useEffect(() => {
279
+ if (!defaultMentorId || previewMode)
280
+ return;
281
+ const mentorNodesMissingId = nodes.filter((node) => node.type === 'mentor' && !node.data.entry_mentor_id && !node.data.mentor_id);
282
+ if (mentorNodesMissingId.length === 0)
283
+ return;
284
+ setNodes((prev) => prev.map((node) => {
285
+ if (node.type !== 'mentor')
286
+ return node;
287
+ if (node.data.entry_mentor_id || node.data.mentor_id)
288
+ return node;
289
+ return {
290
+ ...node,
291
+ data: {
292
+ ...node.data,
293
+ entry_mentor_id: defaultMentorId,
294
+ mentor_id: defaultMentorId,
295
+ },
296
+ };
297
+ }));
298
+ mentorNodesMissingId.forEach((node) => {
299
+ prefillMentorNode(node.id, defaultMentorId, node.data);
300
+ });
301
+ }, [defaultMentorId, previewMode, nodes, prefillMentorNode]);
302
+ const closeMentorModal = useCallback(() => {
303
+ setShowMentorModal(false);
304
+ setMentorSearchQuery('');
305
+ setActiveMentorNodeId(null);
306
+ }, []);
307
+ const handleMentorSelect = useCallback(async (mentor) => {
308
+ var _a;
309
+ if (!activeMentorNodeId)
310
+ return;
311
+ const mentorId = mentor.unique_id;
312
+ if (!mentorId)
313
+ return;
314
+ const activeNode = nodes.find((node) => node.id === activeMentorNodeId);
315
+ const currentData = activeNode === null || activeNode === void 0 ? void 0 : activeNode.data;
316
+ let updates = {
317
+ entry_mentor_id: mentorId,
318
+ mentor_id: mentorId,
319
+ subtitle: (_a = currentData === null || currentData === void 0 ? void 0 : currentData.subtitle) !== null && _a !== void 0 ? _a : 'Agent',
320
+ };
321
+ try {
322
+ if (org && username) {
323
+ const settings = (await fetchMentorSettings({
324
+ mentor: mentorId,
325
+ org,
326
+ }).unwrap());
327
+ updates = {
328
+ ...updates,
329
+ label: settings.display_name ||
330
+ settings.mentor_name ||
331
+ mentor.name ||
332
+ (currentData === null || currentData === void 0 ? void 0 : currentData.label) ||
333
+ 'Agent',
334
+ instructions: settings.system_prompt || (currentData === null || currentData === void 0 ? void 0 : currentData.instructions),
335
+ model: settings.llm_name || (currentData === null || currentData === void 0 ? void 0 : currentData.model),
336
+ };
337
+ }
338
+ else if (mentor.name) {
339
+ updates = { ...updates, label: mentor.name };
340
+ }
341
+ }
342
+ catch (error) {
343
+ console.error('Failed to load mentor settings for workflow node:', error);
344
+ if (mentor.name) {
345
+ updates = { ...updates, label: mentor.name };
346
+ }
347
+ }
348
+ handleUpdateNode(activeMentorNodeId, updates);
349
+ closeMentorModal();
350
+ }, [
351
+ activeMentorNodeId,
352
+ closeMentorModal,
353
+ fetchMentorSettings,
354
+ handleUpdateNode,
355
+ nodes,
356
+ org,
357
+ username,
358
+ ]);
359
+ const handleMentorChangeClick = useCallback((nodeId) => {
360
+ if (previewMode)
361
+ return;
362
+ setActiveMentorNodeId(nodeId);
363
+ setMentorSearchQuery('');
364
+ setShowMentorModal(true);
365
+ }, [previewMode]);
366
+ const findNonOverlappingPosition = useCallback((baseX, baseY, nodeType, view) => {
367
+ var _a, _b, _c;
368
+ const { width: nodeWidth, height: nodeHeight } = nodeSize({
369
+ type: nodeType,
370
+ });
371
+ const SPACING = 20; // Minimum spacing between nodes
372
+ const hasOverlap = ({ x, y }) => nodes.some((node) => {
373
+ const existing = nodeSize(node);
374
+ return !(x + nodeWidth + SPACING < node.position.x ||
375
+ x > node.position.x + existing.width + SPACING ||
376
+ y + nodeHeight + SPACING < node.position.y ||
377
+ y > node.position.y + existing.height + SPACING);
378
+ });
379
+ // A step placed past the canvas edge hides its connection points.
380
+ const isVisible = ({ x, y }) => !view ||
381
+ (x >= view.left &&
382
+ y >= view.top &&
383
+ x + nodeWidth <= view.right &&
384
+ y + nodeHeight <= view.bottom);
385
+ const stepX = nodeWidth + SPACING * 2;
386
+ const stepY = nodeHeight + SPACING * 2;
387
+ const candidates = [
388
+ { x: baseX, y: baseY },
389
+ { x: baseX + stepX, y: baseY }, // Right
390
+ { x: baseX, y: baseY + stepY }, // Below
391
+ { x: baseX - stepX, y: baseY }, // Left
392
+ { x: baseX, y: baseY - stepY }, // Above
393
+ { x: baseX + stepX, y: baseY + stepY }, // Bottom-right
394
+ { x: baseX - stepX, y: baseY + stepY }, // Bottom-left
395
+ { x: baseX + stepX, y: baseY - stepY }, // Top-right
396
+ { x: baseX - stepX, y: baseY - stepY }, // Top-left
397
+ ];
398
+ // The top-left corner wins when the card is bigger than the view.
399
+ const clampToView = ({ x, y }) => view
400
+ ? {
401
+ x: Math.max(view.left, Math.min(x, view.right - nodeWidth)),
402
+ y: Math.max(view.top, Math.min(y, view.bottom - nodeHeight)),
403
+ }
404
+ : { x, y };
405
+ return ((_c = (_b = (_a = candidates.find((pos) => !hasOverlap(pos) && isVisible(pos))) !== null && _a !== void 0 ? _a : candidates.map(clampToView).find((pos) => !hasOverlap(pos))) !== null && _b !== void 0 ? _b : candidates.find((pos) => !hasOverlap(pos))) !== null && _c !== void 0 ? _c : {
406
+ x: baseX + (nodeWidth + SPACING) * 2,
407
+ y: baseY + (nodeHeight + SPACING) * 2,
408
+ });
409
+ }, [nodes]);
410
+ const visibleBounds = (rect) => {
411
+ // `|| 0` keeps an unpanned edge at 0 rather than -0.
412
+ const left = -panOffset.x / zoom || 0;
413
+ const top = -panOffset.y / zoom || 0;
414
+ return {
415
+ left,
416
+ top,
417
+ right: left + rect.width / zoom,
418
+ bottom: top + Math.max(0, rect.height - TOOLBAR_CLEARANCE) / zoom,
419
+ };
420
+ };
421
+ useEffect(() => {
422
+ if (previewMode)
423
+ return; // Don't add nodes in preview mode
424
+ if (onClickedItem && canvasRef.current) {
425
+ // Prevent duplicate additions - skip if we already processed this exact click object
426
+ if (processedClickRef.current === onClickedItem) {
427
+ return;
428
+ }
429
+ processedClickRef.current = onClickedItem;
430
+ const rect = canvasRef.current.getBoundingClientRect();
431
+ const centerX = (rect.width / 2 - panOffset.x) / zoom;
432
+ const centerY = (rect.height / 2 - panOffset.y) / zoom;
433
+ const size = nodeSize({ type: onClickedItem.id });
434
+ const { x, y } = findNonOverlappingPosition(centerX - size.width / 2, centerY - size.height / 2, onClickedItem.id, visibleBounds(rect));
435
+ const newNode = {
436
+ id: `${onClickedItem.id}-${Date.now()}`,
437
+ type: onClickedItem.id,
438
+ position: { x, y },
439
+ data: {
440
+ label: onClickedItem.label,
441
+ ...(onClickedItem.id === 'mentor' && {
442
+ subtitle: 'Agent',
443
+ color: 'chart-1',
444
+ entry_mentor_id: defaultMentorId,
445
+ mentor_id: defaultMentorId,
446
+ }),
447
+ ...(onClickedItem.id === 'note' && { content: 'Sticky Note' }),
448
+ },
449
+ ...(onClickedItem.id === 'note' && { width: 200, height: 120 }),
450
+ draggable: true,
451
+ selectable: true,
452
+ connectable: true,
453
+ };
454
+ const newNodes = [...nodes, newNode];
455
+ setNodes(newNodes);
456
+ saveToHistory(newNodes, edges);
457
+ if (onClickedItem.id === 'mentor' && defaultMentorId) {
458
+ prefillMentorNode(newNode.id, defaultMentorId, newNode.data);
459
+ }
460
+ }
461
+ }, [onClickedItem, defaultMentorId, edges, prefillMentorNode]);
462
+ const handleNodeMouseDown = (e, nodeId) => {
463
+ if (previewMode)
464
+ return; // Prevent node dragging in preview mode
465
+ if (e.button !== 0)
466
+ return;
467
+ e.stopPropagation();
468
+ const node = nodes.find((n) => n.id === nodeId);
469
+ if (!node)
470
+ return;
471
+ setSelectedEdgeId(null);
472
+ if (e.ctrlKey || e.metaKey) {
473
+ setNodes((prev) => prev.map((n) => (n.id === nodeId ? { ...n, selected: !n.selected } : n)));
474
+ if (node.type === 'mentor') {
475
+ setSelectedNodeForConfig(null);
476
+ }
477
+ suppressMentorClickRef.current = true;
478
+ }
479
+ else {
480
+ setNodes((prev) => prev.map((n) => ({ ...n, selected: n.id === nodeId })));
481
+ if (node.type !== 'mentor') {
482
+ setSelectedNodeForConfig(nodeId);
483
+ }
484
+ else {
485
+ setSelectedNodeForConfig(null);
486
+ }
487
+ suppressMentorClickRef.current = false;
488
+ }
489
+ dragStartRef.current = { x: e.clientX, y: e.clientY };
490
+ dragMovedRef.current = false;
491
+ setDraggedNode(nodeId);
492
+ setDragOffset({
493
+ x: e.clientX / zoom - node.position.x,
494
+ y: e.clientY / zoom - node.position.y,
495
+ });
496
+ };
497
+ const handleConnectionStart = (e, nodeId, handle) => {
498
+ if (previewMode)
499
+ return; // Prevent connection creation in preview mode
500
+ e.stopPropagation();
501
+ setConnectingFrom({ nodeId, handle });
502
+ };
503
+ const handleResizeStart = (e, nodeId, corner) => {
504
+ if (previewMode)
505
+ return; // Prevent resizing in preview mode
506
+ e.stopPropagation();
507
+ const node = nodes.find((n) => n.id === nodeId);
508
+ if (!node)
509
+ return;
510
+ setResizingNote({
511
+ nodeId,
512
+ corner,
513
+ startX: e.clientX,
514
+ startY: e.clientY,
515
+ startWidth: node.width || 200,
516
+ startHeight: node.height || 120,
517
+ });
518
+ };
519
+ const handleCanvasMouseDown = (e) => {
520
+ var _a, _b;
521
+ if (e.button !== 0 || draggedNode || connectingFrom)
522
+ return;
523
+ // Dialogs render in portals, so their backdrop's mousedown still bubbles
524
+ // here through the React tree.
525
+ if (!((_a = canvasRef.current) === null || _a === void 0 ? void 0 : _a.contains(e.target)))
526
+ return;
527
+ const target = e.target;
528
+ const isCanvasClick = target === e.currentTarget || target.tagName === 'svg' || target.tagName === 'rect';
529
+ if (isCanvasClick && !previewMode) {
530
+ setSelectedNodeForConfig(null);
531
+ setSelectedEdgeId(null);
532
+ }
533
+ if (tool === 'hand') {
534
+ setIsPanning(true);
535
+ setPanStart({ x: e.clientX - panOffset.x, y: e.clientY - panOffset.y });
536
+ }
537
+ else if (tool === 'pointer' && !previewMode) {
538
+ const rect = (_b = canvasRef.current) === null || _b === void 0 ? void 0 : _b.getBoundingClientRect();
539
+ if (!rect)
540
+ return;
541
+ const x = (e.clientX - rect.left - panOffset.x) / zoom;
542
+ const y = (e.clientY - rect.top - panOffset.y) / zoom;
543
+ setSelectionBox({ startX: x, startY: y, currentX: x, currentY: y });
544
+ setNodes((prev) => prev.map((n) => ({ ...n, selected: false })));
545
+ }
546
+ };
547
+ const handleMouseMove = useCallback((e) => {
548
+ if (draggedNode) {
549
+ if (dragStartRef.current) {
550
+ const movedX = Math.abs(e.clientX - dragStartRef.current.x);
551
+ const movedY = Math.abs(e.clientY - dragStartRef.current.y);
552
+ if (movedX > 3 || movedY > 3) {
553
+ dragMovedRef.current = true;
554
+ }
555
+ }
556
+ const draggedNodeData = nodes.find((n) => n.id === draggedNode);
557
+ if (!draggedNodeData)
558
+ return;
559
+ const deltaX = e.clientX / zoom - dragOffset.x - draggedNodeData.position.x;
560
+ const deltaY = e.clientY / zoom - dragOffset.y - draggedNodeData.position.y;
561
+ setNodes((prev) => prev.map((node) => {
562
+ if (node.selected) {
563
+ return {
564
+ ...node,
565
+ position: {
566
+ x: node.position.x + deltaX,
567
+ y: node.position.y + deltaY,
568
+ },
569
+ dragging: true,
570
+ };
571
+ }
572
+ return node;
573
+ }));
574
+ }
575
+ else if (isPanning) {
576
+ setPanOffset({
577
+ x: e.clientX - panStart.x,
578
+ y: e.clientY - panStart.y,
579
+ });
580
+ }
581
+ else if (connectingFrom && canvasRef.current) {
582
+ const rect = canvasRef.current.getBoundingClientRect();
583
+ setTempConnectionPos({
584
+ x: (e.clientX - rect.left - panOffset.x) / zoom,
585
+ y: (e.clientY - rect.top - panOffset.y) / zoom,
586
+ });
587
+ }
588
+ else if (resizingNote) {
589
+ const deltaX = (e.clientX - resizingNote.startX) / zoom;
590
+ const deltaY = (e.clientY - resizingNote.startY) / zoom;
591
+ setNodes((prev) => prev.map((node) => {
592
+ if (node.id === resizingNote.nodeId) {
593
+ let newWidth = resizingNote.startWidth;
594
+ let newHeight = resizingNote.startHeight;
595
+ if (resizingNote.corner.includes('right')) {
596
+ newWidth = Math.max(150, resizingNote.startWidth + deltaX);
597
+ }
598
+ if (resizingNote.corner.includes('bottom')) {
599
+ newHeight = Math.max(80, resizingNote.startHeight + deltaY);
600
+ }
601
+ return { ...node, width: newWidth, height: newHeight };
602
+ }
603
+ return node;
604
+ }));
605
+ }
606
+ else if (selectionBox && canvasRef.current) {
607
+ const rect = canvasRef.current.getBoundingClientRect();
608
+ const currentX = (e.clientX - rect.left - panOffset.x) / zoom;
609
+ const currentY = (e.clientY - rect.top - panOffset.y) / zoom;
610
+ setSelectionBox((prev) => (prev ? { ...prev, currentX, currentY } : null));
611
+ const minX = Math.min(selectionBox.startX, currentX);
612
+ const maxX = Math.max(selectionBox.startX, currentX);
613
+ const minY = Math.min(selectionBox.startY, currentY);
614
+ const maxY = Math.max(selectionBox.startY, currentY);
615
+ setNodes((prev) => prev.map((node) => {
616
+ const { width, height } = nodeSize(node);
617
+ const nodeInBox = node.position.x + width > minX &&
618
+ node.position.x < maxX &&
619
+ node.position.y + height > minY &&
620
+ node.position.y < maxY;
621
+ return { ...node, selected: nodeInBox };
622
+ }));
623
+ }
624
+ }, [
625
+ draggedNode,
626
+ dragOffset,
627
+ isPanning,
628
+ panStart,
629
+ connectingFrom,
630
+ panOffset,
631
+ resizingNote,
632
+ selectionBox,
633
+ nodes,
634
+ zoom,
635
+ ]);
636
+ const handleMouseUp = useCallback(() => {
637
+ var _a;
638
+ const targetNode = hoveredNode ? nodes.find((n) => n.id === hoveredNode) : undefined;
639
+ if (connectingFrom &&
640
+ targetNode &&
641
+ connectingFrom.nodeId !== targetNode.id &&
642
+ canReceiveConnection(targetNode.type)) {
643
+ const { nodeId, handle } = connectingFrom;
644
+ const fromSameOutput = (edge) => edge.source === nodeId && (edge.sourceHandle || 'right') === handle;
645
+ const existing = edges.find((edge) => fromSameOutput(edge) && edge.target === targetNode.id);
646
+ const connection = existing !== null && existing !== void 0 ? existing : {
647
+ id: edgeIdFor(nodeId, handle, targetNode.id),
648
+ source: nodeId,
649
+ target: targetNode.id,
650
+ sourceHandle: handle,
651
+ targetHandle: 'left',
652
+ };
653
+ // The backend routes each branch output to a single step and keeps
654
+ // only the last connection, so drawing one replaces the others - even
655
+ // when it repeats a connection an older save already had.
656
+ const newEdges = isBranchHandle(handle)
657
+ ? [...edges.filter((edge) => !fromSameOutput(edge)), connection]
658
+ : existing
659
+ ? edges
660
+ : [...edges, connection];
661
+ if (existing ? newEdges.length !== edges.length : true) {
662
+ setEdges(newEdges);
663
+ saveToHistory(nodes, newEdges);
664
+ }
665
+ }
666
+ if (draggedNode) {
667
+ setNodes((prev) => prev.map((n) => ({ ...n, dragging: false })));
668
+ saveToHistory(nodes, edges);
669
+ if (!previewMode && !dragMovedRef.current && !suppressMentorClickRef.current) {
670
+ const clickedNode = nodes.find((node) => node.id === draggedNode);
671
+ if ((clickedNode === null || clickedNode === void 0 ? void 0 : clickedNode.type) === 'mentor') {
672
+ onOpenAgentSettings === null || onOpenAgentSettings === void 0 ? void 0 : onOpenAgentSettings((_a = clickedNode.data.mentor_id) !== null && _a !== void 0 ? _a : clickedNode.data.entry_mentor_id);
673
+ }
674
+ }
675
+ }
676
+ dragMovedRef.current = false;
677
+ dragStartRef.current = null;
678
+ suppressMentorClickRef.current = false;
679
+ setDraggedNode(null);
680
+ setIsPanning(false);
681
+ setConnectingFrom(null);
682
+ setTempConnectionPos(null);
683
+ setResizingNote(null);
684
+ setSelectionBox(null);
685
+ }, [
686
+ connectingFrom,
687
+ hoveredNode,
688
+ draggedNode,
689
+ nodes,
690
+ edges,
691
+ saveToHistory,
692
+ onOpenAgentSettings,
693
+ previewMode,
694
+ defaultMentorId,
695
+ ]);
696
+ useEffect(() => {
697
+ if (draggedNode || isPanning || connectingFrom || resizingNote || selectionBox) {
698
+ window.addEventListener('mousemove', handleMouseMove);
699
+ window.addEventListener('mouseup', handleMouseUp);
700
+ return () => {
701
+ window.removeEventListener('mousemove', handleMouseMove);
702
+ window.removeEventListener('mouseup', handleMouseUp);
703
+ };
704
+ }
705
+ }, [
706
+ draggedNode,
707
+ isPanning,
708
+ connectingFrom,
709
+ resizingNote,
710
+ selectionBox,
711
+ handleMouseMove,
712
+ handleMouseUp,
713
+ ]);
714
+ const handleDrop = (e) => {
715
+ if (previewMode)
716
+ return; // Prevent dropping in preview mode
717
+ e.preventDefault();
718
+ if (!canvasRef.current)
719
+ return;
720
+ const rect = canvasRef.current.getBoundingClientRect();
721
+ const baseX = (e.clientX - rect.left - panOffset.x) / zoom;
722
+ const baseY = (e.clientY - rect.top - panOffset.y) / zoom;
723
+ try {
724
+ const data = JSON.parse(e.dataTransfer.getData('application/json'));
725
+ const { x, y } = findNonOverlappingPosition(baseX, baseY, data.id, visibleBounds(rect));
726
+ const newNode = {
727
+ id: `${data.id}-${Date.now()}`,
728
+ type: data.id,
729
+ position: { x, y },
730
+ data: {
731
+ label: data.label,
732
+ ...(data.id === 'mentor' && {
733
+ subtitle: 'Agent',
734
+ color: 'chart-1',
735
+ entry_mentor_id: defaultMentorId,
736
+ mentor_id: defaultMentorId,
737
+ }),
738
+ ...(data.id === 'note' && { content: 'Sticky Note' }),
739
+ },
740
+ ...(data.id === 'note' && { width: 200, height: 120 }),
741
+ draggable: true,
742
+ selectable: true,
743
+ connectable: true,
744
+ };
745
+ const newNodes = [...nodes, newNode];
746
+ setNodes(newNodes);
747
+ saveToHistory(newNodes, edges);
748
+ if (data.id === 'mentor' && defaultMentorId) {
749
+ prefillMentorNode(newNode.id, defaultMentorId, newNode.data);
750
+ }
751
+ }
752
+ catch (error) {
753
+ console.error('Failed to parse dropped data:', error);
754
+ }
755
+ };
756
+ const handleDragOver = (e) => {
757
+ e.preventDefault();
758
+ };
759
+ const getHandlePosition = (node, handle) => {
760
+ let nodeWidth = 140;
761
+ let nodeHeight = 50;
762
+ if (node.type === 'start') {
763
+ nodeWidth = 100;
764
+ nodeHeight = 50;
765
+ }
766
+ else if (node.type === 'while' || node.type === 'user-approval') {
767
+ nodeWidth = 280;
768
+ nodeHeight = branchCardHeight(2);
769
+ }
770
+ else if (isConditionalType(node.type)) {
771
+ nodeWidth = 280;
772
+ nodeHeight = branchCardHeight(getConditionCount(node.data) + 1);
773
+ }
774
+ else if (node.type === 'note') {
775
+ nodeWidth = node.width || 200;
776
+ nodeHeight = node.height || 120;
777
+ }
778
+ const HANDLE_OFFSET = 8;
779
+ switch (handle) {
780
+ case 'left':
781
+ return {
782
+ x: node.position.x - HANDLE_OFFSET,
783
+ y: node.position.y + nodeHeight / 2,
784
+ };
785
+ case 'right':
786
+ return {
787
+ x: node.position.x + nodeWidth + HANDLE_OFFSET,
788
+ y: node.position.y + nodeHeight / 2,
789
+ };
790
+ case 'top':
791
+ return {
792
+ x: node.position.x + nodeWidth / 2,
793
+ y: node.position.y - HANDLE_OFFSET,
794
+ };
795
+ case 'bottom':
796
+ return {
797
+ x: node.position.x + nodeWidth / 2,
798
+ y: node.position.y + nodeHeight + HANDLE_OFFSET,
799
+ };
800
+ case 'else':
801
+ return {
802
+ x: node.position.x + nodeWidth + HANDLE_OFFSET,
803
+ y: node.position.y + branchRowCenter(getConditionCount(node.data)),
804
+ };
805
+ case WHILE_LOOP_HANDLE:
806
+ case 'approve':
807
+ return {
808
+ x: node.position.x + nodeWidth + HANDLE_OFFSET,
809
+ y: node.position.y + branchRowCenter(0),
810
+ };
811
+ case WHILE_DONE_HANDLE:
812
+ case 'reject':
813
+ return {
814
+ x: node.position.x + nodeWidth + HANDLE_OFFSET,
815
+ y: node.position.y + branchRowCenter(1),
816
+ };
817
+ default:
818
+ if (handle.startsWith('condition-')) {
819
+ const index = Number.parseInt(handle.split('-')[1]);
820
+ return {
821
+ x: node.position.x + nodeWidth + HANDLE_OFFSET,
822
+ y: node.position.y + branchRowCenter(index),
823
+ };
824
+ }
825
+ return { x: node.position.x, y: node.position.y };
826
+ }
827
+ };
828
+ const getConnectionPath = (from, to) => {
829
+ const dx = to.x - from.x;
830
+ const dy = to.y - from.y;
831
+ const distance = Math.sqrt(dx * dx + dy * dy);
832
+ const controlPointOffset = Math.min(distance * 0.5, 100);
833
+ return `M ${from.x} ${from.y} C ${from.x + controlPointOffset} ${from.y}, ${to.x - controlPointOffset} ${to.y}, ${to.x} ${to.y}`;
834
+ };
835
+ const deleteNodes = useCallback((nodeIds) => {
836
+ const ids = new Set(nodes
837
+ .filter((node) => nodeIds.includes(node.id) && isDeletableNode(node))
838
+ .map((node) => node.id));
839
+ if (ids.size === 0)
840
+ return;
841
+ const newNodes = nodes.filter((node) => !ids.has(node.id));
842
+ const newEdges = edges.filter((edge) => !ids.has(edge.source) && !ids.has(edge.target));
843
+ setNodes(newNodes);
844
+ setEdges(newEdges);
845
+ setSelectedNodeForConfig((current) => (current && ids.has(current) ? null : current));
846
+ const keepsEdge = (current) => current && newEdges.some((edge) => edge.id === current) ? current : null;
847
+ setSelectedEdgeId(keepsEdge);
848
+ setHoveredEdgeId(keepsEdge);
849
+ saveToHistory(newNodes, newEdges);
850
+ }, [nodes, edges, saveToHistory]);
851
+ const deleteEdge = useCallback((edgeId) => {
852
+ const newEdges = edges.filter((edge) => edge.id !== edgeId);
853
+ if (newEdges.length === edges.length)
854
+ return;
855
+ setEdges(newEdges);
856
+ setSelectedEdgeId(null);
857
+ setHoveredEdgeId(null);
858
+ saveToHistory(nodes, newEdges);
859
+ }, [nodes, edges, saveToHistory]);
860
+ const selectEdge = (edgeId) => {
861
+ setSelectedEdgeId(edgeId);
862
+ setSelectedNodeForConfig(null);
863
+ setNodes((prev) => prev.map((n) => ({ ...n, selected: false })));
864
+ };
865
+ const handleDeleteSelection = useCallback(() => {
866
+ if (selectedEdgeId && edges.some((edge) => edge.id === selectedEdgeId)) {
867
+ deleteEdge(selectedEdgeId);
868
+ return;
869
+ }
870
+ deleteNodes(nodes.filter((node) => node.selected).map((node) => node.id));
871
+ }, [selectedEdgeId, edges, deleteEdge, deleteNodes, nodes]);
872
+ useEffect(() => {
873
+ if (previewMode)
874
+ return;
875
+ const handleKeyDown = (e) => {
876
+ var _a, _b;
877
+ if (e.key !== 'Delete' && e.key !== 'Backspace')
878
+ return;
879
+ const target = e.target;
880
+ if (target.tagName === 'INPUT' ||
881
+ target.tagName === 'TEXTAREA' ||
882
+ target.isContentEditable ||
883
+ ((_a = target.closest) === null || _a === void 0 ? void 0 : _a.call(target, NODE_PANEL_SELECTOR)) ||
884
+ !canvasHasPointerRef.current) {
885
+ return;
886
+ }
887
+ // Dialogs and menus (Edit Agent, Add State Variable, selects) render in
888
+ // portals; a key pressed there must not delete the step behind them.
889
+ const active = document.activeElement;
890
+ if (active && active !== document.body && !((_b = rootRef.current) === null || _b === void 0 ? void 0 : _b.contains(active))) {
891
+ return;
892
+ }
893
+ e.preventDefault();
894
+ handleDeleteSelection();
895
+ };
896
+ window.addEventListener('keydown', handleKeyDown);
897
+ return () => {
898
+ window.removeEventListener('keydown', handleKeyDown);
899
+ };
900
+ }, [previewMode, handleDeleteSelection]);
901
+ useEffect(() => {
902
+ if (onStateChange && !previewMode) {
903
+ // Strip UI-only properties for comparison to avoid triggering on selection changes
904
+ const strippedNodes = stripUiProperties(nodes);
905
+ const strippedEdges = stripEdgeUiProperties(edges);
906
+ const stateKey = JSON.stringify({
907
+ nodes: strippedNodes,
908
+ edges: strippedEdges,
909
+ });
910
+ // Only trigger onStateChange if actual data changed (not just selection/dragging)
911
+ if (stateKey !== lastEmittedStateRef.current) {
912
+ lastEmittedStateRef.current = stateKey;
913
+ const reactFlowData = {
914
+ nodes: strippedNodes,
915
+ edges: strippedEdges,
916
+ viewport: { x: panOffset.x, y: panOffset.y, zoom },
917
+ };
918
+ onStateChange(reactFlowData);
919
+ }
920
+ }
921
+ }, [nodes, edges, zoom, panOffset, previewMode, onStateChange]);
922
+ const workflowVariables = useMemo(() => collectWorkflowVariables(nodes), [nodes]);
923
+ const variableUsages = useMemo(() => collectVariableUsages(nodes), [nodes]);
924
+ const edgeGeometry = edges.flatMap((edge) => {
925
+ const fromNode = nodes.find((n) => n.id === edge.source);
926
+ const toNode = nodes.find((n) => n.id === edge.target);
927
+ if (!fromNode || !toNode)
928
+ return [];
929
+ const from = getHandlePosition(fromNode, edge.sourceHandle || 'right');
930
+ const to = getHandlePosition(toNode, edge.targetHandle || 'left');
931
+ // getConnectionPath's control points mirror each other, so the curve
932
+ // passes through the plain midpoint of its ends.
933
+ const midpoint = { x: (from.x + to.x) / 2, y: (from.y + to.y) / 2 };
934
+ return [{ edge, path: getConnectionPath(from, to), midpoint }];
935
+ });
936
+ const activeMentorNode = activeMentorNodeId
937
+ ? nodes.find((node) => node.id === activeMentorNodeId)
938
+ : undefined;
939
+ const activeMentorId = (_a = activeMentorNode === null || activeMentorNode === void 0 ? void 0 : activeMentorNode.data.mentor_id) !== null && _a !== void 0 ? _a : activeMentorNode === null || activeMentorNode === void 0 ? void 0 : activeMentorNode.data.entry_mentor_id;
940
+ const selectedMentorIds = activeMentorId ? [activeMentorId] : [];
941
+ return (jsxs("div", { ref: rootRef, onMouseDownCapture: (e) => {
942
+ var _a;
943
+ const target = e.target;
944
+ canvasHasPointerRef.current =
945
+ !!((_a = rootRef.current) === null || _a === void 0 ? void 0 : _a.contains(target)) && !target.closest(NODE_PANEL_SELECTOR);
946
+ }, "data-testid": "workflow-canvas", className: "bg-background relative h-full w-full overflow-hidden select-none", children: [jsxs("div", { ref: canvasRef, className: `absolute inset-0 ${tool === 'hand' ? (isPanning ? 'cursor-grabbing' : 'cursor-grab') : 'cursor-default'}`, onMouseDown: handleCanvasMouseDown, onDrop: handleDrop, onDragOver: handleDragOver, children: [jsxs("svg", { className: "pointer-events-none h-full w-full", children: [jsx("defs", { children: jsx("pattern", { id: "grid", width: "20", height: "20", patternUnits: "userSpaceOnUse", children: jsx("circle", { cx: "1", cy: "1", r: "0.5", fill: "hsl(var(--muted-foreground) / 0.2)" }) }) }), jsx("rect", { width: "100%", height: "100%", fill: "url(#grid)" }), jsxs("g", { transform: `translate(${panOffset.x}, ${panOffset.y}) scale(${zoom})`, children: [edgeGeometry.map(({ edge, path }) => {
947
+ const isActive = !previewMode && (edge.id === selectedEdgeId || edge.id === hoveredEdgeId);
948
+ return (jsxs("g", { children: [jsx("path", { d: path, stroke: isActive ? '#1D7FC0' : '#38A1E5', strokeWidth: isActive ? 3 : 2, fill: "none", strokeLinecap: "round" }), !previewMode && (jsx("path", { "data-testid": `workflow-edge-${edge.id}`, d: path, stroke: "transparent", strokeWidth: 16, fill: "none", className: "cursor-pointer", style: { pointerEvents: 'stroke' }, onMouseDown: (e) => {
949
+ if (e.button !== 0)
950
+ return;
951
+ e.stopPropagation();
952
+ selectEdge(edge.id);
953
+ }, onMouseEnter: () => setHoveredEdgeId(edge.id), onMouseLeave: () => setHoveredEdgeId(null) }))] }, edge.id));
954
+ }), connectingFrom &&
955
+ tempConnectionPos &&
956
+ (() => {
957
+ const fromNode = nodes.find((n) => n.id === connectingFrom.nodeId);
958
+ if (!fromNode)
959
+ return null;
960
+ const fromPos = getHandlePosition(fromNode, connectingFrom.handle);
961
+ return (jsx("path", { d: getConnectionPath(fromPos, tempConnectionPos), stroke: "#38A1E5", strokeWidth: "2", strokeDasharray: "5,5", fill: "none", strokeLinecap: "round" }));
962
+ })(), selectionBox && (jsx("rect", { x: Math.min(selectionBox.startX, selectionBox.currentX), y: Math.min(selectionBox.startY, selectionBox.currentY), width: Math.abs(selectionBox.currentX - selectionBox.startX), height: Math.abs(selectionBox.currentY - selectionBox.startY), fill: "rgba(56, 161, 229, 0.1)", stroke: "#38A1E5", strokeWidth: "1", strokeDasharray: "4,4" }))] })] }), jsxs("div", { className: "pointer-events-none absolute inset-0", style: {
963
+ transform: `translate(${panOffset.x}px, ${panOffset.y}px) scale(${zoom})`,
964
+ transformOrigin: '0 0',
965
+ }, children: [!previewMode &&
966
+ edgeGeometry.map(({ edge, midpoint }) => edge.id === selectedEdgeId || edge.id === hoveredEdgeId ? (jsx(ActionTooltip, { label: t('workflowsWorkflowCanvas.deleteConnection'), children: jsx("button", { type: "button", "aria-label": t('workflowsWorkflowCanvas.deleteConnection'), "data-testid": `workflow-edge-delete-${edge.id}`, className: "bg-card border-border text-muted-foreground hover:text-destructive pointer-events-auto absolute z-10 flex h-6 w-6 -translate-x-1/2 -translate-y-1/2 cursor-pointer items-center justify-center rounded-full border shadow-sm", style: { left: `${midpoint.x}px`, top: `${midpoint.y}px` }, onMouseDown: (e) => e.stopPropagation(), onMouseEnter: () => setHoveredEdgeId(edge.id), onMouseLeave: () => setHoveredEdgeId(null), onClick: (e) => {
967
+ e.stopPropagation();
968
+ deleteEdge(edge.id);
969
+ }, children: jsx(X, { className: "h-3.5 w-3.5" }) }) }, edge.id)) : null), nodes.map((node) => (jsxs("div", { "data-testid": `workflow-node-${node.id}`, "data-node-type": node.type, className: `group pointer-events-auto absolute ${node.selected ? 'ring-offset-background rounded-xl ring-2 ring-[#38A1E5] ring-offset-2' : ''}`, style: {
970
+ left: `${node.position.x}px`,
971
+ top: `${node.position.y}px`,
972
+ }, onMouseDown: (e) => {
973
+ if (node.type !== 'note' || !editingNote) {
974
+ handleNodeMouseDown(e, node.id);
975
+ }
976
+ }, onMouseEnter: () => setHoveredNode(node.id), onMouseLeave: () => setHoveredNode(null), children: [!previewMode && isDeletableNode(node) && (jsx(ActionTooltip, { label: t('workflowsWorkflowCanvas.deleteNode', { label: node.data.label }), children: jsx("button", { type: "button", "aria-label": t('workflowsWorkflowCanvas.deleteNode', { label: node.data.label }), className: `bg-card border-border text-muted-foreground hover:text-destructive absolute -top-3 -right-3 z-10 flex h-6 w-6 cursor-pointer items-center justify-center rounded-full border shadow-sm transition-opacity group-hover:opacity-100 focus-visible:opacity-100 ${node.selected ? 'opacity-100' : 'opacity-0'}`, onMouseDown: (e) => e.stopPropagation(), onClick: (e) => {
977
+ e.stopPropagation();
978
+ deleteNodes([node.id]);
979
+ }, children: jsx(Trash2, { className: "h-3.5 w-3.5" }) }) })), node.type === 'start' ? (jsxs("div", { className: "bg-card border-border relative flex cursor-grab items-center gap-3 rounded-xl border px-4 py-3 transition-colors hover:border-[#38A1E5] active:cursor-grabbing", children: [jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded", style: { backgroundColor: 'rgba(56, 161, 229, 0.2)' }, children: jsx(Play, { className: "h-4 w-4", style: { color: '#38A1E5' } }) }), jsx("span", { className: "text-foreground font-medium", children: node.data.label }), jsx("div", { className: "border-background absolute top-1/2 -right-2 h-3 w-3 -translate-y-1/2 cursor-crosshair rounded-full border-2 opacity-0 transition-opacity group-hover:opacity-100", style: { backgroundColor: '#38A1E5' }, onMouseDown: (e) => handleConnectionStart(e, node.id, 'right') })] })) : node.type === 'while' ? (jsxs("div", { className: "border-border bg-card relative cursor-grab rounded-2xl border transition-colors hover:border-[#38A1E5] active:cursor-grabbing", style: { width: '280px' }, children: [jsxs("div", { className: "flex items-center gap-3 p-4 pb-3", children: [jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-lg", style: { backgroundColor: 'rgba(56, 161, 229, 0.2)' }, children: jsx(RefreshCw, { className: "h-5 w-5", style: { color: '#38A1E5' } }) }), jsx("span", { className: "text-foreground font-medium", children: node.data.label })] }), jsx("div", { className: "space-y-2 px-4 pb-4", children: [
980
+ { handle: WHILE_LOOP_HANDLE, label: t('workflowsWorkflowCanvas.whileLoop') },
981
+ { handle: WHILE_DONE_HANDLE, label: t('workflowsWorkflowCanvas.whileDone') },
982
+ ].map(({ handle, label }) => (jsxs("div", { className: "bg-muted/50 relative flex items-center justify-between rounded-lg px-4 py-3", children: [jsx("span", { className: "text-muted-foreground text-sm", children: label }), jsx("div", { "data-testid": `workflow-handle-${node.id}-${handle}`, className: "border-background absolute top-1/2 -right-[18px] h-3 w-3 -translate-y-1/2 cursor-crosshair rounded-full border-2 opacity-0 transition-opacity group-hover:opacity-100", style: { backgroundColor: '#38A1E5' }, onMouseDown: (e) => handleConnectionStart(e, node.id, handle) })] }, handle))) }), jsx("div", { className: "border-background absolute top-1/2 -left-2 h-3 w-3 -translate-y-1/2 rounded-full border-2 opacity-0 transition-opacity group-hover:opacity-100", style: { backgroundColor: '#38A1E5' } })] })) : isConditionalType(node.type) ? ((() => {
983
+ const conditionCount = getConditionCount(node.data);
984
+ const nodeHeight = branchCardHeight(conditionCount + 1);
985
+ return (jsxs("div", { className: "border-border bg-card relative cursor-grab rounded-2xl border transition-colors hover:border-[#38A1E5] active:cursor-grabbing", style: { width: '280px', minHeight: `${nodeHeight}px` }, children: [jsxs("div", { className: "flex items-center gap-3 p-4 pb-3", children: [jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-lg", style: { backgroundColor: 'rgba(56, 161, 229, 0.2)' }, children: jsx(GitBranch, { className: "h-5 w-5", style: { color: '#38A1E5' } }) }), jsx("span", { className: "text-foreground font-medium", children: node.data.label })] }), jsxs("div", { className: "space-y-2 px-4 pb-4", children: [Array.from({ length: conditionCount }).map((_, index) => (jsxs("div", { className: "bg-muted/50 relative flex items-center justify-between rounded-lg px-4 py-3", children: [jsx("span", { className: "text-muted-foreground text-sm", children: index === 0
986
+ ? t('workflowsWorkflowCanvas.ifCondition')
987
+ : t('workflowsWorkflowCanvas.elseIf', { index }) }), jsx("div", { "data-testid": `workflow-handle-${node.id}-condition-${index}`, className: "border-background absolute top-1/2 -right-[18px] h-3 w-3 -translate-y-1/2 cursor-crosshair rounded-full border-2 opacity-0 transition-opacity group-hover:opacity-100", style: { backgroundColor: '#38A1E5' }, onMouseDown: (e) => handleConnectionStart(e, node.id, `condition-${index}`) })] }, index))), jsxs("div", { className: "bg-muted/50 relative flex items-center justify-between rounded-lg px-4 py-3", children: [jsx("span", { className: "text-muted-foreground text-sm", children: t('workflowsWorkflowCanvas.elseCondition') }), jsx("div", { "data-testid": `workflow-handle-${node.id}-else`, className: "border-background absolute top-1/2 -right-[18px] h-3 w-3 -translate-y-1/2 cursor-crosshair rounded-full border-2 opacity-0 transition-opacity group-hover:opacity-100", style: { backgroundColor: '#38A1E5' }, onMouseDown: (e) => handleConnectionStart(e, node.id, 'else') })] })] }), jsx("div", { className: "border-background absolute top-1/2 -left-2 h-3 w-3 -translate-y-1/2 rounded-full border-2 opacity-0 transition-opacity group-hover:opacity-100", style: { backgroundColor: '#38A1E5' } })] }));
988
+ })()) : node.type === 'user-approval' ? (jsxs("div", { className: "border-border bg-card relative cursor-grab rounded-2xl border transition-colors hover:border-[#38A1E5] active:cursor-grabbing", style: { width: '280px' }, children: [jsxs("div", { className: "flex items-center gap-3 p-4 pb-3", children: [jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-lg", style: { backgroundColor: 'rgba(56, 161, 229, 0.2)' }, children: jsx(ThumbsUp, { className: "h-5 w-5", style: { color: '#38A1E5' } }) }), jsx("span", { className: "text-foreground font-medium", children: node.data.label })] }), jsxs("div", { className: "space-y-2 px-4 pb-4", children: [jsxs("div", { className: "bg-muted/50 relative flex items-center justify-between rounded-lg px-4 py-3", children: [jsx("span", { className: "text-muted-foreground text-sm", children: t('workflowsWorkflowCanvas.approve') }), jsx("div", { className: "border-background absolute top-1/2 -right-[18px] h-3 w-3 -translate-y-1/2 cursor-crosshair rounded-full border-2 opacity-0 transition-opacity group-hover:opacity-100", style: { backgroundColor: '#38A1E5' }, onMouseDown: (e) => handleConnectionStart(e, node.id, 'approve') })] }), jsxs("div", { className: "bg-muted/50 relative flex items-center justify-between rounded-lg px-4 py-3", children: [jsx("span", { className: "text-muted-foreground text-sm", children: t('workflowsWorkflowCanvas.reject') }), jsx("div", { className: "border-background absolute top-1/2 -right-[18px] h-3 w-3 -translate-y-1/2 cursor-crosshair rounded-full border-2 opacity-0 transition-opacity group-hover:opacity-100", style: { backgroundColor: '#38A1E5' }, onMouseDown: (e) => handleConnectionStart(e, node.id, 'reject') })] })] }), jsx("div", { className: "border-background absolute top-1/2 -left-2 h-3 w-3 -translate-y-1/2 rounded-full border-2 opacity-0 transition-opacity group-hover:opacity-100", style: { backgroundColor: '#38A1E5' } })] })) : node.type === 'transform' ||
989
+ node.type === 'set-state' ||
990
+ node.type === 'guardrails' ||
991
+ node.type === 'file-search' ||
992
+ node.type === 'mcp' ? (jsxs("div", { className: "bg-card border-border relative min-w-[140px] cursor-grab rounded-xl border px-4 py-3 transition-colors hover:border-[#38A1E5] active:cursor-grabbing", children: [jsxs("div", { className: "flex items-center gap-2", children: [jsx("div", { className: "flex h-5 w-5 items-center justify-center rounded", style: { backgroundColor: 'rgba(56, 161, 229, 0.2)' }, children: jsx(Bot, { className: "h-3 w-3", style: { color: '#38A1E5' } }) }), jsx("span", { className: "text-foreground text-sm font-medium", children: node.data.label })] }), jsx("div", { className: "border-background absolute top-1/2 -left-2 h-3 w-3 -translate-y-1/2 rounded-full border-2 opacity-0 transition-opacity group-hover:opacity-100", style: { backgroundColor: '#38A1E5' } }), jsx("div", { className: "border-background absolute top-1/2 -right-2 h-3 w-3 -translate-y-1/2 cursor-crosshair rounded-full border-2 opacity-0 transition-opacity group-hover:opacity-100", style: { backgroundColor: '#38A1E5' }, onMouseDown: (e) => handleConnectionStart(e, node.id, 'right') }), jsx("div", { className: "border-background absolute -top-2 left-1/2 h-3 w-3 -translate-x-1/2 cursor-crosshair rounded-full border-2 opacity-0 transition-opacity group-hover:opacity-100", style: { backgroundColor: '#38A1E5' }, onMouseDown: (e) => handleConnectionStart(e, node.id, 'top') }), jsx("div", { className: "border-background absolute -bottom-2 left-1/2 h-3 w-3 -translate-x-1/2 cursor-crosshair rounded-full border-2 opacity-0 transition-opacity group-hover:opacity-100", style: { backgroundColor: '#38A1E5' }, onMouseDown: (e) => handleConnectionStart(e, node.id, 'bottom') })] })) : node.type === 'note' ? (jsxs("div", { className: "border-border relative cursor-grab rounded-lg border bg-amber-50 transition-colors hover:border-[#38A1E5] active:cursor-grabbing dark:bg-amber-950/20", style: {
993
+ width: `${node.width || 200}px`,
994
+ height: `${node.height || 120}px`,
995
+ }, onClick: (e) => {
996
+ e.stopPropagation();
997
+ setEditingNote(node.id);
998
+ }, children: [editingNote === node.id ? (jsx(Textarea, { autoFocus: true, className: "h-full w-full resize-none border-0 bg-transparent text-sm focus-visible:ring-0", value: node.data.content || '', onChange: (e) => {
999
+ setNodes((prev) => prev.map((n) => n.id === node.id
1000
+ ? {
1001
+ ...n,
1002
+ data: { ...n.data, content: e.target.value },
1003
+ }
1004
+ : n));
1005
+ }, onBlur: () => setEditingNote(null) })) : (jsx("div", { className: "text-foreground p-3 text-sm whitespace-pre-wrap", children: node.data.content || t('workflowsWorkflowCanvas.stickyNote') })), jsx("div", { className: "border-background absolute -right-1 -bottom-1 h-4 w-4 cursor-nwse-resize rounded-full border-2 opacity-0 transition-opacity group-hover:opacity-100", style: { backgroundColor: '#38A1E5' }, onMouseDown: (e) => handleResizeStart(e, node.id, 'bottom-right') })] })) : (jsxs("div", { className: "bg-card border-border relative min-w-[140px] cursor-grab rounded-xl border px-4 py-3 transition-colors hover:border-[#38A1E5] active:cursor-grabbing", children: [jsxs("div", { className: "mb-1 flex items-center gap-2", children: [jsx("div", { className: "flex h-5 w-5 items-center justify-center rounded", style: { backgroundColor: 'rgba(56, 161, 229, 0.2)' }, children: jsx(Bot, { className: "h-3 w-3", style: { color: '#38A1E5' } }) }), jsx("span", { className: "text-foreground text-sm font-medium", children: node.data.label }), node.type === 'mentor' && !previewMode && (jsx(ActionTooltip, { label: t('workflowsWorkflowCanvas.changeAgent'), children: jsx("button", { type: "button", "aria-label": t('workflowsWorkflowCanvas.changeAgent'), className: "text-muted-foreground hover:text-foreground ml-auto cursor-pointer p-1 transition-colors", onMouseDown: (e) => e.stopPropagation(), onClick: (e) => {
1006
+ e.stopPropagation();
1007
+ handleMentorChangeClick(node.id);
1008
+ }, children: jsx(Pencil, { className: "h-3.5 w-3.5" }) }) }))] }), node.data.subtitle && (jsx("p", { className: "text-muted-foreground ml-7 text-xs", children: node.data.subtitle })), jsx("div", { className: "border-background absolute top-1/2 -left-2 h-3 w-3 -translate-y-1/2 rounded-full border-2 opacity-0 transition-opacity group-hover:opacity-100", style: { backgroundColor: '#38A1E5' } }), jsx("div", { className: "border-background absolute top-1/2 -right-2 h-3 w-3 -translate-y-1/2 cursor-crosshair rounded-full border-2 opacity-0 transition-opacity group-hover:opacity-100", style: { backgroundColor: '#38A1E5' }, onMouseDown: (e) => handleConnectionStart(e, node.id, 'right') }), jsx("div", { className: "border-background absolute -top-2 left-1/2 h-3 w-3 -translate-x-1/2 cursor-crosshair rounded-full border-2 opacity-0 transition-opacity group-hover:opacity-100", style: { backgroundColor: '#38A1E5' }, onMouseDown: (e) => handleConnectionStart(e, node.id, 'top') }), jsx("div", { className: "border-background absolute -bottom-2 left-1/2 h-3 w-3 -translate-x-1/2 cursor-crosshair rounded-full border-2 opacity-0 transition-opacity group-hover:opacity-100", style: { backgroundColor: '#38A1E5' }, onMouseDown: (e) => handleConnectionStart(e, node.id, 'bottom') })] }))] }, node.id)))] }), !previewMode &&
1009
+ selectedNodeForConfig &&
1010
+ (() => {
1011
+ const node = nodes.find((n) => n.id === selectedNodeForConfig);
1012
+ return node && node.type !== 'mentor' ? (jsx(NodeConfigPanel, { nodeId: node.id, nodeType: node.type || '', nodeData: node.data, onClose: () => setSelectedNodeForConfig(null), onUpdateNode: handleUpdateNode, onDeleteNode: isDeletableNode(node) ? () => deleteNodes([node.id]) : undefined, workflowVariables: workflowVariables, variableUsages: variableUsages, stepReferences: nodes
1013
+ .filter((other) => { var _a; return other.id !== node.id && STEPS_WITH_RESULTS.has((_a = other.type) !== null && _a !== void 0 ? _a : ''); })
1014
+ .map((other) => ({ id: other.id, label: other.data.label })), org: org, defaultMentorId: defaultMentorId, username: username, agentSettings: agentSettings }, node.id)) : null;
1015
+ })(), !previewMode && (jsx(Dialog, { open: showMentorModal, onOpenChange: (open) => {
1016
+ if (open) {
1017
+ setShowMentorModal(true);
1018
+ }
1019
+ else {
1020
+ closeMentorModal();
1021
+ }
1022
+ }, children: jsxs(DialogContent, { className: "w-[calc(100%-2rem)] max-w-3xl rounded-lg", children: [jsx(DialogHeader, { children: jsx(DialogTitle, { children: t('workflowsWorkflowCanvas.changeAgent') }) }), jsx(MentorSelectionGrid, { tenantKey: org !== null && org !== void 0 ? org : '', selectedMentorIds: selectedMentorIds, onMentorSelect: handleMentorSelect, searchQuery: mentorSearchQuery, onSearchChange: setMentorSearchQuery, minHeight: "360px" })] }) }))] }), !previewMode && (jsx("div", { className: "pointer-events-auto absolute bottom-6 left-1/2 -translate-x-1/2 transform", children: jsxs("div", { className: "bg-card border-border flex items-center gap-2 rounded-full border px-3 py-2 shadow-lg", children: [jsx(ActionTooltip, { label: t('workflowsWorkflowCanvas.panTool'), children: jsx(Button, { variant: tool === 'hand' ? 'default' : 'ghost', "aria-label": t('workflowsWorkflowCanvas.panTool'), size: "icon", className: `h-8 w-8 ${tool === 'hand' ? 'bg-[#38A1E5] text-white hover:bg-[#38A1E5]' : ''}`, onClick: () => setTool('hand'), children: jsx(Hand, { className: "h-4 w-4" }) }) }), jsx(ActionTooltip, { label: t('workflowsWorkflowCanvas.selectTool'), children: jsx(Button, { variant: tool === 'pointer' ? 'default' : 'ghost', "aria-label": t('workflowsWorkflowCanvas.selectTool'), size: "icon", className: `h-8 w-8 ${tool === 'pointer' ? 'bg-[#38A1E5] text-white hover:bg-[#38A1E5]' : ''}`, onClick: () => setTool('pointer'), children: jsx(MousePointer, { className: "h-4 w-4" }) }) }), jsx("div", { className: "bg-border mx-1 h-6 w-px" }), jsx(ActionTooltip, { label: t('workflowsWorkflowCanvas.undo'), children: jsx(Button, { variant: "ghost", size: "icon", className: "text-muted-foreground hover:text-foreground h-8 w-8 disabled:opacity-30", onClick: handleUndo, "aria-label": t('workflowsWorkflowCanvas.undo'), disabled: historyIndex <= 0, children: jsx(Undo, { className: "h-4 w-4" }) }) }), jsx(ActionTooltip, { label: t('workflowsWorkflowCanvas.redo'), children: jsx(Button, { variant: "ghost", size: "icon", className: "text-muted-foreground hover:text-foreground h-8 w-8 disabled:opacity-30", onClick: handleRedo, "aria-label": t('workflowsWorkflowCanvas.redo'), disabled: historyIndex >= history.length - 1, children: jsx(Redo, { className: "h-4 w-4" }) }) }), jsx("div", { className: "bg-border mx-1 h-6 w-px" }), jsx(ActionTooltip, { label: t('workflowsWorkflowCanvas.zoomOut'), children: jsx(Button, { variant: "ghost", size: "icon", className: "text-muted-foreground hover:text-foreground h-8 w-8", onClick: handleZoomOut, "aria-label": t('workflowsWorkflowCanvas.zoomOut'), children: jsx(ZoomOut, { className: "h-4 w-4" }) }) }), jsxs("span", { className: "text-muted-foreground min-w-[3ch] text-center text-xs", children: [Math.round(zoom * 100), "%"] }), jsx(ActionTooltip, { label: t('workflowsWorkflowCanvas.zoomIn'), children: jsx(Button, { variant: "ghost", size: "icon", className: "text-muted-foreground hover:text-foreground h-8 w-8", onClick: handleZoomIn, "aria-label": t('workflowsWorkflowCanvas.zoomIn'), children: jsx(ZoomIn, { className: "h-4 w-4" }) }) })] }) }))] }));
1023
+ }
1024
+
1025
+ export { WorkflowCanvas };
1026
+ //# sourceMappingURL=workflow-canvas.js.map