@flowuent-org/diagramming-core 1.1.9 → 1.2.0

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