@flowuent-org/diagramming-core 1.1.8 → 1.2.0

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 (20) hide show
  1. package/package.json +116 -116
  2. package/packages/diagrams/src/index.ts +1 -0
  3. package/packages/diagrams/src/lib/atoms/ConnectionPoints.tsx +149 -0
  4. package/packages/diagrams/src/lib/components/automation/AutomationApiNode.tsx +794 -650
  5. package/packages/diagrams/src/lib/components/automation/AutomationEndNode.tsx +606 -449
  6. package/packages/diagrams/src/lib/components/automation/AutomationFormattingNode.tsx +831 -687
  7. package/packages/diagrams/src/lib/components/automation/AutomationNoteNode.tsx +420 -275
  8. package/packages/diagrams/src/lib/components/automation/AutomationSheetsNode.tsx +1118 -974
  9. package/packages/diagrams/src/lib/components/automation/AutomationStartNode.tsx +509 -344
  10. package/packages/diagrams/src/lib/components/automation/NodeAIAssistantPopup.tsx +504 -0
  11. package/packages/diagrams/src/lib/components/automation/NodeActionButtons.tsx +146 -0
  12. package/packages/diagrams/src/lib/components/automation/index.ts +20 -11
  13. package/packages/diagrams/src/lib/molecules/SideHandles.tsx +177 -12
  14. package/packages/diagrams/src/lib/organisms/CustomEdge/custom-edge-generator.tsx +10 -5
  15. package/packages/diagrams/src/lib/styles.css +53 -0
  16. package/packages/diagrams/src/lib/templates/DiagramContainer.tsx +59 -0
  17. package/packages/diagrams/src/lib/templates/Diagramming.tsx +246 -204
  18. package/packages/diagrams/src/lib/types/edge-types.ts +17 -0
  19. package/packages/diagrams/src/lib/utils/generateEdgesFromNodeOrder.ts +113 -0
  20. package/packages/diagrams/src/lib/utils/nodeAIAssistantConfig.ts +54 -0
