PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.0.5
MxChat – AI Chatbot & Content Generation for WordPress v3.0.5
3.2.21 3.2.20 3.2.19 3.2.18 3.2.17 3.2.16 3.2.15 3.2.14 3.2.12 3.2.13 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 3.2.6 3.2.5 3.2.4 3.2.3 3.2.2 3.2.1 2.0.3 2.0.4 2.0.5 2.0.6 All 152 releases
← All changes | js/mxchat-admin.js +73 -446 3.2.103.0.5 View file →
@@ -408,18 +408,9 @@
408 408
409 409 // Handle real-time range slider value updates
410 410 $autosaveSections.find('input[type="range"]').on('input', function() {
411 411 const value = $(this).val();
412 - const $slider = $(this);
413 - const sliderId = $slider.attr('id');
414 - // Find the corresponding value display span (convention: id_value)
415 - const $valueSpan = $('#' + sliderId + '_value');
416 - if ($valueSpan.length) {
417 - $valueSpan.text(value);
418 - } else {
419 - // Fallback for similarity_threshold which uses threshold_value
420 - $('#threshold_value').text(value);
421 - }
412 + $('#threshold_value').text(value);
422 413 });
423 414
424 415 // Handle all input changes (including range slider)
425 416 // Store pending AJAX requests and debounce timers per field to prevent freezing
@@ -897,27 +888,14 @@
897 888 // Toggle visibility handlers
898 889 function toggleVisibility(selector) {
899 890 $(selector).on('click', function() {
900 891 var inputField = $(this).prev('input');
901 - // Check if this is a CSS-masked field (type="text" with mxchat-api-key-field class)
902 - if (inputField.hasClass('mxchat-api-key-field')) {
903 - // Use CSS class toggle for CSS-masked fields
904 - if (inputField.hasClass('mxchat-show-key')) {
905 - inputField.removeClass('mxchat-show-key');
906 - $(this).text('Show');
907 - } else {
908 - inputField.addClass('mxchat-show-key');
909 - $(this).text('Hide');
910 - }
892 + if (inputField.attr('type') === 'password') {
893 + inputField.attr('type', 'text');
894 + $(this).text('Hide');
911 895 } else {
912 - // Legacy type toggle for password fields
913 - if (inputField.attr('type') === 'password') {
914 - inputField.attr('type', 'text');
915 - $(this).text('Hide');
916 - } else {
917 - inputField.attr('type', 'password');
918 - $(this).text('Show');
919 - }
896 + inputField.attr('type', 'password');
897 + $(this).text('Show');
920 898 }
921 899 });
922 900 }
923 901
@@ -994,9 +972,8 @@
994 972 <button class="mxchat-model-category-btn" data-category="openai">OpenAI</button>
995 973 <button class="mxchat-model-category-btn" data-category="claude">Claude</button>
996 974 <button class="mxchat-model-category-btn" data-category="xai">X.AI</button>
997 975 <button class="mxchat-model-category-btn" data-category="deepseek">DeepSeek</button>
998 - <button class="mxchat-model-category-btn" data-category="custom">Custom / Local</button>
999 976 </div>
1000 977 <div class="mxchat-model-selector-grid" id="mxchat_models_grid"></div>
1001 978 </div>
1002 979 <div class="mxchat-model-selector-modal-footer">
@@ -1011,38 +988,61 @@
1011 988 // MOVE THIS OUTSIDE - Make it a property of the window object so it's accessible globally
1012 989 window.populateModelsGrid = function(filter = '', category = 'all') {
1013 990 const $grid = $('#mxchat_models_grid');
1014 991 $grid.empty();
1015 -
1016 - // Catalog refactor (plan-d14e89): when class-mxchat-model-catalog.php
1017 - // is loaded (via wp_localize_script as mxchatChatModelCatalog), use
1018 - // its data so a single edit there flows to this picker grid. The
1019 - // inline fallback below keeps the picker working if for some reason
1020 - // the localize hasn't run (e.g. legacy admin page bootstrap order).
1021 - const models = (typeof mxchatChatModelCatalog === 'object' && mxchatChatModelCatalog) ? mxchatChatModelCatalog : {
992 +
993 + const models = {
1022 994 openrouter: [
1023 995 { value: 'openrouter', label: 'OpenRouter', description: 'Access 100+ models from multiple providers (add API key to browse)' }
1024 996 ],
1025 997 gemini: [
1026 - { value: 'gemini-3.5-flash', label: 'Gemini 3.5 Flash', description: 'Stable — newest Flash generation, recommended default' },
998 + { value: 'gemini-3-pro-preview', label: 'Gemini 3 Pro', description: 'Most intelligent model - multimodal understanding & agentic' },
999 + { value: 'gemini-3-flash-preview', label: 'Gemini 3 Flash', description: 'Most balanced - speed, scale & frontier intelligence' },
1000 + { value: 'gemini-2.5-pro', label: 'Gemini 2.5 Pro', description: 'Advanced thinking - code, math, STEM & long context' },
1001 + { value: 'gemini-2.5-flash', label: 'Gemini 2.5 Flash', description: 'Best price-performance with thinking capabilities' },
1002 + { value: 'gemini-2.5-flash-lite', label: 'Gemini 2.5 Flash-Lite', description: 'Ultra fast - optimized for cost & high throughput' },
1003 + { value: 'gemini-2.0-flash', label: 'Gemini 2.0 Flash', description: 'Deprecated March 2026' },
1004 + { value: 'gemini-2.0-flash-lite', label: 'Gemini 2.0 Flash-Lite', description: 'Deprecated March 2026' },
1005 + { value: 'gemini-1.5-pro', label: 'Gemini 1.5 Pro', description: 'Deprecated September 2025' },
1006 + { value: 'gemini-1.5-flash', label: 'Gemini 1.5 Flash', description: 'Deprecated September 2025' },
1027 1007 ],
1028 1008 openai: [
1029 - { value: 'gpt-5.1-chat-latest', label: 'GPT-5.1 Chat Latest', description: 'Recommended for most use cases' },
1009 + { value: 'gpt-5.2', label: 'GPT-5.2', description: 'Best general-purpose & agentic model with fast responses' },
1010 + { value: 'gpt-5.1-2025-11-13', label: 'GPT-5.1', description: 'Flagship for coding & agentic tasks with low reasoning (400K context)' },
1011 + { value: 'gpt-5', label: 'GPT-5', description: 'Flagship for coding, reasoning, and agentic tasks across domains' },
1012 + { value: 'gpt-5-mini', label: 'GPT-5 Mini', description: 'Faster, more cost-efficient for well-defined tasks and precise prompts' },
1013 + { value: 'gpt-5-nano', label: 'GPT-5 Nano', description: 'Fastest and cheapest; ideal for summarization and classification' },
1014 + { value: 'gpt-4.1-2025-04-14', label: 'GPT-4.1', description: 'Flagship model for complex tasks' },
1015 + { value: 'gpt-4o', label: 'GPT-4o', description: 'Recommended for most use cases' },
1016 + { value: 'gpt-4o-mini', label: 'GPT-4o Mini', description: 'Fast and lightweight' },
1017 + { value: 'gpt-4-turbo', label: 'GPT-4 Turbo', description: 'High-performance model' },
1018 + { value: 'gpt-4', label: 'GPT-4', description: 'High intelligence model' },
1019 + { value: 'gpt-3.5-turbo', label: 'GPT-3.5 Turbo', description: 'Affordable and fast' },
1030 1020 ],
1031 1021 claude: [
1032 - { value: 'claude-fable-5', label: 'Claude Fable 5', description: 'Latest Flagship — newest and most capable Anthropic model' },
1033 - { value: 'claude-opus-4-8', label: 'Claude Opus 4.8', description: 'Previous flagship — most capable Opus-tier model' },
1034 - { value: 'claude-opus-4-7', label: 'Claude Opus 4.7', description: 'Previous Anthropic flagship model' },
1022 + { value: 'claude-sonnet-4-5-20250929', label: 'Claude Sonnet 4.5', description: 'Best for complex agents and coding' },
1023 + { value: 'claude-opus-4-1-20250805', label: 'Claude Opus 4.1', description: 'Exceptional for specialized complex tasks' },
1024 + { value: 'claude-haiku-4-5-20251001', label: 'Claude Haiku 4.5', description: 'Fastest and most intelligent Haiku' },
1025 + { value: 'claude-opus-4-20250514', label: 'Claude 4 Opus', description: 'Most capable Claude model' },
1026 + { value: 'claude-sonnet-4-20250514', label: 'Claude 4 Sonnet', description: 'High performance' },
1027 + { value: 'claude-3-7-sonnet-20250219', label: 'Claude 3.7 Sonnet', description: 'High intelligence' },
1028 + { value: 'claude-3-opus-20240229', label: 'Claude 3 Opus', description: 'Highly complex tasks' },
1029 + { value: 'claude-3-sonnet-20240229', label: 'Claude 3 Sonnet', description: 'Balanced performance' },
1030 + { value: 'claude-3-haiku-20240307', label: 'Claude 3 Haiku', description: 'Fastest Claude model' },
1035 1031 ],
1036 1032 xai: [
1037 - { value: 'grok-4-0709', label: 'Grok 4', description: 'Latest flagship model' },
1033 + { value: 'grok-4-1-fast-reasoning', label: 'Grok 4.1 Fast (Reasoning)', description: '2M context window and reasoning' },
1034 + { value: 'grok-4-1-fast-non-reasoning', label: 'Grok 4.1 Fast (Non-Reasoning)', description: '2M context window and faster responses' },
1035 + { value: 'grok-4-0709', label: 'Grok 4', description: 'Latest flagship model - unparalleled performance in natural language, math and reasoning' },
1036 + { value: 'grok-3-beta', label: 'Grok-3', description: 'Powerful model with 131K context' },
1037 + { value: 'grok-3-fast-beta', label: 'Grok-3 Fast', description: 'High performance with faster responses' },
1038 + { value: 'grok-3-mini-beta', label: 'Grok-3 Mini', description: 'Affordable model with good performance' },
1039 + { value: 'grok-3-mini-fast-beta', label: 'Grok-3 Mini Fast', description: 'Quick and cost-effective' },
1040 + { value: 'grok-2', label: 'Grok 2', description: 'Latest X.AI model' },
1038 1041 ],
1039 1042 deepseek: [
1040 1043 { value: 'deepseek-chat', label: 'DeepSeek-V3', description: 'Advanced AI assistant' },
1041 1044 ],
1042 - custom: [
1043 - { value: 'custom-provider', label: 'Custom Provider', description: 'OpenAI-compatible local LLM — configure in API Keys tab' },
1044 - ],
1045 1045 };
1046 1046
1047 1047 let allModels = [];
1048 1048 Object.keys(models).forEach(key => {
@@ -1083,9 +1083,8 @@
1083 1083 if (modelValue.startsWith('gemini-')) return '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 48 48" class="mxchat-model-icon-gemini"><defs><path id="a" d="M44.5 20H24v8.5h11.8C34.7 33.9 30.1 37 24 37c-7.2 0-13-5.8-13-13s5.8-13 13-13c3.1 0 5.9 1.1 8.1 2.9l6.4-6.4C34.6 4.1 29.6 2 24 2 11.8 2 2 11.8 2 24s9.8 22 22 22c11 0 21-8 21-22 0-1.3-.2-2.7-.5-4z"></path></defs><clipPath id="b"><use xlink:href="#a" overflow="visible"></use></clipPath><path clip-path="url(#b)" fill="#FBBC05" d="M0 37V11l17 13z"></path><path clip-path="url(#b)" fill="#EA4335" d="M0 11l17 13 7-6.1L48 14V0H0z"></path><path clip-path="url(#b)" fill="#34A853" d="M0 37l30-23 7.9 1L48 0v48H0z"></path><path clip-path="url(#b)" fill="#4285F4" d="M48 48L17 24l-4-3 35-10z"></path></svg>';
1084 1084 if (modelValue.startsWith('gpt-')) return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 320" class="mxchat-model-icon-openai"><path fill="currentColor" d="M297 131a80.6 80.6 0 0 0-93.7-104.2 80.6 80.6 0 0 0-137 29A80.6 80.6 0 0 0 23 189a80.6 80.6 0 0 0 93.7 104.2 80.6 80.6 0 0 0 137-29A80.7 80.7 0 0 0 297.1 131zM176.9 299c-14 .1-27.6-4.8-38.4-13.8l1.9-1 63.7-36.9c3.3-1.8 5.3-5.3 5.2-9v-89.9l27 15.6c.3.1.4.4.5.7v74.4a60 60 0 0 1-60 60zM47.9 244a59.7 59.7 0 0 1-7.1-40.1l1.9 1.1 63.7 36.8c3.2 1.9 7.2 1.9 10.5 0l77.8-45V228c0 .3-.2.6-.4.8L129.9 266a60 60 0 0 1-82-22zM31.2 105c7-12.2 18-21.5 31.2-26.3v75.8c0 3.7 2 7.2 5.2 9l77.8 45-27 15.5a1 1 0 0 1-.9 0L53.1 187a60 60 0 0 1-22-82zm221.2 51.5-77.8-45 27-15.5a1 1 0 0 1 .9 0l64.4 37.1a60 60 0 0 1-9.3 108.2v-75.8c0-3.7-2-7.2-5.2-9zm26.8-40.4-1.9-1.1-63.7-36.8a10.4 10.4 0 0 0-10.5 0L125.4 123V92c0-.3 0-.6.3-.8L190.1 54a60 60 0 0 1 89.1 62.1zm-168.5 55.4-27-15.5a1 1 0 0 1-.4-.7V80.9a60 60 0 0 1 98.3-46.1l-1.9 1L116 72.8a10.3 10.3 0 0 0-5.2 9v89.8zm14.6-31.5 34.7-20 34.6 20v40L160 200l-34.7-20z"></path></svg>';
1085 1085 if (modelValue.startsWith('claude-')) return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 176" fill="none" class="mxchat-model-icon-claude"><path fill="currentColor" d="m147.487 0l70.081 175.78H256L185.919 0zM66.183 106.221l23.98-61.774l23.98 61.774zM70.07 0L0 175.78h39.18l14.33-36.914h73.308l14.328 36.914h39.179L110.255 0z"></path></svg>';
1086 1086 if (modelValue.startsWith('grok-')) return '<svg fill="currentColor" fill-rule="evenodd" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="mxchat-model-icon-xai"><path d="M6.469 8.776L16.512 23h-4.464L2.005 8.776H6.47zm-.004 7.9l2.233 3.164L6.467 23H2l4.465-6.324zM22 2.582V23h-3.659V7.764L22 2.582zM22 1l-9.952 14.095-2.233-3.163L17.533 1H22z"></path></svg>';
1087 - if (modelValue === 'custom-provider' || modelValue.startsWith('custom-')) return '<span class="dashicons dashicons-admin-site-alt3" style="font-size: 24px; color: #2c8a3d;"></span>';
1088 1087 if (modelValue.startsWith('deepseek-')) return '<svg height="1em" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg" class="mxchat-model-icon-deepseek"><path d="M23.748 4.482c-.254-.124-.364.113-.512.234-.051.039-.094.09-.137.136-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.248-1.247-1.548-.352-.156-.708-.311-.955-.65-.172-.241-.219-.51-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.093.172.187.129.323-.082.28-.18.552-.266.833-.055.179-.137.217-.329.14a5.526 5.526 0 01-1.736-1.18c-.857-.828-1.631-1.742-2.597-2.458a11.365 11.365 0 00-.689-.471c-.985-.957.13-1.743.388-1.836.27-.098.093-.432-.779-.428-.872.004-1.67.295-2.687.684a3.055 3.055 0 01-.465.137 9.597 9.597 0 00-2.883-.102c-1.885.21-3.39 1.102-4.497 2.623C.082 8.606-.231 10.684.152 12.85c.403 2.284 1.569 4.175 3.36 5.653 1.858 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.133-.284 4.994-1.86.47.234.962.327 1.78.397.63.059 1.236-.03 1.705-.128.735-.156.684-.837.419-.961-2.155-1.004-1.682-.595-2.113-.926 1.096-1.296 2.746-2.642 3.392-7.003.05-.347.007-.565 0-.845-.004-.17.035-.237.23-.256a4.173 4.173 0 001.545-.475c1.396-.763 1.96-2.015 2.093-3.517.02-.23-.004-.467-.247-.588zM11.581 18c-2.089-1.642-3.102-2.183-3.52-2.16-.392.024-.321.471-.235.763.09.288.207.486.371.739.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.167-1.361-.802-2.5-1.86-3.301-3.307-.774-1.393-1.224-2.887-1.298-4.482-.02-.386.093-.522.477-.592a4.696 4.696 0 011.529-.039c2.132.312 3.946 1.265 5.468 2.774.868.86 1.525 1.887 2.202 2.891.72 1.066 1.494 2.082 2.48 2.914.348.292.625.514.891.677-.802.09-2.14.11-3.054-.614zm1-6.44a.306.306 0 01.415-.287.302.302 0 01.2.288.306.306 0 01-.31.307.303.303 0 01-.304-.308zm3.11 1.596c-.2.081-.399.151-.59.16a1.245 1.245 0 01-.798-.254c-.274-.23-.47-.358-.552-.758a1.73 1.73 0 01.016-.588c.07-.327-.008-.537-.239-.727-.187-.156-.426-.199-.688-.199a.559.559 0 01-.254-.078c-.11-.054-.2-.19-.114-.358.028-.054.16-.186.192-.21.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.391.451.462.576.685.914.176.265.336.537.445.848.067.195-.019.354-.25.452z" fill="currentColor"></path></svg>';
1089 1088 return '<span class="dashicons dashicons-admin-generic mxchat-model-icon-generic"></span>';
1090 1089 }
1091 1090
@@ -1598,71 +1597,8 @@
1598 1597 });
1599 1598
1600 1599 }
1601 1600
1602 -// 3.2.3: Confirmation dialog shown when the user attempts to switch embedding
1603 -// models after they've already embedded content with a different model. Mixing
1604 -// embeddings from two models silently breaks similarity matching.
1605 -function showEmbeddingSwitchWarning(data, newValue, onChoice) {
1606 - $('#mxchat_embedding_switch_warning').remove();
1607 -
1608 - const dimsBlock = data.dims_differ ? `
1609 - <div class="mxchat-embed-warn-dims">
1610 - <strong>Dimension mismatch:</strong>
1611 - Existing vectors are ${data.active_dims}-dimensional, but ${data.new_label}
1612 - produces ${data.new_dims}-dimensional vectors.
1613 - If you use Pinecone, your index will reject queries entirely until you re-embed.
1614 - </div>` : '';
1615 -
1616 - const $modal = $(`
1617 - <div id="mxchat_embedding_switch_warning" class="mxchat-embed-warn-overlay">
1618 - <div class="mxchat-embed-warn-dialog">
1619 - <div class="mxchat-embed-warn-header">
1620 - <span class="mxchat-embed-warn-icon">⚠️</span>
1621 - <h3>Switching embedding models will break similarity matching</h3>
1622 - </div>
1623 - <div class="mxchat-embed-warn-body">
1624 - <p>
1625 - Your knowledge base and actions are currently embedded with
1626 - <code>${data.active_label}</code>. Switching to
1627 - <code>${data.new_label}</code> means new queries are embedded with
1628 - a different model than the stored vectors — the chatbot will return
1629 - inaccurate results or fail to match anything.
1630 - </p>
1631 - ${dimsBlock}
1632 - <p><strong>To switch safely:</strong></p>
1633 - <ol>
1634 - <li>Go to <em>Knowledge Base</em> and delete all existing entries.</li>
1635 - <li>Go to <em>Actions</em> and delete all existing actions.</li>
1636 - <li>Come back here, switch the model, then re-import your content and re-add your actions.</li>
1637 - </ol>
1638 - </div>
1639 - <div class="mxchat-embed-warn-footer">
1640 - <button type="button" class="button button-secondary" id="mxchat_embed_warn_cancel">Cancel — keep ${data.active_label}</button>
1641 - <button type="button" class="button button-primary mxchat-embed-warn-danger" id="mxchat_embed_warn_continue">Switch anyway (I'll handle it)</button>
1642 - </div>
1643 - </div>
1644 - </div>
1645 - `);
1646 -
1647 - $('body').append($modal);
1648 -
1649 - let resolved = false;
1650 - function resolve(confirmed) {
1651 - if (resolved) return;
1652 - resolved = true;
1653 - $modal.remove();
1654 - if (typeof onChoice === 'function') onChoice(confirmed);
1655 - }
1656 -
1657 - // Click on the overlay background dismisses (cancel)
1658 - $modal.on('click', function(e) {
1659 - if (e.target === $modal[0]) resolve(false);
1660 - });
1661 - $modal.find('#mxchat_embed_warn_cancel').on('click', function() { resolve(false); });
1662 - $modal.find('#mxchat_embed_warn_continue').on('click', function() { resolve(true); });
1663 -}
1664 -
1665 1601 // Embedding model selector - completely separate from chat model selector
1666 1602 function setupMxChatEmbeddingModelSelector() {
1667 1603 const $embeddingModelSelect = $('#embedding_model');
1668 1604
@@ -1843,53 +1779,25 @@
1843 1779 const activeCategory = $('.mxchat-embedding-model-selector-categories .mxchat-embedding-model-category-btn.active').data('category');
1844 1780 populateEmbeddingModelsGrid(searchTerm, activeCategory);
1845 1781 });
1846 1782
1847 - // 3.2.3: Commit a model change — same as the original click handler, just
1848 - // factored out so it can be invoked from both the safe path and the
1849 - // post-confirmation path.
1850 - function commitEmbeddingModelChange(modelValue) {
1783 + // Use a direct selector to avoid conflicts with other card elements
1784 + $(document).on('click.embeddingModelSelector', '.mxchat-embedding-model-selector-grid .mxchat-embedding-model-selector-card', function(e) {
1785 + e.stopPropagation(); // Prevent event bubbling
1786 + const modelValue = $(this).data('value');
1787 +
1788 + // Important: Only update this specific select element
1851 1789 $embeddingModelSelect.val(modelValue);
1790 +
1791 + // Manually trigger change only on this element
1852 1792 const changeEvent = new Event('change', { bubbles: true });
1853 1793 $embeddingModelSelect[0].dispatchEvent(changeEvent);
1794 +
1795 + // Update button text
1854 1796 updateButtonText();
1797 +
1798 + // Hide modal
1855 1799 $('#' + embeddingModalId).hide();
1856 - }
1857 -
1858 - // Use a direct selector to avoid conflicts with other card elements
1859 - $(document).on('click.embeddingModelSelector', '.mxchat-embedding-model-selector-grid .mxchat-embedding-model-selector-card', function(e) {
1860 - e.stopPropagation(); // Prevent event bubbling
1861 - const modelValue = $(this).data('value');
1862 - const previousValue = $embeddingModelSelect.val();
1863 -
1864 - // No-op if the user clicked the already-selected card
1865 - if (modelValue === previousValue) {
1866 - $('#' + embeddingModalId).hide();
1867 - return;
1868 - }
1869 -
1870 - // 3.2.3: Preflight — if the user has content embedded with a different
1871 - // model, surface a confirmation dialog before committing the switch.
1872 - $.post(ajaxurl, {
1873 - action: 'mxchat_check_embedding_switch',
1874 - security: (typeof mxchatAdmin !== 'undefined' ? mxchatAdmin.nonce : ''),
1875 - new_model: modelValue
1876 - }).done(function(resp) {
1877 - if (resp && resp.success && resp.data && resp.data.is_mismatch) {
1878 - showEmbeddingSwitchWarning(resp.data, modelValue, function(confirmed) {
1879 - if (confirmed) {
1880 - commitEmbeddingModelChange(modelValue);
1881 - }
1882 - });
1883 - } else {
1884 - commitEmbeddingModelChange(modelValue);
1885 - }
1886 - }).fail(function() {
1887 - // If preflight fails, fall back to the original behavior so a network
1888 - // hiccup doesn't block legitimate model switches.
1889 - commitEmbeddingModelChange(modelValue);
1890 - });
1891 - return;
1892 1800 });
1893 1801
1894 1802 // Close modal when clicking outside - use namespaced events
1895 1803 $(window).on('click.embeddingModelSelector', function(event) {
@@ -2187,14 +2095,24 @@
2187 2095 const actionLabel = this.dataset.label;
2188 2096 const actionIcon = this.querySelector('.dashicons').getAttribute('class').replace('dashicons dashicons-', '');
2189 2097 const actionDescription = this.querySelector('p').textContent;
2190 2098
2191 - // Check if this is a promotional card (add-on not installed)
2192 - const isPromo = this.dataset.promo === 'true';
2193 -
2194 - if (isPromo || (addonName && !isInstalled)) {
2195 - // Add-on required but not installed — show informational notice
2196 - const addonDisplayName = this.querySelector('.mxchat-addon-info')?.textContent?.replace('Requires ', '').replace('— Get Add-on', '').trim() || addonName + ' Add-on';
2099 + // Pro check using the proper detection method
2100 + const proIsActivated = typeof mxchatAdmin !== 'undefined' &&
2101 + (mxchatAdmin.is_activated === '1' ||
2102 + mxchatAdmin.is_activated === 'true' ||
2103 + mxchatAdmin.is_activated === true);
2104 +
2105 + // Handle different states
2106 + if (isPro && !proIsActivated) {
2107 + // Pro feature but no Pro license
2108 + showProFeatureNotice();
2109 + return;
2110 + }
2111 +
2112 + if (addonName && !isInstalled) {
2113 + // Add-on required but not installed
2114 + const addonDisplayName = this.querySelector('.mxchat-addon-info')?.textContent?.replace('Requires ', '') || addonName + ' Add-on';
2197 2115 showAddonRequiredNotice(addonDisplayName);
2198 2116 return;
2199 2117 }
2200 2118
@@ -3356,296 +3274,5 @@
3356 3274 var div = document.createElement('div');
3357 3275 div.appendChild(document.createTextNode(text));
3358 3276 return div.innerHTML;
3359 3277 }
3360 -
3361 - // ========================================
3362 - // DEBUG & OPTIMIZATION TOOLS
3363 - // ========================================
3364 -
3365 - // Debug Mode Toggle
3366 - $('#mxchat_debug_mode').on('change', function() {
3367 - var $toggle = $(this);
3368 - var enabled = $toggle.is(':checked') ? 'on' : 'off';
3369 - var $label = $toggle.closest('label');
3370 -
3371 - // Add loading indicator next to the toggle label
3372 - var $indicator = $label.find('.mxchat-save-indicator');
3373 - if ($indicator.length === 0) {
3374 - $indicator = $('<span class="mxchat-save-indicator" style="margin-left: 10px;"></span>');
3375 - $label.append($indicator);
3376 - }
3377 - $indicator.html('<span class="mxchat-saving-spinner"></span>').show();
3378 -
3379 - $.ajax({
3380 - url: ajaxurl,
3381 - type: 'POST',
3382 - data: {
3383 - action: 'mxchat_toggle_debug_mode',
3384 - enabled: enabled,
3385 - _ajax_nonce: mxchatAdmin.setting_nonce
3386 - },
3387 - success: function(response) {
3388 - if (response.success) {
3389 - // Show success indicator
3390 - $indicator.html('<span class="mxchat-save-success">✓</span>');
3391 - setTimeout(function() {
3392 - $indicator.fadeOut(300);
3393 - }, 2000);
3394 -
3395 - // Always refresh the log after toggling
3396 - refreshDebugLog();
3397 - } else {
3398 - $indicator.html('<span class="mxchat-save-error">✗</span>');
3399 - alert(response.data.message || 'Error toggling debug mode');
3400 - $toggle.prop('checked', !$toggle.is(':checked'));
3401 - }
3402 - },
3403 - error: function() {
3404 - $indicator.html('<span class="mxchat-save-error">✗</span>');
3405 - alert('Error toggling debug mode');
3406 - $toggle.prop('checked', !$toggle.is(':checked'));
3407 - }
3408 - });
3409 - });
3410 -
3411 - // Refresh Debug Log
3412 - function refreshDebugLog() {
3413 - var $container = $('#mxchat-debug-log');
3414 - var $countBadge = $('#mxchat-log-count');
3415 -
3416 - $.ajax({
3417 - url: ajaxurl,
3418 - type: 'POST',
3419 - data: {
3420 - action: 'mxchat_get_debug_log',
3421 - _ajax_nonce: mxchatAdmin.setting_nonce
3422 - },
3423 - success: function(response) {
3424 - if (response.success) {
3425 - var log = response.data.log;
3426 - var count = response.data.count;
3427 -
3428 - if (count > 0) {
3429 - $countBadge.text(count + ' entries').show();
3430 - var html = '<div class="mxchat-debug-log-entries">';
3431 - log.forEach(function(entry) {
3432 - var typeClass = 'mxchat-log-type-' + entry.type;
3433 - var typeLabel = entry.type.replace(/_/g, ' ').toUpperCase();
3434 - html += '<div class="mxchat-debug-log-entry ' + typeClass + '">';
3435 - html += '<div class="mxchat-log-header">';
3436 - html += '<span class="mxchat-log-type">' + escapeHtml(typeLabel) + '</span>';
3437 - html += '<span class="mxchat-log-time">' + escapeHtml(entry.time) + '</span>';
3438 - html += '</div>';
3439 - html += '<div class="mxchat-log-message">' + escapeHtml(entry.message) + '</div>';
3440 - if (entry.data) {
3441 - html += '<div class="mxchat-log-data">' + escapeHtml(JSON.stringify(entry.data, null, 2)) + '</div>';
3442 - }
3443 - html += '</div>';
3444 - });
3445 - html += '</div>';
3446 - $container.html(html);
3447 - } else {
3448 - $countBadge.hide();
3449 - $container.html(
3450 - '<div class="mxchat-debug-log-empty">' +
3451 - '<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" style="opacity: 0.3;"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>' +
3452 - '<p>No log entries yet. Enable debug mode to start logging.</p>' +
3453 - '</div>'
3454 - );
3455 - }
3456 - }
3457 - }
3458 - });
3459 - }
3460 -
3461 - // Load debug log on page load
3462 - if ($('#mxchat-debug-log').length) {
3463 - refreshDebugLog();
3464 - }
3465 -
3466 - // Refresh Log Button
3467 - $('#mxchat-refresh-log').on('click', function() {
3468 - var $btn = $(this);
3469 - $btn.prop('disabled', true);
3470 - refreshDebugLog();
3471 - setTimeout(function() {
3472 - $btn.prop('disabled', false);
3473 - }, 500);
3474 - });
3475 -
3476 - // Clear Log Button
3477 - $('#mxchat-clear-log').on('click', function() {
3478 - if (!confirm('Are you sure you want to clear the debug log?')) {
3479 - return;
3480 - }
3481 -
3482 - var $btn = $(this);
3483 - $btn.prop('disabled', true);
3484 -
3485 - $.ajax({
3486 - url: ajaxurl,
3487 - type: 'POST',
3488 - data: {
3489 - action: 'mxchat_clear_debug_log',
3490 - _ajax_nonce: mxchatAdmin.setting_nonce
3491 - },
3492 - success: function(response) {
3493 - if (response.success) {
3494 - refreshDebugLog();
3495 - } else {
3496 - alert(response.data.message || 'Error clearing log');
3497 - }
3498 - },
3499 - error: function() {
3500 - alert('Error clearing log');
3501 - },
3502 - complete: function() {
3503 - $btn.prop('disabled', false);
3504 - }
3505 - });
3506 - });
3507 -
3508 - // Export Settings Button
3509 - $('#mxchat-export-settings').on('click', function() {
3510 - var $btn = $(this);
3511 - var originalHtml = $btn.html();
3512 - $btn.prop('disabled', true).html(
3513 - '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="animation: spin 1s linear infinite;"><circle cx="12" cy="12" r="10" stroke-dasharray="32" stroke-dashoffset="12"/></svg> Exporting...'
3514 - );
3515 -
3516 - $.ajax({
3517 - url: ajaxurl,
3518 - type: 'POST',
3519 - data: {
3520 - action: 'mxchat_export_settings',
3521 - _ajax_nonce: mxchatAdmin.setting_nonce
3522 - },
3523 - success: function(response) {
3524 - if (response.success) {
3525 - // Create and download the JSON file
3526 - var dataStr = JSON.stringify(response.data.settings, null, 2);
3527 - var blob = new Blob([dataStr], { type: 'application/json' });
3528 - var url = URL.createObjectURL(blob);
3529 - var a = document.createElement('a');
3530 - a.href = url;
3531 - a.download = response.data.filename;
3532 - document.body.appendChild(a);
3533 - a.click();
3534 - document.body.removeChild(a);
3535 - URL.revokeObjectURL(url);
3536 - } else {
3537 - alert(response.data.message || 'Error exporting settings');
3538 - }
3539 - },
3540 - error: function() {
3541 - alert('Error exporting settings');
3542 - },
3543 - complete: function() {
3544 - $btn.prop('disabled', false).html(originalHtml);
3545 - }
3546 - });
3547 - });
3548 -
3549 - // Reset Settings Modal
3550 - var $resetModal = $('#mxchat-reset-modal');
3551 - var $resetConfirmInput = $('#mxchat-reset-confirmation');
3552 - var $resetConfirmBtn = $('#mxchat-reset-confirm');
3553 -
3554 - $('#mxchat-reset-settings').on('click', function() {
3555 - $resetModal.fadeIn(200);
3556 - $resetConfirmInput.val('').focus();
3557 - $resetConfirmBtn.prop('disabled', true);
3558 - });
3559 -
3560 - $('#mxchat-reset-modal-close, #mxchat-reset-cancel, .mxch-modal-backdrop').on('click', function() {
3561 - $resetModal.fadeOut(200);
3562 - });
3563 -
3564 - $resetConfirmInput.on('input', function() {
3565 - var value = $(this).val().toUpperCase();
3566 - $resetConfirmBtn.prop('disabled', value !== 'RESET');
3567 - });
3568 -
3569 - $resetConfirmBtn.on('click', function() {
3570 - var $btn = $(this);
3571 - var confirmation = $resetConfirmInput.val();
3572 -
3573 - $btn.prop('disabled', true).text('Resetting...');
3574 -
3575 - $.ajax({
3576 - url: ajaxurl,
3577 - type: 'POST',
3578 - data: {
3579 - action: 'mxchat_reset_all_settings',
3580 - confirmation: confirmation,
3581 - _ajax_nonce: mxchatAdmin.setting_nonce
3582 - },
3583 - success: function(response) {
3584 - if (response.success) {
3585 - alert(response.data.message);
3586 - window.location.reload();
3587 - } else {
3588 - alert(response.data.message || 'Error resetting settings');
3589 - $btn.prop('disabled', false).text('Reset All Settings');
3590 - }
3591 - },
3592 - error: function() {
3593 - alert('Error resetting settings');
3594 - $btn.prop('disabled', false).text('Reset All Settings');
3595 - }
3596 - });
3597 - });
3598 -
3599 - // Load debug log on page load if we're on the optimization section
3600 - if ($('#optimization').hasClass('active') || window.location.hash === '#optimization') {
3601 - refreshDebugLog();
3602 - }
3603 -
3604 - // Also refresh when switching to optimization tab
3605 - $(document).on('click', '[data-section="optimization"]', function() {
3606 - setTimeout(refreshDebugLog, 100);
3607 - });
3608 -});
3609 -
3610 -// Global rate-limit usage: "Reset counter" button (plan-mxchat-20260603-e9b3f9)
3611 -jQuery(document).ready(function($) {
3612 - var $usage = $('#mxch-global-usage');
3613 - if (!$usage.length) {
3614 - return;
3615 - }
3616 -
3617 - $('#mxch-global-usage-reset').on('click', function() {
3618 - var $btn = $(this);
3619 - if (!window.confirm('Reset the global usage counter to zero now? This clears how many messages have been used in the current window.')) {
3620 - return;
3621 - }
3622 -
3623 - var original = $btn.text();
3624 - $btn.prop('disabled', true).text('Resetting…');
3625 -
3626 - $.ajax({
3627 - url: mxchatAdmin.ajax_url,
3628 - type: 'POST',
3629 - data: {
3630 - action: 'mxchat_reset_global_rate_limit',
3631 - bot_id: $usage.data('bot-id') || 'default',
3632 - _ajax_nonce: $usage.data('reset-nonce')
3633 - },
3634 - success: function(response) {
3635 - $btn.prop('disabled', false).text(original);
3636 - if (response && response.success) {
3637 - $('#mxch-global-usage-fill').css('width', (response.data.pct || 0) + '%');
3638 - if (response.data.text) {
3639 - $('#mxch-global-usage-text').text(response.data.text);
3640 - }
3641 - } else {
3642 - window.alert((response && response.data && response.data.message) || 'Reset failed. Please try again.');
3643 - }
3644 - },
3645 - error: function() {
3646 - $btn.prop('disabled', false).text(original);
3647 - window.alert('Reset failed. Please try again.');
3648 - }
3649 - });
3650 - });
3651 3278 });