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
JavaScript
/**
* 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