UNPKG

flowengine-n8n-workflow-builder

Version:

Build n8n workflows from text using AI. Connect to Claude, Cursor, or any LLM to generate and validate n8n workflows with expert knowledge and intelligent auto-fixing. Built by FlowEngine. Now with real node parameter schemas from n8n packages!

544 lines (473 loc) 16.7 kB
/** * Intelligence Engine - AI-Driven Workflow Analysis and Suggestions * * Provides intelligent pattern recognition, architecture recommendations, * and context-aware suggestions for workflow building. */ import type { Workflow, WorkflowNode } from './generator.js'; import { searchNodes, getNodesByCategory } from './nodes.js'; export interface ArchitecturePattern { name: string; description: string; useCases: string[]; complexity: 'simple' | 'moderate' | 'complex'; requiredNodes: string[]; optionalNodes: string[]; } export interface WorkflowInsight { type: 'info' | 'warning' | 'suggestion' | 'optimization'; message: string; severity: 'low' | 'medium' | 'high'; suggestedFix?: string; } /** * Architecture Patterns */ export const ARCHITECTURE_PATTERNS: Record<string, ArchitecturePattern> = { linear: { name: 'Linear Pipeline', description: 'Simple A -> B -> C flow for straightforward data processing', useCases: ['Data transformation', 'API integration', 'Simple automation'], complexity: 'simple', requiredNodes: ['trigger', 'action'], optionalNodes: ['error-handler'], }, conditional: { name: 'Conditional Branching', description: 'IF/THEN/ELSE logic for decision-based routing', useCases: ['Content filtering', 'Conditional notifications', 'Smart routing'], complexity: 'moderate', requiredNodes: ['trigger', 'if', 'action'], optionalNodes: ['merge', 'error-handler'], }, parallel: { name: 'Parallel Processing', description: 'Split work across multiple parallel paths and merge results', useCases: ['Multi-API calls', 'Concurrent operations', 'Fan-out/fan-in'], complexity: 'moderate', requiredNodes: ['trigger', 'split', 'merge'], optionalNodes: ['error-handler'], }, aiAgent: { name: 'AI Agent', description: 'Autonomous agent with tools, memory, and decision-making', useCases: ['Customer support', 'Data analysis', 'Task automation'], complexity: 'complex', requiredNodes: ['trigger', 'agent', 'model'], optionalNodes: ['tools', 'memory', 'output-parser'], }, eventDriven: { name: 'Event-Driven', description: 'React to external events with webhook/trigger handlers', useCases: ['Real-time alerts', 'Webhook processing', 'Event streaming'], complexity: 'moderate', requiredNodes: ['webhook', 'action'], optionalNodes: ['filter', 'error-handler'], }, dataTransformation: { name: 'ETL Pipeline', description: 'Extract, Transform, Load data processing', useCases: ['Data migration', 'Report generation', 'Database sync'], complexity: 'moderate', requiredNodes: ['trigger', 'extract', 'transform', 'load'], optionalNodes: ['validation', 'error-handler'], }, orchestration: { name: 'Multi-Agent Orchestration', description: 'Coordinate multiple AI agents for complex tasks', useCases: ['Complex reasoning', 'Multi-step analysis', 'Collaborative agents'], complexity: 'complex', requiredNodes: ['trigger', 'router', 'multiple-agents', 'aggregator'], optionalNodes: ['memory', 'tools'], }, }; /** * Suggest architecture pattern based on description */ export function suggestArchitecture(description: string): { recommended: ArchitecturePattern; alternatives: ArchitecturePattern[]; confidence: number; } { const lower = description.toLowerCase(); const scores: Record<string, number> = {}; // Score each pattern based on keywords for (const [key, pattern] of Object.entries(ARCHITECTURE_PATTERNS)) { let score = 0; // Check use cases pattern.useCases.forEach(useCase => { if (lower.includes(useCase.toLowerCase())) score += 3; }); // Check pattern name/description if (lower.includes(pattern.name.toLowerCase())) score += 2; if (lower.includes(pattern.description.toLowerCase())) score += 1; // Specific keywords if (key === 'aiAgent' && (lower.includes('ai') || lower.includes('agent') || lower.includes('llm'))) score += 5; if (key === 'conditional' && (lower.includes('if') || lower.includes('condition') || lower.includes('when'))) score += 4; if (key === 'parallel' && (lower.includes('parallel') || lower.includes('concurrent'))) score += 4; if (key === 'eventDriven' && (lower.includes('webhook') || lower.includes('event') || lower.includes('trigger'))) score += 4; if (key === 'dataTransformation' && (lower.includes('etl') || lower.includes('transform') || lower.includes('data'))) score += 3; scores[key] = score; } // Get top patterns const sorted = Object.entries(scores) .sort(([, a], [, b]) => b - a) .map(([key]) => key); const topPattern = sorted[0] || 'linear'; const confidence = Math.min(scores[topPattern] / 10, 1); return { recommended: ARCHITECTURE_PATTERNS[topPattern], alternatives: sorted.slice(1, 4).map(key => ARCHITECTURE_PATTERNS[key]), confidence, }; } /** * Suggest nodes for a task */ export function suggestNodes(task: string): Array<{ node: string; reason: string; confidence: number; }> { const suggestions: Array<{ node: string; reason: string; confidence: number }> = []; const lower = task.toLowerCase(); // Communication tools if (lower.includes('email') || lower.includes('gmail')) { suggestions.push({ node: 'n8n-nodes-base.gmail', reason: 'Send and manage Gmail emails', confidence: 0.9, }); } if (lower.includes('slack')) { suggestions.push({ node: 'n8n-nodes-base.slack', reason: 'Send Slack messages', confidence: 0.95, }); } if (lower.includes('telegram')) { suggestions.push({ node: 'n8n-nodes-base.telegram', reason: 'Send Telegram messages', confidence: 0.9, }); } if (lower.includes('discord')) { suggestions.push({ node: 'n8n-nodes-base.discord', reason: 'Send Discord messages', confidence: 0.9, }); } // Data storage if (lower.includes('sheets') || lower.includes('google sheets')) { suggestions.push({ node: 'n8n-nodes-base.googleSheets', reason: 'Read/write Google Sheets data', confidence: 0.95, }); } if (lower.includes('airtable')) { suggestions.push({ node: 'n8n-nodes-base.airtable', reason: 'Manage Airtable records', confidence: 0.9, }); } if (lower.includes('database') || lower.includes('postgres') || lower.includes('sql')) { suggestions.push({ node: 'n8n-nodes-base.postgres', reason: 'Execute SQL queries', confidence: 0.8, }); } if (lower.includes('mongodb') || lower.includes('nosql')) { suggestions.push({ node: 'n8n-nodes-base.mongodb', reason: 'Query MongoDB database', confidence: 0.85, }); } // AI/Agent nodes if (lower.includes('ai') || lower.includes('agent') || lower.includes('llm')) { suggestions.push({ node: '@n8n/n8n-nodes-langchain.agent', reason: 'AI Agent with tools and memory', confidence: 0.95, }); // Suggest model based on preference if (lower.includes('openai') || lower.includes('gpt')) { suggestions.push({ node: '@n8n/n8n-nodes-langchain.lmChatOpenAi', reason: 'OpenAI language model', confidence: 0.9, }); } else if (lower.includes('anthropic') || lower.includes('claude')) { suggestions.push({ node: '@n8n/n8n-nodes-langchain.lmChatAnthropic', reason: 'Claude language model', confidence: 0.9, }); } else if (lower.includes('gemini') || lower.includes('google')) { suggestions.push({ node: '@n8n/n8n-nodes-langchain.lmChatGoogleGemini', reason: 'Google Gemini model', confidence: 0.9, }); } else { // Default to OpenAI suggestions.push({ node: '@n8n/n8n-nodes-langchain.lmChatOpenAi', reason: 'OpenAI language model (default)', confidence: 0.7, }); } } // HTTP/API if (lower.includes('api') || lower.includes('http') || lower.includes('webhook')) { suggestions.push({ node: 'n8n-nodes-base.httpRequest', reason: 'Make HTTP API requests', confidence: 0.85, }); } // Code/Transform if (lower.includes('code') || lower.includes('javascript') || lower.includes('custom logic')) { suggestions.push({ node: 'n8n-nodes-base.code', reason: 'Execute custom JavaScript', confidence: 0.8, }); } if (lower.includes('transform') || lower.includes('modify') || lower.includes('set')) { suggestions.push({ node: 'n8n-nodes-base.set', reason: 'Transform and set data fields', confidence: 0.75, }); } // Conditional logic if (lower.includes('if') || lower.includes('condition') || lower.includes('when')) { suggestions.push({ node: 'n8n-nodes-base.if', reason: 'Add conditional branching', confidence: 0.85, }); } if (lower.includes('switch') || lower.includes('route')) { suggestions.push({ node: 'n8n-nodes-base.switch', reason: 'Route to multiple branches', confidence: 0.8, }); } // Sort by confidence return suggestions.sort((a, b) => b.confidence - a.confidence); } /** * Analyze workflow and provide insights */ export function analyzeWorkflow(workflow: Workflow): WorkflowInsight[] { const insights: WorkflowInsight[] = []; // Check for missing error handling const hasErrorHandler = workflow.nodes.some(n => n.type.includes('errorTrigger') || n.type.includes('stopAndError') ); if (!hasErrorHandler && workflow.nodes.length > 3) { insights.push({ type: 'suggestion', message: 'Consider adding error handling to make workflow more robust', severity: 'medium', suggestedFix: 'Add an Error Trigger node or Stop and Error node', }); } // Check for AI agents without memory const hasAgent = workflow.nodes.some(n => n.type.includes('agent')); const hasMemory = workflow.nodes.some(n => n.type.includes('memory')); if (hasAgent && !hasMemory) { insights.push({ type: 'suggestion', message: 'AI Agent without memory - conversations won\'t be remembered', severity: 'low', suggestedFix: 'Add a Memory node (Window Buffer, Summary, etc.)', }); } // Check for agents without tools const hasTools = workflow.nodes.some(n => n.type.includes('Tool') || n.type.includes('tool')); if (hasAgent && !hasTools) { insights.push({ type: 'warning', message: 'AI Agent has no tools - limited capabilities', severity: 'high', suggestedFix: 'Add at least one tool (Calculator, Code, HTTP Request, etc.)', }); } // Check for complex workflows without split nodes if (workflow.nodes.length > 5 && !workflow.nodes.some(n => n.type.includes('if') || n.type.includes('switch') || n.type.includes('merge') )) { insights.push({ type: 'info', message: 'Linear workflow - consider adding conditional logic for flexibility', severity: 'low', suggestedFix: 'Add IF or Switch nodes for branching logic', }); } // Check for hardcoded credentials const hasCredentials = workflow.nodes.some(n => n.parameters && JSON.stringify(n.parameters).includes('api_key') ); if (hasCredentials) { insights.push({ type: 'warning', message: 'Possible hardcoded credentials detected', severity: 'high', suggestedFix: 'Use n8n credential system instead of hardcoding keys', }); } // Check node positioning (too close together) for (let i = 0; i < workflow.nodes.length - 1; i++) { const node1 = workflow.nodes[i]; const node2 = workflow.nodes[i + 1]; const distance = Math.sqrt( Math.pow(node2.position[0] - node1.position[0], 2) + Math.pow(node2.position[1] - node1.position[1], 2) ); if (distance < 150) { insights.push({ type: 'info', message: `Nodes "${node1.name}" and "${node2.name}" are too close together`, severity: 'low', suggestedFix: 'Increase spacing between nodes for better readability', }); break; // Only report once } } // Check for orphaned nodes (no connections) const connectedNodes = new Set<string>(); Object.values(workflow.connections).forEach(nodeConnections => { Object.values(nodeConnections as any).forEach((connectionArray: any) => { connectionArray?.forEach?.((connections: any) => { connections?.forEach?.((conn: any) => { if (conn?.node) connectedNodes.add(conn.node); }); }); }); }); const orphanedNodes = workflow.nodes.filter( n => n.type !== 'n8n-nodes-base.manualTrigger' && !n.type.includes('Trigger') && !connectedNodes.has(n.name) && !Object.keys(workflow.connections).includes(n.name) ); if (orphanedNodes.length > 0) { insights.push({ type: 'warning', message: `${orphanedNodes.length} orphaned node(s) with no connections`, severity: 'medium', suggestedFix: `Connect or remove: ${orphanedNodes.map(n => n.name).join(', ')}`, }); } return insights; } /** * Suggest improvements for a workflow */ export function suggestImprovements(workflow: Workflow): Array<{ category: string; suggestion: string; priority: 'low' | 'medium' | 'high'; }> { const improvements: Array<{ category: string; suggestion: string; priority: 'low' | 'medium' | 'high'; }> = []; const insights = analyzeWorkflow(workflow); // Convert insights to improvements insights.forEach(insight => { if (insight.type === 'suggestion' || insight.type === 'warning') { improvements.push({ category: insight.type === 'warning' ? 'Critical' : 'Enhancement', suggestion: insight.message + (insight.suggestedFix ? ` (${insight.suggestedFix})` : ''), priority: insight.severity as any, }); } }); // Performance improvements if (workflow.nodes.length > 10) { improvements.push({ category: 'Performance', suggestion: 'Consider splitting into multiple workflows for better performance', priority: 'medium', }); } // Naming improvements const hasGenericNames = workflow.nodes.some(n => n.name.includes('Node ') || n.name === 'HTTP Request' || n.name === 'Code' ); if (hasGenericNames) { improvements.push({ category: 'Maintainability', suggestion: 'Use descriptive names for nodes to improve workflow readability', priority: 'low', }); } return improvements; } /** * Explain what a workflow does in natural language */ export function explainWorkflow(workflow: Workflow): string { let explanation = `# ${workflow.name}\n\n`; // Identify pattern const hasAgent = workflow.nodes.some(n => n.type.includes('agent')); const hasIf = workflow.nodes.some(n => n.type.includes('if')); const hasMerge = workflow.nodes.some(n => n.type.includes('merge')); if (hasAgent) { explanation += 'This is an **AI Agent workflow** that:\n\n'; } else if (hasIf) { explanation += 'This is a **conditional workflow** that:\n\n'; } else if (hasMerge) { explanation += 'This is a **parallel processing workflow** that:\n\n'; } else { explanation += 'This is a **linear workflow** that:\n\n'; } // Describe trigger const trigger = workflow.nodes.find(n => n.type.includes('Trigger') || n.type.includes('trigger') ); if (trigger) { if (trigger.type.includes('manual')) { explanation += '1. Starts when manually triggered\n'; } else if (trigger.type.includes('webhook')) { explanation += '1. Starts when a webhook is called\n'; } else if (trigger.type.includes('schedule')) { explanation += '1. Runs on a schedule\n'; } else if (trigger.type.includes('email')) { explanation += '1. Triggers when new email arrives\n'; } else { explanation += '1. Starts automatically\n'; } } // Describe key actions const actions = workflow.nodes.filter(n => !n.type.includes('Trigger') && !n.type.includes('trigger') && !n.type.includes('memory') && !n.type.includes('model') ); let step = 2; actions.slice(0, 5).forEach(node => { const action = node.type.split('.').pop() || node.type; explanation += `${step}. ${node.name} (${action})\n`; step++; }); if (actions.length > 5) { explanation += `... and ${actions.length - 5} more steps\n`; } explanation += `\n**Total nodes:** ${workflow.nodes.length}\n`; explanation += `**Connections:** ${Object.keys(workflow.connections).length}\n`; return explanation; }