is_activated(); $options = get_option('mxchat_options', array()); $plugin_url = plugin_dir_url(dirname(__FILE__)); ?>

show_theme_migration_banner(); ?>

mxchat_model_callback(); }, __('Select the AI model your chatbot will use for conversations.', 'mxchat')); // Embedding Model mxchat_render_field_wrapper('embedding_model', __('Embedding Model', 'mxchat'), function() use ($admin_instance) { $admin_instance->embedding_model_callback(); }, __('Vector embedding model for knowledge base matching. Changing models requires reconfiguring knowledge data.', 'mxchat')); // Enable Streaming mxchat_render_field_wrapper('enable_streaming', __('Enable Streaming', 'mxchat'), function() use ($admin_instance) { $admin_instance->enable_streaming_toggle_callback(); }, __('Enable real-time streaming responses for supported models (OpenAI, Claude, DeepSeek, Grok).', 'mxchat')); // Enable Web Search (OpenAI & Gemini) mxchat_render_field_wrapper('enable_web_search', __('Enable Web Search', 'mxchat'), function() use ($admin_instance) { $admin_instance->enable_web_search_toggle_callback(); }, __('Allow the chatbot to search the web for current information (OpenAI and Gemini models).', 'mxchat')); ?>

system_prompt_instructions_callback(); }, __('Provide system-level instructions to guide the AI\'s behavior and responses.', 'mxchat')); // Similarity Threshold mxchat_render_field_wrapper('similarity_threshold', __('Similarity Threshold', 'mxchat'), function() use ($admin_instance) { $admin_instance->mxchat_similarity_threshold_callback(); }, __('Adjust threshold for content matching. Lower values = more matches, higher values = stricter matching.', 'mxchat')); // RAG Sources Limit mxchat_render_field_wrapper('rag_sources_limit', __('RAG Sources Limit', 'mxchat'), function() use ($admin_instance) { $admin_instance->mxchat_rag_sources_limit_callback(); }, __('Number of knowledge base sources (pages/documents) to include in AI context. Higher values provide more context but use more tokens.', 'mxchat')); // RAG Chunks Limit mxchat_render_field_wrapper('rag_chunks_limit', __('RAG Chunks Limit', 'mxchat'), function() use ($admin_instance) { $admin_instance->mxchat_rag_chunks_limit_callback(); }, __('Maximum total content chunks sent to the AI across all sources. Higher values provide more context but increase token usage and cost.', 'mxchat')); // Hybrid keyword boost MOVED to Knowledge → Chunking & // Retrieval (plan 11720c) — it is KB retrieval tuning, not // conversation behavior. Same option key over there. The // breadcrumb below is a one-release courtesy: drop it in // the release after 3.2.17. echo '

'; printf( /* translators: %s: link to the Chunking & Retrieval settings tab. */ esc_html__('Hybrid keyword boost has moved to %s.', 'mxchat'), '' . esc_html__('Knowledge, Chunking & Retrieval', 'mxchat') . '' ); echo '

'; // Contextual Awareness mxchat_render_field_wrapper('contextual_awareness', __('Contextual Awareness', 'mxchat'), function() use ($admin_instance) { $admin_instance->mxchat_contextual_awareness_callback(); }, __('Allow the chatbot to understand and reference the current page content for more relevant responses.', 'mxchat')); // Citation Links mxchat_render_field_wrapper('citation_links', __('Citation Links', 'mxchat'), function() use ($admin_instance) { $admin_instance->mxchat_citation_links_toggle_callback(); }, __('Allow the AI to include citation links from your knowledge database in responses. If disabled, ensure your AI Behavior settings do not mention links or the AI may fabricate URLs.', 'mxchat')); // Strip Unapproved Links (plan 58f8b4) — enforcement is now // its own control instead of riding Citation Links. mxchat_render_field_wrapper('strip_unapproved_links', __('Strip Unapproved Links', 'mxchat'), function() use ($admin_instance) { $admin_instance->mxchat_strip_unapproved_links_toggle_callback(); }, __('Remove links the AI invents from its answers, even when Citation Links is off. Approved citations, real pages on this site, and links returned by your integrations (e.g. WooCommerce products) are always kept. On by default for new installs; existing sites keep their current behavior until enabled.', 'mxchat')); // Satisfaction Rating Prompt // The toggle callback inline-renders the 5 customization // fields inside a single sub-options wrapper (plan-29caac). // No separate customization group needed — the wrapper handles // its own server-side display state based on the toggle value. mxchat_render_field_wrapper('satisfaction_rating', __('Satisfaction Rating Prompt', 'mxchat'), function() use ($admin_instance) { $admin_instance->mxchat_satisfaction_rating_toggle_callback(); }, __('Show a 👍 / 👎 prompt after a conversation has had a couple of bot replies and the visitor has been idle for a moment. Disable to hide the prompt site-wide.', 'mxchat')); // Editor Assistant toggle moved to Content → Settings (plan-f7df40): // it's a content feature, so it lives on the screen that owns it. // Rendering is in admin-content-page.php; option + save path unchanged. ?>

