@flowuent-org/diagramming-core 1.1.2 → 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/package.json
CHANGED
|
@@ -18,16 +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
|
|
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;
|
|
31
71
|
|
|
32
72
|
// Create new AI Suggestion node
|
|
33
73
|
const newNodeId = `ai-suggestion-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
@@ -71,10 +111,27 @@ export const AISuggestionsPanel: React.FC<AISuggestionsPanelProps> = ({
|
|
|
71
111
|
measured: { width: 336, height: 150 },
|
|
72
112
|
};
|
|
73
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
|
+
|
|
74
130
|
// Add the new node
|
|
75
|
-
setNodes([...
|
|
131
|
+
setNodes((prevNodes) => [...prevNodes, newNode]);
|
|
76
132
|
|
|
77
|
-
// 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
|
|
78
135
|
const edgeId = `edge-${parentNodeId}-${newNodeId}`;
|
|
79
136
|
const newEdge: Edge = {
|
|
80
137
|
id: edgeId,
|
|
@@ -108,10 +165,10 @@ export const AISuggestionsPanel: React.FC<AISuggestionsPanelProps> = ({
|
|
|
108
165
|
onSuggestionClick(suggestion);
|
|
109
166
|
}
|
|
110
167
|
|
|
111
|
-
//
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
168
|
+
// Reset the flag after a delay to allow the layout to settle
|
|
169
|
+
setTimeout(() => {
|
|
170
|
+
isAddingRef.current = false;
|
|
171
|
+
}, 1000);
|
|
115
172
|
};
|
|
116
173
|
|
|
117
174
|
return (
|
|
@@ -154,7 +211,7 @@ export const AISuggestionsPanel: React.FC<AISuggestionsPanelProps> = ({
|
|
|
154
211
|
{/* Suggestions List */}
|
|
155
212
|
<Box sx={{ }}>
|
|
156
213
|
{suggestions.map((suggestion) => (
|
|
157
|
-
<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, } }} >
|
|
158
215
|
<CardContent>
|
|
159
216
|
<Box sx={{ display: 'flex' }}>
|
|
160
217
|
|
|
@@ -115,6 +115,7 @@ 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
|
|
|
119
120
|
// Set layout direction to RIGHT for automation diagrams
|
|
120
121
|
useEffect(() => {
|
|
@@ -123,10 +124,23 @@ export const DiagramContent: React.FC<DiagramContentProps> = ({
|
|
|
123
124
|
}
|
|
124
125
|
}, [diagramType, layoutDirection, setLayoutDirection]);
|
|
125
126
|
|
|
127
|
+
// Debounce layout recalculation to prevent continuous propagation
|
|
126
128
|
useEffect(() => {
|
|
127
|
-
|
|
129
|
+
// Clear any pending layout recalculation
|
|
130
|
+
if (layoutTimeoutRef.current) {
|
|
131
|
+
clearTimeout(layoutTimeoutRef.current);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Debounce the layout recalculation
|
|
135
|
+
layoutTimeoutRef.current = setTimeout(() => {
|
|
136
|
+
getLayoutedElements();
|
|
137
|
+
}, 300); // 300ms debounce delay
|
|
128
138
|
|
|
129
|
-
return () => {
|
|
139
|
+
return () => {
|
|
140
|
+
if (layoutTimeoutRef.current) {
|
|
141
|
+
clearTimeout(layoutTimeoutRef.current);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
130
144
|
}, [nodes.length, edges.length, contentHeights, diagramType, layoutDirection, getLayoutedElements]);
|
|
131
145
|
|
|
132
146
|
// Listen for layout direction changes
|