| 1 |
/* global wpforo, $wpf, wpforo_phrase, wpforo_load_hide */ |
| 2 |
/** |
| 3 |
* wpForo AI Features JavaScript |
| 4 |
* |
| 5 |
* AI-powered search and helper functionality |
| 6 |
* Requires: frontend.js to be loaded first for $wpf, wpforo_phrase, wpforo_load_hide |
| 7 |
*/ |
| 8 |
|
| 9 |
$wpf(document).ready(function ($) { |
| 10 |
var wpforo_wrap = $('#wpforo-wrap'); |
| 11 |
|
| 12 |
// ========================================================================= |
| 13 |
// AI HELPER TOGGLE & TABS |
| 14 |
// ========================================================================= |
| 15 |
|
| 16 |
// AI Helper Toggle |
| 17 |
wpforo_wrap.on('click', '.wpf-ai-helper-toggle', function () { |
| 18 |
var helper = $(this).closest('.wpforo-ai-helper'); |
| 19 |
helper.toggleClass('wpf-ai-open'); |
| 20 |
$('.wpf-ai-helper-content', helper).slideToggle(350); |
| 21 |
}); |
| 22 |
|
| 23 |
// AI Helper Close Button |
| 24 |
wpforo_wrap.on('click', '.wpf-ai-tab-close', function () { |
| 25 |
var helper = $(this).closest('.wpforo-ai-helper'); |
| 26 |
helper.removeClass('wpf-ai-open'); |
| 27 |
$('.wpf-ai-helper-content', helper).slideUp(350); |
| 28 |
}); |
| 29 |
|
| 30 |
// AI Helper Tab Switching |
| 31 |
wpforo_wrap.on('click', '.wpf-ai-tab', function () { |
| 32 |
var tab = $(this); |
| 33 |
var tabId = tab.data('tab'); |
| 34 |
var helper = tab.closest('.wpf-ai-helper-inner'); |
| 35 |
// Switch active tab |
| 36 |
$('.wpf-ai-tab', helper).removeClass('wpf-ai-tab-active'); |
| 37 |
tab.addClass('wpf-ai-tab-active'); |
| 38 |
// Switch active content |
| 39 |
$('.wpf-ai-tab-content', helper).removeClass('wpf-ai-tab-content-active'); |
| 40 |
$('.wpf-ai-tab-content[data-tab-content="' + tabId + '"]', helper).addClass('wpf-ai-tab-content-active'); |
| 41 |
}); |
| 42 |
|
| 43 |
// AI Search Mode Toggle (switch between AI Search and Classic Search forms) |
| 44 |
wpforo_wrap.on('click', '.wpf-ai-search-mode', function () { |
| 45 |
var mode = $(this); |
| 46 |
var modeId = mode.data('mode'); |
| 47 |
var tabContent = mode.closest('.wpf-ai-tab-content'); |
| 48 |
// Switch active mode |
| 49 |
$('.wpf-ai-search-mode', tabContent).removeClass('wpf-ai-search-mode-active'); |
| 50 |
mode.addClass('wpf-ai-search-mode-active'); |
| 51 |
// Switch active form |
| 52 |
$('.wpf-ai-search-form', tabContent).removeClass('wpf-ai-search-form-active'); |
| 53 |
$('.wpf-ai-search-form[data-search-form="' + modeId + '"]', tabContent).addClass('wpf-ai-search-form-active'); |
| 54 |
}); |
| 55 |
|
| 56 |
// ========================================================================= |
| 57 |
// AI PREFERENCES |
| 58 |
// ========================================================================= |
| 59 |
|
| 60 |
// Save AI Preferences |
| 61 |
wpforo_wrap.on('submit', '.wpf-ai-preferences-form', function (e) { |
| 62 |
e.preventDefault(); |
| 63 |
wpforo_load_hide(); |
| 64 |
|
| 65 |
var form = $(this); |
| 66 |
var saveBtn = form.find('.wpf-ai-pref-save'); |
| 67 |
var statusEl = form.find('.wpf-ai-pref-status'); |
| 68 |
|
| 69 |
// Get form values |
| 70 |
var language = form.find('#wpf-ai-pref-language').val(); |
| 71 |
var maxResults = parseInt(form.find('#wpf-ai-pref-max-results').val(), 10); |
| 72 |
|
| 73 |
// Client-side validation (max 10 results) |
| 74 |
if (maxResults < 1) maxResults = 1; |
| 75 |
if (maxResults > 10) maxResults = 10; |
| 76 |
form.find('#wpf-ai-pref-max-results').val(maxResults); |
| 77 |
|
| 78 |
// Disable button during save |
| 79 |
saveBtn.prop('disabled', true).text(wpforo_phrase('Saving...')); |
| 80 |
statusEl.removeClass('success error').text(''); |
| 81 |
|
| 82 |
// AJAX save |
| 83 |
$.ajax({ |
| 84 |
url: wpforo.ajax_url, |
| 85 |
type: 'POST', |
| 86 |
data: { |
| 87 |
action: 'wpforo_save_ai_preferences', |
| 88 |
nonce: form.find('#wpf_ai_pref_nonce').val(), |
| 89 |
language: language, |
| 90 |
max_results: maxResults |
| 91 |
}, |
| 92 |
success: function (response) { |
| 93 |
saveBtn.prop('disabled', false).text(wpforo_phrase('Save Preferences')); |
| 94 |
if (response.success) { |
| 95 |
statusEl.addClass('success').text(wpforo_phrase('Preferences saved!')); |
| 96 |
// Update the search preferences for current session |
| 97 |
if (typeof aiSearchLimit !== 'undefined') { |
| 98 |
aiSearchLimit = maxResults; |
| 99 |
} |
| 100 |
if (typeof aiSearchLanguage !== 'undefined') { |
| 101 |
aiSearchLanguage = language; |
| 102 |
} |
| 103 |
} else { |
| 104 |
statusEl.addClass('error').text(response.data || wpforo_phrase('Error saving preferences')); |
| 105 |
} |
| 106 |
// Clear status after 3 seconds |
| 107 |
setTimeout(function () { |
| 108 |
statusEl.removeClass('success error').text(''); |
| 109 |
}, 3000); |
| 110 |
}, |
| 111 |
error: function (xhr) { |
| 112 |
saveBtn.prop('disabled', false).text(wpforo_phrase('Save Preferences')); |
| 113 |
// Try to get error message from response |
| 114 |
var errorMsg = wpforo_phrase('Network error. Please try again.'); |
| 115 |
try { |
| 116 |
var response = JSON.parse(xhr.responseText); |
| 117 |
if (response.data && response.data.message) { |
| 118 |
errorMsg = response.data.message; |
| 119 |
} |
| 120 |
} catch (e) { |
| 121 |
// Keep default error message |
| 122 |
} |
| 123 |
statusEl.addClass('error').text(errorMsg); |
| 124 |
} |
| 125 |
}); |
| 126 |
}); |
| 127 |
|
| 128 |
// ========================================================================= |
| 129 |
// AI SEMANTIC SEARCH |
| 130 |
// ========================================================================= |
| 131 |
|
| 132 |
var aiSearchOffset = 0; |
| 133 |
var aiSearchQuery = ''; |
| 134 |
var aiSearchLimit = 5; |
| 135 |
var aiSearchLanguage = 'en_US'; |
| 136 |
|
| 137 |
// Load preferences from data attribute (set by PHP with settings hierarchy) |
| 138 |
var aiHelper = $('.wpforo-ai-helper'); |
| 139 |
if (aiHelper.length && aiHelper.data('ai-preferences')) { |
| 140 |
var prefs = aiHelper.data('ai-preferences'); |
| 141 |
if (prefs.max_results) { |
| 142 |
aiSearchLimit = parseInt(prefs.max_results, 10) || 5; |
| 143 |
} |
| 144 |
if (prefs.language) { |
| 145 |
aiSearchLanguage = prefs.language; |
| 146 |
} |
| 147 |
} |
| 148 |
|
| 149 |
// AI Loading animation messages |
| 150 |
var aiLoadingMessages = [ |
| 151 |
'Searching forum content...', |
| 152 |
'Analyzing with AI...', |
| 153 |
'Finding relevant discussions...', |
| 154 |
'Processing semantic matches...', |
| 155 |
'Generating AI summary...', |
| 156 |
'Ranking results by relevance...', |
| 157 |
'Almost ready...' |
| 158 |
]; |
| 159 |
var aiLoadingInterval = null; |
| 160 |
|
| 161 |
function wpforoShowAiLoading(container) { |
| 162 |
var loadingHtml = '<div class="wpf-ai-loading">' + |
| 163 |
'<div class="wpf-ai-loading-animation">' + |
| 164 |
'<div class="wpf-ai-loading-stars">' + |
| 165 |
'<svg class="wpf-ai-star wpf-ai-star-1" viewBox="0 0 24 24"><path d="M12 0L14.59 8.41L23 11L14.59 13.59L12 22L9.41 13.59L1 11L9.41 8.41L12 0Z"/></svg>' + |
| 166 |
'<svg class="wpf-ai-star wpf-ai-star-2" viewBox="0 0 24 24"><path d="M12 0L14.59 8.41L23 11L14.59 13.59L12 22L9.41 13.59L1 11L9.41 8.41L12 0Z"/></svg>' + |
| 167 |
'<svg class="wpf-ai-star wpf-ai-star-3" viewBox="0 0 24 24"><path d="M12 0L14.59 8.41L23 11L14.59 13.59L12 22L9.41 13.59L1 11L9.41 8.41L12 0Z"/></svg>' + |
| 168 |
'</div>' + |
| 169 |
'</div>' + |
| 170 |
'<div class="wpf-ai-loading-text">' + wpforo_phrase(aiLoadingMessages[0]) + '</div>' + |
| 171 |
'</div>'; |
| 172 |
container.html(loadingHtml); |
| 173 |
container.closest('.wpf-ai-results').show(); |
| 174 |
|
| 175 |
// Rotate through loading messages |
| 176 |
var msgIndex = 0; |
| 177 |
aiLoadingInterval = setInterval(function() { |
| 178 |
msgIndex = (msgIndex + 1) % aiLoadingMessages.length; |
| 179 |
container.find('.wpf-ai-loading-text').text(wpforo_phrase(aiLoadingMessages[msgIndex])); |
| 180 |
}, 3000); |
| 181 |
} |
| 182 |
|
| 183 |
function wpforoHideAiLoading() { |
| 184 |
if (aiLoadingInterval) { |
| 185 |
clearInterval(aiLoadingInterval); |
| 186 |
aiLoadingInterval = null; |
| 187 |
} |
| 188 |
} |
| 189 |
|
| 190 |
// AI Search Form Submit Handler |
| 191 |
wpforo_wrap.on('submit', '.wpf-ai-form', function (e) { |
| 192 |
e.preventDefault(); |
| 193 |
wpforo_load_hide(); |
| 194 |
var form = $(this); |
| 195 |
var input = form.find('.wpf-ai-input'); |
| 196 |
var query = input.val().trim(); |
| 197 |
var submitBtn = form.find('.wpf-ai-submit'); |
| 198 |
var resultsWrap = form.closest('.wpf-ai-search-form').find('.wpf-ai-results'); |
| 199 |
var resultsList = resultsWrap.find('.wpf-ai-results-list'); |
| 200 |
var moreBtn = resultsWrap.find('.wpf-ai-results-more'); |
| 201 |
|
| 202 |
if (!query) return; |
| 203 |
|
| 204 |
// Reset for new search |
| 205 |
aiSearchOffset = 0; |
| 206 |
aiSearchQuery = query; |
| 207 |
|
| 208 |
// Show loading state on button |
| 209 |
submitBtn.find('i').removeClass('fa-search').addClass('fa-spinner fa-spin'); |
| 210 |
submitBtn.prop('disabled', true); |
| 211 |
|
| 212 |
// Show AI loading animation in results area |
| 213 |
wpforoShowAiLoading(resultsList); |
| 214 |
moreBtn.hide(); |
| 215 |
|
| 216 |
// Perform search |
| 217 |
wpforoAiSearch(query, aiSearchLimit, aiSearchOffset, function (response) { |
| 218 |
// Stop loading animation |
| 219 |
wpforoHideAiLoading(); |
| 220 |
// Restore button |
| 221 |
submitBtn.find('i').removeClass('fa-spinner fa-spin').addClass('fa-search'); |
| 222 |
submitBtn.prop('disabled', false); |
| 223 |
|
| 224 |
if (response.success && response.data.results.length > 0) { |
| 225 |
// Render AI enhancement (Summary + Recommendations) first, then results |
| 226 |
// Pass results to enhancement renderer for resolving [[#N]] link markers |
| 227 |
var enhancementHtml = wpforoRenderAiEnhancement(response.data.ai_enhancement, response.data.results); |
| 228 |
var resultsHtml = wpforoRenderAiResults(response.data.results); |
| 229 |
resultsList.html(enhancementHtml + resultsHtml); |
| 230 |
resultsWrap.show(); |
| 231 |
|
| 232 |
// Hide recommendations and results until summary typewriter completes |
| 233 |
var recsSection = resultsList.find('.wpf-ai-recommendations-section'); |
| 234 |
var resultsSection = resultsList.find('.wpf-ai-result-wrapper'); |
| 235 |
recsSection.hide(); |
| 236 |
resultsSection.hide(); |
| 237 |
|
| 238 |
// Trigger typewriter effect for AI summary |
| 239 |
// When complete, reveal the other sections with animation |
| 240 |
var typewriterEl = resultsList.find('.wpf-ai-typewriter')[0]; |
| 241 |
if (typewriterEl) { |
| 242 |
var content = typewriterEl.getAttribute('data-typewriter-content'); |
| 243 |
if (content) { |
| 244 |
wpforoTypewriterEffect(typewriterEl, content, 10, function() { |
| 245 |
// Summary complete - now show recommendations and results |
| 246 |
recsSection.slideDown(300); |
| 247 |
setTimeout(function() { |
| 248 |
resultsSection.slideDown(400); |
| 249 |
}, 150); |
| 250 |
}); |
| 251 |
} else { |
| 252 |
// No summary content - show sections immediately |
| 253 |
recsSection.show(); |
| 254 |
resultsSection.show(); |
| 255 |
} |
| 256 |
} else { |
| 257 |
// No typewriter element - show sections immediately |
| 258 |
recsSection.show(); |
| 259 |
resultsSection.show(); |
| 260 |
} |
| 261 |
|
| 262 |
// Show/hide more button |
| 263 |
if (response.data.has_more) { |
| 264 |
moreBtn.show(); |
| 265 |
} else { |
| 266 |
moreBtn.hide(); |
| 267 |
} |
| 268 |
|
| 269 |
aiSearchOffset = aiSearchLimit; |
| 270 |
} else if (response.success && response.data.no_indexed_content) { |
| 271 |
resultsList.html('<div class="wpf-ai-no-results"><i class="fas fa-info-circle"></i><p>' + response.data.message + '</p></div>'); |
| 272 |
resultsWrap.show(); |
| 273 |
moreBtn.hide(); |
| 274 |
} else if (response.success && response.data.results.length === 0) { |
| 275 |
resultsList.html('<div class="wpf-ai-no-results"><i class="fas fa-search"></i><p>' + wpforo_phrase('No results found') + '</p></div>'); |
| 276 |
resultsWrap.show(); |
| 277 |
moreBtn.hide(); |
| 278 |
} else { |
| 279 |
resultsList.html('<div class="wpf-ai-error"><i class="fas fa-exclamation-circle"></i><p>' + (response.data ? response.data.message : wpforo_phrase('Search failed')) + '</p></div>'); |
| 280 |
resultsWrap.show(); |
| 281 |
moreBtn.hide(); |
| 282 |
} |
| 283 |
}); |
| 284 |
}); |
| 285 |
|
| 286 |
// More Results button |
| 287 |
wpforo_wrap.on('click', '.wpf-ai-more-btn', function () { |
| 288 |
var btn = $(this); |
| 289 |
var resultsWrap = btn.closest('.wpf-ai-results'); |
| 290 |
var resultsList = resultsWrap.find('.wpf-ai-results-list'); |
| 291 |
|
| 292 |
// Show loading |
| 293 |
btn.prop('disabled', true).text(wpforo_phrase('Loading...')); |
| 294 |
|
| 295 |
wpforoAiSearch(aiSearchQuery, aiSearchLimit, aiSearchOffset, function (response) { |
| 296 |
btn.prop('disabled', false).text(wpforo_phrase('More Results')); |
| 297 |
|
| 298 |
if (response.success && response.data.results.length > 0) { |
| 299 |
resultsList.append(wpforoRenderAiResults(response.data.results)); |
| 300 |
aiSearchOffset += aiSearchLimit; |
| 301 |
|
| 302 |
if (!response.data.has_more) { |
| 303 |
btn.parent().hide(); |
| 304 |
} |
| 305 |
} |
| 306 |
}); |
| 307 |
}); |
| 308 |
|
| 309 |
// AI Search AJAX function |
| 310 |
function wpforoAiSearch(query, limit, offset, callback) { |
| 311 |
$.ajax({ |
| 312 |
url: wpforo.ajax_url, |
| 313 |
type: 'POST', |
| 314 |
data: { |
| 315 |
action: 'wpforo_ai_public_search', |
| 316 |
query: query, |
| 317 |
limit: limit, |
| 318 |
offset: offset, |
| 319 |
language: aiSearchLanguage, |
| 320 |
_wpnonce: wpforo.nonces.wpforo_ai_public_search |
| 321 |
}, |
| 322 |
success: callback, |
| 323 |
error: function (xhr) { |
| 324 |
// Try to get error message from response (handles 429 rate limit errors) |
| 325 |
var errorMsg = wpforo_phrase('Request failed'); |
| 326 |
try { |
| 327 |
var response = JSON.parse(xhr.responseText); |
| 328 |
if (response.data && response.data.message) { |
| 329 |
errorMsg = response.data.message; |
| 330 |
} |
| 331 |
} catch (e) { |
| 332 |
// Keep default error message |
| 333 |
} |
| 334 |
callback({ success: false, data: { message: errorMsg } }); |
| 335 |
} |
| 336 |
}); |
| 337 |
} |
| 338 |
|
| 339 |
// ========================================================================= |
| 340 |
// TYPEWRITER EFFECT |
| 341 |
// ========================================================================= |
| 342 |
|
| 343 |
// Typewriter effect for AI summary text |
| 344 |
// onComplete callback fires when typing is finished |
| 345 |
function wpforoTypewriterEffect(element, html, speed, onComplete) { |
| 346 |
if (!element || !html) { |
| 347 |
if (onComplete) onComplete(); |
| 348 |
return; |
| 349 |
} |
| 350 |
speed = speed || 15; // milliseconds per character |
| 351 |
|
| 352 |
// Parse HTML to extract text nodes and tags |
| 353 |
var tempDiv = document.createElement('div'); |
| 354 |
tempDiv.innerHTML = html; |
| 355 |
|
| 356 |
element.innerHTML = ''; |
| 357 |
element.style.visibility = 'visible'; |
| 358 |
|
| 359 |
// Recursive function to type through DOM nodes |
| 360 |
function typeNode(node, callback) { |
| 361 |
if (node.nodeType === Node.TEXT_NODE) { |
| 362 |
// Text node - type character by character |
| 363 |
var text = node.textContent; |
| 364 |
var textNode = document.createTextNode(''); |
| 365 |
element.appendChild(textNode); |
| 366 |
var charIndex = 0; |
| 367 |
|
| 368 |
function typeChar() { |
| 369 |
if (charIndex < text.length) { |
| 370 |
textNode.textContent += text[charIndex]; |
| 371 |
charIndex++; |
| 372 |
setTimeout(typeChar, speed); |
| 373 |
} else { |
| 374 |
callback(); |
| 375 |
} |
| 376 |
} |
| 377 |
typeChar(); |
| 378 |
} else if (node.nodeType === Node.ELEMENT_NODE) { |
| 379 |
// Element node - clone and append, then process children |
| 380 |
var clone = node.cloneNode(false); |
| 381 |
element.appendChild(clone); |
| 382 |
|
| 383 |
var children = Array.from(node.childNodes); |
| 384 |
var childIndex = 0; |
| 385 |
|
| 386 |
function processNextChild() { |
| 387 |
if (childIndex < children.length) { |
| 388 |
// Temporarily change element to append to clone |
| 389 |
var originalElement = element; |
| 390 |
element = clone; |
| 391 |
typeNode(children[childIndex], function() { |
| 392 |
element = originalElement; |
| 393 |
childIndex++; |
| 394 |
processNextChild(); |
| 395 |
}); |
| 396 |
} else { |
| 397 |
callback(); |
| 398 |
} |
| 399 |
} |
| 400 |
processNextChild(); |
| 401 |
} else { |
| 402 |
callback(); |
| 403 |
} |
| 404 |
} |
| 405 |
|
| 406 |
// Process all top-level nodes |
| 407 |
var topNodes = Array.from(tempDiv.childNodes); |
| 408 |
var nodeIndex = 0; |
| 409 |
|
| 410 |
function processNextTopNode() { |
| 411 |
if (nodeIndex < topNodes.length) { |
| 412 |
typeNode(topNodes[nodeIndex], function() { |
| 413 |
nodeIndex++; |
| 414 |
processNextTopNode(); |
| 415 |
}); |
| 416 |
} else { |
| 417 |
// All nodes processed - call onComplete callback |
| 418 |
if (onComplete) onComplete(); |
| 419 |
} |
| 420 |
} |
| 421 |
processNextTopNode(); |
| 422 |
} |
| 423 |
|
| 424 |
// ========================================================================= |
| 425 |
// RENDER FUNCTIONS |
| 426 |
// ========================================================================= |
| 427 |
|
| 428 |
// Render AI Enhancement sections (Summary and Recommendations) |
| 429 |
// All HTML is pre-rendered by PHP - JavaScript only inserts it |
| 430 |
function wpforoRenderAiEnhancement(enhancement, results) { |
| 431 |
if (!enhancement) return ''; |
| 432 |
|
| 433 |
var html = ''; |
| 434 |
|
| 435 |
// AI Search Summary Section |
| 436 |
// PHP already converts [[#N]] and [[#N:Title]] to HTML links |
| 437 |
if (enhancement.summary || enhancement.quick_answer) { |
| 438 |
html += '<div class="wpf-ai-summary-section notranslate" translate="no">'; |
| 439 |
html += '<div class="wpf-ai-section-header"><i class="fas fa-brain"></i> ' + wpforo_phrase('AI Search Summary') + '</div>'; |
| 440 |
if (enhancement.quick_answer) { |
| 441 |
// Output directly - PHP has already processed link markers |
| 442 |
html += '<div class="wpf-ai-quick-answer notranslate" translate="no">' + enhancement.quick_answer + '</div>'; |
| 443 |
} |
| 444 |
if (enhancement.summary) { |
| 445 |
// Store summary in data attribute for typewriter effect |
| 446 |
var encodedSummary = enhancement.summary.replace(/"/g, '"'); |
| 447 |
html += '<div class="wpf-ai-summary-text wpf-ai-typewriter notranslate" translate="no" data-typewriter-content="' + encodedSummary + '" style="visibility:hidden;min-height:50px;"></div>'; |
| 448 |
} |
| 449 |
html += '</div>'; |
| 450 |
} |
| 451 |
|
| 452 |
// AI Recommendations Section - use pre-rendered HTML from PHP |
| 453 |
if (enhancement.recommendations_html) { |
| 454 |
html += enhancement.recommendations_html; |
| 455 |
} |
| 456 |
|
| 457 |
return html; |
| 458 |
} |
| 459 |
|
| 460 |
// Render AI search results HTML |
| 461 |
function wpforoRenderAiResults(results) { |
| 462 |
var html = ''; |
| 463 |
// Add "AI Search Results" header before real results |
| 464 |
html += '<div class="wpf-ai-result-wrapper">'; |
| 465 |
html += '<div class="wpf-ai-section-header"><i class="fas fa-search"></i> ' + wpforo_phrase('AI Search Results') + '</div>'; |
| 466 |
for (var i = 0; i < results.length; i++) { |
| 467 |
var r = results[i]; |
| 468 |
html += '<div class="wpf-ai-result-card">'; |
| 469 |
var postIdBadge = r.post_id ? ' <span class="wpf-ai-result-postid">[ <i class="fa-regular fa-message"></i> ' + r.post_id + ' ]</span>' : ''; |
| 470 |
// Render title as link only if URL exists, otherwise plain text |
| 471 |
if (r.url) { |
| 472 |
html += '<div class="wpf-ai-result-title"><a href="' + r.url + '" target="_blank" rel="noopener">' + wpforoEscapeHtml(r.title) + postIdBadge + '</a></div>'; |
| 473 |
} else { |
| 474 |
html += '<div class="wpf-ai-result-title"><span class="wpf-ai-result-title-text">' + wpforoEscapeHtml(r.title) + '</span></div>'; |
| 475 |
} |
| 476 |
html += '<div class="wpf-ai-result-meta">'; |
| 477 |
if (r.content_source === 'custom_knowledge') { |
| 478 |
var kbLabel = r.post_type_label || 'Knowledge Base'; |
| 479 |
html += '<span class="wpf-ai-result-post-type wpf-ai-knowledge-badge"><i class="fas fa-book"></i> ' + wpforoEscapeHtml(kbLabel) + '</span>'; |
| 480 |
} else if (r.content_source === 'wordpress') { |
| 481 |
var typeLabel = r.post_type_label || 'Post'; |
| 482 |
html += '<span class="wpf-ai-result-post-type"><i class="fas fa-file-alt"></i> ' + wpforoEscapeHtml(typeLabel) + '</span>'; |
| 483 |
} else if (r.forum_title) { |
| 484 |
html += '<span class="wpf-ai-result-forum"><i class="fas fa-folder-open"></i> ' + wpforoEscapeHtml(r.forum_title) + '</span>'; |
| 485 |
} |
| 486 |
// Only show author if exists (custom_knowledge has no author) |
| 487 |
if (r.author_name) { |
| 488 |
html += '<span class="wpf-ai-result-author"><i class="fas fa-user"></i> ' + wpforoEscapeHtml(r.author_name) + '</span>'; |
| 489 |
} |
| 490 |
// Only show date if exists (custom_knowledge has no date) |
| 491 |
if (r.created_ago) { |
| 492 |
html += '<span class="wpf-ai-result-date"><i class="far fa-clock"></i> ' + r.created_ago + '</span>'; |
| 493 |
} |
| 494 |
html += '<span class="wpf-ai-result-score"><i class="fas fa-bullseye"></i> ' + r.score + '%</span>'; |
| 495 |
html += '</div>'; |
| 496 |
if (r.content) { |
| 497 |
var formattedContent = wpforoFormatAiContent(r.content); |
| 498 |
var lineCount = (r.content.match(/[\r\n]+/g) || []).length + 1; |
| 499 |
var isLong = r.content.length > 500 || lineCount > 5; |
| 500 |
|
| 501 |
html += '<div class="wpf-ai-result-content-wrap' + (isLong ? ' wpf-ai-collapsed' : '') + '">'; |
| 502 |
html += '<div class="wpf-ai-result-content">' + formattedContent + '</div>'; |
| 503 |
if (isLong) { |
| 504 |
html += '<div class="wpf-ai-content-toggle"><span class="wpf-ai-toggle-btn" data-expanded="false"><i class="fas fa-chevron-down"></i> <span class="wpf-ai-toggle-text">' + wpforo_phrase('Show more') + '</span></span></div>'; |
| 505 |
} |
| 506 |
html += '</div>'; |
| 507 |
} |
| 508 |
html += '</div>'; |
| 509 |
} |
| 510 |
html += '</div>'; |
| 511 |
return html; |
| 512 |
} |
| 513 |
|
| 514 |
// Format AI content: escape HTML and convert line breaks to <br> |
| 515 |
function wpforoFormatAiContent(text) { |
| 516 |
if (!text) return ''; |
| 517 |
// First escape HTML |
| 518 |
var escaped = wpforoEscapeHtml(text); |
| 519 |
// Convert \r\n, \r, \n to <br> for proper line breaks |
| 520 |
escaped = escaped.replace(/\r\n/g, '<br>'); |
| 521 |
escaped = escaped.replace(/\r/g, '<br>'); |
| 522 |
escaped = escaped.replace(/\n/g, '<br>'); |
| 523 |
// Convert multiple <br> to paragraph breaks |
| 524 |
escaped = escaped.replace(/(<br>){3,}/g, '<br><br>'); |
| 525 |
return escaped; |
| 526 |
} |
| 527 |
|
| 528 |
// Toggle AI search result content expand/collapse |
| 529 |
wpforo_wrap.on('click', '.wpf-ai-toggle-btn', function() { |
| 530 |
var $btn = $(this); |
| 531 |
var $wrap = $btn.closest('.wpf-ai-result-content-wrap'); |
| 532 |
var isExpanded = $btn.data('expanded'); |
| 533 |
|
| 534 |
if (isExpanded) { |
| 535 |
$wrap.addClass('wpf-ai-collapsed'); |
| 536 |
$btn.data('expanded', false); |
| 537 |
$btn.find('.wpf-ai-toggle-text').text(wpforo_phrase('Show more')); |
| 538 |
$btn.find('i').removeClass('fa-chevron-up').addClass('fa-chevron-down'); |
| 539 |
} else { |
| 540 |
$wrap.removeClass('wpf-ai-collapsed'); |
| 541 |
$btn.data('expanded', true); |
| 542 |
$btn.find('.wpf-ai-toggle-text').text(wpforo_phrase('Show less')); |
| 543 |
$btn.find('i').removeClass('fa-chevron-down').addClass('fa-chevron-up'); |
| 544 |
} |
| 545 |
}); |
| 546 |
|
| 547 |
// ========================================================================= |
| 548 |
// HELPER FUNCTIONS |
| 549 |
// ========================================================================= |
| 550 |
|
| 551 |
// Escape HTML helper |
| 552 |
function wpforoEscapeHtml(text) { |
| 553 |
if (!text) return ''; |
| 554 |
var div = document.createElement('div'); |
| 555 |
div.textContent = text; |
| 556 |
return div.innerHTML; |
| 557 |
} |
| 558 |
|
| 559 |
// ========================================================================= |
| 560 |
// AI TRANSLATION |
| 561 |
// ========================================================================= |
| 562 |
|
| 563 |
// Toggle translation dropdown |
| 564 |
wpforo_wrap.on('click', '.wpf-ai-translate-btn', function (e) { |
| 565 |
e.stopPropagation(); |
| 566 |
var wrapper = $(this).closest('.wpf-ai-translate-wrapper'); |
| 567 |
var dropdown = wrapper.find('.wpf-ai-translate-dropdown'); |
| 568 |
|
| 569 |
// Close other dropdowns |
| 570 |
$('.wpf-ai-translate-dropdown').not(dropdown).removeClass('wpf-ai-translate-dropdown-open'); |
| 571 |
|
| 572 |
// Toggle this dropdown |
| 573 |
dropdown.toggleClass('wpf-ai-translate-dropdown-open'); |
| 574 |
}); |
| 575 |
|
| 576 |
// Close dropdown when clicking outside |
| 577 |
$(document).on('click', function () { |
| 578 |
$('.wpf-ai-translate-dropdown').removeClass('wpf-ai-translate-dropdown-open'); |
| 579 |
}); |
| 580 |
|
| 581 |
// Prevent dropdown from closing when clicking inside it |
| 582 |
wpforo_wrap.on('click', '.wpf-ai-translate-dropdown', function (e) { |
| 583 |
e.stopPropagation(); |
| 584 |
}); |
| 585 |
|
| 586 |
// Handle language selection for translation |
| 587 |
wpforo_wrap.on('click', '.wpf-ai-translate-option', function () { |
| 588 |
var option = $(this); |
| 589 |
var wrapper = option.closest('.wpf-ai-translate-wrapper'); |
| 590 |
var postId = wrapper.data('postid'); |
| 591 |
var language = option.data('lang'); |
| 592 |
var dropdown = wrapper.find('.wpf-ai-translate-dropdown'); |
| 593 |
var translateBtn = wrapper.find('.wpf-ai-translate-btn'); |
| 594 |
var originalBtn = wrapper.find('.wpf-ai-translate-original'); |
| 595 |
var loadingEl = wrapper.find('.wpf-ai-translate-loading'); |
| 596 |
|
| 597 |
// Close dropdown |
| 598 |
dropdown.removeClass('wpf-ai-translate-dropdown-open'); |
| 599 |
|
| 600 |
// Find the post/comment content element |
| 601 |
// Structure for Post: .post-wrap > .wpforo-post > .wpforo-post-content |
| 602 |
// Structure for Q&A Layout Comment: .comment-wrap > .wpforo-comment-content > .wpforo-comment-text |
| 603 |
var postElement = wrapper.closest('.wpforo-post').find('.wpforo-post-content'); |
| 604 |
if (!postElement.length) { |
| 605 |
postElement = wrapper.closest('.post-wrap').find('.wpforo-post-content'); |
| 606 |
} |
| 607 |
if (!postElement.length) { |
| 608 |
postElement = wrapper.closest('.wpforo-comment').find('.wpforo-comment-text'); |
| 609 |
} |
| 610 |
if (!postElement.length) { |
| 611 |
postElement = wrapper.closest('.comment-wrap').find('.wpforo-comment-text'); |
| 612 |
} |
| 613 |
if (!postElement.length) { |
| 614 |
console.error('wpForo AI: Could not find post content element'); |
| 615 |
return; |
| 616 |
} |
| 617 |
|
| 618 |
// Store original content if not already stored |
| 619 |
if (!postElement.data('original-content')) { |
| 620 |
postElement.data('original-content', postElement.html()); |
| 621 |
} |
| 622 |
|
| 623 |
// Show loading state |
| 624 |
translateBtn.hide(); |
| 625 |
loadingEl.show(); |
| 626 |
|
| 627 |
// Make AJAX request |
| 628 |
$.ajax({ |
| 629 |
url: wpforo.ajax_url, |
| 630 |
type: 'POST', |
| 631 |
data: { |
| 632 |
action: 'wpforo_ai_translate', |
| 633 |
post_id: postId, |
| 634 |
language: language, |
| 635 |
nonce: wpforo.nonces.wpforo_ai_translate |
| 636 |
}, |
| 637 |
success: function (response) { |
| 638 |
loadingEl.hide(); |
| 639 |
|
| 640 |
if (response.success && response.data.translated_content) { |
| 641 |
// Replace content with translated version |
| 642 |
postElement.html(response.data.translated_content); |
| 643 |
postElement.addClass('wpf-ai-translated'); |
| 644 |
|
| 645 |
// Add RTL class for right-to-left languages (Arabic, Hebrew) |
| 646 |
var rtlLanguages = ['Arabic', 'Hebrew', 'ar', 'he']; |
| 647 |
if (rtlLanguages.indexOf(language) !== -1) { |
| 648 |
postElement.addClass('wpf-ai-translated-rtl'); |
| 649 |
} |
| 650 |
|
| 651 |
// Show "Show Original" button |
| 652 |
originalBtn.show(); |
| 653 |
} else { |
| 654 |
// Show error |
| 655 |
translateBtn.show(); |
| 656 |
var errorMsg = response.data && response.data.message ? response.data.message : wpforo_phrase('Translation failed'); |
| 657 |
alert(errorMsg); |
| 658 |
} |
| 659 |
}, |
| 660 |
error: function (xhr) { |
| 661 |
loadingEl.hide(); |
| 662 |
translateBtn.show(); |
| 663 |
// Try to get error message from response (handles 429 rate limit errors) |
| 664 |
var errorMsg = wpforo_phrase('Network error. Please try again.'); |
| 665 |
try { |
| 666 |
var response = JSON.parse(xhr.responseText); |
| 667 |
if (response.data && response.data.message) { |
| 668 |
errorMsg = response.data.message; |
| 669 |
} |
| 670 |
} catch (e) { |
| 671 |
// Keep default error message |
| 672 |
} |
| 673 |
alert(errorMsg); |
| 674 |
} |
| 675 |
}); |
| 676 |
}); |
| 677 |
|
| 678 |
// Handle "Show Original" button click |
| 679 |
wpforo_wrap.on('click', '.wpf-ai-translate-original', function () { |
| 680 |
var wrapper = $(this).closest('.wpf-ai-translate-wrapper'); |
| 681 |
var originalBtn = wrapper.find('.wpf-ai-translate-original'); |
| 682 |
var translateBtn = wrapper.find('.wpf-ai-translate-btn'); |
| 683 |
|
| 684 |
// Find the post/comment content element |
| 685 |
var postElement = wrapper.closest('.wpforo-post').find('.wpforo-post-content'); |
| 686 |
if (!postElement.length) { |
| 687 |
postElement = wrapper.closest('.post-wrap').find('.wpforo-post-content'); |
| 688 |
} |
| 689 |
if (!postElement.length) { |
| 690 |
postElement = wrapper.closest('.wpforo-comment').find('.wpforo-comment-text'); |
| 691 |
} |
| 692 |
if (!postElement.length) { |
| 693 |
postElement = wrapper.closest('.comment-wrap').find('.wpforo-comment-text'); |
| 694 |
} |
| 695 |
|
| 696 |
// Restore original content |
| 697 |
var originalContent = postElement.data('original-content'); |
| 698 |
if (originalContent) { |
| 699 |
postElement.html(originalContent); |
| 700 |
postElement.removeClass('wpf-ai-translated wpf-ai-translated-rtl'); |
| 701 |
} |
| 702 |
|
| 703 |
// Show translate button, hide original button |
| 704 |
originalBtn.hide(); |
| 705 |
translateBtn.show(); |
| 706 |
}); |
| 707 |
|
| 708 |
// ========================================================================= |
| 709 |
// AI TOPIC SUMMARIZATION |
| 710 |
// ========================================================================= |
| 711 |
|
| 712 |
var summaryLoadingInterval = null; |
| 713 |
var summaryLoadingMessages = [ |
| 714 |
'Reading topic posts...', |
| 715 |
'Analyzing discussion...', |
| 716 |
'Generating summary...', |
| 717 |
'Almost ready...' |
| 718 |
]; |
| 719 |
|
| 720 |
// Show loading animation for topic summary |
| 721 |
function wpforoShowSummaryLoading(container) { |
| 722 |
var loadingHtml = '<div class="wpf-ai-loading">' + |
| 723 |
'<div class="wpf-ai-loading-animation">' + |
| 724 |
'<div class="wpf-ai-loading-stars">' + |
| 725 |
'<svg class="wpf-ai-star wpf-ai-star-1" viewBox="0 0 24 24"><path d="M12 0L14.59 8.41L23 11L14.59 13.59L12 22L9.41 13.59L1 11L9.41 8.41L12 0Z"/></svg>' + |
| 726 |
'<svg class="wpf-ai-star wpf-ai-star-2" viewBox="0 0 24 24"><path d="M12 0L14.59 8.41L23 11L14.59 13.59L12 22L9.41 13.59L1 11L9.41 8.41L12 0Z"/></svg>' + |
| 727 |
'<svg class="wpf-ai-star wpf-ai-star-3" viewBox="0 0 24 24"><path d="M12 0L14.59 8.41L23 11L14.59 13.59L12 22L9.41 13.59L1 11L9.41 8.41L12 0Z"/></svg>' + |
| 728 |
'</div>' + |
| 729 |
'</div>' + |
| 730 |
'<div class="wpf-ai-loading-text">' + wpforo_phrase(summaryLoadingMessages[0]) + '</div>' + |
| 731 |
'</div>'; |
| 732 |
container.html(loadingHtml); |
| 733 |
|
| 734 |
// Rotate through loading messages |
| 735 |
var msgIndex = 0; |
| 736 |
summaryLoadingInterval = setInterval(function() { |
| 737 |
msgIndex = (msgIndex + 1) % summaryLoadingMessages.length; |
| 738 |
container.find('.wpf-ai-loading-text').text(wpforo_phrase(summaryLoadingMessages[msgIndex])); |
| 739 |
}, 2500); |
| 740 |
} |
| 741 |
|
| 742 |
function wpforoHideSummaryLoading() { |
| 743 |
if (summaryLoadingInterval) { |
| 744 |
clearInterval(summaryLoadingInterval); |
| 745 |
summaryLoadingInterval = null; |
| 746 |
} |
| 747 |
} |
| 748 |
|
| 749 |
// Topic Summary Button Click Handler |
| 750 |
wpforo_wrap.on('click', '.wpf-ai-summarize-btn', function (e) { |
| 751 |
e.preventDefault(); |
| 752 |
wpforo_load_hide(); |
| 753 |
|
| 754 |
var btn = $(this); |
| 755 |
var topicId = btn.data('topicid'); |
| 756 |
var nonce = btn.data('nonce'); |
| 757 |
var container = $('#wpf-ai-summary-' + topicId); |
| 758 |
var contentArea = container.find('.wpf-ai-summary-content'); |
| 759 |
|
| 760 |
// If container is already visible and has content, just toggle it |
| 761 |
if (container.is(':visible') && contentArea.find('.wpf-ai-summary-result').length > 0) { |
| 762 |
container.slideUp(350); |
| 763 |
return; |
| 764 |
} |
| 765 |
|
| 766 |
// Show container with loading |
| 767 |
container.slideDown(350); |
| 768 |
wpforoShowSummaryLoading(contentArea); |
| 769 |
|
| 770 |
// Disable button during request |
| 771 |
btn.addClass('wpf-ai-loading-btn'); |
| 772 |
|
| 773 |
// Make AJAX request |
| 774 |
$.ajax({ |
| 775 |
url: wpforo.ajax_url, |
| 776 |
type: 'POST', |
| 777 |
data: { |
| 778 |
action: 'wpforo_ai_summarize_topic', |
| 779 |
topicid: topicId, |
| 780 |
nonce: nonce |
| 781 |
}, |
| 782 |
success: function (response) { |
| 783 |
wpforoHideSummaryLoading(); |
| 784 |
btn.removeClass('wpf-ai-loading-btn'); |
| 785 |
|
| 786 |
if (response.success && response.data.summary) { |
| 787 |
// Build posts info (show notice when posts were limited) |
| 788 |
var postsInfo = ''; |
| 789 |
if (response.data.posts_limited && response.data.total_posts_count) { |
| 790 |
postsInfo = '<span class="wpf-ai-summary-meta wpf-ai-posts-limited">' + |
| 791 |
response.data.reply_count + ' ' + wpforo_phrase('of') + ' ' + response.data.total_posts_count + ' ' + wpforo_phrase('posts') + |
| 792 |
'</span>'; |
| 793 |
} |
| 794 |
|
| 795 |
// Render summary with close button in header (like AI Assistant) |
| 796 |
var summaryHtml = '<div class="wpf-ai-summary-result">' + |
| 797 |
'<div class="wpf-ai-summary-header">' + |
| 798 |
'<span class="wpf-ai-summary-title"><i class="fa-solid fa-wand-magic-sparkles"></i> ' + wpforo_phrase('AI Summary') + '</span>' + |
| 799 |
'<span class="wpf-ai-summary-meta">' + wpforo_phrase('Style') + ': ' + wpforoEscapeHtml(response.data.style || 'detailed') + '</span>' + |
| 800 |
postsInfo + |
| 801 |
'<div class="wpf-ai-summary-close-btn">' + |
| 802 |
'<i class="fas fa-times"></i>' + |
| 803 |
'<span>' + wpforo_phrase('Close') + '</span>' + |
| 804 |
'</div>' + |
| 805 |
'</div>' + |
| 806 |
'<div class="wpf-ai-summary-body">' + response.data.summary + '</div>' + |
| 807 |
'</div>'; |
| 808 |
contentArea.html(summaryHtml); |
| 809 |
} else { |
| 810 |
// Show error with close button |
| 811 |
var errorMsg = response.data && response.data.message ? response.data.message : wpforo_phrase('Failed to generate summary'); |
| 812 |
contentArea.html('<div class="wpf-ai-summary-error">' + |
| 813 |
'<i class="fas fa-exclamation-circle"></i> ' + wpforoEscapeHtml(errorMsg) + |
| 814 |
'<div class="wpf-ai-summary-close-btn" style="margin-left: 10px;">' + |
| 815 |
'<i class="fas fa-times"></i>' + |
| 816 |
'<span>' + wpforo_phrase('Close') + '</span>' + |
| 817 |
'</div>' + |
| 818 |
'</div>'); |
| 819 |
} |
| 820 |
}, |
| 821 |
error: function (xhr) { |
| 822 |
wpforoHideSummaryLoading(); |
| 823 |
btn.removeClass('wpf-ai-loading-btn'); |
| 824 |
// Try to get error message from response (handles 429 rate limit errors) |
| 825 |
var errorMsg = wpforo_phrase('Network error. Please try again.'); |
| 826 |
try { |
| 827 |
var response = JSON.parse(xhr.responseText); |
| 828 |
if (response.data && response.data.message) { |
| 829 |
errorMsg = response.data.message; |
| 830 |
} |
| 831 |
} catch (e) { |
| 832 |
// Keep default error message |
| 833 |
} |
| 834 |
contentArea.html('<div class="wpf-ai-summary-error">' + |
| 835 |
'<i class="fas fa-exclamation-circle"></i> ' + wpforoEscapeHtml(errorMsg) + |
| 836 |
'<div class="wpf-ai-summary-close-btn" style="margin-left: 10px;">' + |
| 837 |
'<i class="fas fa-times"></i>' + |
| 838 |
'<span>' + wpforo_phrase('Close') + '</span>' + |
| 839 |
'</div>' + |
| 840 |
'</div>'); |
| 841 |
} |
| 842 |
}); |
| 843 |
}); |
| 844 |
|
| 845 |
// Topic Summary Close Button Handler (handles both footer close and header close button) |
| 846 |
wpforo_wrap.on('click', '.wpf-ai-summary-close, .wpf-ai-summary-close-btn', function () { |
| 847 |
var container = $(this).closest('.wpf-ai-summary-container'); |
| 848 |
container.slideUp(350); |
| 849 |
}); |
| 850 |
|
| 851 |
// ========================================================================= |
| 852 |
// AI TOPIC SUGGESTIONS (Smart Topic Suggestions) |
| 853 |
// ========================================================================= |
| 854 |
|
| 855 |
var suggestionCallCount = 0; |
| 856 |
var suggestionConfig = null; |
| 857 |
var suggestionLastQuery = ''; |
| 858 |
|
| 859 |
// Initialize suggestion config from data attributes |
| 860 |
function wpforoInitSuggestionConfig() { |
| 861 |
var panel = $('.wpf-ai-suggestions-panel'); |
| 862 |
if (panel.length && panel.data('suggestion-config')) { |
| 863 |
suggestionConfig = panel.data('suggestion-config'); |
| 864 |
} else { |
| 865 |
// Default config - disabled if panel not found |
| 866 |
suggestionConfig = { |
| 867 |
enabled: false, |
| 868 |
min_words: 3, |
| 869 |
max_calls: 2, |
| 870 |
show_related: true, |
| 871 |
show_answer: true, |
| 872 |
quality: 'balanced' |
| 873 |
}; |
| 874 |
} |
| 875 |
} |
| 876 |
|
| 877 |
// Count words in a string |
| 878 |
function wpforoCountWords(str) { |
| 879 |
if (!str) return 0; |
| 880 |
return str.trim().split(/\s+/).filter(function(w) { return w.length > 0; }).length; |
| 881 |
} |
| 882 |
|
| 883 |
// Topic title input handler - triggers on blur (when user leaves the title field) |
| 884 |
// Topic title field has name="thread[title]" and id="thread_title" in wpForo |
| 885 |
wpforo_wrap.on('blur', '#thread_title, input[name="thread[title]"]', function (e) { |
| 886 |
// Initialize config if not done |
| 887 |
if (!suggestionConfig) { |
| 888 |
wpforoInitSuggestionConfig(); |
| 889 |
} |
| 890 |
|
| 891 |
// Check if suggestions are enabled |
| 892 |
if (!suggestionConfig || !suggestionConfig.enabled) { |
| 893 |
return; |
| 894 |
} |
| 895 |
|
| 896 |
var input = $(this); |
| 897 |
var title = input.val().trim(); |
| 898 |
var wordCount = wpforoCountWords(title); |
| 899 |
|
| 900 |
// Check minimum words (from config) |
| 901 |
if (wordCount < suggestionConfig.min_words) { |
| 902 |
return; |
| 903 |
} |
| 904 |
|
| 905 |
// Skip if same query as last time |
| 906 |
if (title === suggestionLastQuery) { |
| 907 |
return; |
| 908 |
} |
| 909 |
|
| 910 |
// Check max API calls per topic creation session (from config) |
| 911 |
if (suggestionCallCount >= suggestionConfig.max_calls) { |
| 912 |
return; |
| 913 |
} |
| 914 |
|
| 915 |
// Fetch suggestions immediately on blur (no debounce needed) |
| 916 |
wpforoFetchSuggestions(title, input); |
| 917 |
}); |
| 918 |
|
| 919 |
// Fetch suggestions from API |
| 920 |
function wpforoFetchSuggestions(title, inputElement) { |
| 921 |
if (!suggestionConfig || !suggestionConfig.enabled) return; |
| 922 |
|
| 923 |
suggestionLastQuery = title; |
| 924 |
suggestionCallCount++; |
| 925 |
|
| 926 |
var form = inputElement.closest('form'); |
| 927 |
var panel = form.find('.wpf-ai-suggestions-panel'); |
| 928 |
var contentArea = panel.find('.wpf-ai-suggestions-content'); |
| 929 |
|
| 930 |
// Show panel with loading immediately on blur |
| 931 |
panel.slideDown(300); |
| 932 |
wpforoShowSuggestionLoading(contentArea); |
| 933 |
|
| 934 |
// Make AJAX request |
| 935 |
$.ajax({ |
| 936 |
url: wpforo.ajax_url, |
| 937 |
type: 'POST', |
| 938 |
data: { |
| 939 |
action: 'wpforo_ai_get_topic_suggestions', |
| 940 |
title: title, |
| 941 |
quality: suggestionConfig.quality || 'balanced', |
| 942 |
include_similar: 1, // Always include similar topics - required for the feature |
| 943 |
include_related: suggestionConfig.show_related ? 1 : 0, |
| 944 |
include_answer: suggestionConfig.show_answer ? 1 : 0, |
| 945 |
nonce: wpforo.nonces.wpforo_ai_get_topic_suggestions |
| 946 |
}, |
| 947 |
success: function (response) { |
| 948 |
wpforoHideSuggestionLoading(); |
| 949 |
|
| 950 |
if (response.success && response.data.has_suggestions) { |
| 951 |
wpforoRenderSuggestions(contentArea, response.data); |
| 952 |
} else if (response.success && !response.data.has_suggestions) { |
| 953 |
// No similar topics found - show message briefly then hide |
| 954 |
contentArea.html('<div class="wpf-ai-suggestions-no-results">' + |
| 955 |
'<i class="fas fa-info-circle"></i> ' + wpforo_phrase('No similar topics have been found.') + |
| 956 |
'</div>'); |
| 957 |
// Hide panel after 3 seconds |
| 958 |
setTimeout(function() { |
| 959 |
panel.slideUp(300); |
| 960 |
}, 3000); |
| 961 |
} else { |
| 962 |
// Error |
| 963 |
var errorMsg = response.data && response.data.message ? response.data.message : wpforo_phrase('Could not fetch suggestions'); |
| 964 |
contentArea.html('<div class="wpf-ai-suggestions-error">' + |
| 965 |
'<i class="fas fa-exclamation-circle"></i> ' + wpforoEscapeHtml(errorMsg) + |
| 966 |
'</div>'); |
| 967 |
} |
| 968 |
}, |
| 969 |
error: function (xhr) { |
| 970 |
wpforoHideSuggestionLoading(); |
| 971 |
// Try to get error message from response (handles 429 rate limit errors) |
| 972 |
var errorMsg = wpforo_phrase('Network error. Please try again.'); |
| 973 |
try { |
| 974 |
var response = JSON.parse(xhr.responseText); |
| 975 |
if (response.data && response.data.message) { |
| 976 |
errorMsg = response.data.message; |
| 977 |
} |
| 978 |
} catch (e) { |
| 979 |
// Keep default error message |
| 980 |
} |
| 981 |
contentArea.html('<div class="wpf-ai-suggestions-error">' + |
| 982 |
'<i class="fas fa-exclamation-circle"></i> ' + wpforoEscapeHtml(errorMsg) + |
| 983 |
'</div>'); |
| 984 |
} |
| 985 |
}); |
| 986 |
} |
| 987 |
|
| 988 |
// Loading animation for suggestions - compact single line |
| 989 |
var suggestionLoadingInterval = null; |
| 990 |
var suggestionLoadingMessages = [ |
| 991 |
'Searching similar topics...', |
| 992 |
'Analyzing your question...', |
| 993 |
'Finding relevant discussions...', |
| 994 |
'Almost ready...' |
| 995 |
]; |
| 996 |
|
| 997 |
function wpforoShowSuggestionLoading(container) { |
| 998 |
var loadingHtml = '<div class="wpf-ai-suggestion-loading-inline">' + |
| 999 |
'<svg class="wpf-ai-star-inline" viewBox="0 0 24 24"><path d="M12 0L14.59 8.41L23 11L14.59 13.59L12 22L9.41 13.59L1 11L9.41 8.41L12 0Z"/></svg>' + |
| 1000 |
'<span class="wpf-ai-loading-text-inline">' + wpforo_phrase(suggestionLoadingMessages[0]) + '</span>' + |
| 1001 |
'</div>'; |
| 1002 |
container.html(loadingHtml); |
| 1003 |
|
| 1004 |
var msgIndex = 0; |
| 1005 |
suggestionLoadingInterval = setInterval(function() { |
| 1006 |
msgIndex = (msgIndex + 1) % suggestionLoadingMessages.length; |
| 1007 |
container.find('.wpf-ai-loading-text-inline').text(wpforo_phrase(suggestionLoadingMessages[msgIndex])); |
| 1008 |
}, 2000); |
| 1009 |
} |
| 1010 |
|
| 1011 |
function wpforoHideSuggestionLoading() { |
| 1012 |
if (suggestionLoadingInterval) { |
| 1013 |
clearInterval(suggestionLoadingInterval); |
| 1014 |
suggestionLoadingInterval = null; |
| 1015 |
} |
| 1016 |
} |
| 1017 |
|
| 1018 |
// Render suggestions UI |
| 1019 |
function wpforoRenderSuggestions(container, data) { |
| 1020 |
// No duplicate header - PHP already renders the main header |
| 1021 |
var html = ''; |
| 1022 |
|
| 1023 |
// Similar Topics Section |
| 1024 |
if (data.similar_topics && data.similar_topics.length > 0) { |
| 1025 |
html += '<div class="wpf-ai-suggestions-section wpf-ai-similar-topics">' + |
| 1026 |
'<div class="wpf-ai-suggestions-section-header">' + |
| 1027 |
'<i class="fas fa-copy"></i> ' + wpforo_phrase('Similar Topics Already Exist') + |
| 1028 |
'</div>' + |
| 1029 |
'<div class="wpf-ai-suggestions-section-content">' + |
| 1030 |
'<ul class="wpf-ai-similar-list">'; |
| 1031 |
|
| 1032 |
for (var i = 0; i < data.similar_topics.length; i++) { |
| 1033 |
var topic = data.similar_topics[i]; |
| 1034 |
html += '<li class="wpf-ai-similar-item">' + |
| 1035 |
'<i class="fas fa-angle-double-right wpf-ai-item-icon"></i>' + |
| 1036 |
'<span class="wpf-ai-similar-score">' + topic.score + '% ' + wpforo_phrase('match') + '</span>' + |
| 1037 |
'<a href="' + topic.url + '" target="_blank" rel="noopener" class="wpf-ai-similar-link">' + |
| 1038 |
'<span class="wpf-ai-similar-title">' + wpforoEscapeHtml(topic.title) + '</span>' + |
| 1039 |
'</a>' + |
| 1040 |
'</li>'; |
| 1041 |
} |
| 1042 |
|
| 1043 |
html += '</ul>' + |
| 1044 |
'<div class="wpf-ai-similar-hint">' + |
| 1045 |
'<i class="fas fa-info-circle"></i> ' + wpforo_phrase('Check these topics - your question might already be answered!') + |
| 1046 |
'</div>' + |
| 1047 |
'</div>' + |
| 1048 |
'</div>'; |
| 1049 |
} |
| 1050 |
|
| 1051 |
// Related Topics Section (AI suggestions) |
| 1052 |
if (data.related_topics && data.related_topics.length > 0) { |
| 1053 |
html += '<div class="wpf-ai-suggestions-section wpf-ai-related-topics">' + |
| 1054 |
'<div class="wpf-ai-suggestions-section-header">' + |
| 1055 |
'<svg class="wpf-ai-icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" style="width:22px;height:22px;vertical-align:middle;margin-right:6px;fill:currentColor;margin-bottom: 3px;"><path d="M9,13 L19,13 C19.5522847,13 20,13.4477153 20,14 C20,14.5522847 19.5522847,15 19,15 L9,15 C8.44771525,15 8,14.5522847 8,14 C8,13.4477153 8.44771525,13 9,13 Z M9,17 L19,17 C19.5522847,17 20,17.4477153 20,18 C20,18.5522847 19.5522847,19 19,19 L9,19 C8.44771525,19 8,18.5522847 8,18 C8,17.4477153 8.44771525,17 9,17 Z M15,9 L19,9 C19.5522847,9 20,9.44771525 20,10 C20,10.5522847 19.5522847,11 19,11 L15,11 C14.4477153,11 14,10.5522847 14,10 C14,9.44771525 14.4477153,9 15,9 Z M7.74264069,10.9142136 L4,7.17157288 L5.41421356,5.75735931 L7.74264069,8.08578644 L12.8284271,3 L14.2426407,4.41421356 L7.74264069,10.9142136 Z"/></svg>' + wpforo_phrase('Related Topics You Might Explore') + |
| 1056 |
'</div>' + |
| 1057 |
'<div class="wpf-ai-suggestions-section-content">' + |
| 1058 |
'<ul class="wpf-ai-related-list">'; |
| 1059 |
|
| 1060 |
for (var j = 0; j < data.related_topics.length; j++) { |
| 1061 |
var related = data.related_topics[j]; |
| 1062 |
// Use the URL from API if available, fallback to search |
| 1063 |
var topicUrl = related.url || (wpforo_url + '?foro=search&wpfkeyword=' + encodeURIComponent(related.title)); |
| 1064 |
html += '<li class="wpf-ai-related-item">' + |
| 1065 |
'<svg class="wpf-ai-icon wpf-ai-item-icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" style="width:14px;height:14px;vertical-align:middle;margin-right:5px;fill:currentColor;flex-shrink:0;"><polygon points="6 10 4 12 10 18 20 8 18 6 10 14"/></svg>' + |
| 1066 |
'<a href="' + topicUrl + '" target="_blank" rel="noopener" class="wpf-ai-related-link">' + |
| 1067 |
'<span class="wpf-ai-related-title">' + wpforoEscapeHtml(related.title) + '</span>' + |
| 1068 |
'</a>' + |
| 1069 |
(related.reason ? '<span class="wpf-ai-related-reason">' + wpforoEscapeHtml(related.reason) + '</span>' : '') + |
| 1070 |
'</li>'; |
| 1071 |
} |
| 1072 |
|
| 1073 |
html += '</ul>' + |
| 1074 |
'</div>' + |
| 1075 |
'</div>'; |
| 1076 |
} |
| 1077 |
|
| 1078 |
// Quick AI Answer Section |
| 1079 |
if (data.quick_answer && data.quick_answer.text) { |
| 1080 |
html += '<div class="wpf-ai-suggestions-section wpf-ai-quick-answer-section">' + |
| 1081 |
'<div class="wpf-ai-suggestions-section-header">' + |
| 1082 |
'<svg class="wpf-ai-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="width:22px;height:22px;vertical-align:middle;margin-right:6px;margin-bottom: 3px;"><path fill-rule="evenodd" clip-rule="evenodd" d="M7.33569 3.38268C7.93132 1.87244 10.0687 1.87244 10.6643 3.38268L11.7363 6.10082C11.7657 6.17532 11.8247 6.23429 11.8992 6.26367L14.6173 7.33569C16.1276 7.93132 16.1276 10.0687 14.6173 10.6643L11.8992 11.7363C11.8247 11.7657 11.7657 11.8247 11.7363 11.8992L10.6643 14.6173C10.0687 16.1276 7.93132 16.1276 7.33569 14.6173L6.26367 11.8992C6.23429 11.8247 6.17532 11.7657 6.10082 11.7363L3.38268 10.6643C1.87244 10.0687 1.87244 7.93132 3.38268 7.33569L6.10082 6.26367C6.17532 6.23429 6.23429 6.17532 6.26367 6.10082L7.33569 3.38268ZM9.26891 3.93301C9.17267 3.68899 8.82733 3.689 8.73109 3.93301L7.65907 6.65115C7.47722 7.11224 7.11224 7.47722 6.65116 7.65907L3.93301 8.73109C3.68899 8.82733 3.689 9.17267 3.93301 9.26891L6.65115 10.3409C7.11224 10.5228 7.47722 10.8878 7.65907 11.3488L8.73109 14.067C8.82733 14.311 9.17267 14.311 9.26891 14.067L10.3409 11.3488C10.5228 10.8878 10.8878 10.5228 11.3488 10.3409L14.067 9.26891C14.311 9.17267 14.311 8.82733 14.067 8.73109L11.3488 7.65907C10.8878 7.47722 10.5228 7.11224 10.3409 6.65116L9.26891 3.93301ZM15.7908 13.073C16.2235 11.9757 17.7765 11.9757 18.2092 13.073L18.9779 15.0221L20.927 15.7908C22.0243 16.2235 22.0243 17.7765 20.927 18.2092L18.9779 18.9779L18.2092 20.927C17.7765 22.0243 16.2235 22.0243 15.7908 20.927L15.0221 18.9779L13.073 18.2092C11.9757 17.7765 11.9757 16.2235 13.073 15.7908L15.0221 15.0221L15.7908 13.073ZM17 14.0953L16.3856 15.6533C16.2534 15.9883 15.9883 16.2534 15.6533 16.3856L14.0953 17L15.6533 17.6144C15.9883 17.7466 16.2534 18.0117 16.3856 18.3467L17 19.9047L17.6144 18.3467C17.7466 18.0117 18.0117 17.7466 18.3467 17.6144L19.9047 17L18.3467 16.3856C18.0117 16.2534 17.7466 15.9883 17.6144 15.6533L17 14.0953Z" fill="currentColor"/></svg>' + wpforo_phrase('Quick AI Answer') + |
| 1083 |
'</div>' + |
| 1084 |
'<div class="wpf-ai-suggestions-section-content">' + |
| 1085 |
'<div class="wpf-ai-quick-answer-text">' + wpforoFormatAiContent(data.quick_answer.text) + '</div>'; |
| 1086 |
|
| 1087 |
html += '<div class="wpf-ai-answer-hint">' + |
| 1088 |
wpforo_phrase('This is an AI-generated answer based on existing forum content. Post your topic for more accurate human responses.') + |
| 1089 |
'</div>' + |
| 1090 |
'</div>' + |
| 1091 |
'</div>'; |
| 1092 |
} |
| 1093 |
|
| 1094 |
container.html(html); |
| 1095 |
} |
| 1096 |
|
| 1097 |
// Close suggestions panel |
| 1098 |
wpforo_wrap.on('click', '.wpf-ai-suggestions-close', function () { |
| 1099 |
var panel = $(this).closest('.wpf-ai-suggestions-panel'); |
| 1100 |
panel.slideUp(300); |
| 1101 |
}); |
| 1102 |
|
| 1103 |
// Toggle show more/less for quick answer |
| 1104 |
wpforo_wrap.on('click', '.wpf-ai-show-more-answer', function () { |
| 1105 |
var btn = $(this); |
| 1106 |
var answerText = btn.closest('.wpf-ai-suggestions-section-content').find('.wpf-ai-quick-answer-text'); |
| 1107 |
var fullAnswer = btn.data('full'); |
| 1108 |
var isExpanded = btn.data('expanded'); |
| 1109 |
|
| 1110 |
if (isExpanded) { |
| 1111 |
// Collapse - would need original stored, for now just hide |
| 1112 |
btn.html('<i class="fas fa-chevron-down"></i> ' + wpforo_phrase('Show more')); |
| 1113 |
btn.data('expanded', false); |
| 1114 |
} else { |
| 1115 |
answerText.html(wpforoFormatAiContent(fullAnswer)); |
| 1116 |
btn.html('<i class="fas fa-chevron-up"></i> ' + wpforo_phrase('Show less')); |
| 1117 |
btn.data('expanded', true); |
| 1118 |
} |
| 1119 |
}); |
| 1120 |
|
| 1121 |
// Initialize suggestion config on page load |
| 1122 |
wpforoInitSuggestionConfig(); |
| 1123 |
|
| 1124 |
// Re-initialize suggestion config when topic form is loaded via AJAX |
| 1125 |
// wpForo triggers 'wpforo_topic_portable_form' event after AJAX form load |
| 1126 |
$(document).on('wpforo_topic_portable_form', function(event, formElement) { |
| 1127 |
// Reset suggestion state for new form |
| 1128 |
suggestionCallCount = 0; |
| 1129 |
suggestionLastQuery = ''; |
| 1130 |
suggestionConfig = null; |
| 1131 |
|
| 1132 |
// Re-initialize config from the new form's panel |
| 1133 |
if (formElement && formElement.length) { |
| 1134 |
var panel = formElement.find('.wpf-ai-suggestions-panel'); |
| 1135 |
if (panel.length && panel.data('suggestion-config')) { |
| 1136 |
suggestionConfig = panel.data('suggestion-config'); |
| 1137 |
} |
| 1138 |
} |
| 1139 |
|
| 1140 |
// Fallback to global search if not found in form element |
| 1141 |
if (!suggestionConfig) { |
| 1142 |
wpforoInitSuggestionConfig(); |
| 1143 |
} |
| 1144 |
}); |
| 1145 |
|
| 1146 |
// ========================================================================= |
| 1147 |
// AI BOT REPLY |
| 1148 |
// ========================================================================= |
| 1149 |
|
| 1150 |
/** |
| 1151 |
* AI Bot Reply button click handler |
| 1152 |
* Creates a bot-generated reply to the post |
| 1153 |
*/ |
| 1154 |
wpforo_wrap.on('click', '.wpf-ai-bot-reply', function (e) { |
| 1155 |
e.preventDefault(); |
| 1156 |
e.stopPropagation(); |
| 1157 |
|
| 1158 |
var btn = $(this); |
| 1159 |
var postId = btn.data('postid'); |
| 1160 |
var topicId = btn.data('topicid'); |
| 1161 |
|
| 1162 |
if (!postId || !topicId) { |
| 1163 |
console.error('AI Bot Reply: Missing post or topic ID'); |
| 1164 |
return; |
| 1165 |
} |
| 1166 |
|
| 1167 |
// Prevent double-clicks (check for wpf-processing spinning class) |
| 1168 |
if (btn.hasClass('wpf-processing')) { |
| 1169 |
return; |
| 1170 |
} |
| 1171 |
|
| 1172 |
// Check if nonce is available |
| 1173 |
var nonce = wpforo.nonces && wpforo.nonces.wpforo_ai_bot_reply; |
| 1174 |
if (!nonce) { |
| 1175 |
alert(wpforo_phrase('AI Bot Reply is not properly configured. Please refresh the page and try again.')); |
| 1176 |
return; |
| 1177 |
} |
| 1178 |
|
| 1179 |
// Show loading state - spinning icon (like wpforo-aibot plugin) |
| 1180 |
btn.addClass('wpf-processing'); |
| 1181 |
if (typeof wpforo_load_show === 'function') { |
| 1182 |
wpforo_load_show(); |
| 1183 |
} |
| 1184 |
|
| 1185 |
// Make AJAX request |
| 1186 |
$.ajax({ |
| 1187 |
url: wpforo.ajax_url, |
| 1188 |
type: 'POST', |
| 1189 |
data: { |
| 1190 |
action: 'wpforo_ai_bot_reply', |
| 1191 |
_wpnonce: nonce, |
| 1192 |
post_id: postId, |
| 1193 |
topic_id: topicId |
| 1194 |
} |
| 1195 |
}).done(function (response) { |
| 1196 |
if (response.success) { |
| 1197 |
// Reload page to show new reply (with anchor to new post) |
| 1198 |
var newPostId = response.data.post_id; |
| 1199 |
setTimeout(function() { |
| 1200 |
if (newPostId) { |
| 1201 |
window.location.href = window.location.pathname + window.location.search + '#post-' + newPostId; |
| 1202 |
window.location.reload(); |
| 1203 |
} else { |
| 1204 |
window.location.reload(); |
| 1205 |
} |
| 1206 |
}, 500); |
| 1207 |
} else { |
| 1208 |
btn.removeClass('wpf-processing'); |
| 1209 |
if (typeof wpforo_load_hide === 'function') { |
| 1210 |
wpforo_load_hide(); |
| 1211 |
} |
| 1212 |
|
| 1213 |
var errorMsg = response.data && response.data.message |
| 1214 |
? response.data.message |
| 1215 |
: wpforo_phrase('Failed to generate bot reply'); |
| 1216 |
|
| 1217 |
// Provide helpful message for common configuration issues |
| 1218 |
if (errorMsg.indexOf('Bot user not configured') !== -1) { |
| 1219 |
errorMsg = wpforo_phrase('Bot user not configured') + '.\n\n' + |
| 1220 |
wpforo_phrase('Please go to') + ' wpForo > Settings > AI Features > AI Bot Reply ' + |
| 1221 |
wpforo_phrase('and select a WordPress user for the bot.'); |
| 1222 |
} |
| 1223 |
|
| 1224 |
alert(errorMsg); |
| 1225 |
} |
| 1226 |
}).fail(function (xhr, status, error) { |
| 1227 |
btn.removeClass('wpf-processing'); |
| 1228 |
if (typeof wpforo_load_hide === 'function') { |
| 1229 |
wpforo_load_hide(); |
| 1230 |
} |
| 1231 |
|
| 1232 |
console.error('AI Bot Reply error:', status, error, xhr.responseText); |
| 1233 |
|
| 1234 |
// Try to parse error message from response |
| 1235 |
var errorMsg = wpforo_phrase('Network error. Please try again.'); |
| 1236 |
try { |
| 1237 |
var jsonResponse = JSON.parse(xhr.responseText); |
| 1238 |
if (jsonResponse.data && jsonResponse.data.message) { |
| 1239 |
errorMsg = jsonResponse.data.message; |
| 1240 |
// Add helpful message for bot user not configured |
| 1241 |
if (errorMsg.indexOf('Bot user not configured') !== -1) { |
| 1242 |
errorMsg = wpforo_phrase('Bot user not configured') + '.\n\n' + |
| 1243 |
wpforo_phrase('Please go to') + ' wpForo > Settings > AI Features > AI Bot Reply ' + |
| 1244 |
wpforo_phrase('and select a WordPress user for the bot.'); |
| 1245 |
} |
| 1246 |
} |
| 1247 |
} catch (e) { |
| 1248 |
// Keep default error message |
| 1249 |
} |
| 1250 |
alert(errorMsg); |
| 1251 |
}); |
| 1252 |
}); |
| 1253 |
|
| 1254 |
/** |
| 1255 |
* AI Suggest Reply button click handler |
| 1256 |
* Generates AI reply suggestion and inserts into TinyMCE editor |
| 1257 |
*/ |
| 1258 |
wpforo_wrap.on('click', '.wpf-ai-suggest-reply', function (e) { |
| 1259 |
e.preventDefault(); |
| 1260 |
|
| 1261 |
var btn = $(this); |
| 1262 |
var topicId = btn.data('topicid'); |
| 1263 |
|
| 1264 |
if (!topicId) { |
| 1265 |
console.error('AI Suggest Reply: Missing topic ID'); |
| 1266 |
return; |
| 1267 |
} |
| 1268 |
|
| 1269 |
// Prevent double-clicks |
| 1270 |
if (btn.hasClass('wpf-ai-loading')) { |
| 1271 |
return; |
| 1272 |
} |
| 1273 |
|
| 1274 |
// Check if nonce is available |
| 1275 |
var nonce = wpforo.nonces && wpforo.nonces.wpforo_ai_suggest_reply; |
| 1276 |
if (!nonce) { |
| 1277 |
alert(wpforo_phrase('AI Suggest Reply is not properly configured. Please refresh the page and try again.')); |
| 1278 |
return; |
| 1279 |
} |
| 1280 |
|
| 1281 |
// Find the form and get parent post ID if available (for threaded replies) |
| 1282 |
var form = btn.closest('form'); |
| 1283 |
var parentId = 0; |
| 1284 |
if (form.length) { |
| 1285 |
var parentInput = form.find('input[name="parentid"]'); |
| 1286 |
if (parentInput.length) { |
| 1287 |
parentId = parseInt(parentInput.val(), 10) || 0; |
| 1288 |
} |
| 1289 |
} |
| 1290 |
|
| 1291 |
// Show loading state (CSS handles icon visibility via .wpf-ai-suggest-loading class) |
| 1292 |
btn.addClass('wpf-ai-suggest-loading'); |
| 1293 |
btn.find('span').text(wpforo_phrase('Processing...')); |
| 1294 |
|
| 1295 |
// Make AJAX request |
| 1296 |
$.ajax({ |
| 1297 |
url: wpforo.ajax_url, |
| 1298 |
type: 'POST', |
| 1299 |
data: { |
| 1300 |
action: 'wpforo_ai_suggest_reply', |
| 1301 |
_wpnonce: nonce, |
| 1302 |
topic_id: topicId, |
| 1303 |
parent_id: parentId |
| 1304 |
}, |
| 1305 |
success: function (response) { |
| 1306 |
// Reset button state (CSS handles icon visibility) |
| 1307 |
btn.removeClass('wpf-ai-suggest-loading'); |
| 1308 |
btn.find('span').text(wpforo_phrase('Suggest Reply')); |
| 1309 |
|
| 1310 |
if (response.success) { |
| 1311 |
var content = response.data.content || ''; |
| 1312 |
if (!content) { |
| 1313 |
alert(wpforo_phrase('AI generated an empty reply')); |
| 1314 |
return; |
| 1315 |
} |
| 1316 |
|
| 1317 |
// Append content to TinyMCE editor |
| 1318 |
var inserted = wpforoInsertIntoEditor(content); |
| 1319 |
if (!inserted) { |
| 1320 |
// Fallback: try to append to textarea |
| 1321 |
var textarea = form.find('textarea[name="postbody"]'); |
| 1322 |
if (textarea.length) { |
| 1323 |
var existingVal = textarea.val().trim(); |
| 1324 |
if (existingVal) { |
| 1325 |
textarea.val(existingVal + '\n\n' + content); |
| 1326 |
} else { |
| 1327 |
textarea.val(content); |
| 1328 |
} |
| 1329 |
} else { |
| 1330 |
alert(wpforo_phrase('Could not insert content into editor')); |
| 1331 |
} |
| 1332 |
} |
| 1333 |
|
| 1334 |
// Show credits used info |
| 1335 |
var credits = response.data.credits_used || 0; |
| 1336 |
if (credits > 0) { |
| 1337 |
console.log('AI Suggest Reply: ' + credits + ' credits used'); |
| 1338 |
} |
| 1339 |
} else { |
| 1340 |
var errorMsg = response.data && response.data.message |
| 1341 |
? response.data.message |
| 1342 |
: wpforo_phrase('Failed to generate reply suggestion'); |
| 1343 |
alert(errorMsg); |
| 1344 |
} |
| 1345 |
}, |
| 1346 |
error: function (xhr, status, error) { |
| 1347 |
// Reset button state (CSS handles icon visibility) |
| 1348 |
btn.removeClass('wpf-ai-suggest-loading'); |
| 1349 |
btn.find('span').text(wpforo_phrase('Suggest Reply')); |
| 1350 |
console.error('AI Suggest Reply error:', error); |
| 1351 |
// Try to get error message from response (handles 429 rate limit errors) |
| 1352 |
var errorMsg = wpforo_phrase('Network error. Please try again.'); |
| 1353 |
try { |
| 1354 |
var response = JSON.parse(xhr.responseText); |
| 1355 |
if (response.data && response.data.message) { |
| 1356 |
errorMsg = response.data.message; |
| 1357 |
} |
| 1358 |
} catch (e) { |
| 1359 |
// Keep default error message |
| 1360 |
} |
| 1361 |
alert(errorMsg); |
| 1362 |
} |
| 1363 |
}); |
| 1364 |
}); |
| 1365 |
|
| 1366 |
/** |
| 1367 |
* Append content to TinyMCE editor |
| 1368 |
* @param {string} content HTML content to append |
| 1369 |
* @returns {boolean} True if successful |
| 1370 |
*/ |
| 1371 |
function wpforoInsertIntoEditor(content) { |
| 1372 |
// Try to find the active TinyMCE editor |
| 1373 |
if (typeof tinyMCE !== 'undefined' && tinyMCE.activeEditor) { |
| 1374 |
var editor = tinyMCE.activeEditor; |
| 1375 |
// Append content to existing content with line break |
| 1376 |
var existingContent = editor.getContent().trim(); |
| 1377 |
if (existingContent) { |
| 1378 |
editor.setContent(existingContent + '<p> </p>' + content); |
| 1379 |
} else { |
| 1380 |
editor.setContent(content); |
| 1381 |
} |
| 1382 |
// Focus the editor |
| 1383 |
editor.focus(); |
| 1384 |
return true; |
| 1385 |
} |
| 1386 |
|
| 1387 |
// Try by ID (wpForo's default editor ID) |
| 1388 |
if (typeof tinyMCE !== 'undefined') { |
| 1389 |
var editorIds = ['postbody', 'wpf_editor_postbody']; |
| 1390 |
for (var i = 0; i < editorIds.length; i++) { |
| 1391 |
var ed = tinyMCE.get(editorIds[i]); |
| 1392 |
if (ed) { |
| 1393 |
var existingContent = ed.getContent().trim(); |
| 1394 |
if (existingContent) { |
| 1395 |
ed.setContent(existingContent + '<p> </p>' + content); |
| 1396 |
} else { |
| 1397 |
ed.setContent(content); |
| 1398 |
} |
| 1399 |
ed.focus(); |
| 1400 |
return true; |
| 1401 |
} |
| 1402 |
} |
| 1403 |
} |
| 1404 |
|
| 1405 |
return false; |
| 1406 |
} |
| 1407 |
|
| 1408 |
// ========================================================================= |
| 1409 |
// AI BUTTON VISIBILITY ANIMATIONS |
| 1410 |
// ========================================================================= |
| 1411 |
|
| 1412 |
// Trigger animations when AI buttons become visible on screen |
| 1413 |
if ('IntersectionObserver' in window) { |
| 1414 |
var aiButtonObserver = new IntersectionObserver(function(entries) { |
| 1415 |
entries.forEach(function(entry) { |
| 1416 |
if (entry.isIntersecting) { |
| 1417 |
// Add visible class to trigger animation |
| 1418 |
entry.target.classList.add('wpf-ai-visible'); |
| 1419 |
// Stop observing after animation triggered |
| 1420 |
aiButtonObserver.unobserve(entry.target); |
| 1421 |
} |
| 1422 |
}); |
| 1423 |
}, { |
| 1424 |
threshold: 0.5 // Trigger when 50% visible |
| 1425 |
}); |
| 1426 |
|
| 1427 |
// Observe AI Helper Toggle buttons |
| 1428 |
document.querySelectorAll('.wpf-ai-helper-toggle').forEach(function(el) { |
| 1429 |
aiButtonObserver.observe(el); |
| 1430 |
}); |
| 1431 |
|
| 1432 |
// Observe AI Summarize buttons |
| 1433 |
document.querySelectorAll('.wpf-ai-summarize-btn').forEach(function(el) { |
| 1434 |
aiButtonObserver.observe(el); |
| 1435 |
}); |
| 1436 |
} |
| 1437 |
}); |
| 1438 |
|