@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,7 +7,7 @@ import {
|
|
|
7
7
|
Chip,
|
|
8
8
|
IconButton,
|
|
9
9
|
Card,
|
|
10
|
-
|
|
10
|
+
CardContent,
|
|
11
11
|
} from '@mui/material';
|
|
12
12
|
import {
|
|
13
13
|
Mouse as MouseIcon,
|
|
@@ -18,14 +18,14 @@ import {
|
|
|
18
18
|
CheckBox as CheckBoxIcon,
|
|
19
19
|
RadioButtonChecked as RadioButtonIcon,
|
|
20
20
|
AccessTime as AccessTimeIcon,
|
|
21
|
-
CheckCircle as CheckCircleIcon,
|
|
22
|
-
Error as ErrorIcon,
|
|
23
21
|
} from '@mui/icons-material';
|
|
24
22
|
import { RiCloseLine } from 'react-icons/ri';
|
|
25
23
|
import ReactJson from 'react-json-view';
|
|
24
|
+
import { useTranslation } from 'react-i18next';
|
|
26
25
|
import { useDiagram } from '../../contexts/DiagramProvider';
|
|
27
26
|
import { useSearch } from '../../contexts/SearchContext';
|
|
28
27
|
import { NodeActionButtons } from './NodeActionButtons';
|
|
28
|
+
import { showNodeAIAssistantPopup } from './NodeAIAssistantPopup';
|
|
29
29
|
import { getStatusColor } from './statusColors';
|
|
30
30
|
|
|
31
31
|
// ========================
|
|
@@ -252,22 +252,29 @@ export const AutomationInteractionNode: React.FC<AutomationInteractionNodeProps>
|
|
|
252
252
|
data,
|
|
253
253
|
selected,
|
|
254
254
|
}) => {
|
|
255
|
+
const { t } = useTranslation();
|
|
255
256
|
const { highlightText } = useSearch();
|
|
256
257
|
const [isJsonOpen, setIsJsonOpen] = useState(false);
|
|
257
258
|
const rootRef = useRef<any>(null);
|
|
258
259
|
const portalRef = useRef<HTMLDivElement | null>(null);
|
|
260
|
+
const nodeRef = useRef<HTMLDivElement | null>(null);
|
|
259
261
|
const nodeId = useNodeId();
|
|
260
262
|
const setSelectedNode = useDiagram((state) => state.setSelectedNode);
|
|
261
263
|
const enableJson = useDiagram((state) => state.enableNodeJsonPopover ?? true);
|
|
264
|
+
const onNodesChange = useDiagram((state) => state.onNodesChange);
|
|
265
|
+
const nodes = useDiagram((state) => state.nodes);
|
|
266
|
+
const setNodes = useDiagram((state) => state.setNodes);
|
|
262
267
|
|
|
263
268
|
// Get interaction configuration
|
|
264
|
-
const
|
|
265
|
-
const
|
|
269
|
+
const interactionTypeRaw = data.interactionType || data.formData?.interactionType || 'click';
|
|
270
|
+
const interactionType = (Object.keys(INTERACTION_CONFIG).includes(interactionTypeRaw)
|
|
271
|
+
? interactionTypeRaw
|
|
272
|
+
: 'click') as InteractionType;
|
|
273
|
+
const interactionConfig = INTERACTION_CONFIG[interactionType];
|
|
266
274
|
const InteractionIcon = interactionConfig.icon;
|
|
267
275
|
|
|
268
276
|
// Execution state
|
|
269
277
|
const status = data.status || 'Ready';
|
|
270
|
-
const executionProgress = status === 'Running' ? 50 : 0;
|
|
271
278
|
|
|
272
279
|
// Status configuration - using centralized status colors
|
|
273
280
|
const statusConfig = getStatusColor(status);
|
|
@@ -281,6 +288,7 @@ export const AutomationInteractionNode: React.FC<AutomationInteractionNodeProps>
|
|
|
281
288
|
|
|
282
289
|
const handleClose = () => {
|
|
283
290
|
setIsJsonOpen(false);
|
|
291
|
+
// Clean up portal
|
|
284
292
|
if (rootRef.current) {
|
|
285
293
|
rootRef.current.unmount();
|
|
286
294
|
rootRef.current = null;
|
|
@@ -304,7 +312,6 @@ export const AutomationInteractionNode: React.FC<AutomationInteractionNodeProps>
|
|
|
304
312
|
};
|
|
305
313
|
}, [isJsonOpen]);
|
|
306
314
|
|
|
307
|
-
// JSON popover portal
|
|
308
315
|
useEffect(() => {
|
|
309
316
|
if (isJsonOpen) {
|
|
310
317
|
const portalRoot = document.createElement('div');
|
|
@@ -328,265 +335,352 @@ export const AutomationInteractionNode: React.FC<AutomationInteractionNodeProps>
|
|
|
328
335
|
bgcolor: '#242424',
|
|
329
336
|
color: '#fff',
|
|
330
337
|
border: '1px solid #333',
|
|
338
|
+
'&::-webkit-scrollbar': {
|
|
339
|
+
width: '6px',
|
|
340
|
+
},
|
|
341
|
+
'&::-webkit-scrollbar-track': {
|
|
342
|
+
background: 'transparent',
|
|
343
|
+
},
|
|
344
|
+
'&::-webkit-scrollbar-thumb': {
|
|
345
|
+
background: '#444',
|
|
346
|
+
borderRadius: '3px',
|
|
347
|
+
'&:hover': {
|
|
348
|
+
background: '#666',
|
|
349
|
+
},
|
|
350
|
+
},
|
|
331
351
|
}}
|
|
332
352
|
>
|
|
333
|
-
<
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
<IconButton size="small" onClick={handleClose} sx={{ color: '#fff' }}>
|
|
353
|
+
<CardContent sx={{ bgcolor: '#242424', color: '#fff' }}>
|
|
354
|
+
<IconButton
|
|
355
|
+
aria-label="close"
|
|
356
|
+
onClick={handleClose}
|
|
357
|
+
sx={{
|
|
358
|
+
color: '#999',
|
|
359
|
+
'&:hover': {
|
|
360
|
+
color: '#fff',
|
|
361
|
+
bgcolor: 'rgba(255, 255, 255, 0.1)',
|
|
362
|
+
},
|
|
363
|
+
}}
|
|
364
|
+
>
|
|
346
365
|
<RiCloseLine />
|
|
347
366
|
</IconButton>
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
displayDataTypes={false}
|
|
355
|
-
enableClipboard={false}
|
|
356
|
-
style={{
|
|
357
|
-
backgroundColor: 'transparent',
|
|
358
|
-
fontSize: '12px',
|
|
359
|
-
}}
|
|
360
|
-
/>
|
|
361
|
-
</Box>
|
|
367
|
+
{/* Show full node data */}
|
|
368
|
+
<Typography variant="h6" sx={{ color: '#fff', mb: 1 }}>
|
|
369
|
+
{t('automation.common.fullNodeData')}
|
|
370
|
+
</Typography>
|
|
371
|
+
<ReactJson theme={'monokai'} src={data.formData || data} collapsed={false} />
|
|
372
|
+
</CardContent>
|
|
362
373
|
</Card>
|
|
363
374
|
);
|
|
364
|
-
}
|
|
365
|
-
|
|
375
|
+
} else {
|
|
376
|
+
// Clean up when closing
|
|
366
377
|
if (rootRef.current) {
|
|
367
378
|
rootRef.current.unmount();
|
|
368
379
|
rootRef.current = null;
|
|
369
380
|
}
|
|
370
|
-
if (portalRef.current
|
|
381
|
+
if (portalRef.current) {
|
|
371
382
|
document.body.removeChild(portalRef.current);
|
|
372
383
|
portalRef.current = null;
|
|
373
384
|
}
|
|
374
|
-
}
|
|
385
|
+
}
|
|
375
386
|
}, [isJsonOpen, data]);
|
|
376
387
|
|
|
377
|
-
// Render status badge
|
|
378
|
-
const renderStatusBadge = () => (
|
|
379
|
-
<Chip
|
|
380
|
-
size="small"
|
|
381
|
-
label={
|
|
382
|
-
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
|
383
|
-
{status === 'Completed' && <CheckCircleIcon sx={{ fontSize: 12 }} />}
|
|
384
|
-
{status === 'Error' && <ErrorIcon sx={{ fontSize: 12 }} />}
|
|
385
|
-
<span>{statusConfig.label}</span>
|
|
386
|
-
</Box>
|
|
387
|
-
}
|
|
388
|
-
sx={{
|
|
389
|
-
height: 20,
|
|
390
|
-
fontSize: '10px',
|
|
391
|
-
fontWeight: 600,
|
|
392
|
-
color: statusConfig.color,
|
|
393
|
-
bgcolor: statusConfig.bgColor,
|
|
394
|
-
border: `1px solid ${statusConfig.color}20`,
|
|
395
|
-
'& .MuiChip-label': { px: 1 },
|
|
396
|
-
}}
|
|
397
|
-
/>
|
|
398
|
-
);
|
|
399
|
-
|
|
400
|
-
// ========================
|
|
401
|
-
// Render
|
|
402
|
-
// ========================
|
|
403
388
|
return (
|
|
404
389
|
<Box
|
|
405
390
|
sx={{
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
minWidth: 280,
|
|
410
|
-
maxWidth: 320,
|
|
411
|
-
overflow: 'hidden',
|
|
412
|
-
boxShadow: selected
|
|
413
|
-
? `0 0 20px ${interactionConfig.color}30`
|
|
414
|
-
: '0 4px 12px rgba(0, 0, 0, 0.3)',
|
|
415
|
-
transition: 'all 0.2s ease',
|
|
391
|
+
position: 'relative',
|
|
392
|
+
width: '336px',
|
|
393
|
+
overflow: 'visible',
|
|
416
394
|
}}
|
|
417
|
-
onClick={() => setSelectedNode(nodeId || '')}
|
|
418
395
|
>
|
|
419
|
-
{/* Input Handle */}
|
|
420
|
-
<Handle
|
|
421
|
-
type="target"
|
|
422
|
-
position={Position.Left}
|
|
423
|
-
style={{
|
|
424
|
-
background: interactionConfig.color,
|
|
425
|
-
width: 10,
|
|
426
|
-
height: 10,
|
|
427
|
-
border: '2px solid #1E1E2E',
|
|
428
|
-
}}
|
|
429
|
-
/>
|
|
430
|
-
|
|
431
|
-
{/* Header */}
|
|
432
396
|
<Box
|
|
397
|
+
ref={nodeRef}
|
|
433
398
|
sx={{
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
399
|
+
width: '336px',
|
|
400
|
+
minHeight: '150px',
|
|
401
|
+
backgroundColor: '#181C25',
|
|
402
|
+
border: selected ? `2px solid ${interactionConfig.color}` : '1px solid #1e293b',
|
|
403
|
+
borderRadius: '12px',
|
|
404
|
+
color: '#ffffff',
|
|
405
|
+
position: 'relative',
|
|
406
|
+
boxShadow: selected ? `0 0 0 2px ${interactionConfig.color}50` : '0 4px 8px rgba(0, 0, 0, 0.3)',
|
|
407
|
+
transition: 'all 0.2s ease',
|
|
408
|
+
cursor: 'pointer',
|
|
409
|
+
overflow: 'hidden',
|
|
410
|
+
...(status === 'Running' && {
|
|
411
|
+
animation: 'pulse-glow 2s ease-in-out infinite',
|
|
412
|
+
'@keyframes pulse-glow': {
|
|
413
|
+
'0%, 100%': {
|
|
414
|
+
boxShadow: `0 0 20px ${interactionConfig.color}40, 0 0 40px ${interactionConfig.color}20`,
|
|
415
|
+
borderColor: `${interactionConfig.color}60`,
|
|
416
|
+
},
|
|
417
|
+
'50%': {
|
|
418
|
+
boxShadow: `0 0 30px ${interactionConfig.color}60, 0 0 60px ${interactionConfig.color}30`,
|
|
419
|
+
borderColor: `${interactionConfig.color}90`,
|
|
420
|
+
},
|
|
421
|
+
},
|
|
422
|
+
}),
|
|
441
423
|
}}
|
|
424
|
+
onClick={handleJsonClick}
|
|
442
425
|
>
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
>
|
|
454
|
-
|
|
455
|
-
</Box>
|
|
456
|
-
<Box>
|
|
457
|
-
<Typography
|
|
458
|
-
variant="subtitle2"
|
|
459
|
-
sx={{ color: '#fff', fontWeight: 600, fontSize: 13 }}
|
|
460
|
-
>
|
|
461
|
-
{highlightText(data.label || interactionConfig.label)}
|
|
462
|
-
</Typography>
|
|
463
|
-
<Typography
|
|
464
|
-
variant="caption"
|
|
465
|
-
sx={{ color: '#9CA3AF', fontSize: 10 }}
|
|
466
|
-
>
|
|
467
|
-
{data.description || interactionConfig.description}
|
|
468
|
-
</Typography>
|
|
469
|
-
</Box>
|
|
470
|
-
</Box>
|
|
471
|
-
{renderStatusBadge()}
|
|
472
|
-
</Box>
|
|
473
|
-
|
|
474
|
-
{/* Content */}
|
|
475
|
-
<Box sx={{ px: 2, py: 1.5 }}>
|
|
476
|
-
{/* Progress Bar (when running) */}
|
|
477
|
-
{status === 'Running' && (
|
|
478
|
-
<Box sx={{ mb: 1.5 }}>
|
|
479
|
-
<LinearProgress
|
|
480
|
-
variant="determinate"
|
|
481
|
-
value={executionProgress}
|
|
482
|
-
sx={{
|
|
483
|
-
height: 4,
|
|
484
|
-
borderRadius: 2,
|
|
485
|
-
bgcolor: 'rgba(59, 130, 246, 0.1)',
|
|
486
|
-
'& .MuiLinearProgress-bar': {
|
|
487
|
-
bgcolor: interactionConfig.color,
|
|
488
|
-
},
|
|
489
|
-
}}
|
|
490
|
-
/>
|
|
491
|
-
</Box>
|
|
492
|
-
)}
|
|
493
|
-
|
|
494
|
-
{/* Interaction Details */}
|
|
495
|
-
<Box
|
|
496
|
-
sx={{
|
|
497
|
-
bgcolor: '#1F2937',
|
|
498
|
-
borderRadius: 1,
|
|
499
|
-
p: 1.5,
|
|
500
|
-
mb: 1.5,
|
|
501
|
-
border: '1px solid #374151',
|
|
502
|
-
}}
|
|
503
|
-
>
|
|
504
|
-
<Typography
|
|
505
|
-
variant="caption"
|
|
506
|
-
sx={{
|
|
507
|
-
color: '#9CA3AF',
|
|
508
|
-
fontSize: 10,
|
|
509
|
-
display: 'block',
|
|
510
|
-
mb: 0.5,
|
|
511
|
-
}}
|
|
512
|
-
>
|
|
513
|
-
{interactionConfig.label}
|
|
514
|
-
</Typography>
|
|
515
|
-
<Typography
|
|
516
|
-
variant="body2"
|
|
517
|
-
sx={{
|
|
518
|
-
color: '#fff',
|
|
519
|
-
fontSize: 11,
|
|
520
|
-
fontWeight: 500,
|
|
521
|
-
wordBreak: 'break-word',
|
|
522
|
-
}}
|
|
523
|
-
>
|
|
524
|
-
{getInteractionDetails(data)}
|
|
426
|
+
{/* Top Header Section */}
|
|
427
|
+
<Box sx={{
|
|
428
|
+
backgroundColor: "rgba(67, 93, 132, 0.1)",
|
|
429
|
+
padding: '8px 16px',
|
|
430
|
+
borderRadius: '12px 12px 0 0'
|
|
431
|
+
}}>
|
|
432
|
+
<Typography variant="body2" sx={{
|
|
433
|
+
color: '#ffffff',
|
|
434
|
+
fontSize: '12px',
|
|
435
|
+
fontWeight: 500
|
|
436
|
+
}}>
|
|
437
|
+
{highlightText(data.description || interactionConfig.description)}
|
|
525
438
|
</Typography>
|
|
526
439
|
</Box>
|
|
527
440
|
|
|
528
|
-
{/*
|
|
529
|
-
{
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
: '
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
441
|
+
{/* Main Content */}
|
|
442
|
+
<Box sx={{ padding: '16px' }}>
|
|
443
|
+
{/* Title Section */}
|
|
444
|
+
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
|
|
445
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
|
|
446
|
+
<Box
|
|
447
|
+
sx={{
|
|
448
|
+
width: '32px',
|
|
449
|
+
height: '32px',
|
|
450
|
+
minWidth: '32px',
|
|
451
|
+
backgroundColor: '#1E3A8A',
|
|
452
|
+
borderRadius: '50%',
|
|
453
|
+
display: 'flex',
|
|
454
|
+
alignItems: 'center',
|
|
455
|
+
justifyContent: 'center',
|
|
456
|
+
flexShrink: 0,
|
|
457
|
+
}}
|
|
458
|
+
>
|
|
459
|
+
<InteractionIcon sx={{ color: 'white', fontSize: '18px' }} />
|
|
460
|
+
</Box>
|
|
461
|
+
<Typography variant="h6" sx={{ fontWeight: 600, fontSize: '16px' }}>
|
|
462
|
+
{highlightText(data.label || interactionConfig.label)}
|
|
463
|
+
</Typography>
|
|
464
|
+
</Box>
|
|
465
|
+
<Chip
|
|
466
|
+
label={status}
|
|
467
|
+
size="small"
|
|
547
468
|
sx={{
|
|
548
|
-
|
|
549
|
-
|
|
469
|
+
backgroundColor: statusConfig.bgColor,
|
|
470
|
+
color: statusConfig.color,
|
|
471
|
+
fontWeight: 500,
|
|
472
|
+
fontSize: '12px',
|
|
473
|
+
height: '24px',
|
|
474
|
+
borderRadius: '12px',
|
|
550
475
|
}}
|
|
551
|
-
|
|
552
|
-
{data.executionResult.success
|
|
553
|
-
? '✓ Interaction completed'
|
|
554
|
-
: `✗ ${data.executionResult.error || 'Interaction failed'}`}
|
|
555
|
-
</Typography>
|
|
476
|
+
/>
|
|
556
477
|
</Box>
|
|
557
|
-
)}
|
|
558
478
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
479
|
+
{/* Description Box */}
|
|
480
|
+
<Box sx={{
|
|
481
|
+
backgroundColor: '#1F2937',
|
|
482
|
+
borderRadius: '8px',
|
|
483
|
+
padding: '12px',
|
|
484
|
+
mb: 2,
|
|
485
|
+
border: '1px solid #374151'
|
|
486
|
+
}}>
|
|
487
|
+
{/* Inner text boundary box */}
|
|
488
|
+
<Box sx={{
|
|
489
|
+
backgroundColor: 'transparent',
|
|
490
|
+
borderRadius: '4px',
|
|
491
|
+
padding: '8px',
|
|
492
|
+
border: '1px solid #4B5563',
|
|
493
|
+
minHeight: '40px',
|
|
563
494
|
display: 'flex',
|
|
564
|
-
alignItems: 'center'
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
495
|
+
alignItems: 'center'
|
|
496
|
+
}}>
|
|
497
|
+
<Typography variant="body2" sx={{
|
|
498
|
+
color: '#9CA3AF',
|
|
499
|
+
fontSize: '12px',
|
|
500
|
+
lineHeight: 1.4,
|
|
501
|
+
margin: 0
|
|
502
|
+
}}>
|
|
503
|
+
{getInteractionDetails(data)}
|
|
504
|
+
</Typography>
|
|
505
|
+
</Box>
|
|
574
506
|
</Box>
|
|
575
|
-
)}
|
|
576
|
-
</Box>
|
|
577
507
|
|
|
578
|
-
|
|
579
|
-
|
|
508
|
+
{/* Last Run Info */}
|
|
509
|
+
{data.lastRun && (
|
|
510
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, mt: 1 }}>
|
|
511
|
+
<AccessTimeIcon sx={{ fontSize: '14px', color: '#9CA3AF' }} />
|
|
512
|
+
<Typography variant="body2" sx={{ color: '#9CA3AF', fontSize: '11px' }}>
|
|
513
|
+
{t('automation.common.lastRan')}: {data.lastRun}
|
|
514
|
+
{data.duration && ` · ${data.duration}`}
|
|
515
|
+
</Typography>
|
|
516
|
+
</Box>
|
|
517
|
+
)}
|
|
518
|
+
</Box>
|
|
519
|
+
|
|
520
|
+
{/* Connection Handles - Bidirectional (source + target at each position) */}
|
|
521
|
+
{/* Top - Source */}
|
|
522
|
+
<Handle
|
|
523
|
+
type="source"
|
|
524
|
+
position={Position.Top}
|
|
525
|
+
id="top-source"
|
|
526
|
+
className="connection-handle"
|
|
527
|
+
style={{
|
|
528
|
+
background: selected ? '#10B981' : '#1a1a2e',
|
|
529
|
+
width: '14px',
|
|
530
|
+
height: '14px',
|
|
531
|
+
border: '3px solid #10B981',
|
|
532
|
+
top: '-8px',
|
|
533
|
+
opacity: selected ? 1 : 0,
|
|
534
|
+
transition: 'all 0.2s ease-in-out',
|
|
535
|
+
cursor: 'crosshair',
|
|
536
|
+
zIndex: 10,
|
|
537
|
+
}}
|
|
538
|
+
/>
|
|
539
|
+
{/* Top - Target (hidden but functional) */}
|
|
540
|
+
<Handle
|
|
541
|
+
type="target"
|
|
542
|
+
position={Position.Top}
|
|
543
|
+
id="top-target"
|
|
544
|
+
style={{
|
|
545
|
+
background: 'transparent',
|
|
546
|
+
width: '14px',
|
|
547
|
+
height: '14px',
|
|
548
|
+
border: 'none',
|
|
549
|
+
top: '-8px',
|
|
550
|
+
opacity: 0,
|
|
551
|
+
pointerEvents: selected ? 'all' : 'none',
|
|
552
|
+
}}
|
|
553
|
+
/>
|
|
554
|
+
{/* Bottom - Source */}
|
|
555
|
+
<Handle
|
|
556
|
+
type="source"
|
|
557
|
+
position={Position.Bottom}
|
|
558
|
+
id="bottom-source"
|
|
559
|
+
className="connection-handle"
|
|
560
|
+
style={{
|
|
561
|
+
background: selected ? '#10B981' : '#1a1a2e',
|
|
562
|
+
width: '14px',
|
|
563
|
+
height: '14px',
|
|
564
|
+
border: '3px solid #10B981',
|
|
565
|
+
bottom: '-8px',
|
|
566
|
+
opacity: selected ? 1 : 0,
|
|
567
|
+
transition: 'all 0.2s ease-in-out',
|
|
568
|
+
cursor: 'crosshair',
|
|
569
|
+
zIndex: 10,
|
|
570
|
+
}}
|
|
571
|
+
/>
|
|
572
|
+
{/* Bottom - Target (hidden but functional) */}
|
|
573
|
+
<Handle
|
|
574
|
+
type="target"
|
|
575
|
+
position={Position.Bottom}
|
|
576
|
+
id="bottom-target"
|
|
577
|
+
style={{
|
|
578
|
+
background: 'transparent',
|
|
579
|
+
width: '14px',
|
|
580
|
+
height: '14px',
|
|
581
|
+
border: 'none',
|
|
582
|
+
bottom: '-8px',
|
|
583
|
+
opacity: 0,
|
|
584
|
+
pointerEvents: selected ? 'all' : 'none',
|
|
585
|
+
}}
|
|
586
|
+
/>
|
|
587
|
+
{/* Left - Source */}
|
|
588
|
+
<Handle
|
|
589
|
+
type="source"
|
|
590
|
+
position={Position.Left}
|
|
591
|
+
id="left-source"
|
|
592
|
+
className="connection-handle"
|
|
593
|
+
style={{
|
|
594
|
+
background: selected ? '#10B981' : '#1a1a2e',
|
|
595
|
+
width: '14px',
|
|
596
|
+
height: '14px',
|
|
597
|
+
border: '3px solid #10B981',
|
|
598
|
+
left: '-8px',
|
|
599
|
+
opacity: selected ? 1 : 0,
|
|
600
|
+
transition: 'all 0.2s ease-in-out',
|
|
601
|
+
cursor: 'crosshair',
|
|
602
|
+
zIndex: 10,
|
|
603
|
+
}}
|
|
604
|
+
/>
|
|
605
|
+
{/* Left - Target (hidden but functional) */}
|
|
606
|
+
<Handle
|
|
607
|
+
type="target"
|
|
608
|
+
position={Position.Left}
|
|
609
|
+
id="left-target"
|
|
610
|
+
style={{
|
|
611
|
+
background: 'transparent',
|
|
612
|
+
width: '14px',
|
|
613
|
+
height: '14px',
|
|
614
|
+
border: 'none',
|
|
615
|
+
left: '-8px',
|
|
616
|
+
opacity: 0,
|
|
617
|
+
pointerEvents: selected ? 'all' : 'none',
|
|
618
|
+
}}
|
|
619
|
+
/>
|
|
620
|
+
{/* Right - Source */}
|
|
621
|
+
<Handle
|
|
622
|
+
type="source"
|
|
623
|
+
position={Position.Right}
|
|
624
|
+
id="right-source"
|
|
625
|
+
className="connection-handle"
|
|
626
|
+
style={{
|
|
627
|
+
background: selected ? '#10B981' : '#1a1a2e',
|
|
628
|
+
width: '14px',
|
|
629
|
+
height: '14px',
|
|
630
|
+
border: '3px solid #10B981',
|
|
631
|
+
right: '-8px',
|
|
632
|
+
opacity: selected ? 1 : 0,
|
|
633
|
+
transition: 'all 0.2s ease-in-out',
|
|
634
|
+
cursor: 'crosshair',
|
|
635
|
+
zIndex: 10,
|
|
636
|
+
}}
|
|
637
|
+
/>
|
|
638
|
+
{/* Right - Target (hidden but functional) */}
|
|
639
|
+
<Handle
|
|
640
|
+
type="target"
|
|
641
|
+
position={Position.Right}
|
|
642
|
+
id="right-target"
|
|
643
|
+
style={{
|
|
644
|
+
background: 'transparent',
|
|
645
|
+
width: '14px',
|
|
646
|
+
height: '14px',
|
|
647
|
+
border: 'none',
|
|
648
|
+
right: '-8px',
|
|
649
|
+
opacity: 0,
|
|
650
|
+
pointerEvents: selected ? 'all' : 'none',
|
|
651
|
+
}}
|
|
652
|
+
/>
|
|
653
|
+
</Box>
|
|
580
654
|
|
|
581
|
-
{/*
|
|
582
|
-
<
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
655
|
+
{/* Node Action Buttons - Shows when selected */}
|
|
656
|
+
<NodeActionButtons
|
|
657
|
+
selected={selected}
|
|
658
|
+
onOpenAIAssistant={(buttonElement) => {
|
|
659
|
+
if (nodeId) {
|
|
660
|
+
showNodeAIAssistantPopup(nodeId, 'Interaction Node', buttonElement);
|
|
661
|
+
}
|
|
662
|
+
}}
|
|
663
|
+
onDelete={() => {
|
|
664
|
+
if (nodeId && onNodesChange) {
|
|
665
|
+
onNodesChange([{ id: nodeId, type: 'remove' }]);
|
|
666
|
+
}
|
|
667
|
+
}}
|
|
668
|
+
onDuplicate={() => {
|
|
669
|
+
if (nodeId) {
|
|
670
|
+
const currentNode = nodes.find(n => n.id === nodeId);
|
|
671
|
+
if (currentNode) {
|
|
672
|
+
const newNode = {
|
|
673
|
+
...currentNode,
|
|
674
|
+
id: `${currentNode.id}-copy-${Date.now()}`,
|
|
675
|
+
position: {
|
|
676
|
+
x: currentNode.position.x + 50,
|
|
677
|
+
y: currentNode.position.y + 50,
|
|
678
|
+
},
|
|
679
|
+
selected: false,
|
|
680
|
+
};
|
|
681
|
+
setNodes([...nodes, newNode]);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
590
684
|
}}
|
|
591
685
|
/>
|
|
592
686
|
</Box>
|