@flowuent-org/diagramming-core 1.2.3 → 1.2.4
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 +0 -217
- package/package.json +1 -1
- package/packages/diagrams/src/lib/components/automation/index.ts +0 -8
- package/packages/diagrams/src/lib/types/automation-node-data-types.ts +1 -65
- package/packages/diagrams/src/lib/types/node-types.ts +0 -8
- package/packages/diagrams/src/lib/components/automation/AutomationGoogleServicesNode.tsx +0 -1021
|
@@ -525,163 +525,6 @@ 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: 'Google Docs document එකක් create කරන්න',
|
|
538
|
-
serviceType: 'docs',
|
|
539
|
-
status: 'idle',
|
|
540
|
-
parameters: {
|
|
541
|
-
documentTitle: 'Automation Report',
|
|
542
|
-
operation: 'create',
|
|
543
|
-
content: 'Report content will be generated here...',
|
|
544
|
-
},
|
|
545
|
-
googleAuth: {
|
|
546
|
-
clientId: '', // Will be set from environment
|
|
547
|
-
scopes: [
|
|
548
|
-
'https://www.googleapis.com/auth/documents',
|
|
549
|
-
'https://www.googleapis.com/auth/drive',
|
|
550
|
-
],
|
|
551
|
-
},
|
|
552
|
-
formData: {
|
|
553
|
-
nodeId: 'google-docs-node',
|
|
554
|
-
title: 'Create Report',
|
|
555
|
-
type: 'navigation',
|
|
556
|
-
serviceType: 'docs',
|
|
557
|
-
},
|
|
558
|
-
},
|
|
559
|
-
width: 300,
|
|
560
|
-
height: 200,
|
|
561
|
-
measured: { width: 300, height: 200 },
|
|
562
|
-
},
|
|
563
|
-
{
|
|
564
|
-
id: 'gmail-node',
|
|
565
|
-
type: 'AutomationGmailNode',
|
|
566
|
-
position: { x: 450, y: 450 },
|
|
567
|
-
data: {
|
|
568
|
-
label: 'Send Email',
|
|
569
|
-
description: 'Gmail හරහා email එකක් send කරන්න',
|
|
570
|
-
serviceType: 'gmail',
|
|
571
|
-
status: 'idle',
|
|
572
|
-
parameters: {
|
|
573
|
-
to: ['recipient@example.com'],
|
|
574
|
-
subject: 'Automation Report',
|
|
575
|
-
body: 'Please find the attached report...',
|
|
576
|
-
},
|
|
577
|
-
googleAuth: {
|
|
578
|
-
clientId: '',
|
|
579
|
-
scopes: [
|
|
580
|
-
'https://www.googleapis.com/auth/gmail.send',
|
|
581
|
-
'https://www.googleapis.com/auth/gmail.compose',
|
|
582
|
-
],
|
|
583
|
-
},
|
|
584
|
-
formData: {
|
|
585
|
-
nodeId: 'gmail-node',
|
|
586
|
-
title: 'Send Email',
|
|
587
|
-
type: 'navigation',
|
|
588
|
-
serviceType: 'gmail',
|
|
589
|
-
},
|
|
590
|
-
},
|
|
591
|
-
width: 300,
|
|
592
|
-
height: 200,
|
|
593
|
-
measured: { width: 300, height: 200 },
|
|
594
|
-
},
|
|
595
|
-
{
|
|
596
|
-
id: 'google-meet-node',
|
|
597
|
-
type: 'AutomationGoogleMeetNode',
|
|
598
|
-
position: { x: 800, y: 450 },
|
|
599
|
-
data: {
|
|
600
|
-
label: 'Create Meeting',
|
|
601
|
-
description: 'Google Meet link එකක් create කරන්න',
|
|
602
|
-
serviceType: 'meet',
|
|
603
|
-
status: 'idle',
|
|
604
|
-
parameters: {
|
|
605
|
-
meetingTitle: 'Project Discussion',
|
|
606
|
-
attendees: ['team@example.com'],
|
|
607
|
-
},
|
|
608
|
-
googleAuth: {
|
|
609
|
-
clientId: '',
|
|
610
|
-
scopes: ['https://www.googleapis.com/auth/meetings.space.created'],
|
|
611
|
-
},
|
|
612
|
-
formData: {
|
|
613
|
-
nodeId: 'google-meet-node',
|
|
614
|
-
title: 'Create Meeting',
|
|
615
|
-
type: 'navigation',
|
|
616
|
-
serviceType: 'meet',
|
|
617
|
-
},
|
|
618
|
-
},
|
|
619
|
-
width: 300,
|
|
620
|
-
height: 200,
|
|
621
|
-
measured: { width: 300, height: 200 },
|
|
622
|
-
},
|
|
623
|
-
{
|
|
624
|
-
id: 'google-slides-node',
|
|
625
|
-
type: 'AutomationGoogleSlidesNode',
|
|
626
|
-
position: { x: 100, y: 700 },
|
|
627
|
-
data: {
|
|
628
|
-
label: 'Create Presentation',
|
|
629
|
-
description: 'Google Slides presentation එකක් create කරන්න',
|
|
630
|
-
serviceType: 'slides',
|
|
631
|
-
status: 'idle',
|
|
632
|
-
parameters: {
|
|
633
|
-
presentationTitle: 'Monthly Report',
|
|
634
|
-
slideContent: 'Slide content here...',
|
|
635
|
-
},
|
|
636
|
-
googleAuth: {
|
|
637
|
-
clientId: '',
|
|
638
|
-
scopes: [
|
|
639
|
-
'https://www.googleapis.com/auth/presentations',
|
|
640
|
-
'https://www.googleapis.com/auth/drive',
|
|
641
|
-
],
|
|
642
|
-
},
|
|
643
|
-
formData: {
|
|
644
|
-
nodeId: 'google-slides-node',
|
|
645
|
-
title: 'Create Presentation',
|
|
646
|
-
type: 'navigation',
|
|
647
|
-
serviceType: 'slides',
|
|
648
|
-
},
|
|
649
|
-
},
|
|
650
|
-
width: 300,
|
|
651
|
-
height: 200,
|
|
652
|
-
measured: { width: 300, height: 200 },
|
|
653
|
-
},
|
|
654
|
-
{
|
|
655
|
-
id: 'google-calendar-node',
|
|
656
|
-
type: 'AutomationGoogleCalendarNode',
|
|
657
|
-
position: { x: 450, y: 700 },
|
|
658
|
-
data: {
|
|
659
|
-
label: 'Create Event',
|
|
660
|
-
description: 'Google Calendar event එකක් create කරන්න',
|
|
661
|
-
serviceType: 'calendar',
|
|
662
|
-
status: 'idle',
|
|
663
|
-
parameters: {
|
|
664
|
-
calendarId: 'primary',
|
|
665
|
-
eventTitle: 'Team Meeting',
|
|
666
|
-
eventDescription: 'Weekly sync up',
|
|
667
|
-
startTime: new Date().toISOString(),
|
|
668
|
-
endTime: new Date(Date.now() + 3600000).toISOString(),
|
|
669
|
-
},
|
|
670
|
-
googleAuth: {
|
|
671
|
-
clientId: '',
|
|
672
|
-
scopes: ['https://www.googleapis.com/auth/calendar'],
|
|
673
|
-
},
|
|
674
|
-
formData: {
|
|
675
|
-
nodeId: 'google-calendar-node',
|
|
676
|
-
title: 'Create Event',
|
|
677
|
-
type: 'navigation',
|
|
678
|
-
serviceType: 'calendar',
|
|
679
|
-
},
|
|
680
|
-
},
|
|
681
|
-
width: 300,
|
|
682
|
-
height: 200,
|
|
683
|
-
measured: { width: 300, height: 200 },
|
|
684
|
-
},
|
|
685
528
|
];
|
|
686
529
|
|
|
687
530
|
// Default edges for the automation diagram
|
|
@@ -781,64 +624,4 @@ export const automationDefaultEdges = [
|
|
|
781
624
|
color: '#ffffff',
|
|
782
625
|
},
|
|
783
626
|
},
|
|
784
|
-
// =====================================
|
|
785
|
-
// Google Services Node Edges
|
|
786
|
-
// =====================================
|
|
787
|
-
{
|
|
788
|
-
id: 'edge-docs-to-gmail',
|
|
789
|
-
source: 'google-docs-node',
|
|
790
|
-
target: 'gmail-node',
|
|
791
|
-
sourceHandle: 'right',
|
|
792
|
-
targetHandle: 'left',
|
|
793
|
-
data: {
|
|
794
|
-
label: 'Send Report',
|
|
795
|
-
type: 'flow',
|
|
796
|
-
},
|
|
797
|
-
style: {
|
|
798
|
-
stroke: '#4285F4',
|
|
799
|
-
strokeWidth: 2,
|
|
800
|
-
},
|
|
801
|
-
markerEnd: {
|
|
802
|
-
type: MarkerType.ArrowClosed,
|
|
803
|
-
color: '#4285F4',
|
|
804
|
-
},
|
|
805
|
-
},
|
|
806
|
-
{
|
|
807
|
-
id: 'edge-gmail-to-meet',
|
|
808
|
-
source: 'gmail-node',
|
|
809
|
-
target: 'google-meet-node',
|
|
810
|
-
sourceHandle: 'right',
|
|
811
|
-
targetHandle: 'left',
|
|
812
|
-
data: {
|
|
813
|
-
label: 'Schedule Meet',
|
|
814
|
-
type: 'flow',
|
|
815
|
-
},
|
|
816
|
-
style: {
|
|
817
|
-
stroke: '#EA4335',
|
|
818
|
-
strokeWidth: 2,
|
|
819
|
-
},
|
|
820
|
-
markerEnd: {
|
|
821
|
-
type: MarkerType.ArrowClosed,
|
|
822
|
-
color: '#EA4335',
|
|
823
|
-
},
|
|
824
|
-
},
|
|
825
|
-
{
|
|
826
|
-
id: 'edge-slides-to-calendar',
|
|
827
|
-
source: 'google-slides-node',
|
|
828
|
-
target: 'google-calendar-node',
|
|
829
|
-
sourceHandle: 'right',
|
|
830
|
-
targetHandle: 'left',
|
|
831
|
-
data: {
|
|
832
|
-
label: 'Add to Calendar',
|
|
833
|
-
type: 'flow',
|
|
834
|
-
},
|
|
835
|
-
style: {
|
|
836
|
-
stroke: '#FBBC04',
|
|
837
|
-
strokeWidth: 2,
|
|
838
|
-
},
|
|
839
|
-
markerEnd: {
|
|
840
|
-
type: MarkerType.ArrowClosed,
|
|
841
|
-
color: '#FBBC04',
|
|
842
|
-
},
|
|
843
|
-
},
|
|
844
627
|
];
|
package/package.json
CHANGED
|
@@ -12,14 +12,6 @@ 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';
|
|
23
15
|
export {
|
|
24
16
|
setNodeAIAssistantEndpoint,
|
|
25
17
|
setNodeAIAssistantHeaders,
|
|
@@ -264,77 +264,13 @@ 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
|
-
// Google Auth Configuration
|
|
281
|
-
googleAuth?: {
|
|
282
|
-
clientId?: string;
|
|
283
|
-
scopes?: string[];
|
|
284
|
-
accessToken?: string;
|
|
285
|
-
expiresAt?: number;
|
|
286
|
-
userEmail?: string;
|
|
287
|
-
};
|
|
288
|
-
|
|
289
|
-
// Service-specific Parameters
|
|
290
|
-
// Google Docs
|
|
291
|
-
documentId?: string;
|
|
292
|
-
documentTitle?: string;
|
|
293
|
-
documentContent?: string;
|
|
294
|
-
documentOperation?: 'create' | 'read' | 'update' | 'append';
|
|
295
|
-
|
|
296
|
-
// Google Slides
|
|
297
|
-
presentationId?: string;
|
|
298
|
-
presentationTitle?: string;
|
|
299
|
-
slideContent?: string;
|
|
300
|
-
|
|
301
|
-
// Google Meet
|
|
302
|
-
meetingTitle?: string;
|
|
303
|
-
meetingStartTime?: string;
|
|
304
|
-
meetingEndTime?: string;
|
|
305
|
-
meetingAttendees?: string[];
|
|
306
|
-
|
|
307
|
-
// Gmail
|
|
308
|
-
emailTo?: string[];
|
|
309
|
-
emailSubject?: string;
|
|
310
|
-
emailBody?: string;
|
|
311
|
-
emailAttachments?: string[];
|
|
312
|
-
|
|
313
|
-
// Google Sheets
|
|
314
|
-
spreadsheetId?: string;
|
|
315
|
-
sheetName?: string;
|
|
316
|
-
sheetRange?: string;
|
|
317
|
-
sheetData?: any[];
|
|
318
|
-
|
|
319
|
-
// Google Calendar
|
|
320
|
-
calendarId?: string;
|
|
321
|
-
eventTitle?: string;
|
|
322
|
-
eventDescription?: string;
|
|
323
|
-
eventStartTime?: string;
|
|
324
|
-
eventEndTime?: string;
|
|
325
|
-
|
|
326
|
-
// Output
|
|
327
|
-
outputVariable?: string;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
267
|
export type AutomationNodeForm =
|
|
331
268
|
| AutomationStartNodeForm
|
|
332
269
|
| AutomationApiNodeForm
|
|
333
270
|
| AutomationFormattingNodeForm
|
|
334
271
|
| AutomationSheetsNodeForm
|
|
335
272
|
| AutomationEndNodeForm
|
|
336
|
-
| AutomationNavigationNodeForm
|
|
337
|
-
| AutomationGoogleServicesNodeForm;
|
|
273
|
+
| AutomationNavigationNodeForm;
|
|
338
274
|
|
|
339
275
|
// Automation Workflow Data Structure
|
|
340
276
|
export interface AutomationWorkflowData {
|
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
AutomationNoteNode,
|
|
13
13
|
AutomationNavigationNode,
|
|
14
14
|
AutomationAISuggestionNode,
|
|
15
|
-
AutomationGoogleServicesNode,
|
|
16
15
|
} from '../components/automation';
|
|
17
16
|
|
|
18
17
|
export default {
|
|
@@ -29,11 +28,4 @@ export default {
|
|
|
29
28
|
AutomationNoteNode,
|
|
30
29
|
AutomationNavigationNode,
|
|
31
30
|
AutomationAISuggestionNode,
|
|
32
|
-
// Google Services Nodes (Docs, Slides, Meet, Gmail, Sheets, Calendar)
|
|
33
|
-
AutomationGoogleServicesNode,
|
|
34
|
-
AutomationGoogleDocsNode: AutomationGoogleServicesNode,
|
|
35
|
-
AutomationGoogleSlidesNode: AutomationGoogleServicesNode,
|
|
36
|
-
AutomationGoogleMeetNode: AutomationGoogleServicesNode,
|
|
37
|
-
AutomationGmailNode: AutomationGoogleServicesNode,
|
|
38
|
-
AutomationGoogleCalendarNode: AutomationGoogleServicesNode,
|
|
39
31
|
};
|
|
@@ -1,1021 +0,0 @@
|
|
|
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
|
-
// Google Auth config
|
|
70
|
-
googleAuth?: {
|
|
71
|
-
clientId?: string;
|
|
72
|
-
scopes?: string[];
|
|
73
|
-
tokens?: GoogleTokenData;
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
// Service-specific parameters
|
|
77
|
-
parameters?: {
|
|
78
|
-
// Google Docs
|
|
79
|
-
documentId?: string;
|
|
80
|
-
documentTitle?: string;
|
|
81
|
-
content?: string;
|
|
82
|
-
operation?: 'create' | 'read' | 'update' | 'append';
|
|
83
|
-
|
|
84
|
-
// Google Slides
|
|
85
|
-
presentationId?: string;
|
|
86
|
-
presentationTitle?: string;
|
|
87
|
-
slideContent?: string;
|
|
88
|
-
|
|
89
|
-
// Google Meet
|
|
90
|
-
meetingTitle?: string;
|
|
91
|
-
startTime?: string;
|
|
92
|
-
endTime?: string;
|
|
93
|
-
attendees?: string[];
|
|
94
|
-
|
|
95
|
-
// Gmail
|
|
96
|
-
to?: string[];
|
|
97
|
-
subject?: string;
|
|
98
|
-
body?: string;
|
|
99
|
-
attachments?: string[];
|
|
100
|
-
|
|
101
|
-
// Google Sheets
|
|
102
|
-
spreadsheetId?: string;
|
|
103
|
-
sheetName?: string;
|
|
104
|
-
range?: string;
|
|
105
|
-
data?: any[];
|
|
106
|
-
|
|
107
|
-
// Google Calendar
|
|
108
|
-
calendarId?: string;
|
|
109
|
-
eventTitle?: string;
|
|
110
|
-
eventDescription?: string;
|
|
111
|
-
|
|
112
|
-
// Stored tokens
|
|
113
|
-
useUserAccount?: boolean;
|
|
114
|
-
googleAccessToken?: string;
|
|
115
|
-
googleTokenExpiresAt?: number;
|
|
116
|
-
googleUserEmail?: string;
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
// Form data for configuration
|
|
120
|
-
formData?: {
|
|
121
|
-
aiSuggestionsCount?: number;
|
|
122
|
-
[key: string]: any;
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
// Execution result
|
|
126
|
-
executionResult?: {
|
|
127
|
-
success: boolean;
|
|
128
|
-
data?: any;
|
|
129
|
-
error?: string;
|
|
130
|
-
timestamp?: string;
|
|
131
|
-
executionTime?: number;
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
// Callbacks
|
|
135
|
-
onExecute?: (tokens: GoogleTokenData) => Promise<any>;
|
|
136
|
-
onAuthSuccess?: (tokens: GoogleTokenData) => void;
|
|
137
|
-
onAuthError?: (error: Error) => void;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
export interface AutomationGoogleServicesNodeProps {
|
|
141
|
-
data: AutomationGoogleServicesNodeData;
|
|
142
|
-
selected?: boolean;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// ========================
|
|
146
|
-
// Constants
|
|
147
|
-
// ========================
|
|
148
|
-
|
|
149
|
-
const SERVICE_CONFIG: Record<
|
|
150
|
-
GoogleServiceType,
|
|
151
|
-
{
|
|
152
|
-
icon: React.ElementType;
|
|
153
|
-
label: string;
|
|
154
|
-
color: string;
|
|
155
|
-
scopes: string[];
|
|
156
|
-
description: string;
|
|
157
|
-
}
|
|
158
|
-
> = {
|
|
159
|
-
docs: {
|
|
160
|
-
icon: DocsIcon,
|
|
161
|
-
label: 'Google Docs',
|
|
162
|
-
color: '#4285F4',
|
|
163
|
-
scopes: [
|
|
164
|
-
'https://www.googleapis.com/auth/documents',
|
|
165
|
-
'https://www.googleapis.com/auth/drive',
|
|
166
|
-
],
|
|
167
|
-
description: 'Document එකක් create/edit කරන්න',
|
|
168
|
-
},
|
|
169
|
-
slides: {
|
|
170
|
-
icon: SlidesIcon,
|
|
171
|
-
label: 'Google Slides',
|
|
172
|
-
color: '#FBBC04',
|
|
173
|
-
scopes: [
|
|
174
|
-
'https://www.googleapis.com/auth/presentations',
|
|
175
|
-
'https://www.googleapis.com/auth/drive',
|
|
176
|
-
],
|
|
177
|
-
description: 'Presentation එකක් create/edit කරන්න',
|
|
178
|
-
},
|
|
179
|
-
meet: {
|
|
180
|
-
icon: MeetIcon,
|
|
181
|
-
label: 'Google Meet',
|
|
182
|
-
color: '#34A853',
|
|
183
|
-
scopes: ['https://www.googleapis.com/auth/meetings.space.created'],
|
|
184
|
-
description: 'Meeting එකක් create කරන්න',
|
|
185
|
-
},
|
|
186
|
-
gmail: {
|
|
187
|
-
icon: GmailIcon,
|
|
188
|
-
label: 'Gmail',
|
|
189
|
-
color: '#EA4335',
|
|
190
|
-
scopes: [
|
|
191
|
-
'https://www.googleapis.com/auth/gmail.send',
|
|
192
|
-
'https://www.googleapis.com/auth/gmail.compose',
|
|
193
|
-
],
|
|
194
|
-
description: 'Email එකක් send කරන්න',
|
|
195
|
-
},
|
|
196
|
-
sheets: {
|
|
197
|
-
icon: SheetsIcon,
|
|
198
|
-
label: 'Google Sheets',
|
|
199
|
-
color: '#0F9D58',
|
|
200
|
-
scopes: ['https://www.googleapis.com/auth/spreadsheets'],
|
|
201
|
-
description: 'Spreadsheet එකක් read/write කරන්න',
|
|
202
|
-
},
|
|
203
|
-
calendar: {
|
|
204
|
-
icon: CalendarIcon,
|
|
205
|
-
label: 'Google Calendar',
|
|
206
|
-
color: '#4285F4',
|
|
207
|
-
scopes: ['https://www.googleapis.com/auth/calendar'],
|
|
208
|
-
description: 'Calendar event එකක් create කරන්න',
|
|
209
|
-
},
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
// ========================
|
|
213
|
-
// Helper Functions
|
|
214
|
-
// ========================
|
|
215
|
-
|
|
216
|
-
const isTokenExpired = (expiresAt?: number): boolean => {
|
|
217
|
-
if (!expiresAt) return true;
|
|
218
|
-
// Token expires 5 minutes before actual expiry for safety
|
|
219
|
-
return Date.now() > expiresAt - 5 * 60 * 1000;
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
const getTimeUntilExpiry = (expiresAt?: number): string => {
|
|
223
|
-
if (!expiresAt) return 'N/A';
|
|
224
|
-
const remaining = expiresAt - Date.now();
|
|
225
|
-
if (remaining <= 0) return 'කල් ඉකුත් වී ඇත';
|
|
226
|
-
const minutes = Math.floor(remaining / 60000);
|
|
227
|
-
if (minutes < 60) return `${minutes} min`;
|
|
228
|
-
const hours = Math.floor(minutes / 60);
|
|
229
|
-
return `${hours}h ${minutes % 60}m`;
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
// ========================
|
|
233
|
-
// Main Component
|
|
234
|
-
// ========================
|
|
235
|
-
|
|
236
|
-
export const AutomationGoogleServicesNode: React.FC<
|
|
237
|
-
AutomationGoogleServicesNodeProps
|
|
238
|
-
> = ({ data, selected }) => {
|
|
239
|
-
const { t } = useTranslation();
|
|
240
|
-
const { highlightText } = useSearch();
|
|
241
|
-
const nodeId = useNodeId();
|
|
242
|
-
const setSelectedNode = useDiagram((state) => state.setSelectedNode);
|
|
243
|
-
const nodes = useDiagram((state) => state.nodes);
|
|
244
|
-
const setNodes = useDiagram((state) => state.setNodes);
|
|
245
|
-
|
|
246
|
-
// ========================
|
|
247
|
-
// State
|
|
248
|
-
// ========================
|
|
249
|
-
const [status, setStatus] = useState<NodeStatus>(data.status || 'idle');
|
|
250
|
-
const [tokens, setTokens] = useState<GoogleTokenData | null>(null);
|
|
251
|
-
const [isExecuting, setIsExecuting] = useState(false);
|
|
252
|
-
const [executionProgress, setExecutionProgress] = useState(0);
|
|
253
|
-
const [error, setError] = useState<string | null>(null);
|
|
254
|
-
const [result, setResult] = useState<any>(null);
|
|
255
|
-
|
|
256
|
-
const googleClientRef = useRef<any>(null);
|
|
257
|
-
const serviceType = data.serviceType || 'docs';
|
|
258
|
-
const serviceConfig = SERVICE_CONFIG[serviceType];
|
|
259
|
-
const ServiceIcon = serviceConfig.icon;
|
|
260
|
-
|
|
261
|
-
// ========================
|
|
262
|
-
// Initialize tokens from data
|
|
263
|
-
// ========================
|
|
264
|
-
useEffect(() => {
|
|
265
|
-
if (data.parameters?.googleAccessToken) {
|
|
266
|
-
const savedTokens: GoogleTokenData = {
|
|
267
|
-
accessToken: data.parameters.googleAccessToken,
|
|
268
|
-
expiresAt: data.parameters.googleTokenExpiresAt || 0,
|
|
269
|
-
email: data.parameters.googleUserEmail,
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
if (!isTokenExpired(savedTokens.expiresAt)) {
|
|
273
|
-
setTokens(savedTokens);
|
|
274
|
-
setStatus('authenticated');
|
|
275
|
-
} else {
|
|
276
|
-
setStatus('idle');
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
}, [data.parameters?.googleAccessToken]);
|
|
280
|
-
|
|
281
|
-
// ========================
|
|
282
|
-
// Google OAuth Login
|
|
283
|
-
// ========================
|
|
284
|
-
const handleGoogleLogin = useCallback(async () => {
|
|
285
|
-
setStatus('authenticating');
|
|
286
|
-
setError(null);
|
|
287
|
-
|
|
288
|
-
try {
|
|
289
|
-
// Load Google Identity Services if not loaded
|
|
290
|
-
if (!(window as any).google?.accounts?.oauth2) {
|
|
291
|
-
await new Promise<void>((resolve, reject) => {
|
|
292
|
-
const script = document.createElement('script');
|
|
293
|
-
script.src = 'https://accounts.google.com/gsi/client';
|
|
294
|
-
script.async = true;
|
|
295
|
-
script.defer = true;
|
|
296
|
-
script.onload = () => resolve();
|
|
297
|
-
script.onerror = () => reject(new Error('Failed to load Google Identity Services'));
|
|
298
|
-
document.head.appendChild(script);
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// Initialize OAuth client
|
|
303
|
-
const clientId = data.googleAuth?.clientId || process.env.REACT_APP_GOOGLE_CLIENT_ID;
|
|
304
|
-
|
|
305
|
-
if (!clientId) {
|
|
306
|
-
throw new Error('Google Client ID not configured');
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
const tokenClient = (window as any).google.accounts.oauth2.initTokenClient({
|
|
310
|
-
client_id: clientId,
|
|
311
|
-
scope: serviceConfig.scopes.join(' '),
|
|
312
|
-
callback: (response: any) => {
|
|
313
|
-
if (response.error) {
|
|
314
|
-
setError(response.error_description || 'Authentication failed');
|
|
315
|
-
setStatus('error');
|
|
316
|
-
data.onAuthError?.(new Error(response.error_description));
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
const newTokens: GoogleTokenData = {
|
|
321
|
-
accessToken: response.access_token,
|
|
322
|
-
expiresAt: Date.now() + response.expires_in * 1000,
|
|
323
|
-
scope: response.scope,
|
|
324
|
-
};
|
|
325
|
-
|
|
326
|
-
// Get user email
|
|
327
|
-
fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
|
|
328
|
-
headers: { Authorization: `Bearer ${response.access_token}` },
|
|
329
|
-
})
|
|
330
|
-
.then((res) => res.json())
|
|
331
|
-
.then((userInfo) => {
|
|
332
|
-
newTokens.email = userInfo.email;
|
|
333
|
-
setTokens(newTokens);
|
|
334
|
-
setStatus('authenticated');
|
|
335
|
-
data.onAuthSuccess?.(newTokens);
|
|
336
|
-
|
|
337
|
-
// Save tokens to node data
|
|
338
|
-
updateNodeData({
|
|
339
|
-
parameters: {
|
|
340
|
-
...data.parameters,
|
|
341
|
-
useUserAccount: true,
|
|
342
|
-
googleAccessToken: newTokens.accessToken,
|
|
343
|
-
googleTokenExpiresAt: newTokens.expiresAt,
|
|
344
|
-
googleUserEmail: newTokens.email,
|
|
345
|
-
},
|
|
346
|
-
});
|
|
347
|
-
})
|
|
348
|
-
.catch(() => {
|
|
349
|
-
setTokens(newTokens);
|
|
350
|
-
setStatus('authenticated');
|
|
351
|
-
data.onAuthSuccess?.(newTokens);
|
|
352
|
-
});
|
|
353
|
-
},
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
googleClientRef.current = tokenClient;
|
|
357
|
-
tokenClient.requestAccessToken();
|
|
358
|
-
} catch (err: any) {
|
|
359
|
-
setError(err.message || 'Authentication failed');
|
|
360
|
-
setStatus('error');
|
|
361
|
-
data.onAuthError?.(err);
|
|
362
|
-
}
|
|
363
|
-
}, [data, serviceConfig.scopes]);
|
|
364
|
-
|
|
365
|
-
// ========================
|
|
366
|
-
// Disconnect Google Account
|
|
367
|
-
// ========================
|
|
368
|
-
const handleDisconnect = useCallback(() => {
|
|
369
|
-
setTokens(null);
|
|
370
|
-
setStatus('idle');
|
|
371
|
-
setResult(null);
|
|
372
|
-
setError(null);
|
|
373
|
-
|
|
374
|
-
// Clear tokens from node data
|
|
375
|
-
updateNodeData({
|
|
376
|
-
parameters: {
|
|
377
|
-
...data.parameters,
|
|
378
|
-
useUserAccount: false,
|
|
379
|
-
googleAccessToken: undefined,
|
|
380
|
-
googleTokenExpiresAt: undefined,
|
|
381
|
-
googleUserEmail: undefined,
|
|
382
|
-
},
|
|
383
|
-
});
|
|
384
|
-
}, [data.parameters]);
|
|
385
|
-
|
|
386
|
-
// ========================
|
|
387
|
-
// Execute Node
|
|
388
|
-
// ========================
|
|
389
|
-
const handleExecute = useCallback(async () => {
|
|
390
|
-
// Check if authenticated
|
|
391
|
-
if (!tokens || isTokenExpired(tokens.expiresAt)) {
|
|
392
|
-
// Need to authenticate first
|
|
393
|
-
handleGoogleLogin();
|
|
394
|
-
return;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
setIsExecuting(true);
|
|
398
|
-
setStatus('running');
|
|
399
|
-
setExecutionProgress(0);
|
|
400
|
-
setError(null);
|
|
401
|
-
|
|
402
|
-
try {
|
|
403
|
-
// Simulate progress
|
|
404
|
-
const progressInterval = setInterval(() => {
|
|
405
|
-
setExecutionProgress((prev) => Math.min(prev + 10, 90));
|
|
406
|
-
}, 200);
|
|
407
|
-
|
|
408
|
-
// Execute the actual operation
|
|
409
|
-
let executionResult: any;
|
|
410
|
-
|
|
411
|
-
if (data.onExecute) {
|
|
412
|
-
executionResult = await data.onExecute(tokens);
|
|
413
|
-
} else {
|
|
414
|
-
// Default execution based on service type
|
|
415
|
-
executionResult = await executeGoogleService(serviceType, tokens, data.parameters);
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
clearInterval(progressInterval);
|
|
419
|
-
setExecutionProgress(100);
|
|
420
|
-
setResult(executionResult);
|
|
421
|
-
setStatus('completed');
|
|
422
|
-
|
|
423
|
-
// Update node data with result
|
|
424
|
-
updateNodeData({
|
|
425
|
-
executionResult: {
|
|
426
|
-
success: true,
|
|
427
|
-
data: executionResult,
|
|
428
|
-
timestamp: new Date().toISOString(),
|
|
429
|
-
},
|
|
430
|
-
});
|
|
431
|
-
} catch (err: any) {
|
|
432
|
-
setError(err.message || 'Execution failed');
|
|
433
|
-
setStatus('error');
|
|
434
|
-
|
|
435
|
-
// Update node data with error
|
|
436
|
-
updateNodeData({
|
|
437
|
-
executionResult: {
|
|
438
|
-
success: false,
|
|
439
|
-
error: err.message,
|
|
440
|
-
timestamp: new Date().toISOString(),
|
|
441
|
-
},
|
|
442
|
-
});
|
|
443
|
-
} finally {
|
|
444
|
-
setIsExecuting(false);
|
|
445
|
-
}
|
|
446
|
-
}, [tokens, data, serviceType, handleGoogleLogin]);
|
|
447
|
-
|
|
448
|
-
// ========================
|
|
449
|
-
// Update Node Data
|
|
450
|
-
// ========================
|
|
451
|
-
const updateNodeData = useCallback(
|
|
452
|
-
(updates: Partial<AutomationGoogleServicesNodeData>) => {
|
|
453
|
-
if (!nodeId) return;
|
|
454
|
-
|
|
455
|
-
setNodes(
|
|
456
|
-
nodes.map((node) => {
|
|
457
|
-
if (node.id === nodeId) {
|
|
458
|
-
return {
|
|
459
|
-
...node,
|
|
460
|
-
data: {
|
|
461
|
-
...node.data,
|
|
462
|
-
...updates,
|
|
463
|
-
},
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
return node;
|
|
467
|
-
})
|
|
468
|
-
);
|
|
469
|
-
},
|
|
470
|
-
[nodeId, nodes, setNodes]
|
|
471
|
-
);
|
|
472
|
-
|
|
473
|
-
// ========================
|
|
474
|
-
// Execute Google Service
|
|
475
|
-
// ========================
|
|
476
|
-
const executeGoogleService = async (
|
|
477
|
-
service: GoogleServiceType,
|
|
478
|
-
tokenData: GoogleTokenData,
|
|
479
|
-
params?: any
|
|
480
|
-
): Promise<any> => {
|
|
481
|
-
const headers = {
|
|
482
|
-
Authorization: `Bearer ${tokenData.accessToken}`,
|
|
483
|
-
'Content-Type': 'application/json',
|
|
484
|
-
};
|
|
485
|
-
|
|
486
|
-
switch (service) {
|
|
487
|
-
case 'docs': {
|
|
488
|
-
// Create or update Google Doc
|
|
489
|
-
const docTitle = params?.documentTitle || 'New Document';
|
|
490
|
-
const response = await fetch(
|
|
491
|
-
'https://docs.googleapis.com/v1/documents',
|
|
492
|
-
{
|
|
493
|
-
method: 'POST',
|
|
494
|
-
headers,
|
|
495
|
-
body: JSON.stringify({ title: docTitle }),
|
|
496
|
-
}
|
|
497
|
-
);
|
|
498
|
-
return response.json();
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
case 'slides': {
|
|
502
|
-
// Create Google Slides presentation
|
|
503
|
-
const slideTitle = params?.presentationTitle || 'New Presentation';
|
|
504
|
-
const response = await fetch(
|
|
505
|
-
'https://slides.googleapis.com/v1/presentations',
|
|
506
|
-
{
|
|
507
|
-
method: 'POST',
|
|
508
|
-
headers,
|
|
509
|
-
body: JSON.stringify({ title: slideTitle }),
|
|
510
|
-
}
|
|
511
|
-
);
|
|
512
|
-
return response.json();
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
case 'meet': {
|
|
516
|
-
// Create Google Meet link
|
|
517
|
-
const response = await fetch(
|
|
518
|
-
'https://meet.googleapis.com/v2/spaces',
|
|
519
|
-
{
|
|
520
|
-
method: 'POST',
|
|
521
|
-
headers,
|
|
522
|
-
body: JSON.stringify({
|
|
523
|
-
config: {
|
|
524
|
-
accessType: 'OPEN',
|
|
525
|
-
entryPointAccess: 'ALL',
|
|
526
|
-
},
|
|
527
|
-
}),
|
|
528
|
-
}
|
|
529
|
-
);
|
|
530
|
-
return response.json();
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
case 'gmail': {
|
|
534
|
-
// Send Gmail
|
|
535
|
-
const to = params?.to?.join(', ') || '';
|
|
536
|
-
const subject = params?.subject || 'No Subject';
|
|
537
|
-
const body = params?.body || '';
|
|
538
|
-
|
|
539
|
-
const email = [
|
|
540
|
-
`To: ${to}`,
|
|
541
|
-
`Subject: ${subject}`,
|
|
542
|
-
'',
|
|
543
|
-
body,
|
|
544
|
-
].join('\n');
|
|
545
|
-
|
|
546
|
-
const encodedEmail = btoa(unescape(encodeURIComponent(email)))
|
|
547
|
-
.replace(/\+/g, '-')
|
|
548
|
-
.replace(/\//g, '_')
|
|
549
|
-
.replace(/=+$/, '');
|
|
550
|
-
|
|
551
|
-
const response = await fetch(
|
|
552
|
-
'https://gmail.googleapis.com/gmail/v1/users/me/messages/send',
|
|
553
|
-
{
|
|
554
|
-
method: 'POST',
|
|
555
|
-
headers,
|
|
556
|
-
body: JSON.stringify({ raw: encodedEmail }),
|
|
557
|
-
}
|
|
558
|
-
);
|
|
559
|
-
return response.json();
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
case 'sheets': {
|
|
563
|
-
// Read/Write Google Sheets
|
|
564
|
-
const spreadsheetId = params?.spreadsheetId;
|
|
565
|
-
const range = params?.range || 'Sheet1!A1:Z100';
|
|
566
|
-
|
|
567
|
-
if (!spreadsheetId) {
|
|
568
|
-
// Create new spreadsheet
|
|
569
|
-
const response = await fetch(
|
|
570
|
-
'https://sheets.googleapis.com/v4/spreadsheets',
|
|
571
|
-
{
|
|
572
|
-
method: 'POST',
|
|
573
|
-
headers,
|
|
574
|
-
body: JSON.stringify({
|
|
575
|
-
properties: {
|
|
576
|
-
title: params?.sheetName || 'New Spreadsheet',
|
|
577
|
-
},
|
|
578
|
-
}),
|
|
579
|
-
}
|
|
580
|
-
);
|
|
581
|
-
return response.json();
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
// Read existing spreadsheet
|
|
585
|
-
const response = await fetch(
|
|
586
|
-
`https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}/values/${range}`,
|
|
587
|
-
{ headers }
|
|
588
|
-
);
|
|
589
|
-
return response.json();
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
case 'calendar': {
|
|
593
|
-
// Create calendar event
|
|
594
|
-
const calendarId = params?.calendarId || 'primary';
|
|
595
|
-
const response = await fetch(
|
|
596
|
-
`https://www.googleapis.com/calendar/v3/calendars/${calendarId}/events`,
|
|
597
|
-
{
|
|
598
|
-
method: 'POST',
|
|
599
|
-
headers,
|
|
600
|
-
body: JSON.stringify({
|
|
601
|
-
summary: params?.eventTitle || 'New Event',
|
|
602
|
-
description: params?.eventDescription,
|
|
603
|
-
start: {
|
|
604
|
-
dateTime: params?.startTime || new Date().toISOString(),
|
|
605
|
-
timeZone: 'UTC',
|
|
606
|
-
},
|
|
607
|
-
end: {
|
|
608
|
-
dateTime:
|
|
609
|
-
params?.endTime ||
|
|
610
|
-
new Date(Date.now() + 3600000).toISOString(),
|
|
611
|
-
timeZone: 'UTC',
|
|
612
|
-
},
|
|
613
|
-
}),
|
|
614
|
-
}
|
|
615
|
-
);
|
|
616
|
-
return response.json();
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
default:
|
|
620
|
-
throw new Error(`Unsupported service: ${service}`);
|
|
621
|
-
}
|
|
622
|
-
};
|
|
623
|
-
|
|
624
|
-
// ========================
|
|
625
|
-
// Render Status Badge
|
|
626
|
-
// ========================
|
|
627
|
-
const renderStatusBadge = () => {
|
|
628
|
-
const statusConfig: Record<
|
|
629
|
-
NodeStatus,
|
|
630
|
-
{ color: string; bgColor: string; label: string; icon?: React.ReactNode }
|
|
631
|
-
> = {
|
|
632
|
-
idle: {
|
|
633
|
-
color: '#6B7280',
|
|
634
|
-
bgColor: 'rgba(107, 114, 128, 0.1)',
|
|
635
|
-
label: 'සූදානම්',
|
|
636
|
-
},
|
|
637
|
-
authenticating: {
|
|
638
|
-
color: '#F59E0B',
|
|
639
|
-
bgColor: 'rgba(245, 158, 11, 0.1)',
|
|
640
|
-
label: 'සත්යාපනය...',
|
|
641
|
-
icon: <CircularProgress size={12} color="inherit" />,
|
|
642
|
-
},
|
|
643
|
-
authenticated: {
|
|
644
|
-
color: '#10B981',
|
|
645
|
-
bgColor: 'rgba(16, 185, 129, 0.1)',
|
|
646
|
-
label: 'සම්බන්ධයි',
|
|
647
|
-
icon: <CheckCircleIcon sx={{ fontSize: 12 }} />,
|
|
648
|
-
},
|
|
649
|
-
running: {
|
|
650
|
-
color: '#3B82F6',
|
|
651
|
-
bgColor: 'rgba(59, 130, 246, 0.1)',
|
|
652
|
-
label: 'ක්රියාත්මක...',
|
|
653
|
-
icon: <CircularProgress size={12} color="inherit" />,
|
|
654
|
-
},
|
|
655
|
-
completed: {
|
|
656
|
-
color: '#10B981',
|
|
657
|
-
bgColor: 'rgba(16, 185, 129, 0.1)',
|
|
658
|
-
label: 'සාර්ථකයි',
|
|
659
|
-
icon: <CheckCircleIcon sx={{ fontSize: 12 }} />,
|
|
660
|
-
},
|
|
661
|
-
error: {
|
|
662
|
-
color: '#EF4444',
|
|
663
|
-
bgColor: 'rgba(239, 68, 68, 0.1)',
|
|
664
|
-
label: 'දෝෂය',
|
|
665
|
-
icon: <ErrorIcon sx={{ fontSize: 12 }} />,
|
|
666
|
-
},
|
|
667
|
-
};
|
|
668
|
-
|
|
669
|
-
const config = statusConfig[status];
|
|
670
|
-
|
|
671
|
-
return (
|
|
672
|
-
<Chip
|
|
673
|
-
size="small"
|
|
674
|
-
label={
|
|
675
|
-
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
|
676
|
-
{config.icon}
|
|
677
|
-
<span>{config.label}</span>
|
|
678
|
-
</Box>
|
|
679
|
-
}
|
|
680
|
-
sx={{
|
|
681
|
-
height: 20,
|
|
682
|
-
fontSize: '10px',
|
|
683
|
-
fontWeight: 600,
|
|
684
|
-
color: config.color,
|
|
685
|
-
bgcolor: config.bgColor,
|
|
686
|
-
border: `1px solid ${config.color}20`,
|
|
687
|
-
'& .MuiChip-label': { px: 1 },
|
|
688
|
-
}}
|
|
689
|
-
/>
|
|
690
|
-
);
|
|
691
|
-
};
|
|
692
|
-
|
|
693
|
-
// ========================
|
|
694
|
-
// Render
|
|
695
|
-
// ========================
|
|
696
|
-
return (
|
|
697
|
-
<Box
|
|
698
|
-
sx={{
|
|
699
|
-
bgcolor: '#1E1E2E',
|
|
700
|
-
borderRadius: 2,
|
|
701
|
-
border: selected ? `2px solid ${serviceConfig.color}` : '1px solid #374151',
|
|
702
|
-
minWidth: 280,
|
|
703
|
-
maxWidth: 320,
|
|
704
|
-
overflow: 'hidden',
|
|
705
|
-
boxShadow: selected
|
|
706
|
-
? `0 0 20px ${serviceConfig.color}30`
|
|
707
|
-
: '0 4px 12px rgba(0, 0, 0, 0.3)',
|
|
708
|
-
transition: 'all 0.2s ease',
|
|
709
|
-
}}
|
|
710
|
-
onClick={() => setSelectedNode(nodeId || '')}
|
|
711
|
-
>
|
|
712
|
-
{/* Input Handle */}
|
|
713
|
-
<Handle
|
|
714
|
-
type="target"
|
|
715
|
-
position={Position.Left}
|
|
716
|
-
style={{
|
|
717
|
-
background: serviceConfig.color,
|
|
718
|
-
width: 10,
|
|
719
|
-
height: 10,
|
|
720
|
-
border: '2px solid #1E1E2E',
|
|
721
|
-
}}
|
|
722
|
-
/>
|
|
723
|
-
|
|
724
|
-
{/* Header */}
|
|
725
|
-
<Box
|
|
726
|
-
sx={{
|
|
727
|
-
bgcolor: `${serviceConfig.color}15`,
|
|
728
|
-
borderBottom: `1px solid ${serviceConfig.color}30`,
|
|
729
|
-
px: 2,
|
|
730
|
-
py: 1.5,
|
|
731
|
-
display: 'flex',
|
|
732
|
-
alignItems: 'center',
|
|
733
|
-
justifyContent: 'space-between',
|
|
734
|
-
}}
|
|
735
|
-
>
|
|
736
|
-
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
|
|
737
|
-
<Box
|
|
738
|
-
sx={{
|
|
739
|
-
bgcolor: serviceConfig.color,
|
|
740
|
-
borderRadius: 1,
|
|
741
|
-
p: 0.75,
|
|
742
|
-
display: 'flex',
|
|
743
|
-
alignItems: 'center',
|
|
744
|
-
justifyContent: 'center',
|
|
745
|
-
}}
|
|
746
|
-
>
|
|
747
|
-
<ServiceIcon sx={{ fontSize: 18, color: '#fff' }} />
|
|
748
|
-
</Box>
|
|
749
|
-
<Box>
|
|
750
|
-
<Typography
|
|
751
|
-
variant="subtitle2"
|
|
752
|
-
sx={{ color: '#fff', fontWeight: 600, fontSize: 13 }}
|
|
753
|
-
>
|
|
754
|
-
{highlightText(data.label || serviceConfig.label)}
|
|
755
|
-
</Typography>
|
|
756
|
-
<Typography
|
|
757
|
-
variant="caption"
|
|
758
|
-
sx={{ color: '#9CA3AF', fontSize: 10 }}
|
|
759
|
-
>
|
|
760
|
-
{serviceConfig.description}
|
|
761
|
-
</Typography>
|
|
762
|
-
</Box>
|
|
763
|
-
</Box>
|
|
764
|
-
{renderStatusBadge()}
|
|
765
|
-
</Box>
|
|
766
|
-
|
|
767
|
-
{/* Content */}
|
|
768
|
-
<Box sx={{ px: 2, py: 1.5 }}>
|
|
769
|
-
{/* Authentication Status */}
|
|
770
|
-
<Box
|
|
771
|
-
sx={{
|
|
772
|
-
bgcolor: tokens ? 'rgba(16, 185, 129, 0.1)' : 'rgba(107, 114, 128, 0.1)',
|
|
773
|
-
borderRadius: 1,
|
|
774
|
-
p: 1.5,
|
|
775
|
-
mb: 1.5,
|
|
776
|
-
}}
|
|
777
|
-
>
|
|
778
|
-
{tokens ? (
|
|
779
|
-
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
|
780
|
-
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
781
|
-
<FcGoogle size={20} />
|
|
782
|
-
<Box>
|
|
783
|
-
<Typography
|
|
784
|
-
variant="caption"
|
|
785
|
-
sx={{ color: '#10B981', display: 'block', fontWeight: 600 }}
|
|
786
|
-
>
|
|
787
|
-
සම්බන්ධයි
|
|
788
|
-
</Typography>
|
|
789
|
-
<Typography
|
|
790
|
-
variant="caption"
|
|
791
|
-
sx={{ color: '#9CA3AF', fontSize: 10 }}
|
|
792
|
-
>
|
|
793
|
-
{tokens.email || 'Google Account'}
|
|
794
|
-
</Typography>
|
|
795
|
-
</Box>
|
|
796
|
-
</Box>
|
|
797
|
-
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
|
798
|
-
<Tooltip title={`කල් ඉකුත් වන්නේ: ${getTimeUntilExpiry(tokens.expiresAt)}`}>
|
|
799
|
-
<Typography
|
|
800
|
-
variant="caption"
|
|
801
|
-
sx={{
|
|
802
|
-
color: isTokenExpired(tokens.expiresAt) ? '#EF4444' : '#9CA3AF',
|
|
803
|
-
fontSize: 10,
|
|
804
|
-
}}
|
|
805
|
-
>
|
|
806
|
-
{getTimeUntilExpiry(tokens.expiresAt)}
|
|
807
|
-
</Typography>
|
|
808
|
-
</Tooltip>
|
|
809
|
-
<IconButton
|
|
810
|
-
size="small"
|
|
811
|
-
onClick={handleDisconnect}
|
|
812
|
-
sx={{ color: '#EF4444', p: 0.5 }}
|
|
813
|
-
>
|
|
814
|
-
<LinkOffIcon sx={{ fontSize: 14 }} />
|
|
815
|
-
</IconButton>
|
|
816
|
-
</Box>
|
|
817
|
-
</Box>
|
|
818
|
-
) : (
|
|
819
|
-
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
|
820
|
-
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
821
|
-
<FcGoogle size={20} style={{ opacity: 0.5 }} />
|
|
822
|
-
<Typography variant="caption" sx={{ color: '#6B7280' }}>
|
|
823
|
-
Google ගිණුම සම්බන්ධ කර නැත
|
|
824
|
-
</Typography>
|
|
825
|
-
</Box>
|
|
826
|
-
<Button
|
|
827
|
-
size="small"
|
|
828
|
-
variant="outlined"
|
|
829
|
-
onClick={handleGoogleLogin}
|
|
830
|
-
disabled={status === 'authenticating'}
|
|
831
|
-
startIcon={
|
|
832
|
-
status === 'authenticating' ? (
|
|
833
|
-
<CircularProgress size={12} color="inherit" />
|
|
834
|
-
) : (
|
|
835
|
-
<LinkIcon sx={{ fontSize: 14 }} />
|
|
836
|
-
)
|
|
837
|
-
}
|
|
838
|
-
sx={{
|
|
839
|
-
fontSize: 10,
|
|
840
|
-
py: 0.25,
|
|
841
|
-
px: 1,
|
|
842
|
-
borderColor: '#374151',
|
|
843
|
-
color: '#9CA3AF',
|
|
844
|
-
'&:hover': {
|
|
845
|
-
borderColor: serviceConfig.color,
|
|
846
|
-
color: serviceConfig.color,
|
|
847
|
-
},
|
|
848
|
-
}}
|
|
849
|
-
>
|
|
850
|
-
සම්බන්ධ කරන්න
|
|
851
|
-
</Button>
|
|
852
|
-
</Box>
|
|
853
|
-
)}
|
|
854
|
-
</Box>
|
|
855
|
-
|
|
856
|
-
{/* Progress Bar (when running) */}
|
|
857
|
-
{status === 'running' && (
|
|
858
|
-
<Box sx={{ mb: 1.5 }}>
|
|
859
|
-
<LinearProgress
|
|
860
|
-
variant="determinate"
|
|
861
|
-
value={executionProgress}
|
|
862
|
-
sx={{
|
|
863
|
-
height: 4,
|
|
864
|
-
borderRadius: 2,
|
|
865
|
-
bgcolor: 'rgba(59, 130, 246, 0.1)',
|
|
866
|
-
'& .MuiLinearProgress-bar': {
|
|
867
|
-
bgcolor: serviceConfig.color,
|
|
868
|
-
},
|
|
869
|
-
}}
|
|
870
|
-
/>
|
|
871
|
-
<Typography
|
|
872
|
-
variant="caption"
|
|
873
|
-
sx={{ color: '#9CA3AF', fontSize: 10, mt: 0.5, display: 'block' }}
|
|
874
|
-
>
|
|
875
|
-
ක්රියාත්මක වෙමින්... {executionProgress}%
|
|
876
|
-
</Typography>
|
|
877
|
-
</Box>
|
|
878
|
-
)}
|
|
879
|
-
|
|
880
|
-
{/* Error Display */}
|
|
881
|
-
{error && (
|
|
882
|
-
<Box
|
|
883
|
-
sx={{
|
|
884
|
-
bgcolor: 'rgba(239, 68, 68, 0.1)',
|
|
885
|
-
borderRadius: 1,
|
|
886
|
-
p: 1,
|
|
887
|
-
mb: 1.5,
|
|
888
|
-
border: '1px solid rgba(239, 68, 68, 0.3)',
|
|
889
|
-
}}
|
|
890
|
-
>
|
|
891
|
-
<Typography
|
|
892
|
-
variant="caption"
|
|
893
|
-
sx={{ color: '#EF4444', fontSize: 10, display: 'block' }}
|
|
894
|
-
>
|
|
895
|
-
{error}
|
|
896
|
-
</Typography>
|
|
897
|
-
</Box>
|
|
898
|
-
)}
|
|
899
|
-
|
|
900
|
-
{/* Result Display (when completed) */}
|
|
901
|
-
{status === 'completed' && result && (
|
|
902
|
-
<Box
|
|
903
|
-
sx={{
|
|
904
|
-
bgcolor: 'rgba(16, 185, 129, 0.1)',
|
|
905
|
-
borderRadius: 1,
|
|
906
|
-
p: 1,
|
|
907
|
-
mb: 1.5,
|
|
908
|
-
border: '1px solid rgba(16, 185, 129, 0.3)',
|
|
909
|
-
}}
|
|
910
|
-
>
|
|
911
|
-
<Typography
|
|
912
|
-
variant="caption"
|
|
913
|
-
sx={{ color: '#10B981', fontSize: 10, display: 'block', fontWeight: 600 }}
|
|
914
|
-
>
|
|
915
|
-
✓ සාර්ථකව ක්රියාත්මක විය
|
|
916
|
-
</Typography>
|
|
917
|
-
{result.documentId && (
|
|
918
|
-
<Typography
|
|
919
|
-
variant="caption"
|
|
920
|
-
sx={{ color: '#9CA3AF', fontSize: 10 }}
|
|
921
|
-
>
|
|
922
|
-
Doc ID: {result.documentId}
|
|
923
|
-
</Typography>
|
|
924
|
-
)}
|
|
925
|
-
{result.presentationId && (
|
|
926
|
-
<Typography
|
|
927
|
-
variant="caption"
|
|
928
|
-
sx={{ color: '#9CA3AF', fontSize: 10 }}
|
|
929
|
-
>
|
|
930
|
-
Presentation ID: {result.presentationId}
|
|
931
|
-
</Typography>
|
|
932
|
-
)}
|
|
933
|
-
{result.meetingUri && (
|
|
934
|
-
<Typography
|
|
935
|
-
variant="caption"
|
|
936
|
-
sx={{ color: '#9CA3AF', fontSize: 10 }}
|
|
937
|
-
>
|
|
938
|
-
Meet Link: {result.meetingUri}
|
|
939
|
-
</Typography>
|
|
940
|
-
)}
|
|
941
|
-
</Box>
|
|
942
|
-
)}
|
|
943
|
-
|
|
944
|
-
{/* Execute Button */}
|
|
945
|
-
<Button
|
|
946
|
-
fullWidth
|
|
947
|
-
variant="contained"
|
|
948
|
-
onClick={handleExecute}
|
|
949
|
-
disabled={isExecuting}
|
|
950
|
-
startIcon={
|
|
951
|
-
isExecuting ? (
|
|
952
|
-
<CircularProgress size={16} color="inherit" />
|
|
953
|
-
) : tokens ? (
|
|
954
|
-
<PlayIcon />
|
|
955
|
-
) : (
|
|
956
|
-
<FcGoogle size={16} />
|
|
957
|
-
)
|
|
958
|
-
}
|
|
959
|
-
sx={{
|
|
960
|
-
bgcolor: tokens ? serviceConfig.color : '#374151',
|
|
961
|
-
color: '#fff',
|
|
962
|
-
fontWeight: 600,
|
|
963
|
-
fontSize: 12,
|
|
964
|
-
py: 1,
|
|
965
|
-
'&:hover': {
|
|
966
|
-
bgcolor: tokens ? `${serviceConfig.color}CC` : '#4B5563',
|
|
967
|
-
},
|
|
968
|
-
'&:disabled': {
|
|
969
|
-
bgcolor: '#374151',
|
|
970
|
-
color: '#6B7280',
|
|
971
|
-
},
|
|
972
|
-
}}
|
|
973
|
-
>
|
|
974
|
-
{isExecuting
|
|
975
|
-
? 'ක්රියාත්මක වෙමින්...'
|
|
976
|
-
: tokens
|
|
977
|
-
? 'ක්රියාත්මක කරන්න'
|
|
978
|
-
: 'Google සමඟ ක්රියාත්මක කරන්න'}
|
|
979
|
-
</Button>
|
|
980
|
-
|
|
981
|
-
{/* Hint Text */}
|
|
982
|
-
{!tokens && (
|
|
983
|
-
<Typography
|
|
984
|
-
variant="caption"
|
|
985
|
-
sx={{
|
|
986
|
-
color: '#6B7280',
|
|
987
|
-
fontSize: 9,
|
|
988
|
-
display: 'block',
|
|
989
|
-
textAlign: 'center',
|
|
990
|
-
mt: 1,
|
|
991
|
-
}}
|
|
992
|
-
>
|
|
993
|
-
බොත්තම ඔබන්න → Google සත්යාපනය → ක්රියාත්මක වීම
|
|
994
|
-
</Typography>
|
|
995
|
-
)}
|
|
996
|
-
</Box>
|
|
997
|
-
|
|
998
|
-
{/* Node Action Buttons */}
|
|
999
|
-
<NodeActionButtons
|
|
1000
|
-
nodeId={nodeId || ''}
|
|
1001
|
-
showSettings
|
|
1002
|
-
onSettings={() => setSelectedNode(nodeId || '')}
|
|
1003
|
-
/>
|
|
1004
|
-
|
|
1005
|
-
{/* Output Handle */}
|
|
1006
|
-
<Handle
|
|
1007
|
-
type="source"
|
|
1008
|
-
position={Position.Right}
|
|
1009
|
-
style={{
|
|
1010
|
-
background: serviceConfig.color,
|
|
1011
|
-
width: 10,
|
|
1012
|
-
height: 10,
|
|
1013
|
-
border: '2px solid #1E1E2E',
|
|
1014
|
-
}}
|
|
1015
|
-
/>
|
|
1016
|
-
</Box>
|
|
1017
|
-
);
|
|
1018
|
-
};
|
|
1019
|
-
|
|
1020
|
-
export default AutomationGoogleServicesNode;
|
|
1021
|
-
|