@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.
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowuent-org/diagramming-core",
3
- "version": "1.2.4",
3
+ "version": "1.2.5",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -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
  };