@@ -0,0 +1,504 @@
1
+ import React, { useState, useRef, useEffect } from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import {
4
+ Box,
5
+ Card,
6
+ CardContent,
7
+ IconButton,
8
+ TextField,
9
+ Typography,
10
+ CircularProgress,
11
+ Alert,
12
+ } from '@mui/material';
13
+ import { RiCloseLine, RiSendPlaneFill } from 'react-icons/ri';
14
+ import { IconMessage } from '@tabler/icons-react';
15
+ import {
16
+ getNodeAIAssistantEndpoint,
17
+ getNodeAIAssistantHeaders,
18
+ } from '../../utils/nodeAIAssistantConfig';
19
+
20
+ interface Message {
21
+ id: string;
22
+ role: 'user' | 'assistant';
23
+ content: string;
24
+ timestamp: string;
25
+ }
26
+
27
+ interface NodeAIAssistantPopupProps {
28
+ nodeId: string;
29
+ nodeType: string;
30
+ position: { x: number; y: number };
31
+ onClose: () => void;
32
+ apiEndpoint?: string;
33
+ apiHeaders?: Record<string, string>;
34
+ }
35
+
36
+ export const NodeAIAssistantPopup: React.FC<NodeAIAssistantPopupProps> = ({
37
+ nodeId,
38
+ nodeType,
39
+ position,
40
+ onClose,
41
+ apiEndpoint,
42
+ apiHeaders,
43
+ }) => {
44
+ // Use provided endpoint/headers or fall back to global configuration
45
+ const effectiveEndpoint = apiEndpoint || getNodeAIAssistantEndpoint();
46
+ const effectiveHeaders = apiHeaders || getNodeAIAssistantHeaders();
47
+ const [messages, setMessages] = useState<Message[]>([
48
+ {
49
+ id: '1',
50
+ role: 'assistant',
51
+ content: `Hello! I'm your AI assistant for this ${nodeType} node. How can I help you?`,
52
+ timestamp: new Date().toLocaleTimeString(),
53
+ },
54
+ ]);
55
+ const [inputValue, setInputValue] = useState('');
56
+ const [isLoading, setIsLoading] = useState(false);
57
+ const [error, setError] = useState<string | null>(null);
58
+ const messagesEndRef = useRef<HTMLDivElement>(null);
59
+ const popupRef = useRef<HTMLDivElement>(null);
60
+
61
+ useEffect(() => {
62
+ messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
63
+ }, [messages]);
64
+
65
+ useEffect(() => {
66
+ const handleClickOutside = (event: MouseEvent) => {
67
+ const target = event.target as Element;
68
+ if (
69
+ popupRef.current &&
70
+ !popupRef.current.contains(target) &&
71
+ !target.closest('[data-node-ai-assistant-button]')
72
+ ) {
73
+ onClose();
74
+ }
75
+ };
76
+
77
+ setTimeout(() => {
78
+ document.addEventListener('mousedown', handleClickOutside);
79
+ }, 0);
80
+
81
+ return () => {
82
+ document.removeEventListener('mousedown', handleClickOutside);
83
+ };
84
+ }, [onClose]);
85
+
86
+ const handleSend = async () => {
87
+ if (!inputValue.trim() || isLoading) return;
88
+
89
+ const userMessage: Message = {
90
+ id: Date.now().toString(),
91
+ role: 'user',
92
+ content: inputValue,
93
+ timestamp: new Date().toLocaleTimeString(),
94
+ };
95
+
96
+ setMessages((prev) => [...prev, userMessage]);
97
+ const currentInput = inputValue;
98
+ setInputValue('');
99
+ setError(null);
100
+ setIsLoading(true);
101
+
102
+ // Add a loading message placeholder
103
+ const loadingMessageId = (Date.now() + 1).toString();
104
+ const loadingMessage: Message = {
105
+ id: loadingMessageId,
106
+ role: 'assistant',
107
+ content: '...',
108
+ timestamp: new Date().toLocaleTimeString(),
109
+ };
110
+ setMessages((prev) => [...prev, loadingMessage]);
111
+
112
+ try {
113
+ if (effectiveEndpoint) {
114
+ // Send to backend API
115
+ const response = await fetch(effectiveEndpoint, {
116
+ method: 'POST',
117
+ headers: {
118
+ 'Content-Type': 'application/json',
119
+ ...effectiveHeaders,
120
+ },
121
+ body: JSON.stringify({
122
+ nodeId,
123
+ nodeType,
124
+ message: currentInput,
125
+ conversationHistory: messages.map((msg) => ({
126
+ role: msg.role,
127
+ content: msg.content,
128
+ })),
129
+ }),
130
+ });
131
+
132
+ if (!response.ok) {
133
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
134
+ }
135
+
136
+ const data = await response.json();
137
+
138
+ // Remove loading message and add actual response
139
+ setMessages((prev) => {
140
+ const filtered = prev.filter((msg) => msg.id !== loadingMessageId);
141
+ return [
142
+ ...filtered,
143
+ {
144
+ id: Date.now().toString(),
145
+ role: 'assistant',
146
+ content: data.message || data.content || data.response || 'No response from AI',
147
+ timestamp: new Date().toLocaleTimeString(),
148
+ },
149
+ ];
150
+ });
151
+ } else {
152
+ // Fallback: Simulate AI response if no API endpoint provided
153
+ setTimeout(() => {
154
+ setMessages((prev) => {
155
+ const filtered = prev.filter((msg) => msg.id !== loadingMessageId);
156
+ return [
157
+ ...filtered,
158
+ {
159
+ id: Date.now().toString(),
160
+ role: 'assistant',
161
+ content: `I understand you're asking about: "${currentInput}". This is specific to your ${nodeType} node (ID: ${nodeId}). Please configure an API endpoint to get real AI responses.`,
162
+ timestamp: new Date().toLocaleTimeString(),
163
+ },
164
+ ];
165
+ });
166
+ setIsLoading(false);
167
+ }, 500);
168
+ return;
169
+ }
170
+ } catch (err) {
171
+ // Remove loading message and show error
172
+ setMessages((prev) => {
173
+ const filtered = prev.filter((msg) => msg.id !== loadingMessageId);
174
+ return [
175
+ ...filtered,
176
+ {
177
+ id: Date.now().toString(),
178
+ role: 'assistant',
179
+ content: `Sorry, I encountered an error: ${err instanceof Error ? err.message : 'Unknown error'}. Please check your API endpoint configuration.`,
180
+ timestamp: new Date().toLocaleTimeString(),
181
+ },
182
+ ];
183
+ });
184
+ setError(err instanceof Error ? err.message : 'Failed to get AI response');
185
+ } finally {
186
+ setIsLoading(false);
187
+ }
188
+ };
189
+
190
+ const handleKeyPress = (e: React.KeyboardEvent) => {
191
+ if (e.key === 'Enter' && !e.shiftKey) {
192
+ e.preventDefault();
193
+ handleSend();
194
+ }
195
+ };
196
+
197
+ return (
198
+ <>
199
+ {/* Backdrop */}
200
+ <Box
201
+ data-backdrop
202
+ onClick={onClose}
203
+ sx={{
204
+ position: 'fixed',
205
+ top: 0,
206
+ left: 0,
207
+ right: 0,
208
+ bottom: 0,
209
+ bgcolor: 'rgba(0, 0, 0, 0.3)',
210
+ zIndex: 9998,
211
+ }}
212
+ />
213
+ {/* Popup */}
214
+ <Card
215
+ ref={popupRef}
216
+ id="node-ai-assistant-popup"
217
+ sx={{
218
+ position: 'fixed',
219
+ left: `${position.x}px`,
220
+ top: `${position.y}px`,
221
+ zIndex: 9999,
222
+ width: '420px',
223
+ height: '600px',
224
+ display: 'flex',
225
+ flexDirection: 'column',
226
+ bgcolor: '#1F2937',
227
+ color: '#fff',
228
+ border: '1px solid #374151',
229
+ borderRadius: '12px',
230
+ boxShadow: '0 20px 25px -5px rgba(0, 0, 0, 0.5), 0 10px 10px -5px rgba(0, 0, 0, 0.2)',
231
+ overflow: 'hidden',
232
+ }}
233
+ >
234
+ {/* Header */}
235
+ <Box
236
+ sx={{
237
+ display: 'flex',
238
+ alignItems: 'center',
239
+ justifyContent: 'space-between',
240
+ p: 2,
241
+ borderBottom: '1px solid #374151',
242
+ bgcolor: '#111827',
243
+ }}
244
+ >
245
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
246
+ <IconMessage size={20} style={{ color: '#3b82f6' }} />
247
+ <Typography
248
+ variant="h6"
249
+ sx={{ color: '#fff', fontSize: '16px', fontWeight: 600 }}
250
+ >
251
+ Node AI Assistant
252
+ </Typography>
253
+ <Typography
254
+ variant="caption"
255
+ sx={{ color: '#9CA3AF', fontSize: '11px', ml: 1 }}
256
+ >
257
+ {nodeType}
258
+ </Typography>
259
+ </Box>
260
+ <IconButton
261
+ aria-label="close"
262
+ onClick={onClose}
263
+ sx={{
264
+ color: '#9CA3AF',
265
+ '&:hover': {
266
+ color: '#fff',
267
+ bgcolor: 'rgba(255, 255, 255, 0.1)',
268
+ },
269
+ }}
270
+ >
271
+ <RiCloseLine />
272
+ </IconButton>
273
+ </Box>
274
+
275
+ {/* Messages */}
276
+ <CardContent
277
+ sx={{
278
+ flex: 1,
279
+ overflow: 'auto',
280
+ p: 2,
281
+ bgcolor: '#1F2937',
282
+ '&::-webkit-scrollbar': {
283
+ width: '6px',
284
+ },
285
+ '&::-webkit-scrollbar-track': {
286
+ background: 'transparent',
287
+ },
288
+ '&::-webkit-scrollbar-thumb': {
289
+ background: '#4B5563',
290
+ borderRadius: '3px',
291
+ '&:hover': {
292
+ background: '#6B7280',
293
+ },
294
+ },
295
+ }}
296
+ >
297
+ {error && (
298
+ <Alert
299
+ severity="error"
300
+ sx={{
301
+ mb: 2,
302
+ bgcolor: '#7F1D1D',
303
+ color: '#FCA5A5',
304
+ '& .MuiAlert-icon': {
305
+ color: '#FCA5A5',
306
+ },
307
+ }}
308
+ onClose={() => setError(null)}
309
+ >
310
+ {error}
311
+ </Alert>
312
+ )}
313
+ {messages.map((message) => (
314
+ <Box
315
+ key={message.id}
316
+ sx={{
317
+ display: 'flex',
318
+ justifyContent: message.role === 'user' ? 'flex-end' : 'flex-start',
319
+ mb: 2,
320
+ }}
321
+ >
322
+ <Box
323
+ sx={{
324
+ maxWidth: '80%',
325
+ p: 1.5,
326
+ borderRadius: '8px',
327
+ bgcolor:
328
+ message.role === 'user' ? '#2563EB' : '#111827',
329
+ border:
330
+ message.role === 'assistant'
331
+ ? '1px solid #374151'
332
+ : 'none',
333
+ display: 'flex',
334
+ alignItems: message.content === '...' ? 'center' : 'flex-start',
335
+ gap: 1,
336
+ }}
337
+ >
338
+ {message.content === '...' && isLoading ? (
339
+ <CircularProgress size={16} sx={{ color: '#3b82f6' }} />
340
+ ) : (
341
+ <Typography
342
+ variant="body2"
343
+ sx={{
344
+ color: '#fff',
345
+ fontSize: '13px',
346
+ lineHeight: 1.5,
347
+ whiteSpace: 'pre-wrap',
348
+ wordBreak: 'break-word',
349
+ }}
350
+ >
351
+ {message.content}
352
+ </Typography>
353
+ )}
354
+ {message.content !== '...' && (
355
+ <Typography
356
+ variant="caption"
357
+ sx={{
358
+ color: 'rgba(255, 255, 255, 0.5)',
359
+ fontSize: '10px',
360
+ display: 'block',
361
+ mt: 0.5,
362
+ alignSelf: 'flex-end',
363
+ }}
364
+ >
365
+ {message.timestamp}
366
+ </Typography>
367
+ )}
368
+ </Box>
369
+ </Box>
370
+ ))}
371
+ <div ref={messagesEndRef} />
372
+ </CardContent>
373
+
374
+ {/* Input */}
375
+ <Box
376
+ sx={{
377
+ p: 2,
378
+ borderTop: '1px solid #374151',
379
+ bgcolor: '#111827',
380
+ }}
381
+ >
382
+ <Box sx={{ display: 'flex', gap: 1 }}>
383
+ <TextField
384
+ fullWidth
385
+ placeholder="Ask your Node AI Assistant..."
386
+ value={inputValue}
387
+ onChange={(e) => setInputValue(e.target.value)}
388
+ onKeyPress={handleKeyPress}
389
+ multiline
390
+ maxRows={3}
391
+ sx={{
392
+ '& .MuiOutlinedInput-root': {
393
+ bgcolor: '#1F2937',
394
+ color: '#fff',
395
+ borderRadius: '8px',
396
+ '& fieldset': {
397
+ borderColor: '#374151',
398
+ },
399
+ '&:hover fieldset': {
400
+ borderColor: '#4B5563',
401
+ },
402
+ '&.Mui-focused fieldset': {
403
+ borderColor: '#3b82f6',
404
+ },
405
+ },
406
+ '& .MuiInputBase-input': {
407
+ color: '#fff',
408
+ fontSize: '13px',
409
+ '&::placeholder': {
410
+ color: '#9CA3AF',
411
+ opacity: 1,
412
+ },
413
+ },
414
+ }}
415
+ />
416
+ <IconButton
417
+ onClick={handleSend}
418
+ disabled={!inputValue.trim() || isLoading}
419
+ sx={{
420
+ bgcolor: '#2563EB',
421
+ color: '#fff',
422
+ '&:hover': {
423
+ bgcolor: '#1d4ed8',
424
+ },
425
+ '&:disabled': {
426
+ bgcolor: '#374151',
427
+ color: '#6B7280',
428
+ },
429
+ }}
430
+ >
431
+ {isLoading ? (
432
+ <CircularProgress size={20} sx={{ color: '#fff' }} />
433
+ ) : (
434
+ <RiSendPlaneFill />
435
+ )}
436
+ </IconButton>
437
+ </Box>
438
+ </Box>
439
+ </Card>
440
+ </>
441
+ );
442
+ };
443
+
444
+ /**
445
+ * Show Node AI Assistant Popup at a specific position
446
+ * @param nodeId - The ID of the node
447
+ * @param nodeType - The type of the node (e.g., 'Start Node', 'API Node')
448
+ * @param buttonElement - The button element that triggered the popup (for positioning)
449
+ * @param apiEndpoint - Optional API endpoint (overrides global config)
450
+ * @param apiHeaders - Optional API headers (overrides global config)
451
+ */
452
+ export const showNodeAIAssistantPopup = (
453
+ nodeId: string,
454
+ nodeType: string,
455
+ buttonElement: HTMLElement,
456
+ apiEndpoint?: string,
457
+ apiHeaders?: Record<string, string>
458
+ ): (() => void) => {
459
+ const portalRoot = document.createElement('div');
460
+ document.body.appendChild(portalRoot);
461
+
462
+ const root = createRoot(portalRoot);
463
+
464
+ // Calculate position relative to button
465
+ const rect = buttonElement.getBoundingClientRect();
466
+ const position = {
467
+ x: rect.right + 12, // Position to the right of the button
468
+ y: rect.top - 50, // Align with button
469
+ };
470
+
471
+ // Adjust if popup would go off screen
472
+ const popupWidth = 420;
473
+ const popupHeight = 600;
474
+ if (position.x + popupWidth > window.innerWidth) {
475
+ position.x = rect.left - popupWidth - 12; // Position to the left instead
476
+ }
477
+ if (position.y + popupHeight > window.innerHeight) {
478
+ position.y = window.innerHeight - popupHeight - 20;
479
+ }
480
+ if (position.y < 0) {
481
+ position.y = 20;
482
+ }
483
+
484
+ const handleClose = () => {
485
+ root.unmount();
486
+ if (portalRoot.parentNode) {
487
+ document.body.removeChild(portalRoot);
488
+ }
489
+ };
490
+
491
+ root.render(
492
+ <NodeAIAssistantPopup
493
+ nodeId={nodeId}
494
+ nodeType={nodeType}
495
+ position={position}
496
+ onClose={handleClose}
497
+ apiEndpoint={apiEndpoint}
498
+ apiHeaders={apiHeaders}
499
+ />
500
+ );
501
+
502
+ return handleClose;
503
+ };
504
+
@@ -0,0 +1,146 @@
1
+ import React from 'react';
2
+ import { Box, IconButton, Tooltip } from '@mui/material';
3
+ import {
4
+ IconLayoutGrid,
5
+ IconMessage,
6
+ IconPlus,
7
+ IconCopy,
8
+ IconTrash,
9
+ } from '@tabler/icons-react';
10
+
11
+ interface NodeActionButtonsProps {
12
+ selected?: boolean;
13
+ onLayout?: () => void;
14
+ onOpenAIAssistant?: (buttonElement: HTMLElement) => void;
15
+ onAddToGroup?: () => void;
16
+ onDuplicate?: () => void;
17
+ onDelete?: () => void;
18
+ }
19
+
20
+ export const NodeActionButtons: React.FC<NodeActionButtonsProps> = ({
21
+ selected,
22
+ onLayout,
23
+ onOpenAIAssistant,
24
+ onAddToGroup,
25
+ onDuplicate,
26
+ onDelete,
27
+ }) => {
28
+ if (!selected) return null;
29
+
30
+ return (
31
+ <Box
32
+ sx={{
33
+ position: 'absolute',
34
+ left: '100%',
35
+ top: '50%',
36
+ transform: 'translateY(-50%)',
37
+ marginLeft: '12px',
38
+ display: 'flex',
39
+ flexDirection: 'column',
40
+ gap: 0.5,
41
+ backgroundColor: 'rgba(15, 15, 35, 0.95)',
42
+ borderRadius: '12px',
43
+ border: '1px solid rgba(255, 255, 255, 0.1)',
44
+ p: 0.5,
45
+ zIndex: 1000,
46
+ boxShadow: '0 4px 20px rgba(0, 0, 0, 0.3)',
47
+ }}
48
+ onClick={(e) => e.stopPropagation()}
49
+ >
50
+ <Tooltip title="Layout" placement="right">
51
+ <IconButton
52
+ size="small"
53
+ onClick={(e) => {
54
+ e.stopPropagation();
55
+ onLayout?.();
56
+ }}
57
+ sx={{
58
+ color: 'rgba(255, 255, 255, 0.6)',
59
+ '&:hover': {
60
+ backgroundColor: 'rgba(255, 255, 255, 0.1)',
61
+ color: '#fff',
62
+ },
63
+ }}
64
+ >
65
+ <IconLayoutGrid size={18} />
66
+ </IconButton>
67
+ </Tooltip>
68
+ <Tooltip title="Node AI Assistant" placement="right">
69
+ <IconButton
70
+ size="small"
71
+ data-node-ai-assistant-button
72
+ onClick={(e) => {
73
+ e.stopPropagation();
74
+ onOpenAIAssistant?.(e.currentTarget);
75
+ }}
76
+ sx={{
77
+ color: 'rgba(255, 255, 255, 0.6)',
78
+ '&:hover': {
79
+ backgroundColor: 'rgba(255, 255, 255, 0.1)',
80
+ color: '#fff',
81
+ },
82
+ }}
83
+ >
84
+ <IconMessage size={18} />
85
+ </IconButton>
86
+ </Tooltip>
87
+ <Tooltip title="Add to Group" placement="right">
88
+ <IconButton
89
+ size="small"
90
+ onClick={(e) => {
91
+ e.stopPropagation();
92
+ onAddToGroup?.();
93
+ }}
94
+ sx={{
95
+ color: 'rgba(255, 255, 255, 0.6)',
96
+ '&:hover': {
97
+ backgroundColor: 'rgba(255, 255, 255, 0.1)',
98
+ color: '#fff',
99
+ },
100
+ }}
101
+ >
102
+ <IconPlus size={18} />
103
+ </IconButton>
104
+ </Tooltip>
105
+ <Tooltip title="Duplicate" placement="right">
106
+ <IconButton
107
+ size="small"
108
+ onClick={(e) => {
109
+ e.stopPropagation();
110
+ onDuplicate?.();
111
+ }}
112
+ sx={{
113
+ color: 'rgba(255, 255, 255, 0.6)',
114
+ '&:hover': {
115
+ backgroundColor: 'rgba(255, 255, 255, 0.1)',
116
+ color: '#fff',
117
+ },
118
+ }}
119
+ >
120
+ <IconCopy size={18} />
121
+ </IconButton>
122
+ </Tooltip>
123
+ <Tooltip title="Delete" placement="right">
124
+ <IconButton
125
+ size="small"
126
+ onClick={(e) => {
127
+ e.stopPropagation();
128
+ onDelete?.();
129
+ }}
130
+ sx={{
131
+ color: 'rgba(255, 255, 255, 0.6)',
132
+ '&:hover': {
133
+ backgroundColor: 'rgba(239, 68, 68, 0.2)',
134
+ color: '#EF4444',
135
+ },
136
+ }}
137
+ >
138
+ <IconTrash size={18} />
139
+ </IconButton>
140
+ </Tooltip>
141
+ </Box>
142
+ );
143
+ };
144
+
145
+ export default NodeActionButtons;
146
+
@@ -1,11 +1,20 @@
1
- export { AutomationStartNode } from './AutomationStartNode';
2
- export { AutomationApiNode } from './AutomationApiNode';
3
- export { AutomationFormattingNode } from './AutomationFormattingNode';
4
- export { AutomationSheetsNode } from './AutomationSheetsNode';
5
- export { AutomationEndNode } from './AutomationEndNode';
6
- export { AutomationNoteNode } from './AutomationNoteNode';
7
- export { AutomationExecutionPanel } from './AutomationExecutionPanel';
8
- export { AutomationAISuggestionNode } from './AutomationAISuggestionNode';
9
- export { AISuggestionsModal, showAISuggestionsModal } from './AISuggestionsModal';
10
- export { AISuggestionsPanel } from './AISuggestionsPanel';
11
- export type { AISuggestion } from './AISuggestionsModal';
1
+ export { AutomationStartNode } from './AutomationStartNode';
2
+ export { AutomationApiNode } from './AutomationApiNode';
3
+ export { AutomationFormattingNode } from './AutomationFormattingNode';
4
+ export { AutomationSheetsNode } from './AutomationSheetsNode';
5
+ export { AutomationEndNode } from './AutomationEndNode';
6
+ export { AutomationNoteNode } from './AutomationNoteNode';
7
+ export { AutomationExecutionPanel } from './AutomationExecutionPanel';
8
+ export { AutomationAISuggestionNode } from './AutomationAISuggestionNode';
9
+ export { AISuggestionsModal, showAISuggestionsModal } from './AISuggestionsModal';
10
+ export { AISuggestionsPanel } from './AISuggestionsPanel';
11
+ export { NodeActionButtons } from './NodeActionButtons';
12
+ export { NodeAIAssistantPopup, showNodeAIAssistantPopup } from './NodeAIAssistantPopup';
13
+ export type { AISuggestion } from './AISuggestionsModal';
14
+ export {
15
+ setNodeAIAssistantEndpoint,
16
+ setNodeAIAssistantHeaders,
17
+ getNodeAIAssistantEndpoint,
18
+ getNodeAIAssistantHeaders,
19
+ configureNodeAIAssistant,
20
+ } from '../../utils/nodeAIAssistantConfig';