UNPKG

besper-frontend-site-dev-main

Version:

Professional B-esper Frontend Site - Site-wide integration toolkit for full website bot deployment

1,365 lines (1,232 loc) 271 kB
/** * B-esper Bot Management Interface - Modular Version * Professional management system for bot configuration, knowledge management * Designed for integration into WordPress plugins and other platforms */ import { getBotManagementEndpoint, getRootApiEndpoint, managementApiCall, } from '../../services/centralizedApi.js'; // Import internationalization import { managementI18n } from '../../utils/managementI18n.js'; // Import management components import { ConfigurationTab } from './tabs/ConfigurationTab.js'; import { StylingTab } from './tabs/StylingTab.js'; import { BehaviourTab } from './tabs/BehaviourTab.js'; import { KnowledgeTab } from './tabs/KnowledgeTab.js'; import { ImplementationTab } from './tabs/ImplementationTab.js'; import { ConversationsTab } from './tabs/ConversationsTab.js'; import { LiveLogsTab } from '../LiveLogsTab.jsx'; import { StorageIndicator } from './widgets/StorageIndicator.js'; import { CredentialsDisplay } from './widgets/CredentialsDisplay.js'; import { BesperChatWidget } from '../chat/ChatWidget.js'; /** * Save Button Animation Controller * Manages the progressive emphasis animation for the Save Changes button */ class SaveButtonAnimationController { constructor() { this.saveButton = null; this.animationTimeout = null; this.MAX_ANIMATION_DURATION = 30000; // 30 seconds } /** * Initialize the controller with the save button element */ init(saveButton) { if (!saveButton) { console.warn('Save button not found for animation controller'); return; } this.saveButton = saveButton; // Add the save-changes-btn class for styling this.saveButton.classList.add('save-changes-btn'); // Listen for save button clicks to stop animation this.saveButton.addEventListener('click', () => { this.stopAnimation(); }); } /** * Start the save button animation after translation completes */ startAnimation() { if (!this.saveButton) return; // Clear any existing timeout this.clearAnimationTimeout(); // Add animation class this.saveButton.classList.add('pending-save'); // Auto-stop animation after 30 seconds to prevent fatigue this.animationTimeout = setTimeout(() => { this.stopAnimation(); }, this.MAX_ANIMATION_DURATION); } /** * Stop the save button animation */ stopAnimation() { if (!this.saveButton) return; // Remove animation class this.saveButton.classList.remove('pending-save'); // Clear timeout this.clearAnimationTimeout(); } /** * Clear animation timeout */ clearAnimationTimeout() { if (this.animationTimeout) { clearTimeout(this.animationTimeout); this.animationTimeout = null; } } /** * Clean up on destroy */ destroy() { this.clearAnimationTimeout(); this.stopAnimation(); this.saveButton = null; } } /** * B-esper Bot Management Interface */ export class BesperBotManagement { constructor(credentials, options = {}) { // Validate required credentials if ( !credentials || !credentials.botId || !credentials.managementId || !credentials.managementSecret ) { throw new Error( 'Management credentials are required: botId, managementId, managementSecret' ); } this.credentials = credentials; this.options = { environment: 'prod', container: null, showBot: false, customApiBase: null, fontSize: 'medium', ...options, }; this.state = { isLoading: false, botData: null, config: null, storageUsage: null, knowledgeItems: [], websites: [], dataLoadedTabs: new Set(), // Track which tabs have loaded their data isKnowledgeDataLoaded: false, // Specific flag for knowledge data }; this.widget = null; this.botWidget = null; // Set up API endpoints if (this.options.customApiBase) { this.managementEndpoint = this.options.customApiBase; this.rootApiEndpoint = this.options.customApiBase; } else { this.managementEndpoint = getBotManagementEndpoint(); this.rootApiEndpoint = getRootApiEndpoint(); } // Initialize component references this.configurationTab = null; this.stylingTab = null; this.behaviourTab = null; this.implementationTab = null; this.conversationsTab = null; this.storageIndicator = null; this.credentialsDisplay = null; this.customStylingManager = null; // Save button animation controller this.saveButtonAnimationController = new SaveButtonAnimationController(); // Initialize bot widget if requested if (this.options.showBot) { this.initializeBotWidget(); } } /** * Initialize the bot widget alongside the management interface */ async initializeBotWidget() { try { this.botWidget = new BesperChatWidget(this.credentials.botId, { environment: this.options.environment, position: 'bottom-right', }); await this.botWidget.init(); } catch (error) { console.warn('Failed to initialize bot widget:', error); } } /** * Create the management widget */ createWidget() { // Determine container let targetContainer; if (typeof this.options.container === 'string') { targetContainer = document.querySelector(this.options.container); if (!targetContainer) { throw new Error(`Container not found: ${this.options.container}`); } } else if (this.options.container) { targetContainer = this.options.container; } else { // Create a new container targetContainer = document.createElement('div'); targetContainer.className = 'besper-management-auto-container'; document.body.appendChild(targetContainer); } // Create widget container const widgetContainer = document.createElement('div'); widgetContainer.className = 'besper-management-container'; widgetContainer.innerHTML = this.getManagementHTML(); targetContainer.appendChild(widgetContainer); this.widget = widgetContainer; // Initialize components this.initializeComponents(); } /** * Initialize all sub-components */ initializeComponents() { // Pass i18n to components that need it this.configurationTab = new ConfigurationTab( this.widget, this.state, managementI18n ); this.stylingTab = new StylingTab(this.widget, this.state, managementI18n); this.behaviourTab = new BehaviourTab( this.widget, this.state, managementI18n ); this.knowledgeTab = new KnowledgeTab( this.widget, managementApiCall, this.credentials, this.managementEndpoint, this.options.environment, managementI18n ); this.implementationTab = new ImplementationTab(this.widget, this.state, { environment: this.options.environment, botManagement: this, i18n: managementI18n, }); this.conversationsTab = new ConversationsTab( this.credentials, this.managementEndpoint, { onRefreshConversations: () => this.loadConversationData(), }, this.options.environment, managementI18n ); // Initialize LiveLogsTab component this.liveLogsTab = new LiveLogsTab( this.credentials.botId, this.credentials.managementId, this.credentials.managementSecret, this.managementEndpoint ); this.storageIndicator = new StorageIndicator( this.widget, this.state, managementI18n ); this.credentialsDisplay = new CredentialsDisplay( this.widget, this.state, managementI18n ); // Initialize language selector this.initializeLanguageSelector(); // Update tab content with actual component HTML now that components are initialized this.updateTabContent(); // Set up component-specific event listeners after content is updated this.setupComponentEventListeners(); // Set up language change listener this.setupLanguageChangeListener(); } /** * Update tab content after components are initialized */ updateTabContent() { // Helper function to extract inner content from component HTML const extractInnerContent = html => { const match = html.match( /<div class="bm-tab-content"[^>]*>([\s\S]*)<\/div>\s*$/ ); return match ? match[1] : html; }; // Update Configuration tab const configTab = this.widget.querySelector('#bm-tab-configuration'); if (configTab && this.configurationTab) { configTab.innerHTML = extractInnerContent( this.configurationTab.getHTML() ); } // Update Styling tab const stylingTab = this.widget.querySelector('#bm-tab-styling'); if (stylingTab && this.stylingTab) { stylingTab.innerHTML = extractInnerContent(this.stylingTab.getHTML()); } // Update Behaviour tab const behaviourTab = this.widget.querySelector('#bm-tab-behaviour'); if (behaviourTab && this.behaviourTab) { behaviourTab.innerHTML = extractInnerContent(this.behaviourTab.getHTML()); } // Update Implementation tab const implementationTab = this.widget.querySelector( '#bm-tab-implementation' ); if (implementationTab && this.implementationTab) { implementationTab.innerHTML = extractInnerContent( this.implementationTab.getHTML() ); } // Update Live Logs tab const liveLogsTab = this.widget.querySelector('#live-logs-component'); console.log('🔍 Looking for live-logs-component:', liveLogsTab); if (liveLogsTab && this.liveLogsTab) { console.log( '[SUCCESS] Found live-logs-component and liveLogsTab instance, initializing...' ); this.liveLogsTab.init(liveLogsTab); } else { console.warn( '[ERROR] Missing live-logs-component or liveLogsTab instance:', { container: liveLogsTab, component: this.liveLogsTab, } ); } } /** * Generate the main management HTML with professional B-esper styling */ getManagementHTML() { return ` ${this.getStyles()} <div class="app-container"> <!-- Loading Overlay --> <div class="loading-overlay" id="bm-loadingOverlay"> <div class="spinner"></div> </div> <!-- Language Selector --> <div class="language-selector-container"> <select class="language-selector" id="bm-languageSelector"> <option value="en">English</option> <option value="de">Deutsch</option> </select> </div> <!-- Header --> <header class="app-header"> <div class="header-content"> <div class="header-left"> <h1 class="app-title" id="bm-app-title">${managementI18n.t('header.title')}</h1> <div class="bot-status"> <span class="status-indicator"></span> <span id="bm-status-text">${managementI18n.t('header.status')}</span> </div> </div> <div class="header-actions"> <button class="btn-save" id="bm-saveConfig"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/> <polyline points="17 21 17 13 7 13 7 21"/> <polyline points="7 3 7 8 15 8"/> </svg> <span id="bm-save-text">${managementI18n.t('header.saveChanges')}</span> </button> </div> </div> </header> <!-- Navigation --> <nav class="nav-tabs"> <div class="tabs-container"> <div class="tab active" data-tab="general"> <span id="bm-tab-general-text">${managementI18n.t('tabs.general')}</span> </div> <div class="tab" data-tab="styling"> <span id="bm-tab-styling-text">${managementI18n.t('tabs.styling')}</span> </div> <div class="tab" data-tab="behaviour"> <span id="bm-tab-behaviour-text">${managementI18n.t('tabs.behaviour')}</span> </div> <div class="tab" data-tab="knowledge"> <span id="bm-tab-knowledge-text">${managementI18n.t('tabs.knowledge')}</span> </div> <div class="tab" data-tab="conversations"> <span id="bm-tab-conversations-text">${managementI18n.t('tabs.conversations')}</span> </div> <div class="tab" data-tab="logs"> <span id="bm-tab-logs-text">Live Logs</span> </div> <div class="tab" data-tab="implementation"> <span id="bm-tab-implementation-text">${managementI18n.t('tabs.implementation')}</span> </div> </div> </nav> <!-- Content --> <main class="content-area"> <div class="content-container"> ${this.getTabContentHTML()} </div> </main> </div> `; } /** * Get header HTML */ getHeaderHTML() { return ` <div class="bm-header"> <div class="bm-header-main"> <h1 class="bm-header-title" id="bm-botName">Loading...</h1> <div class="bm-header-subtitle"> <span id="bm-environment">${this.options.environment.toUpperCase()}</span> <span class="bm-live-indicator"> <span class="bm-live-dot"></span> Live </span> </div> </div> <div class="bm-header-actions"> <button class="bm-btn bm-btn-secondary" id="bm-refreshAllBtn"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="M1 4v6h6"/> <path d="M23 20v-6h-6"/> <path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10"/> <path d="M3.51 15a9 9 0 0 0 14.85 3.36L23 14"/> </svg> Refresh All </button> </div> </div> `; } /** * Get bot preview HTML */ getBotPreviewHTML() { return ` <div class="bm-bot-preview-section"> <div class="bm-bot-preview-header"> <div class="besper-h3">Bot Preview</div> <span class="bm-preview-note">Live preview of your bot configuration</span> </div> <div class="bm-bot-widget-container" id="bm-botWidgetContainer"> <!-- Bot widget preview will be rendered here --> </div> </div> `; } /** * Get tabs HTML */ getTabsHTML() { return ` <div class="bm-tabs"> <button class="bm-tab active" data-tab="configuration">General</button> <button class="bm-tab" data-tab="styling">Styling</button> <button class="bm-tab" data-tab="behaviour">Behaviour</button> <button class="bm-tab" data-tab="knowledge">Knowledge Management</button> <button class="bm-tab" data-tab="conversations">Conversations</button> <button class="bm-tab" data-tab="implementation">Implementation</button> </div> `; } /** * Get tab content HTML with professional styling structure */ getTabContentHTML() { return ` <!-- General Tab --> <div class="tab-content active" id="general"> ${this.getGeneralTabHTML()} </div> <!-- Styling Tab --> <div class="tab-content" id="styling"> ${this.getStylingTabHTML()} </div> <!-- Behaviour Tab --> <div class="tab-content" id="behaviour"> ${this.getBehaviourTabHTML()} </div> <!-- Knowledge Management Tab --> <div class="tab-content" id="knowledge"> ${this.getKnowledgeTabHTML()} </div> <!-- Conversations Tab --> <div class="tab-content" id="conversations"> ${this.getConversationsTabHTML()} </div> <!-- Live Logs Tab --> <div class="tab-content" id="logs"> ${this.getLiveLogsTabHTML()} </div> <!-- Implementation Tab --> <div class="tab-content" id="implementation"> ${this.getImplementationTabHTML()} </div> `; } /** * Get placeholder tab for components not yet loaded */ getPlaceholderTab(tabName) { return ` <div class="bm-tab-content" id="bm-tab-${tabName}"> <div class="bm-loading-state">Loading ${tabName}...</div> </div> `; } /** * Get General tab HTML with professional styling */ getGeneralTabHTML() { return ` <section class="section"> <div class="section-header"> <h2 class="section-title">Basic Configuration</h2> <p class="section-description">Core settings and credentials for your bot</p> </div> <div class="two-column"> <!-- Bot Information --> <div class="card"> <div class="card-header"> <h3 class="card-title">Bot Information</h3> <p class="card-subtitle">Identity and basic settings</p> </div> <div class="card-body"> <div class="form-group"> <label class="form-label required">Bot Name</label> <input type="text" class="form-input" id="bm-botNameInput" placeholder="${managementI18n.t('placeholders.botName')}"> <p class="form-hint">Internal identifier for your bot</p> </div> <div class="form-group"> <label class="form-label required">Bot Title</label> <input type="text" class="form-input" id="bm-botTitle" placeholder="Display name"> <p class="form-hint">Shown in the chat interface header</p> </div> <div class="form-group"> <label class="form-label">Data Policy URL</label> <input type="url" class="form-input" id="bm-dataPolicyUrl" placeholder="https://example.com/privacy"> </div> </div> </div> <!-- Credentials --> <div class="card"> <div class="card-header"> <h3 class="card-title">Bot Credentials</h3> <p class="card-subtitle">Secure access credentials</p> </div> <div class="card-body"> <div class="form-group"> <label class="form-label">Bot ID</label> <div class="credential-field"> <input type="text" class="form-input credential-input" id="bm-botId" readonly> <div class="credential-actions"> <button class="btn-icon" title="Copy" data-copy="bm-botId"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <rect x="9" y="9" width="13" height="13" rx="2"/> <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/> </svg> </button> </div> </div> </div> <div class="form-group"> <label class="form-label">Management ID</label> <div class="credential-field"> <input type="text" class="form-input credential-input" id="bm-managementId" readonly> <div class="credential-actions"> <button class="btn-icon" title="Copy" data-copy="bm-managementId"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <rect x="9" y="9" width="13" height="13" rx="2"/> <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/> </svg> </button> </div> </div> </div> <div class="form-group"> <label class="form-label">Management Secret</label> <div class="credential-field"> <input type="password" class="form-input credential-input" id="bm-managementSecret" readonly> <div class="credential-actions"> <button class="btn-icon" title="Toggle visibility" data-toggle="bm-managementSecret"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/> <circle cx="12" cy="12" r="3"/> </svg> </button> <button class="btn-icon" title="Copy" data-copy="bm-managementSecret"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <rect x="9" y="9" width="13" height="13" rx="2"/> <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/> </svg> </button> </div> </div> </div> </div> </div> </div> </section> <!-- Welcome Messages --> <section class="section"> <div class="section-header"> <h2 class="section-title">Welcome Messages</h2> <p class="section-description">Customize greetings for different languages. Click on any language card to set it as the translation source.</p> </div> <div class="card"> <div class="card-body"> <div class="bm-help-text" style="background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 6px; padding: 12px; margin-bottom: 16px; font-size: 13px; color: #6c757d;"> <strong>How it works:</strong> Click on any language card to mark it as your translation source. Then use "Translate All" to automatically translate that message to other languages. </div> <div class="language-search"> <input type="text" class="search-input" placeholder="${managementI18n.t('placeholders.searchLanguages')}" id="bm-languageSearch"> </div> <div class="language-list" id="bm-languageList"> <!-- Language items will be populated here --> </div> <div style="margin-top: 16px; display: flex; gap: 12px; align-items: center; flex-wrap: wrap;"> <button class="btn btn-text" id="bm-addLanguage">+ Add Language</button> <button class="btn btn-primary" id="bm-translateAll">Translate All</button> <button class="btn btn-secondary" id="bm-saveWelcomeMessages">Save Changes</button> </div> </div> </div> </section> `; } /** * Get Styling tab HTML with professional styling and all 15+ color options */ getStylingTabHTML() { return ` <section class="section"> <div class="section-header"> <h2 class="section-title">Widget Styling</h2> <p class="section-description">Customize the visual appearance of your chat widget</p> </div> <div class="card"> <div class="card-body"> <!-- Basic Colors (Always Visible) --> <div class="color-picker-grid"> <!-- Primary Color --> <div class="form-group"> <label class="form-label">Primary Color</label> <div class="color-input-group"> <div class="color-preview" style="background: #5897de;"> <input type="color" class="color-input" value="#5897de" id="bm-primaryColor"> </div> <input type="text" class="form-input" value="#5897de" pattern="^#[0-9A-Fa-f]{6}$" id="bm-primaryColorText"> </div> </div> <!-- Secondary Color --> <div class="form-group"> <label class="form-label">Secondary Color</label> <div class="color-input-group"> <div class="color-preview" style="background: #022d54;"> <input type="color" class="color-input" value="#022d54" id="bm-secondaryColor"> </div> <input type="text" class="form-input" value="#022d54" pattern="^#[0-9A-Fa-f]{6}$" id="bm-secondaryColorText"> </div> </div> <!-- Accent Color --> <div class="form-group"> <label class="form-label">Accent Color</label> <div class="color-input-group"> <div class="color-preview" style="background: #ffbc82;"> <input type="color" class="color-input" value="#ffbc82" id="bm-accentColor"> </div> <input type="text" class="form-input" value="#ffbc82" pattern="^#[0-9A-Fa-f]{6}$" id="bm-accentColorText"> </div> </div> </div> <!-- Expand/Collapse Button --> <button class="btn btn-text" id="bm-expandColors" style="margin-top: 16px;"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 8px;"> <polyline points="6 9 12 15 18 9"></polyline> </svg> Show more color options </button> <!-- Advanced Colors (Initially Hidden) --> <div id="bm-advancedColors" style="display: none; margin-top: 16px;"> <div class="color-picker-grid"> <div class="form-group"> <label class="form-label">User Bubble Background</label> <div class="color-input-group"> <div class="color-preview" style="background: #5897de;"> <input type="color" class="color-input" value="#5897de" id="bm-userBubbleBg"> </div> <input type="text" class="form-input" value="#5897de" pattern="^#[0-9A-Fa-f]{6}$" id="bm-userBubbleBgText"> </div> </div> <!-- User Bubble Text --> <div class="form-group"> <label class="form-label">User Bubble Text</label> <div class="color-input-group"> <div class="color-preview" style="background: #ffffff;"> <input type="color" class="color-input" value="#ffffff" id="bm-userBubbleText"> </div> <input type="text" class="form-input" value="#ffffff" pattern="^#[0-9A-Fa-f]{6}$" id="bm-userBubbleTextText"> </div> </div> <!-- Bot Bubble Background --> <div class="form-group"> <label class="form-label">Bot Bubble Background</label> <div class="color-input-group"> <div class="color-preview" style="background: #f0f4f8;"> <input type="color" class="color-input" value="#f0f4f8" id="bm-botBubbleBg"> </div> <input type="text" class="form-input" value="#f0f4f8" pattern="^#[0-9A-Fa-f]{6}$" id="bm-botBubbleBgText"> </div> </div> <!-- Bot Bubble Text --> <div class="form-group"> <label class="form-label">Bot Bubble Text</label> <div class="color-input-group"> <div class="color-preview" style="background: #333333;"> <input type="color" class="color-input" value="#333333" id="bm-botBubbleText"> </div> <input type="text" class="form-input" value="#333333" pattern="^#[0-9A-Fa-f]{6}$" id="bm-botBubbleTextText"> </div> </div> <!-- Header Background --> <div class="form-group"> <label class="form-label">Header Background</label> <div class="color-input-group"> <div class="color-preview" style="background: #ffffff;"> <input type="color" class="color-input" value="#ffffff" id="bm-headerBg"> </div> <input type="text" class="form-input" value="#ffffff" pattern="^#[0-9A-Fa-f]{6}$" id="bm-headerBgText"> </div> </div> <!-- Header Text --> <div class="form-group"> <label class="form-label">Header Text</label> <div class="color-input-group"> <div class="color-preview" style="background: #333333;"> <input type="color" class="color-input" value="#333333" id="bm-headerText"> </div> <input type="text" class="form-input" value="#333333" pattern="^#[0-9A-Fa-f]{6}$" id="bm-headerTextText"> </div> </div> <!-- Chat Background --> <div class="form-group"> <label class="form-label">Chat Background</label> <div class="color-input-group"> <div class="color-preview" style="background: #ffffff;"> <input type="color" class="color-input" value="#ffffff" id="bm-chatBg"> </div> <input type="text" class="form-input" value="#ffffff" pattern="^#[0-9A-Fa-f]{6}$" id="bm-chatBgText"> </div> </div> <!-- Input Background --> <div class="form-group"> <label class="form-label">Input Background</label> <div class="color-input-group"> <div class="color-preview" style="background: #ffffff;"> <input type="color" class="color-input" value="#ffffff" id="bm-inputBg"> </div> <input type="text" class="form-input" value="#ffffff" pattern="^#[0-9A-Fa-f]{6}$" id="bm-inputBgText"> </div> </div> <!-- Input Border --> <div class="form-group"> <label class="form-label">Input Border</label> <div class="color-input-group"> <div class="color-preview" style="background: #e2e5e9;"> <input type="color" class="color-input" value="#e2e5e9" id="bm-inputBorder"> </div> <input type="text" class="form-input" value="#e2e5e9" pattern="^#[0-9A-Fa-f]{6}$" id="bm-inputBorderText"> </div> </div> <!-- Input Text --> <div class="form-group"> <label class="form-label">Input Text</label> <div class="color-input-group"> <div class="color-preview" style="background: #333333;"> <input type="color" class="color-input" value="#333333" id="bm-inputText"> </div> <input type="text" class="form-input" value="#333333" pattern="^#[0-9A-Fa-f]{6}$" id="bm-inputTextText"> </div> </div> <!-- Button Hover --> <div class="form-group"> <label class="form-label">Button Hover</label> <div class="color-input-group"> <div class="color-preview" style="background: #5897de;"> <input type="color" class="color-input" value="#5897de" id="bm-buttonHover"> </div> <input type="text" class="form-input" value="#5897de" pattern="^#[0-9A-Fa-f]{6}$" id="bm-buttonHoverText"> </div> </div> <!-- Scrollbar --> <div class="form-group"> <label class="form-label">Scrollbar</label> <div class="color-input-group"> <div class="color-preview" style="background: #d0d4da;"> <input type="color" class="color-input" value="#d0d4da" id="bm-scrollbar"> </div> <input type="text" class="form-input" value="#d0d4da" pattern="^#[0-9A-Fa-f]{6}$" id="bm-scrollbarText"> </div> </div> <!-- Shadow Color --> <div class="form-group"> <label class="form-label">Shadow Color</label> <div class="color-input-group"> <div class="color-preview" style="background: #000000;"> <input type="color" class="color-input" value="#000000" id="bm-shadowColor"> </div> <input type="text" class="form-input" value="#000000" pattern="^#[0-9A-Fa-f]{6}$" id="bm-shadowColorText"> </div> </div> </div> </div> <!-- Widget Size Settings --> <div style="margin-top: 32px;"> <h3 class="section-title" style="font-size: 16px; margin-bottom: 16px;">Widget Size</h3> <div class="form-group"> <label class="form-label">Widget Size</label> <select class="form-select" id="bm-widgetSize"> <option value="Medium 400px">Medium 400px</option> <option value="Small 300px">Small 300px</option> <option value="Large 500px">Large 500px</option> <option value="Extra Large 600px">Extra Large 600px</option> </select> </div> <div class="form-group"> <label class="form-label">Logo Size</label> <select class="form-select" id="bm-logoSize"> <option value="Medium 32px">Medium 32px</option> <option value="Small 24px">Small 24px</option> <option value="Large 40px">Large 40px</option> </select> </div> <div class="form-group"> <label class="form-label">Message Font Size</label> <select class="form-select" id="bm-messageFontSize"> <option value="Medium 14px">Medium 14px</option> <option value="Small 12px">Small 12px</option> <option value="Large 16px">Large 16px</option> </select> </div> <div class="form-group"> <label class="form-label">Font Family</label> <select class="form-select" id="bm-fontFamily"> <option value="System Default">System Default</option> <option value="Arial">Arial</option> <option value="Helvetica">Helvetica</option> <option value="Times New Roman">Times New Roman</option> <option value="Georgia">Georgia</option> </select> </div> <div class="form-group"> <label class="form-label">Chat Bubble Style</label> <select class="form-select" id="bm-bubbleStyle"> <option value="Modern">Modern</option> <option value="Classic">Classic</option> <option value="Rounded">Rounded</option> <option value="Square">Square</option> </select> </div> <div class="form-group"> <label class="form-label">Typing Indicator</label> <select class="form-select" id="bm-typingIndicator"> <option value="Dots">Dots</option> <option value="Bars">Bars</option> <option value="Pulse">Pulse</option> <option value="None">None</option> </select> </div> </div> <!-- Close on Outside Click --> <div style="margin-top: 24px;"> <div class="form-group"> <div style="display: flex; align-items: center; gap: 8px;"> <input type="checkbox" id="bm-closeOnOutsideClick" style="width: auto;"> <label class="form-label" for="bm-closeOnOutsideClick" style="margin: 0;">Close on outside click</label> </div> </div> </div> <!-- Reset Button --> <div style="margin-top: 32px;"> <button class="btn btn-secondary" id="bm-resetStyling">Reset to Defaults</button> </div> </div> </div> <!-- Advanced Custom Styling (Completely Hidden) --> <div class="card card-disabled" style="display: none;"> <div class="card-header"> <h3 class="card-title"> Advanced Custom Styling <span class="upcoming-badge">Coming Soon</span> </h3> <p class="card-subtitle">Custom CSS editor and advanced styling features</p> </div> <div class="card-body"> <div class="disabled-overlay"> <div class="disabled-content"> <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <circle cx="12" cy="12" r="10"></circle> <polyline points="12 6 12 12 16 14"></polyline> </svg> <div class="besper-h4">Advanced Styling Coming Soon</div> <div class="besper-p">We're working on advanced styling features including custom CSS editor, visual component editor, and live preview capabilities.</div> </div> </div> </div> </div> </section> `; } /** * Get Behaviour tab HTML with professional styling */ getBehaviourTabHTML() { return ` <section class="section"> <div class="section-header"> <h2 class="section-title">System Instructions</h2> <p class="section-description">Define how your bot should behave and respond</p> </div> <!-- Info Box --> <div class="instructions-info"> <h4 class="info-title">Instruction Priority System</h4> <p class="info-text"> Instructions are processed based on their priority level. <strong>High priority</strong> instructions are processed first and will override conflicting normal priority instructions. Use high priority for critical business rules, compliance requirements, or safety guidelines. <strong>Normal priority</strong> instructions are processed in sequential order after all high priority instructions. </p> </div> <!-- Main Instruction --> <div class="card"> <div class="card-header"> <h3 class="card-title">Primary System Instruction</h3> <p class="card-subtitle">Core behavior definition</p> </div> <div class="card-body"> <textarea class="form-textarea" rows="4" placeholder="Enter the main instruction for your bot..." id="bm-systemInstruction"></textarea> <div class="char-counter" id="bm-systemInstructionCounter">0 / 2000</div> </div> </div> <!-- Sub Instructions --> <div class="card"> <div class="card-header"> <h3 class="card-title">Sub Instructions</h3> <p class="card-subtitle">Additional behavioral rules and guidelines</p> </div> <div class="card-body"> <div class="instruction-search"> <input type="text" class="search-input" placeholder="Search instructions..." id="bm-instructionSearch"> </div> <div id="bm-instructionsList"> <!-- Sub-instructions will be populated here --> </div> <button class="btn btn-text" id="bm-addInstruction" style="margin-top: 16px;">+ Add Instruction</button> </div> </div> </section> `; } /** * Get Implementation tab HTML with code examples and documentation */ getImplementationTabHTML() { // Return the correct implementation content from the ImplementationTab component if (this.implementationTab) { return this.implementationTab.getHTML(); } // Fallback placeholder if component not yet initialized return ` <div class="bm-tab-content" id="bm-tab-implementation"> <div class="bm-loading-state">Loading implementation tab...</div> </div> `; } /** * Get knowledge tab HTML with professional styling */ getKnowledgeTabHTML() { return ` <div id="knowledge-tab-container"> <!-- This will be populated by the KnowledgeTab component --> </div> `; } /** * Get conversations tab HTML with professional styling */ getConversationsTabHTML() { return ` <div id="conversations-tab-container"> <!-- This will be populated by the ConversationsTab component --> </div> `; } /** * Get Live Logs tab HTML with professional styling */ getLiveLogsTabHTML() { return ` <div id="live-logs-tab-container"> <div class="section"> <div class="section-header"> <h2 class="section-title">Live Logs Analytics</h2> <p class="section-description">Real-time monitoring and analytics for your bot operations</p> </div> <div class="card"> <div class="card-header"> <h3 class="card-title">Live Logs Dashboard</h3> <p class="card-subtitle">Monitor bot performance and operations in real-time</p> </div> <div class="card-body"> <div id="live-logs-component"> <!-- LiveLogsTab component will be rendered here --> </div> </div> </div> </div> </div> `; } /** * Setup event listeners for professional interface */ setupEventListeners() { // Professional tab switching const tabs = this.widget.querySelectorAll('.tab'); tabs.forEach(tab => { tab.addEventListener('click', () => { const tabName = tab.getAttribute('data-tab'); this.switchTab(tabName); }); }); // Search functionality for welcome messages const languageSearch = this.widget.querySelector('#bm-languageSearch'); if (languageSearch) { languageSearch.addEventListener('input', e => { this.filterLanguages(e.target.value); }); } // Search functionality for instructions const instructionSearch = this.widget.querySelector( '#bm-instructionSearch' ); if (instructionSearch) { instructionSearch.addEventListener('input', e => { this.filterInstructions(e.target.value); }); } // Save configuration button - use event delegation for robustness const saveBtn = this.widget.querySelector('#bm-saveConfig'); if (saveBtn) { console.log('[SUCCESS] Save button found, attaching click listener'); // Remove any existing listeners first const existingListeners = saveBtn.cloneNode(true); saveBtn.parentNode.replaceChild(existingListeners, saveBtn); const newSaveBtn = this.widget.querySelector('#bm-saveConfig'); newSaveBtn.addEventListener('click', e => { e.preventDefault(); e.stopPropagation(); console.log('💾 Save button clicked'); this.saveAllConfiguration(); }); // Also use event delegation on the widget itself as backup this.widget.addEventListener('click', e => { if (e.target && e.target.id === 'bm-saveConfig') { e.preventDefault(); e.stopPropagation(); console.log('💾 Save button clicked via delegation'); this.saveAllConfiguration(); } }); } else { console.warn('[ERROR] Save button not found'); } // Setup professional tab interactions this.setupProfessionalInteractions(); // Setup mobile-specific enhancements this.setupMobileEnhancements(); // Load data into form fields this.populateFormData(); } /** * Setup mobile-specific enhancements for better user experience */ setupMobileEnhancements() { // Add touch-friendly interactions this.setupTouchFriendlyTabs(); this.setupMobileScrollBehavior(); this.setupMobileKeyboard(); this.setupMobileModals(); } /** * Setup touch-friendly tab navigation for mobile */ setupTouchFriendlyTabs() { const tabsContainer = this.widget.querySelector('.tabs-container'); if (tabsContainer && window.innerWidth <= 768) { // Enable horizontal scrolling for tabs on mobile tabsContainer.style.overflowX = 'auto'; tabsContainer.style.webkitOverflowScrolling = 'touch'; // Add smooth scrolling behavior when tab is clicked const tabs = this.widget.querySelectorAll('.tab'); tabs.forEach((tab, _index) => { tab.addEventListener('click', () => { // Scroll active tab into view on mobile setTimeout(() => { if (tab.classList.contains('active')) { tab.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest', }); } }, 100); }); }); } } /** * Setup mobile scroll behavior improvements */ setupMobileScrollBehavior() { // Prevent body scroll when modal is open on mobile const observer = new MutationObserver(mutations => { mutations.forEach(mutation => { if (mutation.type === 'childList') { const modals = this.widget.querySelectorAll('.bm-modal'); modals.forEach(modal => { if (modal.style.display !== 'none' && window.innerWidth <= 480) { document.body.style.overflow = 'hidden'; } else { document.body.style.overflow = ''; } }); } }); }); observer.observe(this.widget, { childList: true, subtree: true, }); // Add smooth scrolling for mobile navigation if (window.innerWidth <= 768) { const contentArea = this.widget.querySelector('.content-area'); if (contentArea) { contentArea.style.scrollBehavior = 'smooth'; contentArea.style.webkitOverflowScrolling = 'touch'; } } } /** * Setup mobile keyboard optimizations */ setupMobileKeyboard() { // Optimize form inputs for mobile keyboards const inputs = this.widget.querySelectorAll('.form-input, .form-textarea'); inputs.forEach(input => { // Add appropriate input types for mobile keyboards if (input.type === 'text') { if (input.placeholder.toLowerCase().includes('email')) { input.type = 'email'; } else if ( input.placeholder.toLowerCase().includes('url') || input.placeholder.toLowerCase().includes('website') ) { input.type = 'url'; } } // Handle keyboard on mobile - scroll input into view when focused if (window.innerWidth <= 768) { input.addEventListener('focus', () => { setTimeout(() => { input.scrollIntoView({ behavior: 'smooth', block: 'center', }); }, 300); // Delay to account for keyboard animation }); } }); // Handle search inputs specifically const searchInputs = this.widget.querySelectorAll('.search-input'); searchInputs.forEach(input => { input.type = 'search'; input.setAttribute('inputmode', 'search'); }); } /** * Setup mobile modal optimizations */ setupMobileModals() { // Handle modal close on mobile swipe down (simplified version) const modals = this.widget.querySelectorAll('.bm-modal'); modals.forEach(modal => { if (window.innerWidth <= 480) { let startY = 0; let currentY = 0; modal.addEventListener( 'touchstart', e => { startY = e.touches[0].clientY; }, { passive: true } ); modal.addEventListener( 'touchmove', e => { currentY = e.touches[0].clientY; }, { passive: true } ); modal.addEventListener( 'touchend', () => { const deltaY = currentY - startY; // If swipe down more than 100px, close modal if (deltaY > 100) { const closeBtn = modal.querySelector('.bm-modal-close'); if (closeBtn) { closeBtn.click(); } } }, { passive: true } ); } }); // Add mobile-specific modal backdrop click handling this.widget.addEventListener('click', e => { if (e.target.classList.contains('bm-modal') && window.innerWidth <= 480) { const closeBtn = e.target.querySelector('.bm-modal-close'); if (closeBtn) { closeBtn.click(); } } }); } /** * Setup professional UI interactions */ setupProfessionalInteractions() { // Copy buttons this.widget.querySelectorAll('[data-copy]').forEach(btn => { btn.addEventListener('click', _e => { c