| |
| |
| |
| |
| |
| |
| import type { WorkflowIntent, WorkflowGraph } from '../types/workflow'; |
| import { getNodeDef } from '../knowledge/nodeRegistry'; |
|
|
| export interface TriggerConfig { |
| nodeType: string; |
| displayName: string; |
| parameters: Record<string, unknown>; |
| credentials?: Record<string, { id: string; name: string }>; |
| webhookPath?: string; |
| notes: string; |
| } |
|
|
| |
| const TRIGGER_RULES: Array<{ |
| keywords: string[]; |
| integrations?: string[]; |
| domain?: string[]; |
| triggerType: string; |
| nodeType: string; |
| priority: number; |
| }> = [ |
| |
| { |
| keywords: ['telegram', 'bot', 'message', 'chat'], |
| integrations: ['telegram'], |
| triggerType: 'telegram', |
| nodeType: 'n8n-nodes-base.telegramTrigger', |
| priority: 100, |
| }, |
| |
| { |
| keywords: ['slack', 'channel', 'workspace'], |
| integrations: ['slack'], |
| triggerType: 'slack', |
| nodeType: 'n8n-nodes-base.slackTrigger', |
| priority: 100, |
| }, |
| |
| { |
| keywords: ['github', 'push', 'pull request', 'issue', 'commit', 'repository'], |
| integrations: ['github'], |
| triggerType: 'github', |
| nodeType: 'n8n-nodes-base.githubTrigger', |
| priority: 100, |
| }, |
| |
| { |
| keywords: ['email', 'inbox', 'imap', 'mail received', 'new email', 'receive email'], |
| integrations: ['email', 'imap', 'gmail'], |
| triggerType: 'email', |
| nodeType: 'n8n-nodes-base.emailReadImap', |
| priority: 90, |
| }, |
| |
| { |
| keywords: ['notion', 'database page', 'notion page'], |
| integrations: ['notion'], |
| triggerType: 'notion', |
| nodeType: 'n8n-nodes-base.notionTrigger', |
| priority: 90, |
| }, |
| |
| { |
| keywords: [ |
| 'every hour', 'every day', 'daily', 'hourly', 'weekly', 'monthly', 'schedule', |
| 'cron', 'at midnight', 'every morning', 'every night', 'periodic', 'interval', |
| 'every minute', 'every 5 minutes', 'nightly', 'scheduled', |
| ], |
| triggerType: 'schedule', |
| nodeType: 'n8n-nodes-base.scheduleTrigger', |
| priority: 80, |
| }, |
| |
| { |
| keywords: [ |
| 'webhook', 'api call', 'http request', 'post request', 'form submission', |
| 'when called', 'endpoint', 'receive data', 'api endpoint', 'rest api', |
| 'when a request', 'when triggered via', |
| ], |
| triggerType: 'webhook', |
| nodeType: 'n8n-nodes-base.webhook', |
| priority: 70, |
| }, |
| |
| { |
| keywords: ['manually', 'manual trigger', 'on demand', 'test', 'demo'], |
| triggerType: 'manual', |
| nodeType: 'n8n-nodes-base.manualTrigger', |
| priority: 10, |
| }, |
| ]; |
|
|
| export class WebhookAutoBindSystem { |
|
|
| |
| |
| |
| detectTrigger( |
| userRequest: string, |
| intent: WorkflowIntent, |
| ): { nodeType: string; triggerType: string; confidence: 'high' | 'medium' | 'low' } { |
| const requestLower = userRequest.toLowerCase(); |
| const integrationLower = intent.integrations.map((i) => i.toLowerCase()); |
|
|
| let bestMatch: (typeof TRIGGER_RULES)[0] | undefined; |
| let bestScore = 0; |
|
|
| for (const rule of TRIGGER_RULES) { |
| let score = 0; |
|
|
| |
| const keywordMatches = rule.keywords.filter((kw) => requestLower.includes(kw)); |
| score += keywordMatches.length * 10; |
|
|
| |
| if (rule.integrations) { |
| const integrationMatches = rule.integrations.filter((i) => |
| integrationLower.some((intent_i) => intent_i.includes(i) || i.includes(intent_i)), |
| ); |
| score += integrationMatches.length * 20; |
| } |
|
|
| |
| if (intent.triggerType && intent.triggerType === rule.triggerType) { |
| score += 50; |
| } |
|
|
| |
| score += rule.priority * 0.1; |
|
|
| if (score > bestScore) { |
| bestScore = score; |
| bestMatch = rule; |
| } |
| } |
|
|
| |
| const nodeType = bestMatch?.nodeType ?? 'n8n-nodes-base.webhook'; |
| const triggerType = bestMatch?.triggerType ?? 'webhook'; |
| const confidence: 'high' | 'medium' | 'low' = |
| bestScore >= 20 ? 'high' : bestScore >= 10 ? 'medium' : 'low'; |
|
|
| return { nodeType, triggerType, confidence }; |
| } |
|
|
| |
| |
| |
| buildTriggerConfig( |
| nodeType: string, |
| userRequest: string, |
| intent: WorkflowIntent, |
| workflowName: string, |
| ): TriggerConfig { |
| switch (nodeType) { |
| case 'n8n-nodes-base.webhook': |
| return this.buildWebhookConfig(workflowName, intent); |
|
|
| case 'n8n-nodes-base.telegramTrigger': |
| return this.buildTelegramTriggerConfig(); |
|
|
| case 'n8n-nodes-base.scheduleTrigger': |
| return this.buildScheduleConfig(userRequest); |
|
|
| case 'n8n-nodes-base.emailReadImap': |
| return this.buildEmailTriggerConfig(); |
|
|
| case 'n8n-nodes-base.slackTrigger': |
| return this.buildSlackTriggerConfig(); |
|
|
| case 'n8n-nodes-base.githubTrigger': |
| return this.buildGitHubTriggerConfig(); |
|
|
| case 'n8n-nodes-base.notionTrigger': |
| return this.buildNotionTriggerConfig(); |
|
|
| case 'n8n-nodes-base.manualTrigger': |
| return this.buildManualTriggerConfig(); |
|
|
| default: |
| return this.buildWebhookConfig(workflowName, intent); |
| } |
| } |
|
|
| |
| |
| |
| injectTriggerIntoGraph( |
| graph: WorkflowGraph, |
| triggerConfig: TriggerConfig, |
| triggerNodeType: string, |
| ): WorkflowGraph { |
| const def = getNodeDef(triggerNodeType); |
| const triggerId = 'trigger-auto-' + Date.now(); |
|
|
| |
| const existingTrigger = graph.nodes.find((n) => n.layer === 'trigger'); |
|
|
| if (existingTrigger) { |
| |
| const updatedNodes = graph.nodes.map((node) => { |
| if (node.layer !== 'trigger') return node; |
| return { |
| ...node, |
| n8nNodeType: triggerNodeType, |
| label: triggerConfig.displayName, |
| autoConfigured: true, |
| webhookPath: triggerConfig.webhookPath, |
| }; |
| }); |
| return { ...graph, nodes: updatedNodes }; |
| } |
|
|
| |
| const newTriggerNode = { |
| id: triggerId, |
| label: triggerConfig.displayName, |
| n8nNodeType: triggerNodeType, |
| layer: 'trigger' as const, |
| description: triggerConfig.notes, |
| isCritical: true, |
| autoConfigured: true, |
| webhookPath: triggerConfig.webhookPath, |
| position: { x: 0, y: 300 }, |
| parameters: triggerConfig.parameters, |
| retryPolicy: undefined, |
| dataContract: { |
| inputs: [], |
| outputs: ['body', 'headers', 'query', 'params'], |
| }, |
| }; |
|
|
| |
| const shiftedNodes = graph.nodes.map((node) => ({ |
| ...node, |
| position: { x: (node.position?.x ?? 0) + 220, y: node.position?.y ?? 300 }, |
| })); |
|
|
| |
| const firstNode = shiftedNodes[0]; |
| const newEdge = firstNode ? { |
| id: `edge-trigger-to-${firstNode.id}`, |
| sourceNodeId: triggerId, |
| targetNodeId: firstNode.id, |
| outputIndex: 0, |
| inputIndex: 0, |
| label: 'on receive', |
| } : undefined; |
|
|
| return { |
| ...graph, |
| nodes: [newTriggerNode, ...shiftedNodes], |
| edges: newEdge ? [newEdge, ...graph.edges] : graph.edges, |
| }; |
| } |
|
|
| |
|
|
| private buildWebhookConfig(workflowName: string, intent: WorkflowIntent): TriggerConfig { |
| const slug = workflowName |
| .toLowerCase() |
| .replace(/[^a-z0-9]+/g, '-') |
| .replace(/^-+|-+$/g, '') |
| .slice(0, 40); |
| const path = `wfo/${slug}`; |
|
|
| return { |
| nodeType: 'n8n-nodes-base.webhook', |
| displayName: 'Webhook Trigger', |
| parameters: { |
| httpMethod: intent.syncVsAsync === 'sync' ? 'POST' : 'POST', |
| path, |
| responseMode: intent.syncVsAsync === 'sync' ? 'lastNode' : 'onReceived', |
| responseData: 'allEntries', |
| options: {}, |
| }, |
| webhookPath: path, |
| notes: `Auto-configured webhook trigger. Path: /webhook/${path}. Method: POST. Response: ${intent.syncVsAsync === 'sync' ? 'synchronous (after processing)' : 'immediate acknowledgement'}.`, |
| }; |
| } |
|
|
| private buildTelegramTriggerConfig(): TriggerConfig { |
| return { |
| nodeType: 'n8n-nodes-base.telegramTrigger', |
| displayName: 'Telegram Trigger', |
| parameters: { |
| updates: ['message', 'callback_query'], |
| }, |
| credentials: { |
| telegramApi: { id: '', name: 'Telegram Bot API' }, |
| }, |
| notes: 'Auto-configured Telegram trigger. Listens for messages and callback queries. Requires telegramApi credential.', |
| }; |
| } |
|
|
| private buildScheduleConfig(userRequest: string): TriggerConfig { |
| |
| const req = userRequest.toLowerCase(); |
| let interval: Record<string, unknown>[] = [{ field: 'hours', hoursInterval: 1 }]; |
|
|
| if (req.includes('every minute')) interval = [{ field: 'minutes', minutesInterval: 1 }]; |
| else if (req.includes('every 5 minute')) interval = [{ field: 'minutes', minutesInterval: 5 }]; |
| else if (req.includes('every 15 minute')) interval = [{ field: 'minutes', minutesInterval: 15 }]; |
| else if (req.includes('every 30 minute')) interval = [{ field: 'minutes', minutesInterval: 30 }]; |
| else if (req.includes('every hour') || req.includes('hourly')) interval = [{ field: 'hours', hoursInterval: 1 }]; |
| else if (req.includes('every 6 hour')) interval = [{ field: 'hours', hoursInterval: 6 }]; |
| else if (req.includes('every 12 hour')) interval = [{ field: 'hours', hoursInterval: 12 }]; |
| else if (req.includes('daily') || req.includes('every day') || req.includes('nightly') || req.includes('midnight')) { |
| interval = [{ field: 'cronExpression', expression: '0 0 * * *' }]; |
| } |
| else if (req.includes('weekly') || req.includes('every week')) { |
| interval = [{ field: 'cronExpression', expression: '0 9 * * 1' }]; |
| } |
| else if (req.includes('monthly') || req.includes('every month')) { |
| interval = [{ field: 'cronExpression', expression: '0 9 1 * *' }]; |
| } |
|
|
| return { |
| nodeType: 'n8n-nodes-base.scheduleTrigger', |
| displayName: 'Schedule Trigger', |
| parameters: { |
| rule: { interval }, |
| }, |
| notes: `Auto-configured schedule trigger. Interval detected from request: ${JSON.stringify(interval)}.`, |
| }; |
| } |
|
|
| private buildEmailTriggerConfig(): TriggerConfig { |
| return { |
| nodeType: 'n8n-nodes-base.emailReadImap', |
| displayName: 'Email Trigger (IMAP)', |
| parameters: { |
| mailbox: 'INBOX', |
| action: 'read', |
| downloadAttachments: false, |
| format: 'simple', |
| options: {}, |
| }, |
| credentials: { |
| imap: { id: '', name: 'IMAP Account' }, |
| }, |
| notes: 'Auto-configured IMAP email trigger. Monitors INBOX for new emails. Requires IMAP credential.', |
| }; |
| } |
|
|
| private buildSlackTriggerConfig(): TriggerConfig { |
| return { |
| nodeType: 'n8n-nodes-base.slackTrigger', |
| displayName: 'Slack Trigger', |
| parameters: { |
| trigger: 'any_message', |
| }, |
| credentials: { |
| slackOAuth2Api: { id: '', name: 'Slack OAuth2' }, |
| }, |
| notes: 'Auto-configured Slack trigger. Listens for messages in configured channels. Requires Slack OAuth2 credential.', |
| }; |
| } |
|
|
| private buildGitHubTriggerConfig(): TriggerConfig { |
| return { |
| nodeType: 'n8n-nodes-base.githubTrigger', |
| displayName: 'GitHub Trigger', |
| parameters: { |
| owner: '', |
| repository: '', |
| events: ['push'], |
| }, |
| credentials: { |
| githubApi: { id: '', name: 'GitHub API' }, |
| }, |
| notes: 'Auto-configured GitHub trigger. Listens for push events. Set owner and repository in parameters.', |
| }; |
| } |
|
|
| private buildNotionTriggerConfig(): TriggerConfig { |
| return { |
| nodeType: 'n8n-nodes-base.notionTrigger', |
| displayName: 'Notion Trigger', |
| parameters: { |
| databaseId: '', |
| event: 'page_added', |
| simple: true, |
| }, |
| credentials: { |
| notionApi: { id: '', name: 'Notion API' }, |
| }, |
| notes: 'Auto-configured Notion trigger. Monitors a database for new pages. Set databaseId in parameters.', |
| }; |
| } |
|
|
| private buildManualTriggerConfig(): TriggerConfig { |
| return { |
| nodeType: 'n8n-nodes-base.manualTrigger', |
| displayName: 'Manual Trigger', |
| parameters: {}, |
| notes: 'Manual trigger β start this workflow by clicking "Execute Workflow" in n8n.', |
| }; |
| } |
| } |
|
|
| |
| export const webhookAutoBind = new WebhookAutoBindSystem(); |
|
|