')
.addClass('notice notice-' + type + ' is-dismissible')
.append($('
').text(message));
// Insert notice after page title
$('.wpforo-ai-title').after($notice);
// Auto-dismiss after 5 seconds
setTimeout(function() {
$notice.fadeOut(function() {
$(this).remove();
});
}, 5000);
// Make dismissible
$(document).trigger('wp-updates-notice-added');
},
/**
* Format numbers with thousand separators
*/
formatNumber: function(num) {
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
},
/**
* Validate form before submission
*/
validateForm: function($form) {
let isValid = true;
const requiredFields = $form.find('[required]');
requiredFields.each(function() {
const $field = $(this);
const value = $field.val().trim();
if (!value) {
isValid = false;
$field.addClass('error');
$field.on('input change', function() {
$(this).removeClass('error');
});
}
});
if (!isValid) {
alert('Please fill in all required fields.');
}
return isValid;
},
/**
* Initialize RAG-specific features
*/
initRAGFeatures: function() {
// Check if indexing was being stopped before page reload
if (localStorage.getItem('wpforo_indexing_stopping') === 'true') {
this.indexingStopping = true;
// Update status to show "Stopping..." if still processing
const $statusElement = $('#rag-indexing-status');
const statusText = $statusElement.text().trim();
// Only show "Stopping..." if status indicates processing (not idle)
if ($statusElement.length && statusText !== 'Idle') {
$statusElement.text('Stopping...');
} else if (statusText === 'Idle') {
// Process already stopped, clear the flag
this.indexingStopping = false;
localStorage.removeItem('wpforo_indexing_stopping');
}
}
// Unbind first to prevent duplicate handlers
$(document).off('click', '.wpforo-ai-reindex-all');
$(document).off('click', '.wpforo-ai-reindex-images');
$(document).off('click', '.wpforo-ai-clear-database');
$(document).off('click', '.wpforo-ai-clear-and-reindex');
$(document).off('click', '.wpforo-ai-stop-indexing');
$(document).off('click', '.wpforo-ai-cleanup-session');
$(document).off('submit', '#wpforo-ai-search-test-form');
// Bind bulk action buttons
$(document).on('click', '.wpforo-ai-reindex-all', this.handleReindexAll.bind(this));
$(document).on('click', '.wpforo-ai-reindex-images', this.handleReindexImages.bind(this));
$(document).on('click', '.wpforo-ai-clear-database', this.handleClearDatabase.bind(this));
$(document).on('click', '.wpforo-ai-clear-and-reindex', this.handleClearAndReindex.bind(this));
$(document).on('click', '.wpforo-ai-stop-indexing', this.handleStopIndexing.bind(this));
$(document).on('click', '.wpforo-ai-cleanup-session', this.handleCleanupSession.bind(this));
// Bind search test form
$(document).on('submit', '#wpforo-ai-search-test-form', this.handleSearchTest.bind(this));
// Bind storage mode toggle
$(document).off('change', 'input[name="wpforo_ai_storage_mode"]');
$(document).on('change', 'input[name="wpforo_ai_storage_mode"]', this.handleStorageModeChange.bind(this));
// Bind auto-indexing toggle
$(document).off('change', '#wpforo-ai-auto-indexing');
$(document).on('change', '#wpforo-ai-auto-indexing', this.handleAutoIndexingToggle.bind(this));
// Bind image indexing toggle
$(document).off('change', '#wpforo-ai-image-indexing');
$(document).on('change', '#wpforo-ai-image-indexing', this.handleImageIndexingToggle.bind(this));
// Bind document indexing toggle
$(document).off('change', '#wpforo-ai-document-indexing');
$(document).on('change', '#wpforo-ai-document-indexing', this.handleDocumentIndexingToggle.bind(this));
// Bind refresh status button
$(document).off('click', '.wpforo-ai-refresh-rag-status');
$(document).on('click', '.wpforo-ai-refresh-rag-status', this.handleRefreshStatus.bind(this));
// Check for in-progress local indexing and auto-resume
this.checkLocalIndexingProgress();
// Check for in-progress cloud indexing auto-refresh (survives page reloads)
this.checkForumIndexingAutoRefresh();
// Load indexing breakdown asynchronously (cached 1 day)
this.loadIndexingBreakdown();
// Note: Polling is started from PHP inline script based on server-side $is_indexing status
// No need to start it here to avoid duplicate polling
},
/**
* Handle storage mode toggle change
*/
handleStorageModeChange: function(e) {
const $input = $(e.currentTarget);
const newMode = $input.val();
const $container = $input.closest('.wpforo-ai-storage-toggle');
// Update active state on labels
$container.find('.wpforo-ai-storage-option').removeClass('active');
$input.next('label').addClass('active');
// Get the current board ID from URL
const urlParams = new URLSearchParams(window.location.search);
const boardId = urlParams.get('boardid') || 0;
// Save via AJAX
$.ajax({
url: wpforoAIAdmin.ajaxUrl,
type: 'POST',
data: {
action: 'wpforo_ai_save_storage_mode',
nonce: wpforoAIAdmin.nonce,
storage_mode: newMode,
board_id: boardId
},
beforeSend: function() {
$container.css('opacity', '0.6');
},
success: function(response) {
$container.css('opacity', '1');
if (response.success) {
// Reload page to update storage info section
window.location.reload();
} else {
alert(response.data?.message || 'Failed to save storage mode.');
// Revert the change
window.location.reload();
}
},
error: function() {
$container.css('opacity', '1');
alert('Error saving storage mode. Please try again.');
window.location.reload();
}
});
},
/**
* Handle auto-indexing toggle change
*/
handleAutoIndexingToggle: function(e) {
const $input = $(e.currentTarget);
const isEnabled = $input.is(':checked') ? 1 : 0;
const boardId = $input.data('board-id') || 0;
const $toggle = $input.closest('.wpforo-ai-auto-index-toggle');
// Disable the toggle during AJAX request
$input.prop('disabled', true);
$toggle.css('opacity', '0.6');
// Save via AJAX
$.ajax({
url: wpforoAIAdmin.ajaxUrl,
type: 'POST',
data: {
action: 'wpforo_ai_save_auto_indexing',
nonce: wpforoAIAdmin.nonce,
enabled: isEnabled,
board_id: boardId
},
success: function(response) {
$input.prop('disabled', false);
$toggle.css('opacity', '1');
if (!response.success) {
// Revert the change on failure
$input.prop('checked', !isEnabled);
alert(response.data?.message || 'Failed to save auto-indexing setting.');
}
},
error: function() {
$input.prop('disabled', false);
$toggle.css('opacity', '1');
// Revert the change on error
$input.prop('checked', !isEnabled);
alert('Error saving auto-indexing setting. Please try again.');
}
});
},
/**
* Handle image indexing toggle change
*
* When enabled, posts with images will consume +1 additional credit
* for multimodal processing (image → text → embedding).
* Requires Business or Enterprise plan.
*/
handleImageIndexingToggle: function(e) {
const $input = $(e.currentTarget);
const isEnabled = $input.is(':checked') ? 1 : 0;
const boardId = $input.data('board-id') || 0;
const $toggle = $input.closest('.wpforo-ai-auto-index-toggle');
// Show confirmation when enabling (due to credit impact)
if (isEnabled) {
const confirmed = confirm(
'Enable Image Indexing?\n\n' +
'When enabled, posts with images will consume +1 additional credit during indexing.\n\n' +
'• Maximum 10 images per post are processed\n' +
'• Images are converted to text descriptions for search\n' +
'• Small images (< 50x50px) like smileys are skipped\n\n' +
'Continue?'
);
if (!confirmed) {
$input.prop('checked', false);
return;
}
}
// Disable the toggle during AJAX request
$input.prop('disabled', true);
$toggle.css('opacity', '0.6');
// Save via AJAX
$.ajax({
url: wpforoAIAdmin.ajaxUrl,
type: 'POST',
data: {
action: 'wpforo_ai_save_image_indexing',
nonce: wpforoAIAdmin.nonce,
enabled: isEnabled,
board_id: boardId
},
success: function(response) {
$input.prop('disabled', false);
$toggle.css('opacity', '1');
if (response.success) {
// Show success message
if (response.data?.message) {
// Brief notification instead of alert
console.log('Image indexing: ' + response.data.message);
}
} else {
// Revert the change on failure
$input.prop('checked', !isEnabled);
alert(response.data?.message || 'Failed to save image indexing setting.');
}
},
error: function() {
$input.prop('disabled', false);
$toggle.css('opacity', '1');
// Revert the change on error
$input.prop('checked', !isEnabled);
alert('Error saving image indexing setting. Please try again.');
}
});
},
/**
* Handle document indexing toggle change
*/
handleDocumentIndexingToggle: function(e) {
const $input = $(e.currentTarget);
const isEnabled = $input.is(':checked') ? 1 : 0;
const boardId = $input.data('board-id') || 0;
const $toggle = $input.closest('.wpforo-ai-auto-index-toggle');
// Show confirmation when enabling (due to credit impact)
if (isEnabled) {
const confirmed = confirm(
'Enable Document Indexing?\n\n' +
'When enabled, document attachments (PDF, DOCX, PPTX, etc.) will be processed during indexing.\n\n' +
'• Maximum 5 documents per post\n' +
'• Text is extracted from documents for search\n' +
'• Credit cost: 1 per page\n\n' +
'Continue?'
);
if (!confirmed) {
$input.prop('checked', false);
return;
}
}
// Disable the toggle during AJAX request
$input.prop('disabled', true);
$toggle.css('opacity', '0.6');
// Save via AJAX
$.ajax({
url: wpforoAIAdmin.ajaxUrl,
type: 'POST',
data: {
action: 'wpforo_ai_save_document_indexing',
nonce: wpforoAIAdmin.nonce,
enabled: isEnabled,
board_id: boardId
},
success: function(response) {
$input.prop('disabled', false);
$toggle.css('opacity', '1');
if (response.success) {
if (response.data?.message) {
console.log('Document indexing: ' + response.data.message);
}
} else {
$input.prop('checked', !isEnabled);
alert(response.data?.message || 'Failed to save document indexing setting.');
}
},
error: function() {
$input.prop('disabled', false);
$toggle.css('opacity', '1');
$input.prop('checked', !isEnabled);
alert('Error saving document indexing setting. Please try again.');
}
});
},
/**
* Handle refresh status button click
*/
handleRefreshStatus: function(e) {
e.preventDefault();
const $button = $(e.currentTarget);
const $icon = $button.find('.dashicons-update');
// Add spinning animation
$icon.addClass('wpforo-spin');
$button.prop('disabled', true);
// Store reference for callback
const self = this;
// Refresh status via AJAX
$.ajax({
url: wpforoAIAdmin.ajaxUrl,
type: 'POST',
data: {
action: 'wpforo_ai_get_rag_status',
nonce: wpforoAIAdmin.nonce
},
success: function(response) {
if (response.success && response.data) {
self.updateRAGStatusDisplay(response.data);
}
},
error: function(xhr, status, error) {
console.error('Failed to refresh RAG status:', error);
},
complete: function() {
// Stop spinning animation
$icon.removeClass('wpforo-spin');
$button.prop('disabled', false);
}
});
},
/**
* Load indexing breakdown via AJAX (private/unapproved topic counts)
* Data is cached server-side for 1 day to avoid slow GROUP BY queries
*/
loadIndexingBreakdown: function() {
const $container = $('#wpforo-ai-indexing-breakdown-container');
if (!$container.length) {
return;
}
const self = this;
const loadingText = $container.data('loading-text') || 'Loading...';
// Show small loading spinner
$container.html(' ' + loadingText + '');
$.ajax({
url: wpforoAIAdmin.ajaxUrl,
type: 'POST',
data: {
action: 'wpforo_ai_get_indexing_breakdown',
nonce: wpforoAIAdmin.nonce
},
success: function(response) {
if (response.success && response.data) {
self.renderIndexingBreakdown($container, response.data);
} else {
$container.empty();
}
},
error: function() {
$container.empty();
}
});
},
/**
* Render the indexing breakdown HTML
*/
renderIndexingBreakdown: function($container, data) {
const privateCount = parseInt(data.private, 10) || 0;
const unapprovedCount = parseInt(data.unapproved, 10) || 0;
if (privateCount === 0 && unapprovedCount === 0) {
$container.empty();
return;
}
const excludedCount = privateCount + unapprovedCount;
const excludedText = $container.data('excluded-text') || '%s topics are excluded from indexing';
const introText = $container.data('intro-text') || 'The following topics are automatically excluded from AI indexing:';
const privateText = $container.data('private-text') || 'private topics - these are only visible to their authors';
const unapprovedText = $container.data('unapproved-text') || 'unapproved topics - these will be indexed once approved by moderators';
const noteText = $container.data('note-text') || 'Private topics are never indexed to protect user privacy. Unapproved topics will be automatically indexed when approved.';
let html = '
';
html += '
';
html += '';
html += '';
html += excludedText.replace('%s', '' + this.formatNumber(excludedCount) + '');
html += '';
html += '
';
html += '';
html += '
' + introText + '
';
html += '
';
if (privateCount > 0) {
html += '- ';
html += '' + this.formatNumber(privateCount) + ' ' + privateText + '
';
}
if (unapprovedCount > 0) {
html += '- ';
html += '' + this.formatNumber(unapprovedCount) + ' ' + unapprovedText + '
';
}
html += '
';
html += '
' + noteText + '
';
html += '
';
$container.html(html);
},
// WordPress Content Indexing Methods have been moved to
// ai-features-wp-indexing.js for the dedicated WordPress Indexing tab.
// See: WpForoWPIndexing in admin/assets/js/ai-features-wp-indexing.js
/**
* Initialize tag autocomplete using WordPress suggest script
*/
initTagSuggest: function() {
var $tagInput = $('.wpforo-ai-tags-input');
if ($tagInput.length && typeof $.fn.suggest === 'function' && typeof wpforoAIAdmin !== 'undefined') {
var ajaxUrl = wpforoAIAdmin.ajaxUrl;
$tagInput.suggest(
ajaxUrl + (ajaxUrl.indexOf('?') !== -1 ? '&' : '?') + 'action=wpforo_tag_search',
{
multiple: true,
multipleSep: ',',
delay: 500,
minchars: 2,
resultsClass: 'wpforo-ai-tag-results',
selectClass: 'wpforo-ai-tag-over',
matchClass: 'wpforo-ai-tag-match'
}
);
}
},
/**
* Initialize Bot User Search autocomplete for AI Bot Reply settings
*/
initBotUserSearch: function() {
const self = this;
const $searchInput = $('#wpforo-ai-bot-user-search');
// Only init if the search input exists (settings page with Bot Reply section)
if (!$searchInput.length) {
return;
}
const $wrapper = $searchInput.closest('.wpforo-ai-user-search-wrapper');
const $hiddenInput = $wrapper.find('.wpforo-ai-user-id-input');
const $resultsContainer = $wrapper.find('.wpforo-ai-user-search-results');
const nonce = $('#wpforo_ai_bot_user_nonce').val() || '';
let searchTimeout = null;
// Handle input for search
$searchInput.on('input', function() {
const searchTerm = $(this).val().trim();
// Clear previous timeout
if (searchTimeout) {
clearTimeout(searchTimeout);
}
// Clear results if search term is too short
if (searchTerm.length < 2) {
$resultsContainer.empty().hide();
return;
}
// Debounce the search
searchTimeout = setTimeout(function() {
self.searchBotUsers(searchTerm, $resultsContainer, $hiddenInput, $searchInput, nonce);
}, 300);
});
// Handle click outside to close results
$(document).on('click', function(e) {
if (!$(e.target).closest('.wpforo-ai-user-search-wrapper').length) {
$resultsContainer.empty().hide();
}
});
// Handle focus to show results if there's a search term
$searchInput.on('focus', function() {
if ($(this).val().trim().length >= 2 && $resultsContainer.children().length > 0) {
$resultsContainer.show();
}
});
},
/**
* Perform AJAX search for bot users
*/
searchBotUsers: function(searchTerm, $resultsContainer, $hiddenInput, $searchInput, nonce) {
$resultsContainer.html('
Searching...
').show();
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'wpforo_ai_search_bot_users',
search: searchTerm,
_wpnonce: nonce
},
success: function(response) {
$resultsContainer.empty();
if (response.success && response.data.users && response.data.users.length > 0) {
const $list = $('
');
response.data.users.forEach(function(user) {
const $item = $('
');
$item.text(user.label);
$item.on('click', function() {
$hiddenInput.val(user.id);
$searchInput.val(user.label);
$resultsContainer.empty().hide();
// Clear usergroup when specific user is selected
$hiddenInput.closest('.wpforo-ai-form-section').find('.wpforo-ai-author-groupid-select').val('');
});
$list.append($item);
});
$resultsContainer.append($list).show();
} else {
$resultsContainer.html('
No users found
').show();
}
},
error: function() {
$resultsContainer.html('
Search error
').show();
}
});
},
/**
* Initialize character counters for textareas with limits
* Uses proper character counting that works with multibyte characters
*/
initCharCounters: function() {
const self = this;
// Find all textareas with data-char-limit attribute
$(document).on('input', 'textarea[data-char-limit]', function() {
self.updateCharCounter($(this));
});
// Also handle when form fields are populated (e.g., when editing a task)
$(document).on('wpforo-ai-task-loaded', function() {
$('textarea[data-char-limit]').each(function() {
self.updateCharCounter($(this));
});
});
// Initialize counters on page load
$('textarea[data-char-limit]').each(function() {
self.updateCharCounter($(this));
});
},
/**
* Update character counter for a textarea
* Uses string spread operator for proper Unicode character counting
*/
updateCharCounter: function($textarea) {
const limit = parseInt($textarea.data('char-limit'), 10) || 120;
const $counter = $textarea.siblings('.wpforo-ai-char-counter').find('.current');
const $counterWrapper = $textarea.siblings('.wpforo-ai-char-counter');
if (!$counter.length) {
return;
}
// Use spread operator to properly count Unicode characters (multibyte safe)
const text = $textarea.val() || '';
const charCount = [...text].length;
$counter.text(charCount);
// Update counter styling based on proximity to limit
$counterWrapper.removeClass('warning limit');
if (charCount >= limit) {
$counterWrapper.addClass('limit');
} else if (charCount >= limit * 0.8) {
$counterWrapper.addClass('warning');
}
// Enforce limit (multibyte safe truncation)
if (charCount > limit) {
const truncated = [...text].slice(0, limit).join('');
$textarea.val(truncated);
$counter.text(limit);
$counterWrapper.addClass('limit');
}
},
/**
* Scroll to the Indexing Status section
*/
scrollToIndexingStatus: function() {
const $statusBox = $('.wpforo-ai-rag-status-box');
if ($statusBox.length) {
$('html, body').animate({
scrollTop: $statusBox.offset().top - 50
}, 500);
}
},
/**
* Handle Re-Index All button click
*/
handleReindexAll: function(e) {
e.preventDefault();
const $button = $(e.currentTarget);
const confirmMessage = $button.data('confirm');
if (!confirm(confirmMessage)) {
return;
}
// Scroll to status section
this.scrollToIndexingStatus();
// Check if we're in local storage mode
if (this.isLocalStorageMode()) {
// Use AJAX-driven batch processing for local mode
this.startLocalIndexing($button);
} else {
// Use form submission for cloud mode
this.submitRAGAction('reindex_all', $button);
}
},
/**
* Handle Re-Index Topic Images button click
* Only re-indexes topics that contain images
*/
handleReindexImages: function(e) {
e.preventDefault();
const $button = $(e.currentTarget);
const confirmMessage = $button.data('confirm');
if (!confirm(confirmMessage)) {
return;
}
// Scroll to status section
this.scrollToIndexingStatus();
// Check if we're in local storage mode
if (this.isLocalStorageMode()) {
// Use AJAX-driven batch processing for local mode with images_only flag
this.startLocalIndexing($button, { images_only: true });
} else {
// Use form submission for cloud mode with images_only flag
this.submitRAGAction('reindex_images', $button);
}
},
/**
* Handle Clear Database button click
*/
handleClearDatabase: function(e) {
e.preventDefault();
const $button = $(e.currentTarget);
const confirmMessage = 'WARNING: This will permanently delete all indexed data.\n\nType "DELETE" to confirm:';
const userInput = prompt(confirmMessage);
if (userInput !== 'DELETE') {
if (userInput !== null) {
alert('Confirmation failed. Database was not cleared.');
}
return;
}
// Create and submit form with confirmation value
this.submitRAGAction('clear_database', $button, { confirm: userInput });
},
/**
* Handle Clear & Re-Index button click
*/
handleClearAndReindex: function(e) {
e.preventDefault();
const $button = $(e.currentTarget);
const confirmMessage = 'This will:\n1. Clear all indexed data\n2. Re-index all topics\n\nType "CONFIRM" to proceed:';
const userInput = prompt(confirmMessage);
if (userInput !== 'CONFIRM') {
if (userInput !== null) {
alert('Confirmation failed. Operation cancelled.');
}
return;
}
// Check if we're in local storage mode
if (this.isLocalStorageMode()) {
// Use AJAX-driven process for local mode
this.clearAndReindexLocal($button);
} else {
// Use form submission for cloud mode
this.submitRAGAction('clear_and_reindex', $button);
}
},
/**
* Clear and re-index for local storage mode via AJAX
*/
clearAndReindexLocal: function($button) {
const self = this;
// Show loading state
$button.addClass('loading').prop('disabled', true);
$button.html('
Clearing...');
// First clear local embeddings
$.ajax({
url: wpforoAIAdmin.ajaxUrl,
type: 'POST',
data: {
action: 'wpforo_ai_action',
wpforo_ai_action: 'clear_local_embeddings',
_wpnonce: wpforoAIAdmin.nonce
},
success: function(response) {
if (response.success) {
console.log('Local embeddings cleared:', response.data);
// Now start the indexing
self.startLocalIndexing($button);
} else {
const errorMsg = response.data && response.data.message
? response.data.message
: 'Failed to clear embeddings';
alert('Error: ' + errorMsg);
$button.removeClass('loading').prop('disabled', false);
$button.html('
Clear & Re-Index');
}
},
error: function(xhr, status, error) {
console.error('Clear local embeddings error:', error);
alert('Error clearing embeddings: ' + error);
$button.removeClass('loading').prop('disabled', false);
$button.html('
Clear & Re-Index');
}
});
},
/**
* Handle Stop Indexing button click
*/
handleStopIndexing: function(e) {
e.preventDefault();
const $button = $(e.currentTarget);
const confirmMessage = $button.data('confirm');
if (!confirm(confirmMessage)) {
return;
}
// Set stopping flag so status shows "Stopping..." while process winds down
// Use localStorage to persist across page reloads
this.indexingStopping = true;
localStorage.setItem('wpforo_indexing_stopping', 'true');
// Clear auto-refresh flag so page doesn't keep reloading after stop
this.stopForumIndexingAutoRefresh();
// Immediately update status to show "Stopping..."
const $statusElement = $('#rag-indexing-status');
if ($statusElement.length) {
$statusElement.text('Stopping...');
}
// Check if we're in local storage mode with AJAX indexing
if (this.isLocalStorageMode() && this.localIndexingState) {
// Stop the AJAX-driven indexing loop (this updates UI)
this.stopLocalIndexing();
// Clear the queue on the server via AJAX (no page reload)
this.clearLocalIndexingQueue();
} else {
// Cloud mode: tell the backend to stop the image_worker
// draining queued media jobs. Polling will pick up the
// drained state via the regular /rag/status poll.
this.cancelCloudIndexing();
}
},
/**
* Tell the backend to stop in-flight cloud indexing (image worker).
* No page reload — polling will pick up the drained state.
*/
cancelCloudIndexing: function() {
const self = this;
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'wpforo_ai_cancel_cloud_indexing',
_wpnonce: wpforoAIAdmin.nonce
},
success: function(response) {
console.log('Cloud indexing cancel requested:', response);
},
error: function(xhr, status, error) {
console.error('Failed to cancel cloud indexing:', error);
// Clear the stopping flag so the user can retry
self.indexingStopping = false;
localStorage.removeItem('wpforo_indexing_stopping');
}
});
},
/**
* Handle "Cleanup Indexing Session" button clicks.
*
* Resets stuck indexing state (queues, WP-Cron jobs, transient locks,
* status caches) without touching any already-indexed data. Works for
* both local and cloud storage modes — the backend cleans up both
* queue keys in one call and also tells the cloud image_worker to
* drop any in-flight messages.
*
* Also clears the browser-side localStorage stopping flag so the UI
* doesn't get stuck on "Stopping..." after the cleanup.
*
* data-scope on the button is 'forum' or 'wp'.
*/
handleCleanupSession: function(e) {
e.preventDefault();
const $button = $(e.currentTarget);
const scope = $button.data('scope') || 'forum';
const confirmMsg = $button.data('confirm') || 'Reset stuck indexing session?';
if (!window.confirm(confirmMsg)) {
return;
}
const originalHtml = $button.html();
$button.prop('disabled', true).html('
Cleaning up...');
// Clear any browser-side stuck state first — regardless of AJAX
// outcome. These are the client-side flags the plugin sets for
// indexing (see handleStopIndexing / checkLocalIndexingProgress).
try {
localStorage.removeItem('wpforo_indexing_stopping');
localStorage.removeItem('wpforo_wp_indexing_auto_refresh');
localStorage.removeItem('wpforo_forum_indexing_auto_refresh');
} catch (err) { /* localStorage may be blocked in some contexts */ }
this.indexingStopping = false;
this.stopWPIndexingAutoRefresh();
this.stopForumIndexingAutoRefresh();
const self = this;
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'wpforo_ai_cleanup_indexing_session',
scope: scope,
_wpnonce: wpforoAIAdmin.nonce
},
success: function(response) {
$button.prop('disabled', false).html(originalHtml);
if (response && response.success) {
// Reload to refresh all server-rendered counts and
// flip the UI out of "Indexing..." state cleanly.
window.location.reload();
} else {
const msg = (response && response.data && response.data.message) || 'Cleanup failed.';
window.alert(msg);
}
},
error: function(xhr, status, error) {
$button.prop('disabled', false).html(originalHtml);
console.error('Cleanup indexing session failed:', error);
window.alert('Cleanup failed. Check the browser console for details.');
}
});
},
/**
* Clear local indexing queue via AJAX (no page reload)
*/
clearLocalIndexingQueue: function() {
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'wpforo_ai_action',
wpforo_ai_action: 'stop_local_indexing',
_wpnonce: wpforoAIAdmin.nonce
},
success: function(response) {
console.log('Local indexing queue cleared:', response);
},
error: function(xhr, status, error) {
console.error('Failed to clear queue:', error);
}
});
},
/**
* Submit RAG action form
*/
submitRAGAction: function(action, $button, additionalData) {
// Create hidden form
const $form = $('