@flowuent-org/diagramming-core 1.0.8 → 1.1.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 (40) hide show
  1. package/apps/diagramming/src/AutomationDiagramData.ts +22 -0
  2. package/apps/diagramming/src/components/AddNodeView.tsx +252 -252
  3. package/apps/diagramming/src/main.tsx +463 -463
  4. package/apps/diagramming/src/node-data.ts +664 -664
  5. package/apps/diagramming/src/stencil-items.ts +31 -31
  6. package/apps/diagramming/src/vite-env.d.ts +1 -1
  7. package/package.json +1 -1
  8. package/packages/diagrams/NODE_DATA_UPDATE_API.md +430 -430
  9. package/packages/diagrams/README.md +7 -463
  10. package/packages/diagrams/UNDO_REDO_API.md +306 -306
  11. package/packages/diagrams/package.json +27 -27
  12. package/packages/diagrams/project.json +42 -42
  13. package/packages/diagrams/rollup.config.js +26 -26
  14. package/packages/diagrams/src/DiagramFlow.tsx +7 -7
  15. package/packages/diagrams/src/index.ts +116 -116
  16. package/packages/diagrams/src/index.ts.bak +99 -99
  17. package/packages/diagrams/src/lib/atoms/CardEditableTitle.tsx +76 -76
  18. package/packages/diagrams/src/lib/atoms/ExpressionInput.tsx +437 -437
  19. package/packages/diagrams/src/lib/components/DiagramPanel.tsx +331 -331
  20. package/packages/diagrams/src/lib/components/automation/AISuggestionsModal.tsx +269 -0
  21. package/packages/diagrams/src/lib/components/automation/AISuggestionsPanel.tsx +227 -0
  22. package/packages/diagrams/src/lib/components/automation/AutomationAISuggestionNode.tsx +178 -115
  23. package/packages/diagrams/src/lib/components/automation/AutomationApiNode.tsx +133 -27
  24. package/packages/diagrams/src/lib/components/automation/AutomationEndNode.tsx +134 -28
  25. package/packages/diagrams/src/lib/components/automation/AutomationFormattingNode.tsx +132 -27
  26. package/packages/diagrams/src/lib/components/automation/AutomationNoteNode.tsx +124 -17
  27. package/packages/diagrams/src/lib/components/automation/AutomationSheetsNode.tsx +122 -15
  28. package/packages/diagrams/src/lib/components/automation/index.ts +3 -0
  29. package/packages/diagrams/src/lib/contexts/onWorkflowNodeDelete.ts +65 -65
  30. package/packages/diagrams/src/lib/organisms/CustomEdge/useCreateBendPoint.tsx +121 -121
  31. package/packages/diagrams/src/lib/organisms/WorkFlowNode/NodeActionButtons.tsx +45 -45
  32. package/packages/diagrams/src/lib/templates/node-forms/CallForm.tsx +370 -370
  33. package/packages/diagrams/src/lib/templates/systemFlow/components/FloatingEdge.tsx +219 -219
  34. package/packages/diagrams/src/lib/types/card-node.ts +68 -68
  35. package/packages/diagrams/src/lib/types/node-types.ts +29 -29
  36. package/packages/diagrams/src/lib/utils/AutomationExecutionEngine.ts +1179 -1179
  37. package/packages/diagrams/tsconfig.lib.json +25 -25
  38. package/tsconfig.base.json +29 -30
  39. package/TRANSLATION_FIX_SUMMARY.md +0 -118
  40. package/packages/diagrams/I18N_SETUP.md +0 -126
@@ -1,6 +1,7 @@
1
1
  import React, { useEffect, useRef } from 'react';
2
- import { Handle, Position } from '@xyflow/react';
2
+ import { Handle, Position, useReactFlow, useNodeId } from '@xyflow/react';
3
3
  import { Box, Typography, Chip, Button } from '@mui/material';
4
+ import { AccessTime as AccessTimeIcon, CheckCircle as CheckCircleIcon, Link as LinkIcon } from '@mui/icons-material';
4
5
  import { getIconByName } from '../../utils/iconMapper';
5
6
  import { useTranslation } from 'react-i18next';
6
7
 
@@ -14,11 +15,19 @@ interface AutomationAISuggestionNodeProps {
14
15
  iconName?: string;
15
16
  formData?: {
16
17
  pointers?: string[];
18
+ mappings?: Array<{
19
+ from: string;
20
+ to: string;
21
+ }>;
17
22
  mapping?: {
18
23
  from: string;
19
24
  to: string;
20
25
  };
21
26
  badgeText?: string;
27
+ benefits?: string[];
28
+ lastRun?: string;
29
+ edgeId?: string; // ID of the edge connecting this node
30
+ isConfirmed?: boolean; // Whether "Add this" button has been clicked
22
31
  };
23
32
  };
24
33
  selected?: boolean;
@@ -27,9 +36,59 @@ interface AutomationAISuggestionNodeProps {
27
36
  export const AutomationAISuggestionNode: React.FC<AutomationAISuggestionNodeProps> = ({ data, selected }) => {
28
37
  const { t } = useTranslation();
29
38
  const nodeRef = useRef<HTMLDivElement | null>(null);
39
+ const { setEdges, getEdges, setNodes } = useReactFlow();
40
+ const nodeId = useNodeId();
30
41
 
31
42
  const IconComponent = getIconByName(data.iconName || 'Lightbulb');
32
43
 
44
+ // Handle "Add this" button click - convert dotted edge to solid
45
+ const handleAddThis = () => {
46
+ if (!data.formData?.edgeId || data.formData?.isConfirmed || !nodeId) return;
47
+
48
+ const edges = getEdges();
49
+ const edge = edges.find(e => e.id === data.formData?.edgeId);
50
+
51
+ if (edge) {
52
+ // Update edge to solid line (remove strokeDasharray)
53
+ setEdges((prevEdges) =>
54
+ prevEdges.map((e) =>
55
+ e.id === edge.id
56
+ ? {
57
+ ...e,
58
+ style: {
59
+ ...e.style,
60
+ strokeDasharray: undefined, // Remove dotted line
61
+ },
62
+ data: {
63
+ ...e.data,
64
+ isSuggested: false, // Mark as confirmed
65
+ },
66
+ }
67
+ : e
68
+ )
69
+ );
70
+
71
+ // Update node to mark as confirmed and change background from transparent to opaque
72
+ setNodes((prevNodes) =>
73
+ prevNodes.map((n) =>
74
+ n.id === nodeId
75
+ ? {
76
+ ...n,
77
+ data: {
78
+ ...n.data,
79
+ backgroundColor: 'rgba(24, 28, 37, 0.99)', // Change from 0.65 to 0.99 opacity
80
+ formData: {
81
+ ...(n.data.formData || {}),
82
+ isConfirmed: true,
83
+ },
84
+ },
85
+ }
86
+ : n
87
+ )
88
+ );
89
+ }
90
+ };
91
+
33
92
  // Debug logging for node dimensions
34
93
  useEffect(() => {
35
94
  if (nodeRef.current) {
@@ -65,7 +124,7 @@ export const AutomationAISuggestionNode: React.FC<AutomationAISuggestionNodeProp
65
124
  sx={{
66
125
  width: '336px',
67
126
  minHeight: '150px',
68
- backgroundColor: '#181C25',
127
+ backgroundColor: data.backgroundColor || 'rgba(24, 28, 37, 0.65)',
69
128
  border: selected ? '2px solid #3b82f6' : '1px solid #1e293b',
70
129
  borderRadius: '12px',
71
130
  color: '#ffffff',
@@ -76,133 +135,137 @@ export const AutomationAISuggestionNode: React.FC<AutomationAISuggestionNodeProp
76
135
  overflow: 'hidden',
77
136
  }}
78
137
  >
79
- {/* Suggested badge / label strip */}
80
- <Box sx={{
81
- backgroundColor: 'rgba(30, 58, 138, 0.8)', // #1E3A8A with 0.8 opacity
82
- padding: '8px 16px',
83
- borderRadius: '12px 12px 0 0',
84
- display: 'flex',
85
- alignItems: 'center',
86
- justifyContent: 'space-between'
87
- }}>
88
- <Typography variant="body2" sx={{ color: '#ffffff', fontSize: '12px', fontWeight: 500 }}>
89
- {data.formData?.badgeText || t('automation.aiSuggestionNode.badgeSuggested')}
90
- </Typography>
91
- <Chip
92
- label={t('automation.aiSuggestionNode.badgeAI')}
93
- size="small"
94
- sx={{
95
- bgcolor: 'rgba(147, 197, 253, 0.15)',
96
- color: '#93C5FD',
97
- height: '20px',
98
- fontSize: '11px',
99
- borderRadius: '10px'
100
- }}
101
- />
102
- </Box>
103
-
104
138
  {/* Main content */}
105
139
  <Box sx={{ padding: '16px' }}>
106
- {/* Title */}
107
- <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
108
- <Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
109
- <Box
110
- sx={{
111
- width: '32px',
112
- height: '32px',
113
- backgroundColor: '#0ea5e9',
114
- borderRadius: '50%',
115
- display: 'flex',
116
- alignItems: 'center',
117
- justifyContent: 'center',
118
- }}
119
- >
120
- <IconComponent sx={{ color: 'white', fontSize: '18px' }} />
121
- </Box>
122
- <Typography variant="h6" sx={{ fontWeight: 600, fontSize: '16px' }}>
123
- {data.label}
124
- </Typography>
140
+ {/* Title with Icon */}
141
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5, mb: 1.5 }}>
142
+ <Box
143
+ sx={{
144
+ width: '32px',
145
+ height: '32px',
146
+ backgroundColor: '#0ea5e9',
147
+ borderRadius: '50%',
148
+ display: 'flex',
149
+ alignItems: 'center',
150
+ justifyContent: 'center',
151
+ }}
152
+ >
153
+ <IconComponent sx={{ color: 'white', fontSize: '18px' }} />
125
154
  </Box>
155
+ <Typography variant="h6" sx={{ fontWeight: 600, fontSize: '16px', color: '#ffffff' }}>
156
+ {data.label}
157
+ </Typography>
126
158
  </Box>
127
159
 
128
- {/* Description box */}
129
- <Box sx={{
130
- backgroundColor: '#1F2937',
131
- borderRadius: '8px',
132
- padding: '12px',
133
- mb: 2,
134
- border: '1px solid #374151'
135
- }}>
136
- <Box sx={{
137
- backgroundColor: 'transparent',
138
- borderRadius: '4px',
139
- padding: '8px',
140
- border: '1px solid #4B5563',
141
- minHeight: '40px',
142
- display: 'flex',
143
- alignItems: 'center'
144
- }}>
145
- <Typography variant="body2" sx={{
146
- color: '#9CA3AF',
147
- fontSize: '12px',
148
- lineHeight: 1.4,
149
- m: 0
150
- }}>
151
- {data.description}
152
- </Typography>
153
- </Box>
154
- </Box>
155
-
156
- {/* Mapping / pointers section */}
157
- <Box sx={{
158
- backgroundColor: '#1F2937',
159
- borderRadius: '8px',
160
- padding: '12px',
161
- mb: 2,
162
- border: '1px solid #374151'
163
- }}>
164
- {/* Simple 2-column mapping like Data Mapping */}
165
- {data.formData?.mapping ? (
166
- <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
167
- <Typography variant="body2" sx={{ color: '#9CA3AF', fontSize: '11px' }}>
168
- {data.formData.mapping.from}
169
- </Typography>
170
- <Typography variant="body2" sx={{ color: '#9CA3AF', fontSize: '11px' }}>
171
- → {data.formData.mapping.to}
172
- </Typography>
173
- </Box>
174
- ) : null}
175
-
176
- {(data.formData?.pointers || []).map((p, i) => (
177
- <Box key={i} sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 0.5 }}>
178
- <Box sx={{ color: '#93C5FD', fontSize: '14px' }}>•</Box>
179
- <Typography variant="body2" sx={{ color: '#E5E7EB', fontSize: '11px' }}>{p}</Typography>
180
- </Box>
181
- ))}
182
- </Box>
183
-
184
- {/* Add this button */}
160
+ {/* Suggested Node Button */}
185
161
  <Button
186
- variant="outlined"
187
- fullWidth
162
+ variant="contained"
188
163
  sx={{
189
- backgroundColor: 'rgba(37, 99, 235, 0.15)', // #2563EB with 0.15 opacity
190
- borderColor: '#2563EB',
191
- color: '#93C5FD',
164
+ backgroundColor: '#4B5563', // Dark grey background
165
+ color: '#ffffff',
166
+ borderRadius: '20px', // Pill-shaped
192
167
  textTransform: 'none',
193
- fontSize: '13px',
194
- fontWeight: 600,
168
+ fontSize: '11px',
169
+ fontWeight: 400,
170
+ padding: '6px 14px',
171
+ mb: 1.5,
172
+ minWidth: 'auto',
195
173
  '&:hover': {
196
- backgroundColor: 'rgba(37, 99, 235, 0.2)',
197
- borderColor: '#2563EB'
174
+ backgroundColor: '#6B7280',
198
175
  }
199
176
  }}
200
177
  >
201
- {t('automation.aiSuggestionNode.addThis')}
178
+ {data.formData?.badgeText || 'Suggested Node'}
202
179
  </Button>
203
180
 
204
- {/* Last spacer */}
205
- <Box sx={{ mt: 1 }} />
181
+ {/* Description */}
182
+ <Typography variant="body2" sx={{
183
+ color: '#9CA3AF',
184
+ fontSize: '12px',
185
+ lineHeight: 1.4,
186
+ mb: 2
187
+ }}>
188
+ {data.description}
189
+ </Typography>
190
+
191
+ {/* Data Mapping Section */}
192
+ {(data.formData?.mappings && data.formData.mappings.length > 0) || data.formData?.mapping ? (
193
+ <Box sx={{ mb: 2 }}>
194
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, mb: 1 }}>
195
+ <LinkIcon sx={{ fontSize: '14px', color: '#93C5FD' }} />
196
+ <Typography variant="body2" sx={{ color: '#93C5FD', fontSize: '12px', fontWeight: 500 }}>
197
+ Data Mapping
198
+ </Typography>
199
+ </Box>
200
+ {(data.formData?.mappings || (data.formData?.mapping ? [data.formData.mapping] : [])).map((mapping, index) => (
201
+ <Box key={index} sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 0.5 }}>
202
+ <Typography variant="body2" sx={{ color: '#9CA3AF', fontSize: '11px' }}>
203
+ {mapping.from}
204
+ </Typography>
205
+ <Typography variant="body2" sx={{ color: '#93C5FD', fontSize: '11px' }}>
206
+
207
+ </Typography>
208
+ <Typography variant="body2" sx={{ color: '#93C5FD', fontSize: '11px' }}>
209
+ {mapping.to}
210
+ </Typography>
211
+ </Box>
212
+ ))}
213
+ </Box>
214
+ ) : null}
215
+
216
+ {/* Benefits Section */}
217
+ {(data.formData?.benefits && data.formData.benefits.length > 0) || (data.formData?.pointers && data.formData.pointers.length > 0) ? (
218
+ <Box sx={{ mb: 2 }}>
219
+ <Typography variant="body2" sx={{ color: '#93C5FD', fontSize: '12px', fontWeight: 500, mb: 1 }}>
220
+ Benefits
221
+ </Typography>
222
+ {(data.formData?.benefits || data.formData?.pointers || []).map((benefit, index) => (
223
+ <Box key={index} sx={{ display: 'flex', alignItems: 'flex-start', gap: 1, mb: 0.5 }}>
224
+ <Typography variant="body2" sx={{ color: '#9CA3AF', fontSize: '11px', mt: 0.25 }}>•</Typography>
225
+ <Typography variant="body2" sx={{ color: '#9CA3AF', fontSize: '11px', lineHeight: 1.4 }}>
226
+ {benefit}
227
+ </Typography>
228
+ </Box>
229
+ ))}
230
+ </Box>
231
+ ) : null}
232
+
233
+ {/* Last Ran and Add This Button Row */}
234
+ <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mt: 2 }}>
235
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
236
+ <AccessTimeIcon sx={{ fontSize: '14px', color: '#9CA3AF' }} />
237
+ <Typography variant="body2" sx={{ color: '#9CA3AF', fontSize: '11px' }}>
238
+ Last ran: {data.formData?.lastRun || 'Never'}
239
+ </Typography>
240
+ </Box>
241
+ <Button
242
+ variant="outlined"
243
+ startIcon={<CheckCircleIcon sx={{ fontSize: '16px' }} />}
244
+ onClick={handleAddThis}
245
+ disabled={data.formData?.isConfirmed}
246
+ sx={{
247
+ backgroundColor: 'transparent',
248
+ borderColor: '#2563EB',
249
+ color: '#93C5FD',
250
+ textTransform: 'none',
251
+ fontSize: '12px',
252
+ fontWeight: 500,
253
+ borderRadius: '20px',
254
+ padding: '6px 16px',
255
+ '&:hover': {
256
+ backgroundColor: 'rgba(37, 99, 235, 0.1)',
257
+ borderColor: '#2563EB'
258
+ },
259
+ '&:disabled': {
260
+ borderColor: '#10b981',
261
+ color: '#10b981',
262
+ opacity: 0.7
263
+ }
264
+ }}
265
+ >
266
+ {data.formData?.isConfirmed ? 'Added' : 'Add this'}
267
+ </Button>
268
+ </Box>
206
269
  </Box>
207
270
 
208
271
  {/* Handles - Hidden but functional */}
@@ -1,7 +1,7 @@
1
1
  import React, { useEffect, useState, useRef } from 'react';
2
2
  import { createRoot } from 'react-dom/client';
3
3
  import { Handle, Position, useNodeId } from '@xyflow/react';
4
- import { Box, Typography, Chip, IconButton, Card, CardContent } from '@mui/material';
4
+ import { Box, Typography, Chip, IconButton, Card, CardContent, Button } from '@mui/material';
5
5
  import {
6
6
  AccessTime as AccessTimeIcon,
7
7
  Visibility as VisibilityIcon,
@@ -9,13 +9,16 @@ import {
9
9
  Psychology as PsychologyIcon,
10
10
  Send as SendIcon,
11
11
  Settings as SettingsIcon,
12
- Storage as StorageIcon
12
+ Storage as StorageIcon,
13
+ Lightbulb as LightbulbIcon
13
14
  } from '@mui/icons-material';
14
15
  import { RiCloseLine, RiUser2Line } from 'react-icons/ri';
15
16
  import ReactJson from 'react-json-view';
16
17
  import { getIconByName } from '../../utils/iconMapper';
17
18
  import { useTranslation } from 'react-i18next';
18
19
  import { useDiagram } from '../../contexts/DiagramProvider';
20
+ import { AISuggestion } from './AISuggestionsModal';
21
+ import { AISuggestionsPanel } from './AISuggestionsPanel';
19
22
 
20
23
  interface AutomationApiNodeProps {
21
24
  data: {
@@ -45,6 +48,8 @@ interface AutomationApiNodeProps {
45
48
  label: string;
46
49
  iconColor: string;
47
50
  }>;
51
+ aiSuggestionsCount?: number; // Number of AI suggestions available
52
+ aiSuggestions?: AISuggestion[]; // AI suggestions data
48
53
  [key: string]: any;
49
54
  }; // Include formData for configuration
50
55
  };
@@ -54,6 +59,7 @@ interface AutomationApiNodeProps {
54
59
  export const AutomationApiNode: React.FC<AutomationApiNodeProps> = ({ data, selected }) => {
55
60
  const { t } = useTranslation();
56
61
  const [isJsonOpen, setIsJsonOpen] = useState(false);
62
+ const [showSuggestions, setShowSuggestions] = useState(false);
57
63
  const rootRef = useRef<any>(null);
58
64
  const portalRef = useRef<HTMLDivElement | null>(null);
59
65
  const nodeRef = useRef<HTMLDivElement | null>(null);
@@ -265,35 +271,42 @@ export const AutomationApiNode: React.FC<AutomationApiNodeProps> = ({ data, sele
265
271
 
266
272
  return (
267
273
  <Box
268
- ref={nodeRef}
269
274
  sx={{
270
- width: '336px',
271
- minHeight: '150px',
272
- backgroundColor: '#181C25', // New background color from image
273
- border: selected ? '2px solid #3b82f6' : '1px solid #1e293b',
274
- borderRadius: '12px',
275
- color: '#ffffff',
276
275
  position: 'relative',
277
- boxShadow: selected ? '0 0 0 2px rgba(59, 130, 246, 0.5)' : '0 4px 8px rgba(0, 0, 0, 0.3)',
278
- transition: 'all 0.2s ease',
279
- cursor: 'pointer',
280
- overflow: 'hidden',
281
- ...(data.status === 'Running' && {
282
- animation: 'pulse-glow 2s ease-in-out infinite',
283
- '@keyframes pulse-glow': {
284
- '0%, 100%': {
285
- boxShadow: '0 0 20px rgba(251, 191, 36, 0.4), 0 0 40px rgba(251, 191, 36, 0.2)',
286
- borderColor: 'rgba(251, 191, 36, 0.6)',
287
- },
288
- '50%': {
289
- boxShadow: '0 0 30px rgba(251, 191, 36, 0.6), 0 0 60px rgba(251, 191, 36, 0.3)',
290
- borderColor: 'rgba(251, 191, 36, 0.9)',
291
- },
292
- },
293
- }),
276
+ width: '336px',
277
+ overflow: 'visible',
294
278
  }}
295
- onClick={handleJsonClick}
296
279
  >
280
+ <Box
281
+ ref={nodeRef}
282
+ sx={{
283
+ width: '336px',
284
+ minHeight: '150px',
285
+ backgroundColor: '#181C25', // New background color from image
286
+ border: selected ? '2px solid #3b82f6' : '1px solid #1e293b',
287
+ borderRadius: '12px',
288
+ color: '#ffffff',
289
+ position: 'relative',
290
+ boxShadow: selected ? '0 0 0 2px rgba(59, 130, 246, 0.5)' : '0 4px 8px rgba(0, 0, 0, 0.3)',
291
+ transition: 'all 0.2s ease',
292
+ cursor: 'pointer',
293
+ overflow: 'hidden',
294
+ ...(data.status === 'Running' && {
295
+ animation: 'pulse-glow 2s ease-in-out infinite',
296
+ '@keyframes pulse-glow': {
297
+ '0%, 100%': {
298
+ boxShadow: '0 0 20px rgba(251, 191, 36, 0.4), 0 0 40px rgba(251, 191, 36, 0.2)',
299
+ borderColor: 'rgba(251, 191, 36, 0.6)',
300
+ },
301
+ '50%': {
302
+ boxShadow: '0 0 30px rgba(251, 191, 36, 0.6), 0 0 60px rgba(251, 191, 36, 0.3)',
303
+ borderColor: 'rgba(251, 191, 36, 0.9)',
304
+ },
305
+ },
306
+ }),
307
+ }}
308
+ onClick={handleJsonClick}
309
+ >
297
310
  {/* Top Header Section */}
298
311
  <Box sx={{
299
312
  backgroundColor: "rgba(67, 93, 132, 0.1)",
@@ -535,6 +548,99 @@ export const AutomationApiNode: React.FC<AutomationApiNodeProps> = ({ data, sele
535
548
  opacity: 0, // Hidden but functional
536
549
  }}
537
550
  />
551
+ </Box>
552
+
553
+ {/* AI Suggestions Button - Positioned below the node box */}
554
+ {data.formData?.aiSuggestionsCount !== undefined && data.formData.aiSuggestionsCount > 0 && (
555
+ <Box
556
+ sx={{
557
+ position: 'absolute',
558
+ top: '100%',
559
+ left: '50%',
560
+ transform: 'translateX(-50%)',
561
+ marginTop: '12px',
562
+ zIndex: 10,
563
+ whiteSpace: 'nowrap',
564
+ }}
565
+ onClick={(e) => {
566
+ e.stopPropagation();
567
+ // Toggle AI Suggestions panel
568
+ setShowSuggestions(!showSuggestions);
569
+ }}
570
+ >
571
+ <Button
572
+ variant="contained"
573
+ startIcon={<LightbulbIcon sx={{ fontSize: '12px' }} />}
574
+ sx={{
575
+ backgroundColor: '#2563EB',
576
+ color: '#ffffff',
577
+ borderRadius: '20px',
578
+ textTransform: 'none',
579
+ fontSize: '10px',
580
+ fontWeight: 400,
581
+ padding: '8px 16px',
582
+ whiteSpace: 'nowrap',
583
+ display: 'inline-flex',
584
+ alignItems: 'center',
585
+ boxShadow: '0 2px 8px rgba(0, 0, 0, 0.3)',
586
+ '&:hover': {
587
+ backgroundColor: '#2563eb',
588
+ },
589
+ '& .MuiButton-startIcon': {
590
+ marginRight: '8px',
591
+ }
592
+ }}
593
+ >
594
+ AI Suggestions
595
+ <Box
596
+ component="span"
597
+ sx={{
598
+ marginLeft: '8px',
599
+ backgroundColor: '#FFFFFF26',
600
+ color: '#ffffff',
601
+ fontSize: '10px',
602
+ fontWeight: 400,
603
+ minWidth: '18px',
604
+ height: '18px',
605
+ borderRadius: '9px',
606
+ display: 'inline-flex',
607
+ alignItems: 'center',
608
+ justifyContent: 'center',
609
+ padding: '0 6px',
610
+ border: '1px solid rgba(255, 255, 255, 0.2)',
611
+ }}
612
+ >
613
+ {data.formData.aiSuggestionsCount}
614
+ </Box>
615
+ </Button>
616
+ </Box>
617
+ )}
618
+
619
+ {/* AI Suggestions Panel - Rendered on canvas below the button */}
620
+ {showSuggestions && data.formData?.aiSuggestionsCount !== undefined && data.formData.aiSuggestionsCount > 0 && nodeId && (
621
+ <AISuggestionsPanel
622
+ suggestions={data.formData?.aiSuggestions || [
623
+ {
624
+ id: '1',
625
+ title: 'Add Citation Extraction',
626
+ description: 'Automatically extract and format citations from article content.',
627
+ tags: ['classification', 'enhancement'],
628
+ },
629
+ {
630
+ id: '2',
631
+ title: 'Generate Bullet Summary',
632
+ description: 'Create a concise bullet-point summary of the article\'s main points.',
633
+ tags: ['classification', 'enhancement'],
634
+ },
635
+ ]}
636
+ parentNodeId={nodeId}
637
+ onSuggestionClick={(suggestion) => {
638
+ console.log('Suggestion clicked:', suggestion);
639
+ // Handle suggestion selection here
640
+ }}
641
+ onClose={() => setShowSuggestions(false)}
642
+ />
643
+ )}
538
644
  </Box>
539
645
  );
540
646
  };