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
JavaScript
/**
* 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'
);
}
}
}