UNPKG

embedia

Version:

Zero-configuration AI chatbot integration CLI - direct file copy with embedded API keys

837 lines (708 loc) 21.4 kB
const chalk = require('chalk'); /** * Web Component Code Generator * Generates universal web components that can be embedded anywhere */ class WebComponentGenerator { constructor() { this.version = '1.0.0'; } /** * Generate a complete web component bundle * @param {Object} config - Configuration object * @param {string} config.botId - Unique bot identifier * @param {string} config.botName - Display name for the bot * @param {string} config.apiProvider - AI provider (openai, gemini, etc.) * @param {string} config.apiKey - API key for direct provider access * @param {Object} config.persona - Bot personality configuration * @param {Object} config.styling - Custom styling options * @param {Object} config.projectContext - CLI detection context for endpoint calculation * @returns {Object} Generated web component code and assets */ generateWebComponent(config) { const { botId, botName = 'Embedia Chat', apiProvider = 'gemini', apiKey, persona = {}, styling = {}, projectContext } = config; // CRITICAL FIX: Calculate correct API endpoint based on CLI detection const apiEndpoint = this.calculateApiEndpoint(projectContext); // Generate the main web component with calculated endpoint const webComponentCode = this.generateWebComponentClass({ ...config, apiEndpoint }); // Generate supporting files const htmlTemplate = this.generateHTMLTemplate(config); const cssStyles = this.generateStyles(styling); const readme = this.generateReadme(config); const exampleUsage = this.generateExampleUsage(config); return { webComponent: webComponentCode, styles: cssStyles, html: htmlTemplate, readme: readme, examples: exampleUsage, config: { botId, botName, apiProvider, version: this.version } }; } /** * Generate the main web component class */ generateWebComponentClass(config) { const { botId, botName, apiProvider, apiKey, persona, apiEndpoint } = config; return `/** * Embedia Chatbot Web Component * Universal chatbot component that works in any web environment * Generated by Embedia CLI v${this.version} */ class EmbediaChatbot extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); this.isOpen = false; this.messages = []; this.config = { botId: '${botId}', botName: '${botName}', apiProvider: '${apiProvider}', apiKey: ${apiKey ? `'${apiKey}'` : 'null'}, persona: ${JSON.stringify(persona, null, 2)}, apiEndpoint: this.getAttribute('api-endpoint') || '${apiEndpoint || '/api/embedia/chat'}' }; this.init(); } static get observedAttributes() { return ['bot-id', 'api-key', 'api-endpoint', 'theme', 'position']; } attributeChangedCallback(name, oldValue, newValue) { if (oldValue !== newValue) { this.updateConfig(); } } updateConfig() { this.config.botId = this.getAttribute('bot-id') || this.config.botId; this.config.apiKey = this.getAttribute('api-key') || this.config.apiKey; this.config.apiEndpoint = this.getAttribute('api-endpoint') || this.config.apiEndpoint; } init() { this.render(); this.attachEventListeners(); } render() { const theme = this.getAttribute('theme') || 'default'; const position = this.getAttribute('position') || 'bottom-right'; this.shadowRoot.innerHTML = \` <style> \${this.getStyles(theme, position)} </style> <div class="embedia-chatbot"> <!-- Chat Toggle Button --> <button class="chat-toggle" id="chatToggle"> <svg class="chat-icon" viewBox="0 0 24 24"> <path d="M20 2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h4l4 4 4-4h4c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z"/> </svg> <svg class="close-icon" viewBox="0 0 24 24"> <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/> </svg> </button> <!-- Chat Window --> <div class="chat-window" id="chatWindow"> <div class="chat-header"> <h3>\${this.config.botName}</h3> <button class="minimize-btn" id="minimizeBtn"></button> </div> <div class="chat-messages" id="chatMessages"> <div class="message bot-message"> <div class="message-content"> \${this.config.persona.greeting || 'Hello! How can I help you today?'} </div> </div> </div> <div class="chat-input-container"> <input type="text" class="chat-input" id="chatInput" placeholder="Type your message..." /> <button class="send-btn" id="sendBtn"> <svg viewBox="0 0 24 24"> <path d="M2,21L23,12L2,3V10L17,12L2,14V21Z" /> </svg> </button> </div> <div class="loading-indicator" id="loadingIndicator" style="display: none;"> <div class="typing-dots"> <span></span> <span></span> <span></span> </div> </div> </div> </div> \`; } getStyles(theme, position) { return \` :host { position: fixed; \${position.includes('bottom') ? 'bottom: 20px;' : 'top: 20px;'} \${position.includes('right') ? 'right: 20px;' : 'left: 20px;'} z-index: 10000; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; } .embedia-chatbot { position: relative; } .chat-toggle { width: 60px; height: 60px; border-radius: 50%; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border: none; cursor: pointer; box-shadow: 0 4px 12px rgba(0,0,0,0.15); transition: all 0.3s ease; display: flex; align-items: center; justify-content: center; } .chat-toggle:hover { transform: scale(1.1); box-shadow: 0 6px 20px rgba(0,0,0,0.2); } .chat-icon, .close-icon { width: 24px; height: 24px; fill: white; transition: opacity 0.2s ease; } .close-icon { position: absolute; opacity: 0; } .chat-toggle.open .chat-icon { opacity: 0; } .chat-toggle.open .close-icon { opacity: 1; } .chat-window { position: absolute; bottom: 70px; right: 0; width: 350px; height: 500px; background: white; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); display: none; flex-direction: column; overflow: hidden; } .chat-window.open { display: flex; animation: slideUp 0.3s ease; } @keyframes slideUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } .chat-header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 16px; display: flex; justify-content: space-between; align-items: center; } .chat-header h3 { margin: 0; font-size: 16px; font-weight: 600; } .minimize-btn { background: none; border: none; color: white; font-size: 20px; cursor: pointer; padding: 0; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; } .chat-messages { flex: 1; padding: 16px; overflow-y: auto; display: flex; flex-direction: column; gap: 12px; } .message { display: flex; flex-direction: column; max-width: 80%; } .bot-message { align-self: flex-start; } .user-message { align-self: flex-end; } .message-content { background: #f1f3f4; padding: 12px 16px; border-radius: 18px; word-wrap: break-word; font-size: 14px; line-height: 1.4; } .user-message .message-content { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; } .chat-input-container { display: flex; padding: 16px; border-top: 1px solid #e0e0e0; gap: 8px; } .chat-input { flex: 1; border: 1px solid #e0e0e0; border-radius: 20px; padding: 12px 16px; outline: none; font-size: 14px; } .chat-input:focus { border-color: #667eea; } .send-btn { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border: none; border-radius: 50%; width: 40px; height: 40px; cursor: pointer; display: flex; align-items: center; justify-content: center; } .send-btn svg { width: 18px; height: 18px; fill: white; } .loading-indicator { padding: 16px; text-align: center; } .typing-dots { display: inline-flex; gap: 4px; } .typing-dots span { width: 8px; height: 8px; background: #667eea; border-radius: 50%; animation: bounce 1.4s infinite ease-in-out; } .typing-dots span:nth-child(1) { animation-delay: -0.32s; } .typing-dots span:nth-child(2) { animation-delay: -0.16s; } @keyframes bounce { 0%, 80%, 100% { transform: scale(0); } 40% { transform: scale(1); } } @media (max-width: 480px) { .chat-window { width: calc(100vw - 40px); height: calc(100vh - 100px); } } \`; } attachEventListeners() { const toggle = this.shadowRoot.getElementById('chatToggle'); const window = this.shadowRoot.getElementById('chatWindow'); const minimize = this.shadowRoot.getElementById('minimizeBtn'); const input = this.shadowRoot.getElementById('chatInput'); const sendBtn = this.shadowRoot.getElementById('sendBtn'); toggle.addEventListener('click', () => this.toggleChat()); minimize.addEventListener('click', () => this.closeChat()); sendBtn.addEventListener('click', () => this.sendMessage()); input.addEventListener('keypress', (e) => { if (e.key === 'Enter') this.sendMessage(); }); } toggleChat() { this.isOpen = !this.isOpen; const toggle = this.shadowRoot.getElementById('chatToggle'); const window = this.shadowRoot.getElementById('chatWindow'); toggle.classList.toggle('open', this.isOpen); window.classList.toggle('open', this.isOpen); if (this.isOpen) { this.shadowRoot.getElementById('chatInput').focus(); } } closeChat() { this.isOpen = false; const toggle = this.shadowRoot.getElementById('chatToggle'); const window = this.shadowRoot.getElementById('chatWindow'); toggle.classList.remove('open'); window.classList.remove('open'); } async sendMessage() { const input = this.shadowRoot.getElementById('chatInput'); const message = input.value.trim(); if (!message) return; // Add user message this.addMessage(message, 'user'); input.value = ''; // Show loading this.showLoading(true); try { const response = await this.callAPI(message); this.addMessage(response, 'bot'); } catch (error) { console.error('Chat API Error:', error); this.addMessage('Sorry, I encountered an error. Please try again.', 'bot'); } finally { this.showLoading(false); } } addMessage(content, sender) { const messagesContainer = this.shadowRoot.getElementById('chatMessages'); const messageEl = document.createElement('div'); messageEl.className = \`message \${sender}-message\`; messageEl.innerHTML = \` <div class="message-content">\${this.escapeHtml(content)}</div> \`; messagesContainer.appendChild(messageEl); messagesContainer.scrollTop = messagesContainer.scrollHeight; this.messages.push({ content, sender, timestamp: Date.now() }); } showLoading(show) { const loading = this.shadowRoot.getElementById('loadingIndicator'); loading.style.display = show ? 'block' : 'none'; } async callAPI(message) { const endpoint = this.config.apiEndpoint; const payload = { message, botId: this.config.botId, history: this.messages.slice(-10) // Send last 10 messages for context }; if (this.config.apiKey) { // Direct API call mode return await this.callDirectAPI(message); } else { // Proxy mode const response = await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (!response.ok) { throw new Error('API request failed'); } const data = await response.json(); return data.response || data.message || 'No response received'; } } async callDirectAPI(message) { // Direct API implementation based on provider if (this.config.apiProvider === 'openai') { return await this.callOpenAI(message); } else if (this.config.apiProvider === 'gemini') { return await this.callGemini(message); } else { throw new Error('Unsupported API provider'); } } async callOpenAI(message) { const response = await fetch('https://api.openai.com/v1/chat/completions', { method: 'POST', headers: { 'Authorization': \`Bearer \${this.config.apiKey}\`, 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'gpt-3.5-turbo', messages: [ { role: 'system', content: this.config.persona.systemPrompt || 'You are a helpful assistant.' }, ...this.messages.slice(-10).map(m => ({ role: m.sender === 'user' ? 'user' : 'assistant', content: m.content })), { role: 'user', content: message } ] }) }); const data = await response.json(); return data.choices[0].message.content; } async callGemini(message) { const response = await fetch(\`https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=\${this.config.apiKey}\`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ contents: [{ parts: [{ text: message }] }] }) }); const data = await response.json(); return data.candidates[0].content.parts[0].text; } escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } } // Register the custom element customElements.define('embedia-chatbot', EmbediaChatbot); // Export for module systems if (typeof module !== 'undefined' && module.exports) { module.exports = EmbediaChatbot; } `; } /** * Generate HTML template for standalone usage */ generateHTMLTemplate(config) { const { botId } = config; return `<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Embedia Chatbot Integration</title> </head> <body> <!-- Your existing page content --> <h1>Welcome to Your Website</h1> <p>Your chatbot will appear in the bottom-right corner.</p> <!-- Embedia Chatbot --> <embedia-chatbot bot-id="${botId}" theme="default" position="bottom-right"> </embedia-chatbot> <!-- Load the chatbot script --> <script src="./embedia-chatbot.js"></script> </body> </html>`; } /** * Generate additional CSS styles */ generateStyles(styling) { return `/* Additional custom styles for Embedia Chatbot */ .embedia-chatbot-container { /* Add any global styles here */ } /* Dark theme variant */ embedia-chatbot[theme="dark"] { /* Dark theme styles will be handled by the component */ } /* Mobile responsive adjustments */ @media (max-width: 768px) { embedia-chatbot { /* Mobile-specific adjustments */ } }`; } /** * Generate README documentation */ generateReadme(config) { const { botId, botName } = config; return `# ${botName} - Universal Web Component A universal chatbot web component that can be embedded in any website or web application. ## Quick Start ### Self-Hosted Integration \`\`\`html <script src="./embedia-chatbot.js"></script> <embedia-chatbot bot-id="${botId}" api-key="your-api-key" theme="default" position="bottom-right"> </embedia-chatbot> \`\`\` ## Attributes - \`bot-id\`: Your unique bot identifier - \`api-key\`: Your API key (required for self-hosted mode) - \`api-endpoint\`: Custom API endpoint URL - \`theme\`: Visual theme (default, dark, custom) - \`position\`: Position on screen (bottom-right, bottom-left, top-right, top-left) ## Framework Integration ### React \`\`\`jsx import React from 'react'; function App() { return ( <div> <h1>My React App</h1> <embedia-chatbot bot-id="${botId}" /> </div> ); } \`\`\` ### Vue.js \`\`\`vue <template> <div> <h1>My Vue App</h1> <embedia-chatbot bot-id="${botId}" /> </div> </template> \`\`\` ### Angular \`\`\`typescript // In your module import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; @NgModule({ schemas: [CUSTOM_ELEMENTS_SCHEMA] }) export class AppModule { } \`\`\` \`\`\`html <!-- In your template --> <embedia-chatbot bot-id="${botId}"></embedia-chatbot> \`\`\` ### WordPress Add this to your theme's footer.php or use a plugin: \`\`\`html <script src="./embedia-chatbot.js"></script> <embedia-chatbot bot-id="${botId}"></embedia-chatbot> \`\`\` ## Customization ### Custom Styling \`\`\`css embedia-chatbot { --primary-color: #your-color; --secondary-color: #your-secondary-color; } \`\`\` ### JavaScript API \`\`\`javascript const chatbot = document.querySelector('embedia-chatbot'); // Open the chat chatbot.toggleChat(); // Send a message programmatically chatbot.addMessage('Hello from JavaScript!', 'user'); \`\`\` ## Browser Support - Chrome 54+ - Firefox 63+ - Safari 10.1+ - Edge 79+ ## Support For help and documentation, visit [https://docs.embedia.ai](https://docs.embedia.ai) Generated by Embedia CLI v${this.version} `; } /** * Generate usage examples */ generateExampleUsage(config) { const { botId } = config; return { 'basic.html': this.generateHTMLTemplate(config), 'react-example.jsx': `import React from 'react'; function App() { return ( <div className="App"> <header> <h1>My React App with Embedia Chat</h1> </header> <main> <p>Your main content here...</p> </main> {/* Embedia Chatbot */} <embedia-chatbot bot-id="${botId}" theme="default" position="bottom-right" /> </div> ); } export default App;`, 'vue-example.vue': `<template> <div id="app"> <header> <h1>My Vue App with Embedia Chat</h1> </header> <main> <p>Your main content here...</p> </main> <!-- Embedia Chatbot --> <embedia-chatbot bot-id="${botId}" theme="default" position="bottom-right" /> </div> </template> <script> export default { name: 'App' } </script>`, 'wordpress-integration.php': `<?php // Add this to your theme's functions.php function add_embedia_chatbot() { ?> <script src="<?php echo get_template_directory_uri(); ?>/embedia-chatbot.js"></script> <embedia-chatbot bot-id="${botId}"></embedia-chatbot> <?php } add_action('wp_footer', 'add_embedia_chatbot'); ?>` }; } /** * Calculate correct API endpoint based on CLI detection * @param {Object} projectContext - CLI detection context * @returns {string} Calculated API endpoint path */ calculateApiEndpoint(projectContext) { if (!projectContext) { return '/api/embedia/chat'; // Default fallback } const { routerType, framework } = projectContext; // CRITICAL FIX: Match endpoint to actual generated API route structure if (framework?.name === 'next') { // Both App Router and Pages Router use the same endpoint path // The difference is in file structure (app/api/ vs pages/api/) // but the HTTP endpoint is the same return '/api/embedia/chat'; } // For other frameworks, use standard API path return '/api/embedia/chat'; } } module.exports = WebComponentGenerator;