mxchat_append_to_body_callback(); }, __('Show chatbot automatically on all pages. Disable to use shortcode [mxchat_chatbot floating="yes"] for floating widget or [mxchat_chatbot floating="no"] for embedded chat.', 'mxchat')); // Open Links in New Tab mxchat_render_field_wrapper('link_target', __('Open Links in New Tab', 'mxchat'), function() use ($admin_instance) { $admin_instance->mxchat_link_target_toggle_callback(); }, __('Open links clicked in chat responses in a new browser tab.', 'mxchat')); // Chat Persistence mxchat_render_field_wrapper('chat_persistence', __('Chat Persistence', 'mxchat'), function() use ($admin_instance) { $admin_instance->mxchat_chat_persistence_toggle_callback(); }, __('Keep chat history when users navigate or return within 24 hours.', 'mxchat')); // Download Transcript button mxchat_render_field_wrapper('print_button', __('Show Download Transcript Button', 'mxchat'), function() use ($admin_instance) { $admin_instance->mxchat_print_button_toggle_callback(); }, __('Show the "Download Transcript" item in the chat window menu.', 'mxchat')); // Start-New-Chat button (plan ac2e81) — lets a visitor reset the // conversation without the owner disabling chat persistence globally. mxchat_render_field_wrapper('reset_chat_enabled', __('Show Start-New-Chat Button', 'mxchat'), function() use ($admin_instance) { $admin_instance->mxchat_reset_chat_toggle_callback(); }, __('Show a "Start new chat" item in the chat window menu that clears the current conversation and begins a fresh session.', 'mxchat')); mxchat_render_field_wrapper('reset_chat_label', __('Start-New-Chat Button Label', 'mxchat'), function() use ($admin_instance) { $admin_instance->mxchat_reset_chat_label_callback(); }, __('Label for the Start-New-Chat menu item. Leave blank to use "Start new chat".', 'mxchat')); ?>

mxchat_top_bar_title_callback(); }, __('Title text shown in the chatbot header.', 'mxchat')); // AI Agent Text mxchat_render_field_wrapper('ai_agent_text', __('AI Agent Text', 'mxchat'), function() use ($admin_instance) { $admin_instance->mxchat_ai_agent_text_callback(); }, __('Text shown for AI agents in the status indicator.', 'mxchat')); // Intro Message mxchat_render_field_wrapper('intro_message', __('Introductory Message', 'mxchat'), function() use ($admin_instance) { $admin_instance->mxchat_intro_message_callback(); }, __('First message shown when chat opens. HTML allowed.', 'mxchat')); // Input Placeholder mxchat_render_field_wrapper('input_copy', __('Input Placeholder', 'mxchat'), function() use ($admin_instance) { $admin_instance->mxchat_input_copy_callback(); }, __('Placeholder text in the chat input field.', 'mxchat')); // Max Input Length (characters) — plan a3fae2 part C mxchat_render_field_wrapper('max_input_length', __('Max Input Length (characters)', 'mxchat'), function() use ($admin_instance) { $admin_instance->mxchat_max_input_length_callback(); }, __('Cap how many characters a visitor can type or paste into a single message. Set to 0 for unlimited (default).', 'mxchat')); // Chat Teaser mxchat_render_field_wrapper('pre_chat_message', __('Chat Teaser Pop-up', 'mxchat'), function() use ($admin_instance) { $admin_instance->mxchat_pre_chat_message_callback(); }, __('Message shown before users start chatting.', 'mxchat')); ?>

