@flowuent-org/diagramming-core 1.1.1 → 1.1.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 +8 -8
- package/package.json +1 -1
- package/packages/diagrams/src/lib/components/automation/AISuggestionsPanel.tsx +71 -15
- package/packages/diagrams/src/lib/contexts/DiagramProvider.tsx +1 -1
- package/packages/diagrams/src/lib/hooks/useWorkflowNodeActiont.ts +8 -8
- package/packages/diagrams/src/lib/templates/DiagramContent.tsx +31 -3
- package/packages/diagrams/src/lib/utils/create-updated.tsx +2 -2
|
@@ -5,7 +5,7 @@ export const automationDefaultNodes = [
|
|
|
5
5
|
{
|
|
6
6
|
id: 'start-node',
|
|
7
7
|
type: 'AutomationStartNode',
|
|
8
|
-
position: { x:
|
|
8
|
+
position: { x: 100, y: 200 },
|
|
9
9
|
data: {
|
|
10
10
|
// Display data for the node UI
|
|
11
11
|
label: 'Workflow Trigger',
|
|
@@ -61,7 +61,7 @@ export const automationDefaultNodes = [
|
|
|
61
61
|
{
|
|
62
62
|
id: 'api-call-node',
|
|
63
63
|
type: 'AutomationApiNode',
|
|
64
|
-
position: { x:
|
|
64
|
+
position: { x: 500, y: 200 },
|
|
65
65
|
data: {
|
|
66
66
|
// Display data for the node UI
|
|
67
67
|
label: 'Discover Headlines',
|
|
@@ -152,7 +152,7 @@ export const automationDefaultNodes = [
|
|
|
152
152
|
{
|
|
153
153
|
id: 'ai-suggestion-node',
|
|
154
154
|
type: 'AutomationAISuggestionNode',
|
|
155
|
-
position: { x:
|
|
155
|
+
position: { x: 900, y: 200 },
|
|
156
156
|
data: {
|
|
157
157
|
label: 'Citation Extractor',
|
|
158
158
|
description: 'Extract and format citation from article text.',
|
|
@@ -174,7 +174,7 @@ export const automationDefaultNodes = [
|
|
|
174
174
|
{
|
|
175
175
|
id: 'data-formatting-node',
|
|
176
176
|
type: 'AutomationFormattingNode',
|
|
177
|
-
position: { x:
|
|
177
|
+
position: { x: 700, y: 200 },
|
|
178
178
|
data: {
|
|
179
179
|
// Display data for the node UI
|
|
180
180
|
label: 'Article Analyzer',
|
|
@@ -249,7 +249,7 @@ export const automationDefaultNodes = [
|
|
|
249
249
|
{
|
|
250
250
|
id: 'google-sheets-node',
|
|
251
251
|
type: 'AutomationSheetsNode',
|
|
252
|
-
position: { x:
|
|
252
|
+
position: { x: 1100, y: 200 },
|
|
253
253
|
data: {
|
|
254
254
|
// Display data for the node UI
|
|
255
255
|
label: 'Data Sync Node',
|
|
@@ -357,7 +357,7 @@ export const automationDefaultNodes = [
|
|
|
357
357
|
{
|
|
358
358
|
id: 'note-node-1',
|
|
359
359
|
type: 'AutomationNoteNode',
|
|
360
|
-
position: { x:
|
|
360
|
+
position: { x: 700, y: 50 },
|
|
361
361
|
data: {
|
|
362
362
|
label: 'Article Analyzer',
|
|
363
363
|
description:
|
|
@@ -388,7 +388,7 @@ export const automationDefaultNodes = [
|
|
|
388
388
|
{
|
|
389
389
|
id: 'note-node-2',
|
|
390
390
|
type: 'AutomationNoteNode',
|
|
391
|
-
position: { x:
|
|
391
|
+
position: { x: 500, y: 50 },
|
|
392
392
|
data: {
|
|
393
393
|
label: 'Discover Headlines',
|
|
394
394
|
description:
|
|
@@ -419,7 +419,7 @@ export const automationDefaultNodes = [
|
|
|
419
419
|
{
|
|
420
420
|
id: 'end-node',
|
|
421
421
|
type: 'AutomationEndNode',
|
|
422
|
-
position: { x:
|
|
422
|
+
position: { x: 1500, y: 200 },
|
|
423
423
|
data: {
|
|
424
424
|
// Display data for the node UI
|
|
425
425
|
label: 'Results Display',
|
package/package.json
CHANGED
|
@@ -18,17 +18,56 @@ export const AISuggestionsPanel: React.FC<AISuggestionsPanelProps> = ({
|
|
|
18
18
|
onClose,
|
|
19
19
|
}) => {
|
|
20
20
|
const { getNodes, setNodes, setEdges, getNode } = useReactFlow();
|
|
21
|
+
const isAddingRef = React.useRef(false); // Prevent multiple simultaneous additions
|
|
22
|
+
|
|
23
|
+
const handleSuggestionClick = (suggestion: AISuggestion, event?: React.MouseEvent) => {
|
|
24
|
+
// Prevent event propagation
|
|
25
|
+
if (event) {
|
|
26
|
+
event.stopPropagation();
|
|
27
|
+
event.preventDefault();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Prevent multiple rapid clicks
|
|
31
|
+
if (isAddingRef.current) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
isAddingRef.current = true;
|
|
35
|
+
|
|
36
|
+
// Close the panel immediately to prevent multiple clicks
|
|
37
|
+
if (onClose) {
|
|
38
|
+
onClose();
|
|
39
|
+
}
|
|
21
40
|
|
|
22
|
-
const handleSuggestionClick = (suggestion: AISuggestion) => {
|
|
23
41
|
const nodes = getNodes();
|
|
24
42
|
const parentNode = getNode(parentNodeId);
|
|
25
43
|
if (!parentNode) return;
|
|
26
44
|
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
45
|
+
// Find the rightmost node in the workflow by X position
|
|
46
|
+
// This ensures suggestion nodes are added horizontally after the last node
|
|
47
|
+
// We'll find the node with the highest X position + width
|
|
48
|
+
const findRightmostNode = (): Node => {
|
|
49
|
+
// Get all nodes and find the one with the rightmost position
|
|
50
|
+
const allNodes = getNodes();
|
|
51
|
+
let rightmostNode = parentNode;
|
|
52
|
+
let maxRightEdge = parentNode.position.x + (parentNode.width || 336);
|
|
53
|
+
|
|
54
|
+
allNodes.forEach((node) => {
|
|
55
|
+
const nodeRightEdge = node.position.x + (node.width || 336);
|
|
56
|
+
if (nodeRightEdge > maxRightEdge) {
|
|
57
|
+
maxRightEdge = nodeRightEdge;
|
|
58
|
+
rightmostNode = node;
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return rightmostNode;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const rightmostNode = findRightmostNode();
|
|
66
|
+
|
|
67
|
+
// Calculate position for new node - to the right of the rightmost node
|
|
68
|
+
const nodeWidth = rightmostNode.width || 336;
|
|
69
|
+
const spacing = 50; // Spacing between nodes
|
|
70
|
+
const newNodeX = rightmostNode.position.x + nodeWidth + spacing;
|
|
32
71
|
|
|
33
72
|
// Create new AI Suggestion node
|
|
34
73
|
const newNodeId = `ai-suggestion-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
@@ -55,8 +94,8 @@ export const AISuggestionsPanel: React.FC<AISuggestionsPanelProps> = ({
|
|
|
55
94
|
id: newNodeId,
|
|
56
95
|
type: 'AutomationAISuggestionNode',
|
|
57
96
|
position: {
|
|
58
|
-
x:
|
|
59
|
-
y:
|
|
97
|
+
x: newNodeX,
|
|
98
|
+
y: parentNode.position.y,
|
|
60
99
|
},
|
|
61
100
|
data: {
|
|
62
101
|
label: suggestion.title,
|
|
@@ -72,10 +111,27 @@ export const AISuggestionsPanel: React.FC<AISuggestionsPanelProps> = ({
|
|
|
72
111
|
measured: { width: 336, height: 150 },
|
|
73
112
|
};
|
|
74
113
|
|
|
114
|
+
// Check if a suggestion node with the same title already exists to prevent duplicates
|
|
115
|
+
const existingSuggestionNode = nodes.find(
|
|
116
|
+
(n) => n.type === 'AutomationAISuggestionNode' &&
|
|
117
|
+
n.data?.label === suggestion.title &&
|
|
118
|
+
!(n.data?.formData as any)?.isConfirmed
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
if (existingSuggestionNode) {
|
|
122
|
+
// If a similar unconfirmed suggestion already exists, don't add another
|
|
123
|
+
isAddingRef.current = false;
|
|
124
|
+
if (onClose) {
|
|
125
|
+
onClose();
|
|
126
|
+
}
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
75
130
|
// Add the new node
|
|
76
|
-
setNodes([...
|
|
131
|
+
setNodes((prevNodes) => [...prevNodes, newNode]);
|
|
77
132
|
|
|
78
|
-
// Create edge from parent to new node with dotted line (unconfirmed)
|
|
133
|
+
// Create edge from parent node to new node with dotted line (unconfirmed)
|
|
134
|
+
// The suggestion is for the parent node, but the new node is positioned after the rightmost node
|
|
79
135
|
const edgeId = `edge-${parentNodeId}-${newNodeId}`;
|
|
80
136
|
const newEdge: Edge = {
|
|
81
137
|
id: edgeId,
|
|
@@ -109,10 +165,10 @@ export const AISuggestionsPanel: React.FC<AISuggestionsPanelProps> = ({
|
|
|
109
165
|
onSuggestionClick(suggestion);
|
|
110
166
|
}
|
|
111
167
|
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
168
|
+
// Reset the flag after a delay to allow the layout to settle
|
|
169
|
+
setTimeout(() => {
|
|
170
|
+
isAddingRef.current = false;
|
|
171
|
+
}, 1000);
|
|
116
172
|
};
|
|
117
173
|
|
|
118
174
|
return (
|
|
@@ -155,7 +211,7 @@ export const AISuggestionsPanel: React.FC<AISuggestionsPanelProps> = ({
|
|
|
155
211
|
{/* Suggestions List */}
|
|
156
212
|
<Box sx={{ }}>
|
|
157
213
|
{suggestions.map((suggestion) => (
|
|
158
|
-
<Card key={suggestion.id} onClick={() => handleSuggestionClick(suggestion)} sx={{ mb: 1, bgcolor: '#2563EB1A', border: '1px solid #2563EB', borderRadius: '8px', cursor: 'pointer', transition: 'all 0.2s ease', '&:last-child': { mb: 0, } }} >
|
|
214
|
+
<Card key={suggestion.id} onClick={(e) => handleSuggestionClick(suggestion, e)} sx={{ mb: 1, bgcolor: '#2563EB1A', border: '1px solid #2563EB', borderRadius: '8px', cursor: 'pointer', transition: 'all 0.2s ease', '&:last-child': { mb: 0, } }} >
|
|
159
215
|
<CardContent>
|
|
160
216
|
<Box sx={{ display: 'flex' }}>
|
|
161
217
|
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
createUpdatedEdges,
|
|
11
11
|
createUpdatedNodes,
|
|
12
12
|
} from '../utils/create-updated';
|
|
13
|
-
import { getNodeHeight } from '../utils/utilities';
|
|
13
|
+
import { getNodeHeight, getNodeWidth } from '../utils/utilities';
|
|
14
14
|
import {
|
|
15
15
|
useDiagram,
|
|
16
16
|
useGetDefaultNodeData,
|
|
@@ -154,7 +154,7 @@ export const useGenerateNodesAndEdges = () => {
|
|
|
154
154
|
position:
|
|
155
155
|
index === 0
|
|
156
156
|
? position
|
|
157
|
-
: { x: position.x
|
|
157
|
+
: { x: position.x + (constantLengths.SUB_NODE_WIDTH + constantLengths.CONNECTION_LENGTH) * index, y: position.y },
|
|
158
158
|
replacingNode: isReplacement && index === 0 ? currentNode : {},
|
|
159
159
|
});
|
|
160
160
|
node.data.branchIndex = nodeInfo.branchIndex;
|
|
@@ -165,7 +165,7 @@ export const useGenerateNodesAndEdges = () => {
|
|
|
165
165
|
const edge = createEdgeConnection({
|
|
166
166
|
sourceId: newNodes[edgeInfo.source].id,
|
|
167
167
|
targetId: newNodes[edgeInfo.target].id,
|
|
168
|
-
sourceHandle: `${newNodes[edgeInfo.source].id}-
|
|
168
|
+
sourceHandle: `${newNodes[edgeInfo.source].id}-right`,
|
|
169
169
|
edgeLabel: edgeInfo.label,
|
|
170
170
|
});
|
|
171
171
|
if (edge) newEdges.push(edge);
|
|
@@ -203,11 +203,11 @@ export const useCalculateNewNodesAndEdges = () => {
|
|
|
203
203
|
: isParallelRoot || addBeforeCurrentNode
|
|
204
204
|
? { x: 0, y: constantLengths.NODE_TOP_HEIGHT }
|
|
205
205
|
: {
|
|
206
|
-
x:
|
|
207
|
-
|
|
208
|
-
currentNode
|
|
209
|
-
getNodeHeight(currentNode) +
|
|
206
|
+
x:
|
|
207
|
+
currentNode.position.x +
|
|
208
|
+
getNodeWidth(currentNode) +
|
|
210
209
|
constantLengths.CONNECTION_LENGTH,
|
|
210
|
+
y: currentNode.position.y,
|
|
211
211
|
};
|
|
212
212
|
|
|
213
213
|
const { nodes, edges } = generateNodesAndEdges(
|
|
@@ -224,7 +224,7 @@ export const useCalculateNewNodesAndEdges = () => {
|
|
|
224
224
|
createEdgeConnection({
|
|
225
225
|
sourceId: currentNode.id,
|
|
226
226
|
targetId: nodes[0].id,
|
|
227
|
-
sourceHandle: `${currentNode.id}-
|
|
227
|
+
sourceHandle: `${currentNode.id}-right`,
|
|
228
228
|
})!,
|
|
229
229
|
);
|
|
230
230
|
}
|
|
@@ -115,13 +115,33 @@ export const DiagramContent: React.FC<DiagramContentProps> = ({
|
|
|
115
115
|
const { getLayoutedElements } = useElk();
|
|
116
116
|
const layoutDirection = useLayoutDirection();
|
|
117
117
|
const setLayoutDirection = useSetLayoutDirection();
|
|
118
|
+
const layoutTimeoutRef = React.useRef<NodeJS.Timeout | null>(null);
|
|
118
119
|
|
|
120
|
+
// Set layout direction to RIGHT for automation diagrams
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
if (diagramType === 'automation' && layoutDirection !== 'RIGHT') {
|
|
123
|
+
setLayoutDirection('RIGHT');
|
|
124
|
+
}
|
|
125
|
+
}, [diagramType, layoutDirection, setLayoutDirection]);
|
|
119
126
|
|
|
127
|
+
// Debounce layout recalculation to prevent continuous propagation
|
|
120
128
|
useEffect(() => {
|
|
121
|
-
|
|
129
|
+
// Clear any pending layout recalculation
|
|
130
|
+
if (layoutTimeoutRef.current) {
|
|
131
|
+
clearTimeout(layoutTimeoutRef.current);
|
|
132
|
+
}
|
|
122
133
|
|
|
123
|
-
|
|
124
|
-
|
|
134
|
+
// Debounce the layout recalculation
|
|
135
|
+
layoutTimeoutRef.current = setTimeout(() => {
|
|
136
|
+
getLayoutedElements();
|
|
137
|
+
}, 300); // 300ms debounce delay
|
|
138
|
+
|
|
139
|
+
return () => {
|
|
140
|
+
if (layoutTimeoutRef.current) {
|
|
141
|
+
clearTimeout(layoutTimeoutRef.current);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
}, [nodes.length, edges.length, contentHeights, diagramType, layoutDirection, getLayoutedElements]);
|
|
125
145
|
|
|
126
146
|
// Listen for layout direction changes
|
|
127
147
|
useEffect(() => {
|
|
@@ -208,6 +228,14 @@ export const DiagramContent: React.FC<DiagramContentProps> = ({
|
|
|
208
228
|
edges={visibleEdges}
|
|
209
229
|
onEdgesChange={onEdgesChange}
|
|
210
230
|
fitView
|
|
231
|
+
fitViewOptions={{
|
|
232
|
+
padding: 0.2,
|
|
233
|
+
minZoom: 0.3,
|
|
234
|
+
maxZoom: 2,
|
|
235
|
+
includeHiddenNodes: false,
|
|
236
|
+
}}
|
|
237
|
+
minZoom={0.1}
|
|
238
|
+
maxZoom={2}
|
|
211
239
|
onNodeDragStart={onNodeDragStart}
|
|
212
240
|
onNodeDragStop={onNodeDragEnd}
|
|
213
241
|
onConnect={onConnect}
|
|
@@ -28,8 +28,8 @@ export const createUpdatedEdges = (
|
|
|
28
28
|
? {
|
|
29
29
|
...e,
|
|
30
30
|
source,
|
|
31
|
-
id: `${source}-
|
|
32
|
-
sourceHandle: `${source}-
|
|
31
|
+
id: `${source}-rightv${e.target}`, // Ensure edge ID includes handle
|
|
32
|
+
sourceHandle: `${source}-right`, // Update the source handle ID for horizontal workflow
|
|
33
33
|
}
|
|
34
34
|
: e,
|
|
35
35
|
);
|