@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.
- package/apps/diagramming/src/AutomationDiagramData.ts +110 -4
- package/package.json +1 -1
- package/packages/diagrams/src/lib/components/automation/AutomationApiNode.tsx +3 -13
- package/packages/diagrams/src/lib/components/automation/AutomationDecisionNode.tsx +0 -1
- package/packages/diagrams/src/lib/components/automation/AutomationEndNode.tsx +3 -13
- package/packages/diagrams/src/lib/components/automation/AutomationFormattingNode.tsx +3 -13
- package/packages/diagrams/src/lib/components/automation/AutomationInteractionNode.tsx +3 -13
- package/packages/diagrams/src/lib/components/automation/AutomationLoopNode.tsx +105 -0
- package/packages/diagrams/src/lib/components/automation/AutomationNavigationNode.tsx +3 -13
- package/packages/diagrams/src/lib/components/automation/AutomationNodeCard.tsx +32 -11
- package/packages/diagrams/src/lib/components/automation/AutomationNodeHeader.tsx +45 -0
- package/packages/diagrams/src/lib/components/automation/AutomationSlackNode.tsx +3 -13
- package/packages/diagrams/src/lib/components/automation/AutomationSmsNode.tsx +0 -1
- package/packages/diagrams/src/lib/components/automation/AutomationStartNode.tsx +3 -13
- package/packages/diagrams/src/lib/components/automation/AutomationTelegramNode.tsx +3 -13
- package/packages/diagrams/src/lib/components/automation/AutomationVoiceCallNode.tsx +0 -1
- package/packages/diagrams/src/lib/components/automation/AutomationWorkflowNode.tsx +0 -1
- package/packages/diagrams/src/lib/components/automation/index.ts +11 -0
- package/packages/diagrams/src/lib/icons/icons.tsx +10 -0
- package/packages/diagrams/src/lib/icons/index.ts +1 -0
- package/packages/diagrams/src/lib/types/automation-node-data-types.ts +14 -1
- package/packages/diagrams/src/lib/types/node-types.ts +2 -0
- package/packages/diagrams/src/lib/utils/AutomationExecutionEngine.ts +268 -41
- package/packages/diagrams/src/lib/utils/automation-flow-processor.ts +70 -0
- 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-
|
|
734
|
+
id: 'edge-formatting-to-loop',
|
|
687
735
|
source: 'data-formatting-node',
|
|
688
|
-
target: '
|
|
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
|
@@ -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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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
|
-
<
|
|
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
|