@flowuent-org/diagramming-core 1.2.4 → 1.2.5
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 +145 -0
- package/package.json +1 -1
- package/packages/diagrams/src/lib/components/automation/AutomationGoogleServicesNode.tsx +739 -0
- package/packages/diagrams/src/lib/components/automation/index.ts +8 -0
- package/packages/diagrams/src/lib/types/automation-node-data-types.ts +74 -1
- package/packages/diagrams/src/lib/types/node-types.ts +8 -0
|
@@ -525,6 +525,110 @@ export const automationDefaultNodes = [
|
|
|
525
525
|
height: 150,
|
|
526
526
|
measured: { width: 336, height: 150 },
|
|
527
527
|
},
|
|
528
|
+
// =====================================
|
|
529
|
+
// Google Services Nodes
|
|
530
|
+
// =====================================
|
|
531
|
+
{
|
|
532
|
+
id: 'google-docs-node',
|
|
533
|
+
type: 'AutomationGoogleDocsNode',
|
|
534
|
+
position: { x: 100, y: 450 },
|
|
535
|
+
data: {
|
|
536
|
+
label: 'Create Report',
|
|
537
|
+
description: 'Create a Google Docs document',
|
|
538
|
+
serviceType: 'docs',
|
|
539
|
+
status: 'idle',
|
|
540
|
+
isFirstGoogleNode: true, // Only first Google node shows Connect button
|
|
541
|
+
parameters: {
|
|
542
|
+
documentTitle: 'Automation Report',
|
|
543
|
+
operation: 'create',
|
|
544
|
+
content: 'Report content will be generated here...',
|
|
545
|
+
},
|
|
546
|
+
googleAuth: {
|
|
547
|
+
clientId: '',
|
|
548
|
+
scopes: [
|
|
549
|
+
'https://www.googleapis.com/auth/documents',
|
|
550
|
+
'https://www.googleapis.com/auth/drive',
|
|
551
|
+
],
|
|
552
|
+
isAuthenticated: false,
|
|
553
|
+
isLoading: false,
|
|
554
|
+
},
|
|
555
|
+
formData: {
|
|
556
|
+
nodeId: 'google-docs-node',
|
|
557
|
+
title: 'Create Report',
|
|
558
|
+
type: 'navigation',
|
|
559
|
+
serviceType: 'docs',
|
|
560
|
+
},
|
|
561
|
+
},
|
|
562
|
+
width: 300,
|
|
563
|
+
height: 200,
|
|
564
|
+
measured: { width: 300, height: 200 },
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
id: 'gmail-node',
|
|
568
|
+
type: 'AutomationGmailNode',
|
|
569
|
+
position: { x: 450, y: 450 },
|
|
570
|
+
data: {
|
|
571
|
+
label: 'Send Email',
|
|
572
|
+
description: 'Send email via Gmail',
|
|
573
|
+
serviceType: 'gmail',
|
|
574
|
+
status: 'idle',
|
|
575
|
+
isFirstGoogleNode: false, // Not first, no Connect button
|
|
576
|
+
parameters: {
|
|
577
|
+
to: ['recipient@example.com'],
|
|
578
|
+
subject: 'Automation Report',
|
|
579
|
+
body: 'Please find the attached report...',
|
|
580
|
+
},
|
|
581
|
+
googleAuth: {
|
|
582
|
+
clientId: '',
|
|
583
|
+
scopes: [
|
|
584
|
+
'https://www.googleapis.com/auth/gmail.send',
|
|
585
|
+
'https://www.googleapis.com/auth/gmail.compose',
|
|
586
|
+
],
|
|
587
|
+
isAuthenticated: false,
|
|
588
|
+
isLoading: false,
|
|
589
|
+
},
|
|
590
|
+
formData: {
|
|
591
|
+
nodeId: 'gmail-node',
|
|
592
|
+
title: 'Send Email',
|
|
593
|
+
type: 'navigation',
|
|
594
|
+
serviceType: 'gmail',
|
|
595
|
+
},
|
|
596
|
+
},
|
|
597
|
+
width: 300,
|
|
598
|
+
height: 200,
|
|
599
|
+
measured: { width: 300, height: 200 },
|
|
600
|
+
},
|
|
601
|
+
{
|
|
602
|
+
id: 'google-meet-node',
|
|
603
|
+
type: 'AutomationGoogleMeetNode',
|
|
604
|
+
position: { x: 800, y: 450 },
|
|
605
|
+
data: {
|
|
606
|
+
label: 'Create Meeting',
|
|
607
|
+
description: 'Create a Google Meet link',
|
|
608
|
+
serviceType: 'meet',
|
|
609
|
+
status: 'idle',
|
|
610
|
+
isFirstGoogleNode: false, // Not first, no Connect button
|
|
611
|
+
parameters: {
|
|
612
|
+
meetingTitle: 'Project Discussion',
|
|
613
|
+
attendees: ['team@example.com'],
|
|
614
|
+
},
|
|
615
|
+
googleAuth: {
|
|
616
|
+
clientId: '',
|
|
617
|
+
scopes: ['https://www.googleapis.com/auth/meetings.space.created'],
|
|
618
|
+
isAuthenticated: false,
|
|
619
|
+
isLoading: false,
|
|
620
|
+
},
|
|
621
|
+
formData: {
|
|
622
|
+
nodeId: 'google-meet-node',
|
|
623
|
+
title: 'Create Meeting',
|
|
624
|
+
type: 'navigation',
|
|
625
|
+
serviceType: 'meet',
|
|
626
|
+
},
|
|
627
|
+
},
|
|
628
|
+
width: 300,
|
|
629
|
+
height: 200,
|
|
630
|
+
measured: { width: 300, height: 200 },
|
|
631
|
+
},
|
|
528
632
|
];
|
|
529
633
|
|
|
530
634
|
// Default edges for the automation diagram
|
|
@@ -624,4 +728,45 @@ export const automationDefaultEdges = [
|
|
|
624
728
|
color: '#ffffff',
|
|
625
729
|
},
|
|
626
730
|
},
|
|
731
|
+
// =====================================
|
|
732
|
+
// Google Services Node Edges
|
|
733
|
+
// =====================================
|
|
734
|
+
{
|
|
735
|
+
id: 'edge-docs-to-gmail',
|
|
736
|
+
source: 'google-docs-node',
|
|
737
|
+
target: 'gmail-node',
|
|
738
|
+
sourceHandle: 'right',
|
|
739
|
+
targetHandle: 'left',
|
|
740
|
+
data: {
|
|
741
|
+
label: 'Send Report',
|
|
742
|
+
type: 'flow',
|
|
743
|
+
},
|
|
744
|
+
style: {
|
|
745
|
+
stroke: '#4285F4',
|
|
746
|
+
strokeWidth: 2,
|
|
747
|
+
},
|
|
748
|
+
markerEnd: {
|
|
749
|
+
type: MarkerType.ArrowClosed,
|
|
750
|
+
color: '#4285F4',
|
|
751
|
+
},
|
|
752
|
+
},
|
|
753
|
+
{
|
|
754
|
+
id: 'edge-gmail-to-meet',
|
|
755
|
+
source: 'gmail-node',
|
|
756
|
+
target: 'google-meet-node',
|
|
757
|
+
sourceHandle: 'right',
|
|
758
|
+
targetHandle: 'left',
|
|
759
|
+
data: {
|
|
760
|
+
label: 'Schedule Meeting',
|
|
761
|
+
type: 'flow',
|
|
762
|
+
},
|
|
763
|
+
style: {
|
|
764
|
+
stroke: '#EA4335',
|
|
765
|
+
strokeWidth: 2,
|
|
766
|
+
},
|
|
767
|
+
markerEnd: {
|
|
768
|
+
type: MarkerType.ArrowClosed,
|
|
769
|
+
color: '#EA4335',
|
|
770
|
+
},
|
|
771
|
+
},
|
|
627
772
|
];
|
package/package.json
CHANGED
|
@@ -0,0 +1,739 @@
|
|
|
1
|
+
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
|
2
|
+
import { Handle, Position, useNodeId } from '@xyflow/react';
|
|
3
|
+
import {
|
|
4
|
+
Box,
|
|
5
|
+
Typography,
|
|
6
|
+
Chip,
|
|
7
|
+
IconButton,
|
|
8
|
+
Button,
|
|
9
|
+
CircularProgress,
|
|
10
|
+
Tooltip,
|
|
11
|
+
LinearProgress,
|
|
12
|
+
} from '@mui/material';
|
|
13
|
+
import {
|
|
14
|
+
Description as DocsIcon,
|
|
15
|
+
Slideshow as SlidesIcon,
|
|
16
|
+
VideoCall as MeetIcon,
|
|
17
|
+
Email as GmailIcon,
|
|
18
|
+
PlayArrow as PlayIcon,
|
|
19
|
+
Stop as StopIcon,
|
|
20
|
+
CheckCircle as CheckCircleIcon,
|
|
21
|
+
Error as ErrorIcon,
|
|
22
|
+
Settings as SettingsIcon,
|
|
23
|
+
Link as LinkIcon,
|
|
24
|
+
LinkOff as LinkOffIcon,
|
|
25
|
+
Refresh as RefreshIcon,
|
|
26
|
+
TableChart as SheetsIcon,
|
|
27
|
+
CalendarMonth as CalendarIcon,
|
|
28
|
+
} from '@mui/icons-material';
|
|
29
|
+
import { FcGoogle } from 'react-icons/fc';
|
|
30
|
+
import { useTranslation } from 'react-i18next';
|
|
31
|
+
import { useDiagram } from '../../contexts/DiagramProvider';
|
|
32
|
+
import { useSearch } from '../../contexts/SearchContext';
|
|
33
|
+
import { NodeActionButtons } from './NodeActionButtons';
|
|
34
|
+
|
|
35
|
+
// ========================
|
|
36
|
+
// Types / Interfaces
|
|
37
|
+
// ========================
|
|
38
|
+
|
|
39
|
+
export type GoogleServiceType =
|
|
40
|
+
| 'docs'
|
|
41
|
+
| 'slides'
|
|
42
|
+
| 'meet'
|
|
43
|
+
| 'gmail'
|
|
44
|
+
| 'sheets'
|
|
45
|
+
| 'calendar';
|
|
46
|
+
|
|
47
|
+
export type NodeStatus =
|
|
48
|
+
| 'idle'
|
|
49
|
+
| 'authenticating'
|
|
50
|
+
| 'authenticated'
|
|
51
|
+
| 'running'
|
|
52
|
+
| 'completed'
|
|
53
|
+
| 'error';
|
|
54
|
+
|
|
55
|
+
export interface GoogleTokenData {
|
|
56
|
+
accessToken: string;
|
|
57
|
+
expiresAt: number;
|
|
58
|
+
email?: string;
|
|
59
|
+
scope?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface AutomationGoogleServicesNodeData {
|
|
63
|
+
label: string;
|
|
64
|
+
description?: string;
|
|
65
|
+
serviceType: GoogleServiceType;
|
|
66
|
+
status?: NodeStatus;
|
|
67
|
+
iconName?: string;
|
|
68
|
+
|
|
69
|
+
// Flag to indicate if this is the first Google node (show auth button)
|
|
70
|
+
isFirstGoogleNode?: boolean;
|
|
71
|
+
|
|
72
|
+
// Google Auth config
|
|
73
|
+
googleAuth?: {
|
|
74
|
+
clientId?: string;
|
|
75
|
+
scopes?: string[];
|
|
76
|
+
tokens?: GoogleTokenData;
|
|
77
|
+
isAuthenticated?: boolean;
|
|
78
|
+
isLoading?: boolean;
|
|
79
|
+
userEmail?: string;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// Service-specific parameters
|
|
83
|
+
parameters?: {
|
|
84
|
+
// Google Docs
|
|
85
|
+
documentId?: string;
|
|
86
|
+
documentTitle?: string;
|
|
87
|
+
content?: string;
|
|
88
|
+
operation?: 'create' | 'read' | 'update' | 'append';
|
|
89
|
+
|
|
90
|
+
// Google Slides
|
|
91
|
+
presentationId?: string;
|
|
92
|
+
presentationTitle?: string;
|
|
93
|
+
slideContent?: string;
|
|
94
|
+
|
|
95
|
+
// Google Meet
|
|
96
|
+
meetingTitle?: string;
|
|
97
|
+
startTime?: string;
|
|
98
|
+
endTime?: string;
|
|
99
|
+
attendees?: string[];
|
|
100
|
+
|
|
101
|
+
// Gmail
|
|
102
|
+
to?: string[];
|
|
103
|
+
subject?: string;
|
|
104
|
+
body?: string;
|
|
105
|
+
attachments?: string[];
|
|
106
|
+
|
|
107
|
+
// Google Sheets
|
|
108
|
+
spreadsheetId?: string;
|
|
109
|
+
sheetName?: string;
|
|
110
|
+
range?: string;
|
|
111
|
+
data?: any[];
|
|
112
|
+
|
|
113
|
+
// Google Calendar
|
|
114
|
+
calendarId?: string;
|
|
115
|
+
eventTitle?: string;
|
|
116
|
+
eventDescription?: string;
|
|
117
|
+
|
|
118
|
+
// Stored tokens
|
|
119
|
+
useUserAccount?: boolean;
|
|
120
|
+
googleAccessToken?: string;
|
|
121
|
+
googleTokenExpiresAt?: number;
|
|
122
|
+
googleUserEmail?: string;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// Form data for configuration
|
|
126
|
+
formData?: {
|
|
127
|
+
aiSuggestionsCount?: number;
|
|
128
|
+
[key: string]: any;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// Execution result
|
|
132
|
+
executionResult?: {
|
|
133
|
+
success: boolean;
|
|
134
|
+
data?: any;
|
|
135
|
+
error?: string;
|
|
136
|
+
timestamp?: string;
|
|
137
|
+
executionTime?: number;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// Callbacks - passed from parent workflow
|
|
141
|
+
onGoogleLogin?: () => void;
|
|
142
|
+
onGoogleDisconnect?: () => void;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface AutomationGoogleServicesNodeProps {
|
|
146
|
+
data: AutomationGoogleServicesNodeData;
|
|
147
|
+
selected?: boolean;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ========================
|
|
151
|
+
// Constants
|
|
152
|
+
// ========================
|
|
153
|
+
|
|
154
|
+
const SERVICE_CONFIG: Record<
|
|
155
|
+
GoogleServiceType,
|
|
156
|
+
{
|
|
157
|
+
icon: React.ElementType;
|
|
158
|
+
label: string;
|
|
159
|
+
color: string;
|
|
160
|
+
scopes: string[];
|
|
161
|
+
description: string;
|
|
162
|
+
}
|
|
163
|
+
> = {
|
|
164
|
+
docs: {
|
|
165
|
+
icon: DocsIcon,
|
|
166
|
+
label: 'Google Docs',
|
|
167
|
+
color: '#4285F4',
|
|
168
|
+
scopes: [
|
|
169
|
+
'https://www.googleapis.com/auth/documents',
|
|
170
|
+
'https://www.googleapis.com/auth/drive',
|
|
171
|
+
],
|
|
172
|
+
description: 'Create or edit a document',
|
|
173
|
+
},
|
|
174
|
+
slides: {
|
|
175
|
+
icon: SlidesIcon,
|
|
176
|
+
label: 'Google Slides',
|
|
177
|
+
color: '#FBBC04',
|
|
178
|
+
scopes: [
|
|
179
|
+
'https://www.googleapis.com/auth/presentations',
|
|
180
|
+
'https://www.googleapis.com/auth/drive',
|
|
181
|
+
],
|
|
182
|
+
description: 'Create or edit a presentation',
|
|
183
|
+
},
|
|
184
|
+
meet: {
|
|
185
|
+
icon: MeetIcon,
|
|
186
|
+
label: 'Google Meet',
|
|
187
|
+
color: '#34A853',
|
|
188
|
+
scopes: ['https://www.googleapis.com/auth/meetings.space.created'],
|
|
189
|
+
description: 'Create a meeting link',
|
|
190
|
+
},
|
|
191
|
+
gmail: {
|
|
192
|
+
icon: GmailIcon,
|
|
193
|
+
label: 'Gmail',
|
|
194
|
+
color: '#EA4335',
|
|
195
|
+
scopes: [
|
|
196
|
+
'https://www.googleapis.com/auth/gmail.send',
|
|
197
|
+
'https://www.googleapis.com/auth/gmail.compose',
|
|
198
|
+
],
|
|
199
|
+
description: 'Send an email',
|
|
200
|
+
},
|
|
201
|
+
sheets: {
|
|
202
|
+
icon: SheetsIcon,
|
|
203
|
+
label: 'Google Sheets',
|
|
204
|
+
color: '#0F9D58',
|
|
205
|
+
scopes: ['https://www.googleapis.com/auth/spreadsheets'],
|
|
206
|
+
description: 'Read or write spreadsheet data',
|
|
207
|
+
},
|
|
208
|
+
calendar: {
|
|
209
|
+
icon: CalendarIcon,
|
|
210
|
+
label: 'Google Calendar',
|
|
211
|
+
color: '#4285F4',
|
|
212
|
+
scopes: ['https://www.googleapis.com/auth/calendar'],
|
|
213
|
+
description: 'Create a calendar event',
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// ========================
|
|
218
|
+
// Helper Functions
|
|
219
|
+
// ========================
|
|
220
|
+
|
|
221
|
+
const isTokenExpired = (expiresAt?: number): boolean => {
|
|
222
|
+
if (!expiresAt) return true;
|
|
223
|
+
// Token expires 5 minutes before actual expiry for safety
|
|
224
|
+
return Date.now() > expiresAt - 5 * 60 * 1000;
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const getTimeUntilExpiry = (expiresAt?: number): string => {
|
|
228
|
+
if (!expiresAt) return 'N/A';
|
|
229
|
+
const remaining = expiresAt - Date.now();
|
|
230
|
+
if (remaining <= 0) return 'Expired';
|
|
231
|
+
const minutes = Math.floor(remaining / 60000);
|
|
232
|
+
if (minutes < 60) return `${minutes} min`;
|
|
233
|
+
const hours = Math.floor(minutes / 60);
|
|
234
|
+
return `${hours}h ${minutes % 60}m`;
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// ========================
|
|
238
|
+
// Main Component
|
|
239
|
+
// ========================
|
|
240
|
+
|
|
241
|
+
export const AutomationGoogleServicesNode: React.FC<
|
|
242
|
+
AutomationGoogleServicesNodeProps
|
|
243
|
+
> = ({ data, selected }) => {
|
|
244
|
+
const { t } = useTranslation();
|
|
245
|
+
const { highlightText } = useSearch();
|
|
246
|
+
const nodeId = useNodeId();
|
|
247
|
+
const setSelectedNode = useDiagram((state) => state.setSelectedNode);
|
|
248
|
+
const nodes = useDiagram((state) => state.nodes);
|
|
249
|
+
const setNodes = useDiagram((state) => state.setNodes);
|
|
250
|
+
|
|
251
|
+
// ========================
|
|
252
|
+
// State
|
|
253
|
+
// ========================
|
|
254
|
+
const [status, setStatus] = useState<NodeStatus>(data.status || 'idle');
|
|
255
|
+
const [isExecuting, setIsExecuting] = useState(false);
|
|
256
|
+
const [executionProgress, setExecutionProgress] = useState(0);
|
|
257
|
+
const [error, setError] = useState<string | null>(null);
|
|
258
|
+
const [result, setResult] = useState<any>(null);
|
|
259
|
+
|
|
260
|
+
const serviceType = data.serviceType || 'docs';
|
|
261
|
+
const serviceConfig = SERVICE_CONFIG[serviceType];
|
|
262
|
+
const ServiceIcon = serviceConfig.icon;
|
|
263
|
+
|
|
264
|
+
// Check if authenticated from data
|
|
265
|
+
const isAuthenticated = data.googleAuth?.isAuthenticated ||
|
|
266
|
+
(data.parameters?.googleAccessToken && !isTokenExpired(data.parameters?.googleTokenExpiresAt));
|
|
267
|
+
const isAuthLoading = data.googleAuth?.isLoading || false;
|
|
268
|
+
const userEmail = data.googleAuth?.userEmail || data.parameters?.googleUserEmail;
|
|
269
|
+
|
|
270
|
+
// Show Google Auth button only on first Google node
|
|
271
|
+
const showGoogleAuthButton = data.isFirstGoogleNode === true;
|
|
272
|
+
|
|
273
|
+
// ========================
|
|
274
|
+
// Update Node Data
|
|
275
|
+
// ========================
|
|
276
|
+
const updateNodeData = useCallback(
|
|
277
|
+
(updates: Partial<AutomationGoogleServicesNodeData>) => {
|
|
278
|
+
if (!nodeId) return;
|
|
279
|
+
|
|
280
|
+
setNodes(
|
|
281
|
+
nodes.map((node) => {
|
|
282
|
+
if (node.id === nodeId) {
|
|
283
|
+
return {
|
|
284
|
+
...node,
|
|
285
|
+
data: {
|
|
286
|
+
...node.data,
|
|
287
|
+
...updates,
|
|
288
|
+
},
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
return node;
|
|
292
|
+
})
|
|
293
|
+
);
|
|
294
|
+
},
|
|
295
|
+
[nodeId, nodes, setNodes]
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
// ========================
|
|
299
|
+
// Render Status Badge
|
|
300
|
+
// ========================
|
|
301
|
+
const renderStatusBadge = () => {
|
|
302
|
+
const statusConfig: Record<
|
|
303
|
+
NodeStatus,
|
|
304
|
+
{ color: string; bgColor: string; label: string; icon?: React.ReactNode }
|
|
305
|
+
> = {
|
|
306
|
+
idle: {
|
|
307
|
+
color: '#6B7280',
|
|
308
|
+
bgColor: 'rgba(107, 114, 128, 0.1)',
|
|
309
|
+
label: 'Ready',
|
|
310
|
+
},
|
|
311
|
+
authenticating: {
|
|
312
|
+
color: '#F59E0B',
|
|
313
|
+
bgColor: 'rgba(245, 158, 11, 0.1)',
|
|
314
|
+
label: 'Authenticating...',
|
|
315
|
+
icon: <CircularProgress size={12} color="inherit" />,
|
|
316
|
+
},
|
|
317
|
+
authenticated: {
|
|
318
|
+
color: '#10B981',
|
|
319
|
+
bgColor: 'rgba(16, 185, 129, 0.1)',
|
|
320
|
+
label: 'Connected',
|
|
321
|
+
icon: <CheckCircleIcon sx={{ fontSize: 12 }} />,
|
|
322
|
+
},
|
|
323
|
+
running: {
|
|
324
|
+
color: '#3B82F6',
|
|
325
|
+
bgColor: 'rgba(59, 130, 246, 0.1)',
|
|
326
|
+
label: 'Running...',
|
|
327
|
+
icon: <CircularProgress size={12} color="inherit" />,
|
|
328
|
+
},
|
|
329
|
+
completed: {
|
|
330
|
+
color: '#10B981',
|
|
331
|
+
bgColor: 'rgba(16, 185, 129, 0.1)',
|
|
332
|
+
label: 'Success',
|
|
333
|
+
icon: <CheckCircleIcon sx={{ fontSize: 12 }} />,
|
|
334
|
+
},
|
|
335
|
+
error: {
|
|
336
|
+
color: '#EF4444',
|
|
337
|
+
bgColor: 'rgba(239, 68, 68, 0.1)',
|
|
338
|
+
label: 'Error',
|
|
339
|
+
icon: <ErrorIcon sx={{ fontSize: 12 }} />,
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
const currentStatus = isAuthenticated ? 'authenticated' : status;
|
|
344
|
+
const config = statusConfig[currentStatus];
|
|
345
|
+
|
|
346
|
+
return (
|
|
347
|
+
<Chip
|
|
348
|
+
size="small"
|
|
349
|
+
label={
|
|
350
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
|
351
|
+
{config.icon}
|
|
352
|
+
<span>{config.label}</span>
|
|
353
|
+
</Box>
|
|
354
|
+
}
|
|
355
|
+
sx={{
|
|
356
|
+
height: 20,
|
|
357
|
+
fontSize: '10px',
|
|
358
|
+
fontWeight: 600,
|
|
359
|
+
color: config.color,
|
|
360
|
+
bgcolor: config.bgColor,
|
|
361
|
+
border: `1px solid ${config.color}20`,
|
|
362
|
+
'& .MuiChip-label': { px: 1 },
|
|
363
|
+
}}
|
|
364
|
+
/>
|
|
365
|
+
);
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
// ========================
|
|
369
|
+
// Render Google Auth Button
|
|
370
|
+
// ========================
|
|
371
|
+
const renderGoogleAuthButton = () => {
|
|
372
|
+
if (!showGoogleAuthButton) return null;
|
|
373
|
+
|
|
374
|
+
if (isAuthenticated) {
|
|
375
|
+
return (
|
|
376
|
+
<Button
|
|
377
|
+
onClick={data.onGoogleDisconnect}
|
|
378
|
+
disabled={isAuthLoading}
|
|
379
|
+
fullWidth
|
|
380
|
+
sx={{
|
|
381
|
+
backgroundColor: '#10B981',
|
|
382
|
+
border: '1px solid #10B981',
|
|
383
|
+
borderRadius: '8px',
|
|
384
|
+
padding: '8px 12px',
|
|
385
|
+
gap: '8px',
|
|
386
|
+
color: '#FFFFFF',
|
|
387
|
+
textTransform: 'none',
|
|
388
|
+
fontSize: '13px',
|
|
389
|
+
fontWeight: 500,
|
|
390
|
+
'&:hover': {
|
|
391
|
+
backgroundColor: '#059669',
|
|
392
|
+
},
|
|
393
|
+
'&:disabled': {
|
|
394
|
+
opacity: 0.6,
|
|
395
|
+
},
|
|
396
|
+
}}
|
|
397
|
+
>
|
|
398
|
+
<FcGoogle size={18} />
|
|
399
|
+
{userEmail ? userEmail.split('@')[0] : 'Connected'}
|
|
400
|
+
</Button>
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return (
|
|
405
|
+
<Button
|
|
406
|
+
onClick={data.onGoogleLogin}
|
|
407
|
+
disabled={isAuthLoading}
|
|
408
|
+
fullWidth
|
|
409
|
+
sx={{
|
|
410
|
+
backgroundColor: '#171C29',
|
|
411
|
+
border: '1px solid #FFFFFF1A',
|
|
412
|
+
borderRadius: '8px',
|
|
413
|
+
padding: '8px 12px',
|
|
414
|
+
gap: '8px',
|
|
415
|
+
color: '#B2BCD8',
|
|
416
|
+
textTransform: 'none',
|
|
417
|
+
fontSize: '13px',
|
|
418
|
+
fontWeight: 400,
|
|
419
|
+
'&:hover': {
|
|
420
|
+
backgroundColor: '#1e293b',
|
|
421
|
+
borderColor: '#3B82F6',
|
|
422
|
+
},
|
|
423
|
+
'&:disabled': {
|
|
424
|
+
opacity: 0.6,
|
|
425
|
+
},
|
|
426
|
+
}}
|
|
427
|
+
>
|
|
428
|
+
{isAuthLoading ? (
|
|
429
|
+
<>
|
|
430
|
+
<CircularProgress size={16} sx={{ color: '#B2BCD8' }} />
|
|
431
|
+
Connecting...
|
|
432
|
+
</>
|
|
433
|
+
) : (
|
|
434
|
+
<>
|
|
435
|
+
<FcGoogle size={18} />
|
|
436
|
+
Connect Google
|
|
437
|
+
</>
|
|
438
|
+
)}
|
|
439
|
+
</Button>
|
|
440
|
+
);
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
// ========================
|
|
444
|
+
// Render
|
|
445
|
+
// ========================
|
|
446
|
+
return (
|
|
447
|
+
<Box
|
|
448
|
+
sx={{
|
|
449
|
+
bgcolor: '#1E1E2E',
|
|
450
|
+
borderRadius: 2,
|
|
451
|
+
border: selected ? `2px solid ${serviceConfig.color}` : '1px solid #374151',
|
|
452
|
+
minWidth: 280,
|
|
453
|
+
maxWidth: 320,
|
|
454
|
+
overflow: 'hidden',
|
|
455
|
+
boxShadow: selected
|
|
456
|
+
? `0 0 20px ${serviceConfig.color}30`
|
|
457
|
+
: '0 4px 12px rgba(0, 0, 0, 0.3)',
|
|
458
|
+
transition: 'all 0.2s ease',
|
|
459
|
+
}}
|
|
460
|
+
onClick={() => setSelectedNode(nodeId || '')}
|
|
461
|
+
>
|
|
462
|
+
{/* Input Handle */}
|
|
463
|
+
<Handle
|
|
464
|
+
type="target"
|
|
465
|
+
position={Position.Left}
|
|
466
|
+
style={{
|
|
467
|
+
background: serviceConfig.color,
|
|
468
|
+
width: 10,
|
|
469
|
+
height: 10,
|
|
470
|
+
border: '2px solid #1E1E2E',
|
|
471
|
+
}}
|
|
472
|
+
/>
|
|
473
|
+
|
|
474
|
+
{/* Header */}
|
|
475
|
+
<Box
|
|
476
|
+
sx={{
|
|
477
|
+
bgcolor: `${serviceConfig.color}15`,
|
|
478
|
+
borderBottom: `1px solid ${serviceConfig.color}30`,
|
|
479
|
+
px: 2,
|
|
480
|
+
py: 1.5,
|
|
481
|
+
display: 'flex',
|
|
482
|
+
alignItems: 'center',
|
|
483
|
+
justifyContent: 'space-between',
|
|
484
|
+
}}
|
|
485
|
+
>
|
|
486
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
|
|
487
|
+
<Box
|
|
488
|
+
sx={{
|
|
489
|
+
bgcolor: serviceConfig.color,
|
|
490
|
+
borderRadius: 1,
|
|
491
|
+
p: 0.75,
|
|
492
|
+
display: 'flex',
|
|
493
|
+
alignItems: 'center',
|
|
494
|
+
justifyContent: 'center',
|
|
495
|
+
}}
|
|
496
|
+
>
|
|
497
|
+
<ServiceIcon sx={{ fontSize: 18, color: '#fff' }} />
|
|
498
|
+
</Box>
|
|
499
|
+
<Box>
|
|
500
|
+
<Typography
|
|
501
|
+
variant="subtitle2"
|
|
502
|
+
sx={{ color: '#fff', fontWeight: 600, fontSize: 13 }}
|
|
503
|
+
>
|
|
504
|
+
{highlightText(data.label || serviceConfig.label)}
|
|
505
|
+
</Typography>
|
|
506
|
+
<Typography
|
|
507
|
+
variant="caption"
|
|
508
|
+
sx={{ color: '#9CA3AF', fontSize: 10 }}
|
|
509
|
+
>
|
|
510
|
+
{data.description || serviceConfig.description}
|
|
511
|
+
</Typography>
|
|
512
|
+
</Box>
|
|
513
|
+
</Box>
|
|
514
|
+
{renderStatusBadge()}
|
|
515
|
+
</Box>
|
|
516
|
+
|
|
517
|
+
{/* Content */}
|
|
518
|
+
<Box sx={{ px: 2, py: 1.5 }}>
|
|
519
|
+
{/* Google Auth Button - Only on first Google node */}
|
|
520
|
+
{showGoogleAuthButton && (
|
|
521
|
+
<Box sx={{ mb: 1.5 }}>
|
|
522
|
+
{renderGoogleAuthButton()}
|
|
523
|
+
{!isAuthenticated && (
|
|
524
|
+
<Typography
|
|
525
|
+
variant="caption"
|
|
526
|
+
sx={{
|
|
527
|
+
color: '#6B7280',
|
|
528
|
+
fontSize: 10,
|
|
529
|
+
display: 'block',
|
|
530
|
+
textAlign: 'center',
|
|
531
|
+
mt: 0.5,
|
|
532
|
+
}}
|
|
533
|
+
>
|
|
534
|
+
Click to authenticate with Google
|
|
535
|
+
</Typography>
|
|
536
|
+
)}
|
|
537
|
+
</Box>
|
|
538
|
+
)}
|
|
539
|
+
|
|
540
|
+
{/* Authentication Status for non-first nodes */}
|
|
541
|
+
{!showGoogleAuthButton && (
|
|
542
|
+
<Box
|
|
543
|
+
sx={{
|
|
544
|
+
bgcolor: isAuthenticated ? 'rgba(16, 185, 129, 0.1)' : 'rgba(107, 114, 128, 0.1)',
|
|
545
|
+
borderRadius: 1,
|
|
546
|
+
p: 1.5,
|
|
547
|
+
mb: 1.5,
|
|
548
|
+
}}
|
|
549
|
+
>
|
|
550
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
551
|
+
<FcGoogle size={18} style={{ opacity: isAuthenticated ? 1 : 0.5 }} />
|
|
552
|
+
<Box>
|
|
553
|
+
<Typography
|
|
554
|
+
variant="caption"
|
|
555
|
+
sx={{
|
|
556
|
+
color: isAuthenticated ? '#10B981' : '#6B7280',
|
|
557
|
+
display: 'block',
|
|
558
|
+
fontWeight: 600,
|
|
559
|
+
fontSize: 11,
|
|
560
|
+
}}
|
|
561
|
+
>
|
|
562
|
+
{isAuthenticated ? 'Google Connected' : 'Google Not Connected'}
|
|
563
|
+
</Typography>
|
|
564
|
+
{isAuthenticated && userEmail && (
|
|
565
|
+
<Typography
|
|
566
|
+
variant="caption"
|
|
567
|
+
sx={{ color: '#9CA3AF', fontSize: 10 }}
|
|
568
|
+
>
|
|
569
|
+
{userEmail}
|
|
570
|
+
</Typography>
|
|
571
|
+
)}
|
|
572
|
+
</Box>
|
|
573
|
+
</Box>
|
|
574
|
+
</Box>
|
|
575
|
+
)}
|
|
576
|
+
|
|
577
|
+
{/* Progress Bar (when running) */}
|
|
578
|
+
{status === 'running' && (
|
|
579
|
+
<Box sx={{ mb: 1.5 }}>
|
|
580
|
+
<LinearProgress
|
|
581
|
+
variant="determinate"
|
|
582
|
+
value={executionProgress}
|
|
583
|
+
sx={{
|
|
584
|
+
height: 4,
|
|
585
|
+
borderRadius: 2,
|
|
586
|
+
bgcolor: 'rgba(59, 130, 246, 0.1)',
|
|
587
|
+
'& .MuiLinearProgress-bar': {
|
|
588
|
+
bgcolor: serviceConfig.color,
|
|
589
|
+
},
|
|
590
|
+
}}
|
|
591
|
+
/>
|
|
592
|
+
<Typography
|
|
593
|
+
variant="caption"
|
|
594
|
+
sx={{ color: '#9CA3AF', fontSize: 10, mt: 0.5, display: 'block' }}
|
|
595
|
+
>
|
|
596
|
+
Executing... {executionProgress}%
|
|
597
|
+
</Typography>
|
|
598
|
+
</Box>
|
|
599
|
+
)}
|
|
600
|
+
|
|
601
|
+
{/* Error Display */}
|
|
602
|
+
{error && (
|
|
603
|
+
<Box
|
|
604
|
+
sx={{
|
|
605
|
+
bgcolor: 'rgba(239, 68, 68, 0.1)',
|
|
606
|
+
borderRadius: 1,
|
|
607
|
+
p: 1,
|
|
608
|
+
mb: 1.5,
|
|
609
|
+
border: '1px solid rgba(239, 68, 68, 0.3)',
|
|
610
|
+
}}
|
|
611
|
+
>
|
|
612
|
+
<Typography
|
|
613
|
+
variant="caption"
|
|
614
|
+
sx={{ color: '#EF4444', fontSize: 10, display: 'block' }}
|
|
615
|
+
>
|
|
616
|
+
{error}
|
|
617
|
+
</Typography>
|
|
618
|
+
</Box>
|
|
619
|
+
)}
|
|
620
|
+
|
|
621
|
+
{/* Result Display (when completed) */}
|
|
622
|
+
{status === 'completed' && result && (
|
|
623
|
+
<Box
|
|
624
|
+
sx={{
|
|
625
|
+
bgcolor: 'rgba(16, 185, 129, 0.1)',
|
|
626
|
+
borderRadius: 1,
|
|
627
|
+
p: 1,
|
|
628
|
+
mb: 1.5,
|
|
629
|
+
border: '1px solid rgba(16, 185, 129, 0.3)',
|
|
630
|
+
}}
|
|
631
|
+
>
|
|
632
|
+
<Typography
|
|
633
|
+
variant="caption"
|
|
634
|
+
sx={{ color: '#10B981', fontSize: 10, display: 'block', fontWeight: 600 }}
|
|
635
|
+
>
|
|
636
|
+
✓ Executed successfully
|
|
637
|
+
</Typography>
|
|
638
|
+
{result.documentId && (
|
|
639
|
+
<Typography
|
|
640
|
+
variant="caption"
|
|
641
|
+
sx={{ color: '#9CA3AF', fontSize: 10 }}
|
|
642
|
+
>
|
|
643
|
+
Doc ID: {result.documentId}
|
|
644
|
+
</Typography>
|
|
645
|
+
)}
|
|
646
|
+
{result.presentationId && (
|
|
647
|
+
<Typography
|
|
648
|
+
variant="caption"
|
|
649
|
+
sx={{ color: '#9CA3AF', fontSize: 10 }}
|
|
650
|
+
>
|
|
651
|
+
Presentation ID: {result.presentationId}
|
|
652
|
+
</Typography>
|
|
653
|
+
)}
|
|
654
|
+
{result.meetingUri && (
|
|
655
|
+
<Typography
|
|
656
|
+
variant="caption"
|
|
657
|
+
sx={{ color: '#9CA3AF', fontSize: 10 }}
|
|
658
|
+
>
|
|
659
|
+
Meet Link: {result.meetingUri}
|
|
660
|
+
</Typography>
|
|
661
|
+
)}
|
|
662
|
+
</Box>
|
|
663
|
+
)}
|
|
664
|
+
|
|
665
|
+
{/* Service-specific details */}
|
|
666
|
+
<Box
|
|
667
|
+
sx={{
|
|
668
|
+
bgcolor: 'rgba(255, 255, 255, 0.03)',
|
|
669
|
+
borderRadius: 1,
|
|
670
|
+
p: 1,
|
|
671
|
+
}}
|
|
672
|
+
>
|
|
673
|
+
{serviceType === 'docs' && data.parameters?.documentTitle && (
|
|
674
|
+
<Typography variant="caption" sx={{ color: '#9CA3AF', fontSize: 10 }}>
|
|
675
|
+
Document: {data.parameters.documentTitle}
|
|
676
|
+
</Typography>
|
|
677
|
+
)}
|
|
678
|
+
{serviceType === 'gmail' && data.parameters?.subject && (
|
|
679
|
+
<Typography variant="caption" sx={{ color: '#9CA3AF', fontSize: 10 }}>
|
|
680
|
+
Subject: {data.parameters.subject}
|
|
681
|
+
</Typography>
|
|
682
|
+
)}
|
|
683
|
+
{serviceType === 'meet' && data.parameters?.meetingTitle && (
|
|
684
|
+
<Typography variant="caption" sx={{ color: '#9CA3AF', fontSize: 10 }}>
|
|
685
|
+
Meeting: {data.parameters.meetingTitle}
|
|
686
|
+
</Typography>
|
|
687
|
+
)}
|
|
688
|
+
{serviceType === 'slides' && data.parameters?.presentationTitle && (
|
|
689
|
+
<Typography variant="caption" sx={{ color: '#9CA3AF', fontSize: 10 }}>
|
|
690
|
+
Presentation: {data.parameters.presentationTitle}
|
|
691
|
+
</Typography>
|
|
692
|
+
)}
|
|
693
|
+
{serviceType === 'calendar' && data.parameters?.eventTitle && (
|
|
694
|
+
<Typography variant="caption" sx={{ color: '#9CA3AF', fontSize: 10 }}>
|
|
695
|
+
Event: {data.parameters.eventTitle}
|
|
696
|
+
</Typography>
|
|
697
|
+
)}
|
|
698
|
+
{serviceType === 'sheets' && data.parameters?.sheetName && (
|
|
699
|
+
<Typography variant="caption" sx={{ color: '#9CA3AF', fontSize: 10 }}>
|
|
700
|
+
Sheet: {data.parameters.sheetName}
|
|
701
|
+
</Typography>
|
|
702
|
+
)}
|
|
703
|
+
{!data.parameters?.documentTitle &&
|
|
704
|
+
!data.parameters?.subject &&
|
|
705
|
+
!data.parameters?.meetingTitle &&
|
|
706
|
+
!data.parameters?.presentationTitle &&
|
|
707
|
+
!data.parameters?.eventTitle &&
|
|
708
|
+
!data.parameters?.sheetName && (
|
|
709
|
+
<Typography variant="caption" sx={{ color: '#6B7280', fontSize: 10, fontStyle: 'italic' }}>
|
|
710
|
+
Configure node parameters
|
|
711
|
+
</Typography>
|
|
712
|
+
)}
|
|
713
|
+
</Box>
|
|
714
|
+
</Box>
|
|
715
|
+
|
|
716
|
+
{/* Node Action Buttons */}
|
|
717
|
+
<NodeActionButtons
|
|
718
|
+
nodeId={nodeId || ''}
|
|
719
|
+
showSettings
|
|
720
|
+
onSettings={() => setSelectedNode(nodeId || '')}
|
|
721
|
+
/>
|
|
722
|
+
|
|
723
|
+
{/* Output Handle */}
|
|
724
|
+
<Handle
|
|
725
|
+
type="source"
|
|
726
|
+
position={Position.Right}
|
|
727
|
+
style={{
|
|
728
|
+
background: serviceConfig.color,
|
|
729
|
+
width: 10,
|
|
730
|
+
height: 10,
|
|
731
|
+
border: '2px solid #1E1E2E',
|
|
732
|
+
}}
|
|
733
|
+
/>
|
|
734
|
+
</Box>
|
|
735
|
+
);
|
|
736
|
+
};
|
|
737
|
+
|
|
738
|
+
export default AutomationGoogleServicesNode;
|
|
739
|
+
|
|
@@ -12,6 +12,14 @@ export { AISuggestionsPanel } from './AISuggestionsPanel';
|
|
|
12
12
|
export { NodeActionButtons } from './NodeActionButtons';
|
|
13
13
|
export { NodeAIAssistantPopup, showNodeAIAssistantPopup } from './NodeAIAssistantPopup';
|
|
14
14
|
export type { AISuggestion } from './AISuggestionsModal';
|
|
15
|
+
// Google Services Node (Docs, Slides, Meet, Gmail, Sheets, Calendar)
|
|
16
|
+
export { AutomationGoogleServicesNode } from './AutomationGoogleServicesNode';
|
|
17
|
+
export type {
|
|
18
|
+
GoogleServiceType,
|
|
19
|
+
GoogleTokenData,
|
|
20
|
+
AutomationGoogleServicesNodeData,
|
|
21
|
+
AutomationGoogleServicesNodeProps,
|
|
22
|
+
} from './AutomationGoogleServicesNode';
|
|
15
23
|
export {
|
|
16
24
|
setNodeAIAssistantEndpoint,
|
|
17
25
|
setNodeAIAssistantHeaders,
|
|
@@ -264,13 +264,86 @@ export interface AutomationNavigationNodeForm extends AutomationNodeFormData {
|
|
|
264
264
|
};
|
|
265
265
|
}
|
|
266
266
|
|
|
267
|
+
// Google Services Node Form
|
|
268
|
+
export type GoogleServiceType =
|
|
269
|
+
| 'docs'
|
|
270
|
+
| 'slides'
|
|
271
|
+
| 'meet'
|
|
272
|
+
| 'gmail'
|
|
273
|
+
| 'sheets'
|
|
274
|
+
| 'calendar';
|
|
275
|
+
|
|
276
|
+
export interface AutomationGoogleServicesNodeForm extends AutomationNodeFormData {
|
|
277
|
+
type: 'start' | 'api' | 'formatting' | 'sheets' | 'end' | 'navigation';
|
|
278
|
+
serviceType: GoogleServiceType;
|
|
279
|
+
|
|
280
|
+
// Flag to show Google auth button only on first node
|
|
281
|
+
isFirstGoogleNode?: boolean;
|
|
282
|
+
|
|
283
|
+
// Google Auth Configuration
|
|
284
|
+
googleAuth?: {
|
|
285
|
+
clientId?: string;
|
|
286
|
+
scopes?: string[];
|
|
287
|
+
accessToken?: string;
|
|
288
|
+
expiresAt?: number;
|
|
289
|
+
userEmail?: string;
|
|
290
|
+
isAuthenticated?: boolean;
|
|
291
|
+
isLoading?: boolean;
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
// Service-specific Parameters
|
|
295
|
+
// Google Docs
|
|
296
|
+
documentId?: string;
|
|
297
|
+
documentTitle?: string;
|
|
298
|
+
documentContent?: string;
|
|
299
|
+
documentOperation?: 'create' | 'read' | 'update' | 'append';
|
|
300
|
+
|
|
301
|
+
// Google Slides
|
|
302
|
+
presentationId?: string;
|
|
303
|
+
presentationTitle?: string;
|
|
304
|
+
slideContent?: string;
|
|
305
|
+
|
|
306
|
+
// Google Meet
|
|
307
|
+
meetingTitle?: string;
|
|
308
|
+
meetingStartTime?: string;
|
|
309
|
+
meetingEndTime?: string;
|
|
310
|
+
meetingAttendees?: string[];
|
|
311
|
+
|
|
312
|
+
// Gmail
|
|
313
|
+
emailTo?: string[];
|
|
314
|
+
emailSubject?: string;
|
|
315
|
+
emailBody?: string;
|
|
316
|
+
emailAttachments?: string[];
|
|
317
|
+
|
|
318
|
+
// Google Sheets
|
|
319
|
+
spreadsheetId?: string;
|
|
320
|
+
sheetName?: string;
|
|
321
|
+
sheetRange?: string;
|
|
322
|
+
sheetData?: any[];
|
|
323
|
+
|
|
324
|
+
// Google Calendar
|
|
325
|
+
calendarId?: string;
|
|
326
|
+
eventTitle?: string;
|
|
327
|
+
eventDescription?: string;
|
|
328
|
+
eventStartTime?: string;
|
|
329
|
+
eventEndTime?: string;
|
|
330
|
+
|
|
331
|
+
// Output
|
|
332
|
+
outputVariable?: string;
|
|
333
|
+
|
|
334
|
+
// Callbacks
|
|
335
|
+
onGoogleLogin?: () => void;
|
|
336
|
+
onGoogleDisconnect?: () => void;
|
|
337
|
+
}
|
|
338
|
+
|
|
267
339
|
export type AutomationNodeForm =
|
|
268
340
|
| AutomationStartNodeForm
|
|
269
341
|
| AutomationApiNodeForm
|
|
270
342
|
| AutomationFormattingNodeForm
|
|
271
343
|
| AutomationSheetsNodeForm
|
|
272
344
|
| AutomationEndNodeForm
|
|
273
|
-
| AutomationNavigationNodeForm
|
|
345
|
+
| AutomationNavigationNodeForm
|
|
346
|
+
| AutomationGoogleServicesNodeForm;
|
|
274
347
|
|
|
275
348
|
// Automation Workflow Data Structure
|
|
276
349
|
export interface AutomationWorkflowData {
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
AutomationNoteNode,
|
|
13
13
|
AutomationNavigationNode,
|
|
14
14
|
AutomationAISuggestionNode,
|
|
15
|
+
AutomationGoogleServicesNode,
|
|
15
16
|
} from '../components/automation';
|
|
16
17
|
|
|
17
18
|
export default {
|
|
@@ -28,4 +29,11 @@ export default {
|
|
|
28
29
|
AutomationNoteNode,
|
|
29
30
|
AutomationNavigationNode,
|
|
30
31
|
AutomationAISuggestionNode,
|
|
32
|
+
// Google Services Nodes
|
|
33
|
+
AutomationGoogleServicesNode,
|
|
34
|
+
AutomationGoogleDocsNode: AutomationGoogleServicesNode,
|
|
35
|
+
AutomationGoogleSlidesNode: AutomationGoogleServicesNode,
|
|
36
|
+
AutomationGoogleMeetNode: AutomationGoogleServicesNode,
|
|
37
|
+
AutomationGmailNode: AutomationGoogleServicesNode,
|
|
38
|
+
AutomationGoogleCalendarNode: AutomationGoogleServicesNode,
|
|
31
39
|
};
|