UNPKG

besper-frontend-site-dev-main

Version:

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

812 lines (719 loc) 27.8 kB
/** * Configuration Tab Component * Handles the configuration tab in the bot management interface */ export class ConfigurationTab { constructor(widget, state, i18n = null) { this.widget = widget; this.state = state; this.i18n = i18n; } /** * Update language * @param {string} _language - New language code */ updateLanguage(_language) { // Re-render the tab content with new language const tabContent = this.widget.querySelector('#bm-tab-configuration'); if (tabContent && this.i18n) { const updatedHTML = this.getHTML(); const match = updatedHTML.match( /<div class="bm-tab-content"[^>]*>([\s\S]*)<\/div>\s*$/ ); if (match) { tabContent.innerHTML = match[1]; // Re-setup event listeners after content update this.setupEventListeners(this.currentCallbacks || {}); } } } /** * Get translated text * @param {string} key - Translation key * @param {Object} variables - Variables for substitution * @returns {string} Translated text */ t(key, variables = {}) { return this.i18n ? this.i18n.t(`configuration.${key}`, variables) : key; } /** * Generate the HTML for the configuration tab * @returns {string} Configuration tab HTML string */ getHTML() { return ` <div class="bm-tab-content" id="bm-tab-configuration"> <h2 style="font-size: 24px; font-weight: 500; color: #022d54; margin: 0 0 8px 0;">${this.t('title')}</h2> <p style="font-size: 14px; color: #6b7684; margin: 0 0 32px 0;">${this.t('subtitle')}</p> <div class="bm-grid"> <!-- Basic Information --> ${this.getBasicInformationCard()} <!-- Bot Credentials --> ${this.getBotCredentialsCard()} <!-- Welcome Message --> ${this.getWelcomeMessageCard()} <!-- Logo Upload --> ${this.getLogoUploadCard()} <!-- CORS Configuration --> ${this.getCorsConfigurationCard()} <!-- Welcome Messages --> ${this.getWelcomeMessagesCard()} </div> <div class="bm-actions"> <button class="bm-btn bm-btn-primary" id="bm-saveBtn">${this.t('actions.save')}</button> <button class="bm-btn bm-btn-secondary" id="bm-refreshBtn">${this.t('actions.refresh')}</button> </div> </div> `; } /** * Get Basic Information card HTML */ getBasicInformationCard() { return ` <div class="bm-card"> <h2 class="bm-card-title" style="display: flex; align-items: center; gap: 8px;"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="M12 2L2 7L12 12L22 7L12 2Z"/> <path d="M2 17L12 22L22 17"/> <path d="M2 12L12 17L22 12"/> </svg> ${this.t('basicInfo.title')} </h2> <p class="bm-card-subtitle">${this.t('basicInfo.subtitle')}</p> <div class="bm-form-group"> <label class="bm-form-label">${this.t('basicInfo.botName')} *</label> <input type="text" class="bm-form-input" id="bm-nameInput" placeholder="${this.t('basicInfo.botName')}"> <p class="bm-form-hint">${this.t('basicInfo.internalIdentifier')}</p> </div> <div class="bm-form-group"> <label class="bm-form-label">${this.t('basicInfo.botTitle')} *</label> <input type="text" class="bm-form-input" id="bm-botTitleInput" placeholder="Customer Support"> <p class="bm-form-hint">${this.t('basicInfo.shownInHeader')}</p> </div> <div class="bm-form-group"> <label class="bm-form-label"> ${this.t('basicInfo.dataPolicyUrl')} <button type="button" class="bm-info-btn" title="Information"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <circle cx="12" cy="12" r="10"/> <path d="M12 16v-4"/> <path d="M12 8h.01"/> </svg> </button> </label> <input type="text" class="bm-form-input" id="bm-dataPolicyInput" placeholder="https://example.com/privacy"> <div class="bm-info-tooltip"> <p>${this.t('basicInfo.dataPolicyHint')}</p> </div> </div> </div> `; } /** * Get Bot Credentials card HTML */ getBotCredentialsCard() { return ` <div class="bm-card"> <h2 class="bm-card-title" style="display: flex; align-items: center; gap: 8px;"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <rect x="3" y="11" width="18" height="11" rx="2" ry="2"/> <circle cx="12" cy="16" r="1"/> <path d="M7 11V7a5 5 0 0 1 10 0v4"/> </svg> ${this.t('credentials.title')} </h2> <p class="bm-card-subtitle">${this.t('credentials.subtitle')}</p> <div class="bm-form-group"> <label class="bm-form-label">${this.t('credentials.botId')}</label> <div class="bm-credential-field"> <input type="text" class="bm-form-input" id="bm-botIdInput" readonly> <button class="bm-copy-btn" data-copy-target="bm-botIdInput"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <rect x="9" y="9" width="13" height="13" rx="2" ry="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 class="bm-form-group"> <label class="bm-form-label">${this.t('credentials.managementId')}</label> <div class="bm-credential-field"> <input type="text" class="bm-form-input" id="bm-managementIdInput" readonly> <button class="bm-copy-btn" data-copy-target="bm-managementIdInput"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <rect x="9" y="9" width="13" height="13" rx="2" ry="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 class="bm-form-group"> <label class="bm-form-label">${this.t('credentials.managementSecret')}</label> <div class="bm-credential-field"> <input type="password" class="bm-form-input" id="bm-managementSecretInput" readonly> <button class="bm-toggle-btn" id="bm-toggleSecretBtn"> <svg width="16" height="16" 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="bm-copy-btn" data-copy-target="bm-managementSecretInput"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <rect x="9" y="9" width="13" height="13" rx="2" ry="2"/> <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2 2v1"/> </svg> </button> </div> </div> </div> `; } /** * Get Instructions card HTML */ getInstructionsCard() { return ` <div class="bm-card"> <h2 class="bm-card-title" style="display: flex; align-items: center; gap: 8px;"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="M14 2H6A2 2 0 0 0 4 4V20A2 2 0 0 0 6 22H18A2 2 0 0 0 20 20V8Z"/> <path d="M14 2V8H20"/> </svg> Instructions </h2> <div class="bm-form-group"> <textarea class="bm-form-textarea" id="bm-instructionsInput" placeholder="${this.i18n ? this.i18n.t('placeholders.botInstructions') : 'Enter bot instructions...'}" rows="6"></textarea> </div> </div> `; } /** * Get Welcome Message card HTML */ getWelcomeMessageCard() { return ` <div class="bm-card"> <h2 class="bm-card-title" style="display: flex; align-items: center; gap: 8px;"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/> </svg> ${this.t('welcomeMessage.title')} </h2> <div class="bm-form-group"> <label class="bm-form-label">${this.t('welcomeMessage.defaultMessage')}</label> <textarea class="bm-form-textarea" id="bm-welcomeMessageInput" placeholder="${this.t('welcomeMessage.placeholder')}" rows="3"></textarea> <p class="bm-form-hint">${this.t('welcomeMessage.hint')}</p> </div> </div> `; } /** * Get Logo Upload card HTML */ getLogoUploadCard() { return ` <div class="bm-card"> <h2 class="bm-card-title" style="display: flex; align-items: center; gap: 8px;"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/> <circle cx="8.5" cy="8.5" r="1.5"/> <path d="M21 15l-5-5L5 21"/> </svg> ${this.t('logo.title')} </h2> <div class="bm-logo-section"> <div class="bm-logo-preview" id="bm-logoPreview"> <div class="bm-logo-placeholder"> <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"> <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/> <circle cx="8.5" cy="8.5" r="1.5"/> <path d="M21 15l-5-5L5 21"/> </svg> <span>${this.t('logo.noLogo')}</span> </div> </div> <div class="bm-logo-actions"> <button class="bm-btn bm-btn-primary" id="bm-selectLogoBtn"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/> <polyline points="17 8 12 3 7 8"/> <line x1="12" y1="3" x2="12" y2="15"/> </svg> ${this.t('logo.upload')} </button> <button class="bm-btn bm-btn-secondary" id="bm-removeLogoBtn" style="display: none;"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <line x1="18" y1="6" x2="6" y2="18"/> <line x1="6" y1="6" x2="18" y2="18"/> </svg> ${this.t('logo.remove')} </button> <input type="file" id="bm-logoFileInput" style="display: none;" accept="image/*"> </div> <p class="bm-form-hint">${this.t('logo.hint')}</p> </div> </div> `; } /** * Get CORS Configuration card HTML */ getCorsConfigurationCard() { return ` <div class="bm-card bm-card-full"> <h2 class="bm-card-title" style="display: flex; align-items: center; gap: 8px;"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <circle cx="12" cy="12" r="3"/> <path d="M12 1v6M12 17v6M4.22 4.22l4.24 4.24M15.54 15.54l4.24 4.24M1 12h6M17 12h6M4.22 19.78l4.24-4.24M15.54 8.46l4.24-4.24"/> </svg> ${this.t('cors.title')} </h2> <p class="bm-form-description"> ${this.t('cors.subtitle')} </p> <div class="bm-form-group"> <label class="bm-form-label">${this.t('cors.allowedOrigins')}</label> <div class="bm-cors-origins-container"> <div id="bm-corsOriginsList" class="bm-cors-origins-list"> <!-- Origins will be populated here --> </div> <div class="bm-cors-add-container"> <input type="text" class="bm-form-input" id="bm-corsOriginInput" placeholder="https://example.com" style="flex: 1;"> <button class="bm-btn bm-btn-secondary bm-btn-sm" id="bm-addCorsOriginBtn"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <circle cx="12" cy="12" r="10"/> <line x1="12" y1="8" x2="12" y2="16"/> <line x1="8" y1="12" x2="16" y2="12"/> </svg> ${this.t('cors.addOrigin')} </button> </div> </div> </div> </div> `; } /** * Get Welcome Messages card HTML */ getWelcomeMessagesCard() { return ` <div class="bm-card bm-card-full"> <h2 class="bm-card-title" style="display: flex; align-items: center; gap: 8px;"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/> </svg> Welcome Messages </h2> <div class="bm-welcome-section"> <div class="bm-welcome-actions"> <button class="bm-btn bm-btn-primary" id="bm-addLanguageBtn"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <line x1="12" y1="5" x2="12" y2="19"/> <line x1="5" y1="12" x2="19" y2="12"/> </svg> ${this.t('welcomeMessages.addLanguage')} </button> <button class="bm-btn bm-btn-secondary" id="bm-translateAllBtn" disabled> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="M5 8l6 6M4 14l6-6 2-3 3 3 6 6"/> <path d="M2 5h12M7 2v3"/> <path d="M22 22l-5-10-5 10"/> <path d="M14 18h6"/> </svg> ${this.t('welcomeMessages.translateAll')} </button> </div> <div class="bm-welcome-messages" id="bm-welcomeMessages"> <!-- Dynamic welcome message fields will be added here --> </div> </div> </div> `; } /** * Setup event listeners for configuration tab * @param {Object} callbacks - Callback functions for events */ setupEventListeners(callbacks = {}) { // Store callbacks for later use this.currentCallbacks = callbacks; // Copy buttons const copyButtons = this.widget.querySelectorAll( '.bm-copy-btn[data-copy-target]' ); copyButtons.forEach(btn => { btn.addEventListener('click', () => { const targetId = btn.getAttribute('data-copy-target'); this.copyToClipboard(targetId); }); }); // Toggle secret visibility const toggleSecretBtn = this.widget.querySelector('#bm-toggleSecretBtn'); if (toggleSecretBtn) { toggleSecretBtn.addEventListener('click', () => this.toggleSecretVisibility() ); } // Logo upload const selectLogoBtn = this.widget.querySelector('#bm-selectLogoBtn'); const logoFileInput = this.widget.querySelector('#bm-logoFileInput'); const removeLogoBtn = this.widget.querySelector('#bm-removeLogoBtn'); if (selectLogoBtn && logoFileInput) { selectLogoBtn.addEventListener('click', () => logoFileInput.click()); logoFileInput.addEventListener('change', e => this.handleLogoUpload(e)); } if (removeLogoBtn) { removeLogoBtn.addEventListener('click', () => this.removeLogo()); } // CORS origins const addCorsOriginBtn = this.widget.querySelector('#bm-addCorsOriginBtn'); const corsOriginInput = this.widget.querySelector('#bm-corsOriginInput'); if (addCorsOriginBtn) { addCorsOriginBtn.addEventListener('click', () => this.addCorsOrigin()); } if (corsOriginInput) { corsOriginInput.addEventListener('keypress', e => { if (e.key === 'Enter') { e.preventDefault(); this.addCorsOrigin(); } }); } // Welcome messages const addLanguageBtn = this.widget.querySelector('#bm-addLanguageBtn'); const translateAllBtn = this.widget.querySelector('#bm-translateAllBtn'); if (addLanguageBtn && callbacks.onAddLanguage) { addLanguageBtn.addEventListener('click', callbacks.onAddLanguage); } if (translateAllBtn && callbacks.onTranslateAll) { translateAllBtn.addEventListener('click', callbacks.onTranslateAll); } // Info tooltip for data policy URL const infoBtn = this.widget.querySelector('.bm-info-btn'); const infoTooltip = this.widget.querySelector('.bm-info-tooltip'); if (infoBtn && infoTooltip) { let isTooltipVisible = false; const isMobile = window.innerWidth <= 768; const showTooltip = () => { infoTooltip.style.display = 'block'; isTooltipVisible = true; // On mobile, add backdrop to prevent scrolling if (isMobile) { document.body.style.overflow = 'hidden'; } }; const hideTooltip = () => { infoTooltip.style.display = 'none'; isTooltipVisible = false; // Restore scrolling on mobile if (isMobile) { document.body.style.overflow = ''; } }; // Toggle on click (primary interaction for mobile) infoBtn.addEventListener('click', e => { e.preventDefault(); e.stopPropagation(); if (isTooltipVisible) { hideTooltip(); } else { showTooltip(); } }); // Hide when clicking outside document.addEventListener('click', e => { if ( isTooltipVisible && !infoBtn.contains(e.target) && !infoTooltip.contains(e.target) ) { hideTooltip(); } }); // Mobile-specific: Close button in tooltip if (isMobile) { infoTooltip.addEventListener('click', e => { // Check if clicked on the close button (×) if ( e.target === infoTooltip && e.offsetX > infoTooltip.offsetWidth - 30 ) { hideTooltip(); } }); // Escape key to close document.addEventListener('keydown', e => { if (e.key === 'Escape' && isTooltipVisible) { hideTooltip(); } }); } else { // Desktop: Show on hover infoBtn.addEventListener('mouseenter', showTooltip); infoBtn.addEventListener('mouseleave', () => { // Small delay to allow moving to tooltip setTimeout(() => { if (!infoTooltip.matches(':hover') && !infoBtn.matches(':hover')) { hideTooltip(); } }, 100); }); // Keep visible when hovering tooltip infoTooltip.addEventListener('mouseenter', () => { showTooltip(); }); infoTooltip.addEventListener('mouseleave', hideTooltip); } } // Save and refresh buttons const saveBtn = this.widget.querySelector('#bm-saveBtn'); const refreshBtn = this.widget.querySelector('#bm-refreshBtn'); if (saveBtn && callbacks.onSave) { saveBtn.addEventListener('click', callbacks.onSave); } if (refreshBtn && callbacks.onRefresh) { refreshBtn.addEventListener('click', callbacks.onRefresh); } } /** * Copy text to clipboard * @param {string} targetId - ID of the input element to copy from */ copyToClipboard(targetId) { const input = this.widget.querySelector(`#${targetId}`); if (input) { input.select(); input.setSelectionRange(0, 99999); // For mobile devices document.execCommand('copy'); // Show feedback this.showCopyFeedback(input); } } /** * Show copy feedback * @param {HTMLElement} element - Element to show feedback for */ showCopyFeedback(element) { const originalBorder = element.style.border; element.style.border = '2px solid #10b981'; setTimeout(() => { element.style.border = originalBorder; }, 500); } /** * Toggle secret visibility */ toggleSecretVisibility() { const secretInput = this.widget.querySelector('#bm-managementSecretInput'); const toggleBtn = this.widget.querySelector('#bm-toggleSecretBtn'); if (secretInput && toggleBtn) { const isPassword = secretInput.type === 'password'; secretInput.type = isPassword ? 'text' : 'password'; const svg = toggleBtn.querySelector('svg'); if (svg) { svg.innerHTML = isPassword ? '<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/>' : '<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"/>'; } } } /** * Handle logo upload * @param {Event} event - File input change event */ handleLogoUpload(event) { const file = event.target.files[0]; if (file) { // Validate file if (!this.validateLogoFile(file)) { return; } // Create preview const reader = new FileReader(); reader.onload = e => { this.updateLogoPreview(e.target.result); }; reader.readAsDataURL(file); } } /** * Validate logo file * @param {File} file - File to validate * @returns {boolean} Whether file is valid */ validateLogoFile(file) { const maxSize = 2 * 1024 * 1024; // 2MB const allowedTypes = [ 'image/jpeg', 'image/png', 'image/gif', 'image/svg+xml', 'image/webp', ]; if (file.size > maxSize) { console.error('File size must be less than 2MB'); return false; } if (!allowedTypes.includes(file.type)) { console.error( 'File type not supported. Please use JPG, PNG, GIF, SVG, or WebP' ); return false; } return true; } /** * Update logo preview * @param {string} imageUrl - Image URL to display */ updateLogoPreview(imageUrl) { const logoPreview = this.widget.querySelector('#bm-logoPreview'); const removeLogoBtn = this.widget.querySelector('#bm-removeLogoBtn'); if (logoPreview) { logoPreview.innerHTML = `<img src="${imageUrl}" alt="Bot Logo" style="max-width: 100%; max-height: 100%; border-radius: 8px;">`; } if (removeLogoBtn) { removeLogoBtn.style.display = 'inline-flex'; } } /** * Remove logo */ removeLogo() { const logoPreview = this.widget.querySelector('#bm-logoPreview'); const logoFileInput = this.widget.querySelector('#bm-logoFileInput'); const removeLogoBtn = this.widget.querySelector('#bm-removeLogoBtn'); if (logoPreview) { logoPreview.innerHTML = ` <div class="bm-logo-placeholder"> <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"> <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/> <circle cx="8.5" cy="8.5" r="1.5"/> <path d="M21 15l-5-5L5 21"/> </svg> <span>No logo uploaded</span> </div> `; } if (logoFileInput) { logoFileInput.value = ''; } if (removeLogoBtn) { removeLogoBtn.style.display = 'none'; } } /** * Add CORS origin */ addCorsOrigin() { const input = this.widget.querySelector('#bm-corsOriginInput'); const originsList = this.widget.querySelector('#bm-corsOriginsList'); if (input && originsList) { const origin = input.value.trim(); if (origin) { this.addCorsOriginToList(origin, originsList); input.value = ''; } } } /** * Add CORS origin to list * @param {string} origin - Origin to add * @param {HTMLElement} container - Container to add origin to */ addCorsOriginToList(origin, container) { const originElement = document.createElement('div'); originElement.className = 'bm-cors-origin-item'; originElement.innerHTML = ` <span class="bm-cors-origin-text">${origin}</span> <button class="bm-cors-remove-btn" type="button"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <line x1="18" y1="6" x2="6" y2="18"/> <line x1="6" y1="6" x2="18" y2="18"/> </svg> </button> `; const removeBtn = originElement.querySelector('.bm-cors-remove-btn'); removeBtn.addEventListener('click', () => { originElement.remove(); }); container.appendChild(originElement); } /** * Populate configuration data * @param {Object} config - Configuration data */ populateData(config) { if (!config) return; // Basic information this.setInputValue('bm-nameInput', config.name); this.setInputValue('bm-botTitleInput', config.botTitle); this.setInputValue('bm-dataPolicyInput', config.dataPolicyUrl); // Credentials this.setInputValue('bm-botIdInput', config.botId); this.setInputValue('bm-managementIdInput', config.managementId); this.setInputValue('bm-managementSecretInput', config.managementSecret); // Instructions and welcome message this.setInputValue('bm-instructionsInput', config.instructions); this.setInputValue('bm-welcomeMessageInput', config.welcomeMessage); // Logo if (config.logoUrl) { this.updateLogoPreview(config.logoUrl); } // CORS origins if (config.corsOrigins && Array.isArray(config.corsOrigins)) { const originsList = this.widget.querySelector('#bm-corsOriginsList'); if (originsList) { originsList.innerHTML = ''; config.corsOrigins.forEach(origin => { this.addCorsOriginToList(origin, originsList); }); } } } /** * Set input value safely * @param {string} id - Input ID * @param {any} value - Value to set */ setInputValue(id, value) { const input = this.widget.querySelector(`#${id}`); if (input && value !== undefined && value !== null) { input.value = value; } } /** * Get configuration data from form * @returns {Object} Configuration data */ getConfigurationData() { return { name: this.getInputValue('bm-nameInput'), botTitle: this.getInputValue('bm-botTitleInput'), dataPolicyUrl: this.getInputValue('bm-dataPolicyInput'), instructions: this.getInputValue('bm-instructionsInput'), welcomeMessage: this.getInputValue('bm-welcomeMessageInput'), corsOrigins: this.getCorsOrigins(), }; } /** * Get input value safely * @param {string} id - Input ID * @returns {string} Input value */ getInputValue(id) { const input = this.widget.querySelector(`#${id}`); return input ? input.value.trim() : ''; } /** * Get CORS origins from the list * @returns {Array} Array of CORS origins */ getCorsOrigins() { const originItems = this.widget.querySelectorAll('.bm-cors-origin-text'); return Array.from(originItems).map(item => item.textContent.trim()); } }