/** * HVAC AI Assist JavaScript * * Handles AI-powered event population modal interface and form integration * * @package HVAC_Community_Events * @since 3.2.0 */ jQuery(document).ready(function($) { 'use strict'; /** * AI Assist functionality object */ const HVACAIAssist = { // Properties modal: null, isProcessing: false, currentInput: '', currentInputType: 'auto', // Initialize init: function() { this.createModal(); this.bindEvents(); this.enableAIButton(); }, /** * Create the AI modal interface */ createModal: function() { const modalHTML = ` `; $('body').append(modalHTML); this.modal = $('#hvac-ai-modal'); }, /** * Bind event handlers */ bindEvents: function() { const self = this; // AI Assist button click $(document).on('click', '#ai-assist-btn', function(e) { e.preventDefault(); if (!$(this).prop('disabled')) { self.openModal(); } }); // Modal close handlers $(document).on('click', '#ai-modal-close, #ai-modal-cancel, .modal-overlay', function() { self.closeModal(); }); // Tab switching $(document).on('click', '.tab-btn', function() { const type = $(this).data('type'); self.switchTab(type); }); // Process button $(document).on('click', '#ai-process-btn', function() { self.processInput(); }); // Apply button $(document).on('click', '#ai-apply-btn', function() { self.applyToForm(); }); // Input change handlers for validation $(document).on('input', '#ai-input-auto, #ai-input-url, #ai-input-text, #ai-input-description', function() { self.validateInput(); }); // ESC key handler $(document).on('keyup', function(e) { if (e.keyCode === 27 && self.modal.is(':visible')) { self.closeModal(); } }); }, /** * Enable the AI Assist button (remove placeholder status) */ enableAIButton: function() { const $aiBtn = $('#ai-assist-btn'); $aiBtn.removeClass('placeholder-btn') .prop('disabled', false) .attr('title', 'AI-powered event creation assistant') .text('AI Assist'); }, /** * Open the AI modal */ openModal: function() { this.modal.fadeIn(300); this.resetModal(); $('#ai-input-auto').focus(); }, /** * Close the AI modal */ closeModal: function() { if (!this.isProcessing) { this.modal.fadeOut(300); this.resetModal(); } }, /** * Reset modal to initial state */ resetModal: function() { // Reset tabs $('.tab-btn').removeClass('active'); $('.tab-btn[data-type="auto"]').addClass('active'); $('.input-tab-content').removeClass('active'); $('.input-tab-content[data-type="auto"]').addClass('active'); // Clear inputs $('#ai-input-auto, #ai-input-url, #ai-input-text, #ai-input-description').val(''); // Hide sections $('#ai-processing, #ai-results').hide(); $('.ai-input-section').show(); // Reset buttons $('#ai-process-btn').show().prop('disabled', true); $('#ai-apply-btn').hide(); // Reset progress steps $('.progress-steps .step').removeClass('active'); $('.status-message').text('Analyzing your input...'); // Reset confidence indicator $('.confidence-fill').css('width', '0%'); $('.confidence-percent').text('0%'); $('.confidence-bar').removeClass('confidence-low confidence-medium confidence-high'); // Clear results content $('.result-fields').html(''); $('.warning-list').html(''); $('.result-warnings').hide(); // Clear stored data this.extractedData = null; // Reset properties this.currentInput = ''; this.currentInputType = 'auto'; this.isProcessing = false; }, /** * Switch input tabs */ switchTab: function(type) { $('.tab-btn').removeClass('active'); $(`.tab-btn[data-type="${type}"]`).addClass('active'); $('.input-tab-content').removeClass('active'); $(`.input-tab-content[data-type="${type}"]`).addClass('active'); this.currentInputType = type; // Focus on the input field $(`#ai-input-${type}`).focus(); this.validateInput(); }, /** * Validate current input */ validateInput: function() { const input = this.getCurrentInput(); const $processBtn = $('#ai-process-btn'); if (input.length >= 10) { $processBtn.prop('disabled', false); } else { $processBtn.prop('disabled', true); } }, /** * Get current input value */ getCurrentInput: function() { const activeTab = $('.input-tab-content.active'); const inputElement = activeTab.find('input, textarea'); return inputElement.val().trim(); }, /** * Process input through AI */ processInput: function() { const input = this.getCurrentInput(); if (input.length < 10) { this.showError('Please provide at least 10 characters of event information.'); return; } this.isProcessing = true; this.currentInput = input; // Show processing UI $('.ai-input-section').hide(); $('#ai-processing').show(); $('#ai-process-btn').hide(); // Start progress animation this.animateProgress(); // Make AJAX request this.makeAIRequest(input, this.currentInputType); }, /** * Animate processing steps */ animateProgress: function() { const isUrl = this.currentInputType === 'url'; const steps = isUrl ? [ { step: 1, message: 'Fetching webpage content...', delay: 500 }, { step: 2, message: 'Processing webpage data (this may take up to 40 seconds)...', delay: 3000 }, { step: 3, message: 'Extracting event details with AI...', delay: 15000 }, { step: 4, message: 'Preparing form data...', delay: 25000 } ] : [ { step: 1, message: 'Analyzing your input...', delay: 500 }, { step: 2, message: 'Extracting event details...', delay: 2000 }, { step: 3, message: 'Validating information...', delay: 4000 }, { step: 4, message: 'Preparing form data...', delay: 6000 } ]; steps.forEach(({ step, message, delay }) => { setTimeout(() => { if (this.isProcessing) { $(`.progress-steps .step[data-step="${step}"]`).addClass('active'); $('.status-message').text(message); } }, delay); }); }, /** * Make AJAX request to AI endpoint */ makeAIRequest: function(input, inputType) { const self = this; const requestData = { action: 'hvac_ai_populate_event', input: input, input_type: inputType, nonce: hvacAjaxVars.nonce // Assuming nonce is available }; $.ajax({ url: hvacAjaxVars.ajaxUrl, type: 'POST', data: requestData, timeout: inputType === 'url' ? 60000 : 50000, // 60 seconds for URLs, 50 for text success: function(response) { self.handleAISuccess(response); }, error: function(xhr, status, error) { self.handleAIError(xhr, status, error); } }); }, /** * Handle successful AI response */ handleAISuccess: function(response) { this.isProcessing = false; if (response.success && response.data && response.data.event_data) { this.displayResults(response.data.event_data); } else { const message = response.data && response.data.message ? response.data.message : 'Unexpected response format from AI service.'; this.showError(message); } }, /** * Handle AI request error */ handleAIError: function(xhr, status, error) { this.isProcessing = false; let message = 'AI service temporarily unavailable. Please try again later.'; if (status === 'timeout') { message = 'Request timed out. The AI might be processing a complex input. Please try with simpler content.'; } else if (xhr.responseJSON && xhr.responseJSON.data && xhr.responseJSON.data.message) { message = xhr.responseJSON.data.message; } this.showError(message); }, /** * Display AI extraction results */ displayResults: function(eventData) { $('#ai-processing').hide(); $('#ai-results').show(); // Update confidence indicator const confidence = eventData.confidence && eventData.confidence.overall ? eventData.confidence.overall : 0; const confidencePercent = Math.round(confidence * 100); $('.confidence-fill').css('width', confidencePercent + '%'); $('.confidence-percent').text(confidencePercent + '%'); // Color code confidence let confidenceClass = 'confidence-low'; if (confidencePercent >= 80) confidenceClass = 'confidence-high'; else if (confidencePercent >= 60) confidenceClass = 'confidence-medium'; $('.confidence-bar').removeClass('confidence-low confidence-medium confidence-high') .addClass(confidenceClass); // Display extracted fields this.displayExtractedFields(eventData); // Check for warnings this.checkAndDisplayWarnings(eventData); // Show apply button $('#ai-apply-btn').show(); // Store data for form application this.extractedData = eventData; }, /** * Display extracted fields summary */ displayExtractedFields: function(eventData) { const fieldsHtml = []; // Title if (eventData.title) { fieldsHtml.push(`
${this.escapeHtml(eventData.title)}
`); } // Description (truncated for display) if (eventData.description) { let descriptionPreview = eventData.description; // Truncate if too long for modal display if (descriptionPreview.length > 200) { descriptionPreview = descriptionPreview.substring(0, 200) + '...'; } fieldsHtml.push(`
${this.escapeHtml(descriptionPreview)}
`); } // Date and time if (eventData.start_date) { let dateDisplay = eventData.start_date; if (eventData.start_time) { dateDisplay += ` at ${eventData.start_time}`; } fieldsHtml.push(`
${this.escapeHtml(dateDisplay)}
`); } if (eventData.end_date) { let dateDisplay = eventData.end_date; if (eventData.end_time) { dateDisplay += ` at ${eventData.end_time}`; } fieldsHtml.push(`
${this.escapeHtml(dateDisplay)}
`); } // Venue if (eventData.venue_name) { let venueDisplay = eventData.venue_name; if (eventData.venue_address) { venueDisplay += ` (${eventData.venue_address})`; } fieldsHtml.push(`
${this.escapeHtml(venueDisplay)}
`); } // Cost if (eventData.cost !== null && eventData.cost !== undefined) { fieldsHtml.push(`
$${eventData.cost}
`); } $('.result-fields').html(fieldsHtml.join('')); }, /** * Check for and display warnings */ checkAndDisplayWarnings: function(eventData) { const warnings = []; // Check confidence levels if (eventData.confidence && eventData.confidence.per_field) { Object.entries(eventData.confidence.per_field).forEach(([field, confidence]) => { if (confidence < 0.7) { warnings.push(`${field} information may need review (${Math.round(confidence * 100)}% confidence)`); } }); } // Check for missing critical fields if (!eventData.title) warnings.push('Event title not found'); if (!eventData.start_date) warnings.push('Event date not found'); if (warnings.length > 0) { const warningsHtml = warnings.map(warning => `
  • ${this.escapeHtml(warning)}
  • `).join(''); $('.warning-list').html(warningsHtml); $('.result-warnings').show(); } }, /** * Apply extracted data to the event form */ applyToForm: function() { if (!this.extractedData) return; const data = this.extractedData; try { // Apply title if (data.title) { $('#event_title, [name="event_title"]').val(data.title); } // Apply description (handle TinyMCE, regular textarea, and rich text editor) if (data.description) { // Convert markdown to HTML for proper rich text editor formatting const htmlContent = this.markdownToHtml(data.description); console.log('Original markdown:', data.description); console.log('Converted HTML:', htmlContent); // Wait for TinyMCE to be fully initialized const applyToTinyMCE = () => { if (typeof tinyMCE !== 'undefined' && tinyMCE.get('event_description') && window.hvacTinyMCEReady) { console.log('Setting TinyMCE content'); tinyMCE.get('event_description').setContent(htmlContent); return true; } return false; }; // Use a more robust waiting mechanism const waitForTinyMCE = (maxAttempts = 20) => { let attempts = 0; const tryApply = () => { attempts++; if (applyToTinyMCE()) { console.log(`TinyMCE content applied successfully on attempt ${attempts}`); return; } if (attempts < maxAttempts) { setTimeout(tryApply, 250); } else { console.log('TinyMCE not available after maximum attempts, falling back to textarea'); // Update the hidden textarea with HTML content $('#event_description, [name="event_description"]').val(htmlContent); // Also update the visible rich text editor div if it exists const $richEditor = $('#event-description-editor'); if ($richEditor.length && $richEditor.is('[contenteditable]')) { $richEditor.html(htmlContent); } } }; tryApply(); }; waitForTinyMCE(); } // Apply start date and time (combine into datetime-local format) if (data.start_date) { let startDateTime = data.start_date; if (data.start_time) { startDateTime += 'T' + data.start_time; } else { // Default to 9:00 AM if no time specified startDateTime += 'T09:00'; } $('#event_start_datetime, [name="event_start_datetime"]').val(startDateTime); } // Apply end date and time (combine into datetime-local format) if (data.end_date) { let endDateTime = data.end_date; if (data.end_time) { endDateTime += 'T' + data.end_time; } else { // Default to 5:00 PM if no time specified endDateTime += 'T17:00'; } $('#event_end_datetime, [name="event_end_datetime"]').val(endDateTime); } // Apply cost if (data.cost !== null && data.cost !== undefined) { $('#event_cost, [name="event_cost"]').val(data.cost); } // Apply capacity if (data.capacity) { $('#event_capacity, [name="event_capacity"]').val(data.capacity); } // Apply URL if (data.url) { $('#event_url, [name="event_url"]').val(data.url); } // Apply venue fields (flatter structure) if (data.venue_name) { $('#venue_name, [name="venue_name"]').val(data.venue_name); } if (data.venue_address) { $('#venue_address, [name="venue_address"]').val(data.venue_address); } if (data.venue_city) { $('#venue_city, [name="venue_city"]').val(data.venue_city); } if (data.venue_state) { $('#venue_state, [name="venue_state"]').val(data.venue_state); } if (data.venue_zip) { $('#venue_zip, [name="venue_zip"]').val(data.venue_zip); } // Apply organizer fields (flatter structure) if (data.organizer_name) { $('#organizer_name, [name="organizer_name"]').val(data.organizer_name); } if (data.organizer_email) { $('#organizer_email, [name="organizer_email"]').val(data.organizer_email); } if (data.organizer_phone) { $('#organizer_phone, [name="organizer_phone"]').val(data.organizer_phone); } // Apply website URL if (data.website) { $('#website, [name="website"]').val(data.website); } // Apply event URL if (data.event_url) { $('#event_url, [name="event_url"]').val(data.event_url); } // Apply timezone (if provided) if (data.timezone) { $('#event_timezone, [name="event_timezone"]').val(data.timezone); } // Apply event image (only if >= 200x200px) if (data.event_image_url) { $('#event_image_url, [name="event_image_url"]').val(data.event_image_url); } // Trigger autosave if available if (typeof performAutoSave === 'function') { setTimeout(performAutoSave, 1000); } // Close modal and show success message this.closeModal(); this.showSuccess('Event information applied successfully! Please review and adjust as needed before submitting.'); } catch (error) { console.error('Error applying AI data to form:', error); this.showError('Error applying data to form. Please try filling the fields manually.'); } }, /** * Show error message */ showError: function(message) { // Reset processing UI $('#ai-processing').hide(); $('.ai-input-section').show(); $('#ai-process-btn').show(); this.isProcessing = false; // Show error in modal or as alert alert('Error: ' + message); }, /** * Show success message */ showSuccess: function(message) { // You might want to show this in a nicer way, like a toast notification alert(message); }, /** * Convert markdown to HTML for rich text editor */ markdownToHtml: function(markdown) { // Test the function with sample input if (window.hvacDebugMarkdown) { console.log('Testing markdown conversion:'); console.log('Input:', markdown); } let html = markdown; // Convert headers (#### -> h4, ### -> h3, ## -> h2, # -> h1) html = html.replace(/^#### (.+)$/gm, '

    $1

    '); html = html.replace(/^### (.+)$/gm, '

    $1

    '); html = html.replace(/^## (.+)$/gm, '

    $1

    '); html = html.replace(/^# (.+)$/gm, '

    $1

    '); // Convert bold text (**text** -> text) html = html.replace(/\*\*([^*]+)\*\*/g, '$1'); // Convert italic text (*text* -> text) html = html.replace(/\*([^*]+)\*/g, '$1'); // Process lines for better list handling const lines = html.split('\n'); const processedLines = []; let inList = false; for (let i = 0; i < lines.length; i++) { let line = lines[i].trim(); // Handle bullet list items if (line.match(/^\* (.+)$/)) { const listItemContent = line.replace(/^\* (.+)$/, '$1'); if (!inList) { processedLines.push(''); inList = false; } } else { // Close list if we were in one if (inList) { processedLines.push(''); inList = false; } // Add regular line if (line !== '') { processedLines.push(line); } else { processedLines.push(''); // Preserve empty lines for paragraph breaks } } } // Close any remaining open list if (inList) { processedLines.push(''); } // Convert to paragraphs const paragraphs = []; let currentParagraph = ''; for (let line of processedLines) { // Skip empty lines if (line === '') { if (currentParagraph) { paragraphs.push(currentParagraph); currentParagraph = ''; } continue; } // If line is already wrapped in HTML tags, add it as is if (line.match(/^<(h[1-6]|ul|\/ul|li|strong|em)/)) { if (currentParagraph) { paragraphs.push(currentParagraph); currentParagraph = ''; } paragraphs.push(line); } else { // Regular text line if (currentParagraph) { currentParagraph += ' ' + line; } else { currentParagraph = line; } } } // Add final paragraph if exists if (currentParagraph) { paragraphs.push(currentParagraph); } // Wrap non-HTML paragraphs in

    tags const formattedParagraphs = paragraphs.map(p => { if (p.match(/^<(h[1-6]|ul|\/ul|li)/)) { return p; } else { return '

    ' + p + '

    '; } }); const result = formattedParagraphs.join('\n'); if (window.hvacDebugMarkdown) { console.log('Output:', result); } return result; }, /** * Escape HTML for safe display */ escapeHtml: function(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } }; // Initialize when document is ready HVACAIAssist.init(); // Add global test function for debugging window.testMarkdownConversion = function(testMarkdown) { window.hvacDebugMarkdown = true; console.log('=== MARKDOWN CONVERSION TEST ==='); const testInput = testMarkdown || `## Event Overview This is a **bold** text and *italic* text example. #### Key Details * First item in list * Second item in list * Third item in list ### Additional Information Here's a regular paragraph with more details.`; const result = HVACAIAssist.markdownToHtml(testInput); console.log('=== TEST COMPLETE ==='); // Also test setting it to TinyMCE if available if (typeof tinyMCE !== 'undefined' && tinyMCE.get('event_description')) { console.log('Setting test content to TinyMCE...'); tinyMCE.get('event_description').setContent(result); } else { console.log('TinyMCE not available for testing'); } window.hvacDebugMarkdown = false; return result; }; console.log('HVAC AI Assist loaded. Use testMarkdownConversion() to test markdown conversion.'); });