enable_email_block_callback(); }, __('Require users to enter their email before chatting. Shows for non-logged-in users.', 'mxchat')); // Email Form Content mxchat_render_field_wrapper('email_blocker_header', __('Email Form Content', 'mxchat'), function() use ($admin_instance) { $admin_instance->email_blocker_header_content_callback(); }, __('HTML content shown above the email form.', 'mxchat')); // Email Button Text mxchat_render_field_wrapper('email_blocker_button', __('Submit Button Text', 'mxchat'), function() use ($admin_instance) { $admin_instance->email_blocker_button_text_callback(); }, __('Text for the email form submit button.', 'mxchat')); // Require Name mxchat_render_field_wrapper('enable_name_field', __('Also Require Name', 'mxchat'), function() use ($admin_instance) { $admin_instance->enable_name_field_callback(); }, __('Also require users to enter their name with email.', 'mxchat')); // Name Placeholder mxchat_render_field_wrapper('name_placeholder', __('Name Field Placeholder', 'mxchat'), function() use ($admin_instance) { $admin_instance->name_field_placeholder_callback(); }, __('Placeholder text for the name input field.', 'mxchat')); // Consent Checkbox (b062c4) mxchat_render_field_wrapper('enable_consent_checkbox', __('Consent Checkbox', 'mxchat'), function() use ($admin_instance) { $admin_instance->enable_consent_checkbox_callback(); }, __('Show a consent checkbox on the email form. The decision, time, and exact label shown are stored with the lead.', 'mxchat')); // Consent Label mxchat_render_field_wrapper('consent_checkbox_label', __('Consent Label', 'mxchat'), function() use ($admin_instance) { $admin_instance->consent_checkbox_label_callback(); }, __('Text shown beside the checkbox. Anchor tags are allowed so you can link your Privacy Policy; other HTML is stripped.', 'mxchat')); // Consent Required mxchat_render_field_wrapper('consent_checkbox_required', __('Require Consent to Submit', 'mxchat'), function() use ($admin_instance) { $admin_instance->consent_checkbox_required_callback(); }, __('Block form submission until the box is ticked. Off means the checkbox is optional (a soft opt-in).', 'mxchat')); ?>

mxchat_privacy_toggle_callback(); }, __('Show privacy notice below the chat widget.', 'mxchat')); // Complianz mxchat_render_field_wrapper('complianz_toggle', __('Complianz Integration', 'mxchat'), function() use ($admin_instance) { $admin_instance->mxchat_complianz_toggle_callback(); }, __('Apply Complianz consent logic to the chatbot (requires Complianz plugin).', 'mxchat')); ?>

mxchat_popular_question_1_callback(); }); // Quick Question 2 mxchat_render_field_wrapper('popular_question_2', __('Quick Question 2', 'mxchat'), function() use ($admin_instance) { $admin_instance->mxchat_popular_question_2_callback(); }); // Quick Question 3 mxchat_render_field_wrapper('popular_question_3', __('Quick Question 3', 'mxchat'), function() use ($admin_instance) { $admin_instance->mxchat_popular_question_3_callback(); }); // Additional Questions mxchat_render_field_wrapper('additional_questions', __('Additional Questions', 'mxchat'), function() use ($admin_instance) { $admin_instance->mxchat_additional_popular_questions_callback(); }, __('Add as many quick questions as you need.', 'mxchat')); ?>

{limit}, {timeframe}, {count}, {remaining}
[Link text](https://example.com)

mxchat_smart_asset_loading_toggle_callback(); }, __('Skips the chatbot\'s CSS and JavaScript on pages where the widget is hidden (per-page visibility, post type include/exclude, or auto-display disabled). Pages using the chatbot shortcode still load everything automatically. Helps with "reduce unused CSS/JS" audit findings.', 'mxchat'), __('If your theme or page builder inserts the chatbot in an unusual way and it stops appearing, turn this off — or force-load assets with the mxchat_should_load_assets filter.', 'mxchat')); ?>

'WP Rocket', 'state' => 'auto', 'exclusion' => '/wp-admin/admin-ajax\\.php\\?action=mxchat_.*'), array('name' => 'LiteSpeed Cache', 'state' => 'auto', 'exclusion' => '/wp-admin/admin-ajax.php?action=mxchat_*'), array('name' => 'W3 Total Cache', 'state' => 'auto', 'exclusion' => '/wp-admin/admin-ajax.php?action=mxchat_*'), array('name' => 'FlyingPress', 'state' => 'auto', 'exclusion' => '/wp-admin/admin-ajax.php?action=mxchat_*'), array('name' => 'WP Super Cache', 'state' => 'auto', 'exclusion' => 'wp-admin/admin-ajax.php'), array('name' => 'Cloudflare APO', 'state' => 'manual', 'exclusion' => 'Page Rule / Cache Rule: URI Path contains "/wp-admin/admin-ajax.php" → Bypass Cache'), ); ?>

' . esc_html__('documentation page', 'mxchat') . '' ); ?>

Scopes):', 'mxchat'); ?>

channels:manage channels:read chat:write chat:write.public groups:write users:read


X-MxChat-Signature: sha256=<hex hmac-sha256 of the raw body>

__('Quick Setup with MxChat', 'mxchat'), 'description' => __('Learn how to quickly setup and understand how your chatbot works.', 'mxchat'), 'url' => 'https://www.youtube.com/watch?v=3BqoiyWaQiM&t' ), array( 'title' => __('AI Content Generation', 'mxchat'), 'description' => __('Generate full blog posts and landing pages with AI—from prompt to publish in minutes.', 'mxchat'), 'url' => 'https://www.youtube.com/watch?v=n8TeEeHpxs4' ), array( 'title' => __('Chat With Your GSC Data', 'mxchat'), 'description' => __('Find content gaps, quick win keywords, and SEO opportunities by chatting with your Google Search Console data using the Admin Assistant add-on.', 'mxchat'), 'url' => 'https://www.youtube.com/watch?v=ytgMou_7SZA&t=2s' ), array( 'title' => __('AI Theme Generator', 'mxchat'), 'description' => __('Learn how to instantly restyle your chatbot using plain English prompts with real-time previews.', 'mxchat'), 'url' => 'https://www.youtube.com/watch?v=rSQDW2qbtRU&t' ), array( 'title' => __('MxChat Forms', 'mxchat'), 'description' => __('Create and manage smart forms that automatically trigger during chat conversations.', 'mxchat'), 'url' => 'https://www.youtube.com/watch?v=3MrWy5dRalA' ), array( 'title' => __('Admin Assistant Add-on', 'mxchat'), 'description' => __('Bring a ChatGPT-like experience directly inside your WordPress dashboard.', 'mxchat'), 'url' => 'https://youtu.be/AdEA1k-UCFM' ), array( 'title' => __('Chat Themes', 'mxchat'), 'description' => __('Customize appearance with real-time previews to match your brand.', 'mxchat'), 'url' => 'https://youtu.be/MfbB9mZi6ag' ), array( 'title' => __('WooCommerce Integration', 'mxchat'), 'description' => __('Provide product recommendations and shopping assistance to customers.', 'mxchat'), 'url' => 'https://www.youtube.com/watch?v=WsqAppHRGdA' ), array( 'title' => __('Knowledge Base Setup', 'mxchat'), 'description' => __('Set up your knowledge base using PDFs, sitemaps, and manual entries.', 'mxchat'), 'url' => 'https://www.youtube.com/watch?v=8Ztjs66-VTo' ), array( 'title' => __('Document Chat', 'mxchat'), 'description' => __('Chat with PDF and Word documents for enhanced document analysis.', 'mxchat'), 'url' => 'https://www.youtube.com/watch?v=j_c45WWCTG0' ), array( 'title' => __('Perplexity Integration', 'mxchat'), 'description' => __('Enable real-time web search capabilities for your chatbot.', 'mxchat'), 'url' => 'https://youtu.be/wpKkbt24-bo' ), array( 'title' => __('Brave Search Intent', 'mxchat'), 'description' => __('Leverage Brave Search for improved query understanding.', 'mxchat'), 'url' => 'https://www.youtube.com/watch?v=7vDL5H7vToc' ), array( 'title' => __('Loops Email Capture', 'mxchat'), 'description' => __('Set up email capture with Loops to grow your mailing list.', 'mxchat'), 'url' => 'https://www.youtube.com/watch?v=CNgm5TYDyTc' ), array( 'title' => __('AI Agent Testing', 'mxchat'), 'description' => __('Evaluate and improve your chatbot\'s performance and accuracy.', 'mxchat'), 'url' => 'https://www.youtube.com/watch?v=A0jowbpyX54' ), ); foreach ($tutorials as $tutorial): ?>

__('Per Hour', 'mxchat'), 'daily' => __('Per Day', 'mxchat'), 'weekly' => __('Per Week', 'mxchat'), 'monthly' => __('Per Month', 'mxchat') ); $roles = wp_roles()->get_names(); $roles['logged_out'] = __('Logged Out Users', 'mxchat'); // Whole-chatbot global cap (sits above per-role; defaults to unlimited so // existing installs are unchanged). Stored under mxchat_options['rate_limits_global']. $global_cfg = isset($all_options['rate_limits_global']) && is_array($all_options['rate_limits_global']) ? $all_options['rate_limits_global'] : array(); $global_limit_raw = isset($global_cfg['limit']) ? (string) $global_cfg['limit'] : 'unlimited'; $global_timeframe = isset($global_cfg['timeframe']) ? (string) $global_cfg['timeframe'] : 'daily'; $global_is_unlimited = ($global_limit_raw === '' || $global_limit_raw === 'unlimited'); $global_is_preset = !$global_is_unlimited && in_array($global_limit_raw, $rate_limits, true); $global_select_value = $global_is_unlimited ? 'unlimited' : ($global_is_preset ? $global_limit_raw : '__custom__'); $global_custom_value = (!$global_is_unlimited && !$global_is_preset && ctype_digit($global_limit_raw)) ? $global_limit_raw : ''; echo '
'; // --- Global cap card ------------------------------------------------- ?>

>
0, 'timestamp' => time())); $usage_count = isset($usage_raw['count']) ? (int) $usage_raw['count'] : 0; $usage_ts = isset($usage_raw['timestamp']) ? (int) $usage_raw['timestamp'] : time(); $usage_windows = array('hourly' => 3600, 'daily' => 86400, 'weekly' => 604800, 'monthly' => 2592000); $usage_window = isset($usage_windows[$global_timeframe]) ? $usage_windows[$global_timeframe] : 86400; $usage_now = time(); if (($usage_now - $usage_ts) >= $usage_window) { // Window elapsed since the stored timestamp → effective count is 0. $usage_count = 0; $usage_reset_at = $usage_now + $usage_window; } else { $usage_reset_at = $usage_ts + $usage_window; } $usage_left = max(0, $global_usage_limit_int - $usage_count); $usage_pct = ($global_usage_limit_int > 0) ? min(100, (int) round(($usage_count / $global_usage_limit_int) * 100)) : 0; $usage_reset_nonce = wp_create_nonce('mxchat_reset_global_usage'); ?>
>

>

$role_name) { $default_limit = ($role_id === 'logged_out') ? '10' : '100'; $default_timeframe = 'daily'; $default_message = __('Rate limit exceeded. Please try again later.', 'mxchat'); $selected_limit = isset($all_options['rate_limits'][$role_id]['limit']) ? (string) $all_options['rate_limits'][$role_id]['limit'] : $default_limit; $selected_timeframe = isset($all_options['rate_limits'][$role_id]['timeframe']) ? $all_options['rate_limits'][$role_id]['timeframe'] : $default_timeframe; $custom_message = isset($all_options['rate_limits'][$role_id]['message']) ? $all_options['rate_limits'][$role_id]['message'] : $default_message; $timeframe_label = isset($timeframes[$selected_timeframe]) ? $timeframes[$selected_timeframe] : $selected_timeframe; // Custom-value handling: if the stored limit is not in the preset list // and not 'unlimited', it's a custom integer. The select shows __custom__ // and the number input below carries the actual value. $is_preset_limit = in_array($selected_limit, $rate_limits, true); $role_select_value = $is_preset_limit ? $selected_limit : '__custom__'; $role_custom_value = (!$is_preset_limit && ctype_digit($selected_limit)) ? $selected_limit : ''; $custom_field_id = 'rate_limits_' . $role_id . '_limit_custom'; ?>
>
'; }