PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 2.6.2
MxChat – AI Chatbot & Content Generation for WordPress v2.6.2
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 | includes/class-mxchat-admin.php +2895 -3840 3.2.42.6.2 View file →
@@ -8,24 +8,8 @@
8 8 private $chat_count;
9 9 private $is_activated;
10 10 private $knowledge_manager;
11 11
12 - /**
13 - * Get whether the license is activated
14 - * @return bool
15 - */
16 - public function is_activated() {
17 - return $this->is_activated;
18 - }
19 -
20 - /**
21 - * Get plugin options
22 - * @return array
23 - */
24 - public function get_options() {
25 - return $this->options;
26 - }
27 -
28 12 public function __construct($knowledge_manager = null) {
29 13 $this->options = get_option('mxchat_options');
30 14 $this->chat_count = get_option('mxchat_chat_count', 0);
31 15 $this->is_activated = $this->is_license_active();
@@ -44,9 +28,8 @@
44 28 add_action('wp_ajax_mxchat_delete_chat_history', array($this, 'mxchat_delete_chat_history'));
45 29 add_action('admin_post_mxchat_delete_prompt', array($this, 'mxchat_handle_delete_prompt'));
46 30 add_action('wp_ajax_mxchat_fetch_chat_history', array($this, 'mxchat_fetch_chat_history'));
47 31 add_action('wp_ajax_nopriv_mxchat_fetch_chat_history', array($this, 'mxchat_fetch_chat_history'));
48 - add_action('wp_ajax_mxchat_fetch_conversation', array($this, 'mxchat_fetch_conversation'));
49 32 add_action('wp_footer', array($this, 'mxchat_append_chatbot_to_body'));
50 33 add_action('admin_head-mxchat-prompts', array($this, 'mxchat_enqueue_admin_assets'));
51 34 add_action('admin_head-toplevel_page_mxchat-max', array($this, 'mxchat_enqueue_admin_assets'));
52 35 add_action('admin_notices', array($this, 'mxchat_display_admin_notice'));
@@ -54,191 +37,30 @@
54 37 add_action('admin_post_mxchat_add_intent', array($this, 'mxchat_handle_add_intent'));
55 38 add_action('admin_post_mxchat_delete_intent', array($this, 'mxchat_handle_delete_intent'));
56 39 add_action('admin_post_mxchat_edit_intent', array($this, 'mxchat_handle_edit_intent'));
57 40 add_action('wp_ajax_mxchat_export_transcripts', array($this, 'export_chat_transcripts'));
58 -
59 - // Leads tab (inside Transcripts)
60 - add_action('wp_ajax_mxchat_fetch_leads', array($this, 'mxchat_fetch_leads'));
61 - add_action('wp_ajax_mxchat_delete_leads', array($this, 'mxchat_delete_leads'));
62 - add_action('wp_ajax_mxchat_export_leads', array($this, 'mxchat_export_leads'));
63 -
64 41 add_action('admin_init', array($this, 'mxchat_transcripts_page_init'));
65 42 add_action('wp_ajax_dismiss_live_agent_notice', array($this, 'dismiss_live_agent_notice'));
66 - add_action('wp_ajax_dismiss_theme_migration_notice', array($this, 'dismiss_theme_migration_notice'));
67 43 add_action('mxchat_cleanup_old_transcripts', array($this, 'cleanup_old_transcripts'));
68 44
69 45 add_action('admin_init', array($this, 'register_pinecone_settings'));
70 - add_action('admin_init', array($this, 'register_openai_vectorstore_settings'));
71 46
72 47 add_action('admin_notices', array($this, 'display_admin_notices'));
73 48
74 49 add_action('wp_ajax_mxchat_test_streaming_actual', [$this, 'mxchat_handle_test_streaming_actual']);
75 50 add_action('wp_ajax_mxchat_test_streaming', [$this, 'mxchat_handle_test_streaming']); // Keep existing as fallback
76 - add_action('wp_ajax_mxchat_test_vectorstore_connection', array($this, 'mxchat_test_vectorstore_connection'));
77 51
78 52 add_action('wp_ajax_mxchat_save_selected_bot', array($this, 'mxchat_save_selected_bot'));
79 53 add_action('wp_ajax_mxchat_fetch_openrouter_models', array($this, 'fetch_openrouter_models'));
80 54 add_action('wp_ajax_mxchat_get_rag_context', array($this, 'mxchat_get_rag_context'));
81 -
82 - // Actions page AJAX handlers
83 - add_action('wp_ajax_mxchat_fetch_actions_list', array($this, 'mxchat_fetch_actions_list'));
84 - add_action('wp_ajax_mxchat_toggle_action_status', array($this, 'mxchat_toggle_action_status'));
85 - add_action('wp_ajax_mxchat_bulk_delete_actions', array($this, 'mxchat_bulk_delete_actions'));
86 - add_action('wp_ajax_mxchat_add_intent_ajax', array($this, 'mxchat_add_intent_ajax'));
87 - add_action('wp_ajax_mxchat_edit_intent_ajax', array($this, 'mxchat_edit_intent_ajax'));
88 - add_action('wp_ajax_mxchat_delete_intent_ajax', array($this, 'mxchat_delete_intent_ajax'));
89 - add_action('wp_ajax_mxchat_add_phrase', array($this, 'mxchat_add_phrase_ajax'));
90 - add_action('wp_ajax_mxchat_delete_phrase', array($this, 'mxchat_delete_phrase_ajax'));
91 - add_action('wp_ajax_mxchat_get_phrases', array($this, 'mxchat_get_phrases_ajax'));
92 - add_action('wp_ajax_mxchat_delete_legacy_phrases', array($this, 'mxchat_delete_legacy_phrases_ajax'));
93 -
94 - // Slack test connection
95 - add_action('wp_ajax_mxchat_test_slack_connection', array($this, 'mxchat_test_slack_connection'));
96 -
97 - // Translation handlers
98 - add_action('wp_ajax_mxchat_translate_messages', array($this, 'mxchat_translate_messages'));
99 - add_action('wp_ajax_mxchat_get_transcript_translation', array($this, 'mxchat_get_transcript_translation'));
100 -
101 - // 3.2.3: Embedding model switch protection
102 - add_action('wp_ajax_mxchat_check_embedding_switch', array($this, 'mxchat_check_embedding_switch_ajax'));
103 - add_action('wp_ajax_mxchat_dismiss_embedding_mismatch', array($this, 'mxchat_dismiss_embedding_mismatch_ajax'));
104 - add_action('admin_notices', array($this, 'mxchat_embedding_mismatch_notice'));
105 55 }
106 56
107 - /**
108 - * 3.2.3: Preflight check before allowing the embedding model dropdown to
109 - * switch. Pure option comparison — no counts, no DB queries beyond the
110 - * cached options. The dialog is shown whenever the user has previously
111 - * embedded with a different model than the one they're switching to.
112 - */
113 - public function mxchat_check_embedding_switch_ajax() {
114 - check_ajax_referer('mxchat_admin_nonce', 'security');
115 - if (!current_user_can('manage_options')) {
116 - wp_send_json_error(__('Unauthorized', 'mxchat'));
117 - }
118 -
119 - $new_model = isset($_POST['new_model']) ? sanitize_text_field(wp_unslash($_POST['new_model'])) : '';
120 - $active_model = MxChat_Utils::get_active_embedding_model();
121 -
122 - $is_mismatch = !empty($active_model) && !empty($new_model) && $active_model !== $new_model;
123 -
124 - $active_dims = MxChat_Utils::embedding_model_dimensions($active_model);
125 - $new_dims = MxChat_Utils::embedding_model_dimensions($new_model);
126 -
127 - wp_send_json_success(array(
128 - 'is_mismatch' => $is_mismatch,
129 - 'active_model' => $active_model,
130 - 'active_label' => MxChat_Utils::embedding_model_label($active_model),
131 - 'new_model' => $new_model,
132 - 'new_label' => MxChat_Utils::embedding_model_label($new_model),
133 - 'dims_differ' => ($active_dims > 0 && $new_dims > 0 && $active_dims !== $new_dims),
134 - 'active_dims' => $active_dims,
135 - 'new_dims' => $new_dims,
136 - ));
137 - }
138 -
139 - /**
140 - * 3.2.3: Dismiss the persistent mismatch banner. Tied to the active+selected
141 - * pair so the banner reappears on the next switch event.
142 - */
143 - public function mxchat_dismiss_embedding_mismatch_ajax() {
144 - check_ajax_referer('mxchat_admin_nonce', 'security');
145 - if (!current_user_can('manage_options')) {
146 - wp_send_json_error(__('Unauthorized', 'mxchat'));
147 - }
148 -
149 - $options = get_option('mxchat_options', array());
150 - $selected = $options['embedding_model'] ?? '';
151 - $active = MxChat_Utils::get_active_embedding_model();
152 - update_option('mxchat_dismissed_embedding_mismatch', $active . '|' . $selected, false);
153 - wp_send_json_success();
154 - }
155 -
156 - /**
157 - * 3.2.3: Persistent admin banner shown whenever the active embedding model
158 - * (last used to actually embed something) differs from the currently
159 - * selected model. Pure option comparison — no DB queries on every page
160 - * load. The banner auto-clears once both match again, i.e. after a delete
161 - * + re-embed cycle.
162 - */
163 - public function mxchat_embedding_mismatch_notice() {
164 - if (!current_user_can('manage_options')) {
165 - return;
166 - }
167 -
168 - $options = get_option('mxchat_options', array());
169 - $selected = $options['embedding_model'] ?? '';
170 - $active = MxChat_Utils::get_active_embedding_model();
171 -
172 - if (empty($active) || empty($selected) || $active === $selected) {
173 - return;
174 - }
175 -
176 - $dismissed = get_option('mxchat_dismissed_embedding_mismatch', '');
177 - if ($dismissed === $active . '|' . $selected) {
178 - return;
179 - }
180 -
181 - $active_label = MxChat_Utils::embedding_model_label($active);
182 - $selected_label = MxChat_Utils::embedding_model_label($selected);
183 - $active_dims = MxChat_Utils::embedding_model_dimensions($active);
184 - $selected_dims = MxChat_Utils::embedding_model_dimensions($selected);
185 - $dims_differ = ($active_dims > 0 && $selected_dims > 0 && $active_dims !== $selected_dims);
186 -
187 - $kb_url = admin_url('admin.php?page=mxchat-prompts');
188 - $actions_url = admin_url('admin.php?page=mxchat-actions');
189 -
190 - ?>
191 - <div class="notice notice-error is-dismissible mxchat-embedding-mismatch-notice"
192 - data-active="<?php echo esc_attr($active); ?>"
193 - data-selected="<?php echo esc_attr($selected); ?>">
194 - <p><strong><?php esc_html_e('MxChat: Embedding model mismatch detected', 'mxchat'); ?></strong></p>
195 - <p>
196 - <?php
197 - printf(
198 - /* translators: 1: previously-used model name, 2: currently-selected model name */
199 - esc_html__('Your knowledge base and actions were embedded with %1$s, but %2$s is now selected. Similarity matching will return inaccurate or empty results until you delete all existing embeddings and re-embed your content with the new model.', 'mxchat'),
200 - '<code>' . esc_html($active_label) . '</code>',
201 - '<code>' . esc_html($selected_label) . '</code>'
202 - );
203 - ?>
204 - </p>
205 - <?php if ($dims_differ) : ?>
206 - <p>
207 - <strong><?php esc_html_e('Dimension mismatch:', 'mxchat'); ?></strong>
208 - <?php
209 - printf(
210 - /* translators: 1: old dim count, 2: new dim count */
211 - esc_html__('Existing vectors are %1$d-dimensional but the new model produces %2$d-dimensional vectors. If you use Pinecone, your index will reject queries entirely until re-embedded.', 'mxchat'),
212 - (int) $active_dims,
213 - (int) $selected_dims
214 - );
215 - ?>
216 - </p>
217 - <?php endif; ?>
218 - <p>
219 - <?php esc_html_e('To fix this:', 'mxchat'); ?>
220 - <a href="<?php echo esc_url($kb_url); ?>"><?php esc_html_e('Delete all knowledge base entries', 'mxchat'); ?></a> ·
221 - <a href="<?php echo esc_url($actions_url); ?>"><?php esc_html_e('Delete all actions', 'mxchat'); ?></a> ·
222 - <?php esc_html_e('then re-import / re-add them with the new model selected.', 'mxchat'); ?>
223 - </p>
224 - </div>
225 - <script>
226 - (function($){
227 - $(document).on('click', '.mxchat-embedding-mismatch-notice .notice-dismiss', function(){
228 - $.post(ajaxurl, {
229 - action: 'mxchat_dismiss_embedding_mismatch',
230 - security: '<?php echo esc_js(wp_create_nonce('mxchat_admin_nonce')); ?>'
231 - });
232 - });
233 - })(jQuery);
234 - </script>
235 - <?php
236 - }
237 -
238 57 private function is_license_active() {
58 + // Get the raw value without translation
239 59 $license_status = get_option('mxchat_license_status', 'inactive');
240 - return ($license_status === 'active');
60 +
61 + // Check against multiple possible values, bypassing translation issues
62 + return ($license_status === 'active' || $license_status === esc_html__('active', 'mxchat'));
241 63 }
242 64
243 65 private function initialize_default_options() {
244 66 $default_options = array(
@@ -247,10 +69,9 @@
247 69 'claude_api_key' => '',
248 70 'deepseek_api_key' => '',
249 71 'voyage_api_key' => '',
250 72 'gemini_api_key' => '',
251 - 'enable_streaming_toggle' => 'off',
252 - 'enable_web_search' => 'off',
73 + 'enable_streaming_toggle' => 'on',
253 74 'embedding_model' => 'text-embedding-ada-002',
254 75 'system_prompt_instructions' => 'You are an AI Chatbot assistant for this website. Your main goal is to assist visitors with questions and provide helpful information. Here are your key guidelines:
255 76
256 77 # Response Style - CRITICALLY IMPORTANT
@@ -269,9 +90,9 @@
269 90 - If required information is NOT in the knowledge database: "I don\'t have enough information in my knowledge base to answer that question accurately."
270 91 - NEVER invent or hallucinate URLs, links, product specs, procedures, dates, statistics, names, contacts, or company information
271 92 - When knowledge base information is unclear or contradictory, acknowledge the limitation rather than guessing
272 93 - Better to admit insufficient information than provide inaccurate answers',
273 - 'model' => esc_html__('gpt-5.1-chat-latest', 'mxchat'),
94 + 'model' => esc_html__('gpt-4o', 'mxchat'),
274 95 'rate_limit_logged_out' => esc_html__('100', 'mxchat'),
275 96 'role_rate_limits' => array(),
276 97 'rate_limit_message' => esc_html__('Rate limit exceeded. Please try again later.', 'mxchat'),
277 98 'enable_email_block' => '',
@@ -286,9 +107,8 @@
286 107 'append_to_body' => esc_html__('off', 'mxchat'),
287 108 'post_type_visibility_mode' => 'all', // 'all', 'include', 'exclude'
288 109 'post_type_visibility_list' => array(), // Array of post type slugs
289 110 'contextual_awareness_toggle' => 'off',
290 - 'citation_links_toggle' => 'on',
291 111 'close_button_color' => esc_html__('#fff', 'mxchat'),
292 112 'chatbot_bg_color' => esc_html__('#fff', 'mxchat'),
293 113 'user_message_bg_color' => esc_html__('#fff', 'mxchat'),
294 114 'user_message_font_color' => esc_html__('#212121', 'mxchat'),
@@ -317,33 +137,18 @@
317 137 'pdf_max_pages' => 69,
318 138 'show_pdf_upload_button' => 'on',
319 139 'show_word_upload_button' => 'on',
320 140
321 - // Live Agent Integration (Slack)
141 + // Live Agent Integration
322 142 'live_agent_webhook_url' => '',
323 143 'live_agent_secret_key' => '',
324 144 'live_agent_bot_token' => '',
325 145 'live_agent_message_bg_color' => esc_html__('#ffffff', 'mxchat'),
326 146 'live_agent_message_font_color' => esc_html__('#333333', 'mxchat'),
327 -
328 - // Telegram Integration
329 - 'telegram_status' => 'off',
330 - 'telegram_bot_token' => '',
331 - 'telegram_group_id' => '',
332 - 'telegram_webhook_secret' => '',
333 - 'telegram_notification_message' => __("I've notified a support agent. Please allow a moment for them to respond. If you'd like to continue with AI, just type \"Switch to AI\" at any time.", 'mxchat'),
334 - 'telegram_away_message' => __("I just checked, and it looks like our support team isn't personally available at the moment. If you'd like, you can leave your email address, and they'll get back to you as soon as possible. In the meantime, you can keep chatting with me — just let me know how I can help!", 'mxchat'),
335 -
336 147 'chat_toolbar_toggle' => esc_html__('off', 'mxchat'),
337 148 'mode_indicator_bg_color' => esc_html__('#767676', 'mxchat'),
338 149 'mode_indicator_font_color' => esc_html__('#ffffff', 'mxchat'),
339 150 'toolbar_icon_color' => esc_html__('#212121', 'mxchat'),
340 -
341 - // Optimization settings
342 - 'script_loading_strategy' => 'default',
343 -
344 - // Debug settings
345 - 'debug_mode' => 'off',
346 151 );
347 152
348 153
349 154 // Merge existing options with defaults
@@ -378,18 +183,8 @@
378 183 'dashicons-testimonial',
379 184 6
380 185 );
381 186
382 - // Rename the first submenu item from "MxChat" to "Settings"
383 - add_submenu_page(
384 - 'mxchat-max',
385 - esc_html__('MxChat Settings', 'mxchat'),
386 - esc_html__('Settings', 'mxchat'),
387 - 'manage_options',
388 - 'mxchat-max',
389 - array($this, 'mxchat_create_admin_page')
390 - );
391 -
392 187 // Submenu page for Knowledge
393 188 add_submenu_page(
394 189 'mxchat-max',
395 190 esc_html__('Prompts', 'mxchat'),
@@ -416,23 +211,22 @@
416 211 'mxchat-actions',
417 212 array($this, 'mxchat_actions_page_html')
418 213 );
419 214
420 - // Content Generator page
421 - add_submenu_page(
422 - 'mxchat-max',
423 - esc_html__('Content', 'mxchat'),
424 - esc_html__('Content', 'mxchat'),
425 - 'manage_options',
426 - 'mxchat-content',
427 - array($this, 'mxchat_create_content_page')
428 - );
215 + add_submenu_page(
216 + 'mxchat-max',
217 + esc_html__('Add Ons', 'mxchat'),
218 + esc_html__('Add Ons', 'mxchat'),
219 + 'manage_options',
220 + 'mxchat-addons',
221 + array($this, 'mxchat_create_addons_page')
222 + );
429 223
430 - // Consolidated Pro & Extensions page (replaces separate Add Ons and Pro Upgrade pages)
224 + // Submenu page for Activation Key
431 225 add_submenu_page(
432 226 'mxchat-max',
433 - esc_html__('Pro & Extensions', 'mxchat'),
434 - esc_html__('Pro & Extensions', 'mxchat'),
227 + esc_html__('Pro Upgrade', 'mxchat'),
228 + esc_html__('Pro Upgrade', 'mxchat'),
435 229 'manage_options',
436 230 'mxchat-activation',
437 231 array($this, 'mxchat_create_activation_page')
438 232 );
@@ -444,16 +238,8 @@
444 238 $addons_page->render_page();
445 239 }
446 240
447 241 /**
448 - * Render the Content Generator admin page
449 - */
450 -public function mxchat_create_content_page() {
451 - require_once plugin_dir_path(__FILE__) . 'admin-content-page.php';
452 - mxchat_render_content_page($this);
453 -}
454 -
455 -/**
456 242 * Test actual streaming functionality in the WordPress environment
457 243 */
458 244 public function mxchat_handle_test_streaming_actual() {
459 245 check_ajax_referer('mxchat_test_streaming_nonce', 'nonce');
@@ -471,9 +257,9 @@
471 257 }
472 258
473 259 // Get user's selected model and API key
474 260 $options = get_option('mxchat_options', []);
475 - $selected_model = $options['model'] ?? 'gpt-5.1-chat-latest';
261 + $selected_model = $options['model'] ?? 'gpt-4o';
476 262
477 263 // Get the provider from the model
478 264 $model_parts = explode('-', $selected_model);
479 265 $provider = strtolower($model_parts[0]);
@@ -742,236 +528,8 @@
742 528
743 529 return $sanitized;
744 530 }
745 531
746 -public function register_openai_vectorstore_settings() {
747 - register_setting(
748 - 'mxchat_openai_vectorstore_options',
749 - 'mxchat_openai_vectorstore_options',
750 - array(
751 - 'type' => 'array',
752 - 'sanitize_callback' => array($this, 'sanitize_openai_vectorstore_settings'),
753 - 'default' => array(
754 - 'mxchat_use_openai_vectorstore' => '0',
755 - 'mxchat_vectorstore_ids' => '',
756 - 'mxchat_vectorstore_max_results' => 5
757 - )
758 - )
759 - );
760 -}
761 -
762 -public function sanitize_openai_vectorstore_settings($input) {
763 - $sanitized = array();
764 -
765 - $sanitized['mxchat_use_openai_vectorstore'] = isset($input['mxchat_use_openai_vectorstore']) ? '1' : '0';
766 - $sanitized['mxchat_vectorstore_ids'] = sanitize_text_field($input['mxchat_vectorstore_ids'] ?? '');
767 - $sanitized['mxchat_vectorstore_max_results'] = absint($input['mxchat_vectorstore_max_results'] ?? 5);
768 -
769 - // Ensure max results is within reasonable range
770 - if ($sanitized['mxchat_vectorstore_max_results'] < 1) {
771 - $sanitized['mxchat_vectorstore_max_results'] = 1;
772 - }
773 - if ($sanitized['mxchat_vectorstore_max_results'] > 20) {
774 - $sanitized['mxchat_vectorstore_max_results'] = 20;
775 - }
776 -
777 - return $sanitized;
778 -}
779 -
780 -/**
781 - * AJAX handler to test OpenAI Vector Store connection
782 - */
783 -public function mxchat_test_vectorstore_connection() {
784 - check_ajax_referer('mxchat_admin_nonce', 'nonce');
785 -
786 - if (!current_user_can('manage_options')) {
787 - wp_send_json_error(array('message' => __('Permission denied.', 'mxchat')));
788 - return;
789 - }
790 -
791 - $vectorstore_options = get_option('mxchat_openai_vectorstore_options', array());
792 - $vectorstore_ids = $vectorstore_options['mxchat_vectorstore_ids'] ?? '';
793 - $mxchat_options = get_option('mxchat_options', array());
794 - $api_key = $mxchat_options['api_key'] ?? '';
795 -
796 - if (empty($api_key)) {
797 - wp_send_json_error(array('message' => __('OpenAI API key is not configured.', 'mxchat')));
798 - return;
799 - }
800 -
801 - if (empty($vectorstore_ids)) {
802 - wp_send_json_error(array('message' => __('No Vector Store ID configured.', 'mxchat')));
803 - return;
804 - }
805 -
806 - // Get the first Vector Store ID for testing
807 - $ids_array = array_map('trim', explode(',', $vectorstore_ids));
808 - $test_id = $ids_array[0];
809 -
810 - // Test by retrieving the Vector Store info
811 - $response = wp_remote_get(
812 - 'https://api.openai.com/v1/vector_stores/' . $test_id,
813 - array(
814 - 'headers' => array(
815 - 'Authorization' => 'Bearer ' . $api_key,
816 - 'Content-Type' => 'application/json',
817 - 'OpenAI-Beta' => 'assistants=v2'
818 - ),
819 - 'timeout' => 30
820 - )
821 - );
822 -
823 - if (is_wp_error($response)) {
824 - wp_send_json_error(array('message' => __('Connection failed: ', 'mxchat') . $response->get_error_message()));
825 - return;
826 - }
827 -
828 - $status_code = wp_remote_retrieve_response_code($response);
829 - $body = json_decode(wp_remote_retrieve_body($response), true);
830 -
831 - if ($status_code === 200 && isset($body['id'])) {
832 - $file_count = $body['file_counts']['completed'] ?? 0;
833 - $name = $body['name'] ?? $test_id;
834 - wp_send_json_success(array(
835 - 'message' => sprintf(
836 - __('Connected successfully! Vector Store: %s (%d files)', 'mxchat'),
837 - esc_html($name),
838 - $file_count
839 - )
840 - ));
841 - } elseif ($status_code === 404) {
842 - wp_send_json_error(array('message' => __('Vector Store not found. Please check the ID.', 'mxchat')));
843 - } elseif ($status_code === 401) {
844 - wp_send_json_error(array('message' => __('Invalid API key.', 'mxchat')));
845 - } else {
846 - $error_message = $body['error']['message'] ?? __('Unknown error occurred.', 'mxchat');
847 - wp_send_json_error(array('message' => $error_message));
848 - }
849 -}
850 -
851 -/**
852 - * AJAX handler to test Slack connection and validate scopes
853 - */
854 -public function mxchat_test_slack_connection() {
855 - check_ajax_referer('mxchat_admin_nonce', 'nonce');
856 -
857 - if (!current_user_can('manage_options')) {
858 - wp_send_json_error(array('message' => __('Permission denied.', 'mxchat')));
859 - return;
860 - }
861 -
862 - $bot_token = $this->options['live_agent_bot_token'] ?? '';
863 -
864 - if (empty($bot_token)) {
865 - wp_send_json_error(array('message' => __('Slack Bot Token is not configured. Please enter your token and save settings first.', 'mxchat')));
866 - return;
867 - }
868 -
869 - // Test 1: Validate bot token with auth.test
870 - $auth_response = wp_remote_post('https://slack.com/api/auth.test', array(
871 - 'headers' => array(
872 - 'Authorization' => 'Bearer ' . $bot_token,
873 - 'Content-Type' => 'application/json'
874 - ),
875 - 'timeout' => 15
876 - ));
877 -
878 - if (is_wp_error($auth_response)) {
879 - wp_send_json_error(array('message' => __('Connection failed: ', 'mxchat') . $auth_response->get_error_message()));
880 - return;
881 - }
882 -
883 - $auth_body = json_decode(wp_remote_retrieve_body($auth_response), true);
884 -
885 - if (!isset($auth_body['ok']) || !$auth_body['ok']) {
886 - $error = $auth_body['error'] ?? 'unknown_error';
887 - $error_messages = array(
888 - 'invalid_auth' => __('Invalid bot token. Please check your token starts with xoxb-', 'mxchat'),
889 - 'not_authed' => __('No authentication token provided.', 'mxchat'),
890 - 'account_inactive' => __('The Slack workspace has been deactivated.', 'mxchat'),
891 - 'token_revoked' => __('The bot token has been revoked. Please generate a new one.', 'mxchat'),
892 - );
893 - $message = $error_messages[$error] ?? sprintf(__('Authentication failed: %s', 'mxchat'), $error);
894 - wp_send_json_error(array('message' => $message));
895 - return;
896 - }
897 -
898 - $team_name = $auth_body['team'] ?? 'Unknown Workspace';
899 - $bot_name = $auth_body['user'] ?? 'Unknown Bot';
900 -
901 - // Test 2: Check if we can list channels (tests channels:read scope)
902 - $channels_response = wp_remote_post('https://slack.com/api/conversations.list', array(
903 - 'headers' => array(
904 - 'Authorization' => 'Bearer ' . $bot_token,
905 - 'Content-Type' => 'application/json'
906 - ),
907 - 'body' => json_encode(array('limit' => 1)),
908 - 'timeout' => 15
909 - ));
910 -
911 - $channels_body = json_decode(wp_remote_retrieve_body($channels_response), true);
912 - $can_read_channels = isset($channels_body['ok']) && $channels_body['ok'];
913 -
914 - // Test 3: Check if we can create channels (tests channels:manage scope)
915 - // We'll just check the error message without actually creating
916 - $create_response = wp_remote_post('https://slack.com/api/conversations.create', array(
917 - 'headers' => array(
918 - 'Authorization' => 'Bearer ' . $bot_token,
919 - 'Content-Type' => 'application/json'
920 - ),
921 - 'body' => json_encode(array('name' => 'mxchat-test-' . time(), 'is_private' => false)),
922 - 'timeout' => 15
923 - ));
924 -
925 - $create_body = json_decode(wp_remote_retrieve_body($create_response), true);
926 -
927 - // If channel was created, delete it immediately
928 - if (isset($create_body['ok']) && $create_body['ok'] && isset($create_body['channel']['id'])) {
929 - wp_remote_post('https://slack.com/api/conversations.archive', array(
930 - 'headers' => array(
931 - 'Authorization' => 'Bearer ' . $bot_token,
932 - 'Content-Type' => 'application/json'
933 - ),
934 - 'body' => json_encode(array('channel' => $create_body['channel']['id'])),
935 - 'timeout' => 15
936 - ));
937 - $can_create_channels = true;
938 - } else {
939 - $create_error = $create_body['error'] ?? '';
940 - // name_taken means we have permission but channel exists
941 - $can_create_channels = ($create_error === 'name_taken' || (isset($create_body['ok']) && $create_body['ok']));
942 -
943 - // Check for missing scope errors
944 - if ($create_error === 'missing_scope') {
945 - $can_create_channels = false;
946 - }
947 - }
948 -
949 - // Build result message
950 - $results = array();
951 - $results[] = sprintf(__('Workspace: %s', 'mxchat'), esc_html($team_name));
952 - $results[] = sprintf(__('Bot: %s', 'mxchat'), esc_html($bot_name));
953 - $results[] = '';
954 - $results[] = ($can_read_channels ? '✓' : '✗') . ' ' . __('channels:read - List channels', 'mxchat');
955 - $results[] = ($can_create_channels ? '✓' : '✗') . ' ' . __('channels:manage - Create channels', 'mxchat');
956 -
957 - $missing_scopes = array();
958 - if (!$can_read_channels) $missing_scopes[] = 'channels:read';
959 - if (!$can_create_channels) $missing_scopes[] = 'channels:manage';
960 -
961 - if (!empty($missing_scopes)) {
962 - wp_send_json_error(array(
963 - 'message' => implode("\n", $results),
964 - 'missing_scopes' => $missing_scopes,
965 - 'partial' => true
966 - ));
967 - } else {
968 - wp_send_json_success(array(
969 - 'message' => implode("\n", $results)
970 - ));
971 - }
972 -}
973 -
974 532 public function mxchat_display_admin_notice() {
975 533 // Success notice
976 534 if ($message = get_transient('mxchat_admin_notice_success')) {
977 535 ?>
@@ -1018,13 +576,282 @@
1018 576 }
1019 577 }
1020 578 public function mxchat_create_admin_page() {
1021 579 $this->add_live_agent_nonce();
1022 - $this->add_theme_migration_nonce();
1023 580
1024 - // Include and render the new sidebar-based settings page
1025 - require_once plugin_dir_path(__FILE__) . 'admin-settings-page.php';
1026 - mxchat_render_settings_page($this);
581 + ?>
582 + <div class="wrap mxchat-wrapper">
583 + <!-- Hero Section -->
584 + <div class="mxchat-hero">
585 + <h1 class="mxchat-main-title">
586 + <span class="mxchat-gradient-text">MxChat</span> Settings
587 + </h1>
588 + <p class="mxchat-hero-subtitle">
589 + <?php esc_html_e('Configure your AI chatbot, manage integrations and explore tutorials to get the most out of MxChat.', 'mxchat'); ?>
590 + </p>
591 + </div>
592 +
593 + <div class="mxchat-content">
594 +
595 + <?php if (!$this->is_activated): ?>
596 + <div class="mxchat-pro-card">
597 + <div class="mxchat-pro-notification">
598 + <div class="mxchat-pro-content">
599 + <h3>🚀 Limited Lifetime Offer: Save 15% on MxChat Pro, Agency, or Agency Plus!</h3>
600 + <p>Unlock <strong>unlimited access</strong> to our growing collection of powerful add-ons including Admin AI Assistant (ChatGPT-like experience), Forms Builder, AI Theme Generator, WooCommerce, multi-bot, and more – all included with your <strong>lifetime license!</strong></p> </div>
601 + <div class="mxchat-pro-cta">
602 + <a href="https://mxchat.ai/" target="_blank" class="mxchat-button"><?php echo esc_html__('Upgrade Today', 'mxchat'); ?></a>
603 + <a href="<?php echo admin_url('admin.php?page=mxchat-addons'); ?>" class="mxchat-link"><?php echo esc_html__('Preview Add-ons', 'mxchat'); ?></a>
604 + </div>
605 + </div>
606 + </div>
607 + <?php endif; ?>
608 +
609 + <?php
610 + $this->show_live_agent_disabled_banner();
611 + ?>
612 +
613 + <!-- Tabs Navigation -->
614 + <div class="mxchat-tabs">
615 + <button class="mxchat-tab-button active" data-tab="chatbot"><?php echo esc_html__('Chatbot', 'mxchat'); ?></button>
616 + <button class="mxchat-tab-button" data-tab="api-keys"><?php echo esc_html__('API Keys', 'mxchat'); ?></button>
617 + <button class="mxchat-tab-button" data-tab="embed"><?php echo esc_html__('Toolbar & Components', 'mxchat'); ?></button>
618 + <button class="mxchat-tab-button" data-tab="general"><?php echo esc_html__('YouTube Tutorials', 'mxchat'); ?></button>
619 + </div>
620 +
621 + <!-- Tab Contents -->
622 + <div id="chatbot" class="mxchat-tab-content active">
623 + <div class="mxchat-card">
624 + <div class="mxchat-autosave-section">
625 + <?php do_settings_sections('mxchat-chatbot'); ?>
626 + </div>
627 + </div>
628 + </div>
629 +
630 + <div id="api-keys" class="mxchat-tab-content">
631 + <div class="mxchat-card">
632 + <div class="mxchat-autosave-section">
633 + <table class="form-table">
634 + <?php do_settings_fields('mxchat-api-keys', 'mxchat_api_keys_section'); ?>
635 + </table>
636 + </div>
637 + </div>
638 + </div>
639 +
640 + <div id="embed" class="mxchat-tab-content">
641 +
642 + <div class="mxchat-card">
643 + <h2><?php esc_html_e('Toolbar Settings', 'mxchat'); ?></h2>
644 + <div class="mxchat-autosave-section">
645 + <table class="form-table">
646 + <?php do_settings_fields('mxchat-embed', 'mxchat_pdf_intent_section'); ?>
647 + </table>
648 + </div>
649 + </div>
650 +
651 +
652 + <div class="mxchat-card">
653 + <h2><?php esc_html_e('Loops Settings', 'mxchat'); ?></h2>
654 + <div class="mxchat-autosave-section">
655 + <table class="form-table">
656 + <?php do_settings_fields('mxchat-embed', 'mxchat_loops_section'); ?>
657 + </table>
658 + </div>
659 + </div>
660 +
661 + <div class="mxchat-card">
662 + <h2><?php esc_html_e('Brave Search Settings', 'mxchat'); ?></h2>
663 + <div class="mxchat-autosave-section">
664 + <table class="form-table">
665 + <?php do_settings_fields('mxchat-embed', 'mxchat_brave_section'); ?>
666 + </table>
667 + </div>
668 + </div>
669 +
670 + <div class="mxchat-card">
671 + <h2><?php esc_html_e('Live Agent Settings', 'mxchat'); ?></h2>
672 + <div class="mxchat-autosave-section">
673 +<p><?php echo esc_html__('Visit our', 'mxchat'); ?> <a href="https://mxchat.ai/documentation/#slack_integration" target="_blank"><?php echo esc_html__('documentation page', 'mxchat'); ?></a> <?php echo esc_html__('for setup instructions and to see what\'s changed in the latest update.', 'mxchat'); ?></p> <table class="form-table">
674 + <?php do_settings_fields('mxchat-embed', 'mxchat_live_agent_section'); ?>
675 + </table>
676 + </div>
677 + </div>
678 + </div>
679 +
680 + <div id="general" class="mxchat-tab-content">
681 + <div class="mxchat-card">
682 + <?php do_settings_sections('mxchat-general'); ?>
683 + <div class="video-tutorials-section">
684 +
685 +
686 + <div class="support-notification">
687 + <div class="support-notification-icon">
688 + <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
689 + <path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/>
690 + <path d="M12 9v4"/>
691 + <path d="M12 17h.01"/>
692 + </svg>
693 + </div>
694 + <div class="support-notification-content">
695 + <h3 class="support-notification-title"><?php echo esc_html__('Need Help? We\'re Here for You!', 'mxchat'); ?></h3>
696 + <p class="support-notification-message">
697 + <?php echo esc_html__('Our goal is to provide the best experience and AI chatbot plugin available. If you\'re having trouble or believe something is not working as expected, please don\'t hesitate to submit a support ticket.', 'mxchat'); ?>
698 + </p>
699 + <a href="https://wordpress.org/support/plugin/mxchat-basic/" target="_blank" rel="noopener noreferrer" class="support-notification-button">
700 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
701 + <path d="M14 9a2 2 0 0 1-2 2H6l-4 4V4c0-1.1.9-2 2-2h8a2 2 0 0 1 2 2v5Z"/>
702 + <path d="M18 9h2a2 2 0 0 1 2 2v11l-4-4h-6a2 2 0 0 1-2-2v-1"/>
703 + </svg>
704 + <?php echo esc_html__('Submit Support Ticket', 'mxchat'); ?>
705 + </a>
706 + </div>
707 +</div>
708 +
709 + <div class="tutorial-grid">
710 +
711 + <div class="tutorial-item">
712 + <h3><?php echo esc_html__('AI Theme Generator Tutorial', 'mxchat'); ?></h3>
713 + <div class="video-description">
714 + <p><?php echo esc_html__('Learn how to instantly restyle your chatbot using plain English prompts. The AI Theme Generator lets you generate and apply beautiful designs with real-time previews—no CSS skills needed.', 'mxchat'); ?></p>
715 + <a href="https://www.youtube.com/watch?v=rSQDW2qbtRU&t" target="_blank" rel="noopener" class="video-link">
716 + <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
717 + <?php echo esc_html__('Watch AI Theme Generator Tutorial', 'mxchat'); ?>
718 + </a>
719 + </div>
720 +</div>
721 +
722 +
723 + <div class="tutorial-item">
724 + <h3><?php echo esc_html__('MxChat Forms Tutorial', 'mxchat'); ?></h3>
725 + <div class="video-description">
726 + <p><?php echo esc_html__('Learn how to create and manage smart forms that automatically trigger during chat conversations.', 'mxchat'); ?></p>
727 + <a href="https://www.youtube.com/watch?v=3MrWy5dRalA" target="_blank" rel="noopener" class="video-link">
728 + <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
729 + <?php echo esc_html__('Watch MxChat Forms Tutorial', 'mxchat'); ?>
730 + </a>
731 + </div>
732 + </div>
733 +
734 +<div class="tutorial-item">
735 + <h3><?php echo esc_html__('Admin Assistant Add-on', 'mxchat'); ?></h3>
736 + <div class="video-description">
737 + <p><?php echo esc_html__('Discover how to use the MxChat Admin Assistant to bring a ChatGPT-like experience directly inside your WordPress dashboard. Learn to access multiple AI models, save conversations, generate images, and use web search - all without leaving your admin panel.', 'mxchat'); ?></p>
738 + <a href="https://youtu.be/AdEA1k-UCFM" target="_blank" rel="noopener" class="video-link">
739 + <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2-3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
740 + <?php echo esc_html__('Watch Admin Assistant Tutorial', 'mxchat'); ?>
741 + </a>
742 + </div>
743 +</div>
744 +
745 + <div class="tutorial-item">
746 + <h3><?php echo esc_html__('Intent Tester Guide', 'mxchat'); ?></h3>
747 + <div class="video-description">
748 + <p><?php echo esc_html__('Discover how to use the Intent Tester to fine-tune your chatbot\'s responses and ensure it accurately understands user queries.', 'mxchat'); ?></p>
749 + <a href="https://www.youtube.com/watch?v=uTr14tn59Hc" target="_blank" rel="noopener" class="video-link">
750 + <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
751 + <?php echo esc_html__('Watch Intent Tester Tutorial', 'mxchat'); ?>
752 + </a>
753 + </div>
754 + </div>
755 +
756 + <div class="tutorial-item">
757 + <h3><?php echo esc_html__('Theme Customizer Add-on', 'mxchat'); ?></h3>
758 + <div class="video-description">
759 + <p><?php echo esc_html__('Learn how to customize your chatbot appearance with the Theme Customizer add-on. Easily modify colors, fonts, and styles with real-time previews to match your brand perfectly.', 'mxchat'); ?></p>
760 + <a href="https://youtu.be/MfbB9mZi6ag" target="_blank" rel="noopener" class="video-link">
761 + <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
762 + <?php echo esc_html__('Watch Theme Customizer Tutorial', 'mxchat'); ?>
763 + </a>
764 + </div>
765 + </div>
766 +
767 + <div class="tutorial-item">
768 + <h3><?php echo esc_html__('WooCommerce Integration', 'mxchat'); ?></h3>
769 + <div class="video-description">
770 + <p><?php echo esc_html__('See how to integrate MxChat with your WooCommerce store to provide product recommendations and shopping assistance to your customers.', 'mxchat'); ?></p>
771 + <a href="https://www.youtube.com/watch?v=WsqAppHRGdA" target="_blank" rel="noopener" class="video-link">
772 + <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
773 + <?php echo esc_html__('Watch WooCommerce Integration Tutorial', 'mxchat'); ?>
774 + </a>
775 + </div>
776 + </div>
777 +
778 + <div class="tutorial-item">
779 + <h3><?php echo esc_html__('Knowledge Base Setup', 'mxchat'); ?></h3>
780 + <div class="video-description">
781 + <p><?php echo esc_html__('Learn how to set up your knowledge base using PDFs, sitemaps, and manual entries to enhance your chatbot\'s responses with site-specific information.', 'mxchat'); ?></p>
782 + <p><small><?php echo esc_html__('Note: This tutorial uses an older UI, but the process remains the same.', 'mxchat'); ?></small></p>
783 + <a href="https://www.youtube.com/watch?v=8Ztjs66-VTo" target="_blank" rel="noopener" class="video-link">
784 + <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
785 + <?php echo esc_html__('Watch Knowledge Base Setup Tutorial', 'mxchat'); ?>
786 + </a>
787 + </div>
788 + </div>
789 +
790 + <div class="tutorial-item">
791 + <h3><?php echo esc_html__('Toolbar Chat with Documents', 'mxchat'); ?></h3>
792 + <div class="video-description">
793 + <p><?php echo esc_html__('See how to use the MxChat toolbar to chat with PDF and Word documents for enhanced document analysis and information retrieval.', 'mxchat'); ?></p>
794 + <a href="https://www.youtube.com/watch?v=j_c45WWCTG0" target="_blank" rel="noopener" class="video-link">
795 + <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
796 + <?php echo esc_html__('Watch Document Chat Tutorial', 'mxchat'); ?>
797 + </a>
798 + </div>
799 + </div>
800 +
801 + <div class="tutorial-item">
802 + <h3><?php echo esc_html__('Perplexity Integration', 'mxchat'); ?></h3>
803 + <div class="video-description">
804 + <p><?php echo esc_html__('Learn how to integrate Perplexity with your chatbot for real-time web search capabilities. This tutorial covers intent recognition, the toolbar toggle button, and how to enable your chatbot to search the web and provide up-to-date information to your visitors.', 'mxchat'); ?></p>
805 + <a href="https://youtu.be/wpKkbt24-bo" target="_blank" rel="noopener" class="video-link">
806 + <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
807 + <?php echo esc_html__('Watch Perplexity Integration Tutorial', 'mxchat'); ?>
808 + </a>
809 + </div>
810 + </div>
811 +
812 + <div class="tutorial-item">
813 + <h3><?php echo esc_html__('Brave Search Intent', 'mxchat'); ?></h3>
814 + <div class="video-description">
815 + <p><?php echo esc_html__('Learn how to leverage Brave Search intent capabilities to improve your chatbot\'s understanding of user queries and provide more accurate responses.', 'mxchat'); ?></p>
816 + <a href="https://www.youtube.com/watch?v=7vDL5H7vToc" target="_blank" rel="noopener" class="video-link">
817 + <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
818 + <?php echo esc_html__('Watch Brave Search Intent Tutorial', 'mxchat'); ?>
819 + </a>
820 + </div>
821 + </div>
822 +
823 + <div class="tutorial-item">
824 + <h3><?php echo esc_html__('Loops Email Capture', 'mxchat'); ?></h3>
825 + <div class="video-description">
826 + <p><?php echo esc_html__('Discover how to set up email capture with MxChat using Loops to grow your mailing list while providing value through your chatbot.', 'mxchat'); ?></p>
827 + <p><small><?php echo esc_html__('Note: This tutorial uses an older UI, but the process remains the same.', 'mxchat'); ?></small></p>
828 + <a href="https://www.youtube.com/watch?v=CNgm5TYDyTc" target="_blank" rel="noopener" class="video-link">
829 + <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
830 + <?php echo esc_html__('Watch Loops Email Capture Tutorial', 'mxchat'); ?>
831 + </a>
832 + </div>
833 + </div>
834 +
835 + <div class="tutorial-item">
836 + <h3><?php echo esc_html__('MxChat AI Agent Testing Service', 'mxchat'); ?></h3>
837 + <div class="video-description">
838 + <p><?php echo esc_html__('Learn how to use the MxChat AI Agent Testing Service to evaluate and improve your chatbot\'s performance and accuracy.', 'mxchat'); ?></p>
839 + <p><small><?php echo esc_html__('Note: This tutorial uses an older UI, but the process remains the same.', 'mxchat'); ?></small></p>
840 + <a href="https://www.youtube.com/watch?v=A0jowbpyX54" target="_blank" rel="noopener" class="video-link">
841 + <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
842 + <?php echo esc_html__('Watch AI Agent Testing Tutorial', 'mxchat'); ?>
843 + </a>
844 + </div>
845 + </div>
846 + </div>
847 +
848 + </div>
849 + </div>
850 + </div>
851 + </div>
852 + </div>
853 + <?php
1027 854 }
1028 855
1029 856 public function dismiss_live_agent_notice() {
1030 857 // Add debugging
@@ -1052,75 +879,16 @@
1052 879 ));
1053 880 }
1054 881 }
1055 882
1056 -/**
1057 - * Show theme migration notice for Pro users with AI-generated themes
1058 - * Only shown once - dismissible and stored in options
1059 - */
1060 -public function show_theme_migration_banner() {
1061 - // Only show if Pro is activated
1062 - if (!$this->is_activated) {
1063 - return;
1064 - }
1065 883
1066 - // Check if notice should be shown
1067 - $show_notice = get_option('mxchat_show_theme_migration_notice', false);
1068 -
1069 - if ($show_notice) {
1070 - ?>
1071 - <div class="mxchat-theme-migration-notice" id="mxchat-theme-migration-notice">
1072 - <div class="mxchat-pro-notification">
1073 - <button type="button" class="mxchat-dismiss-btn" onclick="dismissThemeMigrationNotice()" aria-label="Dismiss notification">
1074 - <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1075 - <line x1="18" y1="6" x2="6" y2="18"></line>
1076 - <line x1="6" y1="6" x2="18" y2="18"></line>
1077 - </svg>
1078 - </button>
1079 - <div class="mxchat-theme-migration-content">
1080 - <h3>🎨 AI Theme Migration Required</h3>
1081 - <p>If you're using an AI-generated chatbot theme, you'll need to migrate it to match the new CSS structure. Go to <strong>Theme Settings</strong>, select your theme from the sidebar, and click the <strong>Migrate</strong> button.</p>
1082 - </div>
1083 - </div>
1084 - </div>
1085 - <?php
1086 - }
1087 -}
1088 -
1089 -/**
1090 - * Dismiss theme migration notice via AJAX
1091 - */
1092 -public function dismiss_theme_migration_notice() {
1093 - // Verify nonce
1094 - if (!wp_verify_nonce($_POST['nonce'], 'dismiss_theme_migration_notice')) {
1095 - wp_die('Security check failed');
1096 - }
1097 -
1098 - // Remove the notice flag
1099 - delete_option('mxchat_show_theme_migration_notice');
1100 -
1101 - wp_send_json_success();
1102 -}
1103 -
1104 -/**
1105 - * Add nonce for theme migration notice dismiss
1106 - */
1107 -public function add_theme_migration_nonce() {
1108 - if (get_option('mxchat_show_theme_migration_notice', false) && $this->is_activated) {
1109 - wp_localize_script('mxchat-admin-js', 'mxchatThemeMigration', array(
1110 - 'nonce' => wp_create_nonce('dismiss_theme_migration_notice'),
1111 - 'ajaxurl' => admin_url('admin-ajax.php')
1112 - ));
1113 - }
1114 -}
1115 -
1116 884 public function mxchat_create_transcripts_page() {
1117 885 global $wpdb;
1118 886 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1119 887
1120 888 // Get basic stats
1121 - $total_chats = $wpdb->get_var("SELECT COUNT(DISTINCT session_id) FROM $table_name") ?: 0;
1122 - $total_messages = $wpdb->get_var("SELECT COUNT(*) FROM $table_name") ?: 0;
889 + $total_chats = $wpdb->get_var("SELECT COUNT(DISTINCT session_id) FROM $table_name");
890 + $total_messages = $wpdb->get_var("SELECT COUNT(*) FROM $table_name");
1123 891
1124 892 // Count unique users with detailed breakdown
1125 893 $total_users = $wpdb->get_var("
1126 894 SELECT COUNT(DISTINCT
@@ -1258,89 +1026,317 @@
1258 1026 $chart_chats[] = 0;
1259 1027 $chart_messages[] = 0;
1260 1028 }
1261 1029 }
1030 + ?>
1031 + <div class="wrap mxchat-transcripts-wrapper">
1032 + <!-- Hero Section -->
1033 + <div class="mxchat-transcripts-hero">
1034 + <h1 class="mxchat-main-title">
1035 + Chat <span class="mxchat-gradient-text">Transcripts</span>
1036 + </h1>
1037 + <p class="mxchat-hero-subtitle">
1038 + <?php esc_html_e('Review and manage your chatbot conversations with detailed message history.', 'mxchat'); ?>
1039 + </p>
1040 + </div>
1041 + <div class="mxchat-content">
1042 + <!-- Enhanced Metrics Dashboard -->
1043 + <div class="mxchat-metrics-dashboard">
1044 + <!-- Tab Navigation -->
1045 + <div class="mxchat-metrics-tabs">
1046 + <button class="mxchat-metrics-tab active" data-tab="overview">
1047 + <span class="tab-icon">📊</span>
1048 + <span class="tab-label">Overview</span>
1049 + </button>
1050 + <button class="mxchat-metrics-tab" data-tab="activity">
1051 + <span class="tab-icon">📈</span>
1052 + <span class="tab-label">Activity</span>
1053 + </button>
1054 + <button class="mxchat-metrics-tab" data-tab="insights">
1055 + <span class="tab-icon">💡</span>
1056 + <span class="tab-label">Insights</span>
1057 + </button>
1058 + </div>
1059 +
1060 + <!-- Tab Content -->
1061 + <div class="mxchat-metrics-content">
1062 + <!-- Overview Tab -->
1063 + <div class="mxchat-metrics-panel active" data-panel="overview">
1064 + <div class="mxchat-stats-grid">
1065 + <div class="mxchat-stat-card mxchat-stat-primary">
1066 + <div class="stat-icon">💬</div>
1067 + <div class="stat-content">
1068 + <span class="stat-value"><?php echo esc_html($total_chats); ?></span>
1069 + <span class="stat-label"><?php esc_html_e('Total Chats', 'mxchat'); ?></span>
1070 + </div>
1071 + </div>
1072 + <div class="mxchat-stat-card mxchat-stat-primary">
1073 + <div class="stat-icon">📝</div>
1074 + <div class="stat-content">
1075 + <span class="stat-value"><?php echo esc_html($total_messages); ?></span>
1076 + <span class="stat-label"><?php esc_html_e('Total Messages', 'mxchat'); ?></span>
1077 + </div>
1078 + </div>
1079 + <div class="mxchat-stat-card mxchat-stat-primary">
1080 + <div class="stat-icon">👥</div>
1081 + <div class="stat-content">
1082 + <span class="stat-value"><?php echo esc_html($total_users); ?></span>
1083 + <span class="stat-label"><?php esc_html_e('Unique Users', 'mxchat'); ?></span>
1084 + <span class="stat-sublabel">
1085 + <?php
1086 + echo sprintf(
1087 + esc_html__('%d registered • %d guests • %d tests', 'mxchat'),
1088 + $registered_users,
1089 + $guest_users,
1090 + $agent_tests
1091 + );
1092 + ?>
1093 + </span>
1094 + </div>
1095 + </div>
1096 + <div class="mxchat-stat-card mxchat-stat-primary">
1097 + <div class="stat-icon">💭</div>
1098 + <div class="stat-content">
1099 + <span class="stat-value"><?php echo esc_html($avg_messages); ?></span>
1100 + <span class="stat-label"><?php esc_html_e('Avg Messages/Chat', 'mxchat'); ?></span>
1101 + </div>
1102 + </div>
1103 + </div>
1104 + </div>
1105 +
1106 + <!-- Activity Tab -->
1107 + <div class="mxchat-metrics-panel" data-panel="activity">
1108 + <div class="mxchat-activity-layout">
1109 + <div class="mxchat-activity-stats">
1110 + <div class="mxchat-stat-card mxchat-stat-accent">
1111 + <div class="stat-icon">📅</div>
1112 + <div class="stat-content">
1113 + <span class="stat-value"><?php echo esc_html($today_chats); ?></span>
1114 + <span class="stat-label"><?php esc_html_e('Today', 'mxchat'); ?></span>
1115 + </div>
1116 + </div>
1117 + <div class="mxchat-stat-card mxchat-stat-accent">
1118 + <div class="stat-icon">📆</div>
1119 + <div class="stat-content">
1120 + <span class="stat-value"><?php echo esc_html($week_chats); ?></span>
1121 + <span class="stat-label"><?php esc_html_e('Last 7 Days', 'mxchat'); ?></span>
1122 + </div>
1123 + </div>
1124 + <div class="mxchat-stat-card mxchat-stat-accent">
1125 + <div class="stat-icon">🗓️</div>
1126 + <div class="stat-content">
1127 + <span class="stat-value"><?php echo esc_html($month_chats); ?></span>
1128 + <span class="stat-label"><?php esc_html_e('Last 30 Days', 'mxchat'); ?></span>
1129 + </div>
1130 + </div>
1131 + </div>
1132 +
1133 + <div class="mxchat-chart-container">
1134 + <h3 class="mxchat-chart-title"><?php esc_html_e('7-Day Activity Trend', 'mxchat'); ?></h3>
1135 + <canvas id="mxchat-activity-chart"></canvas>
1136 + </div>
1137 + </div>
1138 + </div>
1139 +
1140 + <!-- Insights Tab -->
1141 + <div class="mxchat-metrics-panel" data-panel="insights">
1142 + <div class="mxchat-insights-grid">
1143 + <div class="mxchat-insight-card">
1144 + <div class="insight-header">
1145 + <span class="insight-icon">🕐</span>
1146 + <h3><?php esc_html_e('Peak Activity', 'mxchat'); ?></h3>
1147 + </div>
1148 + <div class="insight-content">
1149 + <div class="insight-value">
1150 + <?php
1151 + if ($busiest_hour) {
1152 + $hour = $busiest_hour->hour;
1153 + $formatted_hour = date('g A', strtotime("$hour:00"));
1154 + echo esc_html($formatted_hour);
1155 + } else {
1156 + echo esc_html__('N/A', 'mxchat');
1157 + }
1158 + ?>
1159 + </div>
1160 + <div class="insight-description">
1161 + <?php esc_html_e('Most active hour of the day', 'mxchat'); ?>
1162 + </div>
1163 + </div>
1164 + </div>
1165 +
1166 + <div class="mxchat-insight-card">
1167 + <div class="insight-header">
1168 + <span class="insight-icon">📊</span>
1169 + <h3><?php esc_html_e('Engagement Rate', 'mxchat'); ?></h3>
1170 + </div>
1171 + <div class="insight-content">
1172 + <div class="insight-value">
1173 + <?php
1174 + $engagement_rate = $total_chats > 0 ? round(($total_messages / $total_chats), 1) : 0;
1175 + echo esc_html($engagement_rate);
1176 + ?>
1177 + </div>
1178 + <div class="insight-description">
1179 + <?php esc_html_e('Messages exchanged per chat', 'mxchat'); ?>
1180 + </div>
1181 + </div>
1182 + </div>
1183 +
1184 + <div class="mxchat-insight-card">
1185 + <div class="insight-header">
1186 + <span class="insight-icon">👤</span>
1187 + <h3><?php esc_html_e('User Distribution', 'mxchat'); ?></h3>
1188 + </div>
1189 + <div class="insight-content">
1190 + <div class="insight-breakdown">
1191 + <div class="breakdown-item">
1192 + <span class="breakdown-label"><?php esc_html_e('Registered', 'mxchat'); ?></span>
1193 + <span class="breakdown-value">
1194 + <?php
1195 + $registered_pct = $total_users > 0 ? round(($registered_users / $total_users) * 100) : 0;
1196 + echo esc_html($registered_pct . '%');
1197 + ?>
1198 + </span>
1199 + </div>
1200 + <div class="breakdown-item">
1201 + <span class="breakdown-label"><?php esc_html_e('Guests', 'mxchat'); ?></span>
1202 + <span class="breakdown-value">
1203 + <?php
1204 + $guest_pct = $total_users > 0 ? round(($guest_users / $total_users) * 100) : 0;
1205 + echo esc_html($guest_pct . '%');
1206 + ?>
1207 + </span>
1208 + </div>
1209 + </div>
1210 + </div>
1211 + </div>
1212 +
1213 + <div class="mxchat-insight-card">
1214 + <div class="insight-header">
1215 + <span class="insight-icon">⚡</span>
1216 + <h3><?php esc_html_e('Activity Status', 'mxchat'); ?></h3>
1217 + </div>
1218 + <div class="insight-content">
1219 + <div class="insight-value insight-status">
1220 + <?php
1221 + if ($today_chats > 0) {
1222 + echo '<span class="status-badge status-active">' . esc_html__('Active', 'mxchat') . '</span>';
1223 + } else if ($week_chats > 0) {
1224 + echo '<span class="status-badge status-moderate">' . esc_html__('Moderate', 'mxchat') . '</span>';
1225 + } else {
1226 + echo '<span class="status-badge status-quiet">' . esc_html__('Quiet', 'mxchat') . '</span>';
1227 + }
1228 + ?>
1229 + </div>
1230 + <div class="insight-description">
1231 + <?php
1232 + if ($today_chats > 0) {
1233 + echo esc_html(sprintf(__('%d chats today', 'mxchat'), $today_chats));
1234 + } else if ($week_chats > 0) {
1235 + echo esc_html(sprintf(__('%d chats this week', 'mxchat'), $week_chats));
1236 + } else {
1237 + echo esc_html__('No recent activity', 'mxchat');
1238 + }
1239 + ?>
1240 + </div>
1241 + </div>
1242 + </div>
1243 + </div>
1244 + </div>
1245 + </div>
1246 + </div>
1247 +
1248 + <script>
1249 + var mxchatChartData = {
1250 + labels: <?php echo json_encode($chart_labels); ?>,
1251 + chats: <?php echo json_encode($chart_chats); ?>,
1252 + messages: <?php echo json_encode($chart_messages); ?>
1253 + };
1254 + </script>
1262 1255
1263 - // Prepare page data for the template
1264 - $page_data = array(
1265 - 'total_chats' => $total_chats,
1266 - 'total_messages' => $total_messages,
1267 - 'total_users' => $total_users,
1268 - 'registered_users' => $registered_users,
1269 - 'guest_users' => $guest_users,
1270 - 'agent_tests' => $agent_tests,
1271 - 'today_chats' => $today_chats,
1272 - 'week_chats' => $week_chats,
1273 - 'month_chats' => $month_chats,
1274 - 'avg_messages' => $avg_messages,
1275 - 'busiest_hour' => $busiest_hour,
1276 - 'chart_labels' => $chart_labels,
1277 - 'chart_chats' => $chart_chats,
1278 - 'chart_messages' => $chart_messages,
1279 - );
1256 + <!-- Search and Filter Controls with Notification Settings Button -->
1257 + <div class="mxchat-controls-wrapper">
1258 + <div class="mxchat-search-box">
1259 + <input type="text" id="mxchat-search-transcripts"
1260 + placeholder="<?php esc_attr_e('Search transcripts...', 'mxchat'); ?>"
1261 + class="regular-text">
1262 + </div>
1263 + <form id="mxchat-delete-form" method="post">
1264 + <?php wp_nonce_field('mxchat_delete_chat_history', 'mxchat_delete_chat_nonce'); ?>
1265 + <div class="mxchat-controls">
1266 + <button type="button" id="mxchat-chat-email-notification-btn" class="mxchat-action-button">
1267 + <span class="dashicons dashicons-admin-generic"></span>
1268 + <?php esc_html_e('Settings', 'mxchat'); ?>
1269 + </button>
1270 + <button type="button" id="mxchat-export-transcripts" class="mxchat-action-button">
1271 + <span class="dashicons dashicons-download"></span>
1272 + <?php esc_html_e('Export All Chats', 'mxchat'); ?>
1273 + </button>
1274 + <button type="button" id="mxchat-select-all-transcripts" class="mxchat-select-button">
1275 + <span class="dashicons dashicons-yes-alt"></span>
1276 + <span class="button-text"><?php esc_html_e('Select All', 'mxchat'); ?></span>
1277 + </button>
1278 + <button type="submit" class="button delete-chats-button">
1279 + <span class="dashicons dashicons-trash"></span>
1280 + <?php esc_html_e('Delete Selected', 'mxchat'); ?>
1281 + </button>
1282 + </div>
1283 + </form>
1284 + </div>
1280 1285
1281 - // Include and render the new template
1282 - require_once plugin_dir_path(__FILE__) . 'admin-transcripts-page.php';
1283 - mxchat_render_transcripts_page($this, $page_data);
1284 -}
1286 + <!-- Transcripts Container -->
1287 + <div id="mxchat-transcripts"></div>
1288 + </div>
1289 + </div>
1285 1290
1286 -/**
1287 - * Get chart data for transcripts page
1288 - * Used by both page render and script localization
1289 - *
1290 - * @return array Chart data with labels, chats, and messages arrays
1291 - */
1292 -private function get_transcripts_chart_data() {
1293 - global $wpdb;
1294 - $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1291 + <!-- Modal for Chat Email Notification Settings -->
1292 + <div id="mxchat-chat-email-notification-modal" class="mxchat-chat-notification-modal-overlay" style="display: none;">
1293 + <div class="mxchat-chat-notification-modal-content">
1294 + <div class="mxchat-chat-notification-modal-header">
1295 + <h2><?php esc_html_e('Transcript Settings', 'mxchat'); ?></h2>
1296 + <button type="button" class="mxchat-chat-notification-modal-close">&times;</button>
1297 + </div>
1298 + <div class="mxchat-chat-notification-modal-body">
1299 + <form method="post" action="options.php" id="mxchat-chat-email-notification-form">
1300 + <?php
1301 + settings_fields('mxchat_transcripts_options');
1302 + do_settings_sections('mxchat-transcripts');
1303 + ?>
1304 + <div class="mxchat-chat-notification-modal-footer">
1305 + <button type="submit" class="button button-primary">
1306 + <?php esc_html_e('Save Settings', 'mxchat'); ?>
1307 + </button>
1308 + <button type="button" class="button mxchat-chat-notification-modal-cancel">
1309 + <?php esc_html_e('Cancel', 'mxchat'); ?>
1310 + </button>
1311 + </div>
1312 + </form>
1313 + </div>
1314 + </div>
1315 + </div>
1295 1316
1296 - // Get daily chat data for last 7 days - same query as mxchat_transcripts_page()
1297 - $daily_stats = $wpdb->get_results("
1298 - SELECT
1299 - DATE(timestamp) as date,
1300 - COUNT(DISTINCT session_id) as chats,
1301 - COUNT(*) as messages
1302 - FROM $table_name
1303 - WHERE timestamp >= DATE_SUB(NOW(), INTERVAL 7 DAY)
1304 - GROUP BY DATE(timestamp)
1305 - ORDER BY date ASC
1306 - ");
1317 + <!-- Modal for RAG Context Display -->
1318 + <div id="mxchat-rag-context-modal" class="mxchat-rag-modal-overlay" style="display: none;">
1319 + <div class="mxchat-rag-modal-content">
1320 + <div class="mxchat-rag-modal-header">
1321 + <h2><?php esc_html_e('Retrieved Documents', 'mxchat'); ?></h2>
1322 + <button type="button" class="mxchat-rag-modal-close">&times;</button>
1323 + </div>
1324 + <div class="mxchat-rag-modal-body">
1325 + <div class="mxchat-rag-loading" style="display: none;">
1326 + <span class="spinner is-active"></span>
1327 + <?php esc_html_e('Loading document context...', 'mxchat'); ?>
1328 + </div>
1329 + <div class="mxchat-rag-content">
1330 + <!-- RAG context will be populated via JavaScript -->
1331 + </div>
1332 + </div>
1333 + </div>
1334 + </div>
1335 + <?php
1336 +}
1307 1337
1308 - // Prepare chart data
1309 - $chart_labels = array();
1310 - $chart_chats = array();
1311 - $chart_messages = array();
1312 1338
1313 - // Fill last 7 days with data - same logic as mxchat_transcripts_page()
1314 - for ($i = 6; $i >= 0; $i--) {
1315 - $date = date('Y-m-d', strtotime("-$i days"));
1316 - $day_name = date('D', strtotime("-$i days"));
1317 - $chart_labels[] = $day_name;
1318 -
1319 - $found = false;
1320 - if ($daily_stats) {
1321 - foreach ($daily_stats as $stat) {
1322 - if ($stat->date === $date) {
1323 - $chart_chats[] = (int)$stat->chats;
1324 - $chart_messages[] = (int)$stat->messages;
1325 - $found = true;
1326 - break;
1327 - }
1328 - }
1329 - }
1330 - if (!$found) {
1331 - $chart_chats[] = 0;
1332 - $chart_messages[] = 0;
1333 - }
1334 - }
1335 -
1336 - return array(
1337 - 'labels' => $chart_labels,
1338 - 'chats' => $chart_chats,
1339 - 'messages' => $chart_messages
1340 - );
1341 -}
1342 -
1343 1339 public function mxchat_transcripts_notification_section_callback() {
1344 1340 echo '<p>' . esc_html__('Configure email notifications for new chat transcripts. You will receive an email notification when a new chat session begins.', 'mxchat') . '</p>';
1345 1341 }
1346 1342 public function mxchat_enable_notifications_callback() {
@@ -1548,43 +1544,23 @@
1548 1544 }
1549 1545
1550 1546 global $wpdb;
1551 1547 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1552 - $translations_table = $wpdb->prefix . 'mxchat_transcript_translations';
1553 -
1548 +
1554 1549 // Calculate the cutoff timestamp
1555 1550 $cutoff_date = date('Y-m-d H:i:s', strtotime("-{$days} days"));
1556 -
1557 - // First, get the session IDs that will be deleted (to clean up translations too)
1558 - $sessions_to_delete = $wpdb->get_col(
1559 - $wpdb->prepare(
1560 - "SELECT DISTINCT session_id FROM {$table_name} WHERE timestamp < %s",
1561 - $cutoff_date
1562 - )
1563 - );
1564 -
1551 +
1565 1552 // Delete transcripts older than the cutoff date
1566 1553 $deleted = $wpdb->query(
1567 1554 $wpdb->prepare(
1568 - "DELETE FROM {$table_name} WHERE timestamp < %s",
1555 + "DELETE FROM {$table_name} WHERE created_at < %s",
1569 1556 $cutoff_date
1570 1557 )
1571 1558 );
1572 -
1573 - // Also delete any translations for the deleted sessions
1574 - if (!empty($sessions_to_delete) && $wpdb->get_var("SHOW TABLES LIKE '$translations_table'") === $translations_table) {
1575 - $placeholders = implode(',', array_fill(0, count($sessions_to_delete), '%s'));
1576 - $wpdb->query(
1577 - $wpdb->prepare(
1578 - "DELETE FROM {$translations_table} WHERE session_id IN ($placeholders)",
1579 - $sessions_to_delete
1580 - )
1581 - );
1582 - }
1583 -
1559 +
1584 1560 // Log the cleanup action
1585 1561 if ($deleted !== false && $deleted > 0) {
1586 - //error_log(sprintf('MXChat: Auto-deleted %d transcripts older than %d days', $deleted, $days));
1562 + error_log(sprintf('MXChat: Auto-deleted %d transcripts older than %d days', $deleted, $days));
1587 1563 }
1588 1564 }
1589 1565
1590 1566 public function export_chat_transcripts() {
@@ -1646,1236 +1622,9 @@
1646 1622 fclose($output);
1647 1623 wp_die();
1648 1624 }
1649 1625
1650 -// ============================================================================
1651 -// Leads tab (inside Transcripts)
1652 -//
1653 -// Leads are derived from existing data — no dedicated table. Primary source:
1654 -// wp_mxchat_chat_transcripts rows where user_email is populated. Secondary
1655 -// source: wp_options entries `mxchat_email_{session_id}` / `mxchat_name_{sid}`
1656 -// for "orphan" leads who submitted the pre-chat form but never chatted.
1657 -// ============================================================================
1658 1626
1659 -/**
1660 - * Fetch leads: dedup-by-email rows, stats strip, and top pages in one call.
1661 - */
1662 -public function mxchat_fetch_leads() {
1663 - if (!current_user_can('manage_options')) {
1664 - wp_send_json_error(['message' => 'Insufficient permissions']);
1665 - wp_die();
1666 - }
1667 -
1668 - global $wpdb;
1669 - $table = $wpdb->prefix . 'mxchat_chat_transcripts';
1670 -
1671 - $page = isset($_POST['page']) ? max(1, absint($_POST['page'])) : 1;
1672 - $per_page = isset($_POST['per_page']) ? min(100, max(10, absint($_POST['per_page']))) : 25;
1673 - $offset = ($page - 1) * $per_page;
1674 - $search = isset($_POST['search']) ? sanitize_text_field(wp_unslash($_POST['search'])) : '';
1675 - $date_range = isset($_POST['date_range']) ? sanitize_key($_POST['date_range']) : 'all';
1676 - $status = isset($_POST['status']) ? sanitize_key($_POST['status']) : 'all';
1677 - $page_filter = isset($_POST['page_url']) ? esc_url_raw(wp_unslash($_POST['page_url'])) : '';
1678 - $sort = isset($_POST['sort']) ? sanitize_key($_POST['sort']) : 'last_seen';
1679 - $sort_dir = (isset($_POST['sort_dir']) && $_POST['sort_dir'] === 'asc') ? 'ASC' : 'DESC';
1680 -
1681 - $date_cutoff = self::mxchat_leads_date_cutoff($date_range);
1682 - $has_page_url_column = !empty($wpdb->get_results("SHOW COLUMNS FROM $table LIKE 'originating_page_url'"));
1683 -
1684 - // Base WHERE for transcripts leads.
1685 - $where_clauses = ["user_email IS NOT NULL", "user_email != ''"];
1686 - $where_params = [];
1687 -
1688 - if ($date_cutoff) {
1689 - $where_clauses[] = 'timestamp >= %s';
1690 - $where_params[] = $date_cutoff;
1691 - }
1692 - if ($page_filter && $has_page_url_column) {
1693 - $where_clauses[] = 'originating_page_url = %s';
1694 - $where_params[] = $page_filter;
1695 - }
1696 - if ($search !== '') {
1697 - $like = '%' . $wpdb->esc_like($search) . '%';
1698 - $where_clauses[] = '(user_email LIKE %s OR user_name LIKE %s)';
1699 - $where_params[] = $like;
1700 - $where_params[] = $like;
1701 - }
1702 - $where_sql = 'WHERE ' . implode(' AND ', $where_clauses);
1703 -
1704 - // Aggregate query grouped by email.
1705 - $select_sql = $has_page_url_column
1706 - ? "SELECT user_email, MAX(timestamp) AS last_seen, MIN(timestamp) AS first_seen,
1707 - COUNT(DISTINCT session_id) AS conversation_count"
1708 - : "SELECT user_email, MAX(timestamp) AS last_seen, MIN(timestamp) AS first_seen,
1709 - COUNT(DISTINCT session_id) AS conversation_count";
1710 -
1711 - $order_column = in_array($sort, ['last_seen', 'conversation_count', 'first_seen'], true) ? $sort : 'last_seen';
1712 - $group_order_limit = " GROUP BY user_email ORDER BY {$order_column} {$sort_dir} LIMIT %d OFFSET %d";
1713 -
1714 - $transcripts_sql = $wpdb->prepare(
1715 - "{$select_sql} FROM {$table} {$where_sql}{$group_order_limit}",
1716 - array_merge($where_params, [$per_page, $offset])
1717 - );
1718 - $transcript_rows = $wpdb->get_results($transcripts_sql);
1719 -
1720 - // Count of unique transcript-based leads under the same filters.
1721 - $count_sql = $wpdb->prepare(
1722 - "SELECT COUNT(DISTINCT user_email) FROM {$table} {$where_sql}",
1723 - $where_params
1724 - );
1725 - $transcripts_lead_count = (int) $wpdb->get_var($count_sql);
1726 -
1727 - // Hydrate each row: name, latest_session_id, top page.
1728 - $leads = [];
1729 - foreach ($transcript_rows as $row) {
1730 - $detail = $has_page_url_column
1731 - ? $wpdb->get_row($wpdb->prepare(
1732 - "SELECT session_id, user_name, originating_page_url, originating_page_title
1733 - FROM {$table} WHERE user_email = %s ORDER BY timestamp DESC LIMIT 1",
1734 - $row->user_email
1735 - ))
1736 - : $wpdb->get_row($wpdb->prepare(
1737 - "SELECT session_id, user_name FROM {$table}
1738 - WHERE user_email = %s ORDER BY timestamp DESC LIMIT 1",
1739 - $row->user_email
1740 - ));
1741 -
1742 - $leads[] = [
1743 - 'email' => $row->user_email,
1744 - 'name' => isset($detail->user_name) ? (string) $detail->user_name : '',
1745 - 'conversation_count' => (int) $row->conversation_count,
1746 - 'last_seen' => $row->last_seen,
1747 - 'last_seen_display' => self::mxchat_leads_format_relative($row->last_seen),
1748 - 'first_seen' => $row->first_seen,
1749 - 'latest_session_id' => isset($detail->session_id) ? $detail->session_id : '',
1750 - 'top_page_url' => isset($detail->originating_page_url) ? $detail->originating_page_url : '',
1751 - 'top_page_title' => isset($detail->originating_page_title) ? $detail->originating_page_title : '',
1752 - 'is_orphan' => false,
1753 - 'status' => 'active',
1754 - ];
1755 - }
1756 -
1757 - // Non-transcript lead sources. Built once here, then filtered/merged based on the
1758 - // status filter below. Deduplication priority when the same email appears in multiple
1759 - // sources: transcripts > chat_deleted > orphan.
1760 - $transcripts_emails_seen = array_flip(array_map(
1761 - function ($r) { return strtolower($r['email']); },
1762 - $leads
1763 - ));
1764 -
1765 - // Chat-deleted leads: had a conversation that an admin removed. Preserved via
1766 - // mxchat_lead_del_* options, with timestamps so they respect date filters.
1767 - $chat_deleted_leads_all = [];
1768 - if ($status === 'all' || $status === 'chat_deleted') {
1769 - $chat_deleted_leads_all = self::mxchat_collect_chat_deleted_leads($search, $date_cutoff);
1770 - // Dedup: drop any chat_deleted row whose email is already in the transcripts set.
1771 - $chat_deleted_leads_all = array_values(array_filter(
1772 - $chat_deleted_leads_all,
1773 - function ($row) use ($transcripts_emails_seen) {
1774 - return !isset($transcripts_emails_seen[strtolower($row['email'])]);
1775 - }
1776 - ));
1777 - foreach ($chat_deleted_leads_all as $row) {
1778 - $transcripts_emails_seen[strtolower($row['email'])] = true;
1779 - }
1780 - }
1781 -
1782 - // Orphans: pre-chat form captures with no conversation. No timestamp, so skipped
1783 - // when a date filter is active.
1784 - $orphan_leads_all = [];
1785 - if (($status === 'all' || $status === 'orphan') && !$date_cutoff && !$page_filter) {
1786 - $orphan_leads_all = self::mxchat_collect_orphan_leads($search);
1787 - $orphan_leads_all = array_values(array_filter(
1788 - $orphan_leads_all,
1789 - function ($row) use ($transcripts_emails_seen) {
1790 - return !isset($transcripts_emails_seen[strtolower($row['email'])]);
1791 - }
1792 - ));
1793 - }
1794 -
1795 - // Apply status filter to the transcripts-derived list.
1796 - if ($status === 'orphan' || $status === 'chat_deleted') {
1797 - $leads = [];
1798 - $transcripts_lead_count = 0;
1799 - }
1800 -
1801 - // Stitch the current page from the three buckets in priority order.
1802 - $total_count = $transcripts_lead_count + count($chat_deleted_leads_all) + count($orphan_leads_all);
1803 - $remaining_slots = $per_page - count($leads);
1804 -
1805 - if ($remaining_slots > 0 && !empty($chat_deleted_leads_all)) {
1806 - $start = max(0, ($page - 1) * $per_page - $transcripts_lead_count);
1807 - if ($start < count($chat_deleted_leads_all)) {
1808 - $leads = array_merge($leads, array_slice($chat_deleted_leads_all, $start, $remaining_slots));
1809 - $remaining_slots = $per_page - count($leads);
1810 - }
1811 - }
1812 -
1813 - if ($remaining_slots > 0 && !empty($orphan_leads_all)) {
1814 - $before = $transcripts_lead_count + count($chat_deleted_leads_all);
1815 - $start = max(0, ($page - 1) * $per_page - $before);
1816 - if ($start < count($orphan_leads_all)) {
1817 - $leads = array_merge($leads, array_slice($orphan_leads_all, $start, $remaining_slots));
1818 - }
1819 - }
1820 -
1821 - $total_pages = $per_page > 0 ? (int) ceil($total_count / $per_page) : 1;
1822 -
1823 - // Stats strip: always computed over full dataset, unaffected by filters.
1824 - $stats = self::mxchat_leads_stats($table, $has_page_url_column);
1825 -
1826 - // Top pages: top 5 by distinct emails captured.
1827 - $top_pages = [];
1828 - if ($has_page_url_column) {
1829 - $top_pages_rows = $wpdb->get_results(
1830 - "SELECT originating_page_url AS url,
1831 - MAX(originating_page_title) AS title,
1832 - COUNT(DISTINCT user_email) AS lead_count
1833 - FROM {$table}
1834 - WHERE user_email IS NOT NULL AND user_email != ''
1835 - AND originating_page_url IS NOT NULL AND originating_page_url != ''
1836 - GROUP BY originating_page_url
1837 - ORDER BY lead_count DESC, url ASC
1838 - LIMIT 5"
1839 - );
1840 - foreach ($top_pages_rows as $p) {
1841 - $top_pages[] = [
1842 - 'url' => $p->url,
1843 - 'title' => $p->title ?: $p->url,
1844 - 'lead_count' => (int) $p->lead_count,
1845 - ];
1846 - }
1847 - }
1848 -
1849 - wp_send_json([
1850 - 'success' => true,
1851 - 'leads' => $leads,
1852 - 'page' => $page,
1853 - 'per_page' => $per_page,
1854 - 'total_count' => $total_count,
1855 - 'total_pages' => $total_pages,
1856 - 'showing_start' => $total_count === 0 ? 0 : ($offset + 1),
1857 - 'showing_end' => min($offset + $per_page, $total_count),
1858 - 'stats' => $stats,
1859 - 'top_pages' => $top_pages,
1860 - ]);
1861 - wp_die();
1862 -}
1863 -
1864 -/**
1865 - * Delete one or more leads by email. Removes every transcripts row for that
1866 - * email and cleans up related wp_options (mxchat_email_{sid}, mxchat_name_{sid},
1867 - * mxchat_history_{sid}) and any orphan option entries matching the email.
1868 - */
1869 -public function mxchat_delete_leads() {
1870 - if (!current_user_can('manage_options')) {
1871 - wp_send_json_error(['message' => 'Insufficient permissions']);
1872 - wp_die();
1873 - }
1874 - check_ajax_referer('mxchat_delete_leads', 'security');
1875 -
1876 - $emails_raw = isset($_POST['emails']) ? (array) wp_unslash($_POST['emails']) : [];
1877 - $emails = [];
1878 - foreach ($emails_raw as $e) {
1879 - $clean = sanitize_email((string) $e);
1880 - if ($clean) {
1881 - $emails[] = $clean;
1882 - }
1883 - }
1884 - if (empty($emails)) {
1885 - wp_send_json_error(['message' => 'No emails provided']);
1886 - wp_die();
1887 - }
1888 -
1889 - $summary = self::mxchat_wipe_leads_by_email($emails);
1890 -
1891 - wp_send_json([
1892 - 'success' => true,
1893 - 'deleted_leads' => count($emails),
1894 - 'deleted_sessions' => $summary['deleted_sessions'],
1895 - 'deleted_rows' => $summary['deleted_rows'],
1896 - ]);
1897 - wp_die();
1898 -}
1899 -
1900 -/**
1901 - * Fully wipe one or more leads by email: every transcripts row, every related wp_options
1902 - * entry (history, pre-chat capture, chat_deleted preservation, agent name, translations).
1903 - *
1904 - * Shared between the Leads-tab Delete button and the transcript-delete opt-in checkbox.
1905 - * Input emails must already be sanitized with sanitize_email().
1906 - */
1907 -private static function mxchat_wipe_leads_by_email(array $emails) {
1908 - global $wpdb;
1909 - $table = $wpdb->prefix . 'mxchat_chat_transcripts';
1910 - $translations_table = $wpdb->prefix . 'mxchat_transcript_translations';
1911 - $has_translations = $wpdb->get_var("SHOW TABLES LIKE '$translations_table'") === $translations_table;
1912 -
1913 - $deleted_sessions = 0;
1914 - $deleted_rows = 0;
1915 - $emails_lc = array_map('strtolower', $emails);
1916 -
1917 - foreach ($emails as $email) {
1918 - $session_ids = $wpdb->get_col($wpdb->prepare(
1919 - "SELECT DISTINCT session_id FROM {$table} WHERE user_email = %s",
1920 - $email
1921 - ));
1922 -
1923 - $rows_removed = $wpdb->delete($table, ['user_email' => $email], ['%s']);
1924 - if ($rows_removed !== false) {
1925 - $deleted_rows += (int) $rows_removed;
1926 - }
1927 -
1928 - foreach ($session_ids as $sid) {
1929 - $deleted_sessions++;
1930 - wp_cache_delete('chat_session_' . $sid, 'mxchat_chat_sessions');
1931 - delete_option('mxchat_history_' . $sid);
1932 - delete_option('mxchat_email_' . $sid);
1933 - delete_option('mxchat_name_' . $sid);
1934 - delete_option('mxchat_agent_name_' . $sid);
1935 - delete_option('mxchat_lead_del_email_' . $sid);
1936 - delete_option('mxchat_lead_del_name_' . $sid);
1937 - delete_option('mxchat_lead_del_ts_' . $sid);
1938 - if ($has_translations) {
1939 - $wpdb->delete($translations_table, ['session_id' => $sid], ['%s']);
1940 - }
1941 - }
1942 - }
1943 -
1944 - // Clean up any lingering option entries (orphan pre-chat captures + chat_deleted
1945 - // preservations) whose stored value matches one of the emails being wiped.
1946 - $lingering = $wpdb->get_results(
1947 - "SELECT option_name, option_value FROM {$wpdb->options}
1948 - WHERE option_name LIKE 'mxchat_email_%' OR option_name LIKE 'mxchat_lead_del_email_%'"
1949 - );
1950 - foreach ($lingering as $opt) {
1951 - if (!in_array(strtolower(trim($opt->option_value)), $emails_lc, true)) {
1952 - continue;
1953 - }
1954 - if (strpos($opt->option_name, 'mxchat_lead_del_email_') === 0) {
1955 - $sid = substr($opt->option_name, strlen('mxchat_lead_del_email_'));
1956 - delete_option('mxchat_lead_del_email_' . $sid);
1957 - delete_option('mxchat_lead_del_name_' . $sid);
1958 - delete_option('mxchat_lead_del_ts_' . $sid);
1959 - } else {
1960 - $sid = substr($opt->option_name, strlen('mxchat_email_'));
1961 - delete_option('mxchat_email_' . $sid);
1962 - delete_option('mxchat_name_' . $sid);
1963 - }
1964 - }
1965 -
1966 - wp_cache_delete('all_chat_sessions', 'mxchat_chat_sessions');
1967 -
1968 - return [
1969 - 'deleted_sessions' => $deleted_sessions,
1970 - 'deleted_rows' => $deleted_rows,
1971 - ];
1972 -}
1973 -
1974 -/**
1975 - * Stream a leads CSV. scope=all exports every lead under current filters is not
1976 - * supported to keep semantics simple; caller either exports all leads or a
1977 - * specific set of selected emails.
1978 - */
1979 -public function mxchat_export_leads() {
1980 - if (!current_user_can('manage_options')) {
1981 - wp_die(esc_html__('You do not have sufficient permissions to access this page.', 'mxchat'));
1982 - }
1983 - check_ajax_referer('mxchat_export_leads', 'security');
1984 -
1985 - $scope = isset($_POST['scope']) ? sanitize_key($_POST['scope']) : 'all';
1986 - $fields_mode = isset($_POST['fields']) ? sanitize_key($_POST['fields']) : 'email_and_name';
1987 - $emails_in = isset($_POST['emails']) ? (array) wp_unslash($_POST['emails']) : [];
1988 -
1989 - $emails_in_clean = [];
1990 - foreach ($emails_in as $e) {
1991 - $clean = sanitize_email((string) $e);
1992 - if ($clean) {
1993 - $emails_in_clean[] = $clean;
1994 - }
1995 - }
1996 -
1997 - global $wpdb;
1998 - $table = $wpdb->prefix . 'mxchat_chat_transcripts';
1999 - $has_page_url_column = !empty($wpdb->get_results("SHOW COLUMNS FROM $table LIKE 'originating_page_url'"));
2000 -
2001 - // Collect leads from transcripts.
2002 - $transcripts_sql = "SELECT user_email AS email,
2003 - MAX(timestamp) AS last_seen,
2004 - COUNT(DISTINCT session_id) AS conversation_count
2005 - FROM {$table}
2006 - WHERE user_email IS NOT NULL AND user_email != ''";
2007 - $params = [];
2008 - if ($scope === 'selected' && !empty($emails_in_clean)) {
2009 - $placeholders = implode(',', array_fill(0, count($emails_in_clean), '%s'));
2010 - $transcripts_sql .= " AND user_email IN ({$placeholders})";
2011 - $params = $emails_in_clean;
2012 - }
2013 - $transcripts_sql .= " GROUP BY user_email ORDER BY last_seen DESC";
2014 -
2015 - $rows = !empty($params)
2016 - ? $wpdb->get_results($wpdb->prepare($transcripts_sql, $params))
2017 - : $wpdb->get_results($transcripts_sql);
2018 -
2019 - // Hydrate each row with name + top page.
2020 - $export_rows = [];
2021 - foreach ($rows as $row) {
2022 - $detail = $has_page_url_column
2023 - ? $wpdb->get_row($wpdb->prepare(
2024 - "SELECT user_name, originating_page_url FROM {$table}
2025 - WHERE user_email = %s ORDER BY timestamp DESC LIMIT 1",
2026 - $row->email
2027 - ))
2028 - : $wpdb->get_row($wpdb->prepare(
2029 - "SELECT user_name FROM {$table}
2030 - WHERE user_email = %s ORDER BY timestamp DESC LIMIT 1",
2031 - $row->email
2032 - ));
2033 - $export_rows[] = [
2034 - 'email' => $row->email,
2035 - 'name' => isset($detail->user_name) ? (string) $detail->user_name : '',
2036 - 'conversation_count' => (int) $row->conversation_count,
2037 - 'last_seen' => $row->last_seen,
2038 - 'top_page_url' => isset($detail->originating_page_url) ? $detail->originating_page_url : '',
2039 - ];
2040 - }
2041 -
2042 - // Include orphan + chat_deleted leads when exporting all.
2043 - if ($scope === 'all') {
2044 - $transcripts_emails_lc = array_flip(array_map(
2045 - function ($r) { return strtolower($r['email']); },
2046 - $export_rows
2047 - ));
2048 - foreach (self::mxchat_collect_chat_deleted_leads('') as $cd) {
2049 - if (isset($transcripts_emails_lc[strtolower($cd['email'])])) continue;
2050 - $transcripts_emails_lc[strtolower($cd['email'])] = true;
2051 - $export_rows[] = [
2052 - 'email' => $cd['email'],
2053 - 'name' => $cd['name'],
2054 - 'conversation_count' => 0,
2055 - 'last_seen' => $cd['last_seen'],
2056 - 'top_page_url' => '',
2057 - ];
2058 - }
2059 - foreach (self::mxchat_collect_orphan_leads('') as $orphan) {
2060 - if (isset($transcripts_emails_lc[strtolower($orphan['email'])])) continue;
2061 - $transcripts_emails_lc[strtolower($orphan['email'])] = true;
2062 - $export_rows[] = [
2063 - 'email' => $orphan['email'],
2064 - 'name' => $orphan['name'],
2065 - 'conversation_count' => 0,
2066 - 'last_seen' => '',
2067 - 'top_page_url' => '',
2068 - ];
2069 - }
2070 - }
2071 -
2072 - if (empty($export_rows)) {
2073 - wp_send_json_error(['message' => 'No leads to export.']);
2074 - wp_die();
2075 - }
2076 -
2077 - $filename = 'mxchat-leads-' . date('Y-m-d') . '.csv';
2078 - header('Content-Type: text/csv');
2079 - header('Content-Disposition: attachment; filename="' . $filename . '"');
2080 - header('Pragma: no-cache');
2081 - header('Expires: 0');
2082 -
2083 - $output = fopen('php://output', 'w');
2084 - fputs($output, "\xEF\xBB\xBF"); // UTF-8 BOM for Excel
2085 -
2086 - if ($fields_mode === 'email_only') {
2087 - fputcsv($output, ['Email']);
2088 - foreach ($export_rows as $r) {
2089 - fputcsv($output, [$r['email']]);
2090 - }
2091 - } else {
2092 - fputcsv($output, ['Email', 'Name', 'Conversations', 'Last seen', 'Top page']);
2093 - foreach ($export_rows as $r) {
2094 - fputcsv($output, [
2095 - $r['email'],
2096 - $r['name'],
2097 - $r['conversation_count'],
2098 - $r['last_seen'],
2099 - $r['top_page_url'],
2100 - ]);
2101 - }
2102 - }
2103 -
2104 - fclose($output);
2105 - wp_die();
2106 -}
2107 -
2108 -/**
2109 - * Stats strip payload (independent of filters).
2110 - */
2111 -private static function mxchat_leads_stats($table, $has_page_url_column) {
2112 - global $wpdb;
2113 -
2114 - $total_transcripts_emails = (int) $wpdb->get_var(
2115 - "SELECT COUNT(DISTINCT user_email) FROM {$table}
2116 - WHERE user_email IS NOT NULL AND user_email != ''"
2117 - );
2118 -
2119 - $new_this_week = (int) $wpdb->get_var($wpdb->prepare(
2120 - "SELECT COUNT(*) FROM (
2121 - SELECT user_email FROM {$table}
2122 - WHERE user_email IS NOT NULL AND user_email != ''
2123 - GROUP BY user_email
2124 - HAVING MIN(timestamp) >= %s
2125 - ) AS new_leads",
2126 - gmdate('Y-m-d H:i:s', strtotime('-7 days'))
2127 - ));
2128 -
2129 - $total_convos = (int) $wpdb->get_var(
2130 - "SELECT COUNT(DISTINCT session_id) FROM {$table}
2131 - WHERE user_email IS NOT NULL AND user_email != ''"
2132 - );
2133 -
2134 - $orphan_count = count(self::mxchat_collect_orphan_leads(''));
2135 - $chat_deleted_count = self::mxchat_count_chat_deleted_leads();
2136 -
2137 - // Total leads = unique emails across all three sources (dedup priority: transcripts > chat_deleted > orphan
2138 - // is already enforced at collection time in mxchat_fetch_leads; stats re-apply it here).
2139 - $total_leads = $total_transcripts_emails + $chat_deleted_count + $orphan_count;
2140 -
2141 - $avg = $total_transcripts_emails > 0
2142 - ? round($total_convos / $total_transcripts_emails, 1)
2143 - : 0;
2144 -
2145 - // Orphan % reflects *true* orphans only (pre-chat dropoffs). Chat-deleted leads are
2146 - // excluded so the metric stays meaningful — admins shouldn't see their cleanups
2147 - // inflate this number.
2148 - $orphan_pct = $total_leads > 0
2149 - ? (int) round(($orphan_count / $total_leads) * 100)
2150 - : 0;
2151 -
2152 - return [
2153 - 'total_leads' => $total_leads,
2154 - 'new_this_week' => $new_this_week,
2155 - 'avg_convos' => $avg,
2156 - 'orphan_pct' => $orphan_pct,
2157 - 'orphan_count' => $orphan_count,
2158 - 'chat_deleted_count' => $chat_deleted_count,
2159 - ];
2160 -}
2161 -
2162 -/**
2163 - * Collect leads who had a conversation that an admin later deleted (preserved via
2164 - * mxchat_lead_del_* options). Returns rows tagged status='chat_deleted' with the
2165 - * original last-seen timestamp so they still sort and filter sensibly.
2166 - *
2167 - * @param string $search Optional email/name substring filter.
2168 - * @param string $date_cutoff Optional 'Y-m-d H:i:s' cutoff — only rows with last_ts >= cutoff.
2169 - * @return array
2170 - */
2171 -private static function mxchat_collect_chat_deleted_leads($search = '', $date_cutoff = '') {
2172 - global $wpdb;
2173 - $table = $wpdb->prefix . 'mxchat_chat_transcripts';
2174 -
2175 - $rows = $wpdb->get_results(
2176 - "SELECT option_name, option_value FROM {$wpdb->options}
2177 - WHERE option_name LIKE 'mxchat_lead_del_email_%'"
2178 - );
2179 - if (empty($rows)) {
2180 - return [];
2181 - }
2182 -
2183 - // Emails that currently have transcripts rows should not appear as chat_deleted —
2184 - // they've come back and chatted, so they're active leads again.
2185 - $emails_in_transcripts = array_map(
2186 - 'strtolower',
2187 - (array) $wpdb->get_col(
2188 - "SELECT DISTINCT user_email FROM {$table}
2189 - WHERE user_email IS NOT NULL AND user_email != ''"
2190 - )
2191 - );
2192 - $emails_in_transcripts = array_flip($emails_in_transcripts);
2193 -
2194 - $needle = strtolower(trim((string) $search));
2195 - $by_email = [];
2196 -
2197 - foreach ($rows as $opt) {
2198 - $email = sanitize_email(trim((string) $opt->option_value));
2199 - if (!$email) {
2200 - continue;
2201 - }
2202 - if (isset($emails_in_transcripts[strtolower($email)])) {
2203 - continue;
2204 - }
2205 - $sid = substr($opt->option_name, strlen('mxchat_lead_del_email_'));
2206 - if (!$sid) {
2207 - continue;
2208 - }
2209 - $name = (string) get_option('mxchat_lead_del_name_' . $sid, '');
2210 - $ts = (string) get_option('mxchat_lead_del_ts_' . $sid, '');
2211 -
2212 - if ($date_cutoff !== '' && ($ts === '' || $ts < $date_cutoff)) {
2213 - continue;
2214 - }
2215 - if ($needle !== '') {
2216 - $hay = strtolower($email . ' ' . $name);
2217 - if (strpos($hay, $needle) === false) {
2218 - continue;
2219 - }
2220 - }
2221 -
2222 - $key = strtolower($email);
2223 - if (!isset($by_email[$key]) || (isset($by_email[$key]['last_seen']) && $ts > $by_email[$key]['last_seen'])) {
2224 - $by_email[$key] = [
2225 - 'email' => $email,
2226 - 'name' => $name,
2227 - 'conversation_count' => 0,
2228 - 'last_seen' => $ts,
2229 - 'last_seen_display' => $ts ? self::mxchat_leads_format_relative($ts) : __('Chat deleted', 'mxchat'),
2230 - 'first_seen' => $ts,
2231 - 'latest_session_id' => '',
2232 - 'top_page_url' => '',
2233 - 'top_page_title' => '',
2234 - 'is_orphan' => false,
2235 - 'status' => 'chat_deleted',
2236 - ];
2237 - }
2238 - }
2239 -
2240 - // Newest chat_deleted first.
2241 - usort($by_email, function ($a, $b) {
2242 - return strcmp((string) $b['last_seen'], (string) $a['last_seen']);
2243 - });
2244 - return array_values($by_email);
2245 -}
2246 -
2247 -/**
2248 - * Count unique emails preserved as "chat deleted" (for the stats strip).
2249 - */
2250 -private static function mxchat_count_chat_deleted_leads() {
2251 - global $wpdb;
2252 - $table = $wpdb->prefix . 'mxchat_chat_transcripts';
2253 -
2254 - $emails = $wpdb->get_col(
2255 - "SELECT DISTINCT option_value FROM {$wpdb->options}
2256 - WHERE option_name LIKE 'mxchat_lead_del_email_%'"
2257 - );
2258 - if (empty($emails)) {
2259 - return 0;
2260 - }
2261 -
2262 - $transcripts_emails = array_map(
2263 - 'strtolower',
2264 - (array) $wpdb->get_col(
2265 - "SELECT DISTINCT user_email FROM {$table}
2266 - WHERE user_email IS NOT NULL AND user_email != ''"
2267 - )
2268 - );
2269 - $transcripts_emails = array_flip($transcripts_emails);
2270 -
2271 - $count = 0;
2272 - $seen = [];
2273 - foreach ($emails as $raw) {
2274 - $email = strtolower(trim((string) $raw));
2275 - if (!$email || isset($seen[$email]) || isset($transcripts_emails[$email])) {
2276 - continue;
2277 - }
2278 - $seen[$email] = true;
2279 - $count++;
2280 - }
2281 - return $count;
2282 -}
2283 -
2284 -/**
2285 - * Find leads who submitted the pre-chat form but never produced a transcripts row.
2286 - * Returned rows have no conversation_count, no timestamp.
2287 - *
2288 - * @param string $search Optional email/name substring filter.
2289 - * @return array
2290 - */
2291 -private static function mxchat_collect_orphan_leads($search = '') {
2292 - global $wpdb;
2293 - $table = $wpdb->prefix . 'mxchat_chat_transcripts';
2294 -
2295 - $option_rows = $wpdb->get_results(
2296 - "SELECT option_name, option_value FROM {$wpdb->options}
2297 - WHERE option_name LIKE 'mxchat_email_%'"
2298 - );
2299 - if (empty($option_rows)) {
2300 - return [];
2301 - }
2302 -
2303 - // Collect all session_ids that have real transcripts rows so we can exclude them.
2304 - $session_ids_with_rows = $wpdb->get_col(
2305 - "SELECT DISTINCT session_id FROM {$table}
2306 - WHERE user_email IS NOT NULL AND user_email != ''"
2307 - );
2308 - $session_ids_with_rows = array_flip($session_ids_with_rows);
2309 -
2310 - // Seen emails in transcripts (so orphans only include truly never-chatted leads).
2311 - $emails_in_transcripts = array_map(
2312 - 'strtolower',
2313 - (array) $wpdb->get_col(
2314 - "SELECT DISTINCT user_email FROM {$table}
2315 - WHERE user_email IS NOT NULL AND user_email != ''"
2316 - )
2317 - );
2318 - $emails_in_transcripts = array_flip($emails_in_transcripts);
2319 -
2320 - $orphans_by_email = [];
2321 - $needle = strtolower(trim((string) $search));
2322 -
2323 - foreach ($option_rows as $opt) {
2324 - $email = sanitize_email(trim((string) $opt->option_value));
2325 - if (!$email) {
2326 - continue;
2327 - }
2328 - $sid = substr($opt->option_name, strlen('mxchat_email_'));
2329 - if (!$sid) {
2330 - continue;
2331 - }
2332 - // Exclude leads who have any transcripts rows (they appear in the main list).
2333 - if (isset($emails_in_transcripts[strtolower($email)])) {
2334 - continue;
2335 - }
2336 - if (isset($session_ids_with_rows[$sid])) {
2337 - continue;
2338 - }
2339 -
2340 - $name_option = get_option('mxchat_name_' . $sid, '');
2341 - $name = is_string($name_option) ? trim($name_option) : '';
2342 -
2343 - if ($needle !== '') {
2344 - $hay = strtolower($email . ' ' . $name);
2345 - if (strpos($hay, $needle) === false) {
2346 - continue;
2347 - }
2348 - }
2349 -
2350 - $key = strtolower($email);
2351 - if (!isset($orphans_by_email[$key])) {
2352 - $orphans_by_email[$key] = [
2353 - 'email' => $email,
2354 - 'name' => $name,
2355 - 'conversation_count' => 0,
2356 - 'last_seen' => '',
2357 - 'last_seen_display' => __('No conversation yet', 'mxchat'),
2358 - 'first_seen' => '',
2359 - 'latest_session_id' => '',
2360 - 'top_page_url' => '',
2361 - 'top_page_title' => '',
2362 - 'is_orphan' => true,
2363 - 'status' => 'orphan',
2364 - ];
2365 - }
2366 - }
2367 -
2368 - return array_values($orphans_by_email);
2369 -}
2370 -
2371 -/**
2372 - * Map a date_range key to a SQL-comparable cutoff string, or '' for all-time.
2373 - */
2374 -private static function mxchat_leads_date_cutoff($date_range) {
2375 - switch ($date_range) {
2376 - case 'today': return gmdate('Y-m-d H:i:s', strtotime('-24 hours'));
2377 - case '7d': return gmdate('Y-m-d H:i:s', strtotime('-7 days'));
2378 - case '30d': return gmdate('Y-m-d H:i:s', strtotime('-30 days'));
2379 - case '90d': return gmdate('Y-m-d H:i:s', strtotime('-90 days'));
2380 - case 'all':
2381 - default: return '';
2382 - }
2383 -}
2384 -
2385 -/**
2386 - * Turn a UTC timestamp into a short relative display like "2h ago" or "Apr 12".
2387 - */
2388 -private static function mxchat_leads_format_relative($timestamp) {
2389 - if (!$timestamp) {
2390 - return '';
2391 - }
2392 - $ts = strtotime($timestamp . ' UTC');
2393 - if (!$ts) {
2394 - return '';
2395 - }
2396 - $diff = time() - $ts;
2397 - if ($diff < 60) return __('just now', 'mxchat');
2398 - if ($diff < 3600) return floor($diff / 60) . __('m ago', 'mxchat');
2399 - if ($diff < 86400) return floor($diff / 3600) . __('h ago', 'mxchat');
2400 - if ($diff < 604800) return floor($diff / 86400) . __('d ago', 'mxchat');
2401 - return wp_date('M j', $ts);
2402 -}
2403 -
2404 -/**
2405 - * Handle translation of chat messages via AJAX
2406 - */
2407 -public function mxchat_translate_messages() {
2408 - if (!current_user_can('manage_options')) {
2409 - wp_send_json_error(['error' => 'Insufficient permissions']);
2410 - wp_die();
2411 - }
2412 -
2413 - $session_id = isset($_POST['session_id']) ? sanitize_text_field($_POST['session_id']) : '';
2414 - $target_lang = isset($_POST['target_lang']) ? sanitize_text_field($_POST['target_lang']) : 'en';
2415 - $messages_json = isset($_POST['messages']) ? wp_unslash($_POST['messages']) : '[]';
2416 - $messages = json_decode($messages_json, true);
2417 -
2418 - if (empty($session_id)) {
2419 - wp_send_json_error(['error' => 'No session ID provided']);
2420 - wp_die();
2421 - }
2422 -
2423 - if (empty($messages) || !is_array($messages)) {
2424 - wp_send_json_error(['error' => 'No messages to translate']);
2425 - wp_die();
2426 - }
2427 -
2428 - // Language names for prompting
2429 - $languages = [
2430 - 'en' => 'English',
2431 - 'es' => 'Spanish',
2432 - 'fr' => 'French',
2433 - 'de' => 'German',
2434 - 'it' => 'Italian',
2435 - 'pt' => 'Portuguese',
2436 - 'nl' => 'Dutch',
2437 - 'ru' => 'Russian',
2438 - 'zh' => 'Chinese',
2439 - 'ja' => 'Japanese',
2440 - 'ko' => 'Korean',
2441 - 'ar' => 'Arabic',
2442 - 'hi' => 'Hindi',
2443 - 'tr' => 'Turkish',
2444 - 'pl' => 'Polish',
2445 - 'vi' => 'Vietnamese',
2446 - 'th' => 'Thai',
2447 - 'id' => 'Indonesian',
2448 - 'sv' => 'Swedish',
2449 - 'da' => 'Danish'
2450 - ];
2451 -
2452 - $target_lang_name = isset($languages[$target_lang]) ? $languages[$target_lang] : 'English';
2453 -
2454 - // Build combined text for translation (numbered for parsing)
2455 - $numbered_messages = [];
2456 - foreach ($messages as $i => $msg) {
2457 - $content = isset($msg['content']) ? trim($msg['content']) : '';
2458 - if (!empty($content)) {
2459 - $numbered_messages[] = "[MSG" . $i . "]" . $content . "[/MSG" . $i . "]";
2460 - }
2461 - }
2462 -
2463 - if (empty($numbered_messages)) {
2464 - wp_send_json_error(['error' => 'No valid messages to translate']);
2465 - wp_die();
2466 - }
2467 -
2468 - $combined_text = implode("\n\n", $numbered_messages);
2469 -
2470 - // Prepare the translation prompt
2471 - $system_prompt = "You are a translator. Translate the following messages to {$target_lang_name}. Keep the [MSG#] and [/MSG#] tags exactly as they are - only translate the content between them. Maintain the original formatting, line breaks, and any HTML tags. Return ONLY the translated messages with the tags, no explanations.";
2472 -
2473 - // Get user's selected model and determine provider
2474 - $options = get_option('mxchat_options', []);
2475 - $selected_model = $options['model'] ?? 'gpt-5.1-chat-latest';
2476 -
2477 - // Check if using OpenRouter
2478 - if ($selected_model === 'openrouter') {
2479 - $provider = 'openrouter';
2480 - $selected_model = $options['openrouter_selected_model'] ?? '';
2481 - $api_key = $options['openrouter_api_key'] ?? '';
2482 -
2483 - if (empty($selected_model)) {
2484 - wp_send_json_error(['error' => 'No OpenRouter model selected']);
2485 - wp_die();
2486 - }
2487 - } else {
2488 - // Determine provider from model name
2489 - $model_parts = explode('-', $selected_model);
2490 - $provider = strtolower($model_parts[0]);
2491 -
2492 - // Get the appropriate API key based on provider
2493 - $api_key = '';
2494 - switch ($provider) {
2495 - case 'gpt':
2496 - case 'o1':
2497 - $api_key = $options['api_key'] ?? '';
2498 - break;
2499 - case 'claude':
2500 - $api_key = $options['claude_api_key'] ?? '';
2501 - break;
2502 - case 'grok':
2503 - $api_key = $options['xai_api_key'] ?? '';
2504 - break;
2505 - case 'deepseek':
2506 - $api_key = $options['deepseek_api_key'] ?? '';
2507 - break;
2508 - case 'gemini':
2509 - $api_key = $options['gemini_api_key'] ?? '';
2510 - break;
2511 - default:
2512 - // Default to OpenAI
2513 - $api_key = $options['api_key'] ?? '';
2514 - $provider = 'gpt';
2515 - break;
2516 - }
2517 - }
2518 -
2519 - if (empty($api_key)) {
2520 - wp_send_json_error(['error' => 'No API key configured for ' . $provider]);
2521 - wp_die();
2522 - }
2523 -
2524 - // Make API request based on provider
2525 - $response = $this->translate_with_provider($provider, $selected_model, $api_key, $system_prompt, $combined_text);
2526 -
2527 - if (is_wp_error($response)) {
2528 - wp_send_json_error(['error' => $response->get_error_message()]);
2529 - wp_die();
2530 - }
2531 -
2532 - // Parse the response to extract translated messages
2533 - $translations = [];
2534 - foreach ($messages as $msg) {
2535 - $index = $msg['index'];
2536 - $pattern = '/\[MSG' . $index . '\](.*?)\[\/MSG' . $index . '\]/s';
2537 - if (preg_match($pattern, $response, $matches)) {
2538 - $translations[] = [
2539 - 'index' => $index,
2540 - 'translated' => trim($matches[1])
2541 - ];
2542 - }
2543 - }
2544 -
2545 - // Save translations to database
2546 - if (!empty($translations)) {
2547 - $this->save_transcript_translation($session_id, $target_lang, $translations);
2548 - }
2549 -
2550 - wp_send_json(['success' => true, 'translations' => $translations, 'language' => $target_lang]);
2551 - wp_die();
2552 -}
2553 -
2554 -/**
2555 - * Save transcript translation to database
2556 - */
2557 -private function save_transcript_translation($session_id, $language_code, $translations) {
2558 - global $wpdb;
2559 - $table_name = $wpdb->prefix . 'mxchat_transcript_translations';
2560 -
2561 - // Check if table exists, create if not
2562 - if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") !== $table_name) {
2563 - mxchat_create_translations_table();
2564 - }
2565 -
2566 - $now = current_time('mysql');
2567 - $translations_json = wp_json_encode($translations);
2568 -
2569 - // Use REPLACE to insert or update
2570 - $wpdb->query($wpdb->prepare(
2571 - "REPLACE INTO $table_name (session_id, language_code, translations, created_at, updated_at)
2572 - VALUES (%s, %s, %s, %s, %s)",
2573 - $session_id,
2574 - $language_code,
2575 - $translations_json,
2576 - $now,
2577 - $now
2578 - ));
2579 -}
2580 -
2581 -/**
2582 - * Get saved translation for a session
2583 - */
2584 -public function mxchat_get_transcript_translation() {
2585 - if (!current_user_can('manage_options')) {
2586 - wp_send_json_error(['error' => 'Insufficient permissions']);
2587 - wp_die();
2588 - }
2589 -
2590 - $session_id = isset($_POST['session_id']) ? sanitize_text_field($_POST['session_id']) : '';
2591 -
2592 - if (empty($session_id)) {
2593 - wp_send_json_error(['error' => 'No session ID provided']);
2594 - wp_die();
2595 - }
2596 -
2597 - global $wpdb;
2598 - $table_name = $wpdb->prefix . 'mxchat_transcript_translations';
2599 -
2600 - // Check if table exists
2601 - if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") !== $table_name) {
2602 - wp_send_json(['success' => true, 'has_translation' => false]);
2603 - wp_die();
2604 - }
2605 -
2606 - // Get the most recent translation for this session
2607 - $result = $wpdb->get_row($wpdb->prepare(
2608 - "SELECT language_code, translations FROM $table_name WHERE session_id = %s ORDER BY updated_at DESC LIMIT 1",
2609 - $session_id
2610 - ));
2611 -
2612 - if ($result) {
2613 - $translations = json_decode($result->translations, true);
2614 - wp_send_json([
2615 - 'success' => true,
2616 - 'has_translation' => true,
2617 - 'language' => $result->language_code,
2618 - 'translations' => $translations
2619 - ]);
2620 - } else {
2621 - wp_send_json(['success' => true, 'has_translation' => false]);
2622 - }
2623 - wp_die();
2624 -}
2625 -
2626 -/**
2627 - * Translate text using the user's selected provider and model
2628 - */
2629 -private function translate_with_provider($provider, $model, $api_key, $system_prompt, $text) {
2630 - switch ($provider) {
2631 - case 'claude':
2632 - return $this->translate_with_claude($api_key, $model, $system_prompt, $text);
2633 - case 'grok':
2634 - return $this->translate_with_xai($api_key, $model, $system_prompt, $text);
2635 - case 'deepseek':
2636 - return $this->translate_with_deepseek($api_key, $model, $system_prompt, $text);
2637 - case 'gemini':
2638 - return $this->translate_with_gemini($api_key, $model, $system_prompt, $text);
2639 - case 'openrouter':
2640 - return $this->translate_with_openrouter($api_key, $model, $system_prompt, $text);
2641 - case 'gpt':
2642 - case 'o1':
2643 - default:
2644 - return $this->translate_with_openai($api_key, $model, $system_prompt, $text);
2645 - }
2646 -}
2647 -
2648 -/**
2649 - * Translate text using OpenAI API
2650 - */
2651 -private function translate_with_openai($api_key, $model, $system_prompt, $text) {
2652 - $response = wp_remote_post('https://api.openai.com/v1/chat/completions', [
2653 - 'timeout' => 60,
2654 - 'headers' => [
2655 - 'Authorization' => 'Bearer ' . $api_key,
2656 - 'Content-Type' => 'application/json'
2657 - ],
2658 - 'body' => wp_json_encode([
2659 - 'model' => $model,
2660 - 'messages' => [
2661 - ['role' => 'system', 'content' => $system_prompt],
2662 - ['role' => 'user', 'content' => $text]
2663 - ],
2664 - 'temperature' => 0.3
2665 - ])
2666 - ]);
2667 -
2668 - if (is_wp_error($response)) {
2669 - return $response;
2670 - }
2671 -
2672 - $body = json_decode(wp_remote_retrieve_body($response), true);
2673 -
2674 - if (isset($body['error'])) {
2675 - return new WP_Error('api_error', $body['error']['message']);
2676 - }
2677 -
2678 - if (isset($body['choices'][0]['message']['content'])) {
2679 - return $body['choices'][0]['message']['content'];
2680 - }
2681 -
2682 - return new WP_Error('api_error', 'Invalid API response');
2683 -}
2684 -
2685 -/**
2686 - * Translate text using Claude API
2687 - */
2688 -private function translate_with_claude($api_key, $model, $system_prompt, $text) {
2689 - $response = wp_remote_post('https://api.anthropic.com/v1/messages', [
2690 - 'timeout' => 60,
2691 - 'headers' => [
2692 - 'x-api-key' => $api_key,
2693 - 'anthropic-version' => '2023-06-01',
2694 - 'Content-Type' => 'application/json'
2695 - ],
2696 - 'body' => wp_json_encode([
2697 - 'model' => $model,
2698 - 'max_tokens' => 4096,
2699 - 'system' => $system_prompt,
2700 - 'messages' => [
2701 - ['role' => 'user', 'content' => $text]
2702 - ]
2703 - ])
2704 - ]);
2705 -
2706 - if (is_wp_error($response)) {
2707 - return $response;
2708 - }
2709 -
2710 - $body = json_decode(wp_remote_retrieve_body($response), true);
2711 -
2712 - if (isset($body['error'])) {
2713 - return new WP_Error('api_error', $body['error']['message']);
2714 - }
2715 -
2716 - if (isset($body['content'][0]['text'])) {
2717 - return $body['content'][0]['text'];
2718 - }
2719 -
2720 - return new WP_Error('api_error', 'Invalid API response');
2721 -}
2722 -
2723 -/**
2724 - * Translate text using xAI (Grok) API
2725 - */
2726 -private function translate_with_xai($api_key, $model, $system_prompt, $text) {
2727 - $response = wp_remote_post('https://api.x.ai/v1/chat/completions', [
2728 - 'timeout' => 60,
2729 - 'headers' => [
2730 - 'Authorization' => 'Bearer ' . $api_key,
2731 - 'Content-Type' => 'application/json'
2732 - ],
2733 - 'body' => wp_json_encode([
2734 - 'model' => $model,
2735 - 'messages' => [
2736 - ['role' => 'system', 'content' => $system_prompt],
2737 - ['role' => 'user', 'content' => $text]
2738 - ],
2739 - 'temperature' => 0.3
2740 - ])
2741 - ]);
2742 -
2743 - if (is_wp_error($response)) {
2744 - return $response;
2745 - }
2746 -
2747 - $body = json_decode(wp_remote_retrieve_body($response), true);
2748 -
2749 - if (isset($body['error'])) {
2750 - return new WP_Error('api_error', $body['error']['message']);
2751 - }
2752 -
2753 - if (isset($body['choices'][0]['message']['content'])) {
2754 - return $body['choices'][0]['message']['content'];
2755 - }
2756 -
2757 - return new WP_Error('api_error', 'Invalid API response');
2758 -}
2759 -
2760 -/**
2761 - * Translate text using DeepSeek API
2762 - */
2763 -private function translate_with_deepseek($api_key, $model, $system_prompt, $text) {
2764 - $response = wp_remote_post('https://api.deepseek.com/v1/chat/completions', [
2765 - 'timeout' => 60,
2766 - 'headers' => [
2767 - 'Authorization' => 'Bearer ' . $api_key,
2768 - 'Content-Type' => 'application/json'
2769 - ],
2770 - 'body' => wp_json_encode([
2771 - 'model' => $model,
2772 - 'messages' => [
2773 - ['role' => 'system', 'content' => $system_prompt],
2774 - ['role' => 'user', 'content' => $text]
2775 - ],
2776 - 'temperature' => 0.3
2777 - ])
2778 - ]);
2779 -
2780 - if (is_wp_error($response)) {
2781 - return $response;
2782 - }
2783 -
2784 - $body = json_decode(wp_remote_retrieve_body($response), true);
2785 -
2786 - if (isset($body['error'])) {
2787 - return new WP_Error('api_error', $body['error']['message']);
2788 - }
2789 -
2790 - if (isset($body['choices'][0]['message']['content'])) {
2791 - return $body['choices'][0]['message']['content'];
2792 - }
2793 -
2794 - return new WP_Error('api_error', 'Invalid API response');
2795 -}
2796 -
2797 -/**
2798 - * Translate text using Google Gemini API
2799 - */
2800 -private function translate_with_gemini($api_key, $model, $system_prompt, $text) {
2801 - $url = 'https://generativelanguage.googleapis.com/v1beta/models/' . $model . ':generateContent?key=' . $api_key;
2802 -
2803 - $response = wp_remote_post($url, [
2804 - 'timeout' => 60,
2805 - 'headers' => [
2806 - 'Content-Type' => 'application/json'
2807 - ],
2808 - 'body' => wp_json_encode([
2809 - 'contents' => [
2810 - [
2811 - 'parts' => [
2812 - ['text' => $system_prompt . "\n\n" . $text]
2813 - ]
2814 - ]
2815 - ],
2816 - 'generationConfig' => [
2817 - 'temperature' => 0.3
2818 - ]
2819 - ])
2820 - ]);
2821 -
2822 - if (is_wp_error($response)) {
2823 - return $response;
2824 - }
2825 -
2826 - $body = json_decode(wp_remote_retrieve_body($response), true);
2827 -
2828 - if (isset($body['error'])) {
2829 - return new WP_Error('api_error', $body['error']['message']);
2830 - }
2831 -
2832 - if (isset($body['candidates'][0]['content']['parts'][0]['text'])) {
2833 - return $body['candidates'][0]['content']['parts'][0]['text'];
2834 - }
2835 -
2836 - return new WP_Error('api_error', 'Invalid API response');
2837 -}
2838 -
2839 -/**
2840 - * Translate text using OpenRouter API
2841 - */
2842 -private function translate_with_openrouter($api_key, $model, $system_prompt, $text) {
2843 - $response = wp_remote_post('https://openrouter.ai/api/v1/chat/completions', [
2844 - 'timeout' => 60,
2845 - 'headers' => [
2846 - 'Authorization' => 'Bearer ' . $api_key,
2847 - 'Content-Type' => 'application/json',
2848 - 'HTTP-Referer' => home_url(),
2849 - 'X-Title' => 'MxChat Translation'
2850 - ],
2851 - 'body' => wp_json_encode([
2852 - 'model' => $model,
2853 - 'messages' => [
2854 - ['role' => 'system', 'content' => $system_prompt],
2855 - ['role' => 'user', 'content' => $text]
2856 - ],
2857 - 'temperature' => 0.3
2858 - ])
2859 - ]);
2860 -
2861 - if (is_wp_error($response)) {
2862 - return $response;
2863 - }
2864 -
2865 - $body = json_decode(wp_remote_retrieve_body($response), true);
2866 -
2867 - if (isset($body['error'])) {
2868 - return new WP_Error('api_error', $body['error']['message']);
2869 - }
2870 -
2871 - if (isset($body['choices'][0]['message']['content'])) {
2872 - return $body['choices'][0]['message']['content'];
2873 - }
2874 -
2875 - return new WP_Error('api_error', 'Invalid API response');
2876 -}
2877 -
2878 1627 public function mxchat_fetch_chat_history() {
2879 1628 global $wpdb;
2880 1629 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
2881 1630 $url_clicks_table = $wpdb->prefix . 'mxchat_url_clicks';
@@ -2880,10 +1629,9 @@
2880 1629 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
2881 1630 $url_clicks_table = $wpdb->prefix . 'mxchat_url_clicks';
2882 1631
2883 1632 if (!current_user_can('manage_options')) {
2884 - wp_send_json_error(['message' => 'Insufficient permissions']);
2885 - wp_die();
1633 + wp_die(esc_html__('You do not have sufficient permissions to view this page.', 'mxchat'));
2886 1634 }
2887 1635
2888 1636 $page = isset($_POST['page']) ? absint($_POST['page']) : 1;
2889 1637 $per_page = isset($_POST['per_page']) ? absint($_POST['per_page']) : 50;
@@ -2888,13 +1636,12 @@
2888 1636 $page = isset($_POST['page']) ? absint($_POST['page']) : 1;
2889 1637 $per_page = isset($_POST['per_page']) ? absint($_POST['per_page']) : 50;
2890 1638 $offset = ($page - 1) * $per_page;
2891 1639 $search = isset($_POST['search']) ? sanitize_text_field($_POST['search']) : '';
2892 - $sort_order = isset($_POST['sort_order']) && $_POST['sort_order'] === 'asc' ? 'ASC' : 'DESC';
2893 1640
2894 - // Build search condition
2895 1641 $search_condition = '';
2896 1642 $search_params = [];
1643 +
2897 1644 if (!empty($search)) {
2898 1645 $search_condition = "WHERE (
2899 1646 session_id LIKE %s
2900 1647 OR user_email LIKE %s
@@ -2904,51 +1651,52 @@
2904 1651 )";
2905 1652 $search_params = array_fill(0, 5, '%' . $wpdb->esc_like($search) . '%');
2906 1653 }
2907 1654
2908 - // Get total count
2909 1655 $count_query = !empty($search)
2910 1656 ? $wpdb->prepare("SELECT COUNT(DISTINCT session_id) FROM {$table_name} {$search_condition}", $search_params)
2911 1657 : "SELECT COUNT(DISTINCT session_id) FROM {$table_name}";
2912 - $total_sessions = (int) $wpdb->get_var($count_query);
2913 1658
2914 - // Get session IDs for current page (sorted by newest or oldest)
1659 + $total_sessions = $wpdb->get_var($count_query);
1660 +
2915 1661 $session_query = !empty($search)
2916 1662 ? $wpdb->prepare(
2917 1663 "SELECT DISTINCT t.session_id FROM {$table_name} t {$search_condition}
2918 - GROUP BY t.session_id ORDER BY MAX(t.timestamp) {$sort_order} LIMIT %d OFFSET %d",
1664 + GROUP BY t.session_id ORDER BY MAX(t.timestamp) DESC LIMIT %d OFFSET %d",
2919 1665 array_merge($search_params, [$per_page, $offset])
2920 1666 )
2921 1667 : $wpdb->prepare(
2922 1668 "SELECT DISTINCT session_id FROM {$table_name}
2923 - GROUP BY session_id ORDER BY MAX(timestamp) {$sort_order} LIMIT %d OFFSET %d",
1669 + GROUP BY session_id ORDER BY MAX(timestamp) DESC LIMIT %d OFFSET %d",
2924 1670 $per_page, $offset
2925 1671 );
1672 +
2926 1673 $session_ids = $wpdb->get_col($session_query);
2927 1674
2928 - $total_pages = ceil($total_sessions / $per_page);
2929 -
2930 1675 if (empty($session_ids)) {
1676 + ob_start();
1677 + echo '<div class="mxchat-no-results">';
1678 + echo esc_html__('No chat history found.', 'mxchat');
1679 + if (!empty($search)) {
1680 + echo ' ' . esc_html__('Try adjusting your search criteria.', 'mxchat');
1681 + }
1682 + echo '</div>';
2931 1683 wp_send_json([
2932 - 'success' => true,
2933 - 'sessions' => [],
1684 + 'html' => ob_get_clean(),
2934 1685 'page' => $page,
2935 1686 'total_pages' => 0,
2936 - 'total_sessions' => 0,
2937 - 'showing_start' => 0,
2938 - 'showing_end' => 0
1687 + 'total_sessions' => 0
2939 1688 ]);
2940 1689 wp_die();
2941 1690 }
2942 1691
2943 - // Check for optional columns/tables
2944 1692 $url_table_exists = $wpdb->get_var("SHOW TABLES LIKE '$url_clicks_table'") === $url_clicks_table;
2945 1693 $originating_columns_exist = !empty($wpdb->get_results("SHOW COLUMNS FROM $table_name LIKE 'originating_page_url'"));
2946 1694
2947 - // Build session list data
2948 - $sessions = [];
1695 + ob_start();
1696 + echo '<div class="mxchat-transcript">';
1697 +
2949 1698 foreach ($session_ids as $session_id) {
2950 - // Get session metadata
2951 1699 $session_data = $originating_columns_exist
2952 1700 ? $wpdb->get_row($wpdb->prepare(
2953 1701 "SELECT user_email, user_name, user_identifier, originating_page_url, originating_page_title, timestamp
2954 1702 FROM {$table_name} WHERE session_id = %s ORDER BY timestamp ASC LIMIT 1",
@@ -2959,217 +1707,202 @@
2959 1707 WHERE session_id = %s LIMIT 1",
2960 1708 $session_id
2961 1709 ));
2962 1710
2963 - // Get message count and latest timestamp
2964 - $message_stats = $wpdb->get_row($wpdb->prepare(
2965 - "SELECT COUNT(*) as count, MAX(timestamp) as latest FROM {$table_name} WHERE session_id = %s",
2966 - $session_id
2967 - ));
1711 + $clicked_urls = [];
1712 + if ($url_table_exists) {
1713 + $url_clicks = $wpdb->get_results(
1714 + $wpdb->prepare(
1715 + "SELECT DISTINCT clicked_url FROM {$url_clicks_table}
1716 + WHERE session_id = %s ORDER BY click_timestamp ASC",
1717 + $session_id
1718 + )
1719 + );
1720 + foreach ($url_clicks as $click) {
1721 + $clicked_urls[] = $click->clicked_url;
1722 + }
1723 + }
2968 1724
2969 - // Get first user message as preview
2970 - $first_user_msg = $wpdb->get_var($wpdb->prepare(
2971 - "SELECT message FROM {$table_name} WHERE session_id = %s AND role = 'user' ORDER BY timestamp ASC LIMIT 1",
2972 - $session_id
2973 - ));
2974 - $preview = $first_user_msg ? wp_trim_words(wp_strip_all_tags(stripslashes($first_user_msg)), 12, '...') : 'No messages';
1725 + $messages = $wpdb->get_results(
1726 + $wpdb->prepare(
1727 + "SELECT * FROM {$table_name}
1728 + WHERE session_id = %s ORDER BY timestamp ASC",
1729 + $session_id
1730 + )
1731 + );
2975 1732
2976 - // Build display name
1733 + $latest_timestamp = !empty($messages) ? end($messages)->timestamp : $session_data->timestamp;
1734 + $session_time = wp_date('M j, g:ia', strtotime($latest_timestamp . ' UTC'));
1735 +
2977 1736 $user_email = !empty($session_data->user_email) ? $session_data->user_email : '';
2978 - $user_name = !empty($session_data->user_name) ? $session_data->user_name : '';
1737 + $user_name = !empty($session_data->user_name) ? $session_data->user_name : ''; // NEW
2979 1738 $user_identifier = !empty($session_data->user_identifier) ? $session_data->user_identifier : 'Guest';
2980 1739
2981 - $display_name = $user_name ?: ($user_email ? explode('@', $user_email)[0] : $user_identifier);
2982 - $display_sub = $user_email ?: ('ID: ' . $user_identifier);
2983 -
2984 - // Format time - show relative for recent, date for older
2985 - $timestamp = strtotime($message_stats->latest . ' UTC');
2986 - $now = time();
2987 - $diff = $now - $timestamp;
2988 - if ($diff < 3600) {
2989 - $time_display = floor($diff / 60) . 'm ago';
2990 - } elseif ($diff < 86400) {
2991 - $time_display = floor($diff / 3600) . 'h ago';
2992 - } elseif ($diff < 604800) {
2993 - $time_display = floor($diff / 86400) . 'd ago';
1740 + // Build user display with name if available
1741 + if (!empty($user_email)) {
1742 + $user_display = $user_email;
1743 + if (!empty($user_name)) {
1744 + $user_display .= ' (' . $user_name . ')';
1745 + }
2994 1746 } else {
2995 - $time_display = wp_date('M j', $timestamp);
1747 + $user_display = $user_identifier;
2996 1748 }
1749 + echo '<div class="mxchat-session">';
1750 + echo '<div class="mxchat-session-header" data-session-id="' . esc_attr($session_id) . '">';
1751 + echo '<div class="mxchat-user-row">' . esc_html($user_display) . '</div>';
2997 1752
2998 - // Get initials for avatar
2999 - $initials = strtoupper(substr($display_name, 0, 2));
3000 - if (strlen($display_name) > 2 && strpos($display_name, ' ') !== false) {
3001 - $parts = explode(' ', $display_name);
3002 - $initials = strtoupper(substr($parts[0], 0, 1) . substr(end($parts), 0, 1));
1753 + if ($originating_columns_exist && !empty($session_data->originating_page_url)) {
1754 + $page_title = !empty($session_data->originating_page_title) ? $session_data->originating_page_title : $session_data->originating_page_url;
1755 +
1756 + echo '<div class="mxchat-header-row flex-between">';
1757 + echo '<div class="mxchat-main-info">';
1758 + echo '<a href="' . esc_url($session_data->originating_page_url) . '" target="_blank" class="mxchat-page-link">';
1759 + echo esc_html($page_title);
1760 + echo '</a>';
1761 + echo '<span class="mxchat-session-time">' . esc_html($session_time) . '</span>';
1762 + echo '</div>';
1763 + echo '</div>';
3003 1764 }
3004 1765
3005 - $sessions[] = [
3006 - 'session_id' => $session_id,
3007 - 'display_name' => $display_name,
3008 - 'display_sub' => $display_sub,
3009 - 'initials' => $initials,
3010 - 'preview' => $preview,
3011 - 'message_count' => (int) $message_stats->count,
3012 - 'time_display' => $time_display,
3013 - 'timestamp' => $message_stats->latest
3014 - ];
3015 - }
1766 + if (!empty($clicked_urls)) {
1767 + echo '<div class="mxchat-links-row">';
1768 + echo '<span class="mxchat-label">Clicked:</span> ';
1769 + $link_count = 0;
1770 + foreach ($clicked_urls as $url) {
1771 + if ($link_count > 0) echo ', ';
1772 + echo '<a href="' . esc_url($url) . '" target="_blank" class="mxchat-link-item">' . esc_html(parse_url($url, PHP_URL_HOST)) . '</a>';
1773 + $link_count++;
1774 + if ($link_count >= 3) {
1775 + if (count($clicked_urls) > 3) {
1776 + echo ' +' . (count($clicked_urls) - 3) . ' more';
1777 + }
1778 + break;
1779 + }
1780 + }
1781 + echo '</div>';
1782 + }
3016 1783
3017 - wp_send_json([
3018 - 'success' => true,
3019 - 'sessions' => $sessions,
3020 - 'page' => $page,
3021 - 'total_pages' => $total_pages,
3022 - 'total_sessions' => $total_sessions,
3023 - 'showing_start' => $offset + 1,
3024 - 'showing_end' => min($offset + $per_page, $total_sessions)
3025 - ]);
3026 - wp_die();
3027 -}
1784 + echo '</div>'; // end session-header
1785 + echo '<div class="mxchat-messages">';
3028 1786
3029 -/**
3030 - * Fetch single conversation details for split-panel view
3031 - */
3032 -public function mxchat_fetch_conversation() {
3033 - global $wpdb;
3034 - $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
3035 - $url_clicks_table = $wpdb->prefix . 'mxchat_url_clicks';
1787 + foreach ($messages as $transcript) {
1788 + $formatted_timestamp = wp_date('F j, Y g:i a', strtotime($transcript->timestamp . ' UTC'));
3036 1789
3037 - if (!current_user_can('manage_options')) {
3038 - wp_send_json_error(['message' => 'Insufficient permissions']);
3039 - wp_die();
3040 - }
1790 + switch ($transcript->role) {
1791 + case 'assistant':
1792 + case 'bot':
1793 + $message_class = 'bot-message';
1794 + $display_role = esc_html__('Chatbot', 'mxchat');
1795 + break;
1796 + case 'user':
1797 + $message_class = 'user-message';
1798 + $display_role = !empty($transcript->user_identifier)
1799 + ? sanitize_text_field($transcript->user_identifier)
1800 + : esc_html__('User', 'mxchat');
1801 + break;
1802 + case 'agent':
1803 + $message_class = 'agent-message';
1804 + $display_role = esc_html__('Agent', 'mxchat');
1805 + break;
1806 + default:
1807 + $message_class = 'unknown-message';
1808 + $display_role = esc_html__('Unknown', 'mxchat');
1809 + }
3041 1810
3042 - $session_id = isset($_POST['session_id']) ? sanitize_text_field($_POST['session_id']) : '';
3043 - if (empty($session_id)) {
3044 - wp_send_json_error(['message' => 'No session ID provided']);
3045 - wp_die();
3046 - }
1811 + $message_content = wp_kses(
1812 + stripslashes($transcript->message),
1813 + [
1814 + 'b' => [], 'strong' => [], 'i' => [], 'em' => [], 'u' => [],
1815 + 'br' => [], 'p' => [], 'ul' => [], 'ol' => [], 'li' => [],
1816 + 'a' => ['href' => [], 'title' => [], 'target' => []]
1817 + ]
1818 + );
1819 + $message_content = nl2br($message_content);
3047 1820
3048 - // Check for optional columns/tables
3049 - $url_table_exists = $wpdb->get_var("SHOW TABLES LIKE '$url_clicks_table'") === $url_clicks_table;
3050 - $originating_columns_exist = !empty($wpdb->get_results("SHOW COLUMNS FROM $table_name LIKE 'originating_page_url'"));
1821 + // Check if this bot message has RAG context data
1822 + $has_rag_context = false;
1823 + if (($transcript->role === 'bot' || $transcript->role === 'assistant') && !empty($transcript->rag_context)) {
1824 + $has_rag_context = true;
1825 + }
3051 1826
3052 - // Get session metadata
3053 - $session_data = $originating_columns_exist
3054 - ? $wpdb->get_row($wpdb->prepare(
3055 - "SELECT user_email, user_name, user_identifier, originating_page_url, originating_page_title, timestamp
3056 - FROM {$table_name} WHERE session_id = %s ORDER BY timestamp ASC LIMIT 1",
3057 - $session_id
3058 - ))
3059 - : $wpdb->get_row($wpdb->prepare(
3060 - "SELECT user_email, user_name, user_identifier, timestamp FROM {$table_name}
3061 - WHERE session_id = %s LIMIT 1",
3062 - $session_id
3063 - ));
1827 + $message_id_attr = $has_rag_context ? ' data-message-id="' . esc_attr($transcript->id) . '"' : '';
3064 1828
3065 - if (!$session_data) {
3066 - wp_send_json_error(['message' => 'Session not found']);
3067 - wp_die();
3068 - }
1829 + // Process markdown in message content
1830 + $formatted_content = $this->format_transcript_message($message_content);
3069 1831
3070 - // Get clicked URLs
3071 - $clicked_urls = [];
3072 - if ($url_table_exists) {
3073 - $url_clicks = $wpdb->get_results($wpdb->prepare(
3074 - "SELECT DISTINCT clicked_url FROM {$url_clicks_table}
3075 - WHERE session_id = %s ORDER BY click_timestamp ASC",
3076 - $session_id
3077 - ));
3078 - foreach ($url_clicks as $click) {
3079 - $clicked_urls[] = $click->clicked_url;
1832 + echo '<div class="mxchat-message ' . esc_attr($message_class) . '"' . $message_id_attr . '>';
1833 + echo '<div class="mxchat-message-header">';
1834 + echo '<span class="mxchat-role-label">' . esc_html($display_role) . '</span>';
1835 + if ($has_rag_context) {
1836 + echo '<span class="mxchat-rag-link" title="' . esc_attr__('View retrieved documents', 'mxchat') . '">Sources &rarr;</span>';
1837 + }
1838 + echo '</div>';
1839 + echo '<div class="mxchat-message-content">' . $formatted_content . '</div>';
1840 + echo '<div class="mxchat-timestamp">' . esc_html($formatted_timestamp) . '</div>';
1841 + echo '</div>';
3080 1842 }
1843 +
1844 + echo '</div></div>'; // end messages and session
3081 1845 }
3082 1846
3083 - // Get all messages
3084 - $messages = $wpdb->get_results($wpdb->prepare(
3085 - "SELECT * FROM {$table_name} WHERE session_id = %s ORDER BY timestamp ASC",
3086 - $session_id
3087 - ));
1847 + echo '</div>'; // end transcript
3088 1848
3089 - // Build user info
3090 - $user_email = !empty($session_data->user_email) ? $session_data->user_email : '';
3091 - $user_name = !empty($session_data->user_name) ? $session_data->user_name : '';
3092 - $user_identifier = !empty($session_data->user_identifier) ? $session_data->user_identifier : 'Guest';
1849 + $total_pages = ceil($total_sessions / $per_page);
3093 1850
3094 - $display_name = $user_name ?: ($user_email ? explode('@', $user_email)[0] : $user_identifier);
3095 - $display_sub = $user_email ?: ('ID: ' . $user_identifier);
1851 + echo '<div class="mxchat-pagination">';
1852 + echo '<div class="mxchat-pagination-info">';
1853 + echo sprintf(
1854 + esc_html__('Showing %1$d to %2$d of %3$d sessions', 'mxchat'),
1855 + $offset + 1,
1856 + min($offset + $per_page, $total_sessions),
1857 + $total_sessions
1858 + );
1859 + echo '</div>';
3096 1860
3097 - // Get initials
3098 - $initials = strtoupper(substr($display_name, 0, 2));
3099 - if (strlen($display_name) > 2 && strpos($display_name, ' ') !== false) {
3100 - $parts = explode(' ', $display_name);
3101 - $initials = strtoupper(substr($parts[0], 0, 1) . substr(end($parts), 0, 1));
3102 - }
1861 + if ($total_pages > 1) {
1862 + echo '<div class="mxchat-pagination-controls">';
1863 + if ($page > 1) {
1864 + echo '<button class="mxchat-pagination-button" data-page="' . ($page - 1) . '">&laquo; ' . esc_html__('Previous', 'mxchat') . '</button>';
1865 + }
3103 1866
3104 - // Page info
3105 - $page_url = $originating_columns_exist && !empty($session_data->originating_page_url) ? $session_data->originating_page_url : '';
3106 - $page_title = '';
3107 - if ($page_url) {
3108 - $page_title = !empty($session_data->originating_page_title) ? $session_data->originating_page_title : parse_url($page_url, PHP_URL_PATH);
3109 - }
1867 + $start_page = max(1, $page - 2);
1868 + $end_page = min($total_pages, $page + 2);
3110 1869
3111 - // Format messages for output
3112 - $formatted_messages = [];
3113 - foreach ($messages as $msg) {
3114 - $is_user = ($msg->role === 'user');
3115 - $is_bot = ($msg->role === 'bot' || $msg->role === 'assistant');
1870 + if ($start_page > 1) {
1871 + echo '<button class="mxchat-pagination-button" data-page="1">1</button>';
1872 + if ($start_page > 2) {
1873 + echo '<span class="mxchat-pagination-ellipsis">...</span>';
1874 + }
1875 + }
3116 1876
3117 - $content = wp_kses(
3118 - stripslashes($msg->message),
3119 - [
3120 - 'b' => [], 'strong' => [], 'i' => [], 'em' => [], 'u' => [],
3121 - 'br' => [], 'p' => [], 'ul' => [], 'ol' => [], 'li' => [],
3122 - 'a' => ['href' => [], 'title' => [], 'target' => [], 'class' => []],
3123 - 'div' => ['class' => [], 'id' => [], 'data-nonce' => []],
3124 - 'img' => ['src' => [], 'alt' => [], 'class' => []],
3125 - 'h3' => ['class' => []],
3126 - 'h4' => ['class' => []],
3127 - 'button' => ['type' => [], 'class' => [], 'data-product-id' => [], 'data-nonce' => [], 'data-product-type' => [], 'data-original-text' => [], 'data-mxchat-action' => []],
3128 - 'select' => ['class' => [], 'data-attribute' => []],
3129 - 'option' => ['value' => []],
3130 - 'span' => ['class' => []],
3131 - 'del' => [], 'ins' => [],
3132 - ]
3133 - );
3134 - $formatted_content = $this->format_transcript_message($content);
1877 + for ($i = $start_page; $i <= $end_page; $i++) {
1878 + $class = $i === $page ? 'mxchat-pagination-button active' : 'mxchat-pagination-button';
1879 + echo '<button class="' . $class . '" data-page="' . $i . '">' . $i . '</button>';
1880 + }
3135 1881
3136 - $has_rag = $is_bot && !empty($msg->rag_context);
1882 + if ($end_page < $total_pages) {
1883 + if ($end_page < $total_pages - 1) {
1884 + echo '<span class="mxchat-pagination-ellipsis">...</span>';
1885 + }
1886 + echo '<button class="mxchat-pagination-button" data-page="' . $total_pages . '">' . $total_pages . '</button>';
1887 + }
3137 1888
3138 - $formatted_messages[] = [
3139 - 'id' => $msg->id,
3140 - 'role' => $msg->role,
3141 - 'is_user' => $is_user,
3142 - 'is_bot' => $is_bot,
3143 - 'content' => $formatted_content,
3144 - 'timestamp' => wp_date('g:i A', strtotime($msg->timestamp . ' UTC')),
3145 - 'full_timestamp' => wp_date('F j, Y g:i A', strtotime($msg->timestamp . ' UTC')),
3146 - 'has_rag' => $has_rag
3147 - ];
1889 + if ($page < $total_pages) {
1890 + echo '<button class="mxchat-pagination-button" data-page="' . ($page + 1) . '">' . esc_html__('Next', 'mxchat') . ' &raquo;</button>';
1891 + }
1892 +
1893 + echo '</div>';
3148 1894 }
3149 1895
3150 - // First message timestamp for "started" display
3151 - $started = !empty($messages) ? wp_date('M j, Y g:i A', strtotime($messages[0]->timestamp . ' UTC')) : '-';
1896 + echo '</div>';
3152 1897
3153 1898 wp_send_json([
3154 - 'success' => true,
3155 - 'session_id' => $session_id,
3156 - 'user' => [
3157 - 'name' => $display_name,
3158 - 'sub' => $display_sub,
3159 - 'initials' => $initials,
3160 - 'email' => $user_email,
3161 - 'identifier' => $user_identifier
3162 - ],
3163 - 'page' => [
3164 - 'url' => $page_url,
3165 - 'title' => $page_title
3166 - ],
3167 - 'clicked_urls' => $clicked_urls,
3168 - 'messages' => $formatted_messages,
3169 - 'message_count' => count($messages),
3170 - 'started' => $started
1899 + 'html' => ob_get_clean(),
1900 + 'page' => $page,
1901 + 'total_pages' => $total_pages,
1902 + 'total_sessions' => $total_sessions
3171 1903 ]);
1904 +
3172 1905 wp_die();
3173 1906 }
3174 1907
3175 1908
@@ -3265,9 +1998,9 @@
3265 1998 $pinecone_manager = MxChat_Pinecone_Manager::get_instance();
3266 1999
3267 2000 // Display success message if all prompts were deleted
3268 2001 if (isset($_GET['all_deleted']) && $_GET['all_deleted'] === 'true') {
3269 - echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__('All knowledge has been deleted successfully.', 'mxchat') . '</p><button type="button" class="notice-dismiss"><span class="screen-reader-text">' . esc_html__('Dismiss this notice.', 'mxchat') . '</span></button></div>';
2002 + echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__('All knowledge has been deleted successfully.', 'mxchat') . '</p></div>';
3270 2003 }
3271 2004
3272 2005 // Set up pagination, search query, and content type filter
3273 2006 $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field($_GET['_wpnonce']) : '';
@@ -3273,9 +2006,9 @@
3273 2006 $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field($_GET['_wpnonce']) : '';
3274 2007 $search_query = (!empty($nonce) && wp_verify_nonce($nonce, 'mxchat_prompts_search_nonce') && isset($_GET['search'])) ? sanitize_text_field($_GET['search']) : '';
3275 2008 $content_type_filter = isset($_GET['content_type']) ? sanitize_key($_GET['content_type']) : ''; // ADDED 2.5.6
3276 2009 $current_page = isset($_GET['paged']) ? absint($_GET['paged']) : 1;
3277 - $per_page = 25;
2010 + $per_page = 10;
3278 2011
3279 2012 //error_log('DEBUG: Search query: ' . $search_query);
3280 2013 //error_log('DEBUG: Content type filter: ' . $content_type_filter);
3281 2014 //error_log('DEBUG: Current page: ' . $current_page);
@@ -3313,12 +2046,8 @@
3313 2046 $pinecone_options = $pinecone_manager->mxchat_get_bot_pinecone_options($current_bot_id);
3314 2047 $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
3315 2048 $pinecone_api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
3316 2049
3317 - // Get OpenAI Vector Store settings
3318 - $vectorstore_options = get_option('mxchat_openai_vectorstore_options', array());
3319 - $use_vectorstore = ($vectorstore_options['mxchat_use_openai_vectorstore'] ?? '0') === '1';
3320 -
3321 2050 //error_log('DEBUG: Bot ' . $current_bot_id . ' - Use Pinecone: ' . ($use_pinecone ? 'YES' : 'NO'));
3322 2051 //error_log('DEBUG: Bot ' . $current_bot_id . ' - Has API Key: ' . (!empty($pinecone_api_key) ? 'YES' : 'NO'));
3323 2052 //error_log('DEBUG: Bot ' . $current_bot_id . ' - Host: ' . ($pinecone_options['mxchat_pinecone_host'] ?? 'NOT SET'));
3324 2053 //error_log('DEBUG: Bot ' . $current_bot_id . ' - Namespace: ' . ($pinecone_options['mxchat_pinecone_namespace'] ?? 'NOT SET'));
@@ -3384,111 +2113,25 @@
3384 2113 if (!empty($where_clauses)) {
3385 2114 $sql_where = "WHERE " . implode(" AND ", $where_clauses);
3386 2115 }
3387 2116
3388 - // UPDATED 2.6.3: Count unique entries (by source_url) instead of individual rows
3389 - // This ensures pagination shows X entries per page, not X chunks
3390 - // Entries with empty source_url are counted individually
2117 + // Retrieve total number of prompts, considering filters
3391 2118 if (!empty($where_values)) {
3392 - // Count unique source_urls + count of rows with empty source_url
3393 - $count_query = $wpdb->prepare(
3394 - "SELECT (SELECT COUNT(DISTINCT source_url) FROM {$table_name} {$sql_where} AND source_url != '') +
3395 - (SELECT COUNT(*) FROM {$table_name} {$sql_where} AND (source_url = '' OR source_url IS NULL))",
3396 - array_merge($where_values, $where_values)
3397 - );
2119 + $count_query = $wpdb->prepare("SELECT COUNT(*) FROM {$table_name} {$sql_where}", $where_values);
3398 2120 } else {
3399 - $count_query = "SELECT (SELECT COUNT(DISTINCT source_url) FROM {$table_name} WHERE source_url != '') +
3400 - (SELECT COUNT(*) FROM {$table_name} WHERE source_url = '' OR source_url IS NULL)";
2121 + $count_query = "SELECT COUNT(*) FROM {$table_name} {$sql_where}";
3401 2122 }
3402 2123 $total_records = $wpdb->get_var($count_query);
3403 2124 $total_pages = ceil($total_records / $per_page);
3404 2125
3405 - // UPDATED 2.6.3: Get unique source_urls for pagination, then fetch all their rows
3406 - // Step 1: Get the source_urls for this page (distinct URLs ordered by latest timestamp)
3407 - if (!empty($where_values)) {
3408 - $urls_query = $wpdb->prepare(
3409 - "SELECT source_url, MAX(timestamp) as latest_ts FROM {$table_name} {$sql_where}
3410 - GROUP BY source_url ORDER BY latest_ts DESC LIMIT %d OFFSET %d",
3411 - array_merge($where_values, array($per_page, $offset))
3412 - );
3413 - } else {
3414 - $urls_query = $wpdb->prepare(
3415 - "SELECT source_url, MAX(timestamp) as latest_ts FROM {$table_name}
3416 - GROUP BY source_url ORDER BY latest_ts DESC LIMIT %d OFFSET %d",
3417 - $per_page, $offset
3418 - );
3419 - }
3420 - $page_urls = $wpdb->get_results($urls_query);
2126 + // Retrieve prompts from the database
2127 + $limit_offset_values = array_merge($where_values, array($per_page, $offset));
2128 + $prompts_query = $wpdb->prepare(
2129 + "SELECT * FROM {$table_name} {$sql_where} ORDER BY timestamp DESC LIMIT %d OFFSET %d",
2130 + ...$limit_offset_values
2131 + );
3421 2132
3422 - // Step 2: Fetch all rows for these source_urls
3423 - $prompts = array();
3424 - if (!empty($page_urls)) {
3425 - // Build URL order map to preserve newest-first ordering from step 1
3426 - $url_order_map = array();
3427 - $url_list = array();
3428 - $has_empty_url = false;
3429 - $order_index = 0;
3430 - foreach ($page_urls as $url_row) {
3431 - if (empty($url_row->source_url)) {
3432 - $has_empty_url = true;
3433 - $url_order_map['__empty__'] = $order_index++;
3434 - } else {
3435 - $url_list[] = $url_row->source_url;
3436 - $url_order_map[$url_row->source_url] = $order_index++;
3437 - }
3438 - }
3439 -
3440 - // Build query to fetch all rows for these URLs
3441 - $url_conditions = array();
3442 - $url_values = array();
3443 -
3444 - if (!empty($url_list)) {
3445 - $placeholders = implode(',', array_fill(0, count($url_list), '%s'));
3446 - $url_conditions[] = "source_url IN ($placeholders)";
3447 - $url_values = array_merge($url_values, $url_list);
3448 - }
3449 -
3450 - if ($has_empty_url) {
3451 - $url_conditions[] = "(source_url = '' OR source_url IS NULL)";
3452 - }
3453 -
3454 - if (!empty($url_conditions)) {
3455 - $url_where = "WHERE (" . implode(" OR ", $url_conditions) . ")";
3456 -
3457 - // Add original filters back
3458 - if (!empty($where_clauses)) {
3459 - $url_where .= " AND " . implode(" AND ", $where_clauses);
3460 - $url_values = array_merge($url_values, $where_values);
3461 - }
3462 -
3463 - if (!empty($url_values)) {
3464 - $prompts_query = $wpdb->prepare(
3465 - "SELECT * FROM {$table_name} {$url_where} ORDER BY timestamp DESC",
3466 - $url_values
3467 - );
3468 - } else {
3469 - $prompts_query = "SELECT * FROM {$table_name} {$url_where} ORDER BY timestamp DESC";
3470 - }
3471 -
3472 - $prompts = $wpdb->get_results($prompts_query);
3473 -
3474 - // Sort prompts by the original URL order (newest first), then by timestamp within each URL group
3475 - usort($prompts, function($a, $b) use ($url_order_map) {
3476 - $url_a = empty($a->source_url) ? '__empty__' : $a->source_url;
3477 - $url_b = empty($b->source_url) ? '__empty__' : $b->source_url;
3478 - $order_a = $url_order_map[$url_a] ?? PHP_INT_MAX;
3479 - $order_b = $url_order_map[$url_b] ?? PHP_INT_MAX;
3480 -
3481 - // First sort by URL order (newest URLs first)
3482 - if ($order_a !== $order_b) {
3483 - return $order_a - $order_b;
3484 - }
3485 -
3486 - // Within same URL, sort by timestamp DESC (newest chunks first)
3487 - return strtotime($b->timestamp) - strtotime($a->timestamp);
3488 - });
3489 - }
3490 - }
2133 + $prompts = $wpdb->get_results($prompts_query);
3491 2134 }
3492 2135
3493 2136 // ================================
3494 2137 // PAGINATION GENERATION
@@ -3495,35 +2138,29 @@
3495 2138 // ================================
3496 2139
3497 2140 // Generate pagination links
3498 2141 if ($total_pages > 1) {
3499 - // Build a clean base URL with only necessary parameters
3500 - $pagination_args = array('page' => 'mxchat-prompts');
3501 -
2142 + // Build base URL with necessary parameters
2143 + $base_url = add_query_arg('paged', '%#%');
2144 +
3502 2145 // Preserve bot_id parameter if multi-bot is active
3503 2146 if ($multibot_active && !empty($current_bot_id) && $current_bot_id !== 'default') {
3504 - $pagination_args['bot_id'] = $current_bot_id;
2147 + $base_url = add_query_arg('bot_id', $current_bot_id, $base_url);
3505 2148 }
3506 2149
3507 - // Preserve search query and nonce if present
2150 + // Preserve search query if present
3508 2151 if ($search_query) {
3509 - $pagination_args['search'] = $search_query;
3510 - if (!empty($nonce)) {
3511 - $pagination_args['_wpnonce'] = $nonce;
3512 - }
2152 + $base_url = add_query_arg('search', $search_query, $base_url);
3513 2153 }
3514 2154
3515 - // Preserve content type filter if present
2155 + // UPDATED 2.5.6: Preserve content type filter if present
3516 2156 if ($content_type_filter) {
3517 - $pagination_args['content_type'] = $content_type_filter;
2157 + $base_url = add_query_arg('content_type', $content_type_filter, $base_url);
3518 2158 }
3519 -
3520 - // Build clean base URL
3521 - $base_url = add_query_arg($pagination_args, admin_url('admin.php'));
3522 -
2159 +
3523 2160 $page_links = paginate_links(array(
3524 - 'base' => $base_url . '%_%',
3525 - 'format' => '&paged=%#%',
2161 + 'base' => $base_url,
2162 + 'format' => '',
3526 2163 'prev_text' => __('&laquo; Previous', 'mxchat'),
3527 2164 'next_text' => __('Next &raquo;', 'mxchat'),
3528 2165 'total' => $total_pages,
3529 2166 'current' => $current_page,
@@ -3535,50 +2172,1197 @@
3535 2172
3536 2173 // ================================
3537 2174 // PROCESSING STATUS RETRIEVAL
3538 2175 // ================================
2176 +
2177 + // Retrieve processing statuses
2178 + $pdf_url = get_transient('mxchat_last_pdf_url');
2179 + $sitemap_url = get_transient('mxchat_last_sitemap_url');
2180 + $pdf_status = $pdf_url ? $knowledge_manager->mxchat_get_pdf_processing_status($pdf_url) : false;
2181 + $sitemap_status = $sitemap_url ? $knowledge_manager->mxchat_get_sitemap_processing_status($sitemap_url) : false;
3539 2182
3540 - // Retrieve processing statuses using queue-based method
3541 - $processing_statuses = $knowledge_manager->mxchat_get_processing_statuses();
3542 - $pdf_status = $processing_statuses['pdf_status'];
3543 - $sitemap_status = $processing_statuses['sitemap_status'];
3544 - $is_processing = $processing_statuses['is_processing'];
2183 + $is_processing = ($pdf_status && ($pdf_status['status'] === 'processing' || $pdf_status['status'] === 'error'))
2184 + || ($sitemap_status && ($sitemap_status['status'] === 'processing' || $sitemap_status['status'] === 'error'));
3545 2185
3546 2186 //error_log('=== DEBUG: mxchat_create_prompts_page data preparation completed ===');
3547 2187
3548 - // ================================
3549 - // RENDER PAGE WITH NEW SIDEBAR LAYOUT
3550 - // ================================
2188 + ?>
3551 2189
3552 - // Include the new knowledge page template
3553 - require_once plugin_dir_path(__FILE__) . 'admin-knowledge-page.php';
2190 + <div class="wrap mxchat-wrapper">
2191 + <!-- Hero Section -->
2192 + <div class="mxchat-hero">
2193 + <h1 class="mxchat-main-title">
2194 + <span class="mxchat-gradient-text">Knowledge Base</span> Manager
2195 + </h1>
2196 + <p class="mxchat-hero-subtitle">
2197 + <?php esc_html_e('Enhance your AI chatbot with custom knowledge. Import, manage, and organize your content to keep responses accurate and relevant.', 'mxchat'); ?>
2198 + </p>
2199 + </div>
3554 2200
3555 - // Package all page data for the render function
3556 - $page_data = array(
3557 - 'prompts' => $prompts,
3558 - 'total_records' => $total_records,
3559 - 'total_pages' => $total_pages,
3560 - 'current_page' => $current_page,
3561 - 'per_page' => $per_page,
3562 - 'page_links' => $page_links,
3563 - 'search_query' => $search_query,
3564 - 'content_type_filter' => $content_type_filter,
3565 - 'data_source' => $data_source,
3566 - 'use_pinecone' => $use_pinecone,
3567 - 'use_vectorstore' => $use_vectorstore,
3568 - 'multibot_active' => $multibot_active,
3569 - 'current_bot_id' => $current_bot_id,
3570 - 'pdf_status' => $pdf_status,
3571 - 'sitemap_status' => $sitemap_status,
3572 - 'is_processing' => $is_processing,
3573 - 'total_in_database' => $total_in_database ?? 0,
3574 - 'showing_recent_only' => $showing_recent_only ?? false,
3575 - );
2201 + <div class="mxchat-content">
2202 +
2203 + <!-- Multi-Bot Selector (only shows if multi-bot addon is active) -->
2204 + <?php if (class_exists('MxChat_Multi_Bot_Manager')) :
2205 + $multi_bot_manager = MxChat_Multi_Bot_Core_Manager::get_instance();
2206 + $available_bots = $multi_bot_manager->get_available_bots();
2207 +
2208 + // Get saved bot selection (user-specific first, then site-wide default)
2209 + $user_id = get_current_user_id();
2210 + $saved_bot_id = get_user_meta($user_id, 'mxchat_selected_knowledge_bot', true);
2211 + if (empty($saved_bot_id)) {
2212 + $saved_bot_id = get_option('mxchat_current_knowledge_bot', 'default');
2213 + }
2214 +
2215 + // Allow URL override but default to saved selection
2216 + $current_bot_id = isset($_GET['bot_id']) ? sanitize_key($_GET['bot_id']) : $saved_bot_id;
2217 + ?>
2218 + <div class="mxchat-card" style="margin-bottom: 20px;">
2219 + <div class="mxchat-bot-selector-wrapper" style="display: flex; align-items: center; gap: 15px;">
2220 + <label for="mxchat-bot-selector" style="font-weight: 600;">
2221 + <?php esc_html_e('Select Bot Database:', 'mxchat'); ?>
2222 + </label>
2223 + <select id="mxchat-bot-selector" class="mxchat-bot-selector" style="min-width: 200px;">
2224 + <?php foreach ($available_bots as $bot_id => $bot_name) : ?>
2225 + <option value="<?php echo esc_attr($bot_id); ?>" <?php selected($current_bot_id, $bot_id); ?>>
2226 + <?php echo esc_html($bot_name); ?>
2227 + </option>
2228 + <?php endforeach; ?>
2229 + </select>
2230 + <span class="mxchat-bot-info" style="color: #666; font-size: 13px;">
2231 + <?php esc_html_e('Content will be added to the selected bot\'s knowledge base', 'mxchat'); ?>
2232 + </span>
2233 + <span id="mxchat-bot-save-status" style="display: none; color: #00a32a; font-size: 13px;">
2234 + ✓ <?php esc_html_e('Saved', 'mxchat'); ?>
2235 + </span>
2236 + </div>
2237 + </div>
3576 2238
3577 - // Render the new sidebar-based page
3578 - mxchat_render_knowledge_page($this, $knowledge_manager, $page_data);
2239 + <?php
2240 + // Set a flag so we know multi-bot is active throughout the page
2241 + $multibot_active = true;
2242 + else:
2243 + $current_bot_id = 'default';
2244 + $multibot_active = false;
2245 + endif;
2246 + ?>
2247 +
2248 + <!-- Tab Navigation -->
2249 + <div class="mxchat-kb-tabs-nav">
2250 + <button class="mxchat-kb-tab-button active" data-tab="import">
2251 + <?php esc_html_e('Knowledge Import', 'mxchat'); ?>
2252 + </button>
2253 + <button class="mxchat-kb-tab-button" data-tab="sync">
2254 + <?php esc_html_e('Settings', 'mxchat'); ?>
2255 + </button>
2256 + <button class="mxchat-kb-tab-button" data-tab="pinecone">
2257 + <?php esc_html_e('Pinecone Settings', 'mxchat'); ?>
2258 + </button>
2259 + </div>
2260 +
2261 + <div class="mxchat-kb-tabs-content">
2262 + <div id="mxchat-kb-tab-import" class="mxchat-kb-tab-content active">
2263 + <!-- Import Options Card -->
2264 + <div class="mxchat-card">
2265 +
2266 + <h2><?php esc_html_e('Knowledge Import Settings', 'mxchat'); ?></h2>
2267 +
2268 + <?php
2269 + // Check if the appropriate embedding API key exists
2270 + $embedding_model = isset($this->options['embedding_model']) ? esc_attr($this->options['embedding_model']) : 'text-embedding-ada-002';
2271 + $has_openai_key = !empty($this->options['api_key']);
2272 + $has_voyage_key = !empty($this->options['voyage_api_key']);
2273 + $has_gemini_key = !empty($this->options['gemini_api_key']);
2274 +
2275 + // Determine if they have the needed API key for their selected embedding model
2276 + $has_required_key = false;
2277 + $required_key_type = '';
2278 +
2279 + if (strpos($embedding_model, 'text-embedding-') !== false && $has_openai_key) {
2280 + $has_required_key = true;
2281 + $required_key_type = 'OpenAI';
2282 + } elseif (strpos($embedding_model, 'voyage-') !== false && $has_voyage_key) {
2283 + $has_required_key = true;
2284 + $required_key_type = 'Voyage AI';
2285 + } elseif (strpos($embedding_model, 'gemini-embedding-') !== false && $has_gemini_key) {
2286 + $has_required_key = true;
2287 + $required_key_type = 'Google Gemini';
2288 + } elseif (strpos($embedding_model, 'text-embedding-') !== false) {
2289 + $required_key_type = 'OpenAI';
2290 + } elseif (strpos($embedding_model, 'voyage-') !== false) {
2291 + $required_key_type = 'Voyage AI';
2292 + } elseif (strpos($embedding_model, 'gemini-embedding-') !== false) {
2293 + $required_key_type = 'Google Gemini';
2294 + }
2295 + ?>
2296 +
2297 + <div class="mxchat-knowledge-warning <?php echo $has_required_key ? 'success' : 'warning'; ?>">
2298 + <?php if ($has_required_key): ?>
2299 + <p><span class="dashicons dashicons-yes-alt"></span> <?php echo wp_kses_post(sprintf(__('We detected your %s API key. <strong>Remember to add credits to your %s account</strong> before using the knowledgebase.', 'mxchat'), $required_key_type, $required_key_type)); ?></p>
2300 + <?php else: ?>
2301 + <p><span class="dashicons dashicons-warning"></span> <strong><?php esc_html_e('Important:', 'mxchat'); ?></strong> <?php echo sprintf(esc_html__('Before importing knowledge, you must add a %s API key with sufficient credits in the Chatbot settings.', 'mxchat'), $required_key_type); ?> <a href="<?php echo admin_url('admin.php?page=mxchat-max'); ?>"><?php esc_html_e('Go to API Key Settings', 'mxchat'); ?></a></p>
2302 + <?php endif; ?>
2303 + </div>
2304 +
2305 + <!-- Import Options Section -->
2306 + <div class="mxchat-import-section">
2307 + <h3><?php esc_html_e('Import Options', 'mxchat'); ?></h3>
2308 +
2309 + <!-- Import Options Grid -->
2310 + <div class="mxchat-import-options">
2311 + <!-- WordPress Import Option -->
2312 + <button type="button" id="mxchat-open-content-selector" class="mxchat-import-box mxchat-import-wordpress" data-option="wordpress">
2313 + <div class="mxchat-import-icon">
2314 + <span class="dashicons dashicons-wordpress"></span>
2315 + </div>
2316 + <div class="mxchat-import-content">
2317 + <h4><?php esc_html_e('WordPress Content', 'mxchat'); ?></h4>
2318 + <p><?php esc_html_e('Import specific posts and pages to your knowledge base.', 'mxchat'); ?></p>
2319 + </div>
2320 + <div class="mxchat-recommended-tag"><?php esc_html_e('Recommended', 'mxchat'); ?></div>
2321 + </button>
2322 +
2323 + <!-- Sitemap Import Option -->
2324 + <button type="button" class="mxchat-import-box" data-option="sitemap" data-placeholder="<?php esc_attr_e('Enter sitemap URL here', 'mxchat'); ?>" data-type="sitemap">
2325 + <div class="mxchat-import-icon">
2326 + <span class="dashicons dashicons-admin-site-alt"></span>
2327 + </div>
2328 + <div class="mxchat-import-content">
2329 + <h4><?php esc_html_e('Sitemap Import', 'mxchat'); ?></h4>
2330 + <p><?php esc_html_e('Use a content-specific sub-sitemap, not the sitemap index.', 'mxchat'); ?></p>
2331 + </div>
2332 + </button>
2333 +
2334 + <!-- Direct URL Import Option -->
2335 + <button type="button" class="mxchat-import-box" data-option="url" data-placeholder="<?php esc_attr_e('Enter webpage URL here', 'mxchat'); ?>" data-type="url">
2336 + <div class="mxchat-import-icon">
2337 + <span class="dashicons dashicons-admin-links"></span>
2338 + </div>
2339 + <div class="mxchat-import-content">
2340 + <h4><?php esc_html_e('Direct URL', 'mxchat'); ?></h4>
2341 + <p><?php esc_html_e('Import content from any webpage.', 'mxchat'); ?></p>
2342 + </div>
2343 + </button>
2344 +
2345 + <!-- Direct Content Import Option -->
2346 + <button type="button" class="mxchat-import-box" data-option="content" data-type="content">
2347 + <div class="mxchat-import-icon">
2348 + <span class="dashicons dashicons-editor-paste-text"></span>
2349 + </div>
2350 + <div class="mxchat-import-content">
2351 + <h4><?php esc_html_e('Direct Content', 'mxchat'); ?></h4>
2352 + <p><?php esc_html_e('Submit content to be vectorized.', 'mxchat'); ?></p>
2353 + </div>
2354 + </button>
2355 +
2356 + <!-- PDF Import Option -->
2357 + <button type="button" class="mxchat-import-box" data-option="pdf" data-placeholder="<?php esc_attr_e('Enter PDF URL here', 'mxchat'); ?>" data-type="pdf">
2358 + <div class="mxchat-import-icon">
2359 + <span class="dashicons dashicons-media-document"></span>
2360 + </div>
2361 + <div class="mxchat-import-content">
2362 + <h4><?php esc_html_e('PDF Import', 'mxchat'); ?></h4>
2363 + <p><?php esc_html_e('Import knowledge from PDF documents.', 'mxchat'); ?></p>
2364 + </div>
2365 + </button>
2366 + </div>
2367 +
2368 + <!-- Input Areas for URL and Content -->
2369 + <div class="mxchat-import-input-area" id="mxchat-url-input-area" style="display: none;">
2370 + <?php if (!$is_processing) : ?>
2371 + <form id="mxchat-url-form" method="post" action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_submit_sitemap')); ?>">
2372 + <?php wp_nonce_field('mxchat_submit_sitemap_action', 'mxchat_submit_sitemap_nonce'); ?>
2373 + <input type="hidden" name="import_type" id="import_type" value="url">
2374 + <?php if ($multibot_active && $current_bot_id !== 'default') : ?>
2375 + <input type="hidden" name="bot_id" value="<?php echo esc_attr($current_bot_id); ?>">
2376 + <?php endif; ?>
2377 + <div class="mxchat-url-input-group">
2378 + <input type="url"
2379 + name="sitemap_url"
2380 + id="sitemap_url"
2381 + placeholder="<?php esc_attr_e('Enter URL here', 'mxchat'); ?>"
2382 + required />
2383 + <button type="submit"
2384 + name="submit_sitemap"
2385 + class="mxchat-button-primary">
2386 + <?php esc_html_e('Import', 'mxchat'); ?>
2387 + </button>
2388 + </div>
2389 + <p class="mxchat-url-description" id="url-description-text"></p>
2390 + </form>
2391 + <?php endif; ?>
2392 + </div>
2393 +
2394 + <div class="mxchat-import-input-area" id="mxchat-content-input-area" style="display: none;">
2395 + <?php if (!$is_processing) : ?>
2396 + <form id="mxchat-content-form" method="post" action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_submit_content')); ?>">
2397 + <?php wp_nonce_field('mxchat_submit_content_action', 'mxchat_submit_content_nonce'); ?>
2398 + <?php if ($multibot_active && $current_bot_id !== 'default') : ?>
2399 + <input type="hidden" name="bot_id" value="<?php echo esc_attr($current_bot_id); ?>">
2400 + <?php endif; ?>
2401 + <div class="mxchat-form-group">
2402 + <textarea
2403 + name="article_content"
2404 + id="article_content"
2405 + placeholder="<?php esc_attr_e('Enter your content here...', 'mxchat'); ?>"
2406 + required
2407 + rows="6"
2408 + ></textarea>
2409 + </div>
2410 + <div class="mxchat-form-group">
2411 + <input type="url"
2412 + name="article_url"
2413 + id="article_url"
2414 + placeholder="<?php esc_attr_e('Enter source URL (Optional)', 'mxchat'); ?>">
2415 + <div class="mxchat-url-helper" style="margin-top: 8px; display: flex; align-items: center; gap: 8px;">
2416 + <label style="display: flex; align-items: center; gap: 6px; font-size: 13px; color: #666; cursor: pointer;">
2417 + <input type="checkbox" id="mxchat-unique-url-toggle" style="margin: 0;">
2418 + <?php esc_html_e('Generate unique URL for duplicate content', 'mxchat'); ?>
2419 + </label>
2420 + <button type="button"
2421 + id="mxchat-generate-unique-url"
2422 + class="mxchat-button-secondary"
2423 + style="padding: 4px 12px; font-size: 12px; display: none;">
2424 + <span class="dashicons dashicons-randomize" style="font-size: 14px; margin-top: 2px;"></span>
2425 + <?php esc_html_e('Generate Unique', 'mxchat'); ?>
2426 + </button>
2427 + </div>
2428 + <p class="description" style="margin-top: 8px; font-size: 12px; color: #666;">
2429 + <?php esc_html_e('Enable this option to submit multiple entries with the same base URL. A unique reference will be appended (e.g., ?ref=1731234567890).', 'mxchat'); ?>
2430 + </p>
2431 + </div>
2432 + <button type="submit"
2433 + name="submit_content"
2434 + class="mxchat-button-primary">
2435 + <?php esc_html_e('Import Content', 'mxchat'); ?>
2436 + </button>
2437 + </form>
2438 + <?php endif; ?>
2439 + </div>
2440 + </div>
2441 +
2442 + <!-- PDF Processing Status - ONLY PROCESSING -->
2443 + <?php if ($pdf_status && $pdf_status['status'] === 'processing') : ?>
2444 + <div class="mxchat-status-card" data-card-type="pdf">
2445 + <div class="mxchat-status-header">
2446 + <h4><?php esc_html_e('PDF Processing Status', 'mxchat'); ?></h4>
2447 +
2448 + <!-- Processing controls -->
2449 + <div class="mxchat-processing-controls">
2450 + <form method="post" class="mxchat-stop-form"
2451 + action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_stop_processing')); ?>">
2452 + <?php wp_nonce_field('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce'); ?>
2453 + <button type="submit" name="stop_processing" class="mxchat-button-secondary">
2454 + <?php esc_html_e('Stop Processing', 'mxchat'); ?>
2455 + </button>
2456 + </form>
2457 +
2458 + <button type="button" class="mxchat-manual-batch-btn"
2459 + data-process-type="pdf"
2460 + data-url="<?php echo esc_attr(get_transient('mxchat_last_pdf_url')); ?>">
2461 + <?php esc_html_e('Process Batch', 'mxchat'); ?>
2462 + </button>
2463 + </div>
2464 + </div>
2465 +
2466 + <div class="mxchat-progress-bar">
2467 + <div class="mxchat-progress-fill" style="width: <?php echo esc_attr($pdf_status['percentage']); ?>%"></div>
2468 + </div>
2469 +
2470 + <div class="mxchat-status-details">
2471 + <p><?php printf(
2472 + esc_html__('Progress: %1$d of %2$d pages (%3$d%%)', 'mxchat'),
2473 + absint($pdf_status['processed_pages']),
2474 + absint($pdf_status['total_pages']),
2475 + absint($pdf_status['percentage'])
2476 + ); ?></p>
2477 +
2478 + <?php if (!empty($pdf_status['failed_pages']) && $pdf_status['failed_pages'] > 0) : ?>
2479 + <p><strong><?php esc_html_e('Failed pages:', 'mxchat'); ?></strong> <?php echo esc_html($pdf_status['failed_pages']); ?></p>
2480 + <?php endif; ?>
2481 +
2482 + <p><strong><?php esc_html_e('Status:', 'mxchat'); ?></strong> <?php echo esc_html(ucfirst($pdf_status['status'])); ?></p>
2483 + <p><strong><?php esc_html_e('Last update:', 'mxchat'); ?></strong> <?php echo esc_html($pdf_status['last_update']); ?></p>
2484 + </div>
2485 + </div>
2486 + <?php endif; ?>
2487 +
2488 + <!-- Sitemap Processing Status - ONLY PROCESSING -->
2489 + <?php if ($sitemap_status && $sitemap_status['status'] === 'processing') : ?>
2490 + <div class="mxchat-status-card" data-card-type="sitemap">
2491 + <div class="mxchat-status-header">
2492 + <h4><?php esc_html_e('Sitemap Processing Status', 'mxchat'); ?></h4>
2493 +
2494 + <!-- Processing controls -->
2495 + <div class="mxchat-processing-controls">
2496 + <form method="post" class="mxchat-stop-form"
2497 + action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_stop_processing')); ?>">
2498 + <?php wp_nonce_field('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce'); ?>
2499 + <button type="submit" name="stop_processing" class="mxchat-button-secondary">
2500 + <?php esc_html_e('Stop Processing', 'mxchat'); ?>
2501 + </button>
2502 + </form>
2503 +
2504 + <button type="button" class="mxchat-manual-batch-btn"
2505 + data-process-type="sitemap"
2506 + data-url="<?php echo esc_attr(get_transient('mxchat_last_sitemap_url')); ?>">
2507 + <?php esc_html_e('Process Batch', 'mxchat'); ?>
2508 + </button>
2509 + </div>
2510 + </div>
2511 +
2512 + <div class="mxchat-progress-bar">
2513 + <div class="mxchat-progress-fill" style="width: <?php echo esc_attr($sitemap_status['percentage']); ?>%"></div>
2514 + </div>
2515 +
2516 + <div class="mxchat-status-details">
2517 + <p><?php printf(
2518 + esc_html__('Progress: %1$d of %2$d URLs (%3$d%%)', 'mxchat'),
2519 + absint($sitemap_status['processed_urls']),
2520 + absint($sitemap_status['total_urls']),
2521 + absint($sitemap_status['percentage'])
2522 + ); ?></p>
2523 +
2524 + <?php if (!empty($sitemap_status['failed_urls']) && $sitemap_status['failed_urls'] > 0) : ?>
2525 + <p><strong><?php esc_html_e('Failed URLs:', 'mxchat'); ?></strong> <?php echo esc_html($sitemap_status['failed_urls']); ?></p>
2526 + <?php endif; ?>
2527 +
2528 + <p><strong><?php esc_html_e('Status:', 'mxchat'); ?></strong> <?php echo esc_html(ucfirst($sitemap_status['status'])); ?></p>
2529 + <p><strong><?php esc_html_e('Last update:', 'mxchat'); ?></strong> <?php echo esc_html($sitemap_status['last_update']); ?></p>
2530 +
2531 + <?php if (!empty($sitemap_status['error']) || !empty($sitemap_status['last_error'])) : ?>
2532 + <div class="mxchat-error-notice">
2533 + <?php if (!empty($sitemap_status['error'])) : ?>
2534 + <p class="error"><?php echo esc_html($sitemap_status['error']); ?></p>
2535 + <?php endif; ?>
2536 + <?php if (!empty($sitemap_status['last_error'])) : ?>
2537 + <p class="last-error"><?php echo esc_html__('Last error:', 'mxchat') . ' ' . esc_html($sitemap_status['last_error']); ?></p>
2538 + <?php endif; ?>
2539 + </div>
2540 + <?php endif; ?>
2541 + </div>
2542 + </div>
2543 + <?php endif; ?>
2544 +
2545 + <!-- Single URL Submission Status -->
2546 + <?php
2547 + // Get single URL status
2548 + $single_url_status = $this->knowledge_manager->mxchat_get_single_url_status();
2549 + $is_active_processing =
2550 + ($sitemap_status && $sitemap_status['status'] === 'processing') ||
2551 + ($pdf_status && $pdf_status['status'] === 'processing');
2552 + ?>
2553 +
2554 + <div id="mxchat-single-url-status-container" <?php echo $is_active_processing ? 'style="display:none;"' : ''; ?>>
2555 + <?php if ($single_url_status && !$is_active_processing) : ?>
2556 + <div class="mxchat-status-card">
2557 + <div class="mxchat-status-header">
2558 + <h4><?php esc_html_e('Last URL Submission', 'mxchat'); ?></h4>
2559 + <?php if ($single_url_status['status'] === 'failed') : ?>
2560 + <span class="mxchat-status-badge mxchat-status-failed"><?php esc_html_e('Failed', 'mxchat'); ?></span>
2561 + <?php else : ?>
2562 + <span class="mxchat-status-badge mxchat-status-success"><?php esc_html_e('Success', 'mxchat'); ?></span>
2563 + <?php endif; ?>
2564 + </div>
2565 + <div class="mxchat-status-details">
2566 + <p><strong><?php esc_html_e('URL:', 'mxchat'); ?></strong>
2567 + <a href="<?php echo esc_url($single_url_status['url']); ?>" target="_blank">
2568 + <?php echo esc_html(strlen($single_url_status['url']) > 60 ? substr($single_url_status['url'], 0, 57) . '...' : $single_url_status['url']); ?>
2569 + </a>
2570 + </p>
2571 + <p><strong><?php esc_html_e('Submitted:', 'mxchat'); ?></strong> <?php echo esc_html($single_url_status['human_time']); ?></p>
2572 +
2573 + <?php if ($single_url_status['status'] === 'failed' && !empty($single_url_status['error'])) : ?>
2574 + <div class="mxchat-error-notice">
2575 + <p class="error"><?php echo esc_html($single_url_status['error']); ?></p>
2576 + </div>
2577 + <?php endif; ?>
2578 +
2579 + <?php if ($single_url_status['status'] === 'complete') : ?>
2580 + <p><strong><?php esc_html_e('Content Length:', 'mxchat'); ?></strong> <?php echo esc_html($single_url_status['content_length']); ?> <?php esc_html_e('characters', 'mxchat'); ?></p>
2581 + <p><strong><?php esc_html_e('Embedding Dimensions:', 'mxchat'); ?></strong> <?php echo esc_html($single_url_status['embedding_dimensions']); ?></p>
2582 + <?php endif; ?>
2583 + </div>
2584 + </div>
2585 + <?php endif; ?>
2586 + </div>
2587 +
2588 + <!-- Completed Status Cards - Handles completed PDF/Sitemap processing -->
2589 + <?php
2590 + echo $knowledge_manager->mxchat_render_completed_status_cards();
2591 + ?>
2592 +
2593 + </div>
2594 +
2595 + <!-- Knowledge Base Table Card -->
2596 + <div class="mxchat-card">
2597 + <div class="mxchat-card-header">
2598 + <h2>
2599 + <?php esc_html_e('Knowledge Base', 'mxchat'); ?>
2600 + <span class="mxchat-record-count">
2601 + <?php if ($showing_recent_only && $total_in_database > 1000): ?>
2602 + (<?php echo esc_html($total_records); ?> of <?php echo esc_html(number_format($total_in_database)); ?> total - recent entries only)
2603 + <?php else: ?>
2604 + (<?php echo esc_html($total_records); ?>)
2605 + <?php endif; ?>
2606 + </span>
2607 + <!-- Data source badge -->
2608 + <?php if ($use_pinecone) : ?>
2609 + <span class="mxchat-data-source-badge pinecone" style="display: inline-flex; align-items: center; gap: 4px; padding: 4px 8px; border-radius: 12px; font-size: 12px; font-weight: 500; margin-left: 8px; background: #e3f2fd; color: #1976d2;">
2610 + <span class="dashicons dashicons-cloud"></span>
2611 + <?php esc_html_e('Pinecone', 'mxchat'); ?>
2612 + </span>
2613 + <?php else : ?>
2614 + <span class="mxchat-data-source-badge wordpress" style="display: inline-flex; align-items: center; gap: 4px; padding: 4px 8px; border-radius: 12px; font-size: 12px; font-weight: 500; margin-left: 8px; background: #f3e5f5; color: #7b1fa2;">
2615 + <span class="dashicons dashicons-database-view"></span>
2616 + <?php esc_html_e('WordPress DB', 'mxchat'); ?>
2617 + </span>
2618 + <?php endif; ?>
2619 + </h2>
2620 + <div class="mxchat-header-actions">
2621 + <!-- Search -->
2622 + <form method="get" id="knowledge-search" class="mxchat-search-form">
2623 + <?php wp_nonce_field('mxchat_prompts_search_nonce'); ?>
2624 + <input type="hidden" name="page" value="mxchat-prompts" />
2625 + <?php if ($multibot_active && !empty($current_bot_id) && $current_bot_id !== 'default') : ?>
2626 + <input type="hidden" name="bot_id" value="<?php echo esc_attr($current_bot_id); ?>" />
2627 + <?php endif; ?>
2628 + <div class="mxchat-search-group">
2629 + <span class="dashicons dashicons-search"></span>
2630 + <input type="text"
2631 + name="search"
2632 + placeholder="<?php esc_attr_e('Search Knowledge', 'mxchat'); ?>"
2633 + value="<?php echo esc_attr($search_query); ?>" />
2634 + </div>
2635 +
2636 + <!-- ADDED 2.5.6: Content Type Filter -->
2637 + <div class="mxchat-filter-group">
2638 + <select name="content_type" id="mxchat-content-type-filter" onchange="this.form.submit()">
2639 + <option value=""><?php esc_html_e('All Content Types', 'mxchat'); ?></option>
2640 + <option value="post" <?php selected($content_type_filter, 'post'); ?>><?php esc_html_e('Posts', 'mxchat'); ?></option>
2641 + <option value="page" <?php selected($content_type_filter, 'page'); ?>><?php esc_html_e('Pages', 'mxchat'); ?></option>
2642 + <option value="product" <?php selected($content_type_filter, 'product'); ?>><?php esc_html_e('Products', 'mxchat'); ?></option>
2643 + <option value="pdf" <?php selected($content_type_filter, 'pdf'); ?>><?php esc_html_e('PDFs', 'mxchat'); ?></option>
2644 + <option value="url" <?php selected($content_type_filter, 'url'); ?>><?php esc_html_e('URLs', 'mxchat'); ?></option>
2645 + <option value="content" <?php selected($content_type_filter, 'content'); ?>><?php esc_html_e('Manual Content', 'mxchat'); ?></option>
2646 + </select>
2647 + </div>
2648 + </form>
2649 +
2650 + <form method="post"
2651 + action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_delete_all_prompts')); ?>"
2652 + class="mxchat-delete-form"
2653 + onsubmit="return confirm('<?php esc_attr_e('Are you sure you want to delete all knowledge? This action cannot be undone.', 'mxchat'); ?>');">
2654 + <?php wp_nonce_field('mxchat_delete_all_prompts_action', 'mxchat_delete_all_prompts_nonce'); ?>
2655 + <input type="hidden" name="data_source" value="<?php echo esc_attr($data_source); ?>" />
2656 + <!-- Always include bot_id, even if it's 'default' -->
2657 + <input type="hidden" name="bot_id" value="<?php echo esc_attr($current_bot_id); ?>" />
2658 + <button type="submit" name="delete_all_prompts" class="mxchat-button-danger">
2659 + <span class="dashicons dashicons-trash"></span>
2660 + <?php esc_html_e('Delete All', 'mxchat'); ?>
2661 + </button>
2662 + </form>
2663 + </div>
2664 + </div>
2665 +
2666 + <!-- Large Database Info Banner -->
2667 + <?php if ($showing_recent_only && $total_in_database > 500): ?>
2668 + <div class="mxchat-info-banner recent-only" style="display: flex; align-items: center; gap: 8px; padding: 12px 16px; margin-bottom: 20px; border-radius: 6px; font-size: 14px; background: #e3f2fd; border-left: 4px solid #2196f3; color: #1565c0;">
2669 + <span class="dashicons dashicons-info"></span>
2670 + <span>
2671 + <?php printf(
2672 + esc_html__('Browsing shows %s of %s total entries. Use the search box to find any entry in your complete database - search works across all entries!', 'mxchat'),
2673 + number_format($total_records),
2674 + number_format($total_in_database)
2675 + ); ?>
2676 + </span>
2677 + </div>
2678 + <?php endif; ?>
2679 +
2680 + <!-- Data Source Info Banner -->
2681 + <?php if ($use_pinecone) : ?>
2682 + <div class="mxchat-info-banner pinecone" style="display: flex; align-items: center; gap: 8px; padding: 12px 16px; margin-bottom: 20px; border-radius: 6px; font-size: 14px; background: #e8f5e8; border-left: 4px solid #4caf50; color: #2e7d2e;">
2683 + <span class="dashicons dashicons-cloud"></span>
2684 + <span><?php esc_html_e('Refresh page to see new content. New entires or deletions can take up to 60 seconds to reflect! Refreshing too early means content won\'t show and you\'ll need to refresh again.', 'mxchat'); ?></span>
2685 + </div>
2686 + <?php else : ?>
2687 + <div class="mxchat-info-banner wordpress" style="display: flex; align-items: center; gap: 8px; padding: 12px 16px; margin-bottom: 20px; border-radius: 6px; font-size: 14px; background: #fff3e0; border-left: 4px solid #ff9800; color: #e65100;">
2688 + <span class="dashicons dashicons-database-view"></span>
2689 + <span><?php esc_html_e('Refresh page to see new content, but wait 5-10 seconds first! Refreshing too early means content won\'t show and you\'ll need to refresh again.', 'mxchat'); ?></span>
2690 + </div>
2691 + <?php endif; ?>
2692 +
2693 + <!-- Table -->
2694 + <div class="mxchat-table-wrapper">
2695 + <table class="mxchat-records-table">
2696 + <thead>
2697 + <tr>
2698 + <th><?php esc_html_e('ID', 'mxchat'); ?></th>
2699 + <th><?php esc_html_e('Content', 'mxchat'); ?></th>
2700 + <th><?php esc_html_e('Source', 'mxchat'); ?></th>
2701 + <?php if ($data_source === 'pinecone') : ?>
2702 + <th><?php esc_html_e('Vector ID', 'mxchat'); ?></th>
2703 + <?php endif; ?>
2704 + <th><?php esc_html_e('Actions', 'mxchat'); ?></th>
2705 + </tr>
2706 + </thead>
2707 + <tbody>
2708 + <?php if ($prompts) : ?>
2709 + <?php foreach ($prompts as $index => $prompt) : ?>
2710 + <tr id="prompt-<?php echo esc_attr($prompt->id); ?>"
2711 + data-source="<?php echo esc_attr($data_source); ?>"
2712 + <?php if ($data_source === 'pinecone') : ?>
2713 + style="background: rgba(33, 150, 243, 0.02);"
2714 + <?php endif; ?>>
2715 + <td>
2716 + <?php if ($data_source === 'pinecone') : ?>
2717 + <?php echo esc_html($index + 1 + (($current_page - 1) * $per_page)); ?>
2718 + <?php else : ?>
2719 + <?php echo esc_html($prompt->id); ?>
2720 + <?php endif; ?>
2721 + </td>
2722 + <td class="mxchat-content-cell">
2723 + <div class="mxchat-accordion-wrapper">
2724 + <div class="mxchat-content-preview">
2725 + <?php
2726 + $content = $prompt->article_content;
2727 + $preview_length = 150;
2728 + $content_preview = mb_strlen($content) > $preview_length
2729 + ? mb_substr($content, 0, $preview_length) . '...'
2730 + : $content;
2731 + ?>
2732 + <span class="preview-text"><?php echo esc_html($content_preview); ?></span>
2733 + <?php if (mb_strlen($content) > $preview_length) : ?>
2734 + <button class="mxchat-expand-toggle" type="button">
2735 + <span class="dashicons dashicons-arrow-down-alt2"></span>
2736 + </button>
2737 + <?php endif; ?>
2738 + </div>
2739 + <div class="mxchat-content-full" style="display: none;">
2740 + <div class="content-view">
2741 + <?php
2742 + // Check if content contains Hebrew characters
2743 + if (preg_match('/[\x{0590}-\x{05FF}]/u', $content)) {
2744 + // Apply RTL direction for Hebrew content
2745 + echo '<div dir="rtl" lang="he" class="rtl-content">';
2746 + echo wp_kses_post(wpautop($content));
2747 + echo '</div>';
2748 + } else {
2749 + echo wp_kses_post(wpautop($content));
2750 + }
2751 + ?>
2752 + </div>
2753 + <?php if ($data_source === 'wordpress') : ?>
2754 + <textarea class="content-edit" style="display:none;"
2755 + <?php if (preg_match('/[\x{0590}-\x{05FF}]/u', $prompt->article_content)) echo 'dir="rtl" lang="he"'; ?>>
2756 + <?php echo htmlspecialchars($prompt->article_content, ENT_QUOTES, 'UTF-8'); ?>
2757 + </textarea>
2758 + <?php endif; ?>
2759 + </div>
2760 + </div>
2761 + </td>
2762 + <td class="mxchat-url-cell">
2763 + <div class="url-view">
2764 + <?php if (!empty($prompt->source_url)) : ?>
2765 + <a href="<?php echo esc_url($prompt->source_url); ?>" target="_blank">
2766 + <span class="dashicons dashicons-external"></span>
2767 + <?php esc_html_e('View Source', 'mxchat'); ?>
2768 + </a>
2769 + <?php else : ?>
2770 + <span class="mxchat-na"><?php esc_html_e('N/A', 'mxchat'); ?></span>
2771 + <?php endif; ?>
2772 + </div>
2773 + <?php if ($data_source === 'wordpress') : ?>
2774 + <input type="text" class="url-edit" style="display:none;"
2775 + value="<?php echo htmlspecialchars($prompt->source_url, ENT_QUOTES, 'UTF-8'); ?>" />
2776 + <?php endif; ?>
2777 + </td>
2778 + <?php if ($data_source === 'pinecone') : ?>
2779 + <td class="mxchat-vector-id-cell">
2780 + <code style="background: #f5f5f5; padding: 2px 6px; border-radius: 3px; font-size: 11px;">
2781 + <?php echo esc_html(substr($prompt->id, 0, 12) . '...'); ?>
2782 + </code>
2783 + </td>
2784 + <?php endif; ?>
2785 +
2786 + <td class="mxchat-actions-cell">
2787 + <?php if ($data_source === 'wordpress') : ?>
2788 + <!-- WordPress - Full editing capabilities -->
2789 + <div class="mxchat-role-restriction-container" style="margin-bottom: 8px;">
2790 + <select class="mxchat-role-select"
2791 + data-entry-id="<?php echo esc_attr($prompt->id); ?>"
2792 + data-data-source="wordpress"
2793 + data-nonce="<?php echo wp_create_nonce('mxchat_update_role_nonce'); ?>">
2794 + <?php
2795 + $current_role = $prompt->role_restriction ?? 'public';
2796 + $role_options = $knowledge_manager->mxchat_get_role_options();
2797 + foreach ($role_options as $role_key => $role_label) : ?>
2798 + <option value="<?php echo esc_attr($role_key); ?>"
2799 + <?php selected($current_role, $role_key); ?>>
2800 + <?php echo esc_html($role_label); ?>
2801 + </option>
2802 + <?php endforeach; ?>
2803 + </select>
2804 + </div>
2805 +
2806 + <!-- Edit/Save Buttons -->
2807 + <div class="mxchat-action-buttons">
2808 + <button class="mxchat-button-icon edit-button"
2809 + data-id="<?php echo esc_attr($prompt->id); ?>">
2810 + <span class="dashicons dashicons-edit"></span>
2811 + </button>
2812 + <button class="mxchat-button-icon save-button"
2813 + data-id="<?php echo esc_attr($prompt->id); ?>"
2814 + style="display:none;"
2815 + data-nonce="<?php echo wp_create_nonce('mxchat_save_inline_nonce'); ?>">
2816 + <span class="dashicons dashicons-saved"></span>
2817 + </button>
2818 + </div>
2819 + <?php else : ?>
2820 + <!-- Pinecone - Role restriction editable, content read-only -->
2821 + <div class="mxchat-role-restriction-container" style="margin-bottom: 8px;">
2822 + <select class="mxchat-role-select"
2823 + data-entry-id="<?php echo esc_attr($prompt->id); ?>"
2824 + data-data-source="pinecone"
2825 + data-nonce="<?php echo wp_create_nonce('mxchat_update_role_nonce'); ?>">
2826 + <?php
2827 + $current_role = $prompt->role_restriction ?? 'public';
2828 + $role_options = $knowledge_manager->mxchat_get_role_options();
2829 + foreach ($role_options as $role_key => $role_label) : ?>
2830 + <option value="<?php echo esc_attr($role_key); ?>"
2831 + <?php selected($current_role, $role_key); ?>>
2832 + <?php echo esc_html($role_label); ?>
2833 + </option>
2834 + <?php endforeach; ?>
2835 + </select>
2836 + </div>
2837 +
2838 + <?php endif; ?>
2839 +
2840 + <!-- Delete button for both sources -->
2841 + <div class="mxchat-delete-container">
2842 + <?php if ($data_source === 'pinecone') : ?>
2843 + <!-- AJAX Delete for Pinecone -->
2844 + <button type="button"
2845 + class="mxchat-button-icon delete-button-ajax"
2846 + data-vector-id="<?php echo esc_attr($prompt->id); ?>"
2847 + data-bot-id="<?php echo esc_attr($current_bot_id); ?>"
2848 + data-nonce="<?php echo wp_create_nonce('mxchat_delete_pinecone_prompt_nonce'); ?>"
2849 + title="<?php esc_attr_e('Delete entry', 'mxchat'); ?>">
2850 + <span class="dashicons dashicons-trash"></span>
2851 + </button>
2852 + <?php else : ?>
2853 + <!-- Regular link delete for WordPress DB -->
2854 + <a href="<?php echo esc_url(admin_url(
2855 + 'admin-post.php?action=mxchat_delete_prompt&id=' . esc_attr($prompt->id)
2856 + . '&_wpnonce=' . wp_create_nonce('mxchat_delete_prompt_nonce')
2857 + )); ?>"
2858 + class="mxchat-button-icon delete-button"
2859 + onclick="return confirm('<?php esc_attr_e('Are you sure you want to delete this entry?', 'mxchat'); ?>');">
2860 + <span class="dashicons dashicons-trash"></span>
2861 + </a>
2862 + <?php endif; ?>
2863 + </div>
2864 + </td>
2865 +
2866 + </tr>
2867 + <?php endforeach; ?>
2868 + <?php else : ?>
2869 + <tr>
2870 + <td colspan="<?php echo $data_source === 'pinecone' ? '5' : '4'; ?>" class="mxchat-no-records">
2871 + <?php if ($use_pinecone) : ?>
2872 + <?php esc_html_e('No vectors found in Pinecone database.', 'mxchat'); ?>
2873 + <?php else : ?>
2874 + <?php esc_html_e('No knowledge base entries found.', 'mxchat'); ?>
2875 + <?php endif; ?>
2876 + </td>
2877 + </tr>
2878 + <?php endif; ?>
2879 + </tbody>
2880 + </table>
2881 + </div>
2882 +
2883 + <?php if ($page_links) : ?>
2884 + <div class="mxchat-pagination">
2885 + <?php echo wp_kses_post($page_links); ?>
2886 + </div>
2887 + <?php endif; ?>
2888 + </div>
2889 +
2890 + </div>
2891 +
2892 + <!-- Settings Tab (Renamed from Auto-Sync Settings) -->
2893 + <div id="mxchat-kb-tab-sync" class="mxchat-kb-tab-content">
2894 +
2895 + <!-- Auto-Sync Settings Card -->
2896 + <div class="mxchat-card">
2897 + <div class="mxchat-settings-section">
2898 + <h3><?php esc_html_e('Auto-Sync Settings', 'mxchat'); ?></h3>
2899 + <p class="mxchat-description">
2900 + <?php esc_html_e('Note: Auto-sync works only for newly published content. Existing posts and pages must be imported manually below. Works with Pinecone if enabled', 'mxchat'); ?>
2901 + </p>
2902 + <div class="mxchat-autosave-section">
2903 + <div class="mxchat-toggle-group">
2904 + <div class="mxchat-toggle-container">
2905 + <label class="mxchat-toggle-switch">
2906 + <input type="checkbox"
2907 + name="mxchat_auto_sync_posts"
2908 + class="mxchat-autosave-field"
2909 + value="1"
2910 + data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>"
2911 + <?php checked(get_option('mxchat_auto_sync_posts', '0'), '1'); ?>>
2912 + <span class="mxchat-toggle-slider"></span>
2913 + </label>
2914 + <span class="mxchat-toggle-label">
2915 + <?php esc_html_e('Auto-sync Posts', 'mxchat'); ?>
2916 + </span>
2917 + </div>
2918 +
2919 + <div class="mxchat-toggle-container">
2920 + <label class="mxchat-toggle-switch">
2921 + <input type="checkbox"
2922 + name="mxchat_auto_sync_pages"
2923 + class="mxchat-autosave-field"
2924 + value="1"
2925 + data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>"
2926 + <?php checked(get_option('mxchat_auto_sync_pages', '0'), '1'); ?>>
2927 + <span class="mxchat-toggle-slider"></span>
2928 + </label>
2929 + <span class="mxchat-toggle-label">
2930 + <?php esc_html_e('Auto-sync Pages', 'mxchat'); ?>
2931 + </span>
2932 + </div>
2933 +
2934 + <!-- Custom Post Types Section -->
2935 + <div class="mxchat-section-content">
2936 + <div class="mxchat-custom-post-types-header">
2937 + <button id="mxchat-custom-post-types-toggle" class="mxchat-button-secondary">
2938 + <?php esc_html_e('Advanced Custom Post Sync Settings', 'mxchat'); ?>
2939 + <span class="mxchat-toggle-icon">▼</span>
2940 + </button>
2941 + </div>
2942 +
2943 + <div id="mxchat-custom-post-types-container" class="mxchat-custom-post-types-container" style="display: none;">
2944 + <h3><?php esc_html_e('Sync Custom Post Types', 'mxchat'); ?></h3>
2945 + <p><?php esc_html_e('Select additional custom post types to automatically sync with the chatbot.', 'mxchat'); ?></p>
2946 +
2947 + <div class="mxchat-custom-post-types">
2948 + <?php
2949 + $post_types = $knowledge_manager->mxchat_get_public_post_types();
2950 + // Skip post and page as they're handled separately
2951 + unset($post_types['post']);
2952 + unset($post_types['page']);
2953 +
2954 + if (!empty($post_types)) {
2955 + foreach ($post_types as $post_type => $label) {
2956 + $option_name = 'mxchat_auto_sync_' . $post_type;
2957 + $is_enabled = get_option($option_name, '0');
2958 + ?>
2959 + <div class="mxchat-toggle-container">
2960 + <label class="mxchat-toggle-switch">
2961 + <input type="checkbox"
2962 + name="<?php echo esc_attr($option_name); ?>"
2963 + class="mxchat-autosave-field"
2964 + value="1"
2965 + data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>"
2966 + <?php checked($is_enabled, '1'); ?>>
2967 + <span class="mxchat-toggle-slider"></span>
2968 + </label>
2969 + <span class="mxchat-toggle-label">
2970 + <?php echo esc_html($label); ?> (<?php echo esc_html($post_type); ?>)
2971 + </span>
2972 + </div>
2973 + <?php
2974 + }
2975 + } else {
2976 + echo '<p>' . esc_html__('No custom post types found.', 'mxchat') . '</p>';
2977 + }
2978 + ?>
2979 + </div>
2980 + </div>
2981 + </div>
2982 + </div>
2983 + </div>
2984 + </div>
2985 + </div>
2986 +
2987 + <!-- Role-Based Content Restrictions Card -->
2988 + <div class="mxchat-card" style="margin-top: 20px;">
2989 + <h3><?php esc_html_e('Role-Based Content Restrictions', 'mxchat'); ?></h3>
2990 + <p class="mxchat-description">
2991 + <?php esc_html_e('Automatically restrict content access based on WordPress tags. When you assign a tag to a role, any post with that tag will only be accessible to users with that role or higher in the chatbot knowledge base.', 'mxchat'); ?>
2992 + </p>
2993 +
2994 + <!-- Info Box -->
2995 + <div class="mxchat-knowledge-warning info" style="background: #e3f2fd; border-left-color: #2196f3;">
2996 + <p>
2997 + <span class="dashicons dashicons-info"></span>
2998 + <strong><?php esc_html_e('How it works:', 'mxchat'); ?></strong>
2999 + <?php esc_html_e('Add a tag below and select which role should have access. Any content tagged with that tag will automatically be restricted to that role level. Works with both auto-sync and manual imports.', 'mxchat'); ?>
3000 + </p>
3001 + </div>
3002 +
3003 + <!-- Add New Tag-Role Mapping -->
3004 + <div class="mxchat-role-mapping-form">
3005 + <h4><?php esc_html_e('Add Tag-Role Mapping', 'mxchat'); ?></h4>
3006 +
3007 + <div class="mxchat-form-row" style="display: flex; gap: 15px; align-items: flex-end; margin-bottom: 20px;">
3008 + <div class="mxchat-form-group" style="flex: 1;">
3009 + <label for="mxchat-tag-input">
3010 + <?php esc_html_e('Tag Name', 'mxchat'); ?>
3011 + </label>
3012 + <input type="text"
3013 + id="mxchat-tag-input"
3014 + class="regular-text"
3015 + placeholder="<?php esc_attr_e('Enter tag name (e.g., premium, members-only)', 'mxchat'); ?>" />
3016 + <p class="description">
3017 + <?php esc_html_e('Enter the exact tag slug as it appears in WordPress', 'mxchat'); ?>
3018 + </p>
3019 + </div>
3020 +
3021 + <div class="mxchat-form-group" style="flex: 1;">
3022 + <label for="mxchat-role-select">
3023 + <?php esc_html_e('Required Role', 'mxchat'); ?>
3024 + </label>
3025 + <select id="mxchat-role-select" class="regular-text">
3026 + <?php
3027 + // Get role options from knowledge manager
3028 + $role_options = array(
3029 + 'public' => __('Public (Everyone)', 'mxchat'),
3030 + 'logged_in' => __('Logged In Users', 'mxchat'),
3031 + 'subscriber' => __('Subscribers & Above', 'mxchat'),
3032 + 'contributor' => __('Contributors & Above', 'mxchat'),
3033 + 'author' => __('Authors & Above', 'mxchat'),
3034 + 'editor' => __('Editors & Above', 'mxchat'),
3035 + 'administrator' => __('Administrators Only', 'mxchat')
3036 + );
3037 +
3038 + foreach ($role_options as $role_key => $role_label) : ?>
3039 + <option value="<?php echo esc_attr($role_key); ?>">
3040 + <?php echo esc_html($role_label); ?>
3041 + </option>
3042 + <?php endforeach; ?>
3043 + </select>
3044 + <p class="description">
3045 + <?php esc_html_e('Select the minimum role required to access this content', 'mxchat'); ?>
3046 + </p>
3047 + </div>
3048 +
3049 + <div style="padding-bottom: 25px;">
3050 + <button type="button"
3051 + id="mxchat-add-tag-role"
3052 + class="mxchat-button-primary">
3053 + <span class="dashicons dashicons-plus-alt"></span>
3054 + <?php esc_html_e('Add Mapping', 'mxchat'); ?>
3055 + </button>
3056 + </div>
3057 + </div>
3058 + </div>
3059 +
3060 + <!-- Existing Tag-Role Mappings -->
3061 + <div class="mxchat-role-mappings-list">
3062 + <h4><?php esc_html_e('Current Tag-Role Mappings', 'mxchat'); ?></h4>
3063 +
3064 + <div id="mxchat-mappings-container">
3065 + <!-- Mappings will be loaded here via AJAX -->
3066 + <div class="mxchat-loading-mappings">
3067 + <span class="mxchat-spinner is-active"></span>
3068 + <?php esc_html_e('Loading mappings...', 'mxchat'); ?>
3069 + </div>
3070 + </div>
3071 +
3072 + <!-- Empty State (initially hidden) -->
3073 + <div id="mxchat-no-mappings" style="display: none; text-align: center; padding: 40px; color: #666;">
3074 + <span class="dashicons dashicons-tag" style="font-size: 48px; opacity: 0.3;"></span>
3075 + <p><?php esc_html_e('No tag-role mappings yet. Add your first mapping above to get started.', 'mxchat'); ?></p>
3076 + </div>
3077 + </div>
3078 +
3079 + <!-- Bulk Update Section -->
3080 + <div class="mxchat-bulk-update-section" style="margin-top: 30px; padding-top: 30px; border-top: 1px solid #ddd;">
3081 + <h4><?php esc_html_e('Bulk Update Existing Content', 'mxchat'); ?></h4>
3082 + <p class="mxchat-description">
3083 + <?php esc_html_e('Apply role restrictions to all existing content that has the mapped tags. This is useful when setting up tag-role mappings for the first time or after changing mappings.', 'mxchat'); ?>
3084 + </p>
3085 +
3086 + <div class="mxchat-bulk-update-controls">
3087 + <button type="button"
3088 + id="mxchat-bulk-update-roles"
3089 + class="mxchat-button-secondary">
3090 + <span class="dashicons dashicons-update"></span>
3091 + <?php esc_html_e('Update All Existing Content', 'mxchat'); ?>
3092 + </button>
3093 +
3094 + <div id="mxchat-bulk-update-progress" style="display: none; margin-top: 15px;">
3095 + <div class="mxchat-progress-bar">
3096 + <div class="mxchat-progress-fill" style="width: 0%"></div>
3097 + </div>
3098 + <p class="mxchat-progress-text" style="margin-top: 10px; color: #666;">
3099 + <?php esc_html_e('Processing...', 'mxchat'); ?>
3100 + </p>
3101 + </div>
3102 +
3103 + <div id="mxchat-bulk-update-result" style="display: none; margin-top: 15px;"></div>
3104 + </div>
3105 + </div>
3106 + </div>
3107 +
3108 + </div>
3109 +
3110 + <!-- Pinecone Settings Tab -->
3111 + <div id="mxchat-kb-tab-pinecone" class="mxchat-kb-tab-content mxchat-autosave-section">
3112 + <div class="mxchat-card">
3113 + <h2><?php esc_html_e('Pinecone Vector Database Settings', 'mxchat'); ?></h2>
3114 + <p class="mxchat-description">
3115 + <?php echo wp_kses(
3116 + __('<strong>Pinecone is optional</strong> and not required for MxChat to function. It provides enhanced search performance for larger knowledge bases. When enabled, content will be stored in Pinecone instead of the WordPress database, but you can use MxChat without it.', 'mxchat'),
3117 + array('strong' => array())
3118 + ); ?>
3119 + </p>
3120 +
3121 + <div class="mxchat-pinecone-info-box">
3122 + <div class="mxchat-info-icon">
3123 + <span class="dashicons dashicons-info"></span>
3124 + </div>
3125 + <div class="mxchat-info-content">
3126 + <h4><?php esc_html_e('What is Pinecone?', 'mxchat'); ?></h4>
3127 + <p><?php esc_html_e('Pinecone is a specialized vector database designed for AI applications. It provides faster similarity searches and better scalability compared to traditional databases for AI-powered features.', 'mxchat'); ?></p>
3128 + <p><a href="https://www.pinecone.io/" target="_blank" rel="noopener noreferrer"><?php esc_html_e('Learn more about Pinecone →', 'mxchat'); ?></a></p>
3129 + </div>
3130 + </div>
3131 +
3132 + <div class="mxchat-database-settings-form">
3133 + <?php
3134 + $pinecone_options = get_option('mxchat_pinecone_addon_options', array());
3135 + $use_pinecone = $pinecone_options['mxchat_use_pinecone'] ?? '0';
3136 + ?>
3137 +
3138 + <div class="mxchat-toggle-container">
3139 + <label class="mxchat-toggle-switch">
3140 + <input type="checkbox"
3141 + name="mxchat_pinecone_addon_options[mxchat_use_pinecone]"
3142 + value="1"
3143 + <?php checked($use_pinecone, '1'); ?>>
3144 + <span class="mxchat-toggle-slider"></span>
3145 + </label>
3146 + <span class="mxchat-toggle-label">
3147 + <?php esc_html_e('Enable Pinecone Database', 'mxchat'); ?>
3148 + </span>
3149 + </div>
3150 +
3151 + <div class="mxchat-pinecone-settings" <?php echo $use_pinecone ? '' : 'style="display: none;"'; ?>>
3152 +
3153 + <?php if ($use_pinecone) : ?>
3154 + <div class="mxchat-knowledge-warning success">
3155 + <p><span class="dashicons dashicons-yes-alt"></span>
3156 + <?php esc_html_e('Pinecone is enabled. All new knowledge base content will be stored in Pinecone.', 'mxchat'); ?>
3157 + </p>
3158 + </div>
3159 + <?php endif; ?>
3160 +
3161 + <div class="mxchat-form-group">
3162 + <label for="mxchat_pinecone_api_key">
3163 + <?php esc_html_e('Pinecone API Key', 'mxchat'); ?> <span class="required">*</span>
3164 + </label>
3165 + <input type="password"
3166 + id="mxchat_pinecone_api_key"
3167 + name="mxchat_pinecone_addon_options[mxchat_pinecone_api_key]"
3168 + value="<?php echo esc_attr($pinecone_options['mxchat_pinecone_api_key'] ?? ''); ?>"
3169 + class="regular-text"
3170 + placeholder="pcsk_..." />
3171 + <p class="description">
3172 + <?php esc_html_e('Found in your Pinecone dashboard under API Keys.', 'mxchat'); ?>
3173 + <a href="https://app.pinecone.io/" target="_blank" rel="noopener noreferrer"><?php esc_html_e('Open Pinecone Dashboard', 'mxchat'); ?></a>
3174 + </p>
3175 + </div>
3176 +
3177 + <div class="mxchat-form-group">
3178 + <label for="mxchat_pinecone_environment">
3179 + <?php esc_html_e('Region', 'mxchat'); ?>
3180 + </label>
3181 + <input type="text"
3182 + id="mxchat_pinecone_environment"
3183 + name="mxchat_pinecone_addon_options[mxchat_pinecone_environment]"
3184 + value="<?php echo esc_attr($pinecone_options['mxchat_pinecone_environment'] ?? ''); ?>"
3185 + placeholder="e.g., gcp-starter"
3186 + class="regular-text" />
3187 + <p class="description">
3188 + <?php esc_html_e('Your Pinecone environment/region (e.g., gcp-starter, us-west1-gcp, us-east-1-aws)', 'mxchat'); ?>
3189 + </p>
3190 + </div>
3191 +
3192 + <div class="mxchat-form-group">
3193 + <label for="mxchat_pinecone_index">
3194 + <?php esc_html_e('Index Name', 'mxchat'); ?> <span class="required">*</span>
3195 + </label>
3196 + <input type="text"
3197 + id="mxchat_pinecone_index"
3198 + name="mxchat_pinecone_addon_options[mxchat_pinecone_index]"
3199 + value="<?php echo esc_attr($pinecone_options['mxchat_pinecone_index'] ?? ''); ?>"
3200 + placeholder="e.g., my-wordpress-vectors"
3201 + class="regular-text" />
3202 + <p class="description">
3203 + <?php esc_html_e('The name of your Pinecone index. Must be created in your Pinecone dashboard first.', 'mxchat'); ?>
3204 + </p>
3205 + </div>
3206 +
3207 + <div class="mxchat-form-group">
3208 + <label for="mxchat_pinecone_host">
3209 + <?php esc_html_e('Pinecone Host', 'mxchat'); ?> <span class="required">*</span>
3210 + </label>
3211 + <input type="text"
3212 + id="mxchat_pinecone_host"
3213 + name="mxchat_pinecone_addon_options[mxchat_pinecone_host]"
3214 + value="<?php echo esc_attr($pinecone_options['mxchat_pinecone_host'] ?? ''); ?>"
3215 + placeholder="e.g., my-index-xyz123.svc.pinecone.io"
3216 + class="regular-text" />
3217 + <p class="description">
3218 + <?php esc_html_e('The hostname from your Pinecone index URL (exclude https://). Found in your index details.', 'mxchat'); ?>
3219 + </p>
3220 + </div>
3221 +
3222 + <div class="mxchat-setup-steps">
3223 + <h3><?php esc_html_e('Setup Instructions', 'mxchat'); ?></h3>
3224 + <div class="mxchat-setup-step">
3225 + <span class="step-number">1</span>
3226 + <div class="step-content">
3227 + <h4><?php esc_html_e('Create Account', 'mxchat'); ?></h4>
3228 + <p><?php esc_html_e('Create a free account at', 'mxchat'); ?> <a href="https://www.pinecone.io/" target="_blank">pinecone.io</a></p>
3229 + </div>
3230 + </div>
3231 + <div class="mxchat-setup-step">
3232 + <span class="step-number">2</span>
3233 + <div class="step-content">
3234 + <h4><?php esc_html_e('Create Index', 'mxchat'); ?></h4>
3235 + <p><?php esc_html_e('Create a new index with these settings:', 'mxchat'); ?></p>
3236 + <ul>
3237 + <li><?php esc_html_e('Dimensions: Choose based on your embedding model - 1536 (OpenAI Ada 2, TE3 Small), 2048 (Voyage-3 Large), 3072 (TE3 Large, Gemini Embedding), 1536 (Gemini Embedding), or 768 (Gemini Embedding)', 'mxchat'); ?></li>
3238 + <li><?php esc_html_e('Metric: Cosine', 'mxchat'); ?></li>
3239 + <li><?php esc_html_e('Cloud & Region: Your preferred region', 'mxchat'); ?></li>
3240 + </ul>
3241 + </div>
3242 + </div>
3243 + <div class="mxchat-setup-step">
3244 + <span class="step-number">3</span>
3245 + <div class="step-content">
3246 + <h4><?php esc_html_e('Get API Key', 'mxchat'); ?></h4>
3247 + <p><?php esc_html_e('Copy your API key from the API Keys section', 'mxchat'); ?></p>
3248 + </div>
3249 + </div>
3250 + <div class="mxchat-setup-step">
3251 + <span class="step-number">4</span>
3252 + <div class="step-content">
3253 + <h4><?php esc_html_e('Get Index Host', 'mxchat'); ?></h4>
3254 + <p><?php esc_html_e('Copy your index host URL from the index details', 'mxchat'); ?></p>
3255 + </div>
3256 + </div>
3257 + <div class="mxchat-setup-step">
3258 + <span class="step-number">5</span>
3259 + <div class="step-content">
3260 + <h4><?php esc_html_e('Save Settings', 'mxchat'); ?></h4>
3261 + <p><?php esc_html_e('Fill in the form above and save settings', 'mxchat'); ?></p>
3262 + </div>
3263 + </div>
3264 + </div>
3265 +
3266 + </div>
3267 +
3268 + </div>
3269 + </div>
3270 + </div>
3271 +
3272 + </div>
3273 +
3274 + </div>
3275 + </div>
3276 +
3277 + <!-- Content Selector Modal -->
3278 + <div id="mxchat-kb-content-selector-modal" class="mxchat-kb-modal">
3279 + <div class="mxchat-kb-modal-content">
3280 + <div class="mxchat-kb-modal-header">
3281 + <h3>
3282 + <?php esc_html_e('Select WordPress Content', 'mxchat'); ?><br>
3283 + <span class="mxchat-kb-header-note"><?php esc_html_e('(Content imported here will be tagged "In Knowledge Base")', 'mxchat'); ?></span>
3284 + </h3>
3285 + <span class="mxchat-kb-modal-close">&times;</span>
3286 + </div>
3287 + <div class="mxchat-kb-modal-filters">
3288 + <div class="mxchat-kb-search-group">
3289 + <input type="text" id="mxchat-kb-content-search" placeholder="<?php esc_attr_e('Search...', 'mxchat'); ?>">
3290 + </div>
3291 +
3292 + <div class="mxchat-kb-filter-group">
3293 + <select id="mxchat-kb-content-type-filter">
3294 + <option value="all"><?php esc_html_e('All Content Types', 'mxchat'); ?></option>
3295 + <option value="post"><?php esc_html_e('Posts', 'mxchat'); ?></option>
3296 + <option value="page"><?php esc_html_e('Pages', 'mxchat'); ?></option>
3297 + <?php
3298 + // Add other post types dynamically
3299 + $post_types = get_post_types(array('public' => true), 'objects');
3300 + foreach ($post_types as $post_type) {
3301 + // Skip post and page as they're already added
3302 + if (!in_array($post_type->name, array('post', 'page'))) {
3303 + echo '<option value="' . esc_attr($post_type->name) . '">' . esc_html($post_type->label) . '</option>';
3304 + }
3305 + }
3306 + ?>
3307 + </select>
3308 +
3309 + <select id="mxchat-kb-content-status-filter">
3310 + <option value="publish"><?php esc_html_e('Published', 'mxchat'); ?></option>
3311 + <option value="draft"><?php esc_html_e('Drafts', 'mxchat'); ?></option>
3312 + <option value="all"><?php esc_html_e('All Statuses', 'mxchat'); ?></option>
3313 + </select>
3314 +
3315 + <select id="mxchat-kb-processed-filter">
3316 + <option value="all"><?php esc_html_e('All Content', 'mxchat'); ?></option>
3317 + <option value="processed"><?php esc_html_e('In Knowledge Base', 'mxchat'); ?></option>
3318 + <option value="unprocessed"><?php esc_html_e('Not In Knowledge Base', 'mxchat'); ?></option>
3319 + </select>
3320 + </div>
3321 + </div>
3322 +
3323 + <div class="mxchat-kb-content-selection">
3324 + <div class="mxchat-kb-selection-header">
3325 + <label>
3326 + <input type="checkbox" id="mxchat-kb-select-all">
3327 + <?php esc_html_e('Select All', 'mxchat'); ?>
3328 + </label>
3329 + <span class="mxchat-kb-selection-count">0 <?php esc_html_e('selected', 'mxchat'); ?></span>
3330 + </div>
3331 +
3332 + <div class="mxchat-kb-content-list">
3333 + <!-- Content will be loaded here via AJAX -->
3334 + <div class="mxchat-kb-loading">
3335 + <span class="mxchat-kb-spinner is-active"></span>
3336 + <?php esc_html_e('Loading content...', 'mxchat'); ?>
3337 + </div>
3338 + </div>
3339 +
3340 + <div class="mxchat-kb-pagination">
3341 + <!-- Pagination will be added here -->
3342 + </div>
3343 + </div>
3344 +
3345 + <div class="mxchat-kb-modal-footer">
3346 + <button type="button" id="mxchat-kb-process-selected" class="mxchat-kb-button-primary" disabled>
3347 + <?php esc_html_e('Process Selected Content', 'mxchat'); ?>
3348 + <span class="mxchat-kb-selected-count">(0)</span>
3349 + </button>
3350 + <button type="button" class="mxchat-kb-button-secondary mxchat-kb-modal-close">
3351 + <?php esc_html_e('Cancel', 'mxchat'); ?>
3352 + </button>
3353 + </div>
3354 + </div>
3355 + </div>
3356 +
3357 +<?php
3579 3358 }
3580 3359
3360 +
3361 +
3362 +
3363 +
3364 +
3581 3365 /**
3582 3366 * Get bot-specific Pinecone configuration
3583 3367 * Used in the knowledge retrieval functions
3584 3368 */
@@ -3621,103 +3405,47 @@
3621 3405 echo wp_json_encode(['error' => esc_html__('You do not have sufficient permissions.', 'mxchat')]);
3622 3406 wp_die();
3623 3407 }
3624 3408 check_ajax_referer('mxchat_delete_chat_history', 'security');
3625 -
3626 - if (!isset($_POST['delete_session_ids']) || !is_array($_POST['delete_session_ids'])) {
3627 - echo wp_json_encode(['error' => esc_html__('No chat sessions selected for deletion.', 'mxchat')]);
3628 - wp_die();
3629 - }
3630 -
3631 3409 global $wpdb;
3632 3410 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
3633 - $translations_table = $wpdb->prefix . 'mxchat_transcript_translations';
3634 - $has_translations = $wpdb->get_var("SHOW TABLES LIKE '$translations_table'") === $translations_table;
3635 3411
3636 - // When true, any lead attached to these sessions is fully wiped (all their sessions,
3637 - // across the whole table). Default false: the chat rows go away but the lead is
3638 - // preserved as a separate "chat deleted" lead in the Leads tab.
3639 - $also_delete_lead = !empty($_POST['also_delete_lead']) && $_POST['also_delete_lead'] !== 'false';
3412 + if (isset($_POST['delete_session_ids']) && is_array($_POST['delete_session_ids'])) {
3413 + $deleted_count = 0;
3640 3414
3641 - $deleted_count = 0;
3642 - $preserved_as_deleted_leads = 0;
3643 - $emails_to_fully_wipe = [];
3415 + foreach ($_POST['delete_session_ids'] as $session_id) {
3416 + $session_id_sanitized = sanitize_text_field($session_id);
3644 3417
3645 - foreach ((array) $_POST['delete_session_ids'] as $session_id) {
3646 - $session_id_sanitized = sanitize_text_field($session_id);
3647 - if ($session_id_sanitized === '') {
3648 - continue;
3649 - }
3418 + // Clear relevant cache before deletion
3419 + $cache_key = 'chat_session_' . $session_id_sanitized;
3420 + wp_cache_delete($cache_key, 'mxchat_chat_sessions');
3650 3421
3651 - // Capture the lead info attached to this session *before* we delete the rows.
3652 - $lead_row = $wpdb->get_row($wpdb->prepare(
3653 - "SELECT user_email, user_name, MAX(timestamp) AS last_ts
3654 - FROM {$table_name}
3655 - WHERE session_id = %s AND user_email IS NOT NULL AND user_email != ''
3656 - GROUP BY user_email, user_name
3657 - ORDER BY last_ts DESC LIMIT 1",
3658 - $session_id_sanitized
3659 - ));
3422 + // Perform the deletion from the database table
3423 + $wpdb->delete($table_name, ['session_id' => $session_id_sanitized]);
3660 3424
3661 - wp_cache_delete('chat_session_' . $session_id_sanitized, 'mxchat_chat_sessions');
3662 - $wpdb->delete($table_name, ['session_id' => $session_id_sanitized]);
3425 + // Delete the corresponding option entry from wp_options table
3426 + delete_option("mxchat_history_" . $session_id_sanitized);
3663 3427
3664 - if ($has_translations) {
3665 - $wpdb->delete($translations_table, ['session_id' => $session_id_sanitized]);
3666 - }
3428 + // Delete any associated metadata options
3429 + delete_option("mxchat_email_" . $session_id_sanitized);
3430 + delete_option("mxchat_agent_name_" . $session_id_sanitized);
3667 3431
3668 - delete_option('mxchat_history_' . $session_id_sanitized);
3669 - delete_option('mxchat_agent_name_' . $session_id_sanitized);
3670 -
3671 - if ($lead_row && !empty($lead_row->user_email)) {
3672 - if ($also_delete_lead) {
3673 - // Full-wipe requested — queue the email so that all their sessions and
3674 - // related options get swept below. Also clear this session's pre-chat
3675 - // capture options (they're no longer meaningful).
3676 - $emails_to_fully_wipe[strtolower($lead_row->user_email)] = $lead_row->user_email;
3677 - delete_option('mxchat_email_' . $session_id_sanitized);
3678 - delete_option('mxchat_name_' . $session_id_sanitized);
3679 - } else {
3680 - // Preserve the lead in a "Chat deleted" state via distinct option keys so
3681 - // they stay out of the orphan bucket (orphan = pre-chat form dropoff).
3682 - update_option('mxchat_lead_del_email_' . $session_id_sanitized, $lead_row->user_email, false);
3683 - if (!empty($lead_row->user_name)) {
3684 - update_option('mxchat_lead_del_name_' . $session_id_sanitized, $lead_row->user_name, false);
3685 - }
3686 - if (!empty($lead_row->last_ts)) {
3687 - update_option('mxchat_lead_del_ts_' . $session_id_sanitized, $lead_row->last_ts, false);
3688 - }
3689 - // Clean up pre-chat capture options for this session — chat_deleted supersedes.
3690 - delete_option('mxchat_email_' . $session_id_sanitized);
3691 - delete_option('mxchat_name_' . $session_id_sanitized);
3692 - $preserved_as_deleted_leads++;
3693 - }
3694 - } else {
3695 - // No lead attached — nothing to preserve. Clean up any orphan options anyway.
3696 - delete_option('mxchat_email_' . $session_id_sanitized);
3697 - delete_option('mxchat_name_' . $session_id_sanitized);
3432 + $deleted_count++;
3698 3433 }
3699 3434
3700 - $deleted_count++;
3701 - }
3435 + // Optionally, clear a general cache if you have one
3436 + wp_cache_delete('all_chat_sessions', 'mxchat_chat_sessions');
3702 3437
3703 - // Opt-in full-lead wipe: sweep every remaining row + every option key (including
3704 - // chat_deleted preservation) for each affected email. Reuses the same internal
3705 - // helper as the Leads-tab Delete button for consistency.
3706 - if (!empty($emails_to_fully_wipe)) {
3707 - self::mxchat_wipe_leads_by_email(array_values($emails_to_fully_wipe));
3438 + echo wp_json_encode([
3439 + 'success' => sprintf(
3440 + esc_html__('%d chat session(s) have been deleted from all storage locations.', 'mxchat'),
3441 + $deleted_count
3442 + )
3443 + ]);
3444 + } else {
3445 + echo wp_json_encode(['error' => esc_html__('No chat sessions selected for deletion.', 'mxchat')]);
3708 3446 }
3709 3447
3710 - wp_cache_delete('all_chat_sessions', 'mxchat_chat_sessions');
3711 -
3712 - echo wp_json_encode([
3713 - 'success' => sprintf(
3714 - esc_html__('%d chat session(s) have been deleted.', 'mxchat'),
3715 - $deleted_count
3716 - ),
3717 - 'preserved_as_deleted_leads' => $preserved_as_deleted_leads,
3718 - 'leads_fully_wiped' => count($emails_to_fully_wipe),
3719 - ]);
3720 3448 wp_die();
3721 3449 }
3722 3450
3723 3451 /**
@@ -3728,23 +3456,12 @@
3728 3456 if (empty($text)) {
3729 3457 return '';
3730 3458 }
3731 3459
3732 - // Normalize line endings and clean up excessive whitespace
3733 - $text = str_replace("\r\n", "\n", $text);
3734 - $text = str_replace("\r", "\n", $text);
3735 -
3736 - // Clean up existing <br> tags that may have been saved (legacy data)
3737 - // Convert <br>, <br/>, <br /> back to newlines for consistent processing
3738 - $text = preg_replace('/<br\s*\/?>\s*/i', "\n", $text);
3739 -
3740 - // Collapse 3+ consecutive newlines to just 2 (paragraph break)
3741 - $text = preg_replace('/\n{3,}/', "\n\n", $text);
3742 -
3743 3460 // Process markdown headers (# Header)
3744 3461 $text = preg_replace_callback('/^(#{1,6})\s+(.+)$/m', function($matches) {
3745 3462 $level = strlen($matches[1]);
3746 - $content = esc_html(trim($matches[2]));
3463 + $content = esc_html($matches[2]);
3747 3464 return "<h{$level}>{$content}</h{$level}>";
3748 3465 }, $text);
3749 3466
3750 3467 // Process code blocks with triple backticks
@@ -3777,11 +3494,11 @@
3777 3494 $url = esc_url($matches[1]);
3778 3495 return "<a href=\"{$url}\" target=\"_blank\" rel=\"noopener\">{$url}</a>";
3779 3496 }, $text);
3780 3497
3781 - // Process standalone URLs (not already in links or img src)
3498 + // Process standalone URLs (not already in links)
3782 3499 $text = preg_replace_callback(
3783 - '/(?<!href="|src="|">)(https?:\/\/[^\s<>"]+)(?![^<]*<\/a>)/',
3500 + '/(?<!href="|">)(https?:\/\/[^\s<>"]+)(?![^<]*<\/a>)/',
3784 3501 function($matches) {
3785 3502 $url = esc_url($matches[1]);
3786 3503 // Truncate display URL if too long
3787 3504 $display = strlen($matches[1]) > 50 ? substr($matches[1], 0, 47) . '...' : $matches[1];
@@ -3796,31 +3513,11 @@
3796 3513 $mailto = esc_url($matches[2]);
3797 3514 return "<a href=\"{$mailto}\">{$link_text}</a>";
3798 3515 }, $text);
3799 3516
3800 - // Convert paragraphs: split by double newlines, wrap in <p> tags
3801 - // This creates proper paragraph structure instead of excessive <br> tags
3802 - $paragraphs = preg_split('/\n\n+/', $text);
3517 + // Convert newlines to <br> tags (but preserve existing block elements)
3518 + $text = preg_replace('/\n/', '<br>', $text);
3803 3519
3804 - // Filter out empty paragraphs but preserve content like "0"
3805 - $paragraphs = array_values(array_filter(array_map('trim', $paragraphs), function($p) {
3806 - return $p !== '';
3807 - }));
3808 -
3809 - if (empty($paragraphs)) {
3810 - // No content after filtering
3811 - return '';
3812 - } elseif (count($paragraphs) > 1) {
3813 - // Multiple paragraphs - wrap each in <p> tags, convert single newlines to <br>
3814 - $formatted_paragraphs = array_map(function($p) {
3815 - return nl2br($p);
3816 - }, $paragraphs);
3817 - $text = '<p>' . implode('</p><p>', $formatted_paragraphs) . '</p>';
3818 - } else {
3819 - // Single paragraph - just convert newlines to <br>
3820 - $text = nl2br($paragraphs[0]);
3821 - }
3822 -
3823 3520 return $text;
3824 3521 }
3825 3522
3826 3523 /**
@@ -3874,44 +3571,173 @@
3874 3571 if (!$screen || strpos($screen->base, 'mxchat') === false) {
3875 3572 return;
3876 3573 }
3877 3574
3878 - $dismiss_button = '<button type="button" class="notice-dismiss"><span class="screen-reader-text">' . esc_html__('Dismiss this notice.', 'mxchat') . '</span></button>';
3575 + //error_log('MxChat admin_notices hook fired on screen: ' . $screen->base);
3879 3576
3880 3577 // Check for error notices
3881 3578 $error_notice = get_transient('mxchat_admin_notice_error');
3882 3579 if ($error_notice) {
3883 - echo '<div class="notice notice-error is-dismissible"><p>' . wp_kses_post($error_notice) . '</p>' . $dismiss_button . '</div>';
3580 + //error_log('Found error transient: ' . $error_notice);
3581 + echo '<div class="notice notice-error is-dismissible"><p>' . wp_kses_post($error_notice) . '</p></div>';
3884 3582 delete_transient('mxchat_admin_notice_error');
3583 + //error_log('Displayed and deleted error transient');
3584 + } else {
3585 + //error_log('No error transient found');
3885 3586 }
3886 3587
3887 3588 // Check for success notices
3888 3589 $success_notice = get_transient('mxchat_admin_notice_success');
3889 3590 if ($success_notice) {
3890 - echo '<div class="notice notice-success is-dismissible"><p>' . wp_kses_post($success_notice) . '</p>' . $dismiss_button . '</div>';
3591 + //error_log('Found success transient: ' . $success_notice);
3592 + echo '<div class="notice notice-success is-dismissible"><p>' . wp_kses_post($success_notice) . '</p></div>';
3891 3593 delete_transient('mxchat_admin_notice_success');
3594 + //error_log('Displayed and deleted success transient');
3892 3595 }
3893 3596
3894 3597 // Check for info notices
3895 3598 $info_notice = get_transient('mxchat_admin_notice_info');
3896 3599 if ($info_notice) {
3897 - echo '<div class="notice notice-info is-dismissible"><p>' . wp_kses_post($info_notice) . '</p>' . $dismiss_button . '</div>';
3600 + //error_log('Found info transient: ' . $info_notice);
3601 + echo '<div class="notice notice-info is-dismissible"><p>' . wp_kses_post($info_notice) . '</p></div>';
3898 3602 delete_transient('mxchat_admin_notice_info');
3603 + //error_log('Displayed and deleted info transient');
3899 3604 }
3605 +
3900 3606 }
3901 3607
3902 3608
3903 3609 public function mxchat_create_activation_page() {
3904 - // Include the new Pro & Extensions page template
3905 - require_once plugin_dir_path(__FILE__) . 'admin-pro-page.php';
3610 + $license_status = get_option('mxchat_license_status', 'inactive');
3611 + $license_error = get_option('mxchat_license_error', '');
3612 + $current_domain = parse_url(home_url(), PHP_URL_HOST);
3906 3613
3907 - // Get addons configuration from the MxChat_Addons class
3908 - require_once plugin_dir_path(__FILE__) . 'class-mxchat-addons.php';
3909 - $addons_instance = new MxChat_Addons();
3910 - $addons_config = $addons_instance->get_addons_config();
3614 + $license_management_url = 'https://mxchat.ai/my-account/orders/';
3911 3615
3912 - // Render the consolidated Pro & Extensions page
3913 - mxchat_render_pro_page($this, $addons_config);
3616 + // Check if this activation has been linked to a domain
3617 + $is_domain_linked = $this->is_current_activation_linked($current_domain);
3618 + ?>
3619 + <div class="wrap mxchat-admin-activation">
3620 + <div class="mxchat-pro-hero">
3621 + <h1 class="pro-title">
3622 + <span class="pro-gradient-text">Manage</span> MxChat Pro
3623 + </h1>
3624 + <p class="pro-subtitle">
3625 + <?php if ($license_status === 'active'): ?>
3626 + <?php esc_html_e('Your pro license is active. Manage your activation below.', 'mxchat'); ?>
3627 + <?php else: ?>
3628 + <?php esc_html_e('Enter your license key to unlock premium features, advanced AI capabilities, and priority support.', 'mxchat'); ?>
3629 + <?php endif; ?>
3630 + </p>
3631 + </div>
3632 +
3633 + <?php if ($license_status === 'inactive' && !empty($license_error)): ?>
3634 + <div class="error notice">
3635 + <p><?php echo esc_html($license_error); ?></p>
3636 + </div>
3637 + <?php endif; ?>
3638 +
3639 + <!-- Domain Linking Notice for Existing Users -->
3640 + <?php if ($license_status === 'active' && !$is_domain_linked): ?>
3641 + <div class="notice notice-info">
3642 + <p>
3643 + <strong><?php esc_html_e('New Feature: Domain Tracking', 'mxchat'); ?></strong><br>
3644 + <?php esc_html_e('We\'ve enhanced our license system to track your activated domains. Link this activation to your domain for better license management.', 'mxchat'); ?>
3645 + </p>
3646 + <p>
3647 + <button type="button" id="link-domain-button" class="button button-secondary">
3648 + <?php esc_html_e('Link This Domain', 'mxchat'); ?>
3649 + </button>
3650 + <span id="domain-link-status" style="margin-left: 10px;"></span>
3651 + </p>
3652 + </div>
3653 + <?php endif; ?>
3654 +
3655 + <!-- Activation Form -->
3656 + <form id="mxchat-activation-form" class="mxchat-pro-form" style="<?php echo $license_status === 'active' ? 'display: none;' : ''; ?>">
3657 + <div class="mxchat-pro-form-container">
3658 + <table class="form-table">
3659 + <tr valign="top">
3660 + <th scope="row"><?php esc_html_e('Email Address', 'mxchat'); ?></th>
3661 + <td>
3662 + <input type="email" id="mxchat_pro_email" name="mxchat_pro_email" value="<?php echo esc_attr(get_option('mxchat_pro_email')); ?>" class="regular-text mxchat-pro-input" required />
3663 + </td>
3664 + </tr>
3665 + <tr valign="top">
3666 + <th scope="row"><?php esc_html_e('Activation Key', 'mxchat'); ?></th>
3667 + <td>
3668 + <input type="text" id="mxchat_activation_key" name="mxchat_activation_key" value="<?php echo esc_attr(get_option('mxchat_activation_key')); ?>" class="regular-text mxchat-pro-input" required />
3669 + </td>
3670 + </tr>
3671 + </table>
3672 +
3673 + <!-- Hidden field for domain -->
3674 + <input type="hidden" id="mxchat_domain" name="domain" value="<?php echo esc_attr($current_domain); ?>" />
3675 +
3676 + <?php if ($license_status !== 'active'): ?>
3677 + <div class="mxchat-pro-button-container">
3678 + <button type="submit" id="activate_license_button" class="button button-primary mxchat-pro-button"><?php esc_html_e('Activate License', 'mxchat'); ?></button>
3679 + <div id="mxchat-activation-spinner" class="mxchat-activation-spinner" style="display: none;"></div>
3680 + </div>
3681 + <?php endif; ?>
3682 + </div>
3683 + </form>
3684 +
3685 + <!-- License Status Display -->
3686 + <div class="mxchat-pro-status">
3687 + <h3><?php esc_html_e('License Status:', 'mxchat'); ?>
3688 + <span id="mxchat-license-status" class="mxchat-status-badge <?php echo $license_status; ?>">
3689 + <?php echo $license_status === 'active' ? esc_html__('Active', 'mxchat') : esc_html__('Inactive', 'mxchat'); ?>
3690 + </span>
3691 + </h3>
3692 +
3693 + <?php if ($license_status === 'active'): ?>
3694 + <div class="mxchat-license-details">
3695 + <p><strong><?php esc_html_e('Domain:', 'mxchat'); ?></strong> <?php echo esc_html($current_domain); ?>
3696 + <?php if ($is_domain_linked): ?>
3697 + <span style="color: green; margin-left: 10px;">✓ <?php esc_html_e('Linked', 'mxchat'); ?></span>
3698 + <?php else: ?>
3699 + <span style="color: orange; margin-left: 10px;">⚠ <?php esc_html_e('Not Linked', 'mxchat'); ?></span>
3700 + <?php endif; ?>
3701 + </p>
3702 + <p><strong><?php esc_html_e('Email:', 'mxchat'); ?></strong> <?php echo esc_html(get_option('mxchat_pro_email')); ?></p>
3703 + <p><strong><?php esc_html_e('License Key:', 'mxchat'); ?></strong>
3704 + <code style="background: #f1f1f1; padding: 2px 6px; border-radius: 3px;">
3705 + <?php echo esc_html(get_option('mxchat_activation_key')); ?>
3706 + </code>
3707 + </p>
3708 + <p>
3709 + <small>
3710 + <?php esc_html_e('Manage all your licenses and domains on', 'mxchat'); ?>
3711 + <a href="<?php echo esc_url($license_management_url); ?>" target="_blank">
3712 + <?php esc_html_e('your account dashboard', 'mxchat'); ?>
3713 + </a>
3714 + </small>
3715 + </p>
3716 +
3717 + <!-- Deactivation Section -->
3718 + <div class="mxchat-deactivation-section" style="margin-top: 20px; padding: 15px; background: #fff3cd; border-left: 4px solid #ffc107; border-radius: 4px;">
3719 + <h4 style="margin-top: 0; color: #856404;">
3720 + <?php esc_html_e('License Management', 'mxchat'); ?>
3721 + </h4>
3722 + <p style="margin-bottom: 15px; color: #856404;">
3723 + <?php esc_html_e('Need to move this license to another site? Deactivate it here first, then activate it on your new site.', 'mxchat'); ?>
3724 + </p>
3725 + <button type="button" id="deactivate-license-button" class="button button-secondary" style="background: #dc3545; color: white; border-color: #dc3545;">
3726 + 🔓 <?php esc_html_e('Deactivate License', 'mxchat'); ?>
3727 + </button>
3728 + <span id="deactivate-status" style="margin-left: 10px;"></span>
3729 + </div>
3730 + </div>
3731 + <?php endif; ?>
3732 + </div>
3733 +
3734 + <!-- Hidden fields for JavaScript -->
3735 + <input type="hidden" id="mxchat-ajax-url" value="<?php echo admin_url('admin-ajax.php'); ?>" />
3736 + <input type="hidden" id="mxchat-nonce" value="<?php echo wp_create_nonce('mxchat_activate_license_nonce'); ?>" />
3737 + <input type="hidden" id="mxchat-api-url" value="https://mxchat.ai" />
3738 + </div>
3739 + <?php
3914 3740 }
3915 3741
3916 3742 /**
3917 3743 * Check if current activation is linked to a domain
@@ -3916,9 +3742,9 @@
3916 3742 /**
3917 3743 * Check if current activation is linked to a domain
3918 3744 * This checks YOUR website's database, not the user's local database
3919 3745 */
3920 -public function is_current_activation_linked($domain) {
3746 +private function is_current_activation_linked($domain) {
3921 3747 $license_key = get_option('mxchat_activation_key');
3922 3748 $email = get_option('mxchat_pro_email');
3923 3749
3924 3750 if (empty($license_key) || empty($email)) {
@@ -3949,54 +3775,50 @@
3949 3775 if (!current_user_can('manage_options')) {
3950 3776 return;
3951 3777 }
3952 3778
3779 + // Keep existing data fetching logic
3953 3780 global $wpdb;
3954 3781 $table_name = $wpdb->prefix . 'mxchat_intents';
3782 + $page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1;
3783 + $per_page = 20;
3784 + $offset = ($page - 1) * $per_page;
3955 3785
3956 - // Get stats for dashboard
3957 - $total_actions = $wpdb->get_var("SELECT COUNT(*) FROM $table_name");
3958 - $enabled_actions = $wpdb->get_var("SELECT COUNT(*) FROM $table_name WHERE enabled = 1");
3959 - $disabled_actions = $total_actions - $enabled_actions;
3786 + // Success message
3787 + if (isset($_GET['updated']) && $_GET['updated'] === 'true') {
3788 + echo '<div class="notice notice-success is-dismissible"><p>' .
3789 + esc_html__('Action updated successfully.', 'mxchat') .
3790 + '</p></div>';
3791 + }
3960 3792
3961 - // Get unique action types count
3962 - $action_types_count = $wpdb->get_var("SELECT COUNT(DISTINCT callback_function) FROM $table_name");
3793 + // Filtering logic
3794 + $where = '1=1';
3795 + $search_term = isset($_GET['s']) ? trim($_GET['s']) : '';
3796 + $callback_filter = isset($_GET['callback_filter']) ? sanitize_text_field($_GET['callback_filter']) : '';
3963 3797
3964 - // Get action type distribution
3965 - $type_distribution_raw = $wpdb->get_results("SELECT callback_function, COUNT(*) as count FROM $table_name GROUP BY callback_function ORDER BY count DESC LIMIT 10");
3966 - $available_callbacks = $this->mxchat_get_available_callbacks();
3967 - $callback_groups = $this->mxchat_get_available_callbacks(true, true);
3798 + if ($search_term) {
3799 + $search_term_like = '%' . $wpdb->esc_like($search_term) . '%';
3800 + $where .= $wpdb->prepare(' AND (intent_label LIKE %s OR phrases LIKE %s)',
3801 + $search_term_like, $search_term_like);
3802 + }
3968 3803
3969 - $action_type_distribution = array();
3970 - foreach ($type_distribution_raw as $row) {
3971 - $label = isset($available_callbacks[$row->callback_function]['label'])
3972 - ? $available_callbacks[$row->callback_function]['label']
3973 - : $row->callback_function;
3974 - $action_type_distribution[$label] = $row->count;
3804 + if ($callback_filter) {
3805 + $where .= $wpdb->prepare(' AND callback_function = %s', $callback_filter);
3975 3806 }
3976 3807
3977 - // Prepare page data
3978 - $page_data = array(
3979 - 'total_actions' => $total_actions,
3980 - 'enabled_actions' => $enabled_actions,
3981 - 'disabled_actions' => $disabled_actions,
3982 - 'action_types_count' => $action_types_count,
3983 - 'action_type_distribution' => $action_type_distribution,
3984 - 'available_callbacks' => $available_callbacks,
3985 - 'callback_groups' => $callback_groups,
3986 - );
3808 + // Pagination
3809 + $total_intents = $wpdb->get_var("SELECT COUNT(*) FROM $table_name WHERE $where");
3810 + $total_pages = ceil($total_intents / $per_page);
3987 3811
3988 - // Include and render the new template
3989 - require_once plugin_dir_path(__FILE__) . 'admin-actions-page.php';
3990 - mxchat_render_actions_page($this, $page_data);
3991 -}
3812 + // Get intents (now called actions)
3813 + $actions = $wpdb->get_results($wpdb->prepare(
3814 + "SELECT * FROM $table_name WHERE $where LIMIT %d OFFSET %d",
3815 + $per_page, $offset
3816 + ));
3992 3817
3993 -/**
3994 - * LEGACY HTML - Preserved below for reference, to be removed in future update
3995 - */
3996 -function mxchat_actions_page_legacy_html() {
3997 - // This function is deprecated and no longer used
3998 - // The new template is in includes/admin-actions-page.php
3818 + // Get callbacks
3819 + $available_callbacks = $this->mxchat_get_available_callbacks();
3820 +
3999 3821 ?>
4000 3822 <div class="wrap mxchat-wrapper">
4001 3823 <!-- Hero Section -->
4002 3824 <div class="mxchat-hero">
@@ -4272,14 +4094,14 @@
4272 4094 $category_slug = sanitize_title($group_label);
4273 4095
4274 4096 foreach ($group_callbacks as $function => $data) :
4275 4097 $label = $data['label'];
4098 + $pro_only = $data['pro_only'];
4276 4099 $icon = isset($data['icon']) ? $data['icon'] : 'admin-generic';
4277 4100 $description = isset($data['description']) ? $data['description'] : '';
4278 4101 $is_addon = isset($data['addon']) && $data['addon'] !== false;
4279 4102 $addon_name = isset($data['addon_name']) ? $data['addon_name'] : '';
4280 4103 $is_installed = isset($data['installed']) ? $data['installed'] : true;
4281 - $is_promo = !empty($data['addon_promo']) && !$is_installed;
4282 4104
4283 4105 // Determine card status and styling
4284 4106 $card_class = 'mxchat-action-type-card';
4285 4107 $icon_class = 'mxchat-action-type-icon';
@@ -4284,13 +4106,15 @@
4284 4106 $card_class = 'mxchat-action-type-card';
4285 4107 $icon_class = 'mxchat-action-type-icon';
4286 4108 $status_badge = '';
4287 4109
4288 - if ($is_promo) {
4289 - // Promotional card — not selectable, just informational
4290 - $card_class .= ' not-installed mxchat-promo-card';
4291 - $status_badge = '<span class="mxchat-addon-badge">' . esc_html__('Add-on Required', 'mxchat') . '</span>';
4292 - } elseif ($is_addon && !$is_installed) {
4110 + if ($pro_only && !$this->is_activated) {
4111 + // Pro feature but no Pro license
4112 + $icon_class .= ' pro-feature';
4113 + $status_badge = '<span class="mxchat-pro-badge">' . esc_html__('Pro', 'mxchat') . '</span>';
4114 + }
4115 +
4116 + if ($is_addon && !$is_installed) {
4293 4117 // Add-on not installed
4294 4118 $card_class .= ' not-installed';
4295 4119 $status_badge .= '<span class="mxchat-addon-badge">' . esc_html__('Add-on Required', 'mxchat') . '</span>';
4296 4120 }
@@ -4304,16 +4128,13 @@
4304 4128 }
4305 4129 ?>
4306 4130 <div class="<?php echo esc_attr($card_class); ?>"
4307 4131 data-category="<?php echo esc_attr($category_slug); ?>"
4308 - <?php if (!$is_promo) : ?>
4309 4132 data-value="<?php echo esc_attr($function); ?>"
4310 4133 data-label="<?php echo esc_attr($label); ?>"
4311 - <?php endif; ?>
4312 - data-pro="false"
4134 + data-pro="<?php echo $pro_only ? 'true' : 'false'; ?>"
4313 4135 data-addon="<?php echo esc_attr($is_addon ? $data['addon'] : ''); ?>"
4314 - data-installed="<?php echo $is_installed ? 'true' : 'false'; ?>"
4315 - <?php if ($is_promo) : ?>data-promo="true"<?php endif; ?>>
4136 + data-installed="<?php echo $is_installed ? 'true' : 'false'; ?>">
4316 4137 <div class="<?php echo esc_attr($icon_class); ?>">
4317 4138 <span class="dashicons dashicons-<?php echo esc_attr($icon); ?>"></span>
4318 4139 </div>
4319 4140 <div class="mxchat-action-type-info">
@@ -4322,15 +4143,14 @@
4322 4143 <?php if (!empty($status_badge)) : ?>
4323 4144 <?php echo $status_badge; ?>
4324 4145 <?php endif; ?>
4325 4146
4326 - <?php if ($is_promo || ($is_addon && !$is_installed)) : ?>
4147 + <?php if ($is_addon && !$is_installed) : ?>
4327 4148 <div class="mxchat-addon-info">
4328 4149 <?php echo esc_html(sprintf(
4329 4150 __('Requires %s', 'mxchat'),
4330 4151 $addon_name
4331 4152 )); ?>
4332 - — <a href="https://mxchat.ai/" target="_blank"><?php esc_html_e('Get Add-on', 'mxchat'); ?></a>
4333 4153 </div>
4334 4154 <?php endif; ?>
4335 4155 </div>
4336 4156 </div>
@@ -4593,9 +4413,9 @@
4593 4413 'intent_label' => $intent_label,
4594 4414 'phrases' => implode(', ', $phrases_array),
4595 4415 'embedding_vector' => $serialized_vector,
4596 4416 'similarity_threshold' => $similarity_threshold,
4597 - 'enabled_bots' => $enabled_bots_json, // Include enabled_bots in update
4417 + 'enabled_bots' => $enabled_bots_json // Include enabled_bots in update
4598 4418 ),
4599 4419 array('id' => $intent_id),
4600 4420 array('%s', '%s', '%s', '%f', '%s'), // Format: string, string, string, float, string
4601 4421 array('%d') // Where format: integer
@@ -4657,15 +4477,12 @@
4657 4477 $this->handle_embedding_error(__('Invalid callback function selected.', 'mxchat'));
4658 4478 return;
4659 4479 }
4660 4480
4661 - // Check if this is an add-on promotional placeholder (not a real action)
4662 - if (!empty($available_callbacks[$callback_function]['addon_promo'])) {
4663 - $addon_name = isset($available_callbacks[$callback_function]['addon_name']) ? $available_callbacks[$callback_function]['addon_name'] : __('an add-on', 'mxchat');
4664 - $this->handle_embedding_error(sprintf(
4665 - __('This action requires the %s to be installed and activated.', 'mxchat'),
4666 - $addon_name
4667 - ));
4481 + // Check Pro requirements
4482 + $is_pro_only = $available_callbacks[$callback_function]['pro_only'];
4483 + if ($is_pro_only && !$this->is_activated) {
4484 + $this->handle_embedding_error(__('This callback function is available in the Pro version only.', 'mxchat'));
4668 4485 return;
4669 4486 }
4670 4487
4671 4488 // Process phrases
@@ -4712,9 +4529,9 @@
4712 4529 'phrases' => implode(', ', $phrases_array),
4713 4530 'embedding_vector' => $serialized_vector,
4714 4531 'callback_function' => $callback_function,
4715 4532 'similarity_threshold' => $similarity_threshold,
4716 - 'enabled_bots' => $enabled_bots_json, // NEW field
4533 + 'enabled_bots' => $enabled_bots_json // NEW field
4717 4534 ]);
4718 4535
4719 4536 if ($result === false) {
4720 4537 $this->handle_embedding_error(__('Database error: ', 'mxchat') . $wpdb->last_error);
@@ -4746,577 +4563,10 @@
4746 4563 exit;
4747 4564 }
4748 4565 }
4749 4566
4750 -/**
4751 - * AJAX handler to fetch actions list for the new split-panel UI
4752 - */
4753 -public function mxchat_fetch_actions_list() {
4754 - check_ajax_referer('mxchat_actions_nonce', 'security');
4755 4567
4756 - if (!current_user_can('manage_options')) {
4757 - wp_send_json_error(__('Unauthorized', 'mxchat'));
4758 - }
4759 -
4760 - global $wpdb;
4761 - $table_name = $wpdb->prefix . 'mxchat_intents';
4762 -
4763 - $page = isset($_POST['page']) ? max(1, intval($_POST['page'])) : 1;
4764 - $per_page = isset($_POST['per_page']) ? min(100, max(1, intval($_POST['per_page']))) : 50;
4765 - $offset = ($page - 1) * $per_page;
4766 - $search = isset($_POST['search']) ? sanitize_text_field($_POST['search']) : '';
4767 - $callback_filter = isset($_POST['callback_filter']) ? sanitize_text_field($_POST['callback_filter']) : '';
4768 - $sort_order = isset($_POST['sort_order']) && $_POST['sort_order'] === 'asc' ? 'ASC' : 'DESC';
4769 -
4770 - // Build WHERE clause
4771 - $where = '1=1';
4772 - $params = array();
4773 -
4774 - if ($search) {
4775 - $search_like = '%' . $wpdb->esc_like($search) . '%';
4776 - $where .= ' AND (intent_label LIKE %s OR phrases LIKE %s)';
4777 - $params[] = $search_like;
4778 - $params[] = $search_like;
4779 - }
4780 -
4781 - if ($callback_filter) {
4782 - $where .= ' AND callback_function = %s';
4783 - $params[] = $callback_filter;
4784 - }
4785 -
4786 - // Get total count
4787 - $count_query = "SELECT COUNT(*) FROM $table_name WHERE $where";
4788 - if (!empty($params)) {
4789 - $count_query = $wpdb->prepare($count_query, $params);
4790 - }
4791 - $total_actions = $wpdb->get_var($count_query);
4792 -
4793 - // Get actions
4794 - $query = "SELECT * FROM $table_name WHERE $where ORDER BY id $sort_order LIMIT %d OFFSET %d";
4795 - $all_params = array_merge($params, array($per_page, $offset));
4796 - $actions = $wpdb->get_results($wpdb->prepare($query, $all_params));
4797 -
4798 - // Get available callbacks for labels/icons
4799 - $available_callbacks = $this->mxchat_get_available_callbacks();
4800 -
4801 - // Prefetch individual phrase counts for all fetched actions
4802 - $phrases_table = $wpdb->prefix . 'mxchat_intent_phrases';
4803 - $phrase_counts = array();
4804 - if ($wpdb->get_var("SHOW TABLES LIKE '$phrases_table'") === $phrases_table) {
4805 - $action_ids = wp_list_pluck($actions, 'id');
4806 - if (!empty($action_ids)) {
4807 - $id_placeholders = implode(',', array_fill(0, count($action_ids), '%d'));
4808 - $count_results = $wpdb->get_results($wpdb->prepare(
4809 - "SELECT intent_id, COUNT(*) as cnt FROM $phrases_table WHERE intent_id IN ($id_placeholders) GROUP BY intent_id",
4810 - $action_ids
4811 - ));
4812 - foreach ($count_results as $row) {
4813 - $phrase_counts[$row->intent_id] = intval($row->cnt);
4814 - }
4815 - }
4816 - }
4817 -
4818 - // Format actions for response
4819 - $formatted_actions = array();
4820 - foreach ($actions as $action) {
4821 - $callback_data = isset($available_callbacks[$action->callback_function])
4822 - ? $available_callbacks[$action->callback_function]
4823 - : array('label' => $action->callback_function, 'icon' => 'admin-generic');
4824 -
4825 - $enabled_bots = json_decode($action->enabled_bots, true);
4826 - if (!is_array($enabled_bots)) {
4827 - $enabled_bots = array('default');
4828 - }
4829 -
4830 - $formatted_actions[] = array(
4831 - 'id' => intval($action->id),
4832 - 'label' => $action->intent_label,
4833 - 'phrases' => $action->phrases,
4834 - 'callback_function' => $action->callback_function,
4835 - 'callback_label' => $callback_data['label'],
4836 - 'icon' => isset($callback_data['icon']) ? $callback_data['icon'] : 'admin-generic',
4837 - 'threshold' => round($action->similarity_threshold * 100),
4838 - 'enabled' => (bool) $action->enabled,
4839 - 'enabled_bots' => $enabled_bots,
4840 - 'has_legacy_vector' => !empty($action->embedding_vector),
4841 - 'individual_phrase_count' => isset($phrase_counts[$action->id]) ? $phrase_counts[$action->id] : 0,
4842 - );
4843 - }
4844 -
4845 - $total_pages = ceil($total_actions / $per_page);
4846 - $showing_start = $total_actions > 0 ? $offset + 1 : 0;
4847 - $showing_end = min($offset + $per_page, $total_actions);
4848 -
4849 - wp_send_json_success(array(
4850 - 'actions' => $formatted_actions,
4851 - 'page' => $page,
4852 - 'per_page' => $per_page,
4853 - 'total_actions' => intval($total_actions),
4854 - 'total_pages' => $total_pages,
4855 - 'showing_start' => $showing_start,
4856 - 'showing_end' => $showing_end,
4857 - ));
4858 -}
4859 -
4860 4568 /**
4861 - * AJAX handler to toggle action enabled status
4862 - */
4863 -public function mxchat_toggle_action_status() {
4864 - check_ajax_referer('mxchat_actions_nonce', 'security');
4865 -
4866 - if (!current_user_can('manage_options')) {
4867 - wp_send_json_error(__('Unauthorized', 'mxchat'));
4868 - }
4869 -
4870 - $action_id = isset($_POST['action_id']) ? intval($_POST['action_id']) : 0;
4871 - $enabled = isset($_POST['enabled']) ? intval($_POST['enabled']) : 0;
4872 -
4873 - if (!$action_id) {
4874 - wp_send_json_error(__('Invalid action ID', 'mxchat'));
4875 - }
4876 -
4877 - global $wpdb;
4878 - $table_name = $wpdb->prefix . 'mxchat_intents';
4879 -
4880 - $result = $wpdb->update(
4881 - $table_name,
4882 - array('enabled' => $enabled ? 1 : 0),
4883 - array('id' => $action_id),
4884 - array('%d'),
4885 - array('%d')
4886 - );
4887 -
4888 - if ($result === false) {
4889 - wp_send_json_error(__('Failed to update action status', 'mxchat'));
4890 - }
4891 -
4892 - wp_send_json_success(array('enabled' => (bool) $enabled));
4893 -}
4894 -
4895 -/**
4896 - * AJAX handler to bulk delete actions
4897 - */
4898 -public function mxchat_bulk_delete_actions() {
4899 - check_ajax_referer('mxchat_delete_intent_nonce', 'security');
4900 -
4901 - if (!current_user_can('manage_options')) {
4902 - wp_send_json_error(__('Unauthorized', 'mxchat'));
4903 - }
4904 -
4905 - $action_ids = isset($_POST['action_ids']) ? array_map('intval', (array) $_POST['action_ids']) : array();
4906 -
4907 - if (empty($action_ids)) {
4908 - wp_send_json_error(__('No actions selected', 'mxchat'));
4909 - }
4910 -
4911 - global $wpdb;
4912 - $table_name = $wpdb->prefix . 'mxchat_intents';
4913 -
4914 - $placeholders = implode(',', array_fill(0, count($action_ids), '%d'));
4915 - $query = $wpdb->prepare("DELETE FROM $table_name WHERE id IN ($placeholders)", $action_ids);
4916 - $result = $wpdb->query($query);
4917 -
4918 - if ($result === false) {
4919 - wp_send_json_error(__('Failed to delete actions', 'mxchat'));
4920 - }
4921 -
4922 - // Also delete individual phrases for these intents
4923 - $phrases_table = $wpdb->prefix . 'mxchat_intent_phrases';
4924 - if ($wpdb->get_var("SHOW TABLES LIKE '$phrases_table'") === $phrases_table) {
4925 - $wpdb->query($wpdb->prepare("DELETE FROM $phrases_table WHERE intent_id IN ($placeholders)", $action_ids));
4926 - }
4927 -
4928 - wp_send_json_success(array('deleted' => $result));
4929 -}
4930 -
4931 -/**
4932 - * AJAX handler to add a new intent/action
4933 - */
4934 -public function mxchat_add_intent_ajax() {
4935 - check_ajax_referer('mxchat_add_intent_nonce', 'security');
4936 -
4937 - if (!current_user_can('manage_options')) {
4938 - wp_send_json_error(__('Unauthorized', 'mxchat'));
4939 - }
4940 -
4941 - global $wpdb;
4942 - $table_name = $wpdb->prefix . 'mxchat_intents';
4943 -
4944 - // Sanitize input
4945 - $intent_label = isset($_POST['intent_label']) ? sanitize_text_field($_POST['intent_label']) : '';
4946 - $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
4947 - $callback_function = isset($_POST['callback_function']) ? sanitize_text_field($_POST['callback_function']) : '';
4948 - $similarity_threshold = isset($_POST['similarity_threshold']) ? floatval($_POST['similarity_threshold']) / 100 : 0.85;
4949 - $enabled_bots = isset($_POST['enabled_bots']) ? array_map('sanitize_text_field', (array) $_POST['enabled_bots']) : array('default');
4950 -
4951 - // Validate
4952 - // Check if using new individual phrases mode or legacy mode
4953 - $individual_phrases = isset($_POST['individual_phrases']) ? array_filter(array_map('sanitize_text_field', (array) $_POST['individual_phrases'])) : array();
4954 - $use_individual = !empty($individual_phrases);
4955 -
4956 - // Validate required fields (phrases not required when using individual mode)
4957 - if (empty($intent_label) || empty($callback_function)) {
4958 - wp_send_json_error(__('Please fill in all required fields.', 'mxchat'));
4959 - }
4960 - if (!$use_individual && empty($phrases_input)) {
4961 - wp_send_json_error(__('Please fill in all required fields.', 'mxchat'));
4962 - }
4963 -
4964 - // Ensure default bot is included
4965 - if (!in_array('default', $enabled_bots)) {
4966 - $enabled_bots[] = 'default';
4967 - }
4968 - $enabled_bots_json = json_encode($enabled_bots);
4969 -
4970 - if ($use_individual) {
4971 - // New mode: individual phrases each get their own vector
4972 - // Insert the intent row with empty legacy fields
4973 - $result = $wpdb->insert(
4974 - $table_name,
4975 - array(
4976 - 'intent_label' => $intent_label,
4977 - 'phrases' => '',
4978 - 'embedding_vector' => '',
4979 - 'similarity_threshold' => $similarity_threshold,
4980 - 'callback_function' => $callback_function,
4981 - 'enabled' => 1,
4982 - 'enabled_bots' => $enabled_bots_json,
4983 - )
4984 - );
4985 -
4986 - if ($result === false) {
4987 - wp_send_json_error(__('Failed to add action to database.', 'mxchat'));
4988 - }
4989 -
4990 - $intent_id = $wpdb->insert_id;
4991 -
4992 - // Insert each phrase individually with its own embedding
4993 - $phrases_table = $wpdb->prefix . 'mxchat_intent_phrases';
4994 - $failed_phrases = array();
4995 - foreach ($individual_phrases as $phrase) {
4996 - $phrase = trim($phrase);
4997 - if (empty($phrase)) continue;
4998 -
4999 - $embedding_vector = $this->mxchat_generate_embedding($phrase);
5000 - if (is_wp_error($embedding_vector)) {
5001 - $failed_phrases[] = $phrase;
5002 - continue;
5003 - }
5004 -
5005 - $wpdb->insert(
5006 - $phrases_table,
5007 - array(
5008 - 'intent_id' => $intent_id,
5009 - 'phrase' => $phrase,
5010 - 'embedding_vector' => maybe_serialize($embedding_vector),
5011 - )
5012 - );
5013 - }
5014 -
5015 - $response = array('id' => $intent_id);
5016 - if (!empty($failed_phrases)) {
5017 - $response['failed_phrases'] = $failed_phrases;
5018 - }
5019 - wp_send_json_success($response);
5020 -
5021 - } else {
5022 - // Legacy mode: combine all phrases into one embedding (backwards compatible)
5023 - $phrases_array = array_filter(array_map('trim', preg_split('/[\n,]+/', $phrases_input)));
5024 - if (empty($phrases_array)) {
5025 - wp_send_json_error(__('Please provide at least one trigger phrase.', 'mxchat'));
5026 - }
5027 -
5028 - // Generate embedding (combine phrases into single string for embedding)
5029 - $embedding_vector = $this->mxchat_generate_embedding(implode(' ', $phrases_array));
5030 - if (is_wp_error($embedding_vector)) {
5031 - // Fallback: store without embedding
5032 - $serialized_vector = null;
5033 - } else {
5034 - $serialized_vector = maybe_serialize($embedding_vector);
5035 - }
5036 -
5037 - // Insert
5038 - $result = $wpdb->insert(
5039 - $table_name,
5040 - array(
5041 - 'intent_label' => $intent_label,
5042 - 'phrases' => implode(', ', $phrases_array),
5043 - 'embedding_vector' => $serialized_vector,
5044 - 'similarity_threshold' => $similarity_threshold,
5045 - 'callback_function' => $callback_function,
5046 - 'enabled' => 1,
5047 - 'enabled_bots' => $enabled_bots_json,
5048 - )
5049 - );
5050 -
5051 - if ($result === false) {
5052 - wp_send_json_error(__('Failed to add action to database.', 'mxchat'));
5053 - }
5054 -
5055 - wp_send_json_success(array('id' => $wpdb->insert_id));
5056 - }
5057 -}
5058 -
5059 -/**
5060 - * AJAX handler to edit an existing intent/action
5061 - */
5062 -public function mxchat_edit_intent_ajax() {
5063 - check_ajax_referer('mxchat_edit_intent', 'security');
5064 -
5065 - if (!current_user_can('manage_options')) {
5066 - wp_send_json_error(__('Unauthorized', 'mxchat'));
5067 - }
5068 -
5069 - global $wpdb;
5070 - $table_name = $wpdb->prefix . 'mxchat_intents';
5071 -
5072 - // Sanitize input
5073 - $intent_id = isset($_POST['intent_id']) ? intval($_POST['intent_id']) : 0;
5074 - $intent_label = isset($_POST['intent_label']) ? sanitize_text_field($_POST['intent_label']) : '';
5075 - $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
5076 - $callback_function = isset($_POST['callback_function']) ? sanitize_text_field($_POST['callback_function']) : '';
5077 - $similarity_threshold = isset($_POST['similarity_threshold']) ? floatval($_POST['similarity_threshold']) / 100 : 0.85;
5078 - $enabled_bots = isset($_POST['enabled_bots']) ? array_map('sanitize_text_field', (array) $_POST['enabled_bots']) : array('default');
5079 -
5080 - // Validate
5081 - if (!$intent_id || empty($intent_label)) {
5082 - wp_send_json_error(__('Please fill in all required fields.', 'mxchat'));
5083 - }
5084 -
5085 - // Check if phrases are managed individually (empty phrases_input means individual mode)
5086 - $uses_individual_phrases = empty($phrases_input);
5087 -
5088 - if ($uses_individual_phrases) {
5089 - // Individual phrase mode: only update non-phrase fields, skip embedding regeneration
5090 - $update_data = array(
5091 - 'intent_label' => $intent_label,
5092 - 'similarity_threshold' => $similarity_threshold,
5093 - 'callback_function' => $callback_function,
5094 - 'enabled_bots' => json_encode($enabled_bots),
5095 - );
5096 - } else {
5097 - // Legacy mode: process phrases and regenerate embedding (backwards compatible for add-ons)
5098 - $phrases_array = array_filter(array_map('trim', preg_split('/[\n,]+/', $phrases_input)));
5099 - if (empty($phrases_array)) {
5100 - wp_send_json_error(__('Please provide at least one trigger phrase.', 'mxchat'));
5101 - }
5102 -
5103 - // Generate new embedding (combine phrases into single string for embedding)
5104 - $embedding_vector = $this->mxchat_generate_embedding(implode(' ', $phrases_array));
5105 - if (is_wp_error($embedding_vector)) {
5106 - // Keep existing embedding
5107 - $update_data = array(
5108 - 'intent_label' => $intent_label,
5109 - 'phrases' => implode(', ', $phrases_array),
5110 - 'similarity_threshold' => $similarity_threshold,
5111 - 'callback_function' => $callback_function,
5112 - 'enabled_bots' => json_encode($enabled_bots),
5113 - );
5114 - } else {
5115 - $serialized_vector = maybe_serialize($embedding_vector);
5116 - $update_data = array(
5117 - 'intent_label' => $intent_label,
5118 - 'phrases' => implode(', ', $phrases_array),
5119 - 'embedding_vector' => $serialized_vector,
5120 - 'similarity_threshold' => $similarity_threshold,
5121 - 'callback_function' => $callback_function,
5122 - 'enabled_bots' => json_encode($enabled_bots),
5123 - );
5124 - }
5125 - }
5126 -
5127 - // Ensure default bot is included
5128 - if (!in_array('default', $enabled_bots)) {
5129 - $enabled_bots[] = 'default';
5130 - }
5131 - $update_data['enabled_bots'] = json_encode($enabled_bots);
5132 -
5133 - $result = $wpdb->update(
5134 - $table_name,
5135 - $update_data,
5136 - array('id' => $intent_id),
5137 - null,
5138 - array('%d')
5139 - );
5140 -
5141 - if ($result === false) {
5142 - wp_send_json_error(__('Failed to update action.', 'mxchat'));
5143 - }
5144 -
5145 - wp_send_json_success(array('updated' => true));
5146 -}
5147 -
5148 -/**
5149 - * AJAX handler to delete a single intent/action
5150 - */
5151 -public function mxchat_delete_intent_ajax() {
5152 - check_ajax_referer('mxchat_delete_intent_nonce', 'security');
5153 -
5154 - if (!current_user_can('manage_options')) {
5155 - wp_send_json_error(__('Unauthorized', 'mxchat'));
5156 - }
5157 -
5158 - $intent_id = isset($_POST['intent_id']) ? intval($_POST['intent_id']) : 0;
5159 -
5160 - if (!$intent_id) {
5161 - wp_send_json_error(__('Invalid action ID', 'mxchat'));
5162 - }
5163 -
5164 - global $wpdb;
5165 - $table_name = $wpdb->prefix . 'mxchat_intents';
5166 -
5167 - $result = $wpdb->delete($table_name, array('id' => $intent_id), array('%d'));
5168 -
5169 - if ($result === false) {
5170 - wp_send_json_error(__('Failed to delete action', 'mxchat'));
5171 - }
5172 -
5173 - // Also delete individual phrases for this intent
5174 - $phrases_table = $wpdb->prefix . 'mxchat_intent_phrases';
5175 - if ($wpdb->get_var("SHOW TABLES LIKE '$phrases_table'") === $phrases_table) {
5176 - $wpdb->delete($phrases_table, array('intent_id' => $intent_id), array('%d'));
5177 - }
5178 -
5179 - wp_send_json_success(array('deleted' => true));
5180 -}
5181 -
5182 -/**
5183 - * AJAX handler to add a single phrase with its own embedding to an intent
5184 - */
5185 -public function mxchat_add_phrase_ajax() {
5186 - check_ajax_referer('mxchat_add_phrase_nonce', 'security');
5187 -
5188 - if (!current_user_can('manage_options')) {
5189 - wp_send_json_error(__('Unauthorized', 'mxchat'));
5190 - }
5191 -
5192 - $intent_id = isset($_POST['intent_id']) ? intval($_POST['intent_id']) : 0;
5193 - $phrase = isset($_POST['phrase']) ? sanitize_text_field($_POST['phrase']) : '';
5194 -
5195 - if (!$intent_id || empty($phrase)) {
5196 - wp_send_json_error(__('Please provide an intent ID and phrase.', 'mxchat'));
5197 - }
5198 -
5199 - // Verify the intent exists
5200 - global $wpdb;
5201 - $intents_table = $wpdb->prefix . 'mxchat_intents';
5202 - $intent = $wpdb->get_row($wpdb->prepare("SELECT id FROM $intents_table WHERE id = %d", $intent_id));
5203 - if (!$intent) {
5204 - wp_send_json_error(__('Action not found.', 'mxchat'));
5205 - }
5206 -
5207 - // Generate embedding for this single phrase
5208 - $embedding_vector = $this->mxchat_generate_embedding($phrase);
5209 - if (is_wp_error($embedding_vector)) {
5210 - wp_send_json_error(__('Failed to generate embedding: ', 'mxchat') . $embedding_vector->get_error_message());
5211 - }
5212 -
5213 - $phrases_table = $wpdb->prefix . 'mxchat_intent_phrases';
5214 - $result = $wpdb->insert(
5215 - $phrases_table,
5216 - array(
5217 - 'intent_id' => $intent_id,
5218 - 'phrase' => $phrase,
5219 - 'embedding_vector' => maybe_serialize($embedding_vector),
5220 - )
5221 - );
5222 -
5223 - if ($result === false) {
5224 - wp_send_json_error(__('Failed to add phrase.', 'mxchat'));
5225 - }
5226 -
5227 - wp_send_json_success(array('id' => $wpdb->insert_id, 'phrase' => $phrase));
5228 -}
5229 -
5230 -/**
5231 - * AJAX handler to delete a single phrase from wp_mxchat_intent_phrases
5232 - */
5233 -public function mxchat_delete_phrase_ajax() {
5234 - check_ajax_referer('mxchat_delete_phrase_nonce', 'security');
5235 -
5236 - if (!current_user_can('manage_options')) {
5237 - wp_send_json_error(__('Unauthorized', 'mxchat'));
5238 - }
5239 -
5240 - $phrase_id = isset($_POST['phrase_id']) ? intval($_POST['phrase_id']) : 0;
5241 - if (!$phrase_id) {
5242 - wp_send_json_error(__('Invalid phrase ID.', 'mxchat'));
5243 - }
5244 -
5245 - global $wpdb;
5246 - $phrases_table = $wpdb->prefix . 'mxchat_intent_phrases';
5247 - $result = $wpdb->delete($phrases_table, array('id' => $phrase_id), array('%d'));
5248 -
5249 - if ($result === false) {
5250 - wp_send_json_error(__('Failed to delete phrase.', 'mxchat'));
5251 - }
5252 -
5253 - wp_send_json_success(array('deleted' => true));
5254 -}
5255 -
5256 -/**
5257 - * AJAX handler to fetch individual phrases for an intent
5258 - */
5259 -public function mxchat_get_phrases_ajax() {
5260 - check_ajax_referer('mxchat_get_phrases_nonce', 'security');
5261 -
5262 - if (!current_user_can('manage_options')) {
5263 - wp_send_json_error(__('Unauthorized', 'mxchat'));
5264 - }
5265 -
5266 - $intent_id = isset($_POST['intent_id']) ? intval($_POST['intent_id']) : 0;
5267 - if (!$intent_id) {
5268 - wp_send_json_error(__('Invalid intent ID.', 'mxchat'));
5269 - }
5270 -
5271 - global $wpdb;
5272 - $phrases_table = $wpdb->prefix . 'mxchat_intent_phrases';
5273 -
5274 - $phrases = array();
5275 - if ($wpdb->get_var("SHOW TABLES LIKE '$phrases_table'") === $phrases_table) {
5276 - $phrases = $wpdb->get_results($wpdb->prepare(
5277 - "SELECT id, phrase, created_at FROM $phrases_table WHERE intent_id = %d ORDER BY created_at ASC",
5278 - $intent_id
5279 - ));
5280 - }
5281 -
5282 - wp_send_json_success(array('phrases' => $phrases));
5283 -}
5284 -
5285 -/**
5286 - * AJAX handler to clear legacy phrases and embedding from the main intents table
5287 - */
5288 -public function mxchat_delete_legacy_phrases_ajax() {
5289 - check_ajax_referer('mxchat_delete_legacy_nonce', 'security');
5290 -
5291 - if (!current_user_can('manage_options')) {
5292 - wp_send_json_error(__('Unauthorized', 'mxchat'));
5293 - }
5294 -
5295 - $intent_id = isset($_POST['intent_id']) ? intval($_POST['intent_id']) : 0;
5296 - if (!$intent_id) {
5297 - wp_send_json_error(__('Invalid intent ID.', 'mxchat'));
5298 - }
5299 -
5300 - global $wpdb;
5301 - $table_name = $wpdb->prefix . 'mxchat_intents';
5302 -
5303 - $result = $wpdb->update(
5304 - $table_name,
5305 - array('phrases' => '', 'embedding_vector' => ''),
5306 - array('id' => $intent_id),
5307 - array('%s', '%s'),
5308 - array('%d')
5309 - );
5310 -
5311 - if ($result === false) {
5312 - wp_send_json_error(__('Failed to clear legacy phrases.', 'mxchat'));
5313 - }
5314 -
5315 - wp_send_json_success(array('cleared' => true));
5316 -}
5317 -
5318 -/**
5319 4569 * Enhanced get_available_callbacks function with form action exclusion
5320 4570 *
5321 4571 * @param bool $grouped Whether to return callbacks grouped by category
5322 4572 * @param bool $include_all Whether to include all potential actions (even if add-on not installed)
@@ -5354,10 +4604,9 @@
5354 4604 'mxchat_handle_product_recommendations',
5355 4605 'mxchat_handle_order_history',
5356 4606 'mxchat_show_product_card',
5357 4607 'mxchat_add_to_cart',
5358 - 'mxchat_checkout_redirect',
5359 - 'mxchat_handle_featured_products'
4608 + 'mxchat_checkout_redirect'
5360 4609 ),
5361 4610 'name' => __('WooCommerce Add-on', 'mxchat'),
5362 4611 'pro_required' => true
5363 4612 ),
@@ -5435,25 +4684,16 @@
5435 4684 'installed' => true
5436 4685 ),
5437 4686 // Pro core features - check is_activated property
5438 4687 'mxchat_generate_image' => array(
5439 - 'label' => __('Generate Image (OpenAI)', 'mxchat'),
4688 + 'label' => __('Generate Image', 'mxchat'),
5440 4689 'pro_only' => false,
5441 4690 'group' => __('Other Features', 'mxchat'),
5442 4691 'icon' => 'art',
5443 - 'description' => __('Create images with GPT Image from OpenAI (requires OpenAI API key)', 'mxchat'),
4692 + 'description' => __('Create images with DALL-E 3 from OpenAI (requires OpenAI API key)', 'mxchat'),
5444 4693 'addon' => false,
5445 4694 'installed' => true
5446 4695 ),
5447 - 'mxchat_generate_gemini_image' => array(
5448 - 'label' => __('Generate Image (Gemini)', 'mxchat'),
5449 - 'pro_only' => false,
5450 - 'group' => __('Other Features', 'mxchat'),
5451 - 'icon' => 'art',
5452 - 'description' => __('Create images with Imagen from Google (requires Gemini API key)', 'mxchat'),
5453 - 'addon' => false,
5454 - 'installed' => true
5455 - ),
5456 4696 'mxchat_handle_pdf_discussion' => array(
5457 4697 'label' => __('Chat with PDF', 'mxchat'),
5458 4698 'pro_only' => false,
5459 4699 'group' => __('Other Features', 'mxchat'),
@@ -5470,17 +4710,8 @@
5470 4710 'description' => __('Transfer conversation to a human support agent on Slack', 'mxchat'),
5471 4711 'addon' => false,
5472 4712 'installed' => true
5473 4713 ),
5474 - 'mxchat_telegram_live_agent_handover' => array(
5475 - 'label' => __('Telegram Live Agent', 'mxchat'),
5476 - 'pro_only' => false,
5477 - 'group' => __('Customer Engagement', 'mxchat'),
5478 - 'icon' => 'format-chat',
5479 - 'description' => __('Transfer conversation to a human support agent on Telegram', 'mxchat'),
5480 - 'addon' => false,
5481 - 'installed' => true
5482 - ),
5483 4714 'mxchat_handle_switch_to_chatbot_intent' => array(
5484 4715 'label' => __('Back to Chatbot', 'mxchat'),
5485 4716 'pro_only' => false,
5486 4717 'group' => __('Customer Engagement', 'mxchat'),
@@ -5491,15 +4722,13 @@
5491 4722 ),
5492 4723 );
5493 4724
5494 4725 // Add-on callbacks with placeholders - only include if the add-on is NOT active
5495 - // These are promotional/informational only — not selectable as real actions
5496 4726 $addon_callbacks = array(
5497 4727 // WooCommerce Add-on
5498 4728 'mxchat_handle_product_recommendations' => array(
5499 4729 'label' => __('Product Recommendations', 'mxchat'),
5500 - 'pro_only' => false,
5501 - 'addon_promo' => true,
4730 + 'pro_only' => true,
5502 4731 'group' => __('WooCommerce Features', 'mxchat'),
5503 4732 'icon' => 'cart',
5504 4733 'description' => __('Suggest products based on customer preferences', 'mxchat'),
5505 4734 ),
@@ -5504,10 +4733,9 @@
5504 4733 'description' => __('Suggest products based on customer preferences', 'mxchat'),
5505 4734 ),
5506 4735 'mxchat_handle_order_history' => array(
5507 4736 'label' => __('Order History', 'mxchat'),
5508 - 'pro_only' => false,
5509 - 'addon_promo' => true,
4737 + 'pro_only' => true,
5510 4738 'group' => __('WooCommerce Features', 'mxchat'),
5511 4739 'icon' => 'clipboard',
5512 4740 'description' => __('Allow customers to check their order status', 'mxchat'),
5513 4741 ),
@@ -5512,10 +4740,9 @@
5512 4740 'description' => __('Allow customers to check their order status', 'mxchat'),
5513 4741 ),
5514 4742 'mxchat_show_product_card' => array(
5515 4743 'label' => __('Show Product Card', 'mxchat'),
5516 - 'pro_only' => false,
5517 - 'addon_promo' => true,
4744 + 'pro_only' => true,
5518 4745 'group' => __('WooCommerce Features', 'mxchat'),
5519 4746 'icon' => 'products',
5520 4747 'description' => __('Display product information in the chat', 'mxchat'),
5521 4748 ),
@@ -5520,10 +4747,9 @@
5520 4747 'description' => __('Display product information in the chat', 'mxchat'),
5521 4748 ),
5522 4749 'mxchat_add_to_cart' => array(
5523 4750 'label' => __('Add to Cart', 'mxchat'),
5524 - 'pro_only' => false,
5525 - 'addon_promo' => true,
4751 + 'pro_only' => true,
5526 4752 'group' => __('WooCommerce Features', 'mxchat'),
5527 4753 'icon' => 'plus-alt',
5528 4754 'description' => __('Add products to cart directly from chat', 'mxchat'),
5529 4755 ),
@@ -5528,38 +4754,27 @@
5528 4754 'description' => __('Add products to cart directly from chat', 'mxchat'),
5529 4755 ),
5530 4756 'mxchat_checkout_redirect' => array(
5531 4757 'label' => __('Proceed to Checkout', 'mxchat'),
5532 - 'pro_only' => false,
5533 - 'addon_promo' => true,
4758 + 'pro_only' => true,
5534 4759 'group' => __('WooCommerce Features', 'mxchat'),
5535 4760 'icon' => 'arrow-right-alt',
5536 4761 'description' => __('Redirect customer to checkout page', 'mxchat'),
5537 4762 ),
5538 - 'mxchat_handle_featured_products' => array(
5539 - 'label' => __('Featured Products Showcase', 'mxchat'),
5540 - 'pro_only' => false,
5541 - 'addon_promo' => true,
5542 - 'group' => __('WooCommerce Features', 'mxchat'),
5543 - 'icon' => 'star-filled',
5544 - 'description' => __('Display a curated selection of products with an AI-generated message', 'mxchat'),
5545 - ),
5546 4763
5547 4764 // Perplexity Add-on
5548 4765 'mxchat_perplexity_research' => array(
5549 4766 'label' => __('Perplexity Research', 'mxchat'),
5550 - 'pro_only' => false,
5551 - 'addon_promo' => true,
4767 + 'pro_only' => true,
5552 4768 'group' => __('Search Features', 'mxchat'),
5553 4769 'icon' => 'book-alt',
5554 4770 'description' => __('Allows the chatbot to search the web for accurate, up-to-date answers', 'mxchat'),
5555 4771 ),
5556 4772
5557 - // Forms Add-on
4773 + // Forms Add-on (only shown when Pro is not activated)
5558 4774 'mxchat_handle_form_collection' => array(
5559 4775 'label' => __('Form Collection', 'mxchat'),
5560 - 'pro_only' => false,
5561 - 'addon_promo' => true,
4776 + 'pro_only' => true,
5562 4777 'group' => __('Form Features', 'mxchat'),
5563 4778 'icon' => 'feedback',
5564 4779 'description' => __('Collect user information through custom forms in chat', 'mxchat'),
5565 4780 ),
@@ -5813,27 +5028,9 @@
5813 5028 'mxchat-chatbot', // Page
5814 5029 'mxchat_chatbot_section' // Section
5815 5030 );
5816 5031
5817 - // RAG Sources Limit Slider
5818 5032 add_settings_field(
5819 - 'rag_sources_limit', // Field ID
5820 - esc_html__('RAG Sources Limit', 'mxchat'), // Field title
5821 - array($this, 'mxchat_rag_sources_limit_callback'), // Callback function
5822 - 'mxchat-chatbot', // Page
5823 - 'mxchat_chatbot_section' // Section
5824 - );
5825 -
5826 - // RAG Chunks Limit Slider
5827 - add_settings_field(
5828 - 'rag_chunks_limit', // Field ID
5829 - esc_html__('RAG Chunks Limit', 'mxchat'), // Field title
5830 - array($this, 'mxchat_rag_chunks_limit_callback'), // Callback function
5831 - 'mxchat-chatbot', // Page
5832 - 'mxchat_chatbot_section' // Section
5833 - );
5834 -
5835 - add_settings_field(
5836 5033 'append_to_body',
5837 5034 esc_html__('Auto-Display Chatbot', 'mxchat'),
5838 5035 array($this, 'mxchat_append_to_body_callback'),
5839 5036 'mxchat-chatbot',
@@ -5848,16 +5045,8 @@
5848 5045 'mxchat_chatbot_section'
5849 5046 );
5850 5047
5851 5048 add_settings_field(
5852 - 'citation_links_toggle',
5853 - esc_html__('Citation Links', 'mxchat'),
5854 - array($this, 'mxchat_citation_links_toggle_callback'),
5855 - 'mxchat-chatbot',
5856 - 'mxchat_chatbot_section'
5857 - );
5858 -
5859 - add_settings_field(
5860 5049 'enable_streaming_toggle',
5861 5050 esc_html__('Enable Streaming', 'mxchat'),
5862 5051 array($this, 'enable_streaming_toggle_callback'),
5863 5052 'mxchat-chatbot',
@@ -6261,64 +5450,11 @@
6261 5450 'mxchat-embed',
6262 5451 'mxchat_live_agent_section'
6263 5452 );
6264 5453
6265 - // Telegram Integration Section
6266 - add_settings_section(
6267 - 'mxchat_telegram_section',
6268 - __('Telegram Settings', 'mxchat'),
6269 - array($this, 'mxchat_telegram_section_callback'),
6270 - 'mxchat-embed'
6271 - );
6272 5454
6273 - add_settings_field(
6274 - 'telegram_status',
6275 - __('Live Agent Status', 'mxchat'),
6276 - array($this, 'mxchat_telegram_status_callback'),
6277 - 'mxchat-embed',
6278 - 'mxchat_telegram_section'
6279 - );
6280 5455
6281 - add_settings_field(
6282 - 'telegram_notification_message',
6283 - __('Notification Message', 'mxchat'),
6284 - array($this, 'mxchat_telegram_notification_message_callback'),
6285 - 'mxchat-embed',
6286 - 'mxchat_telegram_section'
6287 - );
6288 5456
6289 - add_settings_field(
6290 - 'telegram_away_message',
6291 - __('Away Message', 'mxchat'),
6292 - array($this, 'mxchat_telegram_away_message_callback'),
6293 - 'mxchat-embed',
6294 - 'mxchat_telegram_section'
6295 - );
6296 -
6297 - add_settings_field(
6298 - 'telegram_bot_token',
6299 - __('Telegram Bot Token', 'mxchat'),
6300 - array($this, 'mxchat_telegram_bot_token_callback'),
6301 - 'mxchat-embed',
6302 - 'mxchat_telegram_section'
6303 - );
6304 -
6305 - add_settings_field(
6306 - 'telegram_group_id',
6307 - __('Telegram Group ID', 'mxchat'),
6308 - array($this, 'mxchat_telegram_group_id_callback'),
6309 - 'mxchat-embed',
6310 - 'mxchat_telegram_section'
6311 - );
6312 -
6313 - add_settings_field(
6314 - 'telegram_webhook_secret',
6315 - __('Webhook Secret Token', 'mxchat'),
6316 - array($this, 'mxchat_telegram_webhook_secret_callback'),
6317 - 'mxchat-embed',
6318 - 'mxchat_telegram_section'
6319 - );
6320 -
6321 5457 // General Settings Section
6322 5458 add_settings_section(
6323 5459 'mxchat_general_section',
6324 5460 esc_html__('YouTube Tutorials', 'mxchat'),
@@ -6478,9 +5614,8 @@
6478 5614
6479 5615 ?>
6480 5616 <div class="notice notice-success is-dismissible">
6481 5617 <p><?php esc_html_e('Sync settings updated successfully.', 'mxchat'); ?></p>
6482 - <button type="button" class="notice-dismiss"><span class="screen-reader-text"><?php esc_html_e('Dismiss this notice.', 'mxchat'); ?></span></button>
6483 5618 </div>
6484 5619 <?php
6485 5620
6486 5621 }
@@ -6629,12 +5764,11 @@
6629 5764
6630 5765 // OpenAI API Key
6631 5766 public function api_key_callback() {
6632 5767 $apiKey = isset($this->options['api_key']) ? esc_attr($this->options['api_key']) : '';
6633 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
6634 5768
6635 5769 echo '<div class="api-key-wrapper">';
6636 - echo '<input type="text" id="api_key" name="api_key" value="' . $apiKey . '" class="regular-text mxchat-autosave-field mxchat-api-key-field" autocomplete="new-password" data-lpignore="true" data-form-type="other" data-nonce="' . $nonce . '" />';
5770 + echo '<input type="password" id="api_key" name="api_key" value="' . $apiKey . '" class="regular-text" autocomplete="off" />';
6637 5771 echo '<button type="button" id="toggleApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6638 5772 echo '<p class="description">' . esc_html__('Required for OpenAI GPT models and OpenAI embeddings. Get your API key from OpenAI Platform.', 'mxchat') . '</p>';
6639 5773 echo '</div>';
6640 5774 }
@@ -6641,12 +5775,11 @@
6641 5775
6642 5776 // X.AI API Key
6643 5777 public function xai_api_key_callback() {
6644 5778 $xaiApiKey = isset($this->options['xai_api_key']) ? esc_attr($this->options['xai_api_key']) : '';
6645 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
6646 5779
6647 5780 echo '<div class="api-key-wrapper">';
6648 - echo '<input type="text" id="xai_api_key" name="xai_api_key" value="' . $xaiApiKey . '" class="regular-text mxchat-autosave-field mxchat-api-key-field" autocomplete="new-password" data-lpignore="true" data-form-type="other" data-nonce="' . $nonce . '" />';
5781 + echo '<input type="password" id="xai_api_key" name="xai_api_key" value="' . $xaiApiKey . '" class="regular-text" autocomplete="off" />';
6649 5782 echo '<button type="button" id="toggleXaiApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6650 5783 echo '<p class="description">' . esc_html__('Required for X.AI Grok models. Get your API key from X.AI Console.', 'mxchat') . '</p>';
6651 5784 echo '</div>';
6652 5785 }
@@ -6652,12 +5785,11 @@
6652 5785 }
6653 5786 // Claude API Key
6654 5787 public function claude_api_key_callback() {
6655 5788 $claudeApiKey = isset($this->options['claude_api_key']) ? esc_attr($this->options['claude_api_key']) : '';
6656 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
6657 5789
6658 5790 echo '<div class="api-key-wrapper">';
6659 - echo '<input type="text" id="claude_api_key" name="claude_api_key" value="' . $claudeApiKey . '" class="regular-text mxchat-autosave-field mxchat-api-key-field" autocomplete="new-password" data-lpignore="true" data-form-type="other" data-nonce="' . $nonce . '" />';
5791 + echo '<input type="password" id="claude_api_key" name="claude_api_key" value="' . $claudeApiKey . '" class="regular-text" autocomplete="off" />';
6660 5792 echo '<button type="button" id="toggleClaudeApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6661 5793 echo '<p class="description">' . esc_html__('Required for Anthropic Claude models. Get your API key from Anthropic Console.', 'mxchat') . '</p>';
6662 5794 echo '</div>';
6663 5795 }
@@ -6664,12 +5796,11 @@
6664 5796
6665 5797 // DeepSeek API Key
6666 5798 public function deepseek_api_key_callback() {
6667 5799 $apiKey = isset($this->options['deepseek_api_key']) ? esc_attr($this->options['deepseek_api_key']) : '';
6668 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
6669 5800
6670 5801 echo '<div class="api-key-wrapper">';
6671 - echo '<input type="text" id="deepseek_api_key" name="deepseek_api_key" value="' . $apiKey . '" class="regular-text mxchat-autosave-field mxchat-api-key-field" autocomplete="new-password" data-lpignore="true" data-form-type="other" data-nonce="' . $nonce . '" />';
5802 + echo '<input type="password" id="deepseek_api_key" name="deepseek_api_key" value="' . $apiKey . '" class="regular-text" autocomplete="off" />';
6672 5803 echo '<button type="button" id="toggleDeepSeekApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6673 5804 echo '<p class="description">' . esc_html__('Required for DeepSeek models. Get your API key from DeepSeek Platform.', 'mxchat') . '</p>';
6674 5805 echo '</div>';
6675 5806 }
@@ -6676,12 +5807,11 @@
6676 5807
6677 5808 // Gemini API Key
6678 5809 public function gemini_api_key_callback() {
6679 5810 $geminiApiKey = isset($this->options['gemini_api_key']) ? esc_attr($this->options['gemini_api_key']) : '';
6680 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
6681 5811
6682 5812 echo '<div class="api-key-wrapper">';
6683 - echo '<input type="text" id="gemini_api_key" name="gemini_api_key" value="' . $geminiApiKey . '" class="regular-text mxchat-autosave-field mxchat-api-key-field" autocomplete="new-password" data-lpignore="true" data-form-type="other" data-nonce="' . $nonce . '" />';
5813 + echo '<input type="password" id="gemini_api_key" name="gemini_api_key" value="' . $geminiApiKey . '" class="regular-text" autocomplete="off" />';
6684 5814 echo '<button type="button" id="toggleGeminiApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6685 5815 echo '<p class="description">' . esc_html__('Required for Google Gemini models and embeddings. Get your API key from Google AI Studio.', 'mxchat') . '</p>';
6686 5816 echo '</div>';
6687 5817 }
@@ -6689,12 +5819,10 @@
6689 5819
6690 5820 // OpenRouter API Key
6691 5821 public function openrouter_api_key_callback() {
6692 5822 $openrouterApiKey = isset($this->options['openrouter_api_key']) ? esc_attr($this->options['openrouter_api_key']) : '';
6693 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
6694 -
6695 5823 echo '<div class="api-key-wrapper">';
6696 - echo '<input type="text" id="openrouter_api_key" name="openrouter_api_key" value="' . $openrouterApiKey . '" class="regular-text mxchat-autosave-field mxchat-api-key-field" autocomplete="new-password" data-lpignore="true" data-form-type="other" data-nonce="' . $nonce . '" />';
5824 + echo '<input type="password" id="openrouter_api_key" name="openrouter_api_key" value="' . $openrouterApiKey . '" class="regular-text" autocomplete="off" />';
6697 5825 echo '<button type="button" id="toggleOpenRouterApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6698 5826 echo '<p class="description">' . esc_html__('Required for OpenRouter models. Get your API key from OpenRouter.ai', 'mxchat') . '</p>';
6699 5827 echo '</div>';
6700 5828 }
@@ -6701,12 +5829,11 @@
6701 5829
6702 5830 // Voyage API Key
6703 5831 public function voyage_api_key_callback() {
6704 5832 $apiKey = isset($this->options['voyage_api_key']) ? esc_attr($this->options['voyage_api_key']) : '';
6705 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
6706 5833
6707 5834 echo '<div class="api-key-wrapper">';
6708 - echo '<input type="text" id="voyage_api_key" name="voyage_api_key" value="' . $apiKey . '" class="regular-text mxchat-autosave-field mxchat-api-key-field" autocomplete="new-password" data-lpignore="true" data-form-type="other" data-nonce="' . $nonce . '" />';
5835 + echo '<input type="password" id="voyage_api_key" name="voyage_api_key" value="' . $apiKey . '" class="regular-text" autocomplete="off" />';
6709 5836 echo '<button type="button" id="toggleVoyageAPIKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6710 5837 echo '<p class="description">' . esc_html__('Required for Voyage AI embedding models. Get your API key from Voyage AI.', 'mxchat') . '</p>';
6711 5838 echo '</div>';
6712 5839 }
@@ -6712,15 +5839,17 @@
6712 5839 }
6713 5840
6714 5841 public function mxchat_loops_api_key_callback() {
6715 5842 $loops_api_key = isset($this->options['loops_api_key']) ? esc_attr($this->options['loops_api_key']) : '';
6716 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
6717 5843
5844 + // Hidden fields to "trap" autofill
5845 + echo '<input type="text" style="display:none" autocomplete="username" />';
5846 + echo '<input type="password" style="display:none" autocomplete="current-password" />';
5847 +
6718 5848 echo '<div class="api-key-wrapper">';
6719 5849 echo sprintf(
6720 - '<input type="text" id="loops_api_key" name="loops_api_key" value="%s" class="regular-text mxchat-autosave-field mxchat-api-key-field" autocomplete="new-password" data-lpignore="true" data-form-type="other" data-nonce="%s" />',
6721 - $loops_api_key,
6722 - $nonce
5850 + '<input type="password" id="loops_api_key" name="loops_api_key" value="%s" class="regular-text" autocomplete="new-password" />',
5851 + $loops_api_key
6723 5852 );
6724 5853 echo '<button type="button" id="toggleLoopsApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6725 5854 echo '</div>';
6726 5855 echo '<p class="description">' . esc_html__('Required for Loops email integration. Get your API key from Loops.so', 'mxchat') . '</p>';
@@ -6728,9 +5857,8 @@
6728 5857 public function mxchat_loops_mailing_list_callback() {
6729 5858 // Add error handling and type checking
6730 5859 $loops_api_key = '';
6731 5860 $selected_list = '';
6732 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
6733 5861
6734 5862 // Safely get the API key
6735 5863 if (isset($this->options['loops_api_key']) && is_string($this->options['loops_api_key'])) {
6736 5864 $loops_api_key = $this->options['loops_api_key'];
@@ -6743,10 +5871,9 @@
6743 5871
6744 5872 if (!empty($loops_api_key)) {
6745 5873 $lists = $this->mxchat_fetch_loops_mailing_lists($loops_api_key);
6746 5874 if (is_array($lists) && !empty($lists)) {
6747 - echo '<div class="mxchat-field-wrapper">';
6748 - echo '<select id="loops_mailing_list" name="loops_mailing_list" class="mxchat-autosave-field" data-nonce="' . $nonce . '">';
5875 + echo '<select id="loops_mailing_list" name="loops_mailing_list">';
6749 5876
6750 5877 // Add a default "Select a list" option
6751 5878 echo '<option value="" ' . selected($selected_list, '', false) . '>' . esc_html__('Select a list', 'mxchat') . '</option>';
6752 5879
@@ -6760,9 +5887,8 @@
6760 5887 );
6761 5888 }
6762 5889 }
6763 5890 echo '</select>';
6764 - echo '</div>';
6765 5891 echo '<p class="description">' . esc_html__('Please select a mailing list to use with Loops.', 'mxchat') . '</p>';
6766 5892 } else {
6767 5893 echo '<p class="description">' . esc_html__('No lists found. Please verify your API Key.', 'mxchat') . '</p>';
6768 5894 }
@@ -6774,17 +5900,12 @@
6774 5900 $default_response = __('Would you like to join our mailing list? Please provide your email below.', 'mxchat');
6775 5901 $triggered_response = isset($this->options['triggered_phrase_response'])
6776 5902 ? $this->options['triggered_phrase_response']
6777 5903 : $default_response;
6778 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
6779 -
6780 - echo '<div class="mxchat-field-wrapper">';
6781 5904 echo sprintf(
6782 - '<textarea id="triggered_phrase_response" name="triggered_phrase_response" rows="3" cols="50" class="mxchat-autosave-field" data-nonce="%s">%s</textarea>',
6783 - $nonce,
5905 + '<textarea id="triggered_phrase_response" name="triggered_phrase_response" rows="3" cols="50">%s</textarea>',
6784 5906 esc_textarea($triggered_response)
6785 5907 );
6786 - echo '</div>';
6787 5908 echo '<p class="description">' . esc_html__('Enter the instruction for the AI when a trigger keyword is detected. The AI will use this as guidance to naturally ask for the user\'s email in a conversational way.', 'mxchat') . '</p>';
6788 5909 }
6789 5910
6790 5911 public function mxchat_email_capture_response_callback() {
@@ -6791,17 +5912,12 @@
6791 5912 $default_response = __('Thank you for providing your email! You\'ve been added to our list.', 'mxchat');
6792 5913 $email_capture_response = isset($this->options['email_capture_response'])
6793 5914 ? $this->options['email_capture_response']
6794 5915 : $default_response;
6795 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
6796 -
6797 - echo '<div class="mxchat-field-wrapper">';
6798 5916 echo sprintf(
6799 - '<textarea id="email_capture_response" name="email_capture_response" rows="3" cols="50" class="mxchat-autosave-field" data-nonce="%s">%s</textarea>',
6800 - $nonce,
5917 + '<textarea id="email_capture_response" name="email_capture_response" rows="3" cols="50">%s</textarea>',
6801 5918 esc_textarea($email_capture_response)
6802 5919 );
6803 - echo '</div>';
6804 5920 echo '<p class="description">' . esc_html__('Enter the instruction for the AI when a user provides their email. The AI will use this as guidance to naturally confirm the email capture in a conversational way.', 'mxchat') . '</p>';
6805 5921 }
6806 5922 public function mxchat_pre_chat_message_callback() {
6807 5923 // Load the entire 'mxchat_options' array
@@ -6815,8 +5931,9 @@
6815 5931 printf(
6816 5932 '<textarea id="pre_chat_message" name="pre_chat_message" rows="5" cols="50">%s</textarea>',
6817 5933 esc_textarea($pre_chat_message)
6818 5934 );
5935 + echo '<p class="description">' . esc_html__('Set the message displayed to users before they start a chat. Use this to provide a friendly greeting or instructions.', 'mxchat') . '</p>';
6819 5936 }
6820 5937
6821 5938 // Callback for AI Instructions textarea
6822 5939 public function system_prompt_instructions_callback() {
@@ -6826,14 +5943,10 @@
6826 5943 printf(
6827 5944 '<textarea id="system_prompt_instructions" name="system_prompt_instructions" rows="5" cols="50">%s</textarea>',
6828 5945 $instructions
6829 5946 );
6830 - // Personalization hint
6831 - echo '<p class="description" style="margin-top: 8px;">';
6832 - echo esc_html__('Use {visitor_name} to personalize AI responses when lead capture is enabled.', 'mxchat') . '<br>';
6833 - echo '<code style="font-size: 12px;">' . esc_html__('Example: The visitor\'s name is {visitor_name}. Address them by name.', 'mxchat') . '</code>';
6834 - echo '</p>';
6835 - // Sample instructions button
5947 + // Provide a helpful description with sample instructions button
5948 + echo '<p class="description">' . esc_html__('Provide system-level instructions for the AI to guide its behavior. Be clear and concise for better results.', 'mxchat') . '</p>';
6836 5949 echo '<div class="mxchat-instructions-container">';
6837 5950 echo '<button type="button" class="mxchat-instructions-btn" id="mxchatViewSampleBtn">';
6838 5951 echo '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">';
6839 5952 echo '<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/>';
@@ -6914,18 +6027,19 @@
6914 6027 esc_html__('OpenRouter (100+ Models)', 'mxchat') => array(
6915 6028 'openrouter' => esc_html__('OpenRouter - Select after entering API key', 'mxchat'),
6916 6029 ),
6917 6030 esc_html__('Google Gemini Models', 'mxchat') => array(
6918 - 'gemini-3-pro-preview' => esc_html__('Gemini 3 Pro (Most Intelligent, Multimodal)', 'mxchat'),
6919 - 'gemini-3-flash-preview' => esc_html__('Gemini 3 Flash (Balanced Speed & Scale)', 'mxchat'),
6920 - 'gemini-2.5-pro' => esc_html__('Gemini 2.5 Pro (Advanced Thinking)', 'mxchat'),
6921 - 'gemini-2.5-flash' => esc_html__('Gemini 2.5 Flash (Best Price-Performance)', 'mxchat'),
6922 - 'gemini-2.5-flash-lite' => esc_html__('Gemini 2.5 Flash-Lite (Ultra Fast)', 'mxchat'),
6923 - 'gemini-2.0-flash' => esc_html__('Gemini 2.0 Flash (Deprecated Mar 2026)', 'mxchat'),
6924 - 'gemini-2.0-flash-lite' => esc_html__('Gemini 2.0 Flash-Lite (Deprecated Mar 2026)', 'mxchat'),
6925 - 'gemini-1.5-pro' => esc_html__('Gemini 1.5 Pro (Deprecated Sep 2025)', 'mxchat'),
6926 - 'gemini-1.5-flash' => esc_html__('Gemini 1.5 Flash (Deprecated Sep 2025)', 'mxchat'),
6031 + 'gemini-2.0-flash' => esc_html__('Gemini 2.0 Flash (Next-Gen Features)', 'mxchat'),
6032 + 'gemini-2.0-flash-lite' => esc_html__('Gemini 2.0 Flash-Lite (Cost-Efficient)', 'mxchat'),
6033 + 'gemini-1.5-pro' => esc_html__('Gemini 1.5 Pro (Complex Reasoning)', 'mxchat'),
6034 + 'gemini-1.5-flash' => esc_html__('Gemini 1.5 Flash (Fast & Versatile)', 'mxchat'),
6927 6035 ),
6036 + esc_html__('Google Gemini Models', 'mxchat') => array(
6037 + 'gemini-2.0-flash' => esc_html__('Gemini 2.0 Flash (Next-Gen Features)', 'mxchat'),
6038 + 'gemini-2.0-flash-lite' => esc_html__('Gemini 2.0 Flash-Lite (Cost-Efficient)', 'mxchat'),
6039 + 'gemini-1.5-pro' => esc_html__('Gemini 1.5 Pro (Complex Reasoning)', 'mxchat'),
6040 + 'gemini-1.5-flash' => esc_html__('Gemini 1.5 Flash (Fast & Versatile)', 'mxchat'),
6041 + ),
6928 6042 esc_html__('X.AI Models', 'mxchat') => array(
6929 6043 'grok-3-beta' => esc_html__('Grok-3 (Powerful)', 'mxchat'),
6930 6044 'grok-3-fast-beta' => esc_html__('Grok-3 Fast (High Performance)', 'mxchat'),
6931 6045 'grok-3-mini-beta' => esc_html__('Grok-3 Mini (Affordable)', 'mxchat'),
@@ -6938,33 +6052,38 @@
6938 6052 esc_html__('DeepSeek Models', 'mxchat') => array(
6939 6053 'deepseek-chat' => esc_html__('DeepSeek-V3', 'mxchat'),
6940 6054 ),
6941 6055 esc_html__('Claude Models', 'mxchat') => array(
6942 - 'claude-opus-4-6' => esc_html__('Claude Opus 4.6 (Most Capable - Recommended)', 'mxchat'),
6943 - 'claude-sonnet-4-6' => esc_html__('Claude Sonnet 4.6 (Latest Sonnet - Fast & Capable)', 'mxchat'),
6944 - 'claude-opus-4-5' => esc_html__('Claude Opus 4.5 (Highly Capable)', 'mxchat'),
6945 6056 'claude-sonnet-4-5-20250929' => esc_html__('Claude Sonnet 4.5 (Best for Agents & Coding)', 'mxchat'),
6946 6057 'claude-opus-4-1-20250805' => esc_html__('Claude Opus 4.1 (Exceptional for Complex Tasks)', 'mxchat'),
6947 6058 'claude-haiku-4-5-20251001' => esc_html__('Claude Haiku 4.5 (Fastest & Most Intelligent)', 'mxchat'),
6948 - 'claude-opus-4-20250514' => esc_html__('Claude 4 Opus (Complex Tasks)', 'mxchat'),
6059 + 'claude-opus-4-20250514' => esc_html__('Claude 4 Opus (Most Capable)', 'mxchat'),
6949 6060 'claude-sonnet-4-20250514' => esc_html__('Claude 4 Sonnet (High Performance)', 'mxchat'),
6061 + 'claude-3-7-sonnet-20250219' => esc_html__('Claude 3.7 Sonnet (High Intelligence)', 'mxchat'),
6062 + 'claude-3-opus-20240229' => esc_html__('Claude 3 Opus (Complex Tasks)', 'mxchat'),
6063 + 'claude-3-sonnet-20240229' => esc_html__('Claude 3 Sonnet (Balanced)', 'mxchat'),
6064 + 'claude-3-haiku-20240307' => esc_html__('Claude 3 Haiku (Fastest)', 'mxchat'),
6950 6065 ),
6951 6066 esc_html__('OpenAI Models', 'mxchat') => array(
6952 - 'gpt-5.4' => esc_html__('GPT-5.4 (Flagship Reasoning & Coding)', 'mxchat'),
6953 - 'gpt-5.4-mini' => esc_html__('GPT-5.4 Mini (Fast, 400K Context)', 'mxchat'),
6954 - 'gpt-5.4-nano' => esc_html__('GPT-5.4 Nano (Fastest & Cheapest)', 'mxchat'),
6955 - 'gpt-5.3-chat-latest' => esc_html__('GPT-5.3 Chat (Conversational)', 'mxchat'),
6067 + // GPT-5 family
6956 6068 'gpt-5.2' => esc_html__('GPT-5.2 (Best General-Purpose & Agentic Model)', 'mxchat'),
6957 - 'gpt-5.1-chat-latest' => esc_html__('GPT-5.1 Chat Latest (Recommended)', 'mxchat'),
6958 6069 'gpt-5.1-2025-11-13' => esc_html__('GPT-5.1 (Flagship for Coding & Agentic Tasks)', 'mxchat'),
6959 6070 'gpt-5' => esc_html__('GPT-5 (Flagship for Coding, Reasoning & Agents)', 'mxchat'),
6960 - 'gpt-5-mini' => esc_html__('GPT-5 Mini (Fast and Lightweight)', 'mxchat'),
6071 + 'gpt-5-mini' => esc_html__('GPT-5 Mini (Faster, Cost-Efficient for Precise Prompts)', 'mxchat'),
6961 6072 'gpt-5-nano' => esc_html__('GPT-5 Nano (Fastest & Cheapest for Summarization/Classification)', 'mxchat'),
6073 +
6074 + // Existing OpenAI models
6075 + 'gpt-4.1-2025-04-14' => esc_html__('GPT-4.1 (Flagship for Complex Tasks)', 'mxchat'),
6076 + 'gpt-4o' => esc_html__('GPT-4o (Recommended)', 'mxchat'),
6077 + 'gpt-4o-mini' => esc_html__('GPT-4o Mini (Fast and Lightweight)', 'mxchat'),
6078 + 'gpt-4-turbo' => esc_html__('GPT-4 Turbo (High-Performance)', 'mxchat'),
6079 + 'gpt-4' => esc_html__('GPT-4 (High Intelligence)', 'mxchat'),
6080 + 'gpt-3.5-turbo' => esc_html__('GPT-3.5 Turbo (Affordable and Fast)', 'mxchat'),
6962 6081 ),
6963 6082 );
6964 6083
6965 6084 // Retrieve the currently selected model from saved options
6966 - $selected_model = isset($this->options['model']) ? esc_attr($this->options['model']) : 'gpt-5.1-chat-latest';
6085 + $selected_model = isset($this->options['model']) ? esc_attr($this->options['model']) : 'gpt-4o';
6967 6086
6968 6087 // Begin the select dropdown
6969 6088 echo '<select id="model" name="model">';
6970 6089
@@ -6980,8 +6099,10 @@
6980 6099 }
6981 6100
6982 6101 echo '</select>';
6983 6102
6103 + echo '<p class="description">' . esc_html__('Select the AI model your chatbot will use for chatting.', 'mxchat') . '</p>';
6104 +
6984 6105 // Add a note for OpenRouter
6985 6106 echo '<p class="description" id="openrouter-model-note" style="display:none; color: #d63638; font-weight: 500;">';
6986 6107 echo '<span class="dashicons dashicons-info" style="font-size: 16px; vertical-align: middle;"></span> ';
6987 6108 echo esc_html__('After entering your OpenRouter API key above, click the button below to load available models.', 'mxchat');
@@ -7069,8 +6190,11 @@
7069 6190 esc_attr($checked)
7070 6191 );
7071 6192 echo '<span class="slider"></span>';
7072 6193 echo '</label>';
6194 + echo '<p class="description">' .
6195 + esc_html__('Enable real-time streaming responses for supported models (OpenAI, Claude, DeepSeek, and Grok). When disabled, responses will load all at once.', 'mxchat') .
6196 + '</p>';
7073 6197
7074 6198 // Test button with better styling
7075 6199 echo '<div style="margin-top: 15px;">';
7076 6200 echo '<button type="button" id="mxchat-test-streaming-btn" class="button button-secondary">';
@@ -7080,50 +6204,8 @@
7080 6204 echo '<p id="mxchat-test-streaming-result" style="margin-top:10px; font-weight: bold;"></p>';
7081 6205 echo '</div>';
7082 6206 }
7083 6207
7084 -// Web Search toggle callback
7085 -public function enable_web_search_toggle_callback() {
7086 - // Get value from options array, default to 'off'
7087 - $enabled = isset($this->options['enable_web_search']) ? $this->options['enable_web_search'] : 'off';
7088 - $checked = ($enabled === 'on') ? 'checked' : '';
7089 -
7090 - // Get current model to determine if we should show/enable the toggle
7091 - $current_model = isset($this->options['model']) ? $this->options['model'] : 'gpt-5.1-chat-latest';
7092 -
7093 - // Models that DON'T support web search (per OpenAI docs)
7094 - // gpt-5 with minimal reasoning is handled at API level, gpt-4.1-nano doesn't support it
7095 - $unsupported_models = array('gpt-4.1-nano');
7096 -
7097 - // Check if current model is an OpenAI model that supports web search
7098 - $openai_models = array(
7099 - 'gpt-5.4', 'gpt-5.4-mini', 'gpt-5.4-nano', 'gpt-5.3-chat-latest',
7100 - 'gpt-5.2', 'gpt-5.1-chat-latest', 'gpt-5.1-2025-11-13', 'gpt-5', 'gpt-5-mini', 'gpt-5-nano'
7101 - );
7102 -
7103 - $is_openai = in_array($current_model, $openai_models);
7104 - $is_supported = $is_openai && !in_array($current_model, $unsupported_models);
7105 -
7106 - // Wrapper div with data attributes for JS to show/hide
7107 - echo '<div id="web-search-toggle-wrapper" data-openai-models="' . esc_attr(implode(',', $openai_models)) . '" data-unsupported-models="' . esc_attr(implode(',', $unsupported_models)) . '"' . (!$is_supported ? ' style="display:none;"' : '') . '>';
7108 -
7109 - echo '<label class="toggle-switch">';
7110 - echo sprintf(
7111 - '<input type="checkbox" id="enable_web_search" name="enable_web_search" value="on" %s />',
7112 - esc_attr($checked)
7113 - );
7114 - echo '<span class="slider"></span>';
7115 - echo '</label>';
7116 -
7117 - echo '</div>';
7118 -
7119 - // Message shown when non-OpenAI model is selected
7120 - echo '<p id="web-search-unavailable-message" class="description" style="color: #666;' . ($is_supported ? ' display:none;' : '') . '">';
7121 - echo '<span class="dashicons dashicons-info" style="font-size: 16px; vertical-align: middle; margin-right: 4px;"></span>';
7122 - echo esc_html__('Web search is only available for OpenAI models.', 'mxchat');
7123 - echo '</p>';
7124 -}
7125 -
7126 6208 // AJAX handler to fetch OpenRouter models
7127 6209 public function fetch_openrouter_models() {
7128 6210 check_ajax_referer('mxchat_fetch_openrouter_models', 'nonce');
7129 6211
@@ -7180,9 +6262,9 @@
7180 6262 esc_html__('Voyage AI Embeddings', 'mxchat') => array(
7181 6263 'voyage-3-large' => esc_html__('Voyage-3 Large (2048, Most Capable)', 'mxchat'),
7182 6264 ),
7183 6265 esc_html__('Google Gemini Embeddings', 'mxchat') => array(
7184 - 'gemini-embedding-001' => esc_html__('Gemini Embedding (1536, Stable)', 'mxchat'),
6266 + 'gemini-embedding-exp-03-07' => esc_html__('Gemini Embedding (1536, Experimental)', 'mxchat'),
7185 6267 )
7186 6268 );
7187 6269 $selected_model = isset($this->options['embedding_model']) ? esc_attr($this->options['embedding_model']) : 'text-embedding-ada-002';
7188 6270 echo '<select id="embedding_model" name="embedding_model">';
@@ -7193,8 +6275,9 @@
7193 6275 }
7194 6276 echo '</optgroup>';
7195 6277 }
7196 6278 echo '</select>';
6279 + echo '<p class="description"><span class="red-warning">IMPORTANT:</span> A vector embedding model is required for MxChat to function. Changing models is not recommended; if you do, you must delete all existing knowledge & intent data and reconfigure them.</p>';
7197 6280
7198 6281 // API Key Status Messages for Embedding Models
7199 6282 $has_openai_key = !empty($this->options['api_key']);
7200 6283 $has_voyage_key = !empty($this->options['voyage_api_key']);
@@ -7225,9 +6308,8 @@
7225 6308 } else {
7226 6309 echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for Google Gemini detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
7227 6310 }
7228 6311 echo '</p>';
7229 -
7230 6312 }
7231 6313
7232 6314
7233 6315 public function mxchat_top_bar_title_callback() {
@@ -7235,8 +6317,11 @@
7235 6317 $top_bar_title = isset($this->options['top_bar_title']) ? esc_attr($this->options['top_bar_title']) : '';
7236 6318
7237 6319 // Render the input field
7238 6320 echo '<input type="text" id="top_bar_title" name="top_bar_title" value="' . $top_bar_title . '" />';
6321 +
6322 + // Add a description
6323 + echo '<p class="description">' . esc_html__('Enter the title text that will appear on the top bar of the chatbot.', 'mxchat') . '</p>';
7239 6324 }
7240 6325 public function mxchat_ai_agent_text_callback() {
7241 6326 // Retrieve the current value of the AI agent text from saved options
7242 6327 $ai_agent_text = isset($this->options['ai_agent_text']) ? esc_attr($this->options['ai_agent_text']) : '';
@@ -7241,8 +6326,10 @@
7241 6326 // Retrieve the current value of the AI agent text from saved options
7242 6327 $ai_agent_text = isset($this->options['ai_agent_text']) ? esc_attr($this->options['ai_agent_text']) : '';
7243 6328 // Render the input field
7244 6329 echo '<input type="text" id="ai_agent_text" name="ai_agent_text" value="' . $ai_agent_text . '" />';
6330 + // Add a description
6331 + echo '<p class="description">' . esc_html__('Enter the text that will appear for AI agents in the status indicator. Default: "AI Agent"', 'mxchat') . '</p>';
7245 6332 }
7246 6333
7247 6334
7248 6335 public function enable_email_block_callback() {
@@ -7261,8 +6348,9 @@
7261 6348 esc_attr($checked)
7262 6349 );
7263 6350 echo '<span class="slider"></span>';
7264 6351 echo '</label>';
6352 + echo '<p class="description">' . esc_html__('Their email will appear at the top of the transcript. Email form will show for users who are not logged in or have not provided an email within 24h.', 'mxchat') . '</p>';
7265 6353 }
7266 6354
7267 6355 public function email_blocker_header_content_callback() {
7268 6356 // Load the entire 'mxchat_options' array
@@ -7280,8 +6368,12 @@
7280 6368 rows="5"
7281 6369 cols="70"
7282 6370 data-setting="email_blocker_header_content"
7283 6371 >' . esc_textarea($content) . '</textarea>';
6372 +
6373 + echo '<p class="description">';
6374 + echo esc_html__('You may enter HTML here, such as &lt;h2&gt;Welcome&lt;/h2&gt; or &lt;p&gt;Let\'s get started&lt;/p&gt;.', 'mxchat');
6375 + echo '</p>';
7284 6376 }
7285 6377
7286 6378 public function email_blocker_button_text_callback() {
7287 6379 // Load the entire 'mxchat_options' array
@@ -7293,8 +6385,12 @@
7293 6385 : '';
7294 6386
7295 6387 // Use esc_attr to safely render the existing text
7296 6388 echo '<input type="text" id="email_blocker_button_text" name="email_blocker_button_text" value="' . esc_attr($button_text) . '" style="width: 300px;" />';
6389 +
6390 + echo '<p class="description">';
6391 + echo esc_html__('Enter the text you want on the submit button, e.g. "Start Chat".', 'mxchat');
6392 + echo '</p>';
7297 6393 }
7298 6394
7299 6395 //Enable name field callback
7300 6396 public function enable_name_field_callback() {
@@ -7310,8 +6406,9 @@
7310 6406 esc_attr($checked)
7311 6407 );
7312 6408 echo '<span class="slider"></span>';
7313 6409 echo '</label>';
6410 + echo '<p class="description">' . esc_html__('Enable this to also require users to enter their name along with their email before chatting.', 'mxchat') . '</p>';
7314 6411 }
7315 6412 //Name field placeholder callback
7316 6413 public function name_field_placeholder_callback() {
7317 6414 $all_options = get_option('mxchat_options', []);
@@ -7319,8 +6416,11 @@
7319 6416 ? $all_options['name_field_placeholder']
7320 6417 : esc_html__('Enter your name', 'mxchat');
7321 6418
7322 6419 echo '<input type="text" id="name_field_placeholder" name="name_field_placeholder" value="' . esc_attr($placeholder) . '" style="width: 300px;" />';
6420 + echo '<p class="description">';
6421 + echo esc_html__('Placeholder text for the name field.', 'mxchat');
6422 + echo '</p>';
7323 6423 }
7324 6424
7325 6425
7326 6426
@@ -7332,11 +6432,10 @@
7332 6432 $saved_message = isset($all_options['intro_message']) ? $all_options['intro_message'] : $default_message;
7333 6433 // Output the textarea with the saved value without escaping HTML
7334 6434 ?>
7335 6435 <textarea id="intro_message" name="intro_message" rows="5" cols="50"><?php echo $saved_message; ?></textarea>
7336 - <p class="description" style="margin-top: 8px;">
7337 - <?php esc_html_e('Use {visitor_name} to personalize greetings when lead capture is enabled.', 'mxchat'); ?><br>
7338 - <code style="font-size: 12px;"><?php esc_html_e('Example: Hello {visitor_name}! How can I help you today?', 'mxchat'); ?></code>
6436 + <p class="description">
6437 + <?php esc_html_e('Enter your message. HTML tags and line breaks will be preserved.', 'mxchat'); ?>
7339 6438 </p>
7340 6439 <?php
7341 6440 }
7342 6441
@@ -7353,11 +6452,443 @@
7353 6452 '<input type="text" id="input_copy" name="input_copy" value="%s" placeholder="%s" />',
7354 6453 esc_attr($input_copy),
7355 6454 esc_attr__('How can I assist?', 'mxchat')
7356 6455 );
6456 +
6457 + // Output the description
6458 + echo '<p class="description">' . esc_html__('This is the placeholder text for the chat input field.', 'mxchat') . '</p>';
7357 6459 }
7358 6460
7359 6461
6462 +
6463 +public function mxchat_user_message_font_color_callback() {
6464 + $disabled = $this->is_activated ? '' : 'disabled';
6465 + $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6466 +
6467 + echo '<div class="' . esc_attr($class) . '">';
6468 + echo sprintf(
6469 + '<input type="text"
6470 + id="user_message_font_color"
6471 + name="user_message_font_color"
6472 + value="%s"
6473 + class="my-color-field"
6474 + data-default-color="#ffffff"
6475 + %s />',
6476 + isset($this->options['user_message_font_color']) ? esc_attr($this->options['user_message_font_color']) : '#ffffff',
6477 + esc_attr($disabled)
6478 + );
6479 +
6480 + if (!$this->is_activated) {
6481 + echo '<div class="pro-feature-overlay">';
6482 + echo '<a href="https://mxchat.ai/" target="_blank">';
6483 + echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6484 + echo '</a>';
6485 + echo '</div>';
6486 + }
6487 + echo '</div>';
6488 +}
6489 +
6490 +public function mxchat_bot_message_bg_color_callback() {
6491 + $disabled = $this->is_activated ? '' : 'disabled';
6492 + $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6493 +
6494 + echo '<div class="' . esc_attr($class) . '">';
6495 + echo sprintf(
6496 + '<input type="text"
6497 + id="bot_message_bg_color"
6498 + name="bot_message_bg_color"
6499 + value="%s"
6500 + class="my-color-field"
6501 + data-default-color="#e1e1e1"
6502 + %s />',
6503 + isset($this->options['bot_message_bg_color']) ? esc_attr($this->options['bot_message_bg_color']) : '#e1e1e1',
6504 + esc_attr($disabled)
6505 + );
6506 +
6507 + if (!$this->is_activated) {
6508 + echo '<div class="pro-feature-overlay">';
6509 + echo '<a href="https://mxchat.ai/" target="_blank">';
6510 + echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6511 + echo '</a>';
6512 + echo '</div>';
6513 + }
6514 + echo '</div>';
6515 +}
6516 +
6517 +public function mxchat_bot_message_font_color_callback() {
6518 + $disabled = $this->is_activated ? '' : 'disabled';
6519 + $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6520 +
6521 + echo '<div class="' . esc_attr($class) . '">';
6522 + echo sprintf(
6523 + '<input type="text"
6524 + id="bot_message_font_color"
6525 + name="bot_message_font_color"
6526 + value="%s"
6527 + class="my-color-field"
6528 + data-default-color="#333333"
6529 + %s />',
6530 + isset($this->options['bot_message_font_color']) ? esc_attr($this->options['bot_message_font_color']) : '#333333',
6531 + esc_attr($disabled)
6532 + );
6533 +
6534 + if (!$this->is_activated) {
6535 + echo '<div class="pro-feature-overlay">';
6536 + echo '<a href="https://mxchat.ai/" target="_blank">';
6537 + echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6538 + echo '</a>';
6539 + echo '</div>';
6540 + }
6541 + echo '</div>';
6542 +}
6543 +
6544 +public function mxchat_live_agent_message_bg_color_callback() {
6545 + $disabled = $this->is_activated ? '' : 'disabled';
6546 + $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6547 +
6548 + echo '<div class="' . esc_attr($class) . '">';
6549 + echo sprintf(
6550 + '<input type="text"
6551 + id="live_agent_message_bg_color"
6552 + name="live_agent_message_bg_color"
6553 + value="%s"
6554 + class="my-color-field"
6555 + data-default-color="#ffffff"
6556 + %s />',
6557 + isset($this->options['live_agent_message_bg_color']) ? esc_attr($this->options['live_agent_message_bg_color']) : '#ffffff',
6558 + esc_attr($disabled)
6559 + );
6560 +
6561 + if (!$this->is_activated) {
6562 + echo '<div class="pro-feature-overlay">';
6563 + echo '<a href="https://mxchat.ai/" target="_blank">';
6564 + echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6565 + echo '</a>';
6566 + echo '</div>';
6567 + }
6568 + echo '</div>';
6569 +}
6570 +
6571 +public function mxchat_live_agent_message_font_color_callback() {
6572 + $disabled = $this->is_activated ? '' : 'disabled';
6573 + $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6574 +
6575 + echo '<div class="' . esc_attr($class) . '">';
6576 + echo sprintf(
6577 + '<input type="text"
6578 + id="live_agent_message_font_color"
6579 + name="live_agent_message_font_color"
6580 + value="%s"
6581 + class="my-color-field"
6582 + data-default-color="#333333"
6583 + %s />',
6584 + isset($this->options['live_agent_message_font_color']) ? esc_attr($this->options['live_agent_message_font_color']) : '#333333',
6585 + esc_attr($disabled)
6586 + );
6587 +
6588 + if (!$this->is_activated) {
6589 + echo '<div class="pro-feature-overlay">';
6590 + echo '<a href="https://mxchat.ai/" target="_blank">';
6591 + echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6592 + echo '</a>';
6593 + echo '</div>';
6594 + }
6595 + echo '</div>';
6596 +}
6597 +
6598 +public function mxchat_mode_indicator_bg_color_callback() {
6599 + $disabled = $this->is_activated ? '' : 'disabled';
6600 + $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6601 +
6602 + echo '<div class="' . esc_attr($class) . '">';
6603 + echo sprintf(
6604 + '<input type="text"
6605 + id="mode_indicator_bg_color"
6606 + name="mode_indicator_bg_color"
6607 + value="%s"
6608 + class="my-color-field"
6609 + data-default-color="#767676"
6610 + %s />',
6611 + isset($this->options['mode_indicator_bg_color']) ? esc_attr($this->options['mode_indicator_bg_color']) : '#767676',
6612 + esc_attr($disabled)
6613 + );
6614 +
6615 + if (!$this->is_activated) {
6616 + echo '<div class="pro-feature-overlay">';
6617 + echo '<a href="https://mxchat.ai/" target="_blank">';
6618 + echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6619 + echo '</a>';
6620 + echo '</div>';
6621 + }
6622 + echo '</div>';
6623 +}
6624 +
6625 +public function mxchat_mode_indicator_font_color_callback() {
6626 + $disabled = $this->is_activated ? '' : 'disabled';
6627 + $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6628 +
6629 + echo '<div class="' . esc_attr($class) . '">';
6630 + echo sprintf(
6631 + '<input type="text"
6632 + id="mode_indicator_font_color"
6633 + name="mode_indicator_font_color"
6634 + value="%s"
6635 + class="my-color-field"
6636 + data-default-color="#ffffff"
6637 + %s />',
6638 + isset($this->options['mode_indicator_font_color']) ? esc_attr($this->options['mode_indicator_font_color']) : '#ffffff',
6639 + esc_attr($disabled)
6640 + );
6641 +
6642 + if (!$this->is_activated) {
6643 + echo '<div class="pro-feature-overlay">';
6644 + echo '<a href="https://mxchat.ai/" target="_blank">';
6645 + echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6646 + echo '</a>';
6647 + echo '</div>';
6648 + }
6649 + echo '</div>';
6650 +}
6651 +
6652 +public function mxchat_toolbar_icon_color_callback() {
6653 + $disabled = $this->is_activated ? '' : 'disabled';
6654 + $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6655 +
6656 + echo '<div class="' . esc_attr($class) . '">';
6657 + echo sprintf(
6658 + '<input type="text"
6659 + id="toolbar_icon_color"
6660 + name="toolbar_icon_color"
6661 + value="%s"
6662 + class="my-color-field"
6663 + data-default-color="#212121"
6664 + %s />',
6665 + isset($this->options['toolbar_icon_color']) ? esc_attr($this->options['toolbar_icon_color']) : '#212121',
6666 + esc_attr($disabled)
6667 + );
6668 +
6669 + if (!$this->is_activated) {
6670 + echo '<div class="pro-feature-overlay">';
6671 + echo '<a href="https://mxchat.ai/" target="_blank">';
6672 + echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6673 + echo '</a>';
6674 + echo '</div>';
6675 + }
6676 + echo '</div>';
6677 +}
6678 +
6679 +public function mxchat_top_bar_bg_color_callback() {
6680 + $disabled = $this->is_activated ? '' : 'disabled';
6681 + $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6682 +
6683 + echo '<div class="' . esc_attr($class) . '">';
6684 + echo sprintf(
6685 + '<input type="text"
6686 + id="top_bar_bg_color"
6687 + name="top_bar_bg_color"
6688 + value="%s"
6689 + class="my-color-field"
6690 + data-default-color="#00b294"
6691 + %s />',
6692 + isset($this->options['top_bar_bg_color']) ? esc_attr($this->options['top_bar_bg_color']) : '#00b294',
6693 + esc_attr($disabled)
6694 + );
6695 +
6696 + if (!$this->is_activated) {
6697 + echo '<div class="pro-feature-overlay">';
6698 + echo '<a href="https://mxchat.ai/" target="_blank">';
6699 + echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6700 + echo '</a>';
6701 + echo '</div>';
6702 + }
6703 + echo '</div>';
6704 +}
6705 +
6706 +public function mxchat_send_button_font_color_callback() {
6707 + $disabled = $this->is_activated ? '' : 'disabled';
6708 + $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6709 +
6710 + echo '<div class="' . esc_attr($class) . '">';
6711 + echo sprintf(
6712 + '<input type="text"
6713 + id="send_button_font_color"
6714 + name="send_button_font_color"
6715 + value="%s"
6716 + class="my-color-field"
6717 + data-default-color="#ffffff"
6718 + %s />',
6719 + isset($this->options['send_button_font_color']) ? esc_attr($this->options['send_button_font_color']) : '#ffffff',
6720 + esc_attr($disabled)
6721 + );
6722 +
6723 + if (!$this->is_activated) {
6724 + echo '<div class="pro-feature-overlay">';
6725 + echo '<a href="https://mxchat.ai/" target="_blank">';
6726 + echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6727 + echo '</a>';
6728 + echo '</div>';
6729 + }
6730 + echo '</div>';
6731 +}
6732 +
6733 +public function mxchat_chatbot_background_color_callback() {
6734 + $disabled = $this->is_activated ? '' : 'disabled';
6735 + $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6736 +
6737 + echo '<div class="' . esc_attr($class) . '">';
6738 + echo sprintf(
6739 + '<input type="text"
6740 + id="chatbot_background_color"
6741 + name="chatbot_background_color"
6742 + value="%s"
6743 + class="my-color-field"
6744 + data-default-color="#000000"
6745 + %s />',
6746 + isset($this->options['chatbot_background_color']) ? esc_attr($this->options['chatbot_background_color']) : '#000000',
6747 + esc_attr($disabled)
6748 + );
6749 +
6750 + if (!$this->is_activated) {
6751 + echo '<div class="pro-feature-overlay">';
6752 + echo '<a href="https://mxchat.ai/" target="_blank">';
6753 + echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6754 + echo '</a>';
6755 + echo '</div>';
6756 + }
6757 + echo '</div>';
6758 +}
6759 +
6760 +public function mxchat_icon_color_callback() {
6761 + $disabled = $this->is_activated ? '' : 'disabled';
6762 + $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6763 +
6764 + echo '<div class="' . esc_attr($class) . '">';
6765 + echo sprintf(
6766 + '<input type="text"
6767 + id="icon_color"
6768 + name="icon_color"
6769 + value="%s"
6770 + class="my-color-field"
6771 + data-default-color="#ffffff"
6772 + %s />',
6773 + isset($this->options['icon_color']) ? esc_attr($this->options['icon_color']) : '#ffffff',
6774 + esc_attr($disabled)
6775 + );
6776 +
6777 + if (!$this->is_activated) {
6778 + echo '<div class="pro-feature-overlay">';
6779 + echo '<a href="https://mxchat.ai/" target="_blank">';
6780 + echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6781 + echo '</a>';
6782 + echo '</div>';
6783 + }
6784 + echo '</div>';
6785 +}
6786 +
6787 +public function mxchat_custom_icon_callback() {
6788 + $disabled = $this->is_activated ? '' : 'disabled';
6789 + $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6790 + $custom_icon_url = isset($this->options['custom_icon']) ? esc_url($this->options['custom_icon']) : '';
6791 +
6792 + echo '<div class="' . esc_attr($class) . '">';
6793 + echo sprintf(
6794 + '<input type="url"
6795 + id="custom_icon"
6796 + name="custom_icon"
6797 + value="%s"
6798 + placeholder="%s"
6799 + class="regular-text"
6800 + %s />',
6801 + $custom_icon_url,
6802 + esc_attr__('Enter PNG URL', 'mxchat'),
6803 + esc_attr($disabled)
6804 + );
6805 +
6806 + // Preview container for the icon
6807 + if (!empty($custom_icon_url)) {
6808 + echo '<div class="icon-preview" style="margin-top: 10px;">';
6809 + echo '<img src="' . esc_url($custom_icon_url) . '" alt="' . esc_attr__('Custom Icon Preview', 'mxchat') . '" style="max-width: 48px; height: auto;" />';
6810 + echo '</div>';
6811 + }
6812 +
6813 + echo '<p class="description">' . esc_html__('Upload your PNG icon and paste the URL here. Recommended size: 48x48 pixels.', 'mxchat') . '</p>';
6814 +
6815 + if (!$this->is_activated) {
6816 + echo '<div class="pro-feature-overlay">';
6817 + echo '<a href="https://mxchat.ai/" target="_blank">';
6818 + echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6819 + echo '</a>';
6820 + echo '</div>';
6821 + }
6822 + echo '</div>';
6823 +}
6824 +
6825 +public function mxchat_title_icon_callback() {
6826 + $disabled = $this->is_activated ? '' : 'disabled';
6827 + $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6828 + // Fixed the variable reference - it was using custom_icon instead of title_icon
6829 + $title_icon_url = isset($this->options['title_icon']) ? esc_url($this->options['title_icon']) : '';
6830 +
6831 + echo '<div class="' . esc_attr($class) . '">';
6832 + echo sprintf(
6833 + '<input type="url"
6834 + id="title_icon"
6835 + name="title_icon"
6836 + value="%s"
6837 + placeholder="%s"
6838 + class="regular-text"
6839 + %s />',
6840 + $title_icon_url,
6841 + esc_attr__('Enter PNG URL', 'mxchat'),
6842 + esc_attr($disabled)
6843 + );
6844 +
6845 + // Preview container for the icon
6846 + if (!empty($title_icon_url)) {
6847 + echo '<div class="icon-preview" style="margin-top: 10px;">';
6848 + echo '<img src="' . esc_url($title_icon_url) . '" alt="' . esc_attr__('Title Icon Preview', 'mxchat') . '" style="max-width: 48px; height: auto;" />';
6849 + echo '</div>';
6850 + }
6851 +
6852 + echo '<p class="description">' . esc_html__('Upload your PNG icon and paste the URL here. Recommended size: 48x48 pixels.', 'mxchat') . '</p>';
6853 +
6854 + if (!$this->is_activated) {
6855 + echo '<div class="pro-feature-overlay">';
6856 + echo '<a href="https://mxchat.ai/" target="_blank">';
6857 + echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6858 + echo '</a>';
6859 + echo '</div>';
6860 + }
6861 + echo '</div>';
6862 +}
6863 +
6864 +public function mxchat_chat_input_font_color_callback() {
6865 + $disabled = $this->is_activated ? '' : 'disabled';
6866 + $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6867 +
6868 + echo '<div class="' . esc_attr($class) . '">';
6869 + echo sprintf(
6870 + '<input type="text"
6871 + id="chat_input_font_color"
6872 + name="chat_input_font_color"
6873 + value="%s"
6874 + class="my-color-field"
6875 + data-default-color="#555555"
6876 + %s />',
6877 + isset($this->options['chat_input_font_color']) ? esc_attr($this->options['chat_input_font_color']) : '#555555',
6878 + esc_attr($disabled)
6879 + );
6880 +
6881 + if (!$this->is_activated) {
6882 + echo '<div class="pro-feature-overlay">';
6883 + echo '<a href="https://mxchat.ai/" target="_blank">';
6884 + echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6885 + echo '</a>';
6886 + echo '</div>';
6887 + }
6888 + echo '</div>';
6889 +}
6890 +
7360 6891 public function mxchat_append_to_body_callback() {
7361 6892 // Fetch fresh options to ensure we have the latest saved values
7362 6893 $options = get_option('mxchat_options', array());
7363 6894
@@ -7381,8 +6912,11 @@
7381 6912 esc_attr($checked)
7382 6913 );
7383 6914 echo '<span class="slider"></span>';
7384 6915 echo '</label>';
6916 + echo '<p class="description">' .
6917 + esc_html__('Show chatbot automatically on all pages. When disabled, you can place the chatbot manually using shortcode [mxchat_chatbot floating="yes"].', 'mxchat') .
6918 + '</p>';
7385 6919
7386 6920 // Post Type Visibility Options (only visible when auto-display is ON)
7387 6921 $display_style = ($append_to_body === 'on') ? '' : 'display: none;';
7388 6922 echo '<div id="post-type-visibility-options" class="mxchat-sub-options" style="' . esc_attr($display_style) . '">';
@@ -7388,8 +6922,9 @@
7388 6922 echo '<div id="post-type-visibility-options" class="mxchat-sub-options" style="' . esc_attr($display_style) . '">';
7389 6923
7390 6924 echo '<div class="mxchat-post-type-visibility-header">';
7391 6925 echo '<h4>' . esc_html__('Post Type Visibility', 'mxchat') . '</h4>';
6926 + echo '<p class="description">' . esc_html__('Control which post types display the chatbot.', 'mxchat') . '</p>';
7392 6927 echo '</div>';
7393 6928
7394 6929 // Mode selector (radio buttons)
7395 6930 echo '<div class="mxchat-visibility-mode">';
@@ -7447,22 +6982,13 @@
7447 6982 esc_attr($checked)
7448 6983 );
7449 6984 echo '<span class="slider"></span>';
7450 6985 echo '</label>';
6986 + echo '<p class="description">' .
6987 + esc_html__('Enable contextual awareness to allow the chatbot to understand and reference the current page content. When enabled, the chatbot will have access to the page title, content, and URL for more relevant responses.', 'mxchat') .
6988 + '</p>';
7451 6989 }
7452 6990
7453 -public function mxchat_citation_links_toggle_callback() {
7454 - // Get value from options array, default to 'on' (enabled by default)
7455 - $citation_links = isset($this->options['citation_links_toggle']) ? $this->options['citation_links_toggle'] : 'on';
7456 - $checked = ($citation_links === 'on') ? 'checked' : '';
7457 - echo '<label class="toggle-switch">';
7458 - echo sprintf(
7459 - '<input type="checkbox" id="citation_links_toggle" name="citation_links_toggle" value="on" %s />',
7460 - esc_attr($checked)
7461 - );
7462 - echo '<span class="slider"></span>';
7463 - echo '</label>';
7464 -}
7465 6991
7466 6992 public function mxchat_privacy_toggle_callback() {
7467 6993 // Load from mxchat_options array
7468 6994 $options = get_option('mxchat_options', []);
@@ -7483,8 +7009,9 @@
7483 7009 esc_attr($checked)
7484 7010 );
7485 7011 echo '<span class="slider"></span>';
7486 7012 echo '</label>';
7013 + echo '<p class="description">' . esc_html__('Enable this option to display a privacy notice below the chat widget.', 'mxchat') . '</p>';
7487 7014
7488 7015 // Output the custom text input field
7489 7016 echo sprintf(
7490 7017 '<textarea id="privacy_text" name="privacy_text" rows="5" cols="50" class="regular-text">%s</textarea>',
@@ -7489,8 +7016,9 @@
7489 7016 echo sprintf(
7490 7017 '<textarea id="privacy_text" name="privacy_text" rows="5" cols="50" class="regular-text">%s</textarea>',
7491 7018 esc_textarea($privacy_text)
7492 7019 );
7020 + echo '<p class="description">' . esc_html__('Enter the privacy policy text. You can include HTML links.', 'mxchat') . '</p>';
7493 7021 }
7494 7022
7495 7023
7496 7024 public function mxchat_complianz_toggle_callback() {
@@ -7508,8 +7036,10 @@
7508 7036 esc_attr($checked)
7509 7037 );
7510 7038 echo '<span class="slider"></span>';
7511 7039 echo '</label>';
7040 +
7041 + echo '<p class="description">' . esc_html__('Enable this option to apply Complianz consent logic to the chatbot (must have Complianz Plugin).', 'mxchat') . '</p>';
7512 7042 }
7513 7043
7514 7044 public function mxchat_link_target_toggle_callback() {
7515 7045 // Load from mxchat_options array
@@ -7526,8 +7056,9 @@
7526 7056 esc_attr($checked)
7527 7057 );
7528 7058 echo '<span class="slider"></span>';
7529 7059 echo '</label>';
7060 + echo '<p class="description">' . esc_html__('Enable to open links in a new tab (default is to open in the same tab).', 'mxchat') . '</p>';
7530 7061 }
7531 7062
7532 7063 public function mxchat_chat_persistence_toggle_callback() {
7533 7064 // Load from mxchat_options array
@@ -7544,8 +7075,10 @@
7544 7075 esc_attr($checked)
7545 7076 );
7546 7077 echo '<span class="slider"></span>';
7547 7078 echo '</label>';
7079 +
7080 + echo '<p class="description">' . esc_html__('Enable to keep chat history when users navigate tabs or return to the site within 24 hours.', 'mxchat') . '</p>';
7548 7081 }
7549 7082
7550 7083 public function mxchat_popular_question_1_callback() {
7551 7084 // Load the full plugin options array
@@ -7559,8 +7092,11 @@
7559 7092 '<input type="text" id="popular_question_1" name="popular_question_1" value="%s" placeholder="%s" class="regular-text" />',
7560 7093 esc_attr($popular_question_1),
7561 7094 esc_attr__('Enter Quick Question 1', 'mxchat')
7562 7095 );
7096 +
7097 + // Add a description for the field
7098 + echo '<p class="description">' . esc_html__('This will be the first Quick Question in the chatbot, displayed above the input field.', 'mxchat') . '</p>';
7563 7099 }
7564 7100
7565 7101
7566 7102 public function mxchat_popular_question_2_callback() {
@@ -7575,8 +7111,11 @@
7575 7111 '<input type="text" id="popular_question_2" name="popular_question_2" value="%s" placeholder="%s" class="regular-text" />',
7576 7112 esc_attr($popular_question_2),
7577 7113 esc_attr__('Enter Quick Question 2', 'mxchat')
7578 7114 );
7115 +
7116 + // Add a description for the field
7117 + echo '<p class="description">' . esc_html__('This will be the second Quick Question in the chatbot.', 'mxchat') . '</p>';
7579 7118 }
7580 7119
7581 7120
7582 7121 public function mxchat_popular_question_3_callback() {
@@ -7591,8 +7130,11 @@
7591 7130 '<input type="text" id="popular_question_3" name="popular_question_3" value="%s" placeholder="%s" class="regular-text" />',
7592 7131 esc_attr($popular_question_3),
7593 7132 esc_attr(__('Enter Quick Question 3', 'mxchat'))
7594 7133 );
7134 +
7135 + // Add a description for the field
7136 + echo '<p class="description">' . esc_html__('This will be the third Quick Question in the chatbot.', 'mxchat') . '</p>';
7595 7137 }
7596 7138
7597 7139 public function mxchat_additional_popular_questions_callback() {
7598 7140 $options = get_option('mxchat_options', []);
@@ -7641,19 +7183,19 @@
7641 7183 '<button type="button" class="button mxchat-add-question" aria-label="%s">%s</button>',
7642 7184 esc_attr(__('Add question', 'mxchat')),
7643 7185 esc_html__('Add Question', 'mxchat')
7644 7186 );
7187 + echo '<p class="description">' . esc_html__('Add as many Quick Questions as you need.', 'mxchat') . '</p>';
7188 + echo '</div>';
7645 7189 }
7646 7190
7647 7191 public function mxchat_brave_api_key_callback() {
7648 7192 $brave_api_key = isset($this->options['brave_api_key']) ? esc_attr($this->options['brave_api_key']) : '';
7649 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
7650 7193
7651 7194 echo '<div class="api-key-wrapper">';
7652 7195 echo sprintf(
7653 - '<input type="text" id="brave_api_key" name="brave_api_key" value="%s" class="regular-text mxchat-autosave-field mxchat-api-key-field" autocomplete="new-password" data-lpignore="true" data-form-type="other" data-nonce="%s" />',
7654 - $brave_api_key,
7655 - $nonce
7196 + '<input type="password" id="brave_api_key" name="brave_api_key" value="%s" class="regular-text" />',
7197 + $brave_api_key
7656 7198 );
7657 7199 echo '<button type="button" id="toggleBraveApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
7658 7200 echo '</div>';
7659 7201 echo '<p class="description">' . __('Required for Brave Search integration. Get your API key from Brave Search API.', 'mxchat') . '</p>';
@@ -7662,18 +7204,14 @@
7662 7204 public function mxchat_brave_image_count_callback() {
7663 7205 $brave_image_count = isset($this->options['brave_image_count'])
7664 7206 ? intval($this->options['brave_image_count'])
7665 7207 : 4;
7666 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
7667 7208
7668 - echo '<div class="mxchat-field-wrapper">';
7669 7209 echo sprintf(
7670 7210 '<input type="number" id="brave_image_count" name="brave_image_count"
7671 - value="%d" min="1" max="6" class="small-text mxchat-autosave-field" data-nonce="%s" />',
7672 - $brave_image_count,
7673 - $nonce
7211 + value="%d" min="1" max="6" class="small-text" />',
7212 + $brave_image_count
7674 7213 );
7675 - echo '</div>';
7676 7214 echo '<p class="description">' . __('Select the number of images to return (1-6).', 'mxchat') . '</p>';
7677 7215 }
7678 7216
7679 7217 public function mxchat_brave_safe_search_callback() {
@@ -7679,12 +7217,10 @@
7679 7217 public function mxchat_brave_safe_search_callback() {
7680 7218 $brave_safe_search = isset($this->options['brave_safe_search'])
7681 7219 ? esc_attr($this->options['brave_safe_search'])
7682 7220 : 'strict';
7683 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
7684 7221
7685 - echo '<div class="mxchat-field-wrapper">';
7686 - echo '<select id="brave_safe_search" name="brave_safe_search" class="mxchat-autosave-field" data-nonce="' . $nonce . '">';
7222 + echo '<select id="brave_safe_search" name="brave_safe_search">';
7687 7223 echo sprintf(
7688 7224 '<option value="strict" %s>%s</option>',
7689 7225 selected($brave_safe_search, 'strict', false),
7690 7226 __('Strict', 'mxchat')
@@ -7694,9 +7230,8 @@
7694 7230 selected($brave_safe_search, 'off', false),
7695 7231 __('Off', 'mxchat')
7696 7232 );
7697 7233 echo '</select>';
7698 - echo '</div>';
7699 7234 echo '<p class="description">' .
7700 7235 esc_html__('Set the Safe Search level for image searches. Brave Search only supports "Strict" and "Off" options.', 'mxchat') .
7701 7236 '</p>';
7702 7237 }
@@ -7704,18 +7239,14 @@
7704 7239 public function mxchat_brave_news_count_callback() {
7705 7240 $brave_news_count = isset($this->options['brave_news_count'])
7706 7241 ? intval($this->options['brave_news_count'])
7707 7242 : 3;
7708 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
7709 7243
7710 - echo '<div class="mxchat-field-wrapper">';
7711 7244 echo sprintf(
7712 7245 '<input type="number" id="brave_news_count" name="brave_news_count"
7713 - value="%d" min="1" max="10" class="small-text mxchat-autosave-field" data-nonce="%s" />',
7714 - $brave_news_count,
7715 - $nonce
7246 + value="%d" min="1" max="10" class="small-text" />',
7247 + $brave_news_count
7716 7248 );
7717 - echo '</div>';
7718 7249 echo '<p class="description">' . esc_html__('Select the number of news articles to retrieve (1-10).', 'mxchat') . '</p>';
7719 7250 }
7720 7251
7721 7252 public function mxchat_brave_country_callback() {
@@ -7721,18 +7252,14 @@
7721 7252 public function mxchat_brave_country_callback() {
7722 7253 $brave_country = isset($this->options['brave_country'])
7723 7254 ? esc_attr($this->options['brave_country'])
7724 7255 : 'us';
7725 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
7726 7256
7727 - echo '<div class="mxchat-field-wrapper">';
7728 7257 echo sprintf(
7729 7258 '<input type="text" id="brave_country" name="brave_country"
7730 - value="%s" maxlength="2" class="small-text mxchat-autosave-field" data-nonce="%s" />',
7731 - $brave_country,
7732 - $nonce
7259 + value="%s" maxlength="2" class="small-text" />',
7260 + $brave_country
7733 7261 );
7734 - echo '</div>';
7735 7262 echo '<p class="description">' . esc_html__('Enter the country code (e.g., "us" for United States).', 'mxchat') . '</p>';
7736 7263 }
7737 7264
7738 7265 public function mxchat_brave_language_callback() {
@@ -7738,18 +7265,14 @@
7738 7265 public function mxchat_brave_language_callback() {
7739 7266 $brave_language = isset($this->options['brave_language'])
7740 7267 ? esc_attr($this->options['brave_language'])
7741 7268 : 'en';
7742 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
7743 7269
7744 - echo '<div class="mxchat-field-wrapper">';
7745 7270 echo sprintf(
7746 7271 '<input type="text" id="brave_language" name="brave_language"
7747 - value="%s" maxlength="2" class="small-text mxchat-autosave-field" data-nonce="%s" />',
7748 - $brave_language,
7749 - $nonce
7272 + value="%s" maxlength="2" class="small-text" />',
7273 + $brave_language
7750 7274 );
7751 - echo '</div>';
7752 7275 echo '<p class="description">' . esc_html__('Enter the language code (e.g., "en" for English).', 'mxchat') . '</p>';
7753 7276 }
7754 7277
7755 7278
@@ -7969,97 +7492,13 @@
7969 7492 );
7970 7493 echo '<p class="description">' . esc_html__('Enter Slack User IDs of agents who should be automatically invited to chat channels (one per line). Find user IDs in Slack profiles under "More" → "Copy member ID". Example: U1234567890', 'mxchat') . '</p>';
7971 7494 }
7972 7495
7973 -/**
7974 - * Telegram Integration Callbacks
7975 - */
7976 -public function mxchat_telegram_section_callback() {
7977 - echo '<p>' . esc_html__('Configure Telegram integration for live agent support.', 'mxchat') . '</p>';
7978 -}
7979 -
7980 -public function mxchat_telegram_status_callback() {
7981 - $fresh_options = get_option('mxchat_options');
7982 - $status = isset($fresh_options['telegram_status']) ? $fresh_options['telegram_status'] : 'off';
7983 -
7984 - echo '<label class="toggle-switch">';
7985 - echo sprintf(
7986 - '<input type="checkbox" id="telegram_status" name="telegram_status" value="on" %s />',
7987 - checked($status, 'on', false)
7988 - );
7989 - echo '<span class="slider"></span>';
7990 - echo '</label>';
7991 - echo '<label for="telegram_status" class="mxchat-status-label">';
7992 - echo '<span class="status-text">' . ($status === 'on' ? esc_html__('Online', 'mxchat') : esc_html__('Offline', 'mxchat')) . '</span>';
7993 - echo '</label>';
7994 -}
7995 -
7996 -public function mxchat_telegram_notification_message_callback() {
7997 - $message = isset($this->options['telegram_notification_message'])
7998 - ? $this->options['telegram_notification_message']
7999 - : __("I've notified a support agent. Please allow a moment for them to respond.", 'mxchat');
8000 -
8001 - printf(
8002 - '<textarea id="telegram_notification_message" name="telegram_notification_message" rows="3" cols="50">%s</textarea>',
8003 - esc_textarea($message)
8004 - );
8005 - echo '<p class="description">' . esc_html__('Message shown when live transfer activated.', 'mxchat') . '</p>';
8006 -}
8007 -
8008 -public function mxchat_telegram_away_message_callback() {
8009 - $message = isset($this->options['telegram_away_message'])
8010 - ? $this->options['telegram_away_message']
8011 - : __('Sorry, live agents are currently unavailable. I can continue helping you as an AI assistant.', 'mxchat');
8012 -
8013 - printf(
8014 - '<textarea id="telegram_away_message" name="telegram_away_message" rows="3" cols="50">%s</textarea>',
8015 - esc_textarea($message)
8016 - );
8017 - echo '<p class="description">' . esc_html__('Message shown when live agents are offline.', 'mxchat') . '</p>';
8018 -}
8019 -
8020 -public function mxchat_telegram_bot_token_callback() {
8021 - printf(
8022 - '<input type="password" id="telegram_bot_token" name="telegram_bot_token" value="%s" class="regular-text" />',
8023 - isset($this->options['telegram_bot_token']) ? esc_attr($this->options['telegram_bot_token']) : ''
8024 - );
8025 - echo '<button type="button" class="button button-secondary" onclick="var f=document.getElementById(\'telegram_bot_token\'); if(f.type===\'password\'){f.type=\'text\';this.textContent=\'' . esc_js(__('Hide', 'mxchat')) . '\';}else{f.type=\'password\';this.textContent=\'' . esc_js(__('Show', 'mxchat')) . '\';}">' . esc_html__('Show', 'mxchat') . '</button>';
8026 - echo '<p class="description">' . esc_html__('Your Telegram Bot Token from @BotFather (e.g., 123456789:ABCdefGhIJKlmNoPQRsTUVwxyZ).', 'mxchat') . '</p>';
8027 -}
8028 -
8029 -public function mxchat_telegram_group_id_callback() {
8030 - printf(
8031 - '<input type="text" id="telegram_group_id" name="telegram_group_id" value="%s" class="regular-text" />',
8032 - isset($this->options['telegram_group_id']) ? esc_attr($this->options['telegram_group_id']) : ''
8033 - );
8034 - echo '<p class="description">' . esc_html__('Your Telegram supergroup ID (starts with -100). The group must have forum topics enabled.', 'mxchat') . '</p>';
8035 -}
8036 -
8037 -public function mxchat_telegram_webhook_secret_callback() {
8038 - $secret = isset($this->options['telegram_webhook_secret']) ? $this->options['telegram_webhook_secret'] : '';
8039 -
8040 - // Auto-generate secret if empty
8041 - if (empty($secret)) {
8042 - $secret = wp_generate_password(64, false, false);
8043 - $options = get_option('mxchat_options', []);
8044 - $options['telegram_webhook_secret'] = $secret;
8045 - update_option('mxchat_options', $options);
8046 - $this->options['telegram_webhook_secret'] = $secret;
8047 - }
8048 -
8049 - printf(
8050 - '<input type="password" id="telegram_webhook_secret" name="telegram_webhook_secret" value="%s" class="regular-text" />',
8051 - esc_attr($secret)
8052 - );
8053 - echo '<button type="button" class="button button-secondary" onclick="var f=document.getElementById(\'telegram_webhook_secret\'); if(f.type===\'password\'){f.type=\'text\';this.textContent=\'' . esc_js(__('Hide', 'mxchat')) . '\';}else{f.type=\'password\';this.textContent=\'' . esc_js(__('Show', 'mxchat')) . '\';}">' . esc_html__('Show', 'mxchat') . '</button>';
8054 - echo '<p class="description">' . esc_html__('Secret token for webhook verification. Use this when setting up your Telegram webhook with the secret_token parameter.', 'mxchat') . '</p>';
8055 -}
8056 -
8057 7496 public function mxchat_similarity_threshold_callback() {
8058 7497 // Load from mxchat_options array
8059 7498 $options = get_option('mxchat_options', []);
8060 7499
8061 - // Get value from options array with default of 35
7500 + // Get value from options array with default of 80
8062 7501 $threshold = isset($options['similarity_threshold']) ? $options['similarity_threshold'] : 35;
8063 7502
8064 7503 echo '<div class="slider-container">';
8065 7504 echo sprintf(
@@ -8077,62 +7516,13 @@
8077 7516 '<span id="threshold_value" class="range-value">%s</span>',
8078 7517 esc_html($threshold)
8079 7518 );
8080 7519 echo '</div>';
7520 + echo '<p class="description">';
7521 + echo esc_html__('Adjust similarity threshold for optimal content matching. Too high may limit knowledge retrieval. We highly recommend using the MxChat Debugger found on the left side of your website when logged in as admin while testing the bot.', 'mxchat');
7522 + echo '</p>';
8081 7523 }
8082 7524
8083 -public function mxchat_rag_sources_limit_callback() {
8084 - // Load from mxchat_options array
8085 - $options = get_option('mxchat_options', []);
8086 -
8087 - // Get value from options array with default of 3
8088 - $rag_sources_limit = isset($options['rag_sources_limit']) ? intval($options['rag_sources_limit']) : 3;
8089 -
8090 - echo '<div class="slider-container">';
8091 - echo sprintf(
8092 - '<input type="range"
8093 - id="rag_sources_limit"
8094 - name="rag_sources_limit"
8095 - min="3"
8096 - max="10"
8097 - step="1"
8098 - value="%s"
8099 - class="range-slider" />',
8100 - esc_attr($rag_sources_limit)
8101 - );
8102 - echo sprintf(
8103 - '<span id="rag_sources_limit_value" class="range-value">%s</span>',
8104 - esc_html($rag_sources_limit)
8105 - );
8106 - echo '</div>';
8107 -}
8108 -
8109 -public function mxchat_rag_chunks_limit_callback() {
8110 - // Load from mxchat_options array
8111 - $options = get_option('mxchat_options', []);
8112 -
8113 - // Get value from options array with default of 15
8114 - $rag_chunks_limit = isset($options['rag_chunks_limit']) ? intval($options['rag_chunks_limit']) : 15;
8115 -
8116 - echo '<div class="slider-container">';
8117 - echo sprintf(
8118 - '<input type="range"
8119 - id="rag_chunks_limit"
8120 - name="rag_chunks_limit"
8121 - min="8"
8122 - max="20"
8123 - step="1"
8124 - value="%s"
8125 - class="range-slider" />',
8126 - esc_attr($rag_chunks_limit)
8127 - );
8128 - echo sprintf(
8129 - '<span id="rag_chunks_limit_value" class="range-value">%s</span>',
8130 - esc_html($rag_chunks_limit)
8131 - );
8132 - echo '</div>';
8133 -}
8134 -
8135 7525 public function mxchat_enqueue_admin_assets() {
8136 7526 // Get plugin version
8137 7527 $version = MXCHAT_VERSION;
8138 7528
@@ -8158,11 +7548,8 @@
8158 7548 // Core admin styles
8159 7549 wp_enqueue_style('mxchat-admin-css', $plugin_url . 'css/admin-style.css', array(), $version);
8160 7550 wp_enqueue_style('mxchat-knowledge-css', $plugin_url . 'css/knowledge-style.css', array(), $version);
8161 7551
8162 - // New sidebar navigation styles (for main settings page)
8163 - wp_enqueue_style('mxchat-admin-sidebar-css', $plugin_url . 'css/admin-sidebar.css', array(), $version);
8164 -
8165 7552 // Core admin scripts
8166 7553 wp_enqueue_script('mxchat-admin-js', $plugin_url . 'js/mxchat-admin.js', array('jquery'), $version, true);
8167 7554 }
8168 7555 private function enqueue_page_specific_assets($current_page, $plugin_url, $version) {
@@ -8170,72 +7557,24 @@
8170 7557 case 'mxchat-prompts':
8171 7558 // Knowledge processing page assets
8172 7559 wp_enqueue_style('mxchat-content-selector-css', $plugin_url . 'css/content-selector.css', array(), $version);
8173 7560 wp_enqueue_script('mxchat-content-selector-js', $plugin_url . 'js/content-selector.js', array('jquery'), $version, true);
8174 - // Add the knowledge processing script (common script needed for WordPress dismiss functionality)
8175 - wp_enqueue_script('mxchat-knowledge-processing', $plugin_url . 'js/knowledge-processing.js', array('jquery', 'common'), $version, true);
7561 + // Add the knowledge processing script
7562 + wp_enqueue_script('mxchat-knowledge-processing', $plugin_url . 'js/knowledge-processing.js', array('jquery'), $version, true);
8176 7563 break;
8177 7564
8178 7565 case 'mxchat-transcripts':
8179 - // Load admin sidebar CSS first (shared styles for sidebar navigation)
8180 - wp_enqueue_style('mxchat-admin-sidebar-css', $plugin_url . 'css/admin-sidebar.css', array(), $version);
8181 - // Load transcripts-specific styles
8182 - wp_enqueue_style('mxchat-chat-transcripts-css', $plugin_url . 'css/chat-transcripts.css', array('mxchat-admin-sidebar-css'), $version);
7566 + wp_enqueue_style('mxchat-chat-transcripts-css', $plugin_url . 'css/chat-transcripts.css', array(), $version);
8183 7567 wp_enqueue_script('mxchat-transcripts-js', $plugin_url . 'js/mxchat_transcripts.js', array('jquery'), $version, true);
8184 7568 break;
8185 7569
8186 7570 case 'mxchat-actions':
8187 - // Load admin sidebar CSS first (shared styles for sidebar navigation)
8188 - wp_enqueue_style('mxchat-admin-sidebar-css', $plugin_url . 'css/admin-sidebar.css', array(), $version);
8189 - // Load actions-specific styles
8190 - wp_enqueue_style('mxchat-actions-css', $plugin_url . 'css/actions.css', array('mxchat-admin-sidebar-css'), $version);
8191 - wp_enqueue_script('mxchat-actions-js', $plugin_url . 'js/mxchat_actions.js', array('jquery'), $version, true);
8192 -
8193 - // Localize script data for actions page
8194 - $is_activated = get_option('mxchat_pro_license_status') === 'active';
8195 - wp_localize_script('mxchat-actions-js', 'mxchActionsData', array(
8196 - 'ajaxUrl' => admin_url('admin-ajax.php'),
8197 - 'nonce' => wp_create_nonce('mxchat_actions_nonce'),
8198 - 'addNonce' => wp_create_nonce('mxchat_add_intent_nonce'),
8199 - 'editNonce' => wp_create_nonce('mxchat_edit_intent'),
8200 - 'deleteNonce' => wp_create_nonce('mxchat_delete_intent_nonce'),
8201 - 'toggleNonce' => wp_create_nonce('mxchat_actions_nonce'),
8202 - 'addPhraseNonce' => wp_create_nonce('mxchat_add_phrase_nonce'),
8203 - 'deletePhraseNonce' => wp_create_nonce('mxchat_delete_phrase_nonce'),
8204 - 'getPhrasesNonce' => wp_create_nonce('mxchat_get_phrases_nonce'),
8205 - 'deleteLegacyNonce' => wp_create_nonce('mxchat_delete_legacy_nonce'),
8206 - 'isActivated' => $is_activated,
8207 - 'i18n' => array(
8208 - 'confirmDelete' => __('Are you sure you want to delete this action?', 'mxchat'),
8209 - 'confirmBulkDelete' => __('Are you sure you want to delete the selected actions?', 'mxchat'),
8210 - 'saving' => __('Saving...', 'mxchat'),
8211 - 'saved' => __('Saved successfully!', 'mxchat'),
8212 - 'error' => __('An error occurred. Please try again.', 'mxchat'),
8213 - 'proRequired' => __('This feature requires MxChat Pro.', 'mxchat'),
8214 - 'addonRequired' => __('This feature requires an add-on.', 'mxchat')
8215 - )
8216 - ));
7571 + wp_enqueue_style('mxchat-intent-css', $plugin_url . 'css/intent-style.css', array(), $version);
8217 7572 break;
8218 7573
8219 7574 case 'mxchat-activation':
8220 - // Load admin sidebar CSS first (shared styles for sidebar navigation)
8221 - wp_enqueue_style('mxchat-admin-sidebar-css', $plugin_url . 'css/admin-sidebar.css', array(), $version);
8222 - // Load pro page-specific styles
8223 - wp_enqueue_style('mxchat-pro-css', $plugin_url . 'css/admin-pro.css', array('mxchat-admin-sidebar-css'), $version);
8224 - // Load pro page JavaScript
8225 - wp_enqueue_script('mxchat-pro-js', $plugin_url . 'js/mxchat_pro.js', array('jquery'), $version, true);
8226 - // Load activation script for license activation/deactivation
8227 7575 wp_enqueue_script('mxchat-activation-js', $plugin_url . 'js/activation-script.js', array('jquery'), $version, true);
8228 7576 break;
8229 -
8230 - case 'mxchat-content':
8231 - // Load admin sidebar CSS (shared styles for sidebar navigation)
8232 - wp_enqueue_style('mxchat-admin-sidebar-css', $plugin_url . 'css/admin-sidebar.css', array(), $version);
8233 - // Load content page-specific styles
8234 - wp_enqueue_style('mxchat-content-css', $plugin_url . 'css/admin-content.css', array('mxchat-admin-sidebar-css'), $version);
8235 - // Load content page JavaScript
8236 - wp_enqueue_script('mxchat-content-js', $plugin_url . 'js/mxchat-content.js', array('jquery'), $version, true);
8237 - break;
8238 7577 default:
8239 7578 wp_enqueue_script(
8240 7579 'mxchat-test-streaming-js',
8241 7580 $plugin_url . 'js/mxchat-test-streaming.js',
@@ -8245,80 +7584,10 @@
8245 7584 );
8246 7585
8247 7586 wp_localize_script('mxchat-test-streaming-js', 'mxchatTestStreamingAjax', [
8248 7587 'ajax_url' => admin_url('admin-ajax.php'),
8249 - 'nonce' => wp_create_nonce('mxchat_test_streaming_nonce'),
8250 - 'settings_nonce' => wp_create_nonce('mxchat_save_setting_nonce')
7588 + 'nonce' => wp_create_nonce('mxchat_test_streaming_nonce')
8251 7589 ]);
8252 -
8253 - // Testing Tab: Load chatbot assets for the embedded testing chatbot
8254 - wp_enqueue_style('mxchat-chat-css', $plugin_url . 'css/chat-style.css', array(), $version);
8255 - wp_enqueue_style('mxchat-admin-testing-css', $plugin_url . 'css/admin-testing-tab.css', array('mxchat-admin-sidebar-css', 'mxchat-chat-css'), $version);
8256 -
8257 - wp_enqueue_script('mxchat-chat-js', $plugin_url . 'js/chat-script.js', array('jquery'), $version, true);
8258 - wp_enqueue_script('mxchat-admin-testing-js', $plugin_url . 'js/admin-testing-tab.js', array('jquery', 'mxchat-chat-js'), $version, true);
8259 -
8260 - // Allow add-ons to enqueue their public CSS/JS for the testing chatbot
8261 - do_action('mxchat_enqueue_testing_tab_assets');
8262 -
8263 - // Localize mxchatChat for the chatbot JS (same settings as frontend)
8264 - $options = get_option('mxchat_options', array());
8265 - $prompts_options = get_option('mxchat_prompts_options', array());
8266 - $theme_options = get_option('mxchat_theme_options', array());
8267 - $ai_theme_active = !empty($theme_options['active_ai_theme_css']);
8268 - $has_bot_theme_assignments = !empty($theme_options['bot_theme_assignments']);
8269 - $skip_inline_colors = $ai_theme_active || $has_bot_theme_assignments;
8270 -
8271 - wp_localize_script('mxchat-chat-js', 'mxchatChat', array(
8272 - 'ajax_url' => admin_url('admin-ajax.php'),
8273 - 'nonce' => wp_create_nonce('mxchat_chat_nonce'),
8274 - 'model' => isset($options['model']) ? $options['model'] : 'gpt-5.1-chat-latest',
8275 - 'enable_streaming_toggle' => isset($options['enable_streaming_toggle']) ? $options['enable_streaming_toggle'] : 'on',
8276 - 'contextual_awareness_toggle' => isset($options['contextual_awareness_toggle']) ? $options['contextual_awareness_toggle'] : 'off',
8277 - 'link_target_toggle' => $options['link_target_toggle'] ?? 'off',
8278 - 'rate_limit_message' => $options['rate_limit_message'] ?? 'Rate limit exceeded. Please try again later.',
8279 - 'complianz_toggle' => isset($options['complianz_toggle']) && $options['complianz_toggle'] === 'on',
8280 - 'user_message_bg_color' => $options['user_message_bg_color'] ?? '#fff',
8281 - 'user_message_font_color' => $options['user_message_font_color'] ?? '#212121',
8282 - 'bot_message_bg_color' => $options['bot_message_bg_color'] ?? '#212121',
8283 - 'bot_message_font_color' => $options['bot_message_font_color'] ?? '#fff',
8284 - 'top_bar_bg_color' => $options['top_bar_bg_color'] ?? '#212121',
8285 - 'send_button_font_color' => $options['send_button_font_color'] ?? '#212121',
8286 - 'close_button_color' => $options['close_button_color'] ?? '#fff',
8287 - 'chatbot_background_color' => $options['chatbot_background_color'] ?? '#212121',
8288 - 'chatbot_bg_color' => $options['chatbot_bg_color'] ?? '#fff',
8289 - 'icon_color' => $options['icon_color'] ?? '#fff',
8290 - 'chat_input_font_color' => $options['chat_input_font_color'] ?? '#212121',
8291 - 'chat_persistence_toggle' => 'off', // Always off for testing chatbot
8292 - 'appendWidgetToBody' => 'off',
8293 - 'live_agent_message_bg_color' => $options['live_agent_message_bg_color'] ?? '#ffffff',
8294 - 'live_agent_message_font_color' => $options['live_agent_message_font_color'] ?? '#333333',
8295 - 'chat_toolbar_toggle' => $options['chat_toolbar_toggle'] ?? 'off',
8296 - 'mode_indicator_bg_color' => $options['mode_indicator_bg_color'] ?? '#767676',
8297 - 'mode_indicator_font_color' => $options['mode_indicator_font_color'] ?? '#ffffff',
8298 - 'toolbar_icon_color' => $options['toolbar_icon_color'] ?? '#212121',
8299 - 'use_pinecone' => $prompts_options['mxchat_use_pinecone'] ?? '0',
8300 - 'email_collection_enabled' => 'off', // No email collection in testing
8301 - 'initial_email_state' => null,
8302 - 'skip_email_check' => true,
8303 - 'pinecone_enabled' => isset($prompts_options['mxchat_use_pinecone']) && $prompts_options['mxchat_use_pinecone'] === '1',
8304 - 'skip_inline_colors' => $skip_inline_colors,
8305 - 'bot_theme_assignments' => $theme_options['bot_theme_assignments'] ?? array()
8306 - ));
8307 -
8308 - // Localize testing tab script data
8309 - wp_localize_script('mxchat-admin-testing-js', 'mxchatAdminTestData', array(
8310 - 'ajaxUrl' => admin_url('admin-ajax.php'),
8311 - 'nonce' => wp_create_nonce('mxchat_test_nonce'),
8312 - 'isAdmin' => true,
8313 - 'testingEnabled' => true
8314 - ));
8315 -
8316 - // Add testing enabled flag for the chatbot to return debug data
8317 - add_action('admin_footer', function() {
8318 - echo '<script>window.mxchatTestingEnabled = true;</script>';
8319 - });
8320 -
8321 7590 break;
8322 7591 }
8323 7592 }
8324 7593 private function localize_admin_scripts($current_page) {
@@ -8324,9 +7593,9 @@
8324 7593 private function localize_admin_scripts($current_page) {
8325 7594 // Base localization data for main admin script
8326 7595 $base_data = array(
8327 7596 'ajax_url' => admin_url('admin-ajax.php'),
8328 - 'nonce' => wp_create_nonce('mxchat_admin_nonce'),
7597 + 'nonce' => wp_create_nonce('mxchat_status_nonce'),
8329 7598 'admin_url' => admin_url(),
8330 7599 'license_nonce' => wp_create_nonce('mxchat_activate_license_nonce'),
8331 7600 'inline_edit_nonce' => wp_create_nonce('mxchat_save_inline_nonce'),
8332 7601 'setting_nonce' => wp_create_nonce('mxchat_save_setting_nonce'),
@@ -8333,14 +7602,10 @@
8333 7602 'export_nonce' => wp_create_nonce('mxchat_export_transcripts'),
8334 7603 'actions_nonce' => wp_create_nonce('mxchat_actions_nonce'),
8335 7604 'add_intent_nonce' => wp_create_nonce('mxchat_add_intent_nonce'),
8336 7605 'edit_intent_nonce' => wp_create_nonce('mxchat_edit_intent'),
8337 - 'add_phrase_nonce' => wp_create_nonce('mxchat_add_phrase_nonce'),
8338 - 'delete_phrase_nonce' => wp_create_nonce('mxchat_delete_phrase_nonce'),
8339 - 'get_phrases_nonce' => wp_create_nonce('mxchat_get_phrases_nonce'),
8340 - 'delete_legacy_nonce' => wp_create_nonce('mxchat_delete_legacy_nonce'),
8341 7606 'toggle_action_nonce' => wp_create_nonce('mxchat_actions_nonce'),
8342 - 'fetch_openrouter_models_nonce' => wp_create_nonce('mxchat_fetch_openrouter_models'),
7607 + 'fetch_openrouter_models_nonce' => wp_create_nonce('mxchat_fetch_openrouter_models'), // ADD THIS LINE
8343 7608 'is_activated' => $this->is_activated ? '1' : '0',
8344 7609 'status_refresh_interval' => 5000,
8345 7610 'prompts_setting_nonce' => wp_create_nonce('mxchat_prompts_setting_nonce'),
8346 7611 'ajaxurl' => admin_url('admin-ajax.php')
@@ -8380,13 +7645,11 @@
8380 7645 'queue_nonce' => wp_create_nonce('mxchat_queue_nonce'),
8381 7646 'stop_nonce' => wp_create_nonce('mxchat_stop_processing_action'),
8382 7647 'settings_nonce' => wp_create_nonce('mxchat_prompts_setting_nonce'),
8383 7648 'setting_nonce' => wp_create_nonce('mxchat_save_setting_nonce'),
8384 - 'entries_nonce' => wp_create_nonce('mxchat_entries_nonce'),
8385 7649 'admin_url' => admin_url(),
8386 7650 'ajaxurl' => admin_url('admin-ajax.php'),
8387 - 'status_refresh_interval' => 2000,
8388 - 'bot_id' => isset($_GET['bot_id']) ? sanitize_text_field($_GET['bot_id']) : 'default'
7651 + 'status_refresh_interval' => 2000
8389 7652 ));
8390 7653 break;
8391 7654
8392 7655 case 'mxchat-activation':
@@ -8395,60 +7658,8 @@
8395 7658 'license_nonce' => wp_create_nonce('mxchat_activate_license_nonce')
8396 7659 ));
8397 7660 break;
8398 7661
8399 - case 'mxchat-content':
8400 - wp_localize_script('mxchat-content-js', 'mxchatContent', array(
8401 - 'ajaxUrl' => admin_url('admin-ajax.php'),
8402 - 'nonce' => wp_create_nonce('mxchat_content_nonce'),
8403 - 'settingNonce' => wp_create_nonce('mxchat_save_setting_nonce'),
8404 - 'previewUrl' => home_url('/?p='),
8405 - 'isActivated' => $this->is_activated(),
8406 - 'hasAdvancedContent' => apply_filters('mxchat_content_pro_feature', false, 'seo_readability'),
8407 - 'hasGSC' => apply_filters('mxchat_content_pro_feature', false, 'gsc_integration'),
8408 - 'seoOptimize' => array(
8409 - 'meta_description' => ($options['seo_optimize_meta_desc'] ?? 'on') === 'on',
8410 - 'seo_title' => ($options['seo_optimize_seo_title'] ?? 'on') === 'on',
8411 - 'slug' => ($options['seo_optimize_slug'] ?? 'on') === 'on',
8412 - 'readability' => ($options['seo_optimize_readability'] ?? 'on') === 'on',
8413 - 'internal_links' => ($options['seo_optimize_internal_links'] ?? 'on') === 'on',
8414 - 'img_alt' => ($options['seo_optimize_img_alt'] ?? 'on') === 'on',
8415 - 'featured_img' => ($options['seo_optimize_featured_img'] ?? 'on') === 'on',
8416 - ),
8417 - 'i18n' => array(
8418 - 'generating' => __('Generating...', 'mxchat'),
8419 - 'planning' => __('Planning content structure...', 'mxchat'),
8420 - 'images' => __('Generating images...', 'mxchat'),
8421 - 'writing' => __('Writing full content...', 'mxchat'),
8422 - 'creating' => __('Creating WordPress post...', 'mxchat'),
8423 - 'done' => __('Content generated successfully!', 'mxchat'),
8424 - 'error' => __('An error occurred. Please try again.', 'mxchat'),
8425 - 'editSuccess' => __('Content updated successfully.', 'mxchat'),
8426 - 'promptEmpty' => __('Please enter a prompt.', 'mxchat'),
8427 - )
8428 - ));
8429 - break;
8430 -
8431 - case 'mxchat-transcripts':
8432 - // Get chart data for localization
8433 - $chart_data = $this->get_transcripts_chart_data();
8434 -
8435 - wp_localize_script('mxchat-transcripts-js', 'mxchatAdmin', array(
8436 - 'ajax_url' => admin_url('admin-ajax.php'),
8437 - 'export_nonce' => wp_create_nonce('mxchat_export_transcripts'),
8438 - 'delete_nonce' => wp_create_nonce('mxchat_delete_chat_history'),
8439 - 'setting_nonce' => wp_create_nonce('mxchat_save_setting_nonce'),
8440 - 'translate_nonce' => wp_create_nonce('mxchat_translate_messages')
8441 - ));
8442 -
8443 - // Localize chart data separately - use array_values to ensure proper JSON array encoding
8444 - wp_localize_script('mxchat-transcripts-js', 'mxchatChartData', array(
8445 - 'labels' => array_values($chart_data['labels']),
8446 - 'chats' => array_values($chart_data['chats']),
8447 - 'messages' => array_values($chart_data['messages'])
8448 - ));
8449 - break;
8450 -
8451 7662 case 'mxchat-settings':
8452 7663 default:
8453 7664 // Color picker and settings localization
8454 7665 wp_localize_script('mxchat-color-picker', 'mxchatStyleSettings', array(
@@ -8507,18 +7718,8 @@
8507 7718 $new_input['similarity_threshold'] = absint($input['similarity_threshold']); // Ensure it's an integer
8508 7719 $new_input['similarity_threshold'] = min(max($new_input['similarity_threshold'], 20), 85); // Enforce range
8509 7720 }
8510 7721
8511 - if (isset($input['rag_sources_limit'])) {
8512 - $new_input['rag_sources_limit'] = absint($input['rag_sources_limit']); // Ensure it's an integer
8513 - $new_input['rag_sources_limit'] = min(max($new_input['rag_sources_limit'], 3), 10); // Enforce range 3-10
8514 - }
8515 -
8516 - if (isset($input['rag_chunks_limit'])) {
8517 - $new_input['rag_chunks_limit'] = absint($input['rag_chunks_limit']); // Ensure it's an integer
8518 - $new_input['rag_chunks_limit'] = min(max($new_input['rag_chunks_limit'], 8), 20); // Enforce range 8-20
8519 - }
8520 -
8521 7722 if (isset($input['xai_api_key'])) {
8522 7723 $new_input['xai_api_key'] = sanitize_text_field($input['xai_api_key']);
8523 7724 }
8524 7725
@@ -8532,14 +7733,8 @@
8532 7733 // If checkbox not checked, it won't be in $input, so set to 'off'
8533 7734 $new_input['enable_streaming_toggle'] = 'off';
8534 7735 }
8535 7736
8536 - if (isset($input['enable_web_search'])) {
8537 - $new_input['enable_web_search'] = ($input['enable_web_search'] === 'on') ? 'on' : 'off';
8538 - } else {
8539 - // If checkbox not checked, it won't be in $input, so set to 'off'
8540 - $new_input['enable_web_search'] = 'off';
8541 - }
8542 7737
8543 7738 if (isset($input['deepseek_api_key'])) {
8544 7739 $new_input['deepseek_api_key'] = sanitize_text_field($input['deepseek_api_key']);
8545 7740 }
@@ -8601,12 +7796,8 @@
8601 7796 if (isset($input['contextual_awareness_toggle'])) {
8602 7797 $new_input['contextual_awareness_toggle'] = $input['contextual_awareness_toggle'] === 'on' ? 'on' : 'off';
8603 7798 }
8604 7799
8605 - if (isset($input['citation_links_toggle'])) {
8606 - $new_input['citation_links_toggle'] = $input['citation_links_toggle'] === 'on' ? 'on' : 'off';
8607 -}
8608 -
8609 7800 if (isset($input['top_bar_title'])) {
8610 7801 $new_input['top_bar_title'] = sanitize_text_field($input['top_bar_title']);
8611 7802 }
8612 7803
@@ -8697,9 +7888,9 @@
8697 7888 'text-embedding-ada-002',
8698 7889 'text-embedding-3-small',
8699 7890 'text-embedding-3-large',
8700 7891 'voyage-3-large',
8701 - 'gemini-embedding-001'
7892 + 'gemini-embedding-exp-03-07'
8702 7893 );
8703 7894 if (in_array($input['embedding_model'], $allowed_models)) {
8704 7895 $new_input['embedding_model'] = sanitize_text_field($input['embedding_model']);
8705 7896 }
@@ -8709,13 +7900,8 @@
8709 7900 if ($input['model'] === 'openrouter') {
8710 7901 $new_input['model'] = 'openrouter';
8711 7902 } else {
8712 7903 $allowed_models = array(
8713 - 'gemini-3-pro-preview',
8714 - 'gemini-3-flash-preview',
8715 - 'gemini-2.5-pro',
8716 - 'gemini-2.5-flash',
8717 - 'gemini-2.5-flash-lite',
8718 7904 'gemini-2.0-flash',
8719 7905 'gemini-2.0-flash-lite',
8720 7906 'gemini-1.5-pro',
8721 7907 'gemini-1.5-flash',
@@ -8727,33 +7913,36 @@
8727 7913 'grok-3-mini-beta',
8728 7914 'grok-3-mini-fast-beta',
8729 7915 'grok-2',
8730 7916 'deepseek-chat',
8731 - 'claude-opus-4-6',
8732 - 'claude-sonnet-4-6',
8733 - 'claude-opus-4-5',
8734 7917 'claude-sonnet-4-5-20250929',
8735 7918 'claude-opus-4-1-20250805',
8736 7919 'claude-haiku-4-5-20251001',
8737 7920 'claude-opus-4-20250514',
8738 7921 'claude-sonnet-4-20250514',
7922 + 'claude-3-7-sonnet-20250219',
7923 + // REMOVED: 'claude-3-5-sonnet-20241022',
7924 + 'claude-3-opus-20240229',
7925 + 'claude-3-sonnet-20240229',
7926 + 'claude-3-haiku-20240307',
8739 7927 'gpt-5.2',
8740 - 'gpt-5.1-chat-latest',
8741 7928 'gpt-5.1-2025-11-13',
8742 7929 'gpt-5',
8743 7930 'gpt-5-mini',
8744 7931 'gpt-5-nano',
8745 - 'gpt-5.4',
8746 - 'gpt-5.4-mini',
8747 - 'gpt-5.4-nano',
8748 - 'gpt-5.3-chat-latest',
7932 + 'gpt-4.1-2025-04-14',
7933 + 'gpt-4o',
7934 + 'gpt-4o-mini',
7935 + 'gpt-4-turbo',
7936 + 'gpt-4',
7937 + 'gpt-3.5-turbo',
8749 7938 );
8750 -
7939 +
8751 7940 if (in_array($input['model'], $allowed_models)) {
8752 7941 $new_input['model'] = sanitize_text_field($input['model']);
8753 7942 } else {
8754 7943 // Fallback for any deprecated model
8755 - $new_input['model'] = 'gpt-5.1-chat-latest';
7944 + $new_input['model'] = 'claude-3-7-sonnet-20250219';
8756 7945 }
8757 7946 }
8758 7947 }
8759 7948
@@ -8771,9 +7960,16 @@
8771 7960 $new_input['openrouter_api_key'] = sanitize_text_field($input['openrouter_api_key']);
8772 7961 }
8773 7962
8774 7963
7964 + if (isset($input['close_button_color'])) {
7965 + $new_input['close_button_color'] = sanitize_hex_color($input['close_button_color']);
7966 + }
8775 7967
7968 + if (isset($input['chatbot_bg_color'])) {
7969 + $new_input['chatbot_bg_color'] = sanitize_hex_color($input['chatbot_bg_color']);
7970 + }
7971 +
8776 7972 if (isset($input['woocommerce_consumer_key'])) {
8777 7973 $new_input['woocommerce_consumer_key'] = sanitize_text_field($input['woocommerce_consumer_key']);
8778 7974 }
8779 7975
@@ -8780,9 +7976,76 @@
8780 7976 if (isset($input['woocommerce_consumer_secret'])) {
8781 7977 $new_input['woocommerce_consumer_secret'] = sanitize_text_field($input['woocommerce_consumer_secret']);
8782 7978 }
8783 7979
7980 + if (isset($input['user_message_bg_color'])) {
7981 + $new_input['user_message_bg_color'] = sanitize_hex_color($input['user_message_bg_color']);
7982 + }
8784 7983
7984 + if (isset($input['user_message_font_color'])) {
7985 + $new_input['user_message_font_color'] = sanitize_hex_color($input['user_message_font_color']);
7986 + }
7987 +
7988 + if (isset($input['bot_message_bg_color'])) {
7989 + $new_input['bot_message_bg_color'] = sanitize_hex_color($input['bot_message_bg_color']);
7990 + }
7991 +
7992 + if (isset($input['bot_message_font_color'])) {
7993 + $new_input['bot_message_font_color'] = sanitize_hex_color($input['bot_message_font_color']);
7994 + }
7995 +
7996 + if (isset($input['live_agent_message_bg_color'])) {
7997 + $new_input['live_agent_message_bg_color'] = sanitize_hex_color($input['live_agent_message_bg_color']);
7998 + }
7999 +
8000 + if (isset($input['live_agent_message_font_color'])) {
8001 + $new_input['live_agent_message_font_color'] = sanitize_hex_color($input['live_agent_message_font_color']);
8002 + }
8003 +
8004 + if (isset($input['mode_indicator_bg_color'])) {
8005 + $new_input['mode_indicator_bg_color'] = sanitize_hex_color($input['mode_indicator_bg_color']);
8006 + }
8007 +
8008 + if (isset($input['mode_indicator_font_color'])) {
8009 + $new_input['mode_indicator_font_color'] = sanitize_hex_color($input['mode_indicator_font_color']);
8010 + }
8011 +
8012 + if (isset($input['toolbar_icon_color'])) {
8013 + $new_input['toolbar_icon_color'] = sanitize_hex_color($input['toolbar_icon_color']);
8014 + }
8015 +
8016 + if (isset($input['top_bar_bg_color'])) {
8017 + $new_input['top_bar_bg_color'] = sanitize_hex_color($input['top_bar_bg_color']);
8018 + }
8019 +
8020 + if (isset($input['quick_questions_toggle_color'])) {
8021 + $new_input['quick_questions_toggle_color'] = sanitize_hex_color($input['quick_questions_toggle_color']);
8022 + }
8023 +
8024 + if (isset($input['send_button_font_color'])) {
8025 + $new_input['send_button_font_color'] = sanitize_hex_color($input['send_button_font_color']);
8026 + }
8027 +
8028 + if (isset($input['chatbot_background_color'])) {
8029 + $new_input['chatbot_background_color'] = sanitize_hex_color($input['chatbot_background_color']);
8030 + }
8031 +
8032 + if (isset($input['icon_color'])) {
8033 + $new_input['icon_color'] = sanitize_hex_color($input['icon_color']);
8034 + }
8035 +
8036 + if (isset($input['custom_icon'])) {
8037 + $new_input['custom_icon'] = esc_url_raw($input['custom_icon']);
8038 + }
8039 +
8040 + if (isset($input['title_icon'])) {
8041 + $new_input['title_icon'] = esc_url_raw($input['title_icon']);
8042 + }
8043 +
8044 + if (isset($input['chat_input_font_color'])) {
8045 + $new_input['chat_input_font_color'] = sanitize_hex_color($input['chat_input_font_color']);
8046 + }
8047 +
8785 8048 // Sanitize link_target_toggle
8786 8049 if (isset($input['link_target_toggle'])) {
8787 8050 $new_input['link_target_toggle'] = $input['link_target_toggle'] === 'on' ? 'on' : 'off';
8788 8051 }
@@ -8915,226 +8178,12 @@
8915 8178 if (isset($input['live_agent_notification_message'])) {
8916 8179 $new_input['live_agent_notification_message'] = sanitize_textarea_field($input['live_agent_notification_message']);
8917 8180 }
8918 8181
8919 - // Telegram Integration
8920 - if (isset($input['telegram_status'])) {
8921 - $new_input['telegram_status'] = ($input['telegram_status'] === 'on') ? 'on' : 'off';
8922 - }
8923 - if (isset($input['telegram_bot_token'])) {
8924 - $new_input['telegram_bot_token'] = sanitize_text_field($input['telegram_bot_token']);
8925 - }
8926 - if (isset($input['telegram_group_id'])) {
8927 - $new_input['telegram_group_id'] = sanitize_text_field($input['telegram_group_id']);
8928 - }
8929 - if (isset($input['telegram_webhook_secret'])) {
8930 - $new_input['telegram_webhook_secret'] = sanitize_text_field($input['telegram_webhook_secret']);
8931 - }
8932 - if (isset($input['telegram_notification_message'])) {
8933 - $new_input['telegram_notification_message'] = sanitize_textarea_field($input['telegram_notification_message']);
8934 - }
8935 - if (isset($input['telegram_away_message'])) {
8936 - $new_input['telegram_away_message'] = sanitize_textarea_field($input['telegram_away_message']);
8937 - }
8938 -
8939 - // Sanitize script loading strategy
8940 - if (isset($input['script_loading_strategy'])) {
8941 - $allowed_strategies = array('default', 'defer', 'delay_1s', 'delay_3s', 'delay_5s', 'on_interaction');
8942 - $new_input['script_loading_strategy'] = in_array($input['script_loading_strategy'], $allowed_strategies)
8943 - ? $input['script_loading_strategy']
8944 - : 'default';
8945 - }
8946 -
8947 - // Sanitize debug mode
8948 - if (isset($input['debug_mode'])) {
8949 - $new_input['debug_mode'] = ($input['debug_mode'] === 'on') ? 'on' : 'off';
8950 - }
8951 -
8952 - // Content Generator Settings
8953 - if (isset($input['content_model'])) {
8954 - $new_input['content_model'] = sanitize_text_field($input['content_model']);
8955 - }
8956 - if (isset($input['content_image_model'])) {
8957 - $new_input['content_image_model'] = sanitize_text_field($input['content_image_model']);
8958 - }
8959 - if (isset($input['content_enable_images'])) {
8960 - $new_input['content_enable_images'] = ($input['content_enable_images'] === 'on') ? 'on' : 'off';
8961 - }
8962 - if (isset($input['content_use_placeholders'])) {
8963 - $new_input['content_use_placeholders'] = ($input['content_use_placeholders'] === 'on') ? 'on' : 'off';
8964 - }
8965 - if (isset($input['content_internal_linking'])) {
8966 - $new_input['content_internal_linking'] = ($input['content_internal_linking'] === 'on') ? 'on' : 'off';
8967 - }
8968 - if (isset($input['content_tool_use'])) {
8969 - $new_input['content_tool_use'] = ($input['content_tool_use'] === 'on') ? 'on' : 'off';
8970 - }
8971 -
8972 - // SEO Optimize toggle fields
8973 - foreach (array('seo_optimize_meta_desc', 'seo_optimize_seo_title', 'seo_optimize_slug', 'seo_optimize_readability', 'seo_optimize_internal_links', 'seo_optimize_img_alt', 'seo_optimize_featured_img') as $seo_key) {
8974 - if (isset($input[$seo_key])) {
8975 - $new_input[$seo_key] = ($input[$seo_key] === 'on') ? 'on' : 'off';
8976 - }
8977 - }
8978 -
8979 - // Preserve content generator settings when saving from main settings page
8980 - // (where content fields are not in the form submission)
8981 - $existing = get_option('mxchat_options', array());
8982 - foreach (array('content_model', 'content_image_model', 'content_enable_images', 'content_use_placeholders', 'content_internal_linking', 'content_tool_use', 'seo_optimize_meta_desc', 'seo_optimize_seo_title', 'seo_optimize_slug', 'seo_optimize_readability', 'seo_optimize_internal_links', 'seo_optimize_img_alt', 'seo_optimize_featured_img') as $key) {
8983 - if (!isset($new_input[$key]) && isset($existing[$key])) {
8984 - $new_input[$key] = $existing[$key];
8985 - }
8986 - }
8987 -
8988 8182 return $new_input;
8989 8183 }
8990 8184
8991 -/**
8992 - * Log a debug message if debug mode is enabled
8993 - *
8994 - * @param string $type The type of log entry (settings_save, api_error, activation, etc.)
8995 - * @param string $message The log message
8996 - * @param array $data Optional additional data to log
8997 - * @return bool Whether the message was logged
8998 - */
8999 -public static function mxchat_log_debug( $type, $message, $data = array() ) {
9000 - $options = get_option( 'mxchat_options', array() );
9001 8185
9002 - // Check if debug mode is enabled
9003 - if ( ! isset( $options['debug_mode'] ) || $options['debug_mode'] !== 'on' ) {
9004 - return false;
9005 - }
9006 -
9007 - // Get current log
9008 - $log = get_option( 'mxchat_debug_log', array() );
9009 - if ( ! is_array( $log ) ) {
9010 - $log = array();
9011 - }
9012 -
9013 - // Add new entry
9014 - $entry = array(
9015 - 'time' => current_time( 'Y-m-d H:i:s' ),
9016 - 'type' => sanitize_key( $type ),
9017 - 'message' => sanitize_text_field( $message ),
9018 - );
9019 -
9020 - if ( ! empty( $data ) ) {
9021 - $entry['data'] = $data;
9022 - }
9023 -
9024 - // Add to beginning of array (newest first)
9025 - array_unshift( $log, $entry );
9026 -
9027 - // Keep only last 100 entries
9028 - if ( count( $log ) > 100 ) {
9029 - $log = array_slice( $log, 0, 100 );
9030 - }
9031 -
9032 - // Save log
9033 - update_option( 'mxchat_debug_log', $log, false );
9034 -
9035 - return true;
9036 -}
9037 -
9038 -/**
9039 - * Get the debug log entries
9040 - *
9041 - * @return array The debug log entries
9042 - */
9043 -public static function mxchat_get_debug_log() {
9044 - $log = get_option( 'mxchat_debug_log', array() );
9045 - return is_array( $log ) ? $log : array();
9046 -}
9047 -
9048 -/**
9049 - * Clear the debug log
9050 - *
9051 - * @return bool Whether the log was cleared
9052 - */
9053 -public static function mxchat_clear_debug_log() {
9054 - return delete_option( 'mxchat_debug_log' );
9055 -}
9056 -
9057 -/**
9058 - * Export settings as JSON with masked API keys
9059 - *
9060 - * @return array The sanitized settings array
9061 - */
9062 -public static function mxchat_export_settings() {
9063 - $options = get_option( 'mxchat_options', array() );
9064 -
9065 - if ( ! is_array( $options ) ) {
9066 - return array();
9067 - }
9068 -
9069 - // List of API key fields to mask
9070 - $api_key_fields = array(
9071 - 'api_key',
9072 - 'xai_api_key',
9073 - 'claude_api_key',
9074 - 'deepseek_api_key',
9075 - 'voyage_api_key',
9076 - 'gemini_api_key',
9077 - 'openrouter_api_key',
9078 - 'loops_api_key',
9079 - 'brave_api_key',
9080 - 'live_agent_secret_key',
9081 - 'live_agent_bot_token',
9082 - 'telegram_bot_token',
9083 - 'telegram_webhook_secret',
9084 - 'woocommerce_consumer_key',
9085 - 'woocommerce_consumer_secret',
9086 - );
9087 -
9088 - // Mask API keys (show only last 4 characters)
9089 - foreach ( $api_key_fields as $field ) {
9090 - if ( isset( $options[ $field ] ) && ! empty( $options[ $field ] ) ) {
9091 - $value = $options[ $field ];
9092 - if ( strlen( $value ) > 4 ) {
9093 - $options[ $field ] = str_repeat( '*', strlen( $value ) - 4 ) . substr( $value, -4 );
9094 - } else {
9095 - $options[ $field ] = '****';
9096 - }
9097 - }
9098 - }
9099 -
9100 - // Add metadata
9101 - $export = array(
9102 - 'plugin_version' => defined( 'MXCHAT_VERSION' ) ? MXCHAT_VERSION : 'unknown',
9103 - 'export_date' => current_time( 'Y-m-d H:i:s' ),
9104 - 'wordpress_version' => get_bloginfo( 'version' ),
9105 - 'php_version' => phpversion(),
9106 - 'settings' => $options,
9107 - );
9108 -
9109 - return $export;
9110 -}
9111 -
9112 -/**
9113 - * Reset all settings to defaults
9114 - *
9115 - * @return bool Whether the reset was successful
9116 - */
9117 -public static function mxchat_reset_all_settings() {
9118 - // Delete main options
9119 - $deleted = delete_option( 'mxchat_options' );
9120 -
9121 - // Also clear the debug log
9122 - delete_option( 'mxchat_debug_log' );
9123 -
9124 - // Log the reset (will create new log since we just cleared it)
9125 - // We need to temporarily enable debug mode to log this
9126 - $temp_options = array( 'debug_mode' => 'on' );
9127 - update_option( 'mxchat_options', $temp_options );
9128 -
9129 - self::mxchat_log_debug( 'reset', 'All settings have been reset to defaults' );
9130 -
9131 - // Now delete again to trigger re-initialization
9132 - delete_option( 'mxchat_options' );
9133 -
9134 - return $deleted;
9135 -}
9136 -
9137 8186 // Method to append the chatbot to the body
9138 8187 public function mxchat_append_chatbot_to_body() {
9139 8188 $options = get_option('mxchat_options');
9140 8189 if (isset($options['append_to_body']) && $options['append_to_body'] === 'on') {
@@ -9204,65 +8253,73 @@
9204 8253 //error_log('DEBUG: Permission check failed');
9205 8254 wp_die(__('You do not have sufficient permissions to delete all prompts.', 'mxchat'));
9206 8255 }
9207 8256
9208 - // Get bot_id and content type filter from POST data
8257 + // Get bot_id from POST data
9209 8258 $bot_id = isset($_POST['bot_id']) ? sanitize_text_field($_POST['bot_id']) : 'default';
9210 - $content_type_filter = isset($_POST['content_type_filter']) ? sanitize_text_field($_POST['content_type_filter']) : '';
9211 -
8259 + //error_log('DEBUG: Bot ID from POST: ' . $bot_id);
8260 +
9212 8261 $success = true;
9213 8262 $error_messages = array();
9214 -
8263 +
9215 8264 // Get bot-specific Pinecone configuration
9216 8265 $pinecone_manager = MxChat_Pinecone_Manager::get_instance();
9217 8266 $pinecone_options = $pinecone_manager->mxchat_get_bot_pinecone_options($bot_id);
9218 -
8267 +
8268 + //error_log('DEBUG: Pinecone options retrieved:');
8269 + //error_log(' - Use Pinecone: ' . ($pinecone_options['mxchat_use_pinecone'] ?? 'NOT SET'));
8270 + //error_log(' - API Key: ' . (empty($pinecone_options['mxchat_pinecone_api_key']) ? 'EMPTY' : 'SET'));
8271 + //error_log(' - Host: ' . ($pinecone_options['mxchat_pinecone_host'] ?? 'NOT SET'));
8272 +
9219 8273 $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
9220 -
8274 +
9221 8275 if ($use_pinecone && !empty($pinecone_options['mxchat_pinecone_api_key'])) {
9222 - // Delete from Pinecone (with optional content type filter)
9223 - $result = $pinecone_manager->mxchat_delete_all_from_pinecone($pinecone_options, $content_type_filter);
9224 -
8276 + //error_log('[MXCHAT-DELETE] Pinecone is enabled for bot ' . $bot_id . ' - deleting all from Pinecone');
8277 +
8278 + // Delete from bot-specific Pinecone index
8279 + $result = $pinecone_manager->mxchat_delete_all_from_pinecone($pinecone_options);
8280 +
8281 + //error_log('DEBUG: Delete all result: ' . ($result['success'] ? 'SUCCESS' : 'FAILED'));
9225 8282 if (!$result['success']) {
8283 + //error_log('DEBUG: Delete all error: ' . $result['message']);
8284 + }
8285 +
8286 + if (!$result['success']) {
9226 8287 $success = false;
9227 8288 $error_messages[] = $result['message'];
9228 8289 }
9229 -
8290 +
8291 + // No cache clearing needed since we removed caching
8292 +
9230 8293 } else {
8294 + //error_log('[MXCHAT-DELETE] Pinecone not enabled for bot ' . $bot_id . ' - deleting from WordPress database');
8295 +
9231 8296 // Delete from WordPress database
9232 8297 global $wpdb;
9233 8298 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
9234 -
9235 - // Build WHERE conditions
9236 - $where_clauses = array();
9237 - $where_values = array();
9238 -
9239 - // Bot filter
8299 +
8300 + // If multi-bot is active and we have a specific bot, delete only that bot's content
9240 8301 if ($bot_id !== 'default' && class_exists('MxChat_Multi_Bot_Manager')) {
9241 - $where_clauses[] = 'bot_id = %s';
9242 - $where_values[] = $bot_id;
9243 - }
9244 -
9245 - // Content type filter
9246 - if (!empty($content_type_filter)) {
9247 - $where_clauses[] = 'content_type = %s';
9248 - $where_values[] = $content_type_filter;
9249 - }
9250 -
9251 - if (!empty($where_clauses)) {
9252 - $where_sql = implode(' AND ', $where_clauses);
9253 - $result = $wpdb->query($wpdb->prepare("DELETE FROM {$table_name} WHERE {$where_sql}", $where_values));
8302 + $result = $wpdb->delete(
8303 + $table_name,
8304 + array('bot_id' => $bot_id),
8305 + array('%s')
8306 + );
9254 8307 } else {
9255 - // No filters — delete all
8308 + // Delete all content for default bot
9256 8309 $result = $wpdb->query("DELETE FROM {$table_name}");
9257 8310 }
9258 -
8311 +
9259 8312 if ($result === false) {
9260 8313 $success = false;
9261 8314 $error_messages[] = 'Failed to delete from WordPress database';
9262 8315 }
9263 8316 }
8317 +
8318 + // No cache clearing needed since we removed caching
9264 8319
8320 + //error_log('=== DELETE ALL DEBUG END ===');
8321 +
9265 8322 // Redirect back with a success message and bot_id
9266 8323 $redirect_url = add_query_arg(array(
9267 8324 'page' => 'mxchat-prompts',
9268 8325 'bot_id' => $bot_id,
@@ -9567,9 +8624,8 @@
9567 8624 if ($embedding_dimensions !== 1536) {
9568 8625 //error_log('[MXCHAT-EMBED] Warning: Unexpected Gemini embedding dimensions: ' . $embedding_dimensions);
9569 8626 }
9570 8627
9571 - MxChat_Utils::stamp_active_embedding_model($selected_model);
9572 8628 return $response_data['embedding']['values'];
9573 8629 } else {
9574 8630 //error_log('[MXCHAT-EMBED] Error: No embedding found in Gemini response');
9575 8631 //error_log('[MXCHAT-EMBED] Response structure: ' . wp_json_encode(array_keys($response_data)));
@@ -9595,9 +8651,8 @@
9595 8651 ($selected_model === 'voyage-3-large' && $embedding_dimensions !== 2048)) {
9596 8652 //error_log('[MXCHAT-EMBED] Warning: Unexpected embedding dimensions');
9597 8653 }
9598 8654
9599 - MxChat_Utils::stamp_active_embedding_model($selected_model);
9600 8655 return $response_data['data'][0]['embedding'];
9601 8656 } else {
9602 8657 //error_log('[MXCHAT-EMBED] Error: No embedding found in response');
9603 8658 //error_log('[MXCHAT-EMBED] Response structure: ' . wp_json_encode(array_keys($response_data)));