UNPKG

besper-frontend-site-dev-main

Version:

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

969 lines (860 loc) 34.8 kB
/** * Behaviour Tab Component * Handles the enhanced instruction system with sub-instructions and priority levels */ import { waitForElement, getSafeElement } from '../../../utils/DOMReady.js'; export class BehaviourTab { constructor(widget, state, i18n = null) { this.widget = widget; this.state = state; this.i18n = i18n; this.isReady = false; this.readyPromise = null; this.handlersInitialized = false; } /** * Update language * @param {string} language - New language code */ updateLanguage(language) { if (this.i18n) { console.log(`[BehaviourTab] Language updated to: ${language}`); // Re-render behaviour content if needed } } /** * 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(`behaviour.${key}`, variables) : key; } /** * Initialize the tab and ensure it's ready for operations * @returns {Promise<void>} Promise that resolves when tab is ready */ async initialize() { if (this.readyPromise) { return this.readyPromise; } this.readyPromise = this.createReadyPromise(); return this.readyPromise; } /** * Create the ready promise for this tab * @returns {Promise<void>} Promise that resolves when ready */ async createReadyPromise() { // Don't wait for DOM elements during initialization - they don't exist yet // Elements are created when the tab HTML is rendered and inserted into DOM // We'll check for elements when they're actually needed this.isReady = true; console.log('[SUCCESS] BehaviourTab is ready'); } /** * Generate the HTML for the behaviour tab * @returns {string} Behaviour tab HTML string */ getHTML() { return ` <div class="bm-tab-content" id="bm-tab-behaviour"> <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"> <!-- System Instructions --> ${this.getSystemInstructionsCard()} <!-- Sub-Instructions --> ${this.getSubInstructionsCard()} </div> <div class="bm-actions"> <button class="bm-btn bm-btn-primary" id="bm-saveBehaviourBtn">${this.t('actions.save')}</button> <button class="bm-btn bm-btn-secondary" id="bm-refreshBehaviourBtn">${this.t('actions.refresh')}</button> </div> </div> `; } /** * Get System Instructions card HTML */ getSystemInstructionsCard() { return ` <div style="background: white; border: 1px solid #e1e7ef; border-radius: 4px; overflow: hidden; margin-bottom: 24px;"> <div style="padding: 20px 24px; border-bottom: 1px solid #e1e7ef; display: flex; justify-content: space-between; align-items: center;"> <div> <h3 style="font-size: 16px; font-weight: 500; color: #1a1f2c; margin: 0; 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> ${this.t('systemInstruction.title')} </h3> <p style="font-size: 13px; color: #6b7684; margin: 4px 0 0 0;"> ${this.t('systemInstruction.subtitle')} </p> </div> </div> <div style="padding: 24px;"> <div class="bm-form-group" style="margin-bottom: 16px;"> <label style="display: block; font-size: 13px; font-weight: 500; color: #1a1f2c; margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.05em;">${this.t('systemInstruction.title')}</label> <textarea style="width: 100%; padding: 10px 12px; border: 1px solid #e1e7ef; border-radius: 4px; font-size: 14px; color: #1a1f2c; transition: all 0.2s ease; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.5; resize: vertical;" id="bm-systemInstructionInput" placeholder="${this.t('systemInstruction.placeholder')}" rows="6" onfocus="this.style.borderColor='#022d54'; this.style.outline='none'" onblur="this.style.borderColor='#e1e7ef'" ></textarea> <p style="font-size: 12px; color: #6b7684; margin-top: 4px;"> ${this.t('systemInstruction.hint')} </p> </div> </div> </div> `; } /** * Get Sub-Instructions card HTML */ getSubInstructionsCard() { return ` <div style="background: white; border: 1px solid #e1e7ef; border-radius: 4px; overflow: hidden;"> <div style="padding: 20px 24px; border-bottom: 1px solid #e1e7ef; display: flex; justify-content: space-between; align-items: center;"> <div> <h3 style="font-size: 16px; font-weight: 500; color: #1a1f2c; margin: 0; 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="M9 12L11 14L15 10"/> <path d="M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z"/> </svg> ${this.t('subInstructions.title')} </h3> <p style="font-size: 13px; color: #6b7684; margin: 4px 0 0 0;"> ${this.t('subInstructions.subtitle')} </p> </div> <div style="display: flex; gap: 12px;"> <button style="padding: 8px 16px; border-radius: 4px; font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s ease; background: #022d54; border: 1px solid #022d54; color: white; display: flex; align-items: center; gap: 8px;" id="bm-addSubInstructionBtn"> <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('subInstructions.addInstruction')} </button> </div> </div> <div style="padding: 24px;"> <!-- Priority System Info --> <div style="background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 4px; padding: 16px; margin-bottom: 24px;"> <h4 style="font-size: 14px; font-weight: 500; color: #1a1f2c; margin: 0 0 8px 0;">Instruction Priority System</h4> <p style="font-size: 13px; color: #6b7684; margin: 0; line-height: 1.5;"> 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. </p> </div> <div id="bm-subInstructionsList" class="bm-sub-instructions-list"> <!-- Sub-instructions will be dynamically added here --> </div> <div style="display: flex; gap: 24px; margin-top: 16px; padding: 16px 0; border-top: 1px solid #f0f4f8;"> <div style="display: flex; align-items: center; gap: 8px;"> <div style="width: 8px; height: 8px; border-radius: 50%; background: #dc2626;"></div> <span style="font-size: 12px; color: #6b7684;">High Priority - Must be followed strictly</span> </div> <div style="display: flex; align-items: center; gap: 8px;"> <div style="width: 8px; height: 8px; border-radius: 50%; background: #22c55e;"></div> <span style="font-size: 12px; color: #6b7684;">Normal Priority - Additional guidance</span> </div> </div> </div> </div> `; } /** * Generate HTML for a single sub-instruction item */ getSubInstructionItemHTML(instruction, index) { const priorityClass = instruction.priority === 'high' ? 'bm-priority-high' : 'bm-priority-normal'; return ` <div class="bm-sub-instruction-item ${priorityClass}" data-index="${index}"> <div class="bm-sub-instruction-header"> <input type="text" class="bm-form-input bm-sub-instruction-title" placeholder="Rule title..." value="${instruction.title || ''}" > <div class="bm-sub-instruction-controls"> <select class="bm-form-select bm-sub-instruction-priority"> <option value="normal" ${instruction.priority === 'normal' ? 'selected' : ''}>Normal Priority</option> <option value="high" ${instruction.priority === 'high' ? 'selected' : ''}>High Priority</option> </select> <button class="bm-btn bm-btn-small bm-btn-danger bm-remove-sub-instruction" type="button"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="M3 6h18"/> <path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/> <path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/> </svg> </button> </div> </div> <textarea class="bm-form-textarea bm-sub-instruction-content" placeholder="Enter the instruction content..." rows="3" >${instruction.content || ''}</textarea> <div class="bm-character-count"> <span class="bm-char-current">${(instruction.content || '').length}</span>/500 characters </div> </div> `; } /** * Initialize event handlers for the behaviour tab */ async initializeHandlers() { // Wait for next frame to ensure DOM is updated await new Promise(resolve => requestAnimationFrame(resolve)); // Add sub-instruction button const addBtn = this.getInputElement('bm-addSubInstructionBtn'); if (addBtn) { addBtn.addEventListener('click', () => this.addSubInstruction()); } // Save behaviour button const saveBtn = this.getInputElement('bm-saveBehaviourBtn'); if (saveBtn) { saveBtn.addEventListener('click', () => this.saveBehaviour()); } // Refresh behaviour button const refreshBtn = this.getInputElement('bm-refreshBehaviourBtn'); if (refreshBtn) { refreshBtn.addEventListener('click', () => this.refreshBehaviour()); } // Initialize existing sub-instruction handlers this.initializeSubInstructionHandlers(); // Mark handlers as initialized this.handlersInitialized = true; console.log('[SUCCESS] BehaviourTab handlers initialized'); } /** * Initialize handlers for sub-instruction items */ initializeSubInstructionHandlers() { const container = this.getInputElement('bm-subInstructionsList'); if (!container) return; // Remove existing listeners to avoid duplicates container.removeEventListener('change', this.handleSubInstructionChange); container.removeEventListener('click', this.handleSubInstructionClick); container.removeEventListener('input', this.handleSubInstructionInput); // Add event listeners container.addEventListener( 'change', this.handleSubInstructionChange.bind(this) ); container.addEventListener( 'click', this.handleSubInstructionClick.bind(this) ); container.addEventListener( 'input', this.handleSubInstructionInput.bind(this) ); } /** * Handle changes in sub-instruction controls */ handleSubInstructionChange(event) { if (event.target.classList.contains('bm-sub-instruction-priority')) { const item = event.target.closest('.bm-sub-instruction-item'); const priority = event.target.value; // Update priority class item.classList.remove('bm-priority-high', 'bm-priority-normal'); item.classList.add( priority === 'high' ? 'bm-priority-high' : 'bm-priority-normal' ); } } /** * Handle clicks in sub-instruction items */ handleSubInstructionClick(event) { if (event.target.closest('.bm-remove-sub-instruction')) { const item = event.target.closest('.bm-sub-instruction-item'); if (item) { item.remove(); } } } /** * Handle input changes for character counting */ handleSubInstructionInput(event) { if (event.target.classList.contains('bm-sub-instruction-content')) { const content = event.target.value; const counter = event.target.parentNode.querySelector('.bm-char-current'); if (counter) { counter.textContent = content.length; } } } /** * Add a new sub-instruction item */ /** * Safely add a sub-instruction, ensuring the container is ready */ async addSubInstructionSafely( instruction = { title: '', content: '', priority: 'normal' } ) { console.log('🔧 addSubInstructionSafely called with:', instruction); // Wait for the container to be available const container = await waitForElement( '#bm-subInstructionsList', this.widget, 5000 ); console.log(' container found via waitForElement:', !!container); const index = container.children.length; const html = this.getSubInstructionItemHTML(instruction, index); console.log(` Adding sub-instruction at index ${index}`); container.insertAdjacentHTML('beforeend', html); // Re-initialize handlers for the new item this.initializeSubInstructionHandlers(); console.log(' [SUCCESS] Sub-instruction added and handlers initialized'); } /** * Add a sub-instruction (legacy method, now uses safe version) */ async addSubInstruction( instruction = { title: '', content: '', priority: 'normal' } ) { return this.addSubInstructionSafely(instruction); } /** * Load instructions data into the form */ async loadInstructions(instructionsData) { console.log( '🔧 BehaviourTab.loadInstructions() called with:', instructionsData ); // Ensure tab is ready before loading data await this.initialize(); // Process new object format if (instructionsData && typeof instructionsData === 'object') { // Load system instruction const systemInput = this.getInputElement('bm-systemInstructionInput'); console.log(' systemInput element found:', !!systemInput); if (systemInput && instructionsData.system_instruction) { systemInput.value = instructionsData.system_instruction; console.log( ' [SUCCESS] System instruction loaded:', instructionsData.system_instruction.substring(0, 50) + '...' ); } // Clear existing sub-instructions const container = this.getInputElement('bm-subInstructionsList'); console.log(' subInstructionsList container found:', !!container); if (container) { container.innerHTML = ''; } // Load sub-instructions with enhanced error handling if ( instructionsData.sub_instructions && Array.isArray(instructionsData.sub_instructions) ) { console.log( ` 📋 Loading ${instructionsData.sub_instructions.length} sub-instructions` ); try { // Add all sub-instructions for (const [ index, instruction, ] of instructionsData.sub_instructions.entries()) { console.log( ` Processing sub-instruction ${index}:`, instruction ); await this.addSubInstructionSafely(instruction); } // Verify sub-instructions were actually added const addedItems = container ? container.querySelectorAll('.bm-sub-instruction-item') : []; console.log( ` [SUCCESS] Verification: ${addedItems.length} sub-instruction items in DOM` ); if (addedItems.length !== instructionsData.sub_instructions.length) { console.warn( ` [WARN] Mismatch: Expected ${instructionsData.sub_instructions.length}, found ${addedItems.length}` ); throw new Error('Sub-instruction count mismatch'); } } catch (error) { console.error(' [ERROR] Error loading sub-instructions:', error); // Fallback: retry with individual error handling await this.retryLoadSubInstructions( instructionsData.sub_instructions ); } } else { console.log(' 📋 No sub-instructions to load'); } } else { console.log( ' [WARN] Invalid instructions data:', typeof instructionsData, instructionsData ); } } /** * Retry loading sub-instructions if initial load failed */ async retryLoadSubInstructions(subInstructions) { console.log('[LOADING] Retrying sub-instructions load...'); try { const container = await waitForElement( '#bm-subInstructionsList', this.widget, 5000 ); if (container && subInstructions && Array.isArray(subInstructions)) { container.innerHTML = ''; for (const [index, instruction] of subInstructions.entries()) { console.log( ` Retry: Processing sub-instruction ${index}:`, instruction ); try { await this.addSubInstructionSafely(instruction); } catch (error) { console.error( ` [ERROR] Failed to add sub-instruction ${index}:`, error ); } } } } catch (error) { console.error('[LOADING] Retry failed:', error); } } /** * Get input element safely using the new DOM utilities with fallbacks * @param {string} id - Element ID * @returns {Element|null} Element or null if not found */ getInputElement(id) { // Use the new getSafeElement utility first const element = getSafeElement(id, this.widget); if (element) { return element; } // Legacy fallback for special cases if (id === 'bm-systemInstructionInput') { console.log(`[LOADING] Checking alternative IDs for ${id}`); const legacyElement = getSafeElement('bm-systemInstruction', this.widget); if (legacyElement) { console.log( `[SUCCESS] Found alternative element bm-systemInstruction for ${id}` ); return legacyElement; } } // Only warn about missing elements if handlers are being initialized (tab is active) if (this.handlersInitialized) { console.warn(`[ERROR] Element ${id} not found with any strategy`); } return null; } /** * Force read current form values - ensures we get the latest user input * This method performs immediate DOM reading to capture current state */ forceReadCurrentFormValues() { console.log('🔧 forceReadCurrentFormValues() - capturing live DOM state'); // Get all possible references to the system instruction input - prioritize the correct one const candidates = [ // Priority 1: The correct BehaviourTab element that user is typing into this.widget ? this.widget.querySelector('#bm-systemInstructionInput') : null, document.getElementById('bm-systemInstructionInput'), // Priority 2: Legacy element from main HTML this.widget ? this.widget.querySelector('#bm-systemInstruction') : null, document.getElementById('bm-systemInstruction'), // Priority 3: Any other potential matches ...document.querySelectorAll('#bm-systemInstructionInput'), ...document.querySelectorAll('#bm-systemInstruction'), ...document.querySelectorAll('textarea[id*="systemInstruction"]'), // Priority 4: Fallback based on placeholder text ...document.querySelectorAll( 'textarea[placeholder*="helpful assistant"]' ), ].filter(el => el !== null); console.log( ` Found ${candidates.length} potential system instruction inputs` ); // Find the element with actual user input (not default values) let bestCandidate = null; let systemInstruction = ''; for (let i = 0; i < candidates.length; i++) { const input = candidates[i]; const value = input.value || ''; const isDefault = value === 'You are a helpful assistant.' || value === ''; console.log( ` Candidate ${i}: id="${input.id}", value="${value}" (length: ${value.length}), isDefault: ${isDefault}` ); // Prioritize non-default values if (!isDefault && value.trim().length > 0) { if (!bestCandidate || value.length > systemInstruction.length) { bestCandidate = input; systemInstruction = value; console.log( ` [SUCCESS] Using candidate ${i} as it has user input: "${value}"` ); } } // If no user input found yet, use the longest value as fallback else if (!bestCandidate && value.length > systemInstruction.length) { bestCandidate = input; systemInstruction = value; console.log( ` [LOADING] Fallback to candidate ${i} with value: "${value}"` ); } } return { systemInstruction: systemInstruction.trim(), element: bestCandidate || candidates[0] || null, }; } /** * Get current instructions data from the form */ getInstructionsData() { console.log('🔍 BehaviourTab.getInstructionsData() debug:'); console.log(' widget available:', !!this.widget); console.log( ' widget type:', this.widget ? typeof this.widget : 'undefined' ); // Always use force reading to ensure we get the most current user input console.log('[LOADING] Using force read to get current form state'); const forceRead = this.forceReadCurrentFormValues(); let systemInstruction = forceRead.systemInstruction; // Additional validation - if we got a default value, try standard approach as backup if ( !systemInstruction || systemInstruction === 'You are a helpful assistant.' ) { console.log( '[LOADING] Force read returned default/empty, trying standard approach as backup' ); const systemInput = this.getInputElement('bm-systemInstructionInput'); if ( systemInput && systemInput.value.trim() && systemInput.value.trim() !== 'You are a helpful assistant.' ) { systemInstruction = systemInput.value.trim(); console.log( '[SUCCESS] Standard approach provided better value:', `"${systemInstruction}"` ); } } console.log(' systemInstruction final:', `"${systemInstruction}"`); console.log(' force read element found:', !!forceRead.element); console.log( ' force read element id:', forceRead.element ? forceRead.element.id : 'null' ); // Enhanced sub instructions collection with better element finding const subInstructions = []; // Try multiple strategies to find sub-instruction items let items = []; // Strategy 1: Use container-based search const container = this.getInputElement('bm-subInstructionsList'); console.log(' sub-instructions container found:', !!container); if (container) { items = container.querySelectorAll('.bm-sub-instruction-item'); console.log(' Strategy 1 - container items found:', items.length); } // Strategy 2: Widget-scoped search if container approach failed if (items.length === 0 && this.widget) { items = this.widget.querySelectorAll('.bm-sub-instruction-item'); console.log(' Strategy 2 - widget items found:', items.length); } // Strategy 3: Document-wide search as last resort if (items.length === 0) { items = document.querySelectorAll('.bm-sub-instruction-item'); console.log(' Strategy 3 - document items found:', items.length); } // Strategy 4: Look for individual form elements directly (more robust) if (items.length === 0) { console.log( '[LOADING] No items found with class selector, trying direct form element search' ); // Look for any sub-instruction related input fields const allInputs = document.querySelectorAll( 'input[class*="sub-instruction"], textarea[class*="sub-instruction"], select[class*="sub-instruction"]' ); console.log(' Found sub-instruction form elements:', allInputs.length); if (allInputs.length > 0) { // Group form elements by their parent container to reconstruct sub-instruction items const parentContainers = new Set(); allInputs.forEach(input => { const parent = input.closest( '[class*="sub-instruction-item"], .bm-sub-instruction-item' ); if (parent) { parentContainers.add(parent); } }); items = Array.from(parentContainers); console.log(' Reconstructed items from form elements:', items.length); } } // Process the found items if (items.length > 0) { console.log(`📝 Processing ${items.length} sub-instruction items`); this.processSubInstructionItems(items, subInstructions); } else { console.log('[ERROR] No sub-instruction items found with any strategy'); // Final fallback: manually search for any form fields that might contain sub-instructions const titleInputs = document.querySelectorAll( 'input[placeholder*="title"], input[placeholder*="Rule"]' ); const contentInputs = document.querySelectorAll( 'textarea[placeholder*="instruction"], textarea[placeholder*="content"]' ); console.log(' Final fallback - title inputs:', titleInputs.length); console.log(' Final fallback - content inputs:', contentInputs.length); // If we have matching counts, try to pair them up if ( titleInputs.length > 0 && contentInputs.length > 0 && titleInputs.length === contentInputs.length ) { console.log( '[LOADING] Attempting to manually reconstruct sub-instructions from separate fields' ); for ( let i = 0; i < Math.min(titleInputs.length, contentInputs.length); i++ ) { const title = titleInputs[i].value?.trim() || ''; const content = contentInputs[i].value?.trim() || ''; if (content) { // Only include if there's actual content subInstructions.push({ title: title || `Rule ${i + 1}`, content, priority: 'normal', // default priority }); console.log( ` [SUCCESS] Manually reconstructed sub-instruction ${i + 1}: "${title}"` ); } } } } const result = { system_instruction: systemInstruction || 'You are a helpful assistant.', sub_instructions: subInstructions, }; console.log('📋 Final instructions data:', result); console.log(' sub_instructions count:', result.sub_instructions.length); // Additional debugging for empty sub_instructions if (result.sub_instructions.length === 0) { console.log('[WARN] WARNING: No sub-instructions collected!'); console.log(' Performing additional diagnostics...'); // Check for any elements that might be sub-instructions but weren't caught const anySubElements = document.querySelectorAll( '[class*="sub"], [id*="sub"], [class*="instruction"], [id*="instruction"]' ); console.log( ' Found elements with "sub" or "instruction" in class/id:', anySubElements.length ); if (anySubElements.length > 0) { console.log(' Sample elements found:'); Array.from(anySubElements) .slice(0, 5) .forEach((el, i) => { console.log( ` ${i}: ${el.tagName} class="${el.className}" id="${el.id}"` ); }); } } return result; } /** * Process sub instruction items and extract their data * @param {NodeList} items - Sub instruction DOM elements * @param {Array} subInstructions - Array to populate with instruction data */ processSubInstructionItems(items, subInstructions) { console.log( `[LOADING] processSubInstructionItems: Processing ${items.length} items` ); items.forEach((item, index) => { console.log(` processing item ${index}:`, item); // Try multiple selector strategies for each field type const titleSelectors = [ '.bm-sub-instruction-title', 'input[class*="title"]', 'input[class*="sub-instruction"][class*="title"]', 'input[placeholder*="title"]', 'input[placeholder*="Rule"]', ]; const contentSelectors = [ '.bm-sub-instruction-content', 'textarea[class*="content"]', 'textarea[class*="sub-instruction"][class*="content"]', 'textarea[placeholder*="instruction"]', 'textarea[placeholder*="content"]', ]; const prioritySelectors = [ '.bm-sub-instruction-priority', 'select[class*="priority"]', 'select[class*="sub-instruction"][class*="priority"]', ]; let titleInput = null; let contentInput = null; let prioritySelect = null; // Find title input using multiple strategies for (const selector of titleSelectors) { titleInput = item.querySelector(selector); if (titleInput) { console.log(` Found title input with selector: ${selector}`); break; } } // Find content input using multiple strategies for (const selector of contentSelectors) { contentInput = item.querySelector(selector); if (contentInput) { console.log(` Found content input with selector: ${selector}`); break; } } // Find priority select using multiple strategies for (const selector of prioritySelectors) { prioritySelect = item.querySelector(selector); if (prioritySelect) { console.log(` Found priority select with selector: ${selector}`); break; } } console.log(` titleInput found:`, !!titleInput); console.log(` contentInput found:`, !!contentInput); console.log(` prioritySelect found:`, !!prioritySelect); // Extract values with more robust handling const title = titleInput ? titleInput.value?.trim() || '' : ''; const content = contentInput ? contentInput.value?.trim() || '' : ''; const priority = prioritySelect ? prioritySelect.value?.trim() || 'normal' : 'normal'; console.log( ` item ${index}: title="${title}", content="${content}", priority="${priority}"` ); // More forgiving inclusion criteria - include if there's any meaningful content if (content && content.length > 0) { const instruction = { title: title || `Rule ${index + 1}`, // Provide default title if missing content, priority, }; subInstructions.push(instruction); console.log( ` [SUCCESS] Added sub instruction ${index}: "${instruction.title}"` ); } else { console.log( ` [WARN] Skipped empty sub instruction ${index} (no content)` ); // Additional debugging for empty content if (contentInput) { console.log(` Content input element:`, contentInput); console.log(` Content input value:`, contentInput.value); console.log(` Content input innerHTML:`, contentInput.innerHTML); console.log( ` Content input textContent:`, contentInput.textContent ); } } }); console.log( `[LOADING] processSubInstructionItems: Added ${subInstructions.length} valid sub-instructions` ); } /** * Save behaviour instructions */ async saveBehaviour() { console.log('🔧 BehaviourTab.saveBehaviour() called'); try { // Force a final form value sync before reading data console.log('[LOADING] Forcing final form value sync before save...'); // Add a small delay to ensure DOM is fully updated await new Promise(resolve => setTimeout(resolve, 100)); // Get the current DOM state right before saving const instructionsData = this.getInstructionsData(); console.log('📋 Instructions data collected:', instructionsData); console.log( '📋 Sub-instructions count in collected data:', instructionsData.sub_instructions?.length || 0 ); // Call the widget's save method with the new instructions format if (this.widget && typeof this.widget.saveInstructions === 'function') { console.log('💾 Calling widget.saveInstructions...'); await this.widget.saveInstructions(instructionsData); console.log( '[SUCCESS] Instructions saved successfully via BehaviourTab' ); } else { console.error('[ERROR] widget.saveInstructions method not found'); this.widget.showNotification( 'Save method not available. Please refresh the page.', 'error' ); } } catch (error) { console.error('Error saving behaviour:', error); // Show error message to user this.widget.showNotification( 'Failed to save instructions. Please try again.', 'error' ); } } /** * Refresh behaviour data */ async refreshBehaviour() { try { if (this.widget && typeof this.widget.refreshData === 'function') { await this.widget.refreshData(); // Reload instructions from state if (this.state && this.state.config && this.state.config.instructions) { this.loadInstructions(this.state.config.instructions); } } } catch (error) { console.error('Error refreshing behaviour:', error); this.widget.showNotification( 'Failed to refresh data. Please try again.', 'error' ); } } }