@flowuent-org/diagramming-core 1.2.1 → 1.2.2

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 (20) hide show
  1. package/apps/diagramming/src/AutomationDiagramData.ts +65 -1
  2. package/package.json +1 -1
  3. package/packages/diagrams/src/lib/components/CanvasSearchBar.tsx +281 -0
  4. package/packages/diagrams/src/lib/components/automation/AutomationApiNode.tsx +796 -788
  5. package/packages/diagrams/src/lib/components/automation/AutomationEndNode.tsx +608 -600
  6. package/packages/diagrams/src/lib/components/automation/AutomationFormattingNode.tsx +833 -825
  7. package/packages/diagrams/src/lib/components/automation/AutomationNavigationNode.tsx +584 -0
  8. package/packages/diagrams/src/lib/components/automation/AutomationNoteNode.tsx +422 -414
  9. package/packages/diagrams/src/lib/components/automation/AutomationSheetsNode.tsx +1120 -1112
  10. package/packages/diagrams/src/lib/components/automation/AutomationStartNode.tsx +511 -503
  11. package/packages/diagrams/src/lib/components/automation/NodeAIAssistantPopup.tsx +504 -0
  12. package/packages/diagrams/src/lib/components/automation/NodeActionButtons.tsx +146 -145
  13. package/packages/diagrams/src/lib/components/automation/index.ts +21 -12
  14. package/packages/diagrams/src/lib/contexts/SearchContext.tsx +78 -0
  15. package/packages/diagrams/src/lib/templates/DiagramContainer.tsx +79 -76
  16. package/packages/diagrams/src/lib/templates/DiagramContent.tsx +276 -61
  17. package/packages/diagrams/src/lib/types/automation-node-data-types.ts +22 -2
  18. package/packages/diagrams/src/lib/types/node-types.ts +2 -0
  19. package/packages/diagrams/src/lib/utils/highlightText.tsx +93 -0
  20. package/packages/diagrams/src/lib/utils/nodeAIAssistantConfig.ts +54 -0
@@ -1,414 +1,422 @@
1
- import React, { useEffect, useRef, useState } from 'react';
2
- import { Handle, Position, useNodeId } from '@xyflow/react';
3
- import { Box, Typography, Button } from '@mui/material';
4
- import { Description as DescriptionIcon, Lightbulb as LightbulbIcon } from '@mui/icons-material';
5
- import { getIconByName } from '../../utils/iconMapper';
6
- import { AISuggestion } from './AISuggestionsModal';
7
- import { AISuggestionsPanel } from './AISuggestionsPanel';
8
- import { NodeActionButtons } from './NodeActionButtons';
9
- import { useDiagram } from '../../contexts/DiagramProvider';
10
-
11
- interface AutomationNoteNodeProps {
12
- data: {
13
- label: string;
14
- description: string;
15
- backgroundColor?: string;
16
- textColor?: string;
17
- borderColor?: string;
18
- iconName?: string;
19
- noteType?: 'info' | 'warning' | 'note' | 'purpose';
20
- formData?: {
21
- aiSuggestionsCount?: number; // Number of AI suggestions available
22
- [key: string]: any;
23
- };
24
- };
25
- selected?: boolean;
26
- }
27
-
28
- export const AutomationNoteNode: React.FC<AutomationNoteNodeProps> = ({ data, selected }) => {
29
- const nodeRef = useRef<HTMLDivElement | null>(null);
30
- const [showSuggestions, setShowSuggestions] = useState(false);
31
- const nodeId = useNodeId();
32
- const onNodesChange = useDiagram((state) => state.onNodesChange);
33
- const nodes = useDiagram((state) => state.nodes);
34
- const setNodes = useDiagram((state) => state.setNodes);
35
-
36
- // Get the icon component based on the iconName, default to DescriptionIcon
37
- const IconComponent = getIconByName(data.iconName || 'Description');
38
-
39
- // Use consistent colors with other automation nodes
40
- const colors = {
41
- backgroundColor: '#181C25', // Default automation node background
42
- textColor: '#ffffff',
43
- borderColor: '#1e293b',
44
- iconBg: '#3b82f6' // Default blue for icon background
45
- };
46
-
47
- // Debug logging for node dimensions
48
- useEffect(() => {
49
- if (nodeRef.current) {
50
- const rect = nodeRef.current.getBoundingClientRect();
51
- const computedStyle = window.getComputedStyle(nodeRef.current);
52
- console.log('AutomationNoteNode Debug:', {
53
- nodeId: data.label,
54
- actualDimensions: {
55
- width: rect.width,
56
- height: rect.height,
57
- left: rect.left,
58
- top: rect.top
59
- },
60
- computedStyle: {
61
- width: computedStyle.width,
62
- height: computedStyle.height,
63
- padding: computedStyle.padding,
64
- border: computedStyle.border,
65
- boxShadow: computedStyle.boxShadow,
66
- margin: computedStyle.margin
67
- },
68
- handlePositions: {
69
- left: '-8px',
70
- right: '-8px'
71
- }
72
- });
73
- }
74
- }, [data.label]);
75
-
76
- return (
77
- <Box
78
- sx={{
79
- position: 'relative',
80
- width: '336px',
81
- overflow: 'visible',
82
- }}
83
- >
84
- <Box
85
- ref={nodeRef}
86
- sx={{
87
- width: '336px',
88
- minHeight: '150px',
89
- backgroundColor: colors.backgroundColor,
90
- border: selected ? `2px solid ${colors.borderColor}` : `1px solid ${colors.borderColor}`,
91
- borderRadius: '12px',
92
- padding: '0',
93
- color: colors.textColor,
94
- position: 'relative',
95
- boxShadow: selected ? `0 0 0 2px rgba(59, 130, 246, 0.5)` : '0 4px 8px rgba(0, 0, 0, 0.3)',
96
- transition: 'all 0.2s ease',
97
- cursor: 'pointer',
98
- overflow: 'hidden',
99
- }}
100
- >
101
- {/* Header */}
102
- <Box sx={{
103
- display: 'flex',
104
- alignItems: 'center',
105
- justifyContent: 'space-between',
106
- p: 2,
107
- borderBottom: '1px solid rgba(255, 255, 255, 0.1)'
108
- }}>
109
- <Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
110
- <Box
111
- sx={{
112
- width: '32px',
113
- height: '32px',
114
- backgroundColor: colors.iconBg,
115
- borderRadius: '6px',
116
- display: 'flex',
117
- alignItems: 'center',
118
- justifyContent: 'center',
119
- }}
120
- >
121
- <IconComponent sx={{ color: 'white', fontSize: '18px' }} />
122
- </Box>
123
- <Typography variant="h6" sx={{ fontWeight: 600, fontSize: '16px' }}>
124
- {data.label}
125
- </Typography>
126
- </Box>
127
- </Box>
128
-
129
- {/* Content */}
130
- <Box sx={{ p: 2 }}>
131
- <Typography variant="body2" sx={{
132
- color: colors.textColor,
133
- fontSize: '14px',
134
- lineHeight: 1.5,
135
- opacity: 0.9
136
- }}>
137
- {data.description}
138
- </Typography>
139
- </Box>
140
-
141
- {/* Note Type Indicator */}
142
- <Box sx={{
143
- position: 'absolute',
144
- top: '8px',
145
- right: '8px',
146
- px: 1,
147
- py: 0.5,
148
- backgroundColor: 'rgba(255, 255, 255, 0.2)',
149
- borderRadius: '4px',
150
- fontSize: '10px',
151
- fontWeight: 500,
152
- textTransform: 'uppercase',
153
- letterSpacing: '0.5px'
154
- }}>
155
- {data.noteType || 'note'}
156
- </Box>
157
-
158
- {/* Connection Handles - Bidirectional (source + target at each position) */}
159
- {/* Top - Source */}
160
- <Handle
161
- type="source"
162
- position={Position.Top}
163
- id="top-source"
164
- className="connection-handle"
165
- style={{
166
- background: selected ? '#10B981' : '#1a1a2e',
167
- width: '14px',
168
- height: '14px',
169
- border: '3px solid #10B981',
170
- top: '-8px',
171
- opacity: selected ? 1 : 0,
172
- transition: 'all 0.2s ease-in-out',
173
- cursor: 'crosshair',
174
- zIndex: 10,
175
- }}
176
- />
177
- {/* Top - Target (hidden but functional) */}
178
- <Handle
179
- type="target"
180
- position={Position.Top}
181
- id="top-target"
182
- style={{
183
- background: 'transparent',
184
- width: '14px',
185
- height: '14px',
186
- border: 'none',
187
- top: '-8px',
188
- opacity: 0,
189
- pointerEvents: selected ? 'all' : 'none',
190
- }}
191
- />
192
- {/* Bottom - Source */}
193
- <Handle
194
- type="source"
195
- position={Position.Bottom}
196
- id="bottom-source"
197
- className="connection-handle"
198
- style={{
199
- background: selected ? '#10B981' : '#1a1a2e',
200
- width: '14px',
201
- height: '14px',
202
- border: '3px solid #10B981',
203
- bottom: '-8px',
204
- opacity: selected ? 1 : 0,
205
- transition: 'all 0.2s ease-in-out',
206
- cursor: 'crosshair',
207
- zIndex: 10,
208
- }}
209
- />
210
- {/* Bottom - Target (hidden but functional) */}
211
- <Handle
212
- type="target"
213
- position={Position.Bottom}
214
- id="bottom-target"
215
- style={{
216
- background: 'transparent',
217
- width: '14px',
218
- height: '14px',
219
- border: 'none',
220
- bottom: '-8px',
221
- opacity: 0,
222
- pointerEvents: selected ? 'all' : 'none',
223
- }}
224
- />
225
- {/* Left - Source */}
226
- <Handle
227
- type="source"
228
- position={Position.Left}
229
- id="left-source"
230
- className="connection-handle"
231
- style={{
232
- background: selected ? '#10B981' : '#1a1a2e',
233
- width: '14px',
234
- height: '14px',
235
- border: '3px solid #10B981',
236
- left: '-8px',
237
- opacity: selected ? 1 : 0,
238
- transition: 'all 0.2s ease-in-out',
239
- cursor: 'crosshair',
240
- zIndex: 10,
241
- }}
242
- />
243
- {/* Left - Target (hidden but functional) */}
244
- <Handle
245
- type="target"
246
- position={Position.Left}
247
- id="left-target"
248
- style={{
249
- background: 'transparent',
250
- width: '14px',
251
- height: '14px',
252
- border: 'none',
253
- left: '-8px',
254
- opacity: 0,
255
- pointerEvents: selected ? 'all' : 'none',
256
- }}
257
- />
258
- {/* Right - Source */}
259
- <Handle
260
- type="source"
261
- position={Position.Right}
262
- id="right-source"
263
- className="connection-handle"
264
- style={{
265
- background: selected ? '#10B981' : '#1a1a2e',
266
- width: '14px',
267
- height: '14px',
268
- border: '3px solid #10B981',
269
- right: '-8px',
270
- opacity: selected ? 1 : 0,
271
- transition: 'all 0.2s ease-in-out',
272
- cursor: 'crosshair',
273
- zIndex: 10,
274
- }}
275
- />
276
- {/* Right - Target (hidden but functional) */}
277
- <Handle
278
- type="target"
279
- position={Position.Right}
280
- id="right-target"
281
- style={{
282
- background: 'transparent',
283
- width: '14px',
284
- height: '14px',
285
- border: 'none',
286
- right: '-8px',
287
- opacity: 0,
288
- pointerEvents: selected ? 'all' : 'none',
289
- }}
290
- />
291
-
292
- </Box>
293
-
294
- {/* Node Action Buttons - Shows when selected */}
295
- <NodeActionButtons
296
- selected={selected}
297
- onDelete={() => {
298
- if (nodeId && onNodesChange) {
299
- onNodesChange([{ id: nodeId, type: 'remove' }]);
300
- }
301
- }}
302
- onDuplicate={() => {
303
- if (nodeId) {
304
- const currentNode = nodes.find(n => n.id === nodeId);
305
- if (currentNode) {
306
- const newNode = {
307
- ...currentNode,
308
- id: `${currentNode.id}-copy-${Date.now()}`,
309
- position: {
310
- x: currentNode.position.x + 50,
311
- y: currentNode.position.y + 50,
312
- },
313
- selected: false,
314
- };
315
- setNodes([...nodes, newNode]);
316
- }
317
- }
318
- }}
319
- />
320
-
321
- {/* AI Suggestions Button - Positioned below the node box */}
322
- {data.formData?.aiSuggestionsCount !== undefined && data.formData.aiSuggestionsCount > 0 && (
323
- <Box
324
- sx={{
325
- position: 'absolute',
326
- top: '100%',
327
- left: '50%',
328
- transform: 'translateX(-50%)',
329
- marginTop: '12px',
330
- zIndex: 10,
331
- whiteSpace: 'nowrap',
332
- }}
333
- onClick={(e) => {
334
- e.stopPropagation();
335
- // Toggle AI Suggestions panel
336
- setShowSuggestions(!showSuggestions);
337
- }}
338
- >
339
- <Button
340
- variant="contained"
341
- startIcon={<LightbulbIcon sx={{ fontSize: '12px' }} />}
342
- sx={{
343
- backgroundColor: '#2563EB',
344
- color: '#ffffff',
345
- borderRadius: '20px',
346
- textTransform: 'none',
347
- fontSize: '10px',
348
- fontWeight: 400,
349
- padding: '8px 16px',
350
- whiteSpace: 'nowrap',
351
- display: 'inline-flex',
352
- alignItems: 'center',
353
- boxShadow: '0 2px 8px rgba(0, 0, 0, 0.3)',
354
- '&:hover': {
355
- backgroundColor: '#2563eb',
356
- },
357
- '& .MuiButton-startIcon': {
358
- marginRight: '8px',
359
- }
360
- }}
361
- >
362
- AI Suggestions
363
- <Box
364
- component="span"
365
- sx={{
366
- marginLeft: '8px',
367
- backgroundColor: '#FFFFFF26',
368
- color: '#ffffff',
369
- fontSize: '10px',
370
- fontWeight: 400,
371
- minWidth: '18px',
372
- height: '18px',
373
- borderRadius: '9px',
374
- display: 'inline-flex',
375
- alignItems: 'center',
376
- justifyContent: 'center',
377
- padding: '0 6px',
378
- border: '1px solid rgba(255, 255, 255, 0.2)',
379
- }}
380
- >
381
- {data.formData.aiSuggestionsCount}
382
- </Box>
383
- </Button>
384
- </Box>
385
- )}
386
-
387
- {/* AI Suggestions Panel - Rendered on canvas below the button */}
388
- {showSuggestions && data.formData?.aiSuggestionsCount !== undefined && data.formData.aiSuggestionsCount > 0 && nodeId && (
389
- <AISuggestionsPanel
390
- suggestions={data.formData?.aiSuggestions || [
391
- {
392
- id: '1',
393
- title: 'Add Citation Extraction',
394
- description: 'Automatically extract and format citations from article content.',
395
- tags: ['classification', 'enhancement'],
396
- },
397
- {
398
- id: '2',
399
- title: 'Generate Bullet Summary',
400
- description: 'Create a concise bullet-point summary of the article\'s main points.',
401
- tags: ['classification', 'enhancement'],
402
- },
403
- ]}
404
- parentNodeId={nodeId}
405
- onSuggestionClick={(suggestion) => {
406
- console.log('Suggestion clicked:', suggestion);
407
- // Handle suggestion selection here
408
- }}
409
- onClose={() => setShowSuggestions(false)}
410
- />
411
- )}
412
- </Box>
413
- );
414
- };
1
+ import React, { useEffect, useRef, useState } from 'react';
2
+ import { Handle, Position, useNodeId } from '@xyflow/react';
3
+ import { Box, Typography, Button } from '@mui/material';
4
+ import { Description as DescriptionIcon, Lightbulb as LightbulbIcon } from '@mui/icons-material';
5
+ import { getIconByName } from '../../utils/iconMapper';
6
+ import { AISuggestion } from './AISuggestionsModal';
7
+ import { AISuggestionsPanel } from './AISuggestionsPanel';
8
+ import { NodeActionButtons } from './NodeActionButtons';
9
+ import { showNodeAIAssistantPopup } from './NodeAIAssistantPopup';
10
+ import { useDiagram } from '../../contexts/DiagramProvider';
11
+ import { useSearch } from '../../contexts/SearchContext';
12
+
13
+ interface AutomationNoteNodeProps {
14
+ data: {
15
+ label: string;
16
+ description: string;
17
+ backgroundColor?: string;
18
+ textColor?: string;
19
+ borderColor?: string;
20
+ iconName?: string;
21
+ noteType?: 'info' | 'warning' | 'note' | 'purpose';
22
+ formData?: {
23
+ aiSuggestionsCount?: number; // Number of AI suggestions available
24
+ [key: string]: any;
25
+ };
26
+ };
27
+ selected?: boolean;
28
+ }
29
+
30
+ export const AutomationNoteNode: React.FC<AutomationNoteNodeProps> = ({ data, selected }) => {
31
+ const nodeRef = useRef<HTMLDivElement | null>(null);
32
+ const [showSuggestions, setShowSuggestions] = useState(false);
33
+ const { highlightText } = useSearch();
34
+ const nodeId = useNodeId();
35
+ const onNodesChange = useDiagram((state) => state.onNodesChange);
36
+ const nodes = useDiagram((state) => state.nodes);
37
+ const setNodes = useDiagram((state) => state.setNodes);
38
+
39
+ // Get the icon component based on the iconName, default to DescriptionIcon
40
+ const IconComponent = getIconByName(data.iconName || 'Description');
41
+
42
+ // Use consistent colors with other automation nodes
43
+ const colors = {
44
+ backgroundColor: '#181C25', // Default automation node background
45
+ textColor: '#ffffff',
46
+ borderColor: '#1e293b',
47
+ iconBg: '#3b82f6' // Default blue for icon background
48
+ };
49
+
50
+ // Debug logging for node dimensions
51
+ useEffect(() => {
52
+ if (nodeRef.current) {
53
+ const rect = nodeRef.current.getBoundingClientRect();
54
+ const computedStyle = window.getComputedStyle(nodeRef.current);
55
+ console.log('AutomationNoteNode Debug:', {
56
+ nodeId: data.label,
57
+ actualDimensions: {
58
+ width: rect.width,
59
+ height: rect.height,
60
+ left: rect.left,
61
+ top: rect.top
62
+ },
63
+ computedStyle: {
64
+ width: computedStyle.width,
65
+ height: computedStyle.height,
66
+ padding: computedStyle.padding,
67
+ border: computedStyle.border,
68
+ boxShadow: computedStyle.boxShadow,
69
+ margin: computedStyle.margin
70
+ },
71
+ handlePositions: {
72
+ left: '-8px',
73
+ right: '-8px'
74
+ }
75
+ });
76
+ }
77
+ }, [data.label]);
78
+
79
+ return (
80
+ <Box
81
+ sx={{
82
+ position: 'relative',
83
+ width: '336px',
84
+ overflow: 'visible',
85
+ }}
86
+ >
87
+ <Box
88
+ ref={nodeRef}
89
+ sx={{
90
+ width: '336px',
91
+ minHeight: '150px',
92
+ backgroundColor: colors.backgroundColor,
93
+ border: selected ? `2px solid ${colors.borderColor}` : `1px solid ${colors.borderColor}`,
94
+ borderRadius: '12px',
95
+ padding: '0',
96
+ color: colors.textColor,
97
+ position: 'relative',
98
+ boxShadow: selected ? `0 0 0 2px rgba(59, 130, 246, 0.5)` : '0 4px 8px rgba(0, 0, 0, 0.3)',
99
+ transition: 'all 0.2s ease',
100
+ cursor: 'pointer',
101
+ overflow: 'hidden',
102
+ }}
103
+ >
104
+ {/* Header */}
105
+ <Box sx={{
106
+ display: 'flex',
107
+ alignItems: 'center',
108
+ justifyContent: 'space-between',
109
+ p: 2,
110
+ borderBottom: '1px solid rgba(255, 255, 255, 0.1)'
111
+ }}>
112
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
113
+ <Box
114
+ sx={{
115
+ width: '32px',
116
+ height: '32px',
117
+ backgroundColor: colors.iconBg,
118
+ borderRadius: '6px',
119
+ display: 'flex',
120
+ alignItems: 'center',
121
+ justifyContent: 'center',
122
+ }}
123
+ >
124
+ <IconComponent sx={{ color: 'white', fontSize: '18px' }} />
125
+ </Box>
126
+ <Typography variant="h6" sx={{ fontWeight: 600, fontSize: '16px' }}>
127
+ {highlightText(data.label)}
128
+ </Typography>
129
+ </Box>
130
+ </Box>
131
+
132
+ {/* Content */}
133
+ <Box sx={{ p: 2 }}>
134
+ <Typography variant="body2" sx={{
135
+ color: colors.textColor,
136
+ fontSize: '14px',
137
+ lineHeight: 1.5,
138
+ opacity: 0.9
139
+ }}>
140
+ {data.description}
141
+ </Typography>
142
+ </Box>
143
+
144
+ {/* Note Type Indicator */}
145
+ <Box sx={{
146
+ position: 'absolute',
147
+ top: '8px',
148
+ right: '8px',
149
+ px: 1,
150
+ py: 0.5,
151
+ backgroundColor: 'rgba(255, 255, 255, 0.2)',
152
+ borderRadius: '4px',
153
+ fontSize: '10px',
154
+ fontWeight: 500,
155
+ textTransform: 'uppercase',
156
+ letterSpacing: '0.5px'
157
+ }}>
158
+ {data.noteType || 'note'}
159
+ </Box>
160
+
161
+ {/* Connection Handles - Bidirectional (source + target at each position) */}
162
+ {/* Top - Source */}
163
+ <Handle
164
+ type="source"
165
+ position={Position.Top}
166
+ id="top-source"
167
+ className="connection-handle"
168
+ style={{
169
+ background: selected ? '#10B981' : '#1a1a2e',
170
+ width: '14px',
171
+ height: '14px',
172
+ border: '3px solid #10B981',
173
+ top: '-8px',
174
+ opacity: selected ? 1 : 0,
175
+ transition: 'all 0.2s ease-in-out',
176
+ cursor: 'crosshair',
177
+ zIndex: 10,
178
+ }}
179
+ />
180
+ {/* Top - Target (hidden but functional) */}
181
+ <Handle
182
+ type="target"
183
+ position={Position.Top}
184
+ id="top-target"
185
+ style={{
186
+ background: 'transparent',
187
+ width: '14px',
188
+ height: '14px',
189
+ border: 'none',
190
+ top: '-8px',
191
+ opacity: 0,
192
+ pointerEvents: selected ? 'all' : 'none',
193
+ }}
194
+ />
195
+ {/* Bottom - Source */}
196
+ <Handle
197
+ type="source"
198
+ position={Position.Bottom}
199
+ id="bottom-source"
200
+ className="connection-handle"
201
+ style={{
202
+ background: selected ? '#10B981' : '#1a1a2e',
203
+ width: '14px',
204
+ height: '14px',
205
+ border: '3px solid #10B981',
206
+ bottom: '-8px',
207
+ opacity: selected ? 1 : 0,
208
+ transition: 'all 0.2s ease-in-out',
209
+ cursor: 'crosshair',
210
+ zIndex: 10,
211
+ }}
212
+ />
213
+ {/* Bottom - Target (hidden but functional) */}
214
+ <Handle
215
+ type="target"
216
+ position={Position.Bottom}
217
+ id="bottom-target"
218
+ style={{
219
+ background: 'transparent',
220
+ width: '14px',
221
+ height: '14px',
222
+ border: 'none',
223
+ bottom: '-8px',
224
+ opacity: 0,
225
+ pointerEvents: selected ? 'all' : 'none',
226
+ }}
227
+ />
228
+ {/* Left - Source */}
229
+ <Handle
230
+ type="source"
231
+ position={Position.Left}
232
+ id="left-source"
233
+ className="connection-handle"
234
+ style={{
235
+ background: selected ? '#10B981' : '#1a1a2e',
236
+ width: '14px',
237
+ height: '14px',
238
+ border: '3px solid #10B981',
239
+ left: '-8px',
240
+ opacity: selected ? 1 : 0,
241
+ transition: 'all 0.2s ease-in-out',
242
+ cursor: 'crosshair',
243
+ zIndex: 10,
244
+ }}
245
+ />
246
+ {/* Left - Target (hidden but functional) */}
247
+ <Handle
248
+ type="target"
249
+ position={Position.Left}
250
+ id="left-target"
251
+ style={{
252
+ background: 'transparent',
253
+ width: '14px',
254
+ height: '14px',
255
+ border: 'none',
256
+ left: '-8px',
257
+ opacity: 0,
258
+ pointerEvents: selected ? 'all' : 'none',
259
+ }}
260
+ />
261
+ {/* Right - Source */}
262
+ <Handle
263
+ type="source"
264
+ position={Position.Right}
265
+ id="right-source"
266
+ className="connection-handle"
267
+ style={{
268
+ background: selected ? '#10B981' : '#1a1a2e',
269
+ width: '14px',
270
+ height: '14px',
271
+ border: '3px solid #10B981',
272
+ right: '-8px',
273
+ opacity: selected ? 1 : 0,
274
+ transition: 'all 0.2s ease-in-out',
275
+ cursor: 'crosshair',
276
+ zIndex: 10,
277
+ }}
278
+ />
279
+ {/* Right - Target (hidden but functional) */}
280
+ <Handle
281
+ type="target"
282
+ position={Position.Right}
283
+ id="right-target"
284
+ style={{
285
+ background: 'transparent',
286
+ width: '14px',
287
+ height: '14px',
288
+ border: 'none',
289
+ right: '-8px',
290
+ opacity: 0,
291
+ pointerEvents: selected ? 'all' : 'none',
292
+ }}
293
+ />
294
+
295
+ </Box>
296
+
297
+ {/* Node Action Buttons - Shows when selected */}
298
+ <NodeActionButtons
299
+ selected={selected}
300
+ onOpenAIAssistant={(buttonElement) => {
301
+ if (nodeId) {
302
+ showNodeAIAssistantPopup(nodeId, 'Note Node', buttonElement);
303
+ }
304
+ }}
305
+ onDelete={() => {
306
+ if (nodeId && onNodesChange) {
307
+ onNodesChange([{ id: nodeId, type: 'remove' }]);
308
+ }
309
+ }}
310
+ onDuplicate={() => {
311
+ if (nodeId) {
312
+ const currentNode = nodes.find(n => n.id === nodeId);
313
+ if (currentNode) {
314
+ const newNode = {
315
+ ...currentNode,
316
+ id: `${currentNode.id}-copy-${Date.now()}`,
317
+ position: {
318
+ x: currentNode.position.x + 50,
319
+ y: currentNode.position.y + 50,
320
+ },
321
+ selected: false,
322
+ };
323
+ setNodes([...nodes, newNode]);
324
+ }
325
+ }
326
+ }}
327
+ />
328
+
329
+ {/* AI Suggestions Button - Positioned below the node box */}
330
+ {data.formData?.aiSuggestionsCount !== undefined && data.formData.aiSuggestionsCount > 0 && (
331
+ <Box
332
+ sx={{
333
+ position: 'absolute',
334
+ top: '100%',
335
+ left: '50%',
336
+ transform: 'translateX(-50%)',
337
+ marginTop: '12px',
338
+ zIndex: 10,
339
+ whiteSpace: 'nowrap',
340
+ }}
341
+ onClick={(e) => {
342
+ e.stopPropagation();
343
+ // Toggle AI Suggestions panel
344
+ setShowSuggestions(!showSuggestions);
345
+ }}
346
+ >
347
+ <Button
348
+ variant="contained"
349
+ startIcon={<LightbulbIcon sx={{ fontSize: '12px' }} />}
350
+ sx={{
351
+ backgroundColor: '#2563EB',
352
+ color: '#ffffff',
353
+ borderRadius: '20px',
354
+ textTransform: 'none',
355
+ fontSize: '10px',
356
+ fontWeight: 400,
357
+ padding: '8px 16px',
358
+ whiteSpace: 'nowrap',
359
+ display: 'inline-flex',
360
+ alignItems: 'center',
361
+ boxShadow: '0 2px 8px rgba(0, 0, 0, 0.3)',
362
+ '&:hover': {
363
+ backgroundColor: '#2563eb',
364
+ },
365
+ '& .MuiButton-startIcon': {
366
+ marginRight: '8px',
367
+ }
368
+ }}
369
+ >
370
+ AI Suggestions
371
+ <Box
372
+ component="span"
373
+ sx={{
374
+ marginLeft: '8px',
375
+ backgroundColor: '#FFFFFF26',
376
+ color: '#ffffff',
377
+ fontSize: '10px',
378
+ fontWeight: 400,
379
+ minWidth: '18px',
380
+ height: '18px',
381
+ borderRadius: '9px',
382
+ display: 'inline-flex',
383
+ alignItems: 'center',
384
+ justifyContent: 'center',
385
+ padding: '0 6px',
386
+ border: '1px solid rgba(255, 255, 255, 0.2)',
387
+ }}
388
+ >
389
+ {data.formData.aiSuggestionsCount}
390
+ </Box>
391
+ </Button>
392
+ </Box>
393
+ )}
394
+
395
+ {/* AI Suggestions Panel - Rendered on canvas below the button */}
396
+ {showSuggestions && data.formData?.aiSuggestionsCount !== undefined && data.formData.aiSuggestionsCount > 0 && nodeId && (
397
+ <AISuggestionsPanel
398
+ suggestions={data.formData?.aiSuggestions || [
399
+ {
400
+ id: '1',
401
+ title: 'Add Citation Extraction',
402
+ description: 'Automatically extract and format citations from article content.',
403
+ tags: ['classification', 'enhancement'],
404
+ },
405
+ {
406
+ id: '2',
407
+ title: 'Generate Bullet Summary',
408
+ description: 'Create a concise bullet-point summary of the article\'s main points.',
409
+ tags: ['classification', 'enhancement'],
410
+ },
411
+ ]}
412
+ parentNodeId={nodeId}
413
+ onSuggestionClick={(suggestion) => {
414
+ console.log('Suggestion clicked:', suggestion);
415
+ // Handle suggestion selection here
416
+ }}
417
+ onClose={() => setShowSuggestions(false)}
418
+ />
419
+ )}
420
+ </Box>
421
+ );
422
+ };