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