UNPKG

n8n-nodes-enhanced-chat

Version:

Enhanced Chat UI for n8n - Complete interactive chat solution with Enhanced Chat Trigger, Chat UI Renderer, HTML Renderer, and Callback Handler nodes. Features dynamic forms, media, WebRTC, and interactive workflows.

596 lines (595 loc) 28.7 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ChatTrigger = void 0; const n8n_workflow_1 = require("n8n-workflow"); class ChatTrigger { constructor() { this.description = { displayName: 'Enhanced Chat Trigger', name: 'enhancedChatTrigger', icon: 'fa:comments', group: ['trigger'], version: 1, subtitle: '={{$parameter["chatTitle"] || "Enhanced Chat"}}', description: 'Enhanced Chat Trigger with advanced features, better UI, and multimedia support', defaults: { name: 'Enhanced Chat Trigger', }, inputs: [], outputs: ["main" /* NodeConnectionType.Main */], credentials: [], webhooks: [ { name: 'default', httpMethod: 'POST', responseMode: 'onReceived', path: 'enhanced-chat', }, ], triggerPanel: { header: 'Enhanced Chat Interface', executionsHelp: { inactive: 'Chat will trigger executions when messages are received. Make your workflow active to start receiving chat messages.', active: 'Chat is active and ready to receive messages. Use the chat interface below to interact.', }, activationHint: 'Once activated, you can interact with your workflow through the chat interface below.', }, properties: [ // === CHAT CONFIGURATION === { displayName: 'Chat Title', name: 'chatTitle', type: 'string', default: 'Enhanced AI Assistant', description: 'Title displayed in the chat interface', }, { displayName: 'Chat Subtitle', name: 'chatSubtitle', type: 'string', default: 'How can I help you today?', description: 'Subtitle shown in the chat welcome message', }, { displayName: 'Welcome Messages', name: 'welcomeMessages', type: 'fixedCollection', typeOptions: { multipleValues: true, }, default: { messages: [ { text: 'Hi there! 👋' }, { text: 'I\'m your Enhanced AI Assistant. How can I help you today?' } ] }, options: [ { name: 'messages', displayName: 'Message', values: [ { displayName: 'Message Text', name: 'text', type: 'string', default: '', description: 'Welcome message to display when chat starts', }, ], }, ], description: 'Welcome messages to display when the chat interface loads', }, // === CHAT BEHAVIOR === { displayName: 'Chat Mode', name: 'chatMode', type: 'options', options: [ { name: 'Window Mode', value: 'window', description: 'Chat toggle button with hide/show functionality', }, { name: 'Fullscreen Mode', value: 'fullscreen', description: 'Chat takes up entire available space', }, ], default: 'window', description: 'How the chat interface should be displayed', }, { displayName: 'Enable File Uploads', name: 'enableFileUploads', type: 'boolean', default: true, description: 'Allow users to upload files through the chat interface', }, { displayName: 'Allowed File Types', name: 'allowedFileTypes', type: 'string', default: 'image/*,application/pdf,.txt,.doc,.docx,.json', description: 'Comma-separated MIME types for allowed file uploads', displayOptions: { show: { enableFileUploads: [true], }, }, }, { displayName: 'Load Previous Session', name: 'loadPreviousSession', type: 'boolean', default: true, description: 'Whether to load previous chat messages from the session', }, // === RESPONSE CONFIGURATION === { displayName: 'Response Mode', name: 'responseMode', type: 'options', options: [ { name: 'Text Response', value: 'text', description: 'Send plain text responses', }, { name: 'JSON Response', value: 'json', description: 'Send structured JSON responses', }, { name: 'HTML Response', value: 'html', description: 'Send HTML formatted responses', }, { name: 'Workflow Output', value: 'workflow', description: 'Use workflow output as response', }, ], default: 'workflow', description: 'How to format responses sent back to the chat', }, { displayName: 'Default Response', name: 'defaultResponse', type: 'string', typeOptions: { rows: 3, }, default: 'Thank you for your message! I\'m processing your request...', description: 'Default response when workflow doesn\'t provide output', displayOptions: { show: { responseMode: ['text', 'json', 'html'], }, }, }, // === ENHANCED FEATURES === { displayName: 'Enhanced Features', name: 'enhancedFeatures', type: 'collection', placeholder: 'Add Feature', default: {}, options: [ { displayName: 'Enable Session Management', name: 'sessionManagement', type: 'boolean', default: true, description: 'Maintain conversation context across messages', }, { displayName: 'Enable Message History', name: 'messageHistory', type: 'boolean', default: true, description: 'Store and retrieve message history', }, { displayName: 'Auto-save Conversations', name: 'autoSaveConversations', type: 'boolean', default: false, description: 'Automatically save conversations to workflow memory', }, { displayName: 'Enable Typing Indicators', name: 'typingIndicators', type: 'boolean', default: true, description: 'Show typing indicators while processing', }, { displayName: 'Enable Message Timestamps', name: 'messageTimestamps', type: 'boolean', default: true, description: 'Display timestamps on messages', }, { displayName: 'Custom CSS Classes', name: 'customCssClasses', type: 'string', default: '', description: 'Custom CSS classes to apply to the chat interface', }, ], }, // === STYLING OPTIONS === { displayName: 'Styling Options', name: 'stylingOptions', type: 'collection', placeholder: 'Add Styling', default: {}, options: [ { displayName: 'Primary Color', name: 'primaryColor', type: 'string', default: '#667eea', description: 'Primary color for the chat interface (hex code)', }, { displayName: 'Secondary Color', name: 'secondaryColor', type: 'string', default: '#48bb78', description: 'Secondary color for chat elements (hex code)', }, { displayName: 'Background Color', name: 'backgroundColor', type: 'string', default: '#ffffff', description: 'Background color of the chat interface', }, { displayName: 'Text Color', name: 'textColor', type: 'string', default: '#333333', description: 'Primary text color', }, { displayName: 'Chat Width', name: 'chatWidth', type: 'number', default: 400, description: 'Width of chat window in pixels (window mode)', }, { displayName: 'Chat Height', name: 'chatHeight', type: 'number', default: 600, description: 'Height of chat window in pixels (window mode)', }, ], }, // === WEBHOOK CONFIGURATION === { displayName: 'Advanced Options', name: 'advancedOptions', type: 'collection', placeholder: 'Add Option', default: {}, options: [ { displayName: 'Custom Webhook Path', name: 'customWebhookPath', type: 'string', default: '', description: 'Custom path for webhook URL (leave empty for auto-generated)', }, { displayName: 'CORS Origins', name: 'corsOrigins', type: 'string', default: '*', description: 'Allowed origins for CORS (comma-separated domains or * for all)', }, { displayName: 'Rate Limiting', name: 'rateLimiting', type: 'options', options: [ { name: 'None', value: 'none' }, { name: 'Basic (10/min)', value: 'basic' }, { name: 'Moderate (5/min)', value: 'moderate' }, { name: 'Strict (2/min)', value: 'strict' }, ], default: 'basic', description: 'Rate limiting for chat messages', }, { displayName: 'Message Timeout (seconds)', name: 'messageTimeout', type: 'number', default: 30, description: 'Timeout for processing messages', }, ], }, // === CHAT INTERFACE PREVIEW === { displayName: 'Chat Interface', name: 'chatInterface', type: 'notice', typeOptions: { theme: 'info', }, default: '', description: 'Live chat interface will appear here when the workflow is activated. The chat will be fully functional and ready to receive messages.', }, ], }; this.methods = { loadOptions: { getChatModes() { return __awaiter(this, void 0, void 0, function* () { return [ { name: 'Window Mode', value: 'window', description: 'Chat toggle button with hide/show functionality', }, { name: 'Fullscreen Mode', value: 'fullscreen', description: 'Chat takes up entire container space', }, ]; }); }, }, }; } trigger() { return __awaiter(this, void 0, void 0, function* () { const webhookName = 'default'; const webhookUrl = this.getInstanceBaseUrl() + '/webhook/' + this.getNode().id; // Get node parameters with proper defaults const chatTitle = this.getNodeParameter('chatTitle', '') || 'Enhanced Chat Assistant'; const chatSubtitle = this.getNodeParameter('chatSubtitle', '') || 'How can I help you today?'; const chatMode = this.getNodeParameter('chatMode', 'window'); const enableFileUploads = this.getNodeParameter('enableFileUploads', true); const allowedFileTypes = this.getNodeParameter('allowedFileTypes', 'image/*,application/pdf,.txt,.doc,.docx,.json'); const loadPreviousSession = this.getNodeParameter('loadPreviousSession', true); const enhancedFeatures = this.getNodeParameter('enhancedFeatures', {}); const stylingOptions = this.getNodeParameter('stylingOptions', {}); // Create default welcome messages const initialMessages = [ 'Hi there! 👋', `I'm ${chatTitle}. ${chatSubtitle}` ]; // Build chat configuration const chatConfig = { webhookUrl: webhookUrl, mode: chatMode, target: '#enhanced-chat-widget', showWelcomeScreen: true, loadPreviousSession: loadPreviousSession, allowFileUploads: enableFileUploads, allowedFilesMimeTypes: enableFileUploads ? allowedFileTypes : '', initialMessages: initialMessages, chatInputKey: 'chatInput', chatSessionKey: 'sessionId', defaultLanguage: 'en', i18n: { en: { title: chatTitle, subtitle: chatSubtitle, inputPlaceholder: 'Type your message...', getStarted: 'Start Conversation', }, }, metadata: { enhanced: true, nodeId: this.getNode().id, workflowId: this.getWorkflow().id, features: enhancedFeatures, }, }; // Apply custom styling if provided if (stylingOptions && Object.keys(stylingOptions).length > 0) { const customStyles = stylingOptions; const cssVariables = []; if (customStyles.primaryColor) cssVariables.push(`--chat--color-primary: ${customStyles.primaryColor}`); if (customStyles.secondaryColor) cssVariables.push(`--chat--color-secondary: ${customStyles.secondaryColor}`); if (customStyles.backgroundColor) cssVariables.push(`--chat--color-white: ${customStyles.backgroundColor}`); if (customStyles.textColor) cssVariables.push(`--chat--color-dark: ${customStyles.textColor}`); if (customStyles.chatWidth) cssVariables.push(`--chat--window--width: ${customStyles.chatWidth}px`); if (customStyles.chatHeight) cssVariables.push(`--chat--window--height: ${customStyles.chatHeight}px`); if (cssVariables.length > 0) { chatConfig.customStyles = cssVariables.join('; '); } } // Store configuration for webhook handler ChatTrigger.chatSessions.set(this.getNode().id, { config: chatConfig, nodeParameters: { responseMode: this.getNodeParameter('responseMode', 'workflow'), defaultResponse: this.getNodeParameter('defaultResponse', 'Thank you for your message! I\'m processing your request...'), enhancedFeatures: enhancedFeatures, } }); // Set up webhook handler for incoming chat messages const webhookHandler = (context) => __awaiter(this, void 0, void 0, function* () { return ChatTrigger.handleIncomingMessage(context); }); return { manualTriggerFunction: () => __awaiter(this, void 0, void 0, function* () { // Manual trigger just activates the webhook // The actual trigger data comes through the webhook }), }; }); } // Static method to handle incoming chat webhook requests static handleIncomingMessage(context) { return __awaiter(this, void 0, void 0, function* () { try { const body = context.getBodyData(); const headers = context.getHeaderData(); const query = context.getQueryData(); const nodeId = context.getNode().id; // Get stored configuration let sessionData = ChatTrigger.chatSessions.get(nodeId); if (!sessionData) { // Gracefully handle missing session by creating default configuration // This can happen after n8n restart or workflow reactivation const defaultConfig = { nodeParameters: { responseMode: 'workflow', defaultResponse: 'Thank you for your message! I\'m processing your request...', enhancedFeatures: { sessionManagement: true, messageHistory: true, autoSaveConversations: false, typingIndicators: true, messageTimestamps: true, }, } }; // Store the default configuration ChatTrigger.chatSessions.set(nodeId, defaultConfig); sessionData = defaultConfig; // Log this recovery for debugging (optional - will only show in n8n logs) console.log(`Enhanced Chat Trigger: Recovered session for node ${nodeId} after restart/reactivation`); } const { nodeParameters } = sessionData; // Extract message data const chatInput = body.chatInput || body.message || ''; const sessionId = body.sessionId || body.session || `enhanced_session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; const action = query.action || body.action || 'sendMessage'; const files = body.files || []; // Build enhanced message object const enhancedMessage = { id: `enhanced_msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, content: chatInput, timestamp: new Date().toISOString(), sessionId: sessionId, action: action, files: files, enhanced: true, metadata: { userAgent: headers['user-agent'], ip: headers['x-forwarded-for'] || headers['x-real-ip'] || 'unknown', source: 'enhanced-chat-trigger', features: nodeParameters.enhancedFeatures, }, }; // Handle session management if (nodeParameters.enhancedFeatures.sessionManagement && action === 'loadPreviousSession') { const previousMessages = ChatTrigger.messageHistory.get(sessionId) || []; return { webhookResponse: { status: 200, headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Authorization', }, body: { messages: previousMessages, sessionId: sessionId, enhanced: true, }, }, workflowData: [[]], }; } // Store message in history if enabled if (nodeParameters.enhancedFeatures.messageHistory) { if (!ChatTrigger.messageHistory.has(sessionId)) { ChatTrigger.messageHistory.set(sessionId, []); } ChatTrigger.messageHistory.get(sessionId).push(enhancedMessage); } // Prepare response based on mode let responseBody = { enhanced: true, sessionId: sessionId, messageId: enhancedMessage.id, timestamp: enhancedMessage.timestamp, }; const responseMode = nodeParameters.responseMode; const defaultResponse = nodeParameters.defaultResponse; if (responseMode === 'text') { responseBody.response = { text: defaultResponse }; } else if (responseMode === 'json') { responseBody.response = { text: defaultResponse, type: 'json', data: enhancedMessage }; } else if (responseMode === 'html') { responseBody.response = { text: `<div style="padding: 10px; background: #f0f8ff; border-radius: 5px;">${defaultResponse}</div>`, type: 'html' }; } return { webhookResponse: { status: 200, headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Authorization', }, body: responseBody, }, workflowData: [ [ { json: enhancedMessage, }, ], ], }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Enhanced Chat Trigger error: ${errorMessage}`); } }); } // Handle webhook requests (main webhook entry point) webhook() { return __awaiter(this, void 0, void 0, function* () { return ChatTrigger.handleIncomingMessage(this); }); } } exports.ChatTrigger = ChatTrigger; // Store active chat sessions ChatTrigger.chatSessions = new Map(); ChatTrigger.messageHistory = new Map();