@flowuent-org/diagramming-core 1.3.7 → 1.3.9
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.
- package/package.json +1 -1
- package/packages/diagrams/src/lib/components/automation/AutomationApiNode.tsx +7 -8
- package/packages/diagrams/src/lib/components/automation/AutomationEndNode.tsx +5 -21
- package/packages/diagrams/src/lib/components/automation/AutomationFormattingNode.tsx +5 -9
- package/packages/diagrams/src/lib/components/automation/AutomationGoogleServicesNode.tsx +363 -263
- package/packages/diagrams/src/lib/components/automation/AutomationInteractionNode.tsx +325 -231
- package/packages/diagrams/src/lib/components/automation/AutomationNavigationNode.tsx +4 -26
- package/packages/diagrams/src/lib/components/automation/AutomationSheetsNode.tsx +19 -2
- package/packages/diagrams/src/lib/components/automation/AutomationSlackNode.tsx +23 -11
- package/packages/diagrams/src/lib/components/automation/AutomationStartNode.tsx +4 -19
- package/packages/diagrams/src/lib/components/automation/AutomationTelegramNode.tsx +23 -11
- package/packages/diagrams/src/lib/templates/DiagramContent.tsx +3 -44
- package/packages/molecules/src/lib/SvgIcons/icons.tsx +55 -54
|
@@ -7,10 +7,10 @@ import {
|
|
|
7
7
|
Chip,
|
|
8
8
|
IconButton,
|
|
9
9
|
Card,
|
|
10
|
+
CardContent,
|
|
10
11
|
Button,
|
|
11
12
|
CircularProgress,
|
|
12
13
|
Tooltip,
|
|
13
|
-
LinearProgress,
|
|
14
14
|
} from '@mui/material';
|
|
15
15
|
import {
|
|
16
16
|
Description as DocsIcon,
|
|
@@ -20,16 +20,15 @@ import {
|
|
|
20
20
|
TableChart as SheetsIcon,
|
|
21
21
|
CalendarMonth as CalendarIcon,
|
|
22
22
|
AccessTime as AccessTimeIcon,
|
|
23
|
-
CheckCircle as CheckCircleIcon,
|
|
24
|
-
Error as ErrorIcon,
|
|
25
|
-
LinkOff as LinkOffIcon,
|
|
26
23
|
} from '@mui/icons-material';
|
|
27
24
|
import { FcGoogle } from 'react-icons/fc';
|
|
28
25
|
import { RiCloseLine } from 'react-icons/ri';
|
|
29
26
|
import ReactJson from 'react-json-view';
|
|
27
|
+
import { useTranslation } from 'react-i18next';
|
|
30
28
|
import { useDiagram } from '../../contexts/DiagramProvider';
|
|
31
29
|
import { useSearch } from '../../contexts/SearchContext';
|
|
32
30
|
import { NodeActionButtons } from './NodeActionButtons';
|
|
31
|
+
import { showNodeAIAssistantPopup } from './NodeAIAssistantPopup';
|
|
33
32
|
import { getStatusColor } from './statusColors';
|
|
34
33
|
|
|
35
34
|
// ========================
|
|
@@ -219,13 +218,18 @@ export const AutomationGoogleServicesNode: React.FC<AutomationGoogleServicesNode
|
|
|
219
218
|
data,
|
|
220
219
|
selected,
|
|
221
220
|
}) => {
|
|
221
|
+
const { t } = useTranslation();
|
|
222
222
|
const { highlightText } = useSearch();
|
|
223
223
|
const [isJsonOpen, setIsJsonOpen] = useState(false);
|
|
224
224
|
const rootRef = useRef<any>(null);
|
|
225
225
|
const portalRef = useRef<HTMLDivElement | null>(null);
|
|
226
|
+
const nodeRef = useRef<HTMLDivElement | null>(null);
|
|
226
227
|
const nodeId = useNodeId();
|
|
227
228
|
const setSelectedNode = useDiagram((state) => state.setSelectedNode);
|
|
228
229
|
const enableJson = useDiagram((state) => state.enableNodeJsonPopover ?? true);
|
|
230
|
+
const onNodesChange = useDiagram((state) => state.onNodesChange);
|
|
231
|
+
const nodes = useDiagram((state) => state.nodes);
|
|
232
|
+
const setNodes = useDiagram((state) => state.setNodes);
|
|
229
233
|
|
|
230
234
|
// Get service configuration
|
|
231
235
|
const serviceType = data.serviceType || 'docs';
|
|
@@ -240,7 +244,6 @@ export const AutomationGoogleServicesNode: React.FC<AutomationGoogleServicesNode
|
|
|
240
244
|
|
|
241
245
|
// Execution state
|
|
242
246
|
const status = data.status || 'Ready';
|
|
243
|
-
const executionProgress = status === 'Running' ? 50 : 0;
|
|
244
247
|
|
|
245
248
|
// Status configuration - using centralized status colors
|
|
246
249
|
const statusConfig = getStatusColor(status, 'ready');
|
|
@@ -254,6 +257,7 @@ export const AutomationGoogleServicesNode: React.FC<AutomationGoogleServicesNode
|
|
|
254
257
|
|
|
255
258
|
const handleClose = () => {
|
|
256
259
|
setIsJsonOpen(false);
|
|
260
|
+
// Clean up portal
|
|
257
261
|
if (rootRef.current) {
|
|
258
262
|
rootRef.current.unmount();
|
|
259
263
|
rootRef.current = null;
|
|
@@ -277,7 +281,6 @@ export const AutomationGoogleServicesNode: React.FC<AutomationGoogleServicesNode
|
|
|
277
281
|
};
|
|
278
282
|
}, [isJsonOpen]);
|
|
279
283
|
|
|
280
|
-
// JSON popover portal
|
|
281
284
|
useEffect(() => {
|
|
282
285
|
if (isJsonOpen) {
|
|
283
286
|
const portalRoot = document.createElement('div');
|
|
@@ -301,74 +304,56 @@ export const AutomationGoogleServicesNode: React.FC<AutomationGoogleServicesNode
|
|
|
301
304
|
bgcolor: '#242424',
|
|
302
305
|
color: '#fff',
|
|
303
306
|
border: '1px solid #333',
|
|
307
|
+
'&::-webkit-scrollbar': {
|
|
308
|
+
width: '6px',
|
|
309
|
+
},
|
|
310
|
+
'&::-webkit-scrollbar-track': {
|
|
311
|
+
background: 'transparent',
|
|
312
|
+
},
|
|
313
|
+
'&::-webkit-scrollbar-thumb': {
|
|
314
|
+
background: '#444',
|
|
315
|
+
borderRadius: '3px',
|
|
316
|
+
'&:hover': {
|
|
317
|
+
background: '#666',
|
|
318
|
+
},
|
|
319
|
+
},
|
|
304
320
|
}}
|
|
305
321
|
>
|
|
306
|
-
<
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
<IconButton size="small" onClick={handleClose} sx={{ color: '#fff' }}>
|
|
322
|
+
<CardContent sx={{ bgcolor: '#242424', color: '#fff' }}>
|
|
323
|
+
<IconButton
|
|
324
|
+
aria-label="close"
|
|
325
|
+
onClick={handleClose}
|
|
326
|
+
sx={{
|
|
327
|
+
color: '#999',
|
|
328
|
+
'&:hover': {
|
|
329
|
+
color: '#fff',
|
|
330
|
+
bgcolor: 'rgba(255, 255, 255, 0.1)',
|
|
331
|
+
},
|
|
332
|
+
}}
|
|
333
|
+
>
|
|
319
334
|
<RiCloseLine />
|
|
320
335
|
</IconButton>
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
displayDataTypes={false}
|
|
328
|
-
enableClipboard={false}
|
|
329
|
-
style={{
|
|
330
|
-
backgroundColor: 'transparent',
|
|
331
|
-
fontSize: '12px',
|
|
332
|
-
}}
|
|
333
|
-
/>
|
|
334
|
-
</Box>
|
|
336
|
+
{/* Show full node data */}
|
|
337
|
+
<Typography variant="h6" sx={{ color: '#fff', mb: 1 }}>
|
|
338
|
+
{t('automation.common.fullNodeData')}
|
|
339
|
+
</Typography>
|
|
340
|
+
<ReactJson theme={'monokai'} src={data.formData || data} collapsed={false} />
|
|
341
|
+
</CardContent>
|
|
335
342
|
</Card>
|
|
336
343
|
);
|
|
337
|
-
}
|
|
338
|
-
|
|
344
|
+
} else {
|
|
345
|
+
// Clean up when closing
|
|
339
346
|
if (rootRef.current) {
|
|
340
347
|
rootRef.current.unmount();
|
|
341
348
|
rootRef.current = null;
|
|
342
349
|
}
|
|
343
|
-
if (portalRef.current
|
|
350
|
+
if (portalRef.current) {
|
|
344
351
|
document.body.removeChild(portalRef.current);
|
|
345
352
|
portalRef.current = null;
|
|
346
353
|
}
|
|
347
|
-
}
|
|
354
|
+
}
|
|
348
355
|
}, [isJsonOpen, data]);
|
|
349
356
|
|
|
350
|
-
// Render status badge
|
|
351
|
-
const renderStatusBadge = () => (
|
|
352
|
-
<Chip
|
|
353
|
-
size="small"
|
|
354
|
-
label={
|
|
355
|
-
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
|
356
|
-
{(status === 'Completed' || status === 'success') && <CheckCircleIcon sx={{ fontSize: 12 }} />}
|
|
357
|
-
{(status === 'Failed' || status === 'error') && <ErrorIcon sx={{ fontSize: 12 }} />}
|
|
358
|
-
<span>{statusConfig.label}</span>
|
|
359
|
-
</Box>
|
|
360
|
-
}
|
|
361
|
-
sx={{
|
|
362
|
-
height: 20,
|
|
363
|
-
fontSize: '10px',
|
|
364
|
-
fontWeight: 600,
|
|
365
|
-
color: statusConfig.color,
|
|
366
|
-
bgcolor: statusConfig.bgColor,
|
|
367
|
-
border: `1px solid ${statusConfig.color}20`,
|
|
368
|
-
'& .MuiChip-label': { px: 1 },
|
|
369
|
-
}}
|
|
370
|
-
/>
|
|
371
|
-
);
|
|
372
357
|
|
|
373
358
|
// Render Google Auth Button
|
|
374
359
|
const renderGoogleAuthButton = () => {
|
|
@@ -437,234 +422,349 @@ export const AutomationGoogleServicesNode: React.FC<AutomationGoogleServicesNode
|
|
|
437
422
|
);
|
|
438
423
|
};
|
|
439
424
|
|
|
440
|
-
//
|
|
441
|
-
|
|
442
|
-
|
|
425
|
+
// Get service details for display
|
|
426
|
+
const getServiceDetails = (): string => {
|
|
427
|
+
if (serviceType === 'docs' && data.parameters?.documentTitle) {
|
|
428
|
+
return data.parameters.documentTitle;
|
|
429
|
+
}
|
|
430
|
+
if (serviceType === 'slides' && data.parameters?.presentationTitle) {
|
|
431
|
+
return data.parameters.presentationTitle;
|
|
432
|
+
}
|
|
433
|
+
if (serviceType === 'meet' && data.parameters?.meetingTitle) {
|
|
434
|
+
return data.parameters.meetingTitle;
|
|
435
|
+
}
|
|
436
|
+
if (serviceType === 'gmail' && data.parameters?.subject) {
|
|
437
|
+
return data.parameters.subject;
|
|
438
|
+
}
|
|
439
|
+
if (serviceType === 'sheets' && data.parameters?.sheetName) {
|
|
440
|
+
return data.parameters.sheetName;
|
|
441
|
+
}
|
|
442
|
+
if (serviceType === 'calendar' && data.parameters?.eventTitle) {
|
|
443
|
+
return data.parameters.eventTitle;
|
|
444
|
+
}
|
|
445
|
+
return serviceConfig.description;
|
|
446
|
+
};
|
|
447
|
+
|
|
443
448
|
return (
|
|
444
449
|
<Box
|
|
445
450
|
sx={{
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
minWidth: 280,
|
|
450
|
-
maxWidth: 320,
|
|
451
|
-
overflow: 'hidden',
|
|
452
|
-
boxShadow: selected
|
|
453
|
-
? `0 0 20px ${serviceConfig.color}30`
|
|
454
|
-
: '0 4px 12px rgba(0, 0, 0, 0.3)',
|
|
455
|
-
transition: 'all 0.2s ease',
|
|
451
|
+
position: 'relative',
|
|
452
|
+
width: '336px',
|
|
453
|
+
overflow: 'visible',
|
|
456
454
|
}}
|
|
457
|
-
onClick={() => setSelectedNode(nodeId || '')}
|
|
458
455
|
>
|
|
459
|
-
{/* Input Handle */}
|
|
460
|
-
<Handle
|
|
461
|
-
type="target"
|
|
462
|
-
position={Position.Left}
|
|
463
|
-
style={{
|
|
464
|
-
background: serviceConfig.color,
|
|
465
|
-
width: 10,
|
|
466
|
-
height: 10,
|
|
467
|
-
border: '2px solid #1E1E2E',
|
|
468
|
-
}}
|
|
469
|
-
/>
|
|
470
|
-
|
|
471
|
-
{/* Header */}
|
|
472
456
|
<Box
|
|
457
|
+
ref={nodeRef}
|
|
473
458
|
sx={{
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
459
|
+
width: '336px',
|
|
460
|
+
minHeight: '150px',
|
|
461
|
+
backgroundColor: '#181C25',
|
|
462
|
+
border: selected ? `2px solid ${serviceConfig.color}` : '1px solid #1e293b',
|
|
463
|
+
borderRadius: '12px',
|
|
464
|
+
color: '#ffffff',
|
|
465
|
+
position: 'relative',
|
|
466
|
+
boxShadow: selected ? `0 0 0 2px ${serviceConfig.color}50` : '0 4px 8px rgba(0, 0, 0, 0.3)',
|
|
467
|
+
transition: 'all 0.2s ease',
|
|
468
|
+
cursor: 'pointer',
|
|
469
|
+
overflow: 'hidden',
|
|
470
|
+
...(status === 'Running' && {
|
|
471
|
+
animation: 'pulse-glow 2s ease-in-out infinite',
|
|
472
|
+
'@keyframes pulse-glow': {
|
|
473
|
+
'0%, 100%': {
|
|
474
|
+
boxShadow: `0 0 20px ${serviceConfig.color}40, 0 0 40px ${serviceConfig.color}20`,
|
|
475
|
+
borderColor: `${serviceConfig.color}60`,
|
|
476
|
+
},
|
|
477
|
+
'50%': {
|
|
478
|
+
boxShadow: `0 0 30px ${serviceConfig.color}60, 0 0 60px ${serviceConfig.color}30`,
|
|
479
|
+
borderColor: `${serviceConfig.color}90`,
|
|
480
|
+
},
|
|
481
|
+
},
|
|
482
|
+
}),
|
|
481
483
|
}}
|
|
484
|
+
onClick={handleJsonClick}
|
|
482
485
|
>
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
>
|
|
494
|
-
|
|
495
|
-
</
|
|
496
|
-
<Box>
|
|
497
|
-
<Typography
|
|
498
|
-
variant="subtitle2"
|
|
499
|
-
sx={{ color: '#fff', fontWeight: 600, fontSize: 13 }}
|
|
500
|
-
>
|
|
501
|
-
{highlightText(data.label || serviceConfig.label)}
|
|
502
|
-
</Typography>
|
|
503
|
-
<Typography
|
|
504
|
-
variant="caption"
|
|
505
|
-
sx={{ color: '#9CA3AF', fontSize: 10 }}
|
|
506
|
-
>
|
|
507
|
-
{data.description || serviceConfig.description}
|
|
508
|
-
</Typography>
|
|
509
|
-
</Box>
|
|
486
|
+
{/* Top Header Section */}
|
|
487
|
+
<Box sx={{
|
|
488
|
+
backgroundColor: "rgba(67, 93, 132, 0.1)",
|
|
489
|
+
padding: '8px 16px',
|
|
490
|
+
borderRadius: '12px 12px 0 0'
|
|
491
|
+
}}>
|
|
492
|
+
<Typography variant="body2" sx={{
|
|
493
|
+
color: '#ffffff',
|
|
494
|
+
fontSize: '12px',
|
|
495
|
+
fontWeight: 500
|
|
496
|
+
}}>
|
|
497
|
+
{highlightText(data.description || serviceConfig.description)}
|
|
498
|
+
</Typography>
|
|
510
499
|
</Box>
|
|
511
|
-
{renderStatusBadge()}
|
|
512
|
-
</Box>
|
|
513
500
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
<
|
|
539
|
-
|
|
540
|
-
|
|
501
|
+
{/* Main Content */}
|
|
502
|
+
<Box sx={{ padding: '16px' }}>
|
|
503
|
+
{/* Title Section */}
|
|
504
|
+
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
|
|
505
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
|
|
506
|
+
<Box
|
|
507
|
+
sx={{
|
|
508
|
+
width: '32px',
|
|
509
|
+
height: '32px',
|
|
510
|
+
minWidth: '32px',
|
|
511
|
+
backgroundColor: '#1E3A8A',
|
|
512
|
+
borderRadius: '50%',
|
|
513
|
+
display: 'flex',
|
|
514
|
+
alignItems: 'center',
|
|
515
|
+
justifyContent: 'center',
|
|
516
|
+
flexShrink: 0,
|
|
517
|
+
}}
|
|
518
|
+
>
|
|
519
|
+
<ServiceIcon sx={{ color: 'white', fontSize: '18px' }} />
|
|
520
|
+
</Box>
|
|
521
|
+
<Typography variant="h6" sx={{ fontWeight: 600, fontSize: '16px' }}>
|
|
522
|
+
{highlightText(data.label || serviceConfig.label)}
|
|
523
|
+
</Typography>
|
|
524
|
+
</Box>
|
|
525
|
+
<Chip
|
|
526
|
+
label={status}
|
|
527
|
+
size="small"
|
|
541
528
|
sx={{
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
529
|
+
backgroundColor: statusConfig.bgColor,
|
|
530
|
+
color: statusConfig.color,
|
|
531
|
+
fontWeight: 500,
|
|
532
|
+
fontSize: '12px',
|
|
533
|
+
height: '24px',
|
|
534
|
+
borderRadius: '12px',
|
|
548
535
|
}}
|
|
549
536
|
/>
|
|
550
537
|
</Box>
|
|
551
|
-
)}
|
|
552
538
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
<Tooltip title={`Token expires in ${getTimeUntilExpiry(data.parameters?.googleTokenExpiresAt)}`}>
|
|
557
|
-
<Chip
|
|
558
|
-
size="small"
|
|
559
|
-
label={getTimeUntilExpiry(data.parameters?.googleTokenExpiresAt)}
|
|
560
|
-
sx={{
|
|
561
|
-
height: 18,
|
|
562
|
-
fontSize: '9px',
|
|
563
|
-
backgroundColor: tokenExpired ? '#EF4444' : '#3B82F6',
|
|
564
|
-
color: '#FFFFFF',
|
|
565
|
-
fontWeight: 500,
|
|
566
|
-
}}
|
|
567
|
-
/>
|
|
568
|
-
</Tooltip>
|
|
539
|
+
{/* Google Auth Button */}
|
|
540
|
+
<Box sx={{ mb: 2 }}>
|
|
541
|
+
{renderGoogleAuthButton()}
|
|
569
542
|
</Box>
|
|
570
|
-
)}
|
|
571
543
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
544
|
+
{/* Description Box */}
|
|
545
|
+
<Box sx={{
|
|
546
|
+
backgroundColor: '#1F2937',
|
|
547
|
+
borderRadius: '8px',
|
|
548
|
+
padding: '12px',
|
|
549
|
+
mb: 2,
|
|
550
|
+
border: '1px solid #374151'
|
|
551
|
+
}}>
|
|
552
|
+
{/* Inner text boundary box */}
|
|
553
|
+
<Box sx={{
|
|
554
|
+
backgroundColor: 'transparent',
|
|
555
|
+
borderRadius: '4px',
|
|
556
|
+
padding: '8px',
|
|
557
|
+
border: '1px solid #4B5563',
|
|
558
|
+
minHeight: '40px',
|
|
559
|
+
display: 'flex',
|
|
560
|
+
alignItems: 'center'
|
|
561
|
+
}}>
|
|
562
|
+
<Typography variant="body2" sx={{
|
|
563
|
+
color: '#9CA3AF',
|
|
564
|
+
fontSize: '12px',
|
|
565
|
+
lineHeight: 1.4,
|
|
566
|
+
margin: 0
|
|
567
|
+
}}>
|
|
568
|
+
{getServiceDetails()}
|
|
569
|
+
</Typography>
|
|
570
|
+
</Box>
|
|
571
|
+
</Box>
|
|
572
|
+
|
|
573
|
+
{/* Token Expiry Info */}
|
|
574
|
+
{isAuthenticated && (
|
|
575
|
+
<Box sx={{ mb: 2 }}>
|
|
576
|
+
<Tooltip title={`Token expires in ${getTimeUntilExpiry(data.parameters?.googleTokenExpiresAt)}`}>
|
|
577
|
+
<Chip
|
|
578
|
+
size="small"
|
|
579
|
+
label={getTimeUntilExpiry(data.parameters?.googleTokenExpiresAt)}
|
|
580
|
+
sx={{
|
|
581
|
+
height: 18,
|
|
582
|
+
fontSize: '9px',
|
|
583
|
+
backgroundColor: tokenExpired ? '#EF4444' : '#3B82F6',
|
|
584
|
+
color: '#FFFFFF',
|
|
585
|
+
fontWeight: 500,
|
|
586
|
+
}}
|
|
587
|
+
/>
|
|
588
|
+
</Tooltip>
|
|
589
|
+
</Box>
|
|
598
590
|
)}
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
591
|
+
|
|
592
|
+
{/* Last Run Info */}
|
|
593
|
+
{data.lastRun && (
|
|
594
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, mt: 1 }}>
|
|
595
|
+
<AccessTimeIcon sx={{ fontSize: '14px', color: '#9CA3AF' }} />
|
|
596
|
+
<Typography variant="body2" sx={{ color: '#9CA3AF', fontSize: '11px' }}>
|
|
597
|
+
{t('automation.common.lastRan')}: {data.lastRun}
|
|
598
|
+
{data.duration && ` · ${data.duration}`}
|
|
599
|
+
</Typography>
|
|
600
|
+
</Box>
|
|
603
601
|
)}
|
|
604
602
|
</Box>
|
|
605
603
|
|
|
606
|
-
{/*
|
|
607
|
-
{
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
604
|
+
{/* Connection Handles - Bidirectional (source + target at each position) */}
|
|
605
|
+
{/* Top - Source */}
|
|
606
|
+
<Handle
|
|
607
|
+
type="source"
|
|
608
|
+
position={Position.Top}
|
|
609
|
+
id="top-source"
|
|
610
|
+
className="connection-handle"
|
|
611
|
+
style={{
|
|
612
|
+
background: selected ? '#10B981' : '#1a1a2e',
|
|
613
|
+
width: '14px',
|
|
614
|
+
height: '14px',
|
|
615
|
+
border: '3px solid #10B981',
|
|
616
|
+
top: '-8px',
|
|
617
|
+
opacity: selected ? 1 : 0,
|
|
618
|
+
transition: 'all 0.2s ease-in-out',
|
|
619
|
+
cursor: 'crosshair',
|
|
620
|
+
zIndex: 10,
|
|
621
|
+
}}
|
|
622
|
+
/>
|
|
623
|
+
{/* Top - Target (hidden but functional) */}
|
|
624
|
+
<Handle
|
|
625
|
+
type="target"
|
|
626
|
+
position={Position.Top}
|
|
627
|
+
id="top-target"
|
|
628
|
+
style={{
|
|
629
|
+
background: 'transparent',
|
|
630
|
+
width: '14px',
|
|
631
|
+
height: '14px',
|
|
632
|
+
border: 'none',
|
|
633
|
+
top: '-8px',
|
|
634
|
+
opacity: 0,
|
|
635
|
+
pointerEvents: selected ? 'all' : 'none',
|
|
636
|
+
}}
|
|
637
|
+
/>
|
|
638
|
+
{/* Bottom - Source */}
|
|
639
|
+
<Handle
|
|
640
|
+
type="source"
|
|
641
|
+
position={Position.Bottom}
|
|
642
|
+
id="bottom-source"
|
|
643
|
+
className="connection-handle"
|
|
644
|
+
style={{
|
|
645
|
+
background: selected ? '#10B981' : '#1a1a2e',
|
|
646
|
+
width: '14px',
|
|
647
|
+
height: '14px',
|
|
648
|
+
border: '3px solid #10B981',
|
|
649
|
+
bottom: '-8px',
|
|
650
|
+
opacity: selected ? 1 : 0,
|
|
651
|
+
transition: 'all 0.2s ease-in-out',
|
|
652
|
+
cursor: 'crosshair',
|
|
653
|
+
zIndex: 10,
|
|
654
|
+
}}
|
|
655
|
+
/>
|
|
656
|
+
{/* Bottom - Target (hidden but functional) */}
|
|
657
|
+
<Handle
|
|
658
|
+
type="target"
|
|
659
|
+
position={Position.Bottom}
|
|
660
|
+
id="bottom-target"
|
|
661
|
+
style={{
|
|
662
|
+
background: 'transparent',
|
|
663
|
+
width: '14px',
|
|
664
|
+
height: '14px',
|
|
665
|
+
border: 'none',
|
|
666
|
+
bottom: '-8px',
|
|
667
|
+
opacity: 0,
|
|
668
|
+
pointerEvents: selected ? 'all' : 'none',
|
|
669
|
+
}}
|
|
670
|
+
/>
|
|
671
|
+
{/* Left - Source */}
|
|
672
|
+
<Handle
|
|
673
|
+
type="source"
|
|
674
|
+
position={Position.Left}
|
|
675
|
+
id="left-source"
|
|
676
|
+
className="connection-handle"
|
|
677
|
+
style={{
|
|
678
|
+
background: selected ? '#10B981' : '#1a1a2e',
|
|
679
|
+
width: '14px',
|
|
680
|
+
height: '14px',
|
|
681
|
+
border: '3px solid #10B981',
|
|
682
|
+
left: '-8px',
|
|
683
|
+
opacity: selected ? 1 : 0,
|
|
684
|
+
transition: 'all 0.2s ease-in-out',
|
|
685
|
+
cursor: 'crosshair',
|
|
686
|
+
zIndex: 10,
|
|
687
|
+
}}
|
|
688
|
+
/>
|
|
689
|
+
{/* Left - Target (hidden but functional) */}
|
|
690
|
+
<Handle
|
|
691
|
+
type="target"
|
|
692
|
+
position={Position.Left}
|
|
693
|
+
id="left-target"
|
|
694
|
+
style={{
|
|
695
|
+
background: 'transparent',
|
|
696
|
+
width: '14px',
|
|
697
|
+
height: '14px',
|
|
698
|
+
border: 'none',
|
|
699
|
+
left: '-8px',
|
|
700
|
+
opacity: 0,
|
|
701
|
+
pointerEvents: selected ? 'all' : 'none',
|
|
702
|
+
}}
|
|
703
|
+
/>
|
|
704
|
+
{/* Right - Source */}
|
|
705
|
+
<Handle
|
|
706
|
+
type="source"
|
|
707
|
+
position={Position.Right}
|
|
708
|
+
id="right-source"
|
|
709
|
+
className="connection-handle"
|
|
710
|
+
style={{
|
|
711
|
+
background: selected ? '#10B981' : '#1a1a2e',
|
|
712
|
+
width: '14px',
|
|
713
|
+
height: '14px',
|
|
714
|
+
border: '3px solid #10B981',
|
|
715
|
+
right: '-8px',
|
|
716
|
+
opacity: selected ? 1 : 0,
|
|
717
|
+
transition: 'all 0.2s ease-in-out',
|
|
718
|
+
cursor: 'crosshair',
|
|
719
|
+
zIndex: 10,
|
|
720
|
+
}}
|
|
721
|
+
/>
|
|
722
|
+
{/* Right - Target (hidden but functional) */}
|
|
723
|
+
<Handle
|
|
724
|
+
type="target"
|
|
725
|
+
position={Position.Right}
|
|
726
|
+
id="right-target"
|
|
727
|
+
style={{
|
|
728
|
+
background: 'transparent',
|
|
729
|
+
width: '14px',
|
|
730
|
+
height: '14px',
|
|
731
|
+
border: 'none',
|
|
732
|
+
right: '-8px',
|
|
733
|
+
opacity: 0,
|
|
734
|
+
pointerEvents: selected ? 'all' : 'none',
|
|
735
|
+
}}
|
|
736
|
+
/>
|
|
654
737
|
</Box>
|
|
655
738
|
|
|
656
|
-
{/* Node Action Buttons */}
|
|
657
|
-
<NodeActionButtons
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
739
|
+
{/* Node Action Buttons - Shows when selected */}
|
|
740
|
+
<NodeActionButtons
|
|
741
|
+
selected={selected}
|
|
742
|
+
onOpenAIAssistant={(buttonElement) => {
|
|
743
|
+
if (nodeId) {
|
|
744
|
+
showNodeAIAssistantPopup(nodeId, 'Google Services Node', buttonElement);
|
|
745
|
+
}
|
|
746
|
+
}}
|
|
747
|
+
onDelete={() => {
|
|
748
|
+
if (nodeId && onNodesChange) {
|
|
749
|
+
onNodesChange([{ id: nodeId, type: 'remove' }]);
|
|
750
|
+
}
|
|
751
|
+
}}
|
|
752
|
+
onDuplicate={() => {
|
|
753
|
+
if (nodeId) {
|
|
754
|
+
const currentNode = nodes.find(n => n.id === nodeId);
|
|
755
|
+
if (currentNode) {
|
|
756
|
+
const newNode = {
|
|
757
|
+
...currentNode,
|
|
758
|
+
id: `${currentNode.id}-copy-${Date.now()}`,
|
|
759
|
+
position: {
|
|
760
|
+
x: currentNode.position.x + 50,
|
|
761
|
+
y: currentNode.position.y + 50,
|
|
762
|
+
},
|
|
763
|
+
selected: false,
|
|
764
|
+
};
|
|
765
|
+
setNodes([...nodes, newNode]);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
668
768
|
}}
|
|
669
769
|
/>
|
|
670
770
|
</Box>
|