PYAE1994's picture
Upload folder using huggingface_hub
dd480ef verified
/**
* Webhook Auto-Bind System β€” NEW
* Automatically detects trigger type from intent and assigns correct trigger node
* Auto-generates webhook paths, auto-configures trigger settings
* User NEVER manually defines triggers
*/
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;
}
// ─── Trigger detection rules (ordered by specificity) ─────────────────────────
const TRIGGER_RULES: Array<{
keywords: string[];
integrations?: string[];
domain?: string[];
triggerType: string;
nodeType: string;
priority: number;
}> = [
// Telegram
{
keywords: ['telegram', 'bot', 'message', 'chat'],
integrations: ['telegram'],
triggerType: 'telegram',
nodeType: 'n8n-nodes-base.telegramTrigger',
priority: 100,
},
// Slack
{
keywords: ['slack', 'channel', 'workspace'],
integrations: ['slack'],
triggerType: 'slack',
nodeType: 'n8n-nodes-base.slackTrigger',
priority: 100,
},
// GitHub
{
keywords: ['github', 'push', 'pull request', 'issue', 'commit', 'repository'],
integrations: ['github'],
triggerType: 'github',
nodeType: 'n8n-nodes-base.githubTrigger',
priority: 100,
},
// Email (IMAP)
{
keywords: ['email', 'inbox', 'imap', 'mail received', 'new email', 'receive email'],
integrations: ['email', 'imap', 'gmail'],
triggerType: 'email',
nodeType: 'n8n-nodes-base.emailReadImap',
priority: 90,
},
// Notion
{
keywords: ['notion', 'database page', 'notion page'],
integrations: ['notion'],
triggerType: 'notion',
nodeType: 'n8n-nodes-base.notionTrigger',
priority: 90,
},
// Schedule / Cron
{
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,
},
// Webhook (general HTTP)
{
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,
},
// Manual (fallback)
{
keywords: ['manually', 'manual trigger', 'on demand', 'test', 'demo'],
triggerType: 'manual',
nodeType: 'n8n-nodes-base.manualTrigger',
priority: 10,
},
];
export class WebhookAutoBindSystem {
/**
* Detect the appropriate trigger node type from intent and request text
*/
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;
// Keyword matches in request
const keywordMatches = rule.keywords.filter((kw) => requestLower.includes(kw));
score += keywordMatches.length * 10;
// Integration matches
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;
}
// Exact trigger type hint from intent
if (intent.triggerType && intent.triggerType === rule.triggerType) {
score += 50;
}
// Apply rule priority as tiebreaker
score += rule.priority * 0.1;
if (score > bestScore) {
bestScore = score;
bestMatch = rule;
}
}
// Default to webhook if nothing matched
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 };
}
/**
* Generate complete trigger node configuration
*/
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);
}
}
/**
* Inject auto-configured trigger into a WorkflowGraph as the first node
*/
injectTriggerIntoGraph(
graph: WorkflowGraph,
triggerConfig: TriggerConfig,
triggerNodeType: string,
): WorkflowGraph {
const def = getNodeDef(triggerNodeType);
const triggerId = 'trigger-auto-' + Date.now();
// Check if graph already has a trigger node from the detected type
const existingTrigger = graph.nodes.find((n) => n.layer === 'trigger');
if (existingTrigger) {
// Update existing trigger with proper config
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 };
}
// Insert new trigger at front
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'],
},
};
// Shift existing nodes to the right
const shiftedNodes = graph.nodes.map((node) => ({
...node,
position: { x: (node.position?.x ?? 0) + 220, y: node.position?.y ?? 300 },
}));
// Connect trigger to first non-trigger node
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,
};
}
// ─── Trigger config builders ──────────────────────────────────────────────
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 {
// Detect schedule from request text
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' }]; // Monday 9am
}
else if (req.includes('monthly') || req.includes('every month')) {
interval = [{ field: 'cronExpression', expression: '0 9 1 * *' }]; // 1st of month 9am
}
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.',
};
}
}
// ─── Singleton export ─────────────────────────────────────────────────────────
export const webhookAutoBind = new WebhookAutoBindSystem();