Add Gmail integration support across components

- Introduced Gmail integration in ConfigurationWizard with a new wizard step for Gmail operations.
- Updated NodeLibrary to include Gmail as a node type with associated color coding.
- Enhanced BaseNode to represent Gmail with an appropriate icon.
- Configured nodeTypeConfigs for Gmail with detailed descriptions and common use cases.
- Added Gmail-specific fields in fieldConfigs for operation configuration.
- Implemented GmailService and gmailProcessor for handling various Gmail operations, including sending, reading, and managing emails.
- Created GmailNode component for visualizing Gmail operation details, including status and configuration.
- Developed GmailWizard component for configuring Gmail operations with dynamic form fields based on selected operation type.
- Updated ProcessorRegistry to recognize Gmail-related patterns for improved operation handling.
This commit is contained in:
Nikhil-Doye
2025-10-23 16:49:04 -04:00
parent 0756db7e0e
commit 3b095dbafe
16 changed files with 912 additions and 1 deletions
+13
View File
@@ -50,6 +50,7 @@ import { EmbeddingWizard } from "./wizards/EmbeddingWizard";
import { SimilaritySearchWizard } from "./wizards/SimilaritySearchWizard";
import { StructuredOutputWizard } from "./wizards/StructuredOutputWizard";
import { DiscordWizard } from "./wizards/DiscordWizard";
import { GmailWizard } from "./wizards/GmailWizard";
const WIZARD_STEPS: Record<NodeType, WizardStep[]> = {
dataInput: [
@@ -158,6 +159,17 @@ const WIZARD_STEPS: Record<NodeType, WizardStep[]> = {
helpText: "Set up your Discord operation parameters",
},
],
// Gmail integration
gmail: [
{
id: "gmail-config",
title: "Gmail Integration",
description: "Configure your Gmail integration",
component: GmailWizard,
validation: (data: any) => !!data.operation && !!data.accessToken,
helpText: "Set up your Gmail operation parameters",
},
],
};
export const ConfigurationWizard: React.FC<ConfigurationWizardProps> = ({
@@ -221,6 +233,7 @@ export const ConfigurationWizard: React.FC<ConfigurationWizardProps> = ({
database: "Database Operations",
slack: "Slack Integration",
discord: "Discord Integration",
gmail: "Gmail Integration",
};
return labels[type] || "New Node";
};
+2
View File
@@ -43,6 +43,7 @@ export const NodeLibrary: React.FC = () => {
"database",
"slack",
"discord",
"gmail",
];
const nodeTypeColors: Record<NodeType, string> = {
@@ -56,6 +57,7 @@ export const NodeLibrary: React.FC = () => {
database: "cyan",
slack: "purple",
discord: "indigo",
gmail: "red",
};
const nodeTypesByCategory = getNodeTypesByCategory();
+4
View File
@@ -36,6 +36,8 @@ import {
SlackNode,
// Discord node
DiscordNode,
// Gmail node
GmailNode,
} from "./nodes";
import { NodeData } from "../types";
import { Grid, Trash2, Sparkles, X, Lightbulb, Play } from "lucide-react";
@@ -54,6 +56,8 @@ const nodeTypes: NodeTypes = {
slack: SlackNode,
// Discord node
discord: DiscordNode,
// Gmail node
gmail: GmailNode,
};
interface WorkflowEditorProps {
+8
View File
@@ -15,6 +15,7 @@ import {
Database,
MessageSquare,
Bot,
Mail,
} from "lucide-react";
import { clsx } from "clsx";
@@ -29,6 +30,7 @@ const nodeIcons: Record<NodeType, React.ComponentType<any>> = {
database: Database,
slack: MessageSquare,
discord: Bot,
gmail: Mail,
};
const nodeColors: Record<
@@ -95,6 +97,12 @@ const nodeColors: Record<
icon: "text-indigo-600",
accent: "bg-indigo-500",
},
gmail: {
bg: "bg-red-50",
border: "border-red-200",
icon: "text-red-600",
accent: "bg-red-500",
},
};
const statusConfig = {
+185
View File
@@ -0,0 +1,185 @@
import React from "react";
import { NodeData } from "../../types";
import { GmailOperation } from "../../types/gmail";
import { NodeProps } from "reactflow";
import {
Mail,
Send,
Eye,
Reply,
Forward,
FileText,
Tag,
Search,
Paperclip,
CheckCircle,
XCircle,
Clock,
} from "lucide-react";
interface GmailNodeProps extends NodeProps {
data: NodeData;
}
const operationIcons: Record<GmailOperation, React.ComponentType<any>> = {
[GmailOperation.SEND]: Send,
[GmailOperation.READ]: Eye,
[GmailOperation.REPLY]: Reply,
[GmailOperation.FORWARD]: Forward,
[GmailOperation.DRAFT]: FileText,
[GmailOperation.LABEL]: Tag,
[GmailOperation.SEARCH]: Search,
[GmailOperation.ATTACHMENT]: Paperclip,
};
const operationLabels: Record<GmailOperation, string> = {
[GmailOperation.SEND]: "Send Email",
[GmailOperation.READ]: "Read Email",
[GmailOperation.REPLY]: "Reply to Email",
[GmailOperation.FORWARD]: "Forward Email",
[GmailOperation.DRAFT]: "Manage Draft",
[GmailOperation.LABEL]: "Manage Label",
[GmailOperation.SEARCH]: "Search Emails",
[GmailOperation.ATTACHMENT]: "Handle Attachment",
};
export const GmailNode: React.FC<GmailNodeProps> = ({ data, selected }) => {
const operation = data.config?.operation as GmailOperation;
const IconComponent = operation ? operationIcons[operation] : Mail;
const operationLabel = operation
? operationLabels[operation]
: "Gmail Operation";
const getStatusColor = () => {
if (data.status === "success") return "text-green-600";
if (data.status === "error") return "text-red-600";
if (data.status === "running") return "text-blue-600";
return "text-gray-600";
};
const getPreviewData = () => {
if (!data.config) return null;
switch (operation) {
case GmailOperation.SEND:
return {
to: data.config.to || "No recipient",
subject: data.config.subject || "No subject",
};
case GmailOperation.READ:
return {
messageId: data.config.messageId || "No message ID",
query: data.config.query || "No query",
};
case GmailOperation.REPLY:
return {
replyTo: data.config.replyToMessageId || "No reply target",
subject: data.config.subject || "No subject",
};
case GmailOperation.FORWARD:
return {
messageId: data.config.messageId || "No message ID",
to: data.config.to || "No recipient",
};
case GmailOperation.DRAFT:
return {
draftId: data.config.draftId || "New draft",
subject: data.config.subject || "No subject",
};
case GmailOperation.LABEL:
return {
labelName: data.config.labelName || "No label name",
labelId: data.config.labelId || "No label ID",
};
case GmailOperation.SEARCH:
return {
query: data.config.searchQuery || "No search query",
maxResults: data.config.maxResults || "Default",
};
case GmailOperation.ATTACHMENT:
return {
messageId: data.config.messageId || "No message ID",
attachmentId: data.config.attachmentId || "No attachment ID",
};
default:
return null;
}
};
const preview = getPreviewData();
return (
<div
className={`bg-white border-2 rounded-lg shadow-sm transition-all duration-200 ${
selected
? "border-red-500 shadow-lg"
: "border-red-200 hover:border-red-300"
}`}
>
{/* Header */}
<div className="flex items-center justify-between p-4 border-b border-gray-100">
<div className="flex items-center space-x-3">
<div className="w-8 h-8 rounded-lg bg-red-100 flex items-center justify-center">
<IconComponent className="w-4 h-4 text-red-600" />
</div>
<div>
<h3 className="text-sm font-semibold text-gray-900">
{data.label || "Gmail Integration"}
</h3>
<p className="text-xs text-gray-500">{operationLabel}</p>
</div>
</div>
{/* Status Indicator */}
<div className="flex items-center space-x-2">
{data.status === "running" && (
<div className="w-6 h-6 rounded-full flex items-center justify-center bg-blue-100">
<Clock className="w-3 h-3 text-blue-500 animate-spin" />
</div>
)}
{data.status === "success" && (
<div className="w-6 h-6 rounded-full flex items-center justify-center bg-green-100">
<CheckCircle className="w-3 h-3 text-green-500" />
</div>
)}
{data.status === "error" && (
<div className="w-6 h-6 rounded-full flex items-center justify-center bg-red-100">
<XCircle className="w-3 h-3 text-red-500" />
</div>
)}
</div>
</div>
{/* Content */}
<div className="px-4 pb-4">
<div className="space-y-2">
{/* Operation Details */}
<div className="text-xs text-gray-600">
{preview && (
<div className="space-y-1">
{Object.entries(preview).map(([key, value]) => (
<div key={key} className="flex justify-between">
<span className="text-gray-500 capitalize">{key}:</span>
<span className="text-gray-700 truncate ml-2">
{String(value)}
</span>
</div>
))}
</div>
)}
</div>
{/* Status Display */}
{data.status && (
<div className={`text-xs font-medium ${getStatusColor()}`}>
{data.status === "success" && "✓ Completed"}
{data.status === "error" && "✗ Failed"}
{data.status === "running" && "⏳ Running"}
{data.status === "idle" && "⏸ Ready"}
</div>
)}
</div>
</div>
</div>
);
};
+2
View File
@@ -12,3 +12,5 @@ export { UnifiedDatabaseNode } from "./UnifiedDatabaseNode";
export { SlackNode } from "./SlackNode";
// Discord node
export { DiscordNode } from "./DiscordNode";
// Gmail node
export { GmailNode } from "./GmailNode";
+211
View File
@@ -0,0 +1,211 @@
import React, { useState } from "react";
import { WizardStepProps } from "../ConfigurationWizard";
import { Button } from "../ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "../ui/card";
import { Badge } from "../ui/badge";
import {
Mail,
Send,
Eye,
Reply,
Forward,
FileText,
Tag,
Search,
Paperclip,
CheckCircle,
Settings,
} from "lucide-react";
import { GmailOperation } from "../../types/gmail";
const GMAIL_OPERATIONS = [
{
id: GmailOperation.SEND,
name: "Send Email",
description: "Send emails with attachments and rich content",
icon: Send,
color: "bg-blue-100 text-blue-800 border-blue-200",
features: ["Rich HTML", "Attachments", "CC/BCC", "Threading"],
},
{
id: GmailOperation.READ,
name: "Read Email",
description: "Read emails and message threads",
icon: Eye,
color: "bg-green-100 text-green-800 border-green-200",
features: ["Message content", "Headers", "Attachments", "Thread view"],
},
{
id: GmailOperation.REPLY,
name: "Reply to Email",
description: "Reply to specific email messages",
icon: Reply,
color: "bg-purple-100 text-purple-800 border-purple-200",
features: ["Thread replies", "Rich content", "Attachments", "Auto-reply"],
},
{
id: GmailOperation.FORWARD,
name: "Forward Email",
description: "Forward emails to other recipients",
icon: Forward,
color: "bg-orange-100 text-orange-800 border-orange-200",
features: [
"Multiple recipients",
"Add comments",
"Preserve formatting",
"Thread context",
],
},
{
id: GmailOperation.DRAFT,
name: "Manage Draft",
description: "Create, edit, and manage email drafts",
icon: FileText,
color: "bg-yellow-100 text-yellow-800 border-yellow-200",
features: ["Save drafts", "Edit drafts", "Send drafts", "Draft templates"],
},
{
id: GmailOperation.LABEL,
name: "Manage Label",
description: "Create and manage Gmail labels",
icon: Tag,
color: "bg-pink-100 text-pink-800 border-pink-200",
features: [
"Create labels",
"Color coding",
"Nested labels",
"Auto-labeling",
],
},
{
id: GmailOperation.SEARCH,
name: "Search Emails",
description: "Search emails with advanced filters",
icon: Search,
color: "bg-indigo-100 text-indigo-800 border-indigo-200",
features: ["Advanced search", "Filters", "Date ranges", "Custom queries"],
},
{
id: GmailOperation.ATTACHMENT,
name: "Handle Attachment",
description: "Download and manage email attachments",
icon: Paperclip,
color: "bg-teal-100 text-teal-800 border-teal-200",
features: [
"Download files",
"File info",
"Multiple formats",
"Batch processing",
],
},
];
export const GmailWizard: React.FC<WizardStepProps> = ({
data,
onChange,
onNext,
onPrevious,
}) => {
const [selectedOperation, setSelectedOperation] = useState<GmailOperation>(
data.operation || GmailOperation.SEND
);
const handleOperationSelect = (operation: GmailOperation) => {
setSelectedOperation(operation);
onChange({
...data,
operation,
});
};
const handleNext = () => {
if (selectedOperation) {
onNext();
}
};
return (
<div className="space-y-6">
<div className="text-center">
<div className="flex items-center justify-center w-16 h-16 mx-auto mb-4 bg-red-100 rounded-full">
<Mail className="w-8 h-8 text-red-600" />
</div>
<h2 className="text-2xl font-bold text-gray-900">Gmail Integration</h2>
<p className="text-gray-600 mt-2">
Choose what type of Gmail operation you want to perform
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{GMAIL_OPERATIONS.map((operation) => {
const IconComponent = operation.icon;
const isSelected = selectedOperation === operation.id;
return (
<Card
key={operation.id}
className={`cursor-pointer transition-all duration-200 ${
isSelected
? "ring-2 ring-red-500 bg-red-50"
: "hover:shadow-md hover:border-red-200"
}`}
onClick={() => handleOperationSelect(operation.id)}
>
<CardHeader className="pb-3">
<div className="flex items-center space-x-3">
<div className={`p-2 rounded-lg ${operation.color}`}>
<IconComponent className="w-5 h-5" />
</div>
<div className="flex-1">
<CardTitle className="text-lg font-semibold">
{operation.name}
</CardTitle>
<p className="text-sm text-gray-600 mt-1">
{operation.description}
</p>
</div>
{isSelected && (
<CheckCircle className="w-5 h-5 text-red-600" />
)}
</div>
</CardHeader>
<CardContent className="pt-0">
<div className="space-y-2">
{operation.features.map((feature, index) => (
<Badge
key={index}
variant="secondary"
className="text-xs mr-1 mb-1"
>
{feature}
</Badge>
))}
</div>
</CardContent>
</Card>
);
})}
</div>
<div className="flex justify-between pt-6">
<Button
variant="outline"
onClick={onPrevious}
className="flex items-center space-x-2"
>
<Settings className="w-4 h-4" />
<span>Back</span>
</Button>
<Button
onClick={handleNext}
disabled={!selectedOperation}
className="flex items-center space-x-2 bg-red-600 hover:bg-red-700"
>
<span>Next: Configure {selectedOperation}</span>
<CheckCircle className="w-4 h-4" />
</Button>
</div>
</div>
);
};
+37
View File
@@ -388,6 +388,42 @@ export const fieldConfigs: Record<string, FieldConfig> = {
maxLength: 50,
},
},
// Gmail-specific fields
"gmail.accessToken": {
technicalName: "accessToken",
userFriendlyName: "Gmail Access Token",
description: "Your Gmail API access token for authentication",
helpText:
"OAuth2 access token for Gmail API. This allows the workflow to access your Gmail account.",
examples: [
"ya29.a0AfH6SMC... (OAuth2 token)",
"Obtain from Google Cloud Console",
],
validation: {
required: true,
},
},
"gmail.operation": {
technicalName: "operation",
userFriendlyName: "Gmail operation",
description: "Choose what type of Gmail operation to perform",
helpText:
"Select the type of Gmail operation you want to perform. Each operation has different configuration options.",
examples: [
"Send - Send emails with attachments",
"Read - Read emails and threads",
"Reply - Reply to specific messages",
"Forward - Forward emails",
"Draft - Create and manage drafts",
"Label - Manage labels and folders",
"Search - Search emails with filters",
"Attachment - Handle file attachments",
],
validation: {
required: true,
},
},
};
export const getFieldConfig = (
@@ -426,6 +462,7 @@ export const getFieldsForNodeType = (nodeType: string): string[] => {
database: ["connectionId", "operation", "label"],
slack: ["botToken", "operation", "label"],
discord: ["botToken", "operation", "label"],
gmail: ["accessToken", "operation", "label"],
};
return fieldMappings[nodeType] || [];
+22
View File
@@ -171,6 +171,28 @@ export const nodeTypeConfigs: Record<NodeType, NodeTypeConfig> = {
"Welcome new members",
],
},
gmail: {
technicalName: "gmail",
userFriendlyName: "Gmail Integration",
description:
"Send, read, manage emails, labels, drafts, and search Gmail messages",
category: "Communication",
icon: "📧",
helpText:
"Integrate with Gmail to send emails, read messages, manage labels, create drafts, and search your inbox. Perfect for email automation and communication workflows.",
commonUseCases: [
"Send automated emails",
"Read and process incoming emails",
"Reply to emails automatically",
"Forward important messages",
"Create and manage email drafts",
"Organize emails with labels",
"Search emails with filters",
"Handle email attachments",
"Email marketing campaigns",
"Customer support automation",
],
},
};
export const getNodeTypeConfig = (nodeType: NodeType): NodeTypeConfig => {
+2
View File
@@ -836,6 +836,8 @@ export class ExecutionEngine {
return (await import("./processors/slackProcessor")).default;
case "discord":
return (await import("./processors/discordProcessor")).default;
case "gmail":
return (await import("./processors/gmailProcessor")).default;
case "structuredOutput":
return (await import("./processors/structuredOutputProcessor")).default;
case "embeddingGenerator":
+302
View File
@@ -0,0 +1,302 @@
import { GmailOperation, GmailConfig } from "../../types/gmail";
export class GmailService {
private baseUrl = "https://gmail.googleapis.com/gmail/v1";
async executeOperation(
operation: GmailOperation,
config: GmailConfig
): Promise<any> {
try {
switch (operation) {
case GmailOperation.SEND:
return await this.sendEmail(config);
case GmailOperation.READ:
return await this.readEmail(config);
case GmailOperation.REPLY:
return await this.replyToEmail(config);
case GmailOperation.FORWARD:
return await this.forwardEmail(config);
case GmailOperation.DRAFT:
return await this.manageDraft(config);
case GmailOperation.LABEL:
return await this.manageLabel(config);
case GmailOperation.SEARCH:
return await this.searchEmails(config);
case GmailOperation.ATTACHMENT:
return await this.handleAttachment(config);
default:
throw new Error(`Unsupported Gmail operation: ${operation}`);
}
} catch (error) {
throw new Error(
`Gmail operation failed: ${
error instanceof Error ? error.message : "Unknown error"
}`
);
}
}
private async sendEmail(config: GmailConfig): Promise<any> {
const message = this.createMessage(config);
return await this.makeRequest(
"/users/me/messages/send",
{
method: "POST",
body: JSON.stringify({ raw: message }),
},
config.accessToken
);
}
private async readEmail(config: GmailConfig): Promise<any> {
if (config.messageId) {
return await this.makeRequest(
`/users/me/messages/${config.messageId}`,
{
method: "GET",
},
config.accessToken
);
}
// List messages with query
const params = new URLSearchParams();
if (config.query) params.append("q", config.query);
if (config.maxResults)
params.append("maxResults", config.maxResults.toString());
return await this.makeRequest(
`/users/me/messages?${params.toString()}`,
{
method: "GET",
},
config.accessToken
);
}
private async replyToEmail(config: GmailConfig): Promise<any> {
const message = this.createMessage(config);
return await this.makeRequest(
`/users/me/messages/${config.replyToMessageId}/reply`,
{
method: "POST",
body: JSON.stringify({ raw: message }),
},
config.accessToken
);
}
private async forwardEmail(config: GmailConfig): Promise<any> {
const message = this.createMessage(config);
return await this.makeRequest(
`/users/me/messages/${config.messageId}/forward`,
{
method: "POST",
body: JSON.stringify({ raw: message }),
},
config.accessToken
);
}
private async manageDraft(config: GmailConfig): Promise<any> {
if (config.draftId) {
// Get existing draft
return await this.makeRequest(
`/users/me/drafts/${config.draftId}`,
{
method: "GET",
},
config.accessToken
);
}
// Create new draft
const message = this.createMessage(config);
return await this.makeRequest(
"/users/me/drafts",
{
method: "POST",
body: JSON.stringify({
message: { raw: message },
}),
},
config.accessToken
);
}
private async manageLabel(config: GmailConfig): Promise<any> {
if (config.labelId) {
// Get existing label
return await this.makeRequest(
`/users/me/labels/${config.labelId}`,
{
method: "GET",
},
config.accessToken
);
}
// Create new label
const labelData = {
name: config.labelName,
labelListVisibility: "labelShow",
messageListVisibility: "show",
};
if (config.labelColor) {
labelData.color = { textColor: config.labelColor };
}
return await this.makeRequest(
"/users/me/labels",
{
method: "POST",
body: JSON.stringify(labelData),
},
config.accessToken
);
}
private async searchEmails(config: GmailConfig): Promise<any> {
const params = new URLSearchParams();
if (config.searchQuery) params.append("q", config.searchQuery);
if (config.maxResults)
params.append("maxResults", config.maxResults.toString());
if (config.includeSpamTrash) params.append("includeSpamTrash", "true");
return await this.makeRequest(
`/users/me/messages?${params.toString()}`,
{
method: "GET",
},
config.accessToken
);
}
private async handleAttachment(config: GmailConfig): Promise<any> {
return await this.makeRequest(
`/users/me/messages/${config.messageId}/attachments/${config.attachmentId}`,
{
method: "GET",
},
config.accessToken
);
}
private createMessage(config: GmailConfig): string {
const headers = [];
if (config.to) headers.push(`To: ${config.to}`);
if (config.cc) headers.push(`Cc: ${config.cc}`);
if (config.bcc) headers.push(`Bcc: ${config.bcc}`);
if (config.subject) headers.push(`Subject: ${config.subject}`);
headers.push("Content-Type: text/html; charset=utf-8");
headers.push(""); // Empty line before body
const body = config.htmlBody || config.body || "";
const fullMessage = [...headers, body].join("\n");
return Buffer.from(fullMessage).toString("base64url");
}
private async makeRequest(
endpoint: string,
options: RequestInit,
accessToken: string
): Promise<any> {
const url = `${this.baseUrl}${endpoint}`;
const response = await fetch(url, {
...options,
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
...options.headers,
},
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(
`Gmail API error: ${response.status} ${response.statusText} - ${
errorData.error?.message || "Unknown error"
}`
);
}
return await response.json();
}
validateConfig(config: GmailConfig): {
isValid: boolean;
errors: string[];
} {
const errors: string[] = [];
if (!config.accessToken) {
errors.push("Access token is required");
}
if (!config.operation) {
errors.push("Operation is required");
}
switch (config.operation) {
case GmailOperation.SEND:
if (!config.to)
errors.push("Recipient email is required for send operation");
if (!config.subject)
errors.push("Subject is required for send operation");
if (!config.body && !config.htmlBody)
errors.push("Message body is required for send operation");
break;
case GmailOperation.READ:
if (!config.messageId && !config.query) {
errors.push(
"Either message ID or search query is required for read operation"
);
}
break;
case GmailOperation.REPLY:
if (!config.replyToMessageId)
errors.push("Reply to message ID is required for reply operation");
if (!config.body && !config.htmlBody)
errors.push("Reply message body is required");
break;
case GmailOperation.FORWARD:
if (!config.messageId)
errors.push("Message ID is required for forward operation");
if (!config.to)
errors.push("Recipient email is required for forward operation");
break;
case GmailOperation.DRAFT:
if (!config.subject)
errors.push("Subject is required for draft operation");
break;
case GmailOperation.LABEL:
if (!config.labelName && !config.labelId) {
errors.push(
"Either label name or label ID is required for label operation"
);
}
break;
case GmailOperation.SEARCH:
if (!config.searchQuery)
errors.push("Search query is required for search operation");
break;
case GmailOperation.ATTACHMENT:
if (!config.messageId)
errors.push("Message ID is required for attachment operation");
if (!config.attachmentId)
errors.push("Attachment ID is required for attachment operation");
break;
}
return {
isValid: errors.length === 0,
errors,
};
}
}
@@ -10,6 +10,7 @@ export class ProcessorRegistry {
"database",
"slack",
"discord",
"gmail",
"structuredOutput",
"embeddingGenerator",
"similaritySearch",
@@ -155,6 +156,10 @@ export class ProcessorRegistry {
pattern: /discord|gaming|community|server/i,
types: ["discord"],
},
{
pattern: /gmail|email|mail/i,
types: ["gmail"],
},
{
pattern: /embed|vector/i,
types: ["embeddingGenerator", "similaritySearch"],
+54
View File
@@ -0,0 +1,54 @@
import { NodeData } from "../../types";
import { GmailService } from "../gmail/GmailService";
import { GmailOperation } from "../../types/gmail";
async function gmailProcessor(nodeData: NodeData): Promise<{
success: boolean;
data?: any;
error?: string;
}> {
try {
const { config } = nodeData;
if (!config) {
return {
success: false,
error: "Gmail configuration is missing",
};
}
const gmailService = new GmailService();
// Validate configuration
const validation = gmailService.validateConfig(config as any);
if (!validation.isValid) {
return {
success: false,
error: `Configuration validation failed: ${validation.errors.join(
", "
)}`,
};
}
// Execute Gmail operation
const result = await gmailService.executeOperation(
config.operation as GmailOperation,
config as any
);
return {
success: true,
data: result,
};
} catch (error) {
return {
success: false,
error:
error instanceof Error
? error.message
: "Unknown Gmail operation error",
};
}
}
export default gmailProcessor;
+50
View File
@@ -0,0 +1,50 @@
export enum GmailOperation {
SEND = "send",
READ = "read",
REPLY = "reply",
FORWARD = "forward",
DRAFT = "draft",
LABEL = "label",
SEARCH = "search",
ATTACHMENT = "attachment",
}
export interface GmailConfig {
accessToken: string;
operation: GmailOperation;
label: string;
// Send operations
to?: string;
cc?: string;
bcc?: string;
subject?: string;
body?: string;
htmlBody?: string;
// Read operations
messageId?: string;
query?: string;
maxResults?: number;
// Reply/Forward operations
threadId?: string;
replyToMessageId?: string;
// Draft operations
draftId?: string;
// Label operations
labelName?: string;
labelId?: string;
labelColor?: string;
// Search operations
searchQuery?: string;
includeSpamTrash?: boolean;
// Attachment operations
attachmentId?: string;
filename?: string;
mimeType?: string;
}
+3 -1
View File
@@ -21,7 +21,9 @@ export type NodeType =
| "database"
// Slack integration
| "slack"
| "discord";
| "discord"
// Gmail integration
| "gmail";
export type NodeStatus =
| "idle"
+12
View File
@@ -140,6 +140,18 @@ const NODE_COMPATIBILITY: Record<
description:
"Discord nodes typically send messages and updates to community channels",
},
gmail: {
canConnectTo: ["dataOutput"],
shouldConnectTo: ["dataOutput"],
commonPatterns: [
"Process → Send email",
"Generate report → Email results",
"Data analysis → Email summary",
"Notifications → Email alerts",
],
description:
"Gmail nodes typically send emails and notifications to recipients",
},
};
/**