/* global wpforo, $wpf, wpforo_phrase, wpforo_load_hide */ /** * wpForo AI Features JavaScript * * AI-powered search and helper functionality * Requires: frontend.js to be loaded first for $wpf, wpforo_phrase, wpforo_load_hide */ $wpf(document).ready(function ($) { var wpforo_wrap = $('#wpforo-wrap'); // ========================================================================= // AI HELPER TOGGLE & TABS // ========================================================================= // AI Helper Toggle wpforo_wrap.on('click', '.wpf-ai-helper-toggle', function () { var helper = $(this).closest('.wpforo-ai-helper'); helper.toggleClass('wpf-ai-open'); $('.wpf-ai-helper-content', helper).slideToggle(350); }); // AI Helper Close Button wpforo_wrap.on('click', '.wpf-ai-tab-close', function () { var helper = $(this).closest('.wpforo-ai-helper'); helper.removeClass('wpf-ai-open'); $('.wpf-ai-helper-content', helper).slideUp(350); }); // AI Helper Tab Switching wpforo_wrap.on('click', '.wpf-ai-tab', function () { var tab = $(this); var tabId = tab.data('tab'); var helper = tab.closest('.wpf-ai-helper-inner'); // Switch active tab $('.wpf-ai-tab', helper).removeClass('wpf-ai-tab-active'); tab.addClass('wpf-ai-tab-active'); // Switch active content $('.wpf-ai-tab-content', helper).removeClass('wpf-ai-tab-content-active'); $('.wpf-ai-tab-content[data-tab-content="' + tabId + '"]', helper).addClass('wpf-ai-tab-content-active'); }); // AI Search Mode Toggle (switch between AI Search and Classic Search forms) wpforo_wrap.on('click', '.wpf-ai-search-mode', function () { var mode = $(this); var modeId = mode.data('mode'); var tabContent = mode.closest('.wpf-ai-tab-content'); // Switch active mode $('.wpf-ai-search-mode', tabContent).removeClass('wpf-ai-search-mode-active'); mode.addClass('wpf-ai-search-mode-active'); // Switch active form $('.wpf-ai-search-form', tabContent).removeClass('wpf-ai-search-form-active'); $('.wpf-ai-search-form[data-search-form="' + modeId + '"]', tabContent).addClass('wpf-ai-search-form-active'); }); // ========================================================================= // AI PREFERENCES // ========================================================================= // Save AI Preferences wpforo_wrap.on('submit', '.wpf-ai-preferences-form', function (e) { e.preventDefault(); wpforo_load_hide(); var form = $(this); var saveBtn = form.find('.wpf-ai-pref-save'); var statusEl = form.find('.wpf-ai-pref-status'); // Get form values var language = form.find('#wpf-ai-pref-language').val(); var maxResults = parseInt(form.find('#wpf-ai-pref-max-results').val(), 10); // Client-side validation (max 10 results) if (maxResults < 1) maxResults = 1; if (maxResults > 10) maxResults = 10; form.find('#wpf-ai-pref-max-results').val(maxResults); // Disable button during save saveBtn.prop('disabled', true).text(wpforo_phrase('Saving...')); statusEl.removeClass('success error').text(''); // AJAX save $.ajax({ url: wpforo.ajax_url, type: 'POST', data: { action: 'wpforo_save_ai_preferences', nonce: form.find('#wpf_ai_pref_nonce').val(), language: language, max_results: maxResults }, success: function (response) { saveBtn.prop('disabled', false).text(wpforo_phrase('Save Preferences')); if (response.success) { statusEl.addClass('success').text(wpforo_phrase('Preferences saved!')); // Update the search preferences for current session if (typeof aiSearchLimit !== 'undefined') { aiSearchLimit = maxResults; } if (typeof aiSearchLanguage !== 'undefined') { aiSearchLanguage = language; } } else { statusEl.addClass('error').text(response.data || wpforo_phrase('Error saving preferences')); } // Clear status after 3 seconds setTimeout(function () { statusEl.removeClass('success error').text(''); }, 3000); }, error: function (xhr) { saveBtn.prop('disabled', false).text(wpforo_phrase('Save Preferences')); // Try to get error message from response var errorMsg = wpforo_phrase('Network error. Please try again.'); try { var response = JSON.parse(xhr.responseText); if (response.data && response.data.message) { errorMsg = response.data.message; } } catch (e) { // Keep default error message } statusEl.addClass('error').text(errorMsg); } }); }); // ========================================================================= // AI SEMANTIC SEARCH // ========================================================================= var aiSearchOffset = 0; var aiSearchQuery = ''; var aiSearchLimit = 5; var aiSearchLanguage = 'en_US'; // Load preferences from data attribute (set by PHP with settings hierarchy) var aiHelper = $('.wpforo-ai-helper'); if (aiHelper.length && aiHelper.data('ai-preferences')) { var prefs = aiHelper.data('ai-preferences'); if (prefs.max_results) { aiSearchLimit = parseInt(prefs.max_results, 10) || 5; } if (prefs.language) { aiSearchLanguage = prefs.language; } } // AI Loading animation messages var aiLoadingMessages = [ 'Searching forum content...', 'Analyzing with AI...', 'Finding relevant discussions...', 'Processing semantic matches...', 'Generating AI summary...', 'Ranking results by relevance...', 'Almost ready...' ]; var aiLoadingInterval = null; function wpforoShowAiLoading(container) { var loadingHtml = '
' + '
' + '
' + '' + '' + '' + '
' + '
' + '
' + wpforo_phrase(aiLoadingMessages[0]) + '
' + '
'; container.html(loadingHtml); container.closest('.wpf-ai-results').show(); // Rotate through loading messages var msgIndex = 0; aiLoadingInterval = setInterval(function() { msgIndex = (msgIndex + 1) % aiLoadingMessages.length; container.find('.wpf-ai-loading-text').text(wpforo_phrase(aiLoadingMessages[msgIndex])); }, 3000); } function wpforoHideAiLoading() { if (aiLoadingInterval) { clearInterval(aiLoadingInterval); aiLoadingInterval = null; } } // AI Search Form Submit Handler wpforo_wrap.on('submit', '.wpf-ai-form', function (e) { e.preventDefault(); wpforo_load_hide(); var form = $(this); var input = form.find('.wpf-ai-input'); var query = input.val().trim(); var submitBtn = form.find('.wpf-ai-submit'); var resultsWrap = form.closest('.wpf-ai-search-form').find('.wpf-ai-results'); var resultsList = resultsWrap.find('.wpf-ai-results-list'); var moreBtn = resultsWrap.find('.wpf-ai-results-more'); if (!query) return; // Reset for new search aiSearchOffset = 0; aiSearchQuery = query; // Show loading state on button submitBtn.find('i').removeClass('fa-search').addClass('fa-spinner fa-spin'); submitBtn.prop('disabled', true); // Show AI loading animation in results area wpforoShowAiLoading(resultsList); moreBtn.hide(); // Perform search wpforoAiSearch(query, aiSearchLimit, aiSearchOffset, function (response) { // Stop loading animation wpforoHideAiLoading(); // Restore button submitBtn.find('i').removeClass('fa-spinner fa-spin').addClass('fa-search'); submitBtn.prop('disabled', false); if (response.success && response.data.results.length > 0) { // Render AI enhancement (Summary + Recommendations) first, then results // Pass results to enhancement renderer for resolving [[#N]] link markers var enhancementHtml = wpforoRenderAiEnhancement(response.data.ai_enhancement, response.data.results); var resultsHtml = wpforoRenderAiResults(response.data.results); resultsList.html(enhancementHtml + resultsHtml); resultsWrap.show(); // Hide recommendations and results until summary typewriter completes var recsSection = resultsList.find('.wpf-ai-recommendations-section'); var resultsSection = resultsList.find('.wpf-ai-result-wrapper'); recsSection.hide(); resultsSection.hide(); // Trigger typewriter effect for AI summary // When complete, reveal the other sections with animation var typewriterEl = resultsList.find('.wpf-ai-typewriter')[0]; if (typewriterEl) { var content = typewriterEl.getAttribute('data-typewriter-content'); if (content) { wpforoTypewriterEffect(typewriterEl, content, 10, function() { // Summary complete - now show recommendations and results recsSection.slideDown(300); setTimeout(function() { resultsSection.slideDown(400); }, 150); }); } else { // No summary content - show sections immediately recsSection.show(); resultsSection.show(); } } else { // No typewriter element - show sections immediately recsSection.show(); resultsSection.show(); } // Show/hide more button if (response.data.has_more) { moreBtn.show(); } else { moreBtn.hide(); } aiSearchOffset = aiSearchLimit; } else if (response.success && response.data.no_indexed_content) { resultsList.html('

' + response.data.message + '

'); resultsWrap.show(); moreBtn.hide(); } else if (response.success && response.data.results.length === 0) { resultsList.html('

' + wpforo_phrase('No results found') + '

'); resultsWrap.show(); moreBtn.hide(); } else { resultsList.html('

' + (response.data ? response.data.message : wpforo_phrase('Search failed')) + '

'); resultsWrap.show(); moreBtn.hide(); } }); }); // More Results button wpforo_wrap.on('click', '.wpf-ai-more-btn', function () { var btn = $(this); var resultsWrap = btn.closest('.wpf-ai-results'); var resultsList = resultsWrap.find('.wpf-ai-results-list'); // Show loading btn.prop('disabled', true).text(wpforo_phrase('Loading...')); wpforoAiSearch(aiSearchQuery, aiSearchLimit, aiSearchOffset, function (response) { btn.prop('disabled', false).text(wpforo_phrase('More Results')); if (response.success && response.data.results.length > 0) { resultsList.append(wpforoRenderAiResults(response.data.results)); aiSearchOffset += aiSearchLimit; if (!response.data.has_more) { btn.parent().hide(); } } }); }); // AI Search AJAX function function wpforoAiSearch(query, limit, offset, callback) { $.ajax({ url: wpforo.ajax_url, type: 'POST', data: { action: 'wpforo_ai_public_search', query: query, limit: limit, offset: offset, language: aiSearchLanguage, _wpnonce: wpforo.nonces.wpforo_ai_public_search }, success: callback, error: function (xhr) { // Try to get error message from response (handles 429 rate limit errors) var errorMsg = wpforo_phrase('Request failed'); try { var response = JSON.parse(xhr.responseText); if (response.data && response.data.message) { errorMsg = response.data.message; } } catch (e) { // Keep default error message } callback({ success: false, data: { message: errorMsg } }); } }); } // ========================================================================= // TYPEWRITER EFFECT // ========================================================================= // Typewriter effect for AI summary text // onComplete callback fires when typing is finished function wpforoTypewriterEffect(element, html, speed, onComplete) { if (!element || !html) { if (onComplete) onComplete(); return; } speed = speed || 15; // milliseconds per character // Parse HTML to extract text nodes and tags var tempDiv = document.createElement('div'); tempDiv.innerHTML = html; element.innerHTML = ''; element.style.visibility = 'visible'; // Recursive function to type through DOM nodes function typeNode(node, callback) { if (node.nodeType === Node.TEXT_NODE) { // Text node - type character by character var text = node.textContent; var textNode = document.createTextNode(''); element.appendChild(textNode); var charIndex = 0; function typeChar() { if (charIndex < text.length) { textNode.textContent += text[charIndex]; charIndex++; setTimeout(typeChar, speed); } else { callback(); } } typeChar(); } else if (node.nodeType === Node.ELEMENT_NODE) { // Element node - clone and append, then process children var clone = node.cloneNode(false); element.appendChild(clone); var children = Array.from(node.childNodes); var childIndex = 0; function processNextChild() { if (childIndex < children.length) { // Temporarily change element to append to clone var originalElement = element; element = clone; typeNode(children[childIndex], function() { element = originalElement; childIndex++; processNextChild(); }); } else { callback(); } } processNextChild(); } else { callback(); } } // Process all top-level nodes var topNodes = Array.from(tempDiv.childNodes); var nodeIndex = 0; function processNextTopNode() { if (nodeIndex < topNodes.length) { typeNode(topNodes[nodeIndex], function() { nodeIndex++; processNextTopNode(); }); } else { // All nodes processed - call onComplete callback if (onComplete) onComplete(); } } processNextTopNode(); } // ========================================================================= // RENDER FUNCTIONS // ========================================================================= // Render AI Enhancement sections (Summary and Recommendations) // All HTML is pre-rendered by PHP - JavaScript only inserts it function wpforoRenderAiEnhancement(enhancement, results) { if (!enhancement) return ''; var html = ''; // AI Search Summary Section // PHP already converts [[#N]] and [[#N:Title]] to HTML links if (enhancement.summary || enhancement.quick_answer) { html += '
'; html += '
' + wpforo_phrase('AI Search Summary') + '
'; if (enhancement.quick_answer) { // Output directly - PHP has already processed link markers html += '
' + enhancement.quick_answer + '
'; } if (enhancement.summary) { // Store summary in data attribute for typewriter effect var encodedSummary = enhancement.summary.replace(/"/g, '"'); html += ''; } html += '
'; } // AI Recommendations Section - use pre-rendered HTML from PHP if (enhancement.recommendations_html) { html += enhancement.recommendations_html; } return html; } // Render AI search results HTML function wpforoRenderAiResults(results) { var html = ''; // Add "AI Search Results" header before real results html += '
'; html += '
' + wpforo_phrase('AI Search Results') + '
'; for (var i = 0; i < results.length; i++) { var r = results[i]; html += '
'; var postIdBadge = r.post_id ? ' [ ' + r.post_id + ' ]' : ''; // Render title as link only if URL exists, otherwise plain text if (r.url) { html += ''; } else { html += '
' + wpforoEscapeHtml(r.title) + '
'; } html += '
'; if (r.content_source === 'custom_knowledge') { var kbLabel = r.post_type_label || 'Knowledge Base'; html += ' ' + wpforoEscapeHtml(kbLabel) + ''; } else if (r.content_source === 'wordpress') { var typeLabel = r.post_type_label || 'Post'; html += ' ' + wpforoEscapeHtml(typeLabel) + ''; } else if (r.forum_title) { html += ' ' + wpforoEscapeHtml(r.forum_title) + ''; } // Only show author if exists (custom_knowledge has no author) if (r.author_name) { html += ' ' + wpforoEscapeHtml(r.author_name) + ''; } // Only show date if exists (custom_knowledge has no date) if (r.created_ago) { html += ' ' + r.created_ago + ''; } html += ' ' + r.score + '%'; html += '
'; if (r.content) { var formattedContent = wpforoFormatAiContent(r.content); var lineCount = (r.content.match(/[\r\n]+/g) || []).length + 1; var isLong = r.content.length > 500 || lineCount > 5; html += '
'; html += '
' + formattedContent + '
'; if (isLong) { html += '
' + wpforo_phrase('Show more') + '
'; } html += '
'; } html += '
'; } html += '
'; return html; } // Format AI content: escape HTML and convert line breaks to
function wpforoFormatAiContent(text) { if (!text) return ''; // First escape HTML var escaped = wpforoEscapeHtml(text); // Convert \r\n, \r, \n to
for proper line breaks escaped = escaped.replace(/\r\n/g, '
'); escaped = escaped.replace(/\r/g, '
'); escaped = escaped.replace(/\n/g, '
'); // Convert multiple
to paragraph breaks escaped = escaped.replace(/(
){3,}/g, '

'); return escaped; } // Toggle AI search result content expand/collapse wpforo_wrap.on('click', '.wpf-ai-toggle-btn', function() { var $btn = $(this); var $wrap = $btn.closest('.wpf-ai-result-content-wrap'); var isExpanded = $btn.data('expanded'); if (isExpanded) { $wrap.addClass('wpf-ai-collapsed'); $btn.data('expanded', false); $btn.find('.wpf-ai-toggle-text').text(wpforo_phrase('Show more')); $btn.find('i').removeClass('fa-chevron-up').addClass('fa-chevron-down'); } else { $wrap.removeClass('wpf-ai-collapsed'); $btn.data('expanded', true); $btn.find('.wpf-ai-toggle-text').text(wpforo_phrase('Show less')); $btn.find('i').removeClass('fa-chevron-down').addClass('fa-chevron-up'); } }); // ========================================================================= // HELPER FUNCTIONS // ========================================================================= // Escape HTML helper function wpforoEscapeHtml(text) { if (!text) return ''; var div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // ========================================================================= // AI TRANSLATION // ========================================================================= // Toggle translation dropdown wpforo_wrap.on('click', '.wpf-ai-translate-btn', function (e) { e.stopPropagation(); var wrapper = $(this).closest('.wpf-ai-translate-wrapper'); var dropdown = wrapper.find('.wpf-ai-translate-dropdown'); // Close other dropdowns $('.wpf-ai-translate-dropdown').not(dropdown).removeClass('wpf-ai-translate-dropdown-open'); // Toggle this dropdown dropdown.toggleClass('wpf-ai-translate-dropdown-open'); }); // Close dropdown when clicking outside $(document).on('click', function () { $('.wpf-ai-translate-dropdown').removeClass('wpf-ai-translate-dropdown-open'); }); // Prevent dropdown from closing when clicking inside it wpforo_wrap.on('click', '.wpf-ai-translate-dropdown', function (e) { e.stopPropagation(); }); // Handle language selection for translation wpforo_wrap.on('click', '.wpf-ai-translate-option', function () { var option = $(this); var wrapper = option.closest('.wpf-ai-translate-wrapper'); var postId = wrapper.data('postid'); var language = option.data('lang'); var dropdown = wrapper.find('.wpf-ai-translate-dropdown'); var translateBtn = wrapper.find('.wpf-ai-translate-btn'); var originalBtn = wrapper.find('.wpf-ai-translate-original'); var loadingEl = wrapper.find('.wpf-ai-translate-loading'); // Close dropdown dropdown.removeClass('wpf-ai-translate-dropdown-open'); // Find the post/comment content element // Structure for Post: .post-wrap > .wpforo-post > .wpforo-post-content // Structure for Q&A Layout Comment: .comment-wrap > .wpforo-comment-content > .wpforo-comment-text var postElement = wrapper.closest('.wpforo-post').find('.wpforo-post-content'); if (!postElement.length) { postElement = wrapper.closest('.post-wrap').find('.wpforo-post-content'); } if (!postElement.length) { postElement = wrapper.closest('.wpforo-comment').find('.wpforo-comment-text'); } if (!postElement.length) { postElement = wrapper.closest('.comment-wrap').find('.wpforo-comment-text'); } if (!postElement.length) { console.error('wpForo AI: Could not find post content element'); return; } // Store original content if not already stored if (!postElement.data('original-content')) { postElement.data('original-content', postElement.html()); } // Show loading state translateBtn.hide(); loadingEl.show(); // Make AJAX request $.ajax({ url: wpforo.ajax_url, type: 'POST', data: { action: 'wpforo_ai_translate', post_id: postId, language: language, nonce: wpforo.nonces.wpforo_ai_translate }, success: function (response) { loadingEl.hide(); if (response.success && response.data.translated_content) { // Replace content with translated version postElement.html(response.data.translated_content); postElement.addClass('wpf-ai-translated'); // Add RTL class for right-to-left languages (Arabic, Hebrew) var rtlLanguages = ['Arabic', 'Hebrew', 'ar', 'he']; if (rtlLanguages.indexOf(language) !== -1) { postElement.addClass('wpf-ai-translated-rtl'); } // Show "Show Original" button originalBtn.show(); } else { // Show error translateBtn.show(); var errorMsg = response.data && response.data.message ? response.data.message : wpforo_phrase('Translation failed'); alert(errorMsg); } }, error: function (xhr) { loadingEl.hide(); translateBtn.show(); // Try to get error message from response (handles 429 rate limit errors) var errorMsg = wpforo_phrase('Network error. Please try again.'); try { var response = JSON.parse(xhr.responseText); if (response.data && response.data.message) { errorMsg = response.data.message; } } catch (e) { // Keep default error message } alert(errorMsg); } }); }); // Handle "Show Original" button click wpforo_wrap.on('click', '.wpf-ai-translate-original', function () { var wrapper = $(this).closest('.wpf-ai-translate-wrapper'); var originalBtn = wrapper.find('.wpf-ai-translate-original'); var translateBtn = wrapper.find('.wpf-ai-translate-btn'); // Find the post/comment content element var postElement = wrapper.closest('.wpforo-post').find('.wpforo-post-content'); if (!postElement.length) { postElement = wrapper.closest('.post-wrap').find('.wpforo-post-content'); } if (!postElement.length) { postElement = wrapper.closest('.wpforo-comment').find('.wpforo-comment-text'); } if (!postElement.length) { postElement = wrapper.closest('.comment-wrap').find('.wpforo-comment-text'); } // Restore original content var originalContent = postElement.data('original-content'); if (originalContent) { postElement.html(originalContent); postElement.removeClass('wpf-ai-translated wpf-ai-translated-rtl'); } // Show translate button, hide original button originalBtn.hide(); translateBtn.show(); }); // ========================================================================= // AI TOPIC SUMMARIZATION // ========================================================================= var summaryLoadingInterval = null; var summaryLoadingMessages = [ 'Reading topic posts...', 'Analyzing discussion...', 'Generating summary...', 'Almost ready...' ]; // Show loading animation for topic summary function wpforoShowSummaryLoading(container) { var loadingHtml = '
' + '
' + '
' + '' + '' + '' + '
' + '
' + '
' + wpforo_phrase(summaryLoadingMessages[0]) + '
' + '
'; container.html(loadingHtml); // Rotate through loading messages var msgIndex = 0; summaryLoadingInterval = setInterval(function() { msgIndex = (msgIndex + 1) % summaryLoadingMessages.length; container.find('.wpf-ai-loading-text').text(wpforo_phrase(summaryLoadingMessages[msgIndex])); }, 2500); } function wpforoHideSummaryLoading() { if (summaryLoadingInterval) { clearInterval(summaryLoadingInterval); summaryLoadingInterval = null; } } // Topic Summary Button Click Handler wpforo_wrap.on('click', '.wpf-ai-summarize-btn', function (e) { e.preventDefault(); wpforo_load_hide(); var btn = $(this); var topicId = btn.data('topicid'); var nonce = btn.data('nonce'); var container = $('#wpf-ai-summary-' + topicId); var contentArea = container.find('.wpf-ai-summary-content'); // If container is already visible and has content, just toggle it if (container.is(':visible') && contentArea.find('.wpf-ai-summary-result').length > 0) { container.slideUp(350); return; } // Show container with loading container.slideDown(350); wpforoShowSummaryLoading(contentArea); // Disable button during request btn.addClass('wpf-ai-loading-btn'); // Make AJAX request $.ajax({ url: wpforo.ajax_url, type: 'POST', data: { action: 'wpforo_ai_summarize_topic', topicid: topicId, nonce: nonce }, success: function (response) { wpforoHideSummaryLoading(); btn.removeClass('wpf-ai-loading-btn'); if (response.success && response.data.summary) { // Build posts info (show notice when posts were limited) var postsInfo = ''; if (response.data.posts_limited && response.data.total_posts_count) { postsInfo = '' + response.data.reply_count + ' ' + wpforo_phrase('of') + ' ' + response.data.total_posts_count + ' ' + wpforo_phrase('posts') + ''; } // Render summary with close button in header (like AI Assistant) var summaryHtml = '
' + '
' + ' ' + wpforo_phrase('AI Summary') + '' + '' + wpforo_phrase('Style') + ': ' + wpforoEscapeHtml(response.data.style || 'detailed') + '' + postsInfo + '
' + '' + '' + wpforo_phrase('Close') + '' + '
' + '
' + '
' + response.data.summary + '
' + '
'; contentArea.html(summaryHtml); } else { // Show error with close button var errorMsg = response.data && response.data.message ? response.data.message : wpforo_phrase('Failed to generate summary'); contentArea.html('
' + ' ' + wpforoEscapeHtml(errorMsg) + '
' + '' + '' + wpforo_phrase('Close') + '' + '
' + '
'); } }, error: function (xhr) { wpforoHideSummaryLoading(); btn.removeClass('wpf-ai-loading-btn'); // Try to get error message from response (handles 429 rate limit errors) var errorMsg = wpforo_phrase('Network error. Please try again.'); try { var response = JSON.parse(xhr.responseText); if (response.data && response.data.message) { errorMsg = response.data.message; } } catch (e) { // Keep default error message } contentArea.html('
' + ' ' + wpforoEscapeHtml(errorMsg) + '
' + '' + '' + wpforo_phrase('Close') + '' + '
' + '
'); } }); }); // Topic Summary Close Button Handler (handles both footer close and header close button) wpforo_wrap.on('click', '.wpf-ai-summary-close, .wpf-ai-summary-close-btn', function () { var container = $(this).closest('.wpf-ai-summary-container'); container.slideUp(350); }); // ========================================================================= // AI TOPIC SUGGESTIONS (Smart Topic Suggestions) // ========================================================================= var suggestionCallCount = 0; var suggestionConfig = null; var suggestionLastQuery = ''; // Initialize suggestion config from data attributes function wpforoInitSuggestionConfig() { var panel = $('.wpf-ai-suggestions-panel'); if (panel.length && panel.data('suggestion-config')) { suggestionConfig = panel.data('suggestion-config'); } else { // Default config - disabled if panel not found suggestionConfig = { enabled: false, min_words: 3, max_calls: 2, show_related: true, show_answer: true, quality: 'balanced' }; } } // Count words in a string function wpforoCountWords(str) { if (!str) return 0; return str.trim().split(/\s+/).filter(function(w) { return w.length > 0; }).length; } // Topic title input handler - triggers on blur (when user leaves the title field) // Topic title field has name="thread[title]" and id="thread_title" in wpForo wpforo_wrap.on('blur', '#thread_title, input[name="thread[title]"]', function (e) { // Initialize config if not done if (!suggestionConfig) { wpforoInitSuggestionConfig(); } // Check if suggestions are enabled if (!suggestionConfig || !suggestionConfig.enabled) { return; } var input = $(this); var title = input.val().trim(); var wordCount = wpforoCountWords(title); // Check minimum words (from config) if (wordCount < suggestionConfig.min_words) { return; } // Skip if same query as last time if (title === suggestionLastQuery) { return; } // Check max API calls per topic creation session (from config) if (suggestionCallCount >= suggestionConfig.max_calls) { return; } // Fetch suggestions immediately on blur (no debounce needed) wpforoFetchSuggestions(title, input); }); // Fetch suggestions from API function wpforoFetchSuggestions(title, inputElement) { if (!suggestionConfig || !suggestionConfig.enabled) return; suggestionLastQuery = title; suggestionCallCount++; var form = inputElement.closest('form'); var panel = form.find('.wpf-ai-suggestions-panel'); var contentArea = panel.find('.wpf-ai-suggestions-content'); // Show panel with loading immediately on blur panel.slideDown(300); wpforoShowSuggestionLoading(contentArea); // Make AJAX request $.ajax({ url: wpforo.ajax_url, type: 'POST', data: { action: 'wpforo_ai_get_topic_suggestions', title: title, quality: suggestionConfig.quality || 'balanced', include_similar: 1, // Always include similar topics - required for the feature include_related: suggestionConfig.show_related ? 1 : 0, include_answer: suggestionConfig.show_answer ? 1 : 0, nonce: wpforo.nonces.wpforo_ai_get_topic_suggestions }, success: function (response) { wpforoHideSuggestionLoading(); if (response.success && response.data.has_suggestions) { wpforoRenderSuggestions(contentArea, response.data); } else if (response.success && !response.data.has_suggestions) { // No similar topics found - show message briefly then hide contentArea.html('
' + ' ' + wpforo_phrase('No similar topics have been found.') + '
'); // Hide panel after 3 seconds setTimeout(function() { panel.slideUp(300); }, 3000); } else { // Error var errorMsg = response.data && response.data.message ? response.data.message : wpforo_phrase('Could not fetch suggestions'); contentArea.html('
' + ' ' + wpforoEscapeHtml(errorMsg) + '
'); } }, error: function (xhr) { wpforoHideSuggestionLoading(); // Try to get error message from response (handles 429 rate limit errors) var errorMsg = wpforo_phrase('Network error. Please try again.'); try { var response = JSON.parse(xhr.responseText); if (response.data && response.data.message) { errorMsg = response.data.message; } } catch (e) { // Keep default error message } contentArea.html('
' + ' ' + wpforoEscapeHtml(errorMsg) + '
'); } }); } // Loading animation for suggestions - compact single line var suggestionLoadingInterval = null; var suggestionLoadingMessages = [ 'Searching similar topics...', 'Analyzing your question...', 'Finding relevant discussions...', 'Almost ready...' ]; function wpforoShowSuggestionLoading(container) { var loadingHtml = '
' + '' + '' + wpforo_phrase(suggestionLoadingMessages[0]) + '' + '
'; container.html(loadingHtml); var msgIndex = 0; suggestionLoadingInterval = setInterval(function() { msgIndex = (msgIndex + 1) % suggestionLoadingMessages.length; container.find('.wpf-ai-loading-text-inline').text(wpforo_phrase(suggestionLoadingMessages[msgIndex])); }, 2000); } function wpforoHideSuggestionLoading() { if (suggestionLoadingInterval) { clearInterval(suggestionLoadingInterval); suggestionLoadingInterval = null; } } // Render suggestions UI function wpforoRenderSuggestions(container, data) { // No duplicate header - PHP already renders the main header var html = ''; // Similar Topics Section if (data.similar_topics && data.similar_topics.length > 0) { html += '
' + '
' + ' ' + wpforo_phrase('Similar Topics Already Exist') + '
' + '
' + '' + '
' + ' ' + wpforo_phrase('Check these topics - your question might already be answered!') + '
' + '
' + '
'; } // Related Topics Section (AI suggestions) if (data.related_topics && data.related_topics.length > 0) { html += ''; } // Quick AI Answer Section if (data.quick_answer && data.quick_answer.text) { html += '
' + '
' + '' + wpforo_phrase('Quick AI Answer') + '
' + '
' + '
' + wpforoFormatAiContent(data.quick_answer.text) + '
'; html += '
' + wpforo_phrase('This is an AI-generated answer based on existing forum content. Post your topic for more accurate human responses.') + '
' + '
' + '
'; } container.html(html); } // Close suggestions panel wpforo_wrap.on('click', '.wpf-ai-suggestions-close', function () { var panel = $(this).closest('.wpf-ai-suggestions-panel'); panel.slideUp(300); }); // Toggle show more/less for quick answer wpforo_wrap.on('click', '.wpf-ai-show-more-answer', function () { var btn = $(this); var answerText = btn.closest('.wpf-ai-suggestions-section-content').find('.wpf-ai-quick-answer-text'); var fullAnswer = btn.data('full'); var isExpanded = btn.data('expanded'); if (isExpanded) { // Collapse - would need original stored, for now just hide btn.html(' ' + wpforo_phrase('Show more')); btn.data('expanded', false); } else { answerText.html(wpforoFormatAiContent(fullAnswer)); btn.html(' ' + wpforo_phrase('Show less')); btn.data('expanded', true); } }); // Initialize suggestion config on page load wpforoInitSuggestionConfig(); // Re-initialize suggestion config when topic form is loaded via AJAX // wpForo triggers 'wpforo_topic_portable_form' event after AJAX form load $(document).on('wpforo_topic_portable_form', function(event, formElement) { // Reset suggestion state for new form suggestionCallCount = 0; suggestionLastQuery = ''; suggestionConfig = null; // Re-initialize config from the new form's panel if (formElement && formElement.length) { var panel = formElement.find('.wpf-ai-suggestions-panel'); if (panel.length && panel.data('suggestion-config')) { suggestionConfig = panel.data('suggestion-config'); } } // Fallback to global search if not found in form element if (!suggestionConfig) { wpforoInitSuggestionConfig(); } }); // ========================================================================= // AI BOT REPLY // ========================================================================= /** * AI Bot Reply button click handler * Creates a bot-generated reply to the post */ wpforo_wrap.on('click', '.wpf-ai-bot-reply', function (e) { e.preventDefault(); e.stopPropagation(); var btn = $(this); var postId = btn.data('postid'); var topicId = btn.data('topicid'); if (!postId || !topicId) { console.error('AI Bot Reply: Missing post or topic ID'); return; } // Prevent double-clicks (check for wpf-processing spinning class) if (btn.hasClass('wpf-processing')) { return; } // Check if nonce is available var nonce = wpforo.nonces && wpforo.nonces.wpforo_ai_bot_reply; if (!nonce) { alert(wpforo_phrase('AI Bot Reply is not properly configured. Please refresh the page and try again.')); return; } // Show loading state - spinning icon (like wpforo-aibot plugin) btn.addClass('wpf-processing'); if (typeof wpforo_load_show === 'function') { wpforo_load_show(); } // Make AJAX request $.ajax({ url: wpforo.ajax_url, type: 'POST', data: { action: 'wpforo_ai_bot_reply', _wpnonce: nonce, post_id: postId, topic_id: topicId } }).done(function (response) { if (response.success) { // Reload page to show new reply (with anchor to new post) var newPostId = response.data.post_id; setTimeout(function() { if (newPostId) { window.location.href = window.location.pathname + window.location.search + '#post-' + newPostId; window.location.reload(); } else { window.location.reload(); } }, 500); } else { btn.removeClass('wpf-processing'); if (typeof wpforo_load_hide === 'function') { wpforo_load_hide(); } var errorMsg = response.data && response.data.message ? response.data.message : wpforo_phrase('Failed to generate bot reply'); // Provide helpful message for common configuration issues if (errorMsg.indexOf('Bot user not configured') !== -1) { errorMsg = wpforo_phrase('Bot user not configured') + '.\n\n' + wpforo_phrase('Please go to') + ' wpForo > Settings > AI Features > AI Bot Reply ' + wpforo_phrase('and select a WordPress user for the bot.'); } alert(errorMsg); } }).fail(function (xhr, status, error) { btn.removeClass('wpf-processing'); if (typeof wpforo_load_hide === 'function') { wpforo_load_hide(); } console.error('AI Bot Reply error:', status, error, xhr.responseText); // Try to parse error message from response var errorMsg = wpforo_phrase('Network error. Please try again.'); try { var jsonResponse = JSON.parse(xhr.responseText); if (jsonResponse.data && jsonResponse.data.message) { errorMsg = jsonResponse.data.message; // Add helpful message for bot user not configured if (errorMsg.indexOf('Bot user not configured') !== -1) { errorMsg = wpforo_phrase('Bot user not configured') + '.\n\n' + wpforo_phrase('Please go to') + ' wpForo > Settings > AI Features > AI Bot Reply ' + wpforo_phrase('and select a WordPress user for the bot.'); } } } catch (e) { // Keep default error message } alert(errorMsg); }); }); /** * AI Suggest Reply button click handler * Generates AI reply suggestion and inserts into TinyMCE editor */ wpforo_wrap.on('click', '.wpf-ai-suggest-reply', function (e) { e.preventDefault(); var btn = $(this); var topicId = btn.data('topicid'); if (!topicId) { console.error('AI Suggest Reply: Missing topic ID'); return; } // Prevent double-clicks if (btn.hasClass('wpf-ai-loading')) { return; } // Check if nonce is available var nonce = wpforo.nonces && wpforo.nonces.wpforo_ai_suggest_reply; if (!nonce) { alert(wpforo_phrase('AI Suggest Reply is not properly configured. Please refresh the page and try again.')); return; } // Find the form and get parent post ID if available (for threaded replies) var form = btn.closest('form'); var parentId = 0; if (form.length) { var parentInput = form.find('input[name="parentid"]'); if (parentInput.length) { parentId = parseInt(parentInput.val(), 10) || 0; } } // Show loading state (CSS handles icon visibility via .wpf-ai-suggest-loading class) btn.addClass('wpf-ai-suggest-loading'); btn.find('span').text(wpforo_phrase('Processing...')); // Make AJAX request $.ajax({ url: wpforo.ajax_url, type: 'POST', data: { action: 'wpforo_ai_suggest_reply', _wpnonce: nonce, topic_id: topicId, parent_id: parentId }, success: function (response) { // Reset button state (CSS handles icon visibility) btn.removeClass('wpf-ai-suggest-loading'); btn.find('span').text(wpforo_phrase('Suggest Reply')); if (response.success) { var content = response.data.content || ''; if (!content) { alert(wpforo_phrase('AI generated an empty reply')); return; } // Append content to TinyMCE editor var inserted = wpforoInsertIntoEditor(content); if (!inserted) { // Fallback: try to append to textarea var textarea = form.find('textarea[name="postbody"]'); if (textarea.length) { var existingVal = textarea.val().trim(); if (existingVal) { textarea.val(existingVal + '\n\n' + content); } else { textarea.val(content); } } else { alert(wpforo_phrase('Could not insert content into editor')); } } // Show credits used info var credits = response.data.credits_used || 0; if (credits > 0) { console.log('AI Suggest Reply: ' + credits + ' credits used'); } } else { var errorMsg = response.data && response.data.message ? response.data.message : wpforo_phrase('Failed to generate reply suggestion'); alert(errorMsg); } }, error: function (xhr, status, error) { // Reset button state (CSS handles icon visibility) btn.removeClass('wpf-ai-suggest-loading'); btn.find('span').text(wpforo_phrase('Suggest Reply')); console.error('AI Suggest Reply error:', error); // Try to get error message from response (handles 429 rate limit errors) var errorMsg = wpforo_phrase('Network error. Please try again.'); try { var response = JSON.parse(xhr.responseText); if (response.data && response.data.message) { errorMsg = response.data.message; } } catch (e) { // Keep default error message } alert(errorMsg); } }); }); /** * Append content to TinyMCE editor * @param {string} content HTML content to append * @returns {boolean} True if successful */ function wpforoInsertIntoEditor(content) { // Try to find the active TinyMCE editor if (typeof tinyMCE !== 'undefined' && tinyMCE.activeEditor) { var editor = tinyMCE.activeEditor; // Append content to existing content with line break var existingContent = editor.getContent().trim(); if (existingContent) { editor.setContent(existingContent + '

 

' + content); } else { editor.setContent(content); } // Focus the editor editor.focus(); return true; } // Try by ID (wpForo's default editor ID) if (typeof tinyMCE !== 'undefined') { var editorIds = ['postbody', 'wpf_editor_postbody']; for (var i = 0; i < editorIds.length; i++) { var ed = tinyMCE.get(editorIds[i]); if (ed) { var existingContent = ed.getContent().trim(); if (existingContent) { ed.setContent(existingContent + '

 

' + content); } else { ed.setContent(content); } ed.focus(); return true; } } } return false; } // ========================================================================= // AI BUTTON VISIBILITY ANIMATIONS // ========================================================================= // Trigger animations when AI buttons become visible on screen if ('IntersectionObserver' in window) { var aiButtonObserver = new IntersectionObserver(function(entries) { entries.forEach(function(entry) { if (entry.isIntersecting) { // Add visible class to trigger animation entry.target.classList.add('wpf-ai-visible'); // Stop observing after animation triggered aiButtonObserver.unobserve(entry.target); } }); }, { threshold: 0.5 // Trigger when 50% visible }); // Observe AI Helper Toggle buttons document.querySelectorAll('.wpf-ai-helper-toggle').forEach(function(el) { aiButtonObserver.observe(el); }); // Observe AI Summarize buttons document.querySelectorAll('.wpf-ai-summarize-btn').forEach(function(el) { aiButtonObserver.observe(el); }); } });