@flowuent-org/diagramming-core 1.4.1 → 1.4.3

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 (25) hide show
  1. package/apps/diagramming/src/AutomationDiagramData.ts +110 -4
  2. package/package.json +1 -1
  3. package/packages/diagrams/src/lib/components/automation/AutomationApiNode.tsx +3 -13
  4. package/packages/diagrams/src/lib/components/automation/AutomationDecisionNode.tsx +0 -1
  5. package/packages/diagrams/src/lib/components/automation/AutomationEndNode.tsx +3 -13
  6. package/packages/diagrams/src/lib/components/automation/AutomationFormattingNode.tsx +3 -13
  7. package/packages/diagrams/src/lib/components/automation/AutomationInteractionNode.tsx +3 -13
  8. package/packages/diagrams/src/lib/components/automation/AutomationLoopNode.tsx +105 -0
  9. package/packages/diagrams/src/lib/components/automation/AutomationNavigationNode.tsx +3 -13
  10. package/packages/diagrams/src/lib/components/automation/AutomationNodeCard.tsx +32 -11
  11. package/packages/diagrams/src/lib/components/automation/AutomationNodeHeader.tsx +45 -0
  12. package/packages/diagrams/src/lib/components/automation/AutomationSlackNode.tsx +3 -13
  13. package/packages/diagrams/src/lib/components/automation/AutomationSmsNode.tsx +0 -1
  14. package/packages/diagrams/src/lib/components/automation/AutomationStartNode.tsx +3 -13
  15. package/packages/diagrams/src/lib/components/automation/AutomationTelegramNode.tsx +3 -13
  16. package/packages/diagrams/src/lib/components/automation/AutomationVoiceCallNode.tsx +0 -1
  17. package/packages/diagrams/src/lib/components/automation/AutomationWorkflowNode.tsx +0 -1
  18. package/packages/diagrams/src/lib/components/automation/index.ts +11 -0
  19. package/packages/diagrams/src/lib/icons/icons.tsx +10 -0
  20. package/packages/diagrams/src/lib/icons/index.ts +1 -0
  21. package/packages/diagrams/src/lib/types/automation-node-data-types.ts +14 -1
  22. package/packages/diagrams/src/lib/types/node-types.ts +2 -0
  23. package/packages/diagrams/src/lib/utils/AutomationExecutionEngine.ts +268 -41
  24. package/packages/diagrams/src/lib/utils/automation-flow-processor.ts +70 -0
  25. package/packages/diagrams/src/lib/utils/validateAutomationLoopNode.ts +104 -0
@@ -10,6 +10,7 @@ export const automationDefaultNodes = [
10
10
  // Display data for the node UI
11
11
  label: 'Workflow Trigger',
12
12
  description: 'Workflow entry point. Triggered manually or on schedule.',
13
+ headerText: 'Workflow entry point',
13
14
  status: 'Ready',
14
15
  lastRun: 'Never',
15
16
  duration: '0.0s',
@@ -19,6 +20,7 @@ export const automationDefaultNodes = [
19
20
  formData: {
20
21
  nodeId: 'start-node',
21
22
  title: 'Workflow Trigger',
23
+ headerText: 'Workflow entry point',
22
24
  type: 'start',
23
25
  triggerType: 'manual',
24
26
  scheduleConfig: {
@@ -67,6 +69,7 @@ export const automationDefaultNodes = [
67
69
  label: 'Discover Headlines',
68
70
  description:
69
71
  "This node extracts all news headlines from CNN's homepage using web scraping techniques.",
72
+ headerText: 'Get all CNN headlines',
70
73
  status: 'Ready',
71
74
  lastRun: 'Never',
72
75
  duration: '0.8s',
@@ -76,6 +79,7 @@ export const automationDefaultNodes = [
76
79
  formData: {
77
80
  nodeId: 'api-call-node',
78
81
  title: 'Discover Headlines',
82
+ headerText: 'Get all CNN headlines',
79
83
  type: 'api',
80
84
  method: 'GET',
81
85
  url: 'https://jsonplaceholder.typicode.com/posts',
@@ -157,6 +161,7 @@ export const automationDefaultNodes = [
157
161
  // Display data for the node UI
158
162
  label: 'Navigate to Website',
159
163
  description: 'Navigate to the target website and wait for page load',
164
+ headerText: 'Web navigation action',
160
165
  status: 'Ready',
161
166
  navigationType: 'navigate',
162
167
  url: 'https://example.com',
@@ -203,6 +208,7 @@ export const automationDefaultNodes = [
203
208
  label: 'Article Analyzer',
204
209
  description:
205
210
  'Extract and analyze article content, generate summaries and topic classifications.',
211
+ headerText: 'AI-powered content analysis',
206
212
  status: 'Ready',
207
213
  lastRun: '2 minutes ago',
208
214
  duration: '1.2s',
@@ -212,6 +218,7 @@ export const automationDefaultNodes = [
212
218
  formData: {
213
219
  nodeId: 'data-formatting-node',
214
220
  title: 'Article Analyzer',
221
+ headerText: 'AI-powered content analysis',
215
222
  type: 'formatting',
216
223
  inputVariable: 'postsResponse',
217
224
  formattingType: 'ai-powered',
@@ -269,6 +276,42 @@ export const automationDefaultNodes = [
269
276
  height: 150,
270
277
  measured: { width: 336, height: 150 },
271
278
  },
279
+ // =====================================
280
+ // Loop Node
281
+ // =====================================
282
+ {
283
+ id: 'loop-node',
284
+ type: 'AutomationLoopNode',
285
+ position: { x: 1100, y: 200 },
286
+ data: {
287
+ label: 'For Each Item',
288
+ description: 'Iterate over array items and run body steps for each',
289
+ headerText: 'For-each loop over array',
290
+ status: 'Ready',
291
+ parameters: {
292
+ arraySource: { type: 'previousStep' },
293
+ maxIterations: 100,
294
+ currentItemVariable: 'currentItem',
295
+ loopIndexVariable: 'loopIndex',
296
+ },
297
+ formData: {
298
+ nodeId: 'loop-node',
299
+ title: 'For Each Item',
300
+ type: 'loop',
301
+ arraySource: { type: 'previousStep' },
302
+ maxIterations: 100,
303
+ currentItemVariable: 'currentItem',
304
+ loopIndexVariable: 'loopIndex',
305
+ },
306
+ lastRun: 'Never',
307
+ backgroundColor: '#181C25',
308
+ textColor: '#ffffff',
309
+ borderColor: '#1e293b',
310
+ },
311
+ width: 336,
312
+ height: 150,
313
+ measured: { width: 336, height: 150 },
314
+ },
272
315
  {
273
316
  id: 'end-node',
274
317
  type: 'AutomationEndNode',
@@ -277,6 +320,7 @@ export const automationDefaultNodes = [
277
320
  // Display data for the node UI
278
321
  label: 'Results Display',
279
322
  description: 'Display analyzed article results to the user.',
323
+ headerText: 'Workflow completion and results display',
280
324
  status: 'Ready',
281
325
  lastRun: '1 minute ago',
282
326
  duration: '0.3s',
@@ -487,6 +531,7 @@ export const automationDefaultNodes = [
487
531
  data: {
488
532
  label: 'Route by Condition',
489
533
  description: 'Conditional branching / routing',
534
+ headerText: 'Conditional branching / routing',
490
535
  routingType: 'condition',
491
536
  status: 'Need to Config',
492
537
  parameters: {
@@ -524,6 +569,7 @@ export const automationDefaultNodes = [
524
569
  data: {
525
570
  label: 'Send SMS',
526
571
  description: 'Send SMS (recipientPhone)',
572
+ headerText: 'Send SMS (recipientPhone)',
527
573
  status: 'Need to Config',
528
574
  parameters: {
529
575
  recipientPhone: '+1 (555) 123-4567',
@@ -559,6 +605,7 @@ export const automationDefaultNodes = [
559
605
  data: {
560
606
  label: 'Voice Call',
561
607
  description: 'Voice calls with spoken content',
608
+ headerText: 'Voice calls with spoken content',
562
609
  status: 'Need to Config',
563
610
  parameters: {
564
611
  recipientPhone: '+1 (555) 987-6543',
@@ -594,6 +641,7 @@ export const automationDefaultNodes = [
594
641
  data: {
595
642
  label: 'Run Sub-Workflow',
596
643
  description: 'Run/embed another workflow (workflowId)',
644
+ headerText: 'Run/embed another workflow (workflowId)',
597
645
  status: 'Need to Config',
598
646
  parameters: {
599
647
  workflowId: 'wf-notification-pipeline',
@@ -683,11 +731,11 @@ export const automationDefaultEdges = [
683
731
  },
684
732
  },
685
733
  {
686
- id: 'edge-formatting-to-end',
734
+ id: 'edge-formatting-to-loop',
687
735
  source: 'data-formatting-node',
688
- target: 'end-node',
689
- sourceHandle: 'right',
690
- targetHandle: 'left',
736
+ target: 'loop-node',
737
+ sourceHandle: 'right-source',
738
+ targetHandle: 'left-target',
691
739
  data: {
692
740
  label: '',
693
741
  type: 'flow',
@@ -701,6 +749,64 @@ export const automationDefaultEdges = [
701
749
  color: '#ffffff',
702
750
  },
703
751
  },
752
+ {
753
+ id: 'edge-loop-to-slack',
754
+ source: 'loop-node',
755
+ target: 'slack-node',
756
+ sourceHandle: 'loop',
757
+ targetHandle: 'left-target',
758
+ data: {
759
+ label: 'Loop body',
760
+ type: 'flow',
761
+ },
762
+ style: {
763
+ stroke: '#86EFAC',
764
+ strokeWidth: 2,
765
+ },
766
+ markerEnd: {
767
+ type: MarkerType.ArrowClosed,
768
+ color: '#86EFAC',
769
+ },
770
+ },
771
+ {
772
+ id: 'edge-slack-back-to-loop',
773
+ source: 'slack-node',
774
+ target: 'loop-node',
775
+ sourceHandle: 'right-source',
776
+ targetHandle: 'left-target',
777
+ data: {
778
+ label: 'Back to loop',
779
+ type: 'flow',
780
+ },
781
+ style: {
782
+ stroke: '#86EFAC',
783
+ strokeWidth: 2,
784
+ strokeDasharray: '5 5',
785
+ },
786
+ markerEnd: {
787
+ type: MarkerType.ArrowClosed,
788
+ color: '#86EFAC',
789
+ },
790
+ },
791
+ {
792
+ id: 'edge-loop-done-to-end',
793
+ source: 'loop-node',
794
+ target: 'end-node',
795
+ sourceHandle: 'done',
796
+ targetHandle: 'left-target',
797
+ data: {
798
+ label: 'Done',
799
+ type: 'flow',
800
+ },
801
+ style: {
802
+ stroke: '#9CA3AF',
803
+ strokeWidth: 2,
804
+ },
805
+ markerEnd: {
806
+ type: MarkerType.ArrowClosed,
807
+ color: '#9CA3AF',
808
+ },
809
+ },
704
810
  // =====================================
705
811
  // Slack Node Edge
706
812
  // =====================================
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowuent-org/diagramming-core",
3
- "version": "1.4.1",
3
+ "version": "1.4.3",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -23,11 +23,13 @@ import { showNodeAIAssistantPopup } from './NodeAIAssistantPopup';
23
23
  import { useSearch } from '../../contexts/SearchContext';
24
24
  import { getStatusColor } from './statusColors';
25
25
  import { ApiNodeIcon } from '../../icons';
26
+ import { AutomationNodeHeaderSection } from './AutomationNodeHeader';
26
27
 
27
28
  interface AutomationApiNodeProps {
28
29
  data: {
29
30
  label: string;
30
31
  description: string;
32
+ headerText?: string;
31
33
  status: 'Ready' | 'Running' | 'Completed' | 'Error';
32
34
  method: 'GET' | 'POST' | 'PUT' | 'DELETE';
33
35
  url: string;
@@ -313,19 +315,7 @@ export const AutomationApiNode: React.FC<AutomationApiNodeProps> = ({ data, sele
313
315
  onClick={handleJsonClick}
314
316
  >
315
317
  {/* Top Header Section */}
316
- <Box sx={{
317
- backgroundColor: "rgba(67, 93, 132, 0.1)",
318
- padding: '8px 16px',
319
- borderRadius: '12px 12px 0 0'
320
- }}>
321
- <Typography variant="body2" sx={{
322
- color: '#ffffff',
323
- fontSize: '12px',
324
- fontWeight: 500
325
- }}>
326
- {data.formData?.description || t('automation.apiNode.headerDescription')}
327
- </Typography>
328
- </Box>
318
+ <AutomationNodeHeaderSection data={data} />
329
319
 
330
320
  {/* Main Content */}
331
321
  <Box sx={{ padding: '16px' }}>
@@ -44,7 +44,6 @@ export const AutomationDecisionNode: React.FC<AutomationDecisionNodeProps> = ({
44
44
  data={data}
45
45
  selected={selected}
46
46
  icon={<DecisionNodeIcon />}
47
- headerText="Conditional branching / routing"
48
47
  aiAssistantTitle="Decision Node"
49
48
  branchHandles
50
49
  descriptionContent={
@@ -14,11 +14,13 @@ import { showNodeAIAssistantPopup } from './NodeAIAssistantPopup';
14
14
  import { useSearch } from '../../contexts/SearchContext';
15
15
  import { getStatusColor } from './statusColors';
16
16
  import { EndNodeIcon } from '../../icons';
17
+ import { AutomationNodeHeaderSection } from './AutomationNodeHeader';
17
18
 
18
19
  interface AutomationEndNodeProps {
19
20
  data: {
20
21
  label: string;
21
22
  description: string;
23
+ headerText?: string;
22
24
  status: 'Ready' | 'Running' | 'Completed' | 'Error';
23
25
  outputType: 'display' | 'store' | 'send';
24
26
  outputConfig?: {
@@ -244,19 +246,7 @@ export const AutomationEndNode: React.FC<AutomationEndNodeProps> = ({ data, sele
244
246
  onClick={handleJsonClick}
245
247
  >
246
248
  {/* Top Header Section */}
247
- <Box sx={{
248
- backgroundColor: "rgba(67, 93, 132, 0.1)",
249
- padding: '8px 16px',
250
- borderRadius: '12px 12px 0 0'
251
- }}>
252
- <Typography variant="body2" sx={{
253
- color: '#ffffff',
254
- fontSize: '12px',
255
- fontWeight: 500
256
- }}>
257
- {data.formData?.description || t('automation.endNode.headerDescription')}
258
- </Typography>
259
- </Box>
249
+ <AutomationNodeHeaderSection data={data} highlightText={highlightText} />
260
250
 
261
251
  {/* Main Content */}
262
252
  <Box sx={{ padding: '16px' }}>
@@ -23,11 +23,13 @@ import { showNodeAIAssistantPopup } from './NodeAIAssistantPopup';
23
23
  import { useSearch } from '../../contexts/SearchContext';
24
24
  import { getStatusColor } from './statusColors';
25
25
  import { ArticleAnalyzerIcon, FormattingNodeIcon } from '../../icons';
26
+ import { AutomationNodeHeaderSection } from './AutomationNodeHeader';
26
27
 
27
28
  interface AutomationFormattingNodeProps {
28
29
  data: {
29
30
  label: string;
30
31
  description: string;
32
+ headerText?: string;
31
33
  status: 'Ready' | 'Running' | 'Completed' | 'Error';
32
34
  inputVariable: string;
33
35
  formattingType: 'basic' | 'ai-powered';
@@ -319,19 +321,7 @@ export const AutomationFormattingNode: React.FC<AutomationFormattingNodeProps> =
319
321
  onClick={handleJsonClick}
320
322
  >
321
323
  {/* Top Header Section */}
322
- <Box sx={{
323
- backgroundColor: "rgba(67, 93, 132, 0.1)",
324
- padding: '8px 16px',
325
- borderRadius: '12px 12px 0 0'
326
- }}>
327
- <Typography variant="body2" sx={{
328
- color: '#ffffff',
329
- fontSize: '12px',
330
- fontWeight: 500
331
- }}>
332
- {data.formData?.description || t('automation.formattingNode.headerDescription')}
333
- </Typography>
334
- </Box>
324
+ <AutomationNodeHeaderSection data={data} />
335
325
 
336
326
  {/* Main Content */}
337
327
  <Box sx={{ padding: '16px' }}>
@@ -27,6 +27,7 @@ import { useSearch } from '../../contexts/SearchContext';
27
27
  import { NodeActionButtons } from './NodeActionButtons';
28
28
  import { showNodeAIAssistantPopup } from './NodeAIAssistantPopup';
29
29
  import { getStatusColor } from './statusColors';
30
+ import { AutomationNodeHeaderSection } from './AutomationNodeHeader';
30
31
 
31
32
  // ========================
32
33
  // Types
@@ -49,6 +50,7 @@ export type InteractionType =
49
50
  export interface AutomationInteractionNodeData {
50
51
  label: string;
51
52
  description: string;
53
+ headerText?: string;
52
54
  interactionType?: InteractionType;
53
55
  status: 'Ready' | 'Running' | 'Completed' | 'Error';
54
56
  parameters?: {
@@ -424,19 +426,7 @@ export const AutomationInteractionNode: React.FC<AutomationInteractionNodeProps>
424
426
  onClick={handleJsonClick}
425
427
  >
426
428
  {/* 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)}
438
- </Typography>
439
- </Box>
429
+ <AutomationNodeHeaderSection data={data} highlightText={highlightText} />
440
430
 
441
431
  {/* Main Content */}
442
432
  <Box sx={{ padding: '16px' }}>
@@ -0,0 +1,105 @@
1
+ import React from 'react';
2
+ import { Box, Typography, Chip } from '@mui/material';
3
+ import { LoopNodeIcon } from '../../icons';
4
+ import {
5
+ AutomationNodeCard,
6
+ AutomationNodeCardData,
7
+ } from './AutomationNodeCard';
8
+ import { AutomationLoopNodeForm } from '../../types/automation-node-data-types';
9
+
10
+ export interface AutomationLoopNodeData extends AutomationNodeCardData {
11
+ parameters?: {
12
+ arraySource?: AutomationLoopNodeForm['arraySource'];
13
+ maxIterations?: number;
14
+ currentItemVariable?: string;
15
+ loopIndexVariable?: string;
16
+ [key: string]: unknown;
17
+ };
18
+ }
19
+
20
+ export interface AutomationLoopNodeProps {
21
+ data: AutomationLoopNodeData;
22
+ selected?: boolean;
23
+ }
24
+
25
+ export const AutomationLoopNode: React.FC<AutomationLoopNodeProps> = ({
26
+ data,
27
+ selected,
28
+ }) => {
29
+ const formData = (data.formData ?? {}) as Partial<AutomationLoopNodeForm>;
30
+ const arraySource =
31
+ data.parameters?.arraySource ?? formData.arraySource;
32
+ const maxIterations =
33
+ data.parameters?.maxIterations ?? formData.maxIterations ?? 100;
34
+ const currentItemVariable =
35
+ data.parameters?.currentItemVariable ??
36
+ formData.currentItemVariable ??
37
+ 'currentItem';
38
+ const loopIndexVariable =
39
+ data.parameters?.loopIndexVariable ??
40
+ formData.loopIndexVariable ??
41
+ 'loopIndex';
42
+
43
+ const sourceLabel =
44
+ arraySource?.type === 'variable'
45
+ ? arraySource.variableName ?? 'Unnamed variable'
46
+ : arraySource?.type === 'previousStep'
47
+ ? 'Previous step'
48
+ : 'Not configured';
49
+
50
+ return (
51
+ <AutomationNodeCard
52
+ data={data}
53
+ selected={selected}
54
+ icon={<LoopNodeIcon />}
55
+ aiAssistantTitle="Loop Node"
56
+ loopHandles
57
+ descriptionContent={
58
+ <Box sx={{ width: '100%' }}>
59
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75, mb: 1 }}>
60
+ <Chip
61
+ size="small"
62
+ label={sourceLabel.toUpperCase()}
63
+ sx={{
64
+ height: 20,
65
+ fontSize: '10px',
66
+ fontWeight: 600,
67
+ color: '#86EFAC',
68
+ bgcolor: 'rgba(134, 239, 172, 0.12)',
69
+ }}
70
+ />
71
+ <Chip
72
+ size="small"
73
+ label={`MAX ${maxIterations}`}
74
+ sx={{
75
+ height: 20,
76
+ fontSize: '10px',
77
+ fontWeight: 600,
78
+ color: '#9CA3AF',
79
+ bgcolor: 'rgba(156, 163, 175, 0.12)',
80
+ }}
81
+ />
82
+ </Box>
83
+ <Typography
84
+ variant="body2"
85
+ sx={{ color: '#9CA3AF', fontSize: '12px', lineHeight: 1.4, mb: 0.5 }}
86
+ >
87
+ {arraySource
88
+ ? `For each item → ${currentItemVariable}, index → ${loopIndexVariable}`
89
+ : data.description}
90
+ </Typography>
91
+ <Box sx={{ display: 'flex', justifyContent: 'space-between', mt: 1.5, px: 0.5 }}>
92
+ <Typography variant="caption" sx={{ color: '#86EFAC', fontSize: '10px', fontWeight: 600 }}>
93
+ LOOP →
94
+ </Typography>
95
+ <Typography variant="caption" sx={{ color: '#9CA3AF', fontSize: '10px', fontWeight: 600 }}>
96
+ DONE →
97
+ </Typography>
98
+ </Box>
99
+ </Box>
100
+ }
101
+ />
102
+ );
103
+ };
104
+
105
+ export default AutomationLoopNode;
@@ -15,11 +15,13 @@ import { showNodeAIAssistantPopup } from './NodeAIAssistantPopup';
15
15
  import { useSearch } from '../../contexts/SearchContext';
16
16
  import { getStatusColor } from './statusColors';
17
17
  import { NavigationIcon } from '../../icons';
18
+ import { AutomationNodeHeaderSection } from './AutomationNodeHeader';
18
19
 
19
20
  interface AutomationNavigationNodeProps {
20
21
  data: {
21
22
  label: string;
22
23
  description: string;
24
+ headerText?: string;
23
25
  status: 'Ready' | 'Running' | 'Completed' | 'Error';
24
26
  navigationType: 'navigate' | 'click' | 'scroll' | 'wait' | 'extract';
25
27
  url?: string;
@@ -276,19 +278,7 @@ export const AutomationNavigationNode: React.FC<AutomationNavigationNodeProps> =
276
278
  onClick={handleJsonClick}
277
279
  >
278
280
  {/* Top Header Section */}
279
- <Box sx={{
280
- backgroundColor: "rgba(67, 93, 132, 0.1)",
281
- padding: '8px 16px',
282
- borderRadius: '12px 12px 0 0'
283
- }}>
284
- <Typography variant="body2" sx={{
285
- color: '#ffffff',
286
- fontSize: '12px',
287
- fontWeight: 500
288
- }}>
289
- {data.formData?.description || data.description || 'Web navigation action'}
290
- </Typography>
291
- </Box>
281
+ <AutomationNodeHeaderSection data={data} highlightText={highlightText} />
292
282
 
293
283
  {/* Main Content */}
294
284
  <Box sx={{ padding: '16px' }}>
@@ -11,10 +11,12 @@ import { NodeActionButtons } from './NodeActionButtons';
11
11
  import { showNodeAIAssistantPopup } from './NodeAIAssistantPopup';
12
12
  import { useSearch } from '../../contexts/SearchContext';
13
13
  import { getStatusColor } from './statusColors';
14
+ import { AutomationNodeHeaderSection } from './AutomationNodeHeader';
14
15
 
15
16
  export interface AutomationNodeCardData {
16
17
  label: string;
17
18
  description: string;
19
+ headerText?: string;
18
20
  status?: string;
19
21
  lastRun?: string;
20
22
  formData?: Record<string, unknown>;
@@ -28,6 +30,7 @@ export interface AutomationNodeCardProps {
28
30
  headerText?: string;
29
31
  aiAssistantTitle?: string;
30
32
  branchHandles?: boolean;
33
+ loopHandles?: boolean;
31
34
  descriptionContent?: React.ReactNode;
32
35
  }
33
36
 
@@ -62,6 +65,7 @@ export const AutomationNodeCard: React.FC<AutomationNodeCardProps> = ({
62
65
  headerText,
63
66
  aiAssistantTitle,
64
67
  branchHandles = false,
68
+ loopHandles = false,
65
69
  descriptionContent,
66
70
  }) => {
67
71
  const { t } = useTranslation();
@@ -246,6 +250,29 @@ export const AutomationNodeCard: React.FC<AutomationNodeCardProps> = ({
246
250
  style={handleTargetStyle('right', selected)}
247
251
  />
248
252
  </>
253
+ ) : loopHandles ? (
254
+ <>
255
+ <Handle
256
+ type="source"
257
+ position={Position.Right}
258
+ id="loop"
259
+ className="connection-handle"
260
+ style={{ ...handleSourceStyle(selected), right: '-8px', top: '35%' }}
261
+ />
262
+ <Handle
263
+ type="source"
264
+ position={Position.Right}
265
+ id="done"
266
+ className="connection-handle"
267
+ style={{ ...handleSourceStyle(selected), right: '-8px', top: '65%' }}
268
+ />
269
+ <Handle
270
+ type="target"
271
+ position={Position.Right}
272
+ id="right-target"
273
+ style={handleTargetStyle('right', selected)}
274
+ />
275
+ </>
249
276
  ) : (
250
277
  <>
251
278
  <Handle
@@ -300,17 +327,11 @@ export const AutomationNodeCard: React.FC<AutomationNodeCardProps> = ({
300
327
  }}
301
328
  onClick={handleJsonClick}
302
329
  >
303
- <Box
304
- sx={{
305
- backgroundColor: 'rgba(67, 93, 132, 0.1)',
306
- padding: '8px 16px',
307
- borderRadius: '12px 12px 0 0',
308
- }}
309
- >
310
- <Typography variant="body2" sx={{ color: '#ffffff', fontSize: '12px', fontWeight: 500 }}>
311
- {highlightText(headerText || data.description)}
312
- </Typography>
313
- </Box>
330
+ <AutomationNodeHeaderSection
331
+ data={data}
332
+ headerText={headerText}
333
+ highlightText={highlightText}
334
+ />
314
335
 
315
336
  <Box sx={{ padding: '16px' }}>
316
337
  <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
@@ -0,0 +1,45 @@
1
+ import React from 'react';
2
+ import { Box, Typography } from '@mui/material';
3
+
4
+ export interface AutomationNodeHeaderData {
5
+ headerText?: string;
6
+ formData?: Record<string, unknown>;
7
+ }
8
+
9
+ export const getAutomationNodeHeaderText = (
10
+ data: AutomationNodeHeaderData,
11
+ override?: string,
12
+ ): string | undefined => {
13
+ const text = override ?? data.headerText ?? (data.formData?.headerText as string | undefined);
14
+ const trimmed = text?.trim();
15
+ return trimmed || undefined;
16
+ };
17
+
18
+ interface AutomationNodeHeaderSectionProps {
19
+ data: AutomationNodeHeaderData;
20
+ headerText?: string;
21
+ highlightText?: (text: string) => React.ReactNode;
22
+ }
23
+
24
+ export const AutomationNodeHeaderSection: React.FC<AutomationNodeHeaderSectionProps> = ({
25
+ data,
26
+ headerText,
27
+ highlightText,
28
+ }) => {
29
+ const text = getAutomationNodeHeaderText(data, headerText);
30
+ if (!text) return null;
31
+
32
+ return (
33
+ <Box
34
+ sx={{
35
+ backgroundColor: 'rgba(67, 93, 132, 0.1)',
36
+ padding: '8px 16px',
37
+ borderRadius: '12px 12px 0 0',
38
+ }}
39
+ >
40
+ <Typography variant="body2" sx={{ color: '#ffffff', fontSize: '12px', fontWeight: 500 }}>
41
+ {highlightText ? highlightText(text) : text}
42
+ </Typography>
43
+ </Box>
44
+ );
45
+ };
@@ -28,6 +28,7 @@ import { useDiagram } from '../../contexts/DiagramProvider';
28
28
  import { useSearch } from '../../contexts/SearchContext';
29
29
  import { NodeActionButtons } from './NodeActionButtons';
30
30
  import { getStatusColor } from './statusColors';
31
+ import { AutomationNodeHeaderSection } from './AutomationNodeHeader';
31
32
 
32
33
  // ========================
33
34
  // Types
@@ -54,6 +55,7 @@ export interface SlackTokenData {
54
55
  export interface AutomationSlackNodeData {
55
56
  label: string;
56
57
  description: string;
58
+ headerText?: string;
57
59
  operationType?: SlackOperationType;
58
60
  status: 'Ready' | 'Running' | 'Completed' | 'Failed' | 'Need to Config' | 'authenticated';
59
61
  parameters?: {
@@ -469,19 +471,7 @@ export const AutomationSlackNode: React.FC<AutomationSlackNodeProps> = ({
469
471
  />
470
472
 
471
473
  {/* Top Header Section */}
472
- <Box sx={{
473
- backgroundColor: "rgba(67, 93, 132, 0.1)",
474
- padding: '8px 16px',
475
- borderRadius: '12px 12px 0 0'
476
- }}>
477
- <Typography variant="body2" sx={{
478
- color: '#ffffff',
479
- fontSize: '12px',
480
- fontWeight: 500
481
- }}>
482
- {data.description || operationConfig.description}
483
- </Typography>
484
- </Box>
474
+ <AutomationNodeHeaderSection data={data} highlightText={highlightText} />
485
475
 
486
476
  {/* Header */}
487
477
  <Box