PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 2.5.8
MxChat – AI Chatbot & Content Generation for WordPress v2.5.8
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 +2844 -4028 3.2.42.5.8 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,29 @@
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 - 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 54 }
106 55
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 56 private function is_license_active() {
57 + // Get the raw value without translation
239 58 $license_status = get_option('mxchat_license_status', 'inactive');
240 - return ($license_status === 'active');
59 +
60 + // Check against multiple possible values, bypassing translation issues
61 + return ($license_status === 'active' || $license_status === esc_html__('active', 'mxchat'));
241 62 }
242 63
243 64 private function initialize_default_options() {
244 65 $default_options = array(
@@ -247,10 +68,9 @@
247 68 'claude_api_key' => '',
248 69 'deepseek_api_key' => '',
249 70 'voyage_api_key' => '',
250 71 'gemini_api_key' => '',
251 - 'enable_streaming_toggle' => 'off',
252 - 'enable_web_search' => 'off',
72 + 'enable_streaming_toggle' => 'on',
253 73 'embedding_model' => 'text-embedding-ada-002',
254 74 '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 75
256 76 # Response Style - CRITICALLY IMPORTANT
@@ -269,9 +89,9 @@
269 89 - 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 90 - NEVER invent or hallucinate URLs, links, product specs, procedures, dates, statistics, names, contacts, or company information
271 91 - When knowledge base information is unclear or contradictory, acknowledge the limitation rather than guessing
272 92 - Better to admit insufficient information than provide inaccurate answers',
273 - 'model' => esc_html__('gpt-5.1-chat-latest', 'mxchat'),
93 + 'model' => esc_html__('gpt-4o', 'mxchat'),
274 94 'rate_limit_logged_out' => esc_html__('100', 'mxchat'),
275 95 'role_rate_limits' => array(),
276 96 'rate_limit_message' => esc_html__('Rate limit exceeded. Please try again later.', 'mxchat'),
277 97 'enable_email_block' => '',
@@ -283,12 +103,9 @@
283 103 'intro_message' => __('Hello! How can I assist you today?', 'mxchat'),
284 104 'ai_agent_text' => esc_html__('AI Agent', 'mxchat'),
285 105 'input_copy' => esc_html__('How can I assist?', 'mxchat'),
286 106 'append_to_body' => esc_html__('off', 'mxchat'),
287 - 'post_type_visibility_mode' => 'all', // 'all', 'include', 'exclude'
288 - 'post_type_visibility_list' => array(), // Array of post type slugs
289 107 'contextual_awareness_toggle' => 'off',
290 - 'citation_links_toggle' => 'on',
291 108 'close_button_color' => esc_html__('#fff', 'mxchat'),
292 109 'chatbot_bg_color' => esc_html__('#fff', 'mxchat'),
293 110 'user_message_bg_color' => esc_html__('#fff', 'mxchat'),
294 111 'user_message_font_color' => esc_html__('#212121', 'mxchat'),
@@ -317,33 +134,18 @@
317 134 'pdf_max_pages' => 69,
318 135 'show_pdf_upload_button' => 'on',
319 136 'show_word_upload_button' => 'on',
320 137
321 - // Live Agent Integration (Slack)
138 + // Live Agent Integration
322 139 'live_agent_webhook_url' => '',
323 140 'live_agent_secret_key' => '',
324 141 'live_agent_bot_token' => '',
325 142 'live_agent_message_bg_color' => esc_html__('#ffffff', 'mxchat'),
326 143 '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 144 'chat_toolbar_toggle' => esc_html__('off', 'mxchat'),
337 145 'mode_indicator_bg_color' => esc_html__('#767676', 'mxchat'),
338 146 'mode_indicator_font_color' => esc_html__('#ffffff', 'mxchat'),
339 147 '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 148 );
347 149
348 150
349 151 // Merge existing options with defaults
@@ -378,18 +180,8 @@
378 180 'dashicons-testimonial',
379 181 6
380 182 );
381 183
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 184 // Submenu page for Knowledge
393 185 add_submenu_page(
394 186 'mxchat-max',
395 187 esc_html__('Prompts', 'mxchat'),
@@ -416,23 +208,22 @@
416 208 'mxchat-actions',
417 209 array($this, 'mxchat_actions_page_html')
418 210 );
419 211
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 - );
212 + add_submenu_page(
213 + 'mxchat-max',
214 + esc_html__('Add Ons', 'mxchat'),
215 + esc_html__('Add Ons', 'mxchat'),
216 + 'manage_options',
217 + 'mxchat-addons',
218 + array($this, 'mxchat_create_addons_page')
219 + );
429 220
430 - // Consolidated Pro & Extensions page (replaces separate Add Ons and Pro Upgrade pages)
221 + // Submenu page for Activation Key
431 222 add_submenu_page(
432 223 'mxchat-max',
433 - esc_html__('Pro & Extensions', 'mxchat'),
434 - esc_html__('Pro & Extensions', 'mxchat'),
224 + esc_html__('Pro Upgrade', 'mxchat'),
225 + esc_html__('Pro Upgrade', 'mxchat'),
435 226 'manage_options',
436 227 'mxchat-activation',
437 228 array($this, 'mxchat_create_activation_page')
438 229 );
@@ -444,16 +235,8 @@
444 235 $addons_page->render_page();
445 236 }
446 237
447 238 /**
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 239 * Test actual streaming functionality in the WordPress environment
457 240 */
458 241 public function mxchat_handle_test_streaming_actual() {
459 242 check_ajax_referer('mxchat_test_streaming_nonce', 'nonce');
@@ -471,9 +254,9 @@
471 254 }
472 255
473 256 // Get user's selected model and API key
474 257 $options = get_option('mxchat_options', []);
475 - $selected_model = $options['model'] ?? 'gpt-5.1-chat-latest';
258 + $selected_model = $options['model'] ?? 'gpt-4o';
476 259
477 260 // Get the provider from the model
478 261 $model_parts = explode('-', $selected_model);
479 262 $provider = strtolower($model_parts[0]);
@@ -742,236 +525,8 @@
742 525
743 526 return $sanitized;
744 527 }
745 528
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 529 public function mxchat_display_admin_notice() {
975 530 // Success notice
976 531 if ($message = get_transient('mxchat_admin_notice_success')) {
977 532 ?>
@@ -1018,13 +573,282 @@
1018 573 }
1019 574 }
1020 575 public function mxchat_create_admin_page() {
1021 576 $this->add_live_agent_nonce();
1022 - $this->add_theme_migration_nonce();
1023 577
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);
578 + ?>
579 + <div class="wrap mxchat-wrapper">
580 + <!-- Hero Section -->
581 + <div class="mxchat-hero">
582 + <h1 class="mxchat-main-title">
583 + <span class="mxchat-gradient-text">MxChat</span> Settings
584 + </h1>
585 + <p class="mxchat-hero-subtitle">
586 + <?php esc_html_e('Configure your AI chatbot, manage integrations and explore tutorials to get the most out of MxChat.', 'mxchat'); ?>
587 + </p>
588 + </div>
589 +
590 + <div class="mxchat-content">
591 +
592 + <?php if (!$this->is_activated): ?>
593 + <div class="mxchat-pro-card">
594 + <div class="mxchat-pro-notification">
595 + <div class="mxchat-pro-content">
596 + <h3>🚀 Limited Lifetime Offer: Save 15% on MxChat Pro, Agency, or Agency Plus!</h3>
597 + <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>
598 + <div class="mxchat-pro-cta">
599 + <a href="https://mxchat.ai/" target="_blank" class="mxchat-button"><?php echo esc_html__('Upgrade Today', 'mxchat'); ?></a>
600 + <a href="<?php echo admin_url('admin.php?page=mxchat-addons'); ?>" class="mxchat-link"><?php echo esc_html__('Preview Add-ons', 'mxchat'); ?></a>
601 + </div>
602 + </div>
603 + </div>
604 + <?php endif; ?>
605 +
606 + <?php
607 + $this->show_live_agent_disabled_banner();
608 + ?>
609 +
610 + <!-- Tabs Navigation -->
611 + <div class="mxchat-tabs">
612 + <button class="mxchat-tab-button active" data-tab="chatbot"><?php echo esc_html__('Chatbot', 'mxchat'); ?></button>
613 + <button class="mxchat-tab-button" data-tab="api-keys"><?php echo esc_html__('API Keys', 'mxchat'); ?></button>
614 + <button class="mxchat-tab-button" data-tab="embed"><?php echo esc_html__('Toolbar & Components', 'mxchat'); ?></button>
615 + <button class="mxchat-tab-button" data-tab="general"><?php echo esc_html__('YouTube Tutorials', 'mxchat'); ?></button>
616 + </div>
617 +
618 + <!-- Tab Contents -->
619 + <div id="chatbot" class="mxchat-tab-content active">
620 + <div class="mxchat-card">
621 + <div class="mxchat-autosave-section">
622 + <?php do_settings_sections('mxchat-chatbot'); ?>
623 + </div>
624 + </div>
625 + </div>
626 +
627 + <div id="api-keys" class="mxchat-tab-content">
628 + <div class="mxchat-card">
629 + <div class="mxchat-autosave-section">
630 + <table class="form-table">
631 + <?php do_settings_fields('mxchat-api-keys', 'mxchat_api_keys_section'); ?>
632 + </table>
633 + </div>
634 + </div>
635 + </div>
636 +
637 + <div id="embed" class="mxchat-tab-content">
638 +
639 + <div class="mxchat-card">
640 + <h2><?php esc_html_e('Toolbar Settings', 'mxchat'); ?></h2>
641 + <div class="mxchat-autosave-section">
642 + <table class="form-table">
643 + <?php do_settings_fields('mxchat-embed', 'mxchat_pdf_intent_section'); ?>
644 + </table>
645 + </div>
646 + </div>
647 +
648 +
649 + <div class="mxchat-card">
650 + <h2><?php esc_html_e('Loops Settings', 'mxchat'); ?></h2>
651 + <div class="mxchat-autosave-section">
652 + <table class="form-table">
653 + <?php do_settings_fields('mxchat-embed', 'mxchat_loops_section'); ?>
654 + </table>
655 + </div>
656 + </div>
657 +
658 + <div class="mxchat-card">
659 + <h2><?php esc_html_e('Brave Search Settings', 'mxchat'); ?></h2>
660 + <div class="mxchat-autosave-section">
661 + <table class="form-table">
662 + <?php do_settings_fields('mxchat-embed', 'mxchat_brave_section'); ?>
663 + </table>
664 + </div>
665 + </div>
666 +
667 + <div class="mxchat-card">
668 + <h2><?php esc_html_e('Live Agent Settings', 'mxchat'); ?></h2>
669 + <div class="mxchat-autosave-section">
670 +<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">
671 + <?php do_settings_fields('mxchat-embed', 'mxchat_live_agent_section'); ?>
672 + </table>
673 + </div>
674 + </div>
675 + </div>
676 +
677 + <div id="general" class="mxchat-tab-content">
678 + <div class="mxchat-card">
679 + <?php do_settings_sections('mxchat-general'); ?>
680 + <div class="video-tutorials-section">
681 +
682 +
683 + <div class="support-notification">
684 + <div class="support-notification-icon">
685 + <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">
686 + <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"/>
687 + <path d="M12 9v4"/>
688 + <path d="M12 17h.01"/>
689 + </svg>
690 + </div>
691 + <div class="support-notification-content">
692 + <h3 class="support-notification-title"><?php echo esc_html__('Need Help? We\'re Here for You!', 'mxchat'); ?></h3>
693 + <p class="support-notification-message">
694 + <?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'); ?>
695 + </p>
696 + <a href="https://wordpress.org/support/plugin/mxchat-basic/" target="_blank" rel="noopener noreferrer" class="support-notification-button">
697 + <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">
698 + <path d="M14 9a2 2 0 0 1-2 2H6l-4 4V4c0-1.1.9-2 2-2h8a2 2 0 0 1 2 2v5Z"/>
699 + <path d="M18 9h2a2 2 0 0 1 2 2v11l-4-4h-6a2 2 0 0 1-2-2v-1"/>
700 + </svg>
701 + <?php echo esc_html__('Submit Support Ticket', 'mxchat'); ?>
702 + </a>
703 + </div>
704 +</div>
705 +
706 + <div class="tutorial-grid">
707 +
708 + <div class="tutorial-item">
709 + <h3><?php echo esc_html__('AI Theme Generator Tutorial', 'mxchat'); ?></h3>
710 + <div class="video-description">
711 + <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>
712 + <a href="https://www.youtube.com/watch?v=rSQDW2qbtRU&t" target="_blank" rel="noopener" class="video-link">
713 + <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>
714 + <?php echo esc_html__('Watch AI Theme Generator Tutorial', 'mxchat'); ?>
715 + </a>
716 + </div>
717 +</div>
718 +
719 +
720 + <div class="tutorial-item">
721 + <h3><?php echo esc_html__('MxChat Forms Tutorial', 'mxchat'); ?></h3>
722 + <div class="video-description">
723 + <p><?php echo esc_html__('Learn how to create and manage smart forms that automatically trigger during chat conversations.', 'mxchat'); ?></p>
724 + <a href="https://www.youtube.com/watch?v=3MrWy5dRalA" target="_blank" rel="noopener" class="video-link">
725 + <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>
726 + <?php echo esc_html__('Watch MxChat Forms Tutorial', 'mxchat'); ?>
727 + </a>
728 + </div>
729 + </div>
730 +
731 +<div class="tutorial-item">
732 + <h3><?php echo esc_html__('Admin Assistant Add-on', 'mxchat'); ?></h3>
733 + <div class="video-description">
734 + <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>
735 + <a href="https://youtu.be/AdEA1k-UCFM" target="_blank" rel="noopener" class="video-link">
736 + <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>
737 + <?php echo esc_html__('Watch Admin Assistant Tutorial', 'mxchat'); ?>
738 + </a>
739 + </div>
740 +</div>
741 +
742 + <div class="tutorial-item">
743 + <h3><?php echo esc_html__('Intent Tester Guide', 'mxchat'); ?></h3>
744 + <div class="video-description">
745 + <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>
746 + <a href="https://www.youtube.com/watch?v=uTr14tn59Hc" target="_blank" rel="noopener" class="video-link">
747 + <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>
748 + <?php echo esc_html__('Watch Intent Tester Tutorial', 'mxchat'); ?>
749 + </a>
750 + </div>
751 + </div>
752 +
753 + <div class="tutorial-item">
754 + <h3><?php echo esc_html__('Theme Customizer Add-on', 'mxchat'); ?></h3>
755 + <div class="video-description">
756 + <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>
757 + <a href="https://youtu.be/MfbB9mZi6ag" target="_blank" rel="noopener" class="video-link">
758 + <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>
759 + <?php echo esc_html__('Watch Theme Customizer Tutorial', 'mxchat'); ?>
760 + </a>
761 + </div>
762 + </div>
763 +
764 + <div class="tutorial-item">
765 + <h3><?php echo esc_html__('WooCommerce Integration', 'mxchat'); ?></h3>
766 + <div class="video-description">
767 + <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>
768 + <a href="https://www.youtube.com/watch?v=WsqAppHRGdA" target="_blank" rel="noopener" class="video-link">
769 + <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>
770 + <?php echo esc_html__('Watch WooCommerce Integration Tutorial', 'mxchat'); ?>
771 + </a>
772 + </div>
773 + </div>
774 +
775 + <div class="tutorial-item">
776 + <h3><?php echo esc_html__('Knowledge Base Setup', 'mxchat'); ?></h3>
777 + <div class="video-description">
778 + <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>
779 + <p><small><?php echo esc_html__('Note: This tutorial uses an older UI, but the process remains the same.', 'mxchat'); ?></small></p>
780 + <a href="https://www.youtube.com/watch?v=8Ztjs66-VTo" target="_blank" rel="noopener" class="video-link">
781 + <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>
782 + <?php echo esc_html__('Watch Knowledge Base Setup Tutorial', 'mxchat'); ?>
783 + </a>
784 + </div>
785 + </div>
786 +
787 + <div class="tutorial-item">
788 + <h3><?php echo esc_html__('Toolbar Chat with Documents', 'mxchat'); ?></h3>
789 + <div class="video-description">
790 + <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>
791 + <a href="https://www.youtube.com/watch?v=j_c45WWCTG0" target="_blank" rel="noopener" class="video-link">
792 + <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>
793 + <?php echo esc_html__('Watch Document Chat Tutorial', 'mxchat'); ?>
794 + </a>
795 + </div>
796 + </div>
797 +
798 + <div class="tutorial-item">
799 + <h3><?php echo esc_html__('Perplexity Integration', 'mxchat'); ?></h3>
800 + <div class="video-description">
801 + <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>
802 + <a href="https://youtu.be/wpKkbt24-bo" target="_blank" rel="noopener" class="video-link">
803 + <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>
804 + <?php echo esc_html__('Watch Perplexity Integration Tutorial', 'mxchat'); ?>
805 + </a>
806 + </div>
807 + </div>
808 +
809 + <div class="tutorial-item">
810 + <h3><?php echo esc_html__('Brave Search Intent', 'mxchat'); ?></h3>
811 + <div class="video-description">
812 + <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>
813 + <a href="https://www.youtube.com/watch?v=7vDL5H7vToc" target="_blank" rel="noopener" class="video-link">
814 + <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>
815 + <?php echo esc_html__('Watch Brave Search Intent Tutorial', 'mxchat'); ?>
816 + </a>
817 + </div>
818 + </div>
819 +
820 + <div class="tutorial-item">
821 + <h3><?php echo esc_html__('Loops Email Capture', 'mxchat'); ?></h3>
822 + <div class="video-description">
823 + <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>
824 + <p><small><?php echo esc_html__('Note: This tutorial uses an older UI, but the process remains the same.', 'mxchat'); ?></small></p>
825 + <a href="https://www.youtube.com/watch?v=CNgm5TYDyTc" target="_blank" rel="noopener" class="video-link">
826 + <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>
827 + <?php echo esc_html__('Watch Loops Email Capture Tutorial', 'mxchat'); ?>
828 + </a>
829 + </div>
830 + </div>
831 +
832 + <div class="tutorial-item">
833 + <h3><?php echo esc_html__('MxChat AI Agent Testing Service', 'mxchat'); ?></h3>
834 + <div class="video-description">
835 + <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>
836 + <p><small><?php echo esc_html__('Note: This tutorial uses an older UI, but the process remains the same.', 'mxchat'); ?></small></p>
837 + <a href="https://www.youtube.com/watch?v=A0jowbpyX54" target="_blank" rel="noopener" class="video-link">
838 + <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>
839 + <?php echo esc_html__('Watch AI Agent Testing Tutorial', 'mxchat'); ?>
840 + </a>
841 + </div>
842 + </div>
843 + </div>
844 +
845 + </div>
846 + </div>
847 + </div>
848 + </div>
849 + </div>
850 + <?php
1027 851 }
1028 852
1029 853 public function dismiss_live_agent_notice() {
1030 854 // Add debugging
@@ -1052,75 +876,16 @@
1052 876 ));
1053 877 }
1054 878 }
1055 879
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 880
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 881 public function mxchat_create_transcripts_page() {
1117 882 global $wpdb;
1118 883 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1119 884
1120 885 // 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;
886 + $total_chats = $wpdb->get_var("SELECT COUNT(DISTINCT session_id) FROM $table_name");
887 + $total_messages = $wpdb->get_var("SELECT COUNT(*) FROM $table_name");
1123 888
1124 889 // Count unique users with detailed breakdown
1125 890 $total_users = $wpdb->get_var("
1126 891 SELECT COUNT(DISTINCT
@@ -1258,89 +1023,298 @@
1258 1023 $chart_chats[] = 0;
1259 1024 $chart_messages[] = 0;
1260 1025 }
1261 1026 }
1027 + ?>
1028 + <div class="wrap mxchat-transcripts-wrapper">
1029 + <!-- Hero Section -->
1030 + <div class="mxchat-transcripts-hero">
1031 + <h1 class="mxchat-main-title">
1032 + Chat <span class="mxchat-gradient-text">Transcripts</span>
1033 + </h1>
1034 + <p class="mxchat-hero-subtitle">
1035 + <?php esc_html_e('Review and manage your chatbot conversations with detailed message history.', 'mxchat'); ?>
1036 + </p>
1037 + </div>
1038 + <div class="mxchat-content">
1039 + <!-- Enhanced Metrics Dashboard -->
1040 + <div class="mxchat-metrics-dashboard">
1041 + <!-- Tab Navigation -->
1042 + <div class="mxchat-metrics-tabs">
1043 + <button class="mxchat-metrics-tab active" data-tab="overview">
1044 + <span class="tab-icon">📊</span>
1045 + <span class="tab-label">Overview</span>
1046 + </button>
1047 + <button class="mxchat-metrics-tab" data-tab="activity">
1048 + <span class="tab-icon">📈</span>
1049 + <span class="tab-label">Activity</span>
1050 + </button>
1051 + <button class="mxchat-metrics-tab" data-tab="insights">
1052 + <span class="tab-icon">💡</span>
1053 + <span class="tab-label">Insights</span>
1054 + </button>
1055 + </div>
1056 +
1057 + <!-- Tab Content -->
1058 + <div class="mxchat-metrics-content">
1059 + <!-- Overview Tab -->
1060 + <div class="mxchat-metrics-panel active" data-panel="overview">
1061 + <div class="mxchat-stats-grid">
1062 + <div class="mxchat-stat-card mxchat-stat-primary">
1063 + <div class="stat-icon">💬</div>
1064 + <div class="stat-content">
1065 + <span class="stat-value"><?php echo esc_html($total_chats); ?></span>
1066 + <span class="stat-label"><?php esc_html_e('Total Chats', 'mxchat'); ?></span>
1067 + </div>
1068 + </div>
1069 + <div class="mxchat-stat-card mxchat-stat-primary">
1070 + <div class="stat-icon">📝</div>
1071 + <div class="stat-content">
1072 + <span class="stat-value"><?php echo esc_html($total_messages); ?></span>
1073 + <span class="stat-label"><?php esc_html_e('Total Messages', 'mxchat'); ?></span>
1074 + </div>
1075 + </div>
1076 + <div class="mxchat-stat-card mxchat-stat-primary">
1077 + <div class="stat-icon">👥</div>
1078 + <div class="stat-content">
1079 + <span class="stat-value"><?php echo esc_html($total_users); ?></span>
1080 + <span class="stat-label"><?php esc_html_e('Unique Users', 'mxchat'); ?></span>
1081 + <span class="stat-sublabel">
1082 + <?php
1083 + echo sprintf(
1084 + esc_html__('%d registered • %d guests • %d tests', 'mxchat'),
1085 + $registered_users,
1086 + $guest_users,
1087 + $agent_tests
1088 + );
1089 + ?>
1090 + </span>
1091 + </div>
1092 + </div>
1093 + <div class="mxchat-stat-card mxchat-stat-primary">
1094 + <div class="stat-icon">💭</div>
1095 + <div class="stat-content">
1096 + <span class="stat-value"><?php echo esc_html($avg_messages); ?></span>
1097 + <span class="stat-label"><?php esc_html_e('Avg Messages/Chat', 'mxchat'); ?></span>
1098 + </div>
1099 + </div>
1100 + </div>
1101 + </div>
1102 +
1103 + <!-- Activity Tab -->
1104 + <div class="mxchat-metrics-panel" data-panel="activity">
1105 + <div class="mxchat-activity-layout">
1106 + <div class="mxchat-activity-stats">
1107 + <div class="mxchat-stat-card mxchat-stat-accent">
1108 + <div class="stat-icon">📅</div>
1109 + <div class="stat-content">
1110 + <span class="stat-value"><?php echo esc_html($today_chats); ?></span>
1111 + <span class="stat-label"><?php esc_html_e('Today', 'mxchat'); ?></span>
1112 + </div>
1113 + </div>
1114 + <div class="mxchat-stat-card mxchat-stat-accent">
1115 + <div class="stat-icon">📆</div>
1116 + <div class="stat-content">
1117 + <span class="stat-value"><?php echo esc_html($week_chats); ?></span>
1118 + <span class="stat-label"><?php esc_html_e('Last 7 Days', 'mxchat'); ?></span>
1119 + </div>
1120 + </div>
1121 + <div class="mxchat-stat-card mxchat-stat-accent">
1122 + <div class="stat-icon">🗓️</div>
1123 + <div class="stat-content">
1124 + <span class="stat-value"><?php echo esc_html($month_chats); ?></span>
1125 + <span class="stat-label"><?php esc_html_e('Last 30 Days', 'mxchat'); ?></span>
1126 + </div>
1127 + </div>
1128 + </div>
1129 +
1130 + <div class="mxchat-chart-container">
1131 + <h3 class="mxchat-chart-title"><?php esc_html_e('7-Day Activity Trend', 'mxchat'); ?></h3>
1132 + <canvas id="mxchat-activity-chart"></canvas>
1133 + </div>
1134 + </div>
1135 + </div>
1136 +
1137 + <!-- Insights Tab -->
1138 + <div class="mxchat-metrics-panel" data-panel="insights">
1139 + <div class="mxchat-insights-grid">
1140 + <div class="mxchat-insight-card">
1141 + <div class="insight-header">
1142 + <span class="insight-icon">🕐</span>
1143 + <h3><?php esc_html_e('Peak Activity', 'mxchat'); ?></h3>
1144 + </div>
1145 + <div class="insight-content">
1146 + <div class="insight-value">
1147 + <?php
1148 + if ($busiest_hour) {
1149 + $hour = $busiest_hour->hour;
1150 + $formatted_hour = date('g A', strtotime("$hour:00"));
1151 + echo esc_html($formatted_hour);
1152 + } else {
1153 + echo esc_html__('N/A', 'mxchat');
1154 + }
1155 + ?>
1156 + </div>
1157 + <div class="insight-description">
1158 + <?php esc_html_e('Most active hour of the day', 'mxchat'); ?>
1159 + </div>
1160 + </div>
1161 + </div>
1162 +
1163 + <div class="mxchat-insight-card">
1164 + <div class="insight-header">
1165 + <span class="insight-icon">📊</span>
1166 + <h3><?php esc_html_e('Engagement Rate', 'mxchat'); ?></h3>
1167 + </div>
1168 + <div class="insight-content">
1169 + <div class="insight-value">
1170 + <?php
1171 + $engagement_rate = $total_chats > 0 ? round(($total_messages / $total_chats), 1) : 0;
1172 + echo esc_html($engagement_rate);
1173 + ?>
1174 + </div>
1175 + <div class="insight-description">
1176 + <?php esc_html_e('Messages exchanged per chat', 'mxchat'); ?>
1177 + </div>
1178 + </div>
1179 + </div>
1180 +
1181 + <div class="mxchat-insight-card">
1182 + <div class="insight-header">
1183 + <span class="insight-icon">👤</span>
1184 + <h3><?php esc_html_e('User Distribution', 'mxchat'); ?></h3>
1185 + </div>
1186 + <div class="insight-content">
1187 + <div class="insight-breakdown">
1188 + <div class="breakdown-item">
1189 + <span class="breakdown-label"><?php esc_html_e('Registered', 'mxchat'); ?></span>
1190 + <span class="breakdown-value">
1191 + <?php
1192 + $registered_pct = $total_users > 0 ? round(($registered_users / $total_users) * 100) : 0;
1193 + echo esc_html($registered_pct . '%');
1194 + ?>
1195 + </span>
1196 + </div>
1197 + <div class="breakdown-item">
1198 + <span class="breakdown-label"><?php esc_html_e('Guests', 'mxchat'); ?></span>
1199 + <span class="breakdown-value">
1200 + <?php
1201 + $guest_pct = $total_users > 0 ? round(($guest_users / $total_users) * 100) : 0;
1202 + echo esc_html($guest_pct . '%');
1203 + ?>
1204 + </span>
1205 + </div>
1206 + </div>
1207 + </div>
1208 + </div>
1209 +
1210 + <div class="mxchat-insight-card">
1211 + <div class="insight-header">
1212 + <span class="insight-icon">⚡</span>
1213 + <h3><?php esc_html_e('Activity Status', 'mxchat'); ?></h3>
1214 + </div>
1215 + <div class="insight-content">
1216 + <div class="insight-value insight-status">
1217 + <?php
1218 + if ($today_chats > 0) {
1219 + echo '<span class="status-badge status-active">' . esc_html__('Active', 'mxchat') . '</span>';
1220 + } else if ($week_chats > 0) {
1221 + echo '<span class="status-badge status-moderate">' . esc_html__('Moderate', 'mxchat') . '</span>';
1222 + } else {
1223 + echo '<span class="status-badge status-quiet">' . esc_html__('Quiet', 'mxchat') . '</span>';
1224 + }
1225 + ?>
1226 + </div>
1227 + <div class="insight-description">
1228 + <?php
1229 + if ($today_chats > 0) {
1230 + echo esc_html(sprintf(__('%d chats today', 'mxchat'), $today_chats));
1231 + } else if ($week_chats > 0) {
1232 + echo esc_html(sprintf(__('%d chats this week', 'mxchat'), $week_chats));
1233 + } else {
1234 + echo esc_html__('No recent activity', 'mxchat');
1235 + }
1236 + ?>
1237 + </div>
1238 + </div>
1239 + </div>
1240 + </div>
1241 + </div>
1242 + </div>
1243 + </div>
1244 +
1245 + <script>
1246 + var mxchatChartData = {
1247 + labels: <?php echo json_encode($chart_labels); ?>,
1248 + chats: <?php echo json_encode($chart_chats); ?>,
1249 + messages: <?php echo json_encode($chart_messages); ?>
1250 + };
1251 + </script>
1262 1252
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 - );
1253 + <!-- Search and Filter Controls with Notification Settings Button -->
1254 + <div class="mxchat-controls-wrapper">
1255 + <div class="mxchat-search-box">
1256 + <input type="text" id="mxchat-search-transcripts"
1257 + placeholder="<?php esc_attr_e('Search transcripts...', 'mxchat'); ?>"
1258 + class="regular-text">
1259 + </div>
1260 + <form id="mxchat-delete-form" method="post">
1261 + <?php wp_nonce_field('mxchat_delete_chat_history', 'mxchat_delete_chat_nonce'); ?>
1262 + <div class="mxchat-controls">
1263 + <button type="button" id="mxchat-chat-email-notification-btn" class="mxchat-action-button">
1264 + <span class="dashicons dashicons-admin-generic"></span>
1265 + <?php esc_html_e('Settings', 'mxchat'); ?>
1266 + </button>
1267 + <button type="button" id="mxchat-export-transcripts" class="mxchat-action-button">
1268 + <span class="dashicons dashicons-download"></span>
1269 + <?php esc_html_e('Export All Chats', 'mxchat'); ?>
1270 + </button>
1271 + <button type="button" id="mxchat-select-all-transcripts" class="mxchat-select-button">
1272 + <span class="dashicons dashicons-yes-alt"></span>
1273 + <span class="button-text"><?php esc_html_e('Select All', 'mxchat'); ?></span>
1274 + </button>
1275 + <button type="submit" class="button delete-chats-button">
1276 + <span class="dashicons dashicons-trash"></span>
1277 + <?php esc_html_e('Delete Selected', 'mxchat'); ?>
1278 + </button>
1279 + </div>
1280 + </form>
1281 + </div>
1280 1282
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);
1283 + <!-- Transcripts Container -->
1284 + <div id="mxchat-transcripts"></div>
1285 + </div>
1286 + </div>
1287 +
1288 + <!-- Modal for Chat Email Notification Settings -->
1289 + <div id="mxchat-chat-email-notification-modal" class="mxchat-chat-notification-modal-overlay" style="display: none;">
1290 + <div class="mxchat-chat-notification-modal-content">
1291 + <div class="mxchat-chat-notification-modal-header">
1292 + <h2><?php esc_html_e('Transcript Settings', 'mxchat'); ?></h2>
1293 + <button type="button" class="mxchat-chat-notification-modal-close">&times;</button>
1294 + </div>
1295 + <div class="mxchat-chat-notification-modal-body">
1296 + <form method="post" action="options.php" id="mxchat-chat-email-notification-form">
1297 + <?php
1298 + settings_fields('mxchat_transcripts_options');
1299 + do_settings_sections('mxchat-transcripts');
1300 + ?>
1301 + <div class="mxchat-chat-notification-modal-footer">
1302 + <button type="submit" class="button button-primary">
1303 + <?php esc_html_e('Save Settings', 'mxchat'); ?>
1304 + </button>
1305 + <button type="button" class="button mxchat-chat-notification-modal-cancel">
1306 + <?php esc_html_e('Cancel', 'mxchat'); ?>
1307 + </button>
1308 + </div>
1309 + </form>
1310 + </div>
1311 + </div>
1312 + </div>
1313 + <?php
1284 1314 }
1285 1315
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';
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 - ");
1307 -
1308 - // Prepare chart data
1309 - $chart_labels = array();
1310 - $chart_chats = array();
1311 - $chart_messages = array();
1312 -
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 1317 public function mxchat_transcripts_notification_section_callback() {
1344 1318 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 1319 }
1346 1320 public function mxchat_enable_notifications_callback() {
@@ -1548,43 +1522,23 @@
1548 1522 }
1549 1523
1550 1524 global $wpdb;
1551 1525 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1552 - $translations_table = $wpdb->prefix . 'mxchat_transcript_translations';
1553 -
1526 +
1554 1527 // Calculate the cutoff timestamp
1555 1528 $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 -
1529 +
1565 1530 // Delete transcripts older than the cutoff date
1566 1531 $deleted = $wpdb->query(
1567 1532 $wpdb->prepare(
1568 - "DELETE FROM {$table_name} WHERE timestamp < %s",
1533 + "DELETE FROM {$table_name} WHERE created_at < %s",
1569 1534 $cutoff_date
1570 1535 )
1571 1536 );
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 -
1537 +
1584 1538 // Log the cleanup action
1585 1539 if ($deleted !== false && $deleted > 0) {
1586 - //error_log(sprintf('MXChat: Auto-deleted %d transcripts older than %d days', $deleted, $days));
1540 + error_log(sprintf('MXChat: Auto-deleted %d transcripts older than %d days', $deleted, $days));
1587 1541 }
1588 1542 }
1589 1543
1590 1544 public function export_chat_transcripts() {
@@ -1646,1236 +1600,9 @@
1646 1600 fclose($output);
1647 1601 wp_die();
1648 1602 }
1649 1603
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 1604
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 1605 public function mxchat_fetch_chat_history() {
2879 1606 global $wpdb;
2880 1607 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
2881 1608 $url_clicks_table = $wpdb->prefix . 'mxchat_url_clicks';
@@ -2880,10 +1607,9 @@
2880 1607 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
2881 1608 $url_clicks_table = $wpdb->prefix . 'mxchat_url_clicks';
2882 1609
2883 1610 if (!current_user_can('manage_options')) {
2884 - wp_send_json_error(['message' => 'Insufficient permissions']);
2885 - wp_die();
1611 + wp_die(esc_html__('You do not have sufficient permissions to view this page.', 'mxchat'));
2886 1612 }
2887 1613
2888 1614 $page = isset($_POST['page']) ? absint($_POST['page']) : 1;
2889 1615 $per_page = isset($_POST['per_page']) ? absint($_POST['per_page']) : 50;
@@ -2888,13 +1614,12 @@
2888 1614 $page = isset($_POST['page']) ? absint($_POST['page']) : 1;
2889 1615 $per_page = isset($_POST['per_page']) ? absint($_POST['per_page']) : 50;
2890 1616 $offset = ($page - 1) * $per_page;
2891 1617 $search = isset($_POST['search']) ? sanitize_text_field($_POST['search']) : '';
2892 - $sort_order = isset($_POST['sort_order']) && $_POST['sort_order'] === 'asc' ? 'ASC' : 'DESC';
2893 1618
2894 - // Build search condition
2895 1619 $search_condition = '';
2896 1620 $search_params = [];
1621 +
2897 1622 if (!empty($search)) {
2898 1623 $search_condition = "WHERE (
2899 1624 session_id LIKE %s
2900 1625 OR user_email LIKE %s
@@ -2904,51 +1629,52 @@
2904 1629 )";
2905 1630 $search_params = array_fill(0, 5, '%' . $wpdb->esc_like($search) . '%');
2906 1631 }
2907 1632
2908 - // Get total count
2909 1633 $count_query = !empty($search)
2910 1634 ? $wpdb->prepare("SELECT COUNT(DISTINCT session_id) FROM {$table_name} {$search_condition}", $search_params)
2911 1635 : "SELECT COUNT(DISTINCT session_id) FROM {$table_name}";
2912 - $total_sessions = (int) $wpdb->get_var($count_query);
2913 1636
2914 - // Get session IDs for current page (sorted by newest or oldest)
1637 + $total_sessions = $wpdb->get_var($count_query);
1638 +
2915 1639 $session_query = !empty($search)
2916 1640 ? $wpdb->prepare(
2917 1641 "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",
1642 + GROUP BY t.session_id ORDER BY MAX(t.timestamp) DESC LIMIT %d OFFSET %d",
2919 1643 array_merge($search_params, [$per_page, $offset])
2920 1644 )
2921 1645 : $wpdb->prepare(
2922 1646 "SELECT DISTINCT session_id FROM {$table_name}
2923 - GROUP BY session_id ORDER BY MAX(timestamp) {$sort_order} LIMIT %d OFFSET %d",
1647 + GROUP BY session_id ORDER BY MAX(timestamp) DESC LIMIT %d OFFSET %d",
2924 1648 $per_page, $offset
2925 1649 );
1650 +
2926 1651 $session_ids = $wpdb->get_col($session_query);
2927 1652
2928 - $total_pages = ceil($total_sessions / $per_page);
2929 -
2930 1653 if (empty($session_ids)) {
1654 + ob_start();
1655 + echo '<div class="mxchat-no-results">';
1656 + echo esc_html__('No chat history found.', 'mxchat');
1657 + if (!empty($search)) {
1658 + echo ' ' . esc_html__('Try adjusting your search criteria.', 'mxchat');
1659 + }
1660 + echo '</div>';
2931 1661 wp_send_json([
2932 - 'success' => true,
2933 - 'sessions' => [],
1662 + 'html' => ob_get_clean(),
2934 1663 'page' => $page,
2935 1664 'total_pages' => 0,
2936 - 'total_sessions' => 0,
2937 - 'showing_start' => 0,
2938 - 'showing_end' => 0
1665 + 'total_sessions' => 0
2939 1666 ]);
2940 1667 wp_die();
2941 1668 }
2942 1669
2943 - // Check for optional columns/tables
2944 1670 $url_table_exists = $wpdb->get_var("SHOW TABLES LIKE '$url_clicks_table'") === $url_clicks_table;
2945 1671 $originating_columns_exist = !empty($wpdb->get_results("SHOW COLUMNS FROM $table_name LIKE 'originating_page_url'"));
2946 1672
2947 - // Build session list data
2948 - $sessions = [];
1673 + ob_start();
1674 + echo '<div class="mxchat-transcript">';
1675 +
2949 1676 foreach ($session_ids as $session_id) {
2950 - // Get session metadata
2951 1677 $session_data = $originating_columns_exist
2952 1678 ? $wpdb->get_row($wpdb->prepare(
2953 1679 "SELECT user_email, user_name, user_identifier, originating_page_url, originating_page_title, timestamp
2954 1680 FROM {$table_name} WHERE session_id = %s ORDER BY timestamp ASC LIMIT 1",
@@ -2959,217 +1685,186 @@
2959 1685 WHERE session_id = %s LIMIT 1",
2960 1686 $session_id
2961 1687 ));
2962 1688
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 - ));
1689 + $clicked_urls = [];
1690 + if ($url_table_exists) {
1691 + $url_clicks = $wpdb->get_results(
1692 + $wpdb->prepare(
1693 + "SELECT DISTINCT clicked_url FROM {$url_clicks_table}
1694 + WHERE session_id = %s ORDER BY click_timestamp ASC",
1695 + $session_id
1696 + )
1697 + );
1698 + foreach ($url_clicks as $click) {
1699 + $clicked_urls[] = $click->clicked_url;
1700 + }
1701 + }
2968 1702
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';
1703 + $messages = $wpdb->get_results(
1704 + $wpdb->prepare(
1705 + "SELECT * FROM {$table_name}
1706 + WHERE session_id = %s ORDER BY timestamp ASC",
1707 + $session_id
1708 + )
1709 + );
2975 1710
2976 - // Build display name
1711 + $latest_timestamp = !empty($messages) ? end($messages)->timestamp : $session_data->timestamp;
1712 + $session_time = wp_date('M j, g:ia', strtotime($latest_timestamp . ' UTC'));
1713 +
2977 1714 $user_email = !empty($session_data->user_email) ? $session_data->user_email : '';
2978 - $user_name = !empty($session_data->user_name) ? $session_data->user_name : '';
1715 + $user_name = !empty($session_data->user_name) ? $session_data->user_name : ''; // NEW
2979 1716 $user_identifier = !empty($session_data->user_identifier) ? $session_data->user_identifier : 'Guest';
2980 1717
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';
1718 + // Build user display with name if available
1719 + if (!empty($user_email)) {
1720 + $user_display = $user_email;
1721 + if (!empty($user_name)) {
1722 + $user_display .= ' (' . $user_name . ')';
1723 + }
2994 1724 } else {
2995 - $time_display = wp_date('M j', $timestamp);
1725 + $user_display = $user_identifier;
2996 1726 }
1727 + echo '<div class="mxchat-session">';
1728 + echo '<div class="mxchat-session-header" data-session-id="' . esc_attr($session_id) . '">';
1729 + echo '<div class="mxchat-user-row">' . esc_html($user_display) . '</div>';
2997 1730
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));
1731 + if ($originating_columns_exist && !empty($session_data->originating_page_url)) {
1732 + $page_title = !empty($session_data->originating_page_title) ? $session_data->originating_page_title : $session_data->originating_page_url;
1733 +
1734 + echo '<div class="mxchat-header-row flex-between">';
1735 + echo '<div class="mxchat-main-info">';
1736 + echo '<a href="' . esc_url($session_data->originating_page_url) . '" target="_blank" class="mxchat-page-link">';
1737 + echo esc_html($page_title);
1738 + echo '</a>';
1739 + echo '<span class="mxchat-session-time">' . esc_html($session_time) . '</span>';
1740 + echo '</div>';
1741 + echo '</div>';
3003 1742 }
3004 1743
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 - }
1744 + if (!empty($clicked_urls)) {
1745 + echo '<div class="mxchat-links-row">';
1746 + echo '<span class="mxchat-label">Clicked:</span> ';
1747 + $link_count = 0;
1748 + foreach ($clicked_urls as $url) {
1749 + if ($link_count > 0) echo ', ';
1750 + echo '<a href="' . esc_url($url) . '" target="_blank" class="mxchat-link-item">' . esc_html(parse_url($url, PHP_URL_HOST)) . '</a>';
1751 + $link_count++;
1752 + if ($link_count >= 3) {
1753 + if (count($clicked_urls) > 3) {
1754 + echo ' +' . (count($clicked_urls) - 3) . ' more';
1755 + }
1756 + break;
1757 + }
1758 + }
1759 + echo '</div>';
1760 + }
3016 1761
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 -}
1762 + echo '</div>'; // end session-header
1763 + echo '<div class="mxchat-messages">';
3028 1764
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';
1765 + foreach ($messages as $transcript) {
1766 + $formatted_timestamp = wp_date('F j, Y g:i a', strtotime($transcript->timestamp . ' UTC'));
3036 1767
3037 - if (!current_user_can('manage_options')) {
3038 - wp_send_json_error(['message' => 'Insufficient permissions']);
3039 - wp_die();
3040 - }
1768 + switch ($transcript->role) {
1769 + case 'assistant':
1770 + case 'bot':
1771 + $message_class = 'bot-message';
1772 + $display_role = esc_html__('Chatbot', 'mxchat');
1773 + break;
1774 + case 'user':
1775 + $message_class = 'user-message';
1776 + $display_role = !empty($transcript->user_identifier)
1777 + ? sanitize_text_field($transcript->user_identifier)
1778 + : esc_html__('User', 'mxchat');
1779 + break;
1780 + case 'agent':
1781 + $message_class = 'agent-message';
1782 + $display_role = esc_html__('Agent', 'mxchat');
1783 + break;
1784 + default:
1785 + $message_class = 'unknown-message';
1786 + $display_role = esc_html__('Unknown', 'mxchat');
1787 + }
3041 1788
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 - }
1789 + $message_content = wp_kses(
1790 + stripslashes($transcript->message),
1791 + [
1792 + 'b' => [], 'strong' => [], 'i' => [], 'em' => [], 'u' => [],
1793 + 'br' => [], 'p' => [], 'ul' => [], 'ol' => [], 'li' => [],
1794 + 'a' => ['href' => [], 'title' => [], 'target' => []]
1795 + ]
1796 + );
1797 + $message_content = nl2br($message_content);
3047 1798
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'"));
1799 + echo '<div class="mxchat-message ' . esc_attr($message_class) . '">';
1800 + echo '<div class="mxchat-message-header">' . esc_html($display_role) . '</div>';
1801 + echo '<div class="mxchat-message-content">' . $message_content . '</div>';
1802 + echo '<div class="mxchat-timestamp">' . esc_html($formatted_timestamp) . '</div>';
1803 + echo '</div>';
1804 + }
3051 1805
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 - ));
3064 -
3065 - if (!$session_data) {
3066 - wp_send_json_error(['message' => 'Session not found']);
3067 - wp_die();
1806 + echo '</div></div>'; // end messages and session
3068 1807 }
3069 1808
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;
3080 - }
3081 - }
1809 + echo '</div>'; // end transcript
3082 1810
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 - ));
1811 + $total_pages = ceil($total_sessions / $per_page);
3088 1812
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';
1813 + echo '<div class="mxchat-pagination">';
1814 + echo '<div class="mxchat-pagination-info">';
1815 + echo sprintf(
1816 + esc_html__('Showing %1$d to %2$d of %3$d sessions', 'mxchat'),
1817 + $offset + 1,
1818 + min($offset + $per_page, $total_sessions),
1819 + $total_sessions
1820 + );
1821 + echo '</div>';
3093 1822
3094 - $display_name = $user_name ?: ($user_email ? explode('@', $user_email)[0] : $user_identifier);
3095 - $display_sub = $user_email ?: ('ID: ' . $user_identifier);
1823 + if ($total_pages > 1) {
1824 + echo '<div class="mxchat-pagination-controls">';
1825 + if ($page > 1) {
1826 + echo '<button class="mxchat-pagination-button" data-page="' . ($page - 1) . '">&laquo; ' . esc_html__('Previous', 'mxchat') . '</button>';
1827 + }
3096 1828
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 - }
1829 + $start_page = max(1, $page - 2);
1830 + $end_page = min($total_pages, $page + 2);
3103 1831
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 - }
1832 + if ($start_page > 1) {
1833 + echo '<button class="mxchat-pagination-button" data-page="1">1</button>';
1834 + if ($start_page > 2) {
1835 + echo '<span class="mxchat-pagination-ellipsis">...</span>';
1836 + }
1837 + }
3110 1838
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');
1839 + for ($i = $start_page; $i <= $end_page; $i++) {
1840 + $class = $i === $page ? 'mxchat-pagination-button active' : 'mxchat-pagination-button';
1841 + echo '<button class="' . $class . '" data-page="' . $i . '">' . $i . '</button>';
1842 + }
3116 1843
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);
1844 + if ($end_page < $total_pages) {
1845 + if ($end_page < $total_pages - 1) {
1846 + echo '<span class="mxchat-pagination-ellipsis">...</span>';
1847 + }
1848 + echo '<button class="mxchat-pagination-button" data-page="' . $total_pages . '">' . $total_pages . '</button>';
1849 + }
3135 1850
3136 - $has_rag = $is_bot && !empty($msg->rag_context);
1851 + if ($page < $total_pages) {
1852 + echo '<button class="mxchat-pagination-button" data-page="' . ($page + 1) . '">' . esc_html__('Next', 'mxchat') . ' &raquo;</button>';
1853 + }
3137 1854
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 - ];
1855 + echo '</div>';
3148 1856 }
3149 1857
3150 - // First message timestamp for "started" display
3151 - $started = !empty($messages) ? wp_date('M j, Y g:i A', strtotime($messages[0]->timestamp . ' UTC')) : '-';
1858 + echo '</div>';
3152 1859
3153 1860 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
1861 + 'html' => ob_get_clean(),
1862 + 'page' => $page,
1863 + 'total_pages' => $total_pages,
1864 + 'total_sessions' => $total_sessions
3171 1865 ]);
1866 +
3172 1867 wp_die();
3173 1868 }
3174 1869
3175 1870
@@ -3265,9 +1960,9 @@
3265 1960 $pinecone_manager = MxChat_Pinecone_Manager::get_instance();
3266 1961
3267 1962 // Display success message if all prompts were deleted
3268 1963 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>';
1964 + echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__('All knowledge has been deleted successfully.', 'mxchat') . '</p></div>';
3270 1965 }
3271 1966
3272 1967 // Set up pagination, search query, and content type filter
3273 1968 $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field($_GET['_wpnonce']) : '';
@@ -3273,9 +1968,9 @@
3273 1968 $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field($_GET['_wpnonce']) : '';
3274 1969 $search_query = (!empty($nonce) && wp_verify_nonce($nonce, 'mxchat_prompts_search_nonce') && isset($_GET['search'])) ? sanitize_text_field($_GET['search']) : '';
3275 1970 $content_type_filter = isset($_GET['content_type']) ? sanitize_key($_GET['content_type']) : ''; // ADDED 2.5.6
3276 1971 $current_page = isset($_GET['paged']) ? absint($_GET['paged']) : 1;
3277 - $per_page = 25;
1972 + $per_page = 10;
3278 1973
3279 1974 //error_log('DEBUG: Search query: ' . $search_query);
3280 1975 //error_log('DEBUG: Content type filter: ' . $content_type_filter);
3281 1976 //error_log('DEBUG: Current page: ' . $current_page);
@@ -3313,12 +2008,8 @@
3313 2008 $pinecone_options = $pinecone_manager->mxchat_get_bot_pinecone_options($current_bot_id);
3314 2009 $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
3315 2010 $pinecone_api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
3316 2011
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 2012 //error_log('DEBUG: Bot ' . $current_bot_id . ' - Use Pinecone: ' . ($use_pinecone ? 'YES' : 'NO'));
3322 2013 //error_log('DEBUG: Bot ' . $current_bot_id . ' - Has API Key: ' . (!empty($pinecone_api_key) ? 'YES' : 'NO'));
3323 2014 //error_log('DEBUG: Bot ' . $current_bot_id . ' - Host: ' . ($pinecone_options['mxchat_pinecone_host'] ?? 'NOT SET'));
3324 2015 //error_log('DEBUG: Bot ' . $current_bot_id . ' - Namespace: ' . ($pinecone_options['mxchat_pinecone_namespace'] ?? 'NOT SET'));
@@ -3384,111 +2075,25 @@
3384 2075 if (!empty($where_clauses)) {
3385 2076 $sql_where = "WHERE " . implode(" AND ", $where_clauses);
3386 2077 }
3387 2078
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
2079 + // Retrieve total number of prompts, considering filters
3391 2080 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 - );
2081 + $count_query = $wpdb->prepare("SELECT COUNT(*) FROM {$table_name} {$sql_where}", $where_values);
3398 2082 } 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)";
2083 + $count_query = "SELECT COUNT(*) FROM {$table_name} {$sql_where}";
3401 2084 }
3402 2085 $total_records = $wpdb->get_var($count_query);
3403 2086 $total_pages = ceil($total_records / $per_page);
3404 2087
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);
2088 + // Retrieve prompts from the database
2089 + $limit_offset_values = array_merge($where_values, array($per_page, $offset));
2090 + $prompts_query = $wpdb->prepare(
2091 + "SELECT * FROM {$table_name} {$sql_where} ORDER BY timestamp DESC LIMIT %d OFFSET %d",
2092 + ...$limit_offset_values
2093 + );
3421 2094
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 - }
2095 + $prompts = $wpdb->get_results($prompts_query);
3491 2096 }
3492 2097
3493 2098 // ================================
3494 2099 // PAGINATION GENERATION
@@ -3495,35 +2100,29 @@
3495 2100 // ================================
3496 2101
3497 2102 // Generate pagination links
3498 2103 if ($total_pages > 1) {
3499 - // Build a clean base URL with only necessary parameters
3500 - $pagination_args = array('page' => 'mxchat-prompts');
3501 -
2104 + // Build base URL with necessary parameters
2105 + $base_url = add_query_arg('paged', '%#%');
2106 +
3502 2107 // Preserve bot_id parameter if multi-bot is active
3503 2108 if ($multibot_active && !empty($current_bot_id) && $current_bot_id !== 'default') {
3504 - $pagination_args['bot_id'] = $current_bot_id;
2109 + $base_url = add_query_arg('bot_id', $current_bot_id, $base_url);
3505 2110 }
3506 2111
3507 - // Preserve search query and nonce if present
2112 + // Preserve search query if present
3508 2113 if ($search_query) {
3509 - $pagination_args['search'] = $search_query;
3510 - if (!empty($nonce)) {
3511 - $pagination_args['_wpnonce'] = $nonce;
3512 - }
2114 + $base_url = add_query_arg('search', $search_query, $base_url);
3513 2115 }
3514 2116
3515 - // Preserve content type filter if present
2117 + // UPDATED 2.5.6: Preserve content type filter if present
3516 2118 if ($content_type_filter) {
3517 - $pagination_args['content_type'] = $content_type_filter;
2119 + $base_url = add_query_arg('content_type', $content_type_filter, $base_url);
3518 2120 }
3519 -
3520 - // Build clean base URL
3521 - $base_url = add_query_arg($pagination_args, admin_url('admin.php'));
3522 -
2121 +
3523 2122 $page_links = paginate_links(array(
3524 - 'base' => $base_url . '%_%',
3525 - 'format' => '&paged=%#%',
2123 + 'base' => $base_url,
2124 + 'format' => '',
3526 2125 'prev_text' => __('&laquo; Previous', 'mxchat'),
3527 2126 'next_text' => __('Next &raquo;', 'mxchat'),
3528 2127 'total' => $total_pages,
3529 2128 'current' => $current_page,
@@ -3535,50 +2134,1198 @@
3535 2134
3536 2135 // ================================
3537 2136 // PROCESSING STATUS RETRIEVAL
3538 2137 // ================================
2138 +
2139 + // Retrieve processing statuses
2140 + $pdf_url = get_transient('mxchat_last_pdf_url');
2141 + $sitemap_url = get_transient('mxchat_last_sitemap_url');
2142 + $pdf_status = $pdf_url ? $knowledge_manager->mxchat_get_pdf_processing_status($pdf_url) : false;
2143 + $sitemap_status = $sitemap_url ? $knowledge_manager->mxchat_get_sitemap_processing_status($sitemap_url) : false;
3539 2144
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'];
2145 + $is_processing = ($pdf_status && ($pdf_status['status'] === 'processing' || $pdf_status['status'] === 'error'))
2146 + || ($sitemap_status && ($sitemap_status['status'] === 'processing' || $sitemap_status['status'] === 'error'));
3545 2147
3546 2148 //error_log('=== DEBUG: mxchat_create_prompts_page data preparation completed ===');
3547 2149
3548 - // ================================
3549 - // RENDER PAGE WITH NEW SIDEBAR LAYOUT
3550 - // ================================
2150 + ?>
3551 2151
3552 - // Include the new knowledge page template
3553 - require_once plugin_dir_path(__FILE__) . 'admin-knowledge-page.php';
2152 + <div class="wrap mxchat-wrapper">
2153 + <!-- Hero Section -->
2154 + <div class="mxchat-hero">
2155 + <h1 class="mxchat-main-title">
2156 + <span class="mxchat-gradient-text">Knowledge Base</span> Manager
2157 + </h1>
2158 + <p class="mxchat-hero-subtitle">
2159 + <?php esc_html_e('Enhance your AI chatbot with custom knowledge. Import, manage, and organize your content to keep responses accurate and relevant.', 'mxchat'); ?>
2160 + </p>
2161 + </div>
3554 2162
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 - );
2163 + <div class="mxchat-content">
2164 +
2165 + <!-- Multi-Bot Selector (only shows if multi-bot addon is active) -->
2166 + <?php if (class_exists('MxChat_Multi_Bot_Manager')) :
2167 + $multi_bot_manager = MxChat_Multi_Bot_Core_Manager::get_instance();
2168 + $available_bots = $multi_bot_manager->get_available_bots();
2169 +
2170 + // Get saved bot selection (user-specific first, then site-wide default)
2171 + $user_id = get_current_user_id();
2172 + $saved_bot_id = get_user_meta($user_id, 'mxchat_selected_knowledge_bot', true);
2173 + if (empty($saved_bot_id)) {
2174 + $saved_bot_id = get_option('mxchat_current_knowledge_bot', 'default');
2175 + }
2176 +
2177 + // Allow URL override but default to saved selection
2178 + $current_bot_id = isset($_GET['bot_id']) ? sanitize_key($_GET['bot_id']) : $saved_bot_id;
2179 + ?>
2180 + <div class="mxchat-card" style="margin-bottom: 20px;">
2181 + <div class="mxchat-bot-selector-wrapper" style="display: flex; align-items: center; gap: 15px;">
2182 + <label for="mxchat-bot-selector" style="font-weight: 600;">
2183 + <?php esc_html_e('Select Bot Database:', 'mxchat'); ?>
2184 + </label>
2185 + <select id="mxchat-bot-selector" class="mxchat-bot-selector" style="min-width: 200px;">
2186 + <?php foreach ($available_bots as $bot_id => $bot_name) : ?>
2187 + <option value="<?php echo esc_attr($bot_id); ?>" <?php selected($current_bot_id, $bot_id); ?>>
2188 + <?php echo esc_html($bot_name); ?>
2189 + </option>
2190 + <?php endforeach; ?>
2191 + </select>
2192 + <span class="mxchat-bot-info" style="color: #666; font-size: 13px;">
2193 + <?php esc_html_e('Content will be added to the selected bot\'s knowledge base', 'mxchat'); ?>
2194 + </span>
2195 + <span id="mxchat-bot-save-status" style="display: none; color: #00a32a; font-size: 13px;">
2196 + ✓ <?php esc_html_e('Saved', 'mxchat'); ?>
2197 + </span>
2198 + </div>
2199 + </div>
3576 2200
3577 - // Render the new sidebar-based page
3578 - mxchat_render_knowledge_page($this, $knowledge_manager, $page_data);
2201 + <?php
2202 + // Set a flag so we know multi-bot is active throughout the page
2203 + $multibot_active = true;
2204 + else:
2205 + $current_bot_id = 'default';
2206 + $multibot_active = false;
2207 + endif;
2208 + ?>
2209 +
2210 + <!-- Tab Navigation -->
2211 + <div class="mxchat-kb-tabs-nav">
2212 + <button class="mxchat-kb-tab-button active" data-tab="import">
2213 + <?php esc_html_e('Knowledge Import', 'mxchat'); ?>
2214 + </button>
2215 + <button class="mxchat-kb-tab-button" data-tab="sync">
2216 + <?php esc_html_e('Settings', 'mxchat'); ?>
2217 + </button>
2218 + <button class="mxchat-kb-tab-button" data-tab="pinecone">
2219 + <?php esc_html_e('Pinecone Settings', 'mxchat'); ?>
2220 + </button>
2221 + </div>
2222 +
2223 + <div class="mxchat-kb-tabs-content">
2224 + <div id="mxchat-kb-tab-import" class="mxchat-kb-tab-content active">
2225 + <!-- Import Options Card -->
2226 + <div class="mxchat-card">
2227 +
2228 + <h2><?php esc_html_e('Knowledge Import Settings', 'mxchat'); ?></h2>
2229 +
2230 + <?php
2231 + // Check if the appropriate embedding API key exists
2232 + $embedding_model = isset($this->options['embedding_model']) ? esc_attr($this->options['embedding_model']) : 'text-embedding-ada-002';
2233 + $has_openai_key = !empty($this->options['api_key']);
2234 + $has_voyage_key = !empty($this->options['voyage_api_key']);
2235 + $has_gemini_key = !empty($this->options['gemini_api_key']);
2236 +
2237 + // Determine if they have the needed API key for their selected embedding model
2238 + $has_required_key = false;
2239 + $required_key_type = '';
2240 +
2241 + if (strpos($embedding_model, 'text-embedding-') !== false && $has_openai_key) {
2242 + $has_required_key = true;
2243 + $required_key_type = 'OpenAI';
2244 + } elseif (strpos($embedding_model, 'voyage-') !== false && $has_voyage_key) {
2245 + $has_required_key = true;
2246 + $required_key_type = 'Voyage AI';
2247 + } elseif (strpos($embedding_model, 'gemini-embedding-') !== false && $has_gemini_key) {
2248 + $has_required_key = true;
2249 + $required_key_type = 'Google Gemini';
2250 + } elseif (strpos($embedding_model, 'text-embedding-') !== false) {
2251 + $required_key_type = 'OpenAI';
2252 + } elseif (strpos($embedding_model, 'voyage-') !== false) {
2253 + $required_key_type = 'Voyage AI';
2254 + } elseif (strpos($embedding_model, 'gemini-embedding-') !== false) {
2255 + $required_key_type = 'Google Gemini';
2256 + }
2257 + ?>
2258 +
2259 + <div class="mxchat-knowledge-warning <?php echo $has_required_key ? 'success' : 'warning'; ?>">
2260 + <?php if ($has_required_key): ?>
2261 + <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>
2262 + <?php else: ?>
2263 + <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>
2264 + <?php endif; ?>
2265 + </div>
2266 +
2267 + <!-- Import Options Section -->
2268 + <div class="mxchat-import-section">
2269 + <h3><?php esc_html_e('Import Options', 'mxchat'); ?></h3>
2270 +
2271 + <!-- Import Options Grid -->
2272 + <div class="mxchat-import-options">
2273 + <!-- WordPress Import Option -->
2274 + <button type="button" id="mxchat-open-content-selector" class="mxchat-import-box mxchat-import-wordpress" data-option="wordpress">
2275 + <div class="mxchat-import-icon">
2276 + <span class="dashicons dashicons-wordpress"></span>
2277 + </div>
2278 + <div class="mxchat-import-content">
2279 + <h4><?php esc_html_e('WordPress Content', 'mxchat'); ?></h4>
2280 + <p><?php esc_html_e('Import specific posts and pages to your knowledge base.', 'mxchat'); ?></p>
2281 + </div>
2282 + <div class="mxchat-recommended-tag"><?php esc_html_e('Recommended', 'mxchat'); ?></div>
2283 + </button>
2284 +
2285 + <!-- Sitemap Import Option -->
2286 + <button type="button" class="mxchat-import-box" data-option="sitemap" data-placeholder="<?php esc_attr_e('Enter sitemap URL here', 'mxchat'); ?>" data-type="sitemap">
2287 + <div class="mxchat-import-icon">
2288 + <span class="dashicons dashicons-admin-site-alt"></span>
2289 + </div>
2290 + <div class="mxchat-import-content">
2291 + <h4><?php esc_html_e('Sitemap Import', 'mxchat'); ?></h4>
2292 + <p><?php esc_html_e('Use a content-specific sub-sitemap, not the sitemap index.', 'mxchat'); ?></p>
2293 + </div>
2294 + </button>
2295 +
2296 + <!-- Direct URL Import Option -->
2297 + <button type="button" class="mxchat-import-box" data-option="url" data-placeholder="<?php esc_attr_e('Enter webpage URL here', 'mxchat'); ?>" data-type="url">
2298 + <div class="mxchat-import-icon">
2299 + <span class="dashicons dashicons-admin-links"></span>
2300 + </div>
2301 + <div class="mxchat-import-content">
2302 + <h4><?php esc_html_e('Direct URL', 'mxchat'); ?></h4>
2303 + <p><?php esc_html_e('Import content from any webpage.', 'mxchat'); ?></p>
2304 + </div>
2305 + </button>
2306 +
2307 + <!-- Direct Content Import Option -->
2308 + <button type="button" class="mxchat-import-box" data-option="content" data-type="content">
2309 + <div class="mxchat-import-icon">
2310 + <span class="dashicons dashicons-editor-paste-text"></span>
2311 + </div>
2312 + <div class="mxchat-import-content">
2313 + <h4><?php esc_html_e('Direct Content', 'mxchat'); ?></h4>
2314 + <p><?php esc_html_e('Submit content to be vectorized.', 'mxchat'); ?></p>
2315 + </div>
2316 + </button>
2317 +
2318 + <!-- PDF Import Option -->
2319 + <button type="button" class="mxchat-import-box" data-option="pdf" data-placeholder="<?php esc_attr_e('Enter PDF URL here', 'mxchat'); ?>" data-type="pdf">
2320 + <div class="mxchat-import-icon">
2321 + <span class="dashicons dashicons-media-document"></span>
2322 + </div>
2323 + <div class="mxchat-import-content">
2324 + <h4><?php esc_html_e('PDF Import', 'mxchat'); ?></h4>
2325 + <p><?php esc_html_e('Import knowledge from PDF documents.', 'mxchat'); ?></p>
2326 + </div>
2327 + </button>
2328 + </div>
2329 +
2330 + <!-- Input Areas for URL and Content -->
2331 + <div class="mxchat-import-input-area" id="mxchat-url-input-area" style="display: none;">
2332 + <?php if (!$is_processing) : ?>
2333 + <form id="mxchat-url-form" method="post" action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_submit_sitemap')); ?>">
2334 + <?php wp_nonce_field('mxchat_submit_sitemap_action', 'mxchat_submit_sitemap_nonce'); ?>
2335 + <input type="hidden" name="import_type" id="import_type" value="url">
2336 + <?php if ($multibot_active && $current_bot_id !== 'default') : ?>
2337 + <input type="hidden" name="bot_id" value="<?php echo esc_attr($current_bot_id); ?>">
2338 + <?php endif; ?>
2339 + <div class="mxchat-url-input-group">
2340 + <input type="url"
2341 + name="sitemap_url"
2342 + id="sitemap_url"
2343 + placeholder="<?php esc_attr_e('Enter URL here', 'mxchat'); ?>"
2344 + required />
2345 + <button type="submit"
2346 + name="submit_sitemap"
2347 + class="mxchat-button-primary">
2348 + <?php esc_html_e('Import', 'mxchat'); ?>
2349 + </button>
2350 + </div>
2351 + <p class="mxchat-url-description" id="url-description-text"></p>
2352 + </form>
2353 + <?php endif; ?>
2354 + </div>
2355 +
2356 + <div class="mxchat-import-input-area" id="mxchat-content-input-area" style="display: none;">
2357 + <?php if (!$is_processing) : ?>
2358 + <form id="mxchat-content-form" method="post" action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_submit_content')); ?>">
2359 + <?php wp_nonce_field('mxchat_submit_content_action', 'mxchat_submit_content_nonce'); ?>
2360 + <?php if ($multibot_active && $current_bot_id !== 'default') : ?>
2361 + <input type="hidden" name="bot_id" value="<?php echo esc_attr($current_bot_id); ?>">
2362 + <?php endif; ?>
2363 + <div class="mxchat-form-group">
2364 + <textarea
2365 + name="article_content"
2366 + id="article_content"
2367 + placeholder="<?php esc_attr_e('Enter your content here...', 'mxchat'); ?>"
2368 + required
2369 + rows="6"
2370 + ></textarea>
2371 + </div>
2372 + <div class="mxchat-form-group">
2373 + <input type="url"
2374 + name="article_url"
2375 + id="article_url"
2376 + placeholder="<?php esc_attr_e('Enter source URL (Optional)', 'mxchat'); ?>">
2377 + <div class="mxchat-url-helper" style="margin-top: 8px; display: flex; align-items: center; gap: 8px;">
2378 + <label style="display: flex; align-items: center; gap: 6px; font-size: 13px; color: #666; cursor: pointer;">
2379 + <input type="checkbox" id="mxchat-unique-url-toggle" style="margin: 0;">
2380 + <?php esc_html_e('Generate unique URL for duplicate content', 'mxchat'); ?>
2381 + </label>
2382 + <button type="button"
2383 + id="mxchat-generate-unique-url"
2384 + class="mxchat-button-secondary"
2385 + style="padding: 4px 12px; font-size: 12px; display: none;">
2386 + <span class="dashicons dashicons-randomize" style="font-size: 14px; margin-top: 2px;"></span>
2387 + <?php esc_html_e('Generate Unique', 'mxchat'); ?>
2388 + </button>
2389 + </div>
2390 + <p class="description" style="margin-top: 8px; font-size: 12px; color: #666;">
2391 + <?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'); ?>
2392 + </p>
2393 + </div>
2394 + <button type="submit"
2395 + name="submit_content"
2396 + class="mxchat-button-primary">
2397 + <?php esc_html_e('Import Content', 'mxchat'); ?>
2398 + </button>
2399 + </form>
2400 + <?php endif; ?>
2401 + </div>
2402 + </div>
2403 +
2404 + <!-- PDF Processing Status - ONLY PROCESSING -->
2405 + <?php if ($pdf_status && $pdf_status['status'] === 'processing') : ?>
2406 + <div class="mxchat-status-card" data-card-type="pdf">
2407 + <div class="mxchat-status-header">
2408 + <h4><?php esc_html_e('PDF Processing Status', 'mxchat'); ?></h4>
2409 +
2410 + <!-- Processing controls -->
2411 + <div class="mxchat-processing-controls">
2412 + <form method="post" class="mxchat-stop-form"
2413 + action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_stop_processing')); ?>">
2414 + <?php wp_nonce_field('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce'); ?>
2415 + <button type="submit" name="stop_processing" class="mxchat-button-secondary">
2416 + <?php esc_html_e('Stop Processing', 'mxchat'); ?>
2417 + </button>
2418 + </form>
2419 +
2420 + <button type="button" class="mxchat-manual-batch-btn"
2421 + data-process-type="pdf"
2422 + data-url="<?php echo esc_attr(get_transient('mxchat_last_pdf_url')); ?>">
2423 + <?php esc_html_e('Process Batch', 'mxchat'); ?>
2424 + </button>
2425 + </div>
2426 + </div>
2427 +
2428 + <div class="mxchat-progress-bar">
2429 + <div class="mxchat-progress-fill" style="width: <?php echo esc_attr($pdf_status['percentage']); ?>%"></div>
2430 + </div>
2431 +
2432 + <div class="mxchat-status-details">
2433 + <p><?php printf(
2434 + esc_html__('Progress: %1$d of %2$d pages (%3$d%%)', 'mxchat'),
2435 + absint($pdf_status['processed_pages']),
2436 + absint($pdf_status['total_pages']),
2437 + absint($pdf_status['percentage'])
2438 + ); ?></p>
2439 +
2440 + <?php if (!empty($pdf_status['failed_pages']) && $pdf_status['failed_pages'] > 0) : ?>
2441 + <p><strong><?php esc_html_e('Failed pages:', 'mxchat'); ?></strong> <?php echo esc_html($pdf_status['failed_pages']); ?></p>
2442 + <?php endif; ?>
2443 +
2444 + <p><strong><?php esc_html_e('Status:', 'mxchat'); ?></strong> <?php echo esc_html(ucfirst($pdf_status['status'])); ?></p>
2445 + <p><strong><?php esc_html_e('Last update:', 'mxchat'); ?></strong> <?php echo esc_html($pdf_status['last_update']); ?></p>
2446 + </div>
2447 + </div>
2448 + <?php endif; ?>
2449 +
2450 + <!-- Sitemap Processing Status - ONLY PROCESSING -->
2451 + <?php if ($sitemap_status && $sitemap_status['status'] === 'processing') : ?>
2452 + <div class="mxchat-status-card" data-card-type="sitemap">
2453 + <div class="mxchat-status-header">
2454 + <h4><?php esc_html_e('Sitemap Processing Status', 'mxchat'); ?></h4>
2455 +
2456 + <!-- Processing controls -->
2457 + <div class="mxchat-processing-controls">
2458 + <form method="post" class="mxchat-stop-form"
2459 + action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_stop_processing')); ?>">
2460 + <?php wp_nonce_field('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce'); ?>
2461 + <button type="submit" name="stop_processing" class="mxchat-button-secondary">
2462 + <?php esc_html_e('Stop Processing', 'mxchat'); ?>
2463 + </button>
2464 + </form>
2465 +
2466 + <button type="button" class="mxchat-manual-batch-btn"
2467 + data-process-type="sitemap"
2468 + data-url="<?php echo esc_attr(get_transient('mxchat_last_sitemap_url')); ?>">
2469 + <?php esc_html_e('Process Batch', 'mxchat'); ?>
2470 + </button>
2471 + </div>
2472 + </div>
2473 +
2474 + <div class="mxchat-progress-bar">
2475 + <div class="mxchat-progress-fill" style="width: <?php echo esc_attr($sitemap_status['percentage']); ?>%"></div>
2476 + </div>
2477 +
2478 + <div class="mxchat-status-details">
2479 + <p><?php printf(
2480 + esc_html__('Progress: %1$d of %2$d URLs (%3$d%%)', 'mxchat'),
2481 + absint($sitemap_status['processed_urls']),
2482 + absint($sitemap_status['total_urls']),
2483 + absint($sitemap_status['percentage'])
2484 + ); ?></p>
2485 +
2486 + <?php if (!empty($sitemap_status['failed_urls']) && $sitemap_status['failed_urls'] > 0) : ?>
2487 + <p><strong><?php esc_html_e('Failed URLs:', 'mxchat'); ?></strong> <?php echo esc_html($sitemap_status['failed_urls']); ?></p>
2488 + <?php endif; ?>
2489 +
2490 + <p><strong><?php esc_html_e('Status:', 'mxchat'); ?></strong> <?php echo esc_html(ucfirst($sitemap_status['status'])); ?></p>
2491 + <p><strong><?php esc_html_e('Last update:', 'mxchat'); ?></strong> <?php echo esc_html($sitemap_status['last_update']); ?></p>
2492 +
2493 + <?php if (!empty($sitemap_status['error']) || !empty($sitemap_status['last_error'])) : ?>
2494 + <div class="mxchat-error-notice">
2495 + <?php if (!empty($sitemap_status['error'])) : ?>
2496 + <p class="error"><?php echo esc_html($sitemap_status['error']); ?></p>
2497 + <?php endif; ?>
2498 + <?php if (!empty($sitemap_status['last_error'])) : ?>
2499 + <p class="last-error"><?php echo esc_html__('Last error:', 'mxchat') . ' ' . esc_html($sitemap_status['last_error']); ?></p>
2500 + <?php endif; ?>
2501 + </div>
2502 + <?php endif; ?>
2503 + </div>
2504 + </div>
2505 + <?php endif; ?>
2506 +
2507 + <!-- Single URL Submission Status -->
2508 + <?php
2509 + // Get single URL status
2510 + $single_url_status = $this->knowledge_manager->mxchat_get_single_url_status();
2511 + $is_active_processing =
2512 + ($sitemap_status && $sitemap_status['status'] === 'processing') ||
2513 + ($pdf_status && $pdf_status['status'] === 'processing');
2514 + ?>
2515 +
2516 + <div id="mxchat-single-url-status-container" <?php echo $is_active_processing ? 'style="display:none;"' : ''; ?>>
2517 + <?php if ($single_url_status && !$is_active_processing) : ?>
2518 + <div class="mxchat-status-card">
2519 + <div class="mxchat-status-header">
2520 + <h4><?php esc_html_e('Last URL Submission', 'mxchat'); ?></h4>
2521 + <?php if ($single_url_status['status'] === 'failed') : ?>
2522 + <span class="mxchat-status-badge mxchat-status-failed"><?php esc_html_e('Failed', 'mxchat'); ?></span>
2523 + <?php else : ?>
2524 + <span class="mxchat-status-badge mxchat-status-success"><?php esc_html_e('Success', 'mxchat'); ?></span>
2525 + <?php endif; ?>
2526 + </div>
2527 + <div class="mxchat-status-details">
2528 + <p><strong><?php esc_html_e('URL:', 'mxchat'); ?></strong>
2529 + <a href="<?php echo esc_url($single_url_status['url']); ?>" target="_blank">
2530 + <?php echo esc_html(strlen($single_url_status['url']) > 60 ? substr($single_url_status['url'], 0, 57) . '...' : $single_url_status['url']); ?>
2531 + </a>
2532 + </p>
2533 + <p><strong><?php esc_html_e('Submitted:', 'mxchat'); ?></strong> <?php echo esc_html($single_url_status['human_time']); ?></p>
2534 +
2535 + <?php if ($single_url_status['status'] === 'failed' && !empty($single_url_status['error'])) : ?>
2536 + <div class="mxchat-error-notice">
2537 + <p class="error"><?php echo esc_html($single_url_status['error']); ?></p>
2538 + </div>
2539 + <?php endif; ?>
2540 +
2541 + <?php if ($single_url_status['status'] === 'complete') : ?>
2542 + <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>
2543 + <p><strong><?php esc_html_e('Embedding Dimensions:', 'mxchat'); ?></strong> <?php echo esc_html($single_url_status['embedding_dimensions']); ?></p>
2544 + <?php endif; ?>
2545 + </div>
2546 + </div>
2547 + <?php endif; ?>
2548 + </div>
2549 +
2550 + <!-- Completed Status Cards - Handles completed PDF/Sitemap processing -->
2551 + <?php
2552 + echo $knowledge_manager->mxchat_render_completed_status_cards();
2553 + ?>
2554 +
2555 + </div>
2556 +
2557 + <!-- Knowledge Base Table Card -->
2558 + <div class="mxchat-card">
2559 + <div class="mxchat-card-header">
2560 + <h2>
2561 + <?php esc_html_e('Knowledge Base', 'mxchat'); ?>
2562 + <span class="mxchat-record-count">
2563 + <?php if ($showing_recent_only && $total_in_database > 1000): ?>
2564 + (<?php echo esc_html($total_records); ?> of <?php echo esc_html(number_format($total_in_database)); ?> total - recent entries only)
2565 + <?php else: ?>
2566 + (<?php echo esc_html($total_records); ?>)
2567 + <?php endif; ?>
2568 + </span>
2569 + <!-- Data source badge -->
2570 + <?php if ($use_pinecone) : ?>
2571 + <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;">
2572 + <span class="dashicons dashicons-cloud"></span>
2573 + <?php esc_html_e('Pinecone', 'mxchat'); ?>
2574 + </span>
2575 + <?php else : ?>
2576 + <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;">
2577 + <span class="dashicons dashicons-database-view"></span>
2578 + <?php esc_html_e('WordPress DB', 'mxchat'); ?>
2579 + </span>
2580 + <?php endif; ?>
2581 + </h2>
2582 + <div class="mxchat-header-actions">
2583 + <!-- Search -->
2584 + <form method="get" id="knowledge-search" class="mxchat-search-form">
2585 + <?php wp_nonce_field('mxchat_prompts_search_nonce'); ?>
2586 + <input type="hidden" name="page" value="mxchat-prompts" />
2587 + <?php if ($multibot_active && !empty($current_bot_id) && $current_bot_id !== 'default') : ?>
2588 + <input type="hidden" name="bot_id" value="<?php echo esc_attr($current_bot_id); ?>" />
2589 + <?php endif; ?>
2590 + <div class="mxchat-search-group">
2591 + <span class="dashicons dashicons-search"></span>
2592 + <input type="text"
2593 + name="search"
2594 + placeholder="<?php esc_attr_e('Search Knowledge', 'mxchat'); ?>"
2595 + value="<?php echo esc_attr($search_query); ?>" />
2596 + </div>
2597 +
2598 + <!-- ADDED 2.5.6: Content Type Filter -->
2599 + <div class="mxchat-filter-group">
2600 + <select name="content_type" id="mxchat-content-type-filter" onchange="this.form.submit()">
2601 + <option value=""><?php esc_html_e('All Content Types', 'mxchat'); ?></option>
2602 + <option value="post" <?php selected($content_type_filter, 'post'); ?>><?php esc_html_e('Posts', 'mxchat'); ?></option>
2603 + <option value="page" <?php selected($content_type_filter, 'page'); ?>><?php esc_html_e('Pages', 'mxchat'); ?></option>
2604 + <option value="product" <?php selected($content_type_filter, 'product'); ?>><?php esc_html_e('Products', 'mxchat'); ?></option>
2605 + <option value="pdf" <?php selected($content_type_filter, 'pdf'); ?>><?php esc_html_e('PDFs', 'mxchat'); ?></option>
2606 + <option value="url" <?php selected($content_type_filter, 'url'); ?>><?php esc_html_e('URLs', 'mxchat'); ?></option>
2607 + <option value="content" <?php selected($content_type_filter, 'content'); ?>><?php esc_html_e('Manual Content', 'mxchat'); ?></option>
2608 + </select>
2609 + </div>
2610 + </form>
2611 +
2612 + <form method="post"
2613 + action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_delete_all_prompts')); ?>"
2614 + class="mxchat-delete-form"
2615 + onsubmit="return confirm('<?php esc_attr_e('Are you sure you want to delete all knowledge? This action cannot be undone.', 'mxchat'); ?>');">
2616 + <?php wp_nonce_field('mxchat_delete_all_prompts_action', 'mxchat_delete_all_prompts_nonce'); ?>
2617 + <input type="hidden" name="data_source" value="<?php echo esc_attr($data_source); ?>" />
2618 + <!-- Always include bot_id, even if it's 'default' -->
2619 + <input type="hidden" name="bot_id" value="<?php echo esc_attr($current_bot_id); ?>" />
2620 + <button type="submit" name="delete_all_prompts" class="mxchat-button-danger">
2621 + <span class="dashicons dashicons-trash"></span>
2622 + <?php esc_html_e('Delete All', 'mxchat'); ?>
2623 + </button>
2624 + </form>
2625 + </div>
2626 + </div>
2627 +
2628 + <!-- Recent Entries Info Banner -->
2629 + <?php if ($showing_recent_only && $total_in_database > 1000): ?>
2630 + <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;">
2631 + <span class="dashicons dashicons-info"></span>
2632 + <span>
2633 + <?php printf(
2634 + esc_html__('Showing your %s most recent entries for faster loading. Your complete database contains %s entries. ', 'mxchat'),
2635 + number_format($total_records),
2636 + number_format($total_in_database)
2637 + ); ?>
2638 + <a href="https://app.pinecone.io/" target="_blank" rel="noopener noreferrer"><?php esc_html_e('View complete database in Pinecone →', 'mxchat'); ?></a>
2639 + </span>
2640 + </div>
2641 + <?php endif; ?>
2642 +
2643 + <!-- Data Source Info Banner -->
2644 + <?php if ($use_pinecone) : ?>
2645 + <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;">
2646 + <span class="dashicons dashicons-cloud"></span>
2647 + <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>
2648 + </div>
2649 + <?php else : ?>
2650 + <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;">
2651 + <span class="dashicons dashicons-database-view"></span>
2652 + <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>
2653 + </div>
2654 + <?php endif; ?>
2655 +
2656 + <!-- Table -->
2657 + <div class="mxchat-table-wrapper">
2658 + <table class="mxchat-records-table">
2659 + <thead>
2660 + <tr>
2661 + <th><?php esc_html_e('ID', 'mxchat'); ?></th>
2662 + <th><?php esc_html_e('Content', 'mxchat'); ?></th>
2663 + <th><?php esc_html_e('Source', 'mxchat'); ?></th>
2664 + <?php if ($data_source === 'pinecone') : ?>
2665 + <th><?php esc_html_e('Vector ID', 'mxchat'); ?></th>
2666 + <?php endif; ?>
2667 + <th><?php esc_html_e('Actions', 'mxchat'); ?></th>
2668 + </tr>
2669 + </thead>
2670 + <tbody>
2671 + <?php if ($prompts) : ?>
2672 + <?php foreach ($prompts as $index => $prompt) : ?>
2673 + <tr id="prompt-<?php echo esc_attr($prompt->id); ?>"
2674 + data-source="<?php echo esc_attr($data_source); ?>"
2675 + <?php if ($data_source === 'pinecone') : ?>
2676 + style="background: rgba(33, 150, 243, 0.02);"
2677 + <?php endif; ?>>
2678 + <td>
2679 + <?php if ($data_source === 'pinecone') : ?>
2680 + <?php echo esc_html($index + 1 + (($current_page - 1) * $per_page)); ?>
2681 + <?php else : ?>
2682 + <?php echo esc_html($prompt->id); ?>
2683 + <?php endif; ?>
2684 + </td>
2685 + <td class="mxchat-content-cell">
2686 + <div class="mxchat-accordion-wrapper">
2687 + <div class="mxchat-content-preview">
2688 + <?php
2689 + $content = $prompt->article_content;
2690 + $preview_length = 150;
2691 + $content_preview = mb_strlen($content) > $preview_length
2692 + ? mb_substr($content, 0, $preview_length) . '...'
2693 + : $content;
2694 + ?>
2695 + <span class="preview-text"><?php echo esc_html($content_preview); ?></span>
2696 + <?php if (mb_strlen($content) > $preview_length) : ?>
2697 + <button class="mxchat-expand-toggle" type="button">
2698 + <span class="dashicons dashicons-arrow-down-alt2"></span>
2699 + </button>
2700 + <?php endif; ?>
2701 + </div>
2702 + <div class="mxchat-content-full" style="display: none;">
2703 + <div class="content-view">
2704 + <?php
2705 + // Check if content contains Hebrew characters
2706 + if (preg_match('/[\x{0590}-\x{05FF}]/u', $content)) {
2707 + // Apply RTL direction for Hebrew content
2708 + echo '<div dir="rtl" lang="he" class="rtl-content">';
2709 + echo wp_kses_post(wpautop($content));
2710 + echo '</div>';
2711 + } else {
2712 + echo wp_kses_post(wpautop($content));
2713 + }
2714 + ?>
2715 + </div>
2716 + <?php if ($data_source === 'wordpress') : ?>
2717 + <textarea class="content-edit" style="display:none;"
2718 + <?php if (preg_match('/[\x{0590}-\x{05FF}]/u', $prompt->article_content)) echo 'dir="rtl" lang="he"'; ?>>
2719 + <?php echo htmlspecialchars($prompt->article_content, ENT_QUOTES, 'UTF-8'); ?>
2720 + </textarea>
2721 + <?php endif; ?>
2722 + </div>
2723 + </div>
2724 + </td>
2725 + <td class="mxchat-url-cell">
2726 + <div class="url-view">
2727 + <?php if (!empty($prompt->source_url)) : ?>
2728 + <a href="<?php echo esc_url($prompt->source_url); ?>" target="_blank">
2729 + <span class="dashicons dashicons-external"></span>
2730 + <?php esc_html_e('View Source', 'mxchat'); ?>
2731 + </a>
2732 + <?php else : ?>
2733 + <span class="mxchat-na"><?php esc_html_e('N/A', 'mxchat'); ?></span>
2734 + <?php endif; ?>
2735 + </div>
2736 + <?php if ($data_source === 'wordpress') : ?>
2737 + <input type="text" class="url-edit" style="display:none;"
2738 + value="<?php echo htmlspecialchars($prompt->source_url, ENT_QUOTES, 'UTF-8'); ?>" />
2739 + <?php endif; ?>
2740 + </td>
2741 + <?php if ($data_source === 'pinecone') : ?>
2742 + <td class="mxchat-vector-id-cell">
2743 + <code style="background: #f5f5f5; padding: 2px 6px; border-radius: 3px; font-size: 11px;">
2744 + <?php echo esc_html(substr($prompt->id, 0, 12) . '...'); ?>
2745 + </code>
2746 + </td>
2747 + <?php endif; ?>
2748 +
2749 + <td class="mxchat-actions-cell">
2750 + <?php if ($data_source === 'wordpress') : ?>
2751 + <!-- WordPress - Full editing capabilities -->
2752 + <div class="mxchat-role-restriction-container" style="margin-bottom: 8px;">
2753 + <select class="mxchat-role-select"
2754 + data-entry-id="<?php echo esc_attr($prompt->id); ?>"
2755 + data-data-source="wordpress"
2756 + data-nonce="<?php echo wp_create_nonce('mxchat_update_role_nonce'); ?>">
2757 + <?php
2758 + $current_role = $prompt->role_restriction ?? 'public';
2759 + $role_options = $knowledge_manager->mxchat_get_role_options();
2760 + foreach ($role_options as $role_key => $role_label) : ?>
2761 + <option value="<?php echo esc_attr($role_key); ?>"
2762 + <?php selected($current_role, $role_key); ?>>
2763 + <?php echo esc_html($role_label); ?>
2764 + </option>
2765 + <?php endforeach; ?>
2766 + </select>
2767 + </div>
2768 +
2769 + <!-- Edit/Save Buttons -->
2770 + <div class="mxchat-action-buttons">
2771 + <button class="mxchat-button-icon edit-button"
2772 + data-id="<?php echo esc_attr($prompt->id); ?>">
2773 + <span class="dashicons dashicons-edit"></span>
2774 + </button>
2775 + <button class="mxchat-button-icon save-button"
2776 + data-id="<?php echo esc_attr($prompt->id); ?>"
2777 + style="display:none;"
2778 + data-nonce="<?php echo wp_create_nonce('mxchat_save_inline_nonce'); ?>">
2779 + <span class="dashicons dashicons-saved"></span>
2780 + </button>
2781 + </div>
2782 + <?php else : ?>
2783 + <!-- Pinecone - Role restriction editable, content read-only -->
2784 + <div class="mxchat-role-restriction-container" style="margin-bottom: 8px;">
2785 + <select class="mxchat-role-select"
2786 + data-entry-id="<?php echo esc_attr($prompt->id); ?>"
2787 + data-data-source="pinecone"
2788 + data-nonce="<?php echo wp_create_nonce('mxchat_update_role_nonce'); ?>">
2789 + <?php
2790 + $current_role = $prompt->role_restriction ?? 'public';
2791 + $role_options = $knowledge_manager->mxchat_get_role_options();
2792 + foreach ($role_options as $role_key => $role_label) : ?>
2793 + <option value="<?php echo esc_attr($role_key); ?>"
2794 + <?php selected($current_role, $role_key); ?>>
2795 + <?php echo esc_html($role_label); ?>
2796 + </option>
2797 + <?php endforeach; ?>
2798 + </select>
2799 + </div>
2800 +
2801 + <?php endif; ?>
2802 +
2803 + <!-- Delete button for both sources -->
2804 + <div class="mxchat-delete-container">
2805 + <?php if ($data_source === 'pinecone') : ?>
2806 + <!-- AJAX Delete for Pinecone -->
2807 + <button type="button"
2808 + class="mxchat-button-icon delete-button-ajax"
2809 + data-vector-id="<?php echo esc_attr($prompt->id); ?>"
2810 + data-bot-id="<?php echo esc_attr($current_bot_id); ?>"
2811 + data-nonce="<?php echo wp_create_nonce('mxchat_delete_pinecone_prompt_nonce'); ?>"
2812 + title="<?php esc_attr_e('Delete entry', 'mxchat'); ?>">
2813 + <span class="dashicons dashicons-trash"></span>
2814 + </button>
2815 + <?php else : ?>
2816 + <!-- Regular link delete for WordPress DB -->
2817 + <a href="<?php echo esc_url(admin_url(
2818 + 'admin-post.php?action=mxchat_delete_prompt&id=' . esc_attr($prompt->id)
2819 + . '&_wpnonce=' . wp_create_nonce('mxchat_delete_prompt_nonce')
2820 + )); ?>"
2821 + class="mxchat-button-icon delete-button"
2822 + onclick="return confirm('<?php esc_attr_e('Are you sure you want to delete this entry?', 'mxchat'); ?>');">
2823 + <span class="dashicons dashicons-trash"></span>
2824 + </a>
2825 + <?php endif; ?>
2826 + </div>
2827 + </td>
2828 +
2829 + </tr>
2830 + <?php endforeach; ?>
2831 + <?php else : ?>
2832 + <tr>
2833 + <td colspan="<?php echo $data_source === 'pinecone' ? '5' : '4'; ?>" class="mxchat-no-records">
2834 + <?php if ($use_pinecone) : ?>
2835 + <?php esc_html_e('No vectors found in Pinecone database.', 'mxchat'); ?>
2836 + <?php else : ?>
2837 + <?php esc_html_e('No knowledge base entries found.', 'mxchat'); ?>
2838 + <?php endif; ?>
2839 + </td>
2840 + </tr>
2841 + <?php endif; ?>
2842 + </tbody>
2843 + </table>
2844 + </div>
2845 +
2846 + <?php if ($page_links) : ?>
2847 + <div class="mxchat-pagination">
2848 + <?php echo wp_kses_post($page_links); ?>
2849 + </div>
2850 + <?php endif; ?>
2851 + </div>
2852 +
2853 + </div>
2854 +
2855 + <!-- Settings Tab (Renamed from Auto-Sync Settings) -->
2856 + <div id="mxchat-kb-tab-sync" class="mxchat-kb-tab-content">
2857 +
2858 + <!-- Auto-Sync Settings Card -->
2859 + <div class="mxchat-card">
2860 + <div class="mxchat-settings-section">
2861 + <h3><?php esc_html_e('Auto-Sync Settings', 'mxchat'); ?></h3>
2862 + <p class="mxchat-description">
2863 + <?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'); ?>
2864 + </p>
2865 + <div class="mxchat-autosave-section">
2866 + <div class="mxchat-toggle-group">
2867 + <div class="mxchat-toggle-container">
2868 + <label class="mxchat-toggle-switch">
2869 + <input type="checkbox"
2870 + name="mxchat_auto_sync_posts"
2871 + class="mxchat-autosave-field"
2872 + value="1"
2873 + data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>"
2874 + <?php checked(get_option('mxchat_auto_sync_posts', '0'), '1'); ?>>
2875 + <span class="mxchat-toggle-slider"></span>
2876 + </label>
2877 + <span class="mxchat-toggle-label">
2878 + <?php esc_html_e('Auto-sync Posts', 'mxchat'); ?>
2879 + </span>
2880 + </div>
2881 +
2882 + <div class="mxchat-toggle-container">
2883 + <label class="mxchat-toggle-switch">
2884 + <input type="checkbox"
2885 + name="mxchat_auto_sync_pages"
2886 + class="mxchat-autosave-field"
2887 + value="1"
2888 + data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>"
2889 + <?php checked(get_option('mxchat_auto_sync_pages', '0'), '1'); ?>>
2890 + <span class="mxchat-toggle-slider"></span>
2891 + </label>
2892 + <span class="mxchat-toggle-label">
2893 + <?php esc_html_e('Auto-sync Pages', 'mxchat'); ?>
2894 + </span>
2895 + </div>
2896 +
2897 + <!-- Custom Post Types Section -->
2898 + <div class="mxchat-section-content">
2899 + <div class="mxchat-custom-post-types-header">
2900 + <button id="mxchat-custom-post-types-toggle" class="mxchat-button-secondary">
2901 + <?php esc_html_e('Advanced Custom Post Sync Settings', 'mxchat'); ?>
2902 + <span class="mxchat-toggle-icon">▼</span>
2903 + </button>
2904 + </div>
2905 +
2906 + <div id="mxchat-custom-post-types-container" class="mxchat-custom-post-types-container" style="display: none;">
2907 + <h3><?php esc_html_e('Sync Custom Post Types', 'mxchat'); ?></h3>
2908 + <p><?php esc_html_e('Select additional custom post types to automatically sync with the chatbot.', 'mxchat'); ?></p>
2909 +
2910 + <div class="mxchat-custom-post-types">
2911 + <?php
2912 + $post_types = $knowledge_manager->mxchat_get_public_post_types();
2913 + // Skip post and page as they're handled separately
2914 + unset($post_types['post']);
2915 + unset($post_types['page']);
2916 +
2917 + if (!empty($post_types)) {
2918 + foreach ($post_types as $post_type => $label) {
2919 + $option_name = 'mxchat_auto_sync_' . $post_type;
2920 + $is_enabled = get_option($option_name, '0');
2921 + ?>
2922 + <div class="mxchat-toggle-container">
2923 + <label class="mxchat-toggle-switch">
2924 + <input type="checkbox"
2925 + name="<?php echo esc_attr($option_name); ?>"
2926 + class="mxchat-autosave-field"
2927 + value="1"
2928 + data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>"
2929 + <?php checked($is_enabled, '1'); ?>>
2930 + <span class="mxchat-toggle-slider"></span>
2931 + </label>
2932 + <span class="mxchat-toggle-label">
2933 + <?php echo esc_html($label); ?> (<?php echo esc_html($post_type); ?>)
2934 + </span>
2935 + </div>
2936 + <?php
2937 + }
2938 + } else {
2939 + echo '<p>' . esc_html__('No custom post types found.', 'mxchat') . '</p>';
2940 + }
2941 + ?>
2942 + </div>
2943 + </div>
2944 + </div>
2945 + </div>
2946 + </div>
2947 + </div>
2948 + </div>
2949 +
2950 + <!-- Role-Based Content Restrictions Card -->
2951 + <div class="mxchat-card" style="margin-top: 20px;">
2952 + <h3><?php esc_html_e('Role-Based Content Restrictions', 'mxchat'); ?></h3>
2953 + <p class="mxchat-description">
2954 + <?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'); ?>
2955 + </p>
2956 +
2957 + <!-- Info Box -->
2958 + <div class="mxchat-knowledge-warning info" style="background: #e3f2fd; border-left-color: #2196f3;">
2959 + <p>
2960 + <span class="dashicons dashicons-info"></span>
2961 + <strong><?php esc_html_e('How it works:', 'mxchat'); ?></strong>
2962 + <?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'); ?>
2963 + </p>
2964 + </div>
2965 +
2966 + <!-- Add New Tag-Role Mapping -->
2967 + <div class="mxchat-role-mapping-form">
2968 + <h4><?php esc_html_e('Add Tag-Role Mapping', 'mxchat'); ?></h4>
2969 +
2970 + <div class="mxchat-form-row" style="display: flex; gap: 15px; align-items: flex-end; margin-bottom: 20px;">
2971 + <div class="mxchat-form-group" style="flex: 1;">
2972 + <label for="mxchat-tag-input">
2973 + <?php esc_html_e('Tag Name', 'mxchat'); ?>
2974 + </label>
2975 + <input type="text"
2976 + id="mxchat-tag-input"
2977 + class="regular-text"
2978 + placeholder="<?php esc_attr_e('Enter tag name (e.g., premium, members-only)', 'mxchat'); ?>" />
2979 + <p class="description">
2980 + <?php esc_html_e('Enter the exact tag slug as it appears in WordPress', 'mxchat'); ?>
2981 + </p>
2982 + </div>
2983 +
2984 + <div class="mxchat-form-group" style="flex: 1;">
2985 + <label for="mxchat-role-select">
2986 + <?php esc_html_e('Required Role', 'mxchat'); ?>
2987 + </label>
2988 + <select id="mxchat-role-select" class="regular-text">
2989 + <?php
2990 + // Get role options from knowledge manager
2991 + $role_options = array(
2992 + 'public' => __('Public (Everyone)', 'mxchat'),
2993 + 'logged_in' => __('Logged In Users', 'mxchat'),
2994 + 'subscriber' => __('Subscribers & Above', 'mxchat'),
2995 + 'contributor' => __('Contributors & Above', 'mxchat'),
2996 + 'author' => __('Authors & Above', 'mxchat'),
2997 + 'editor' => __('Editors & Above', 'mxchat'),
2998 + 'administrator' => __('Administrators Only', 'mxchat')
2999 + );
3000 +
3001 + foreach ($role_options as $role_key => $role_label) : ?>
3002 + <option value="<?php echo esc_attr($role_key); ?>">
3003 + <?php echo esc_html($role_label); ?>
3004 + </option>
3005 + <?php endforeach; ?>
3006 + </select>
3007 + <p class="description">
3008 + <?php esc_html_e('Select the minimum role required to access this content', 'mxchat'); ?>
3009 + </p>
3010 + </div>
3011 +
3012 + <div style="padding-bottom: 25px;">
3013 + <button type="button"
3014 + id="mxchat-add-tag-role"
3015 + class="mxchat-button-primary">
3016 + <span class="dashicons dashicons-plus-alt"></span>
3017 + <?php esc_html_e('Add Mapping', 'mxchat'); ?>
3018 + </button>
3019 + </div>
3020 + </div>
3021 + </div>
3022 +
3023 + <!-- Existing Tag-Role Mappings -->
3024 + <div class="mxchat-role-mappings-list">
3025 + <h4><?php esc_html_e('Current Tag-Role Mappings', 'mxchat'); ?></h4>
3026 +
3027 + <div id="mxchat-mappings-container">
3028 + <!-- Mappings will be loaded here via AJAX -->
3029 + <div class="mxchat-loading-mappings">
3030 + <span class="mxchat-spinner is-active"></span>
3031 + <?php esc_html_e('Loading mappings...', 'mxchat'); ?>
3032 + </div>
3033 + </div>
3034 +
3035 + <!-- Empty State (initially hidden) -->
3036 + <div id="mxchat-no-mappings" style="display: none; text-align: center; padding: 40px; color: #666;">
3037 + <span class="dashicons dashicons-tag" style="font-size: 48px; opacity: 0.3;"></span>
3038 + <p><?php esc_html_e('No tag-role mappings yet. Add your first mapping above to get started.', 'mxchat'); ?></p>
3039 + </div>
3040 + </div>
3041 +
3042 + <!-- Bulk Update Section -->
3043 + <div class="mxchat-bulk-update-section" style="margin-top: 30px; padding-top: 30px; border-top: 1px solid #ddd;">
3044 + <h4><?php esc_html_e('Bulk Update Existing Content', 'mxchat'); ?></h4>
3045 + <p class="mxchat-description">
3046 + <?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'); ?>
3047 + </p>
3048 +
3049 + <div class="mxchat-bulk-update-controls">
3050 + <button type="button"
3051 + id="mxchat-bulk-update-roles"
3052 + class="mxchat-button-secondary">
3053 + <span class="dashicons dashicons-update"></span>
3054 + <?php esc_html_e('Update All Existing Content', 'mxchat'); ?>
3055 + </button>
3056 +
3057 + <div id="mxchat-bulk-update-progress" style="display: none; margin-top: 15px;">
3058 + <div class="mxchat-progress-bar">
3059 + <div class="mxchat-progress-fill" style="width: 0%"></div>
3060 + </div>
3061 + <p class="mxchat-progress-text" style="margin-top: 10px; color: #666;">
3062 + <?php esc_html_e('Processing...', 'mxchat'); ?>
3063 + </p>
3064 + </div>
3065 +
3066 + <div id="mxchat-bulk-update-result" style="display: none; margin-top: 15px;"></div>
3067 + </div>
3068 + </div>
3069 + </div>
3070 +
3071 + </div>
3072 +
3073 + <!-- Pinecone Settings Tab -->
3074 + <div id="mxchat-kb-tab-pinecone" class="mxchat-kb-tab-content mxchat-autosave-section">
3075 + <div class="mxchat-card">
3076 + <h2><?php esc_html_e('Pinecone Vector Database Settings', 'mxchat'); ?></h2>
3077 + <p class="mxchat-description">
3078 + <?php echo wp_kses(
3079 + __('<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'),
3080 + array('strong' => array())
3081 + ); ?>
3082 + </p>
3083 +
3084 + <div class="mxchat-pinecone-info-box">
3085 + <div class="mxchat-info-icon">
3086 + <span class="dashicons dashicons-info"></span>
3087 + </div>
3088 + <div class="mxchat-info-content">
3089 + <h4><?php esc_html_e('What is Pinecone?', 'mxchat'); ?></h4>
3090 + <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>
3091 + <p><a href="https://www.pinecone.io/" target="_blank" rel="noopener noreferrer"><?php esc_html_e('Learn more about Pinecone →', 'mxchat'); ?></a></p>
3092 + </div>
3093 + </div>
3094 +
3095 + <div class="mxchat-database-settings-form">
3096 + <?php
3097 + $pinecone_options = get_option('mxchat_pinecone_addon_options', array());
3098 + $use_pinecone = $pinecone_options['mxchat_use_pinecone'] ?? '0';
3099 + ?>
3100 +
3101 + <div class="mxchat-toggle-container">
3102 + <label class="mxchat-toggle-switch">
3103 + <input type="checkbox"
3104 + name="mxchat_pinecone_addon_options[mxchat_use_pinecone]"
3105 + value="1"
3106 + <?php checked($use_pinecone, '1'); ?>>
3107 + <span class="mxchat-toggle-slider"></span>
3108 + </label>
3109 + <span class="mxchat-toggle-label">
3110 + <?php esc_html_e('Enable Pinecone Database', 'mxchat'); ?>
3111 + </span>
3112 + </div>
3113 +
3114 + <div class="mxchat-pinecone-settings" <?php echo $use_pinecone ? '' : 'style="display: none;"'; ?>>
3115 +
3116 + <?php if ($use_pinecone) : ?>
3117 + <div class="mxchat-knowledge-warning success">
3118 + <p><span class="dashicons dashicons-yes-alt"></span>
3119 + <?php esc_html_e('Pinecone is enabled. All new knowledge base content will be stored in Pinecone.', 'mxchat'); ?>
3120 + </p>
3121 + </div>
3122 + <?php endif; ?>
3123 +
3124 + <div class="mxchat-form-group">
3125 + <label for="mxchat_pinecone_api_key">
3126 + <?php esc_html_e('Pinecone API Key', 'mxchat'); ?> <span class="required">*</span>
3127 + </label>
3128 + <input type="password"
3129 + id="mxchat_pinecone_api_key"
3130 + name="mxchat_pinecone_addon_options[mxchat_pinecone_api_key]"
3131 + value="<?php echo esc_attr($pinecone_options['mxchat_pinecone_api_key'] ?? ''); ?>"
3132 + class="regular-text"
3133 + placeholder="pcsk_..." />
3134 + <p class="description">
3135 + <?php esc_html_e('Found in your Pinecone dashboard under API Keys.', 'mxchat'); ?>
3136 + <a href="https://app.pinecone.io/" target="_blank" rel="noopener noreferrer"><?php esc_html_e('Open Pinecone Dashboard', 'mxchat'); ?></a>
3137 + </p>
3138 + </div>
3139 +
3140 + <div class="mxchat-form-group">
3141 + <label for="mxchat_pinecone_environment">
3142 + <?php esc_html_e('Region', 'mxchat'); ?>
3143 + </label>
3144 + <input type="text"
3145 + id="mxchat_pinecone_environment"
3146 + name="mxchat_pinecone_addon_options[mxchat_pinecone_environment]"
3147 + value="<?php echo esc_attr($pinecone_options['mxchat_pinecone_environment'] ?? ''); ?>"
3148 + placeholder="e.g., gcp-starter"
3149 + class="regular-text" />
3150 + <p class="description">
3151 + <?php esc_html_e('Your Pinecone environment/region (e.g., gcp-starter, us-west1-gcp, us-east-1-aws)', 'mxchat'); ?>
3152 + </p>
3153 + </div>
3154 +
3155 + <div class="mxchat-form-group">
3156 + <label for="mxchat_pinecone_index">
3157 + <?php esc_html_e('Index Name', 'mxchat'); ?> <span class="required">*</span>
3158 + </label>
3159 + <input type="text"
3160 + id="mxchat_pinecone_index"
3161 + name="mxchat_pinecone_addon_options[mxchat_pinecone_index]"
3162 + value="<?php echo esc_attr($pinecone_options['mxchat_pinecone_index'] ?? ''); ?>"
3163 + placeholder="e.g., my-wordpress-vectors"
3164 + class="regular-text" />
3165 + <p class="description">
3166 + <?php esc_html_e('The name of your Pinecone index. Must be created in your Pinecone dashboard first.', 'mxchat'); ?>
3167 + </p>
3168 + </div>
3169 +
3170 + <div class="mxchat-form-group">
3171 + <label for="mxchat_pinecone_host">
3172 + <?php esc_html_e('Pinecone Host', 'mxchat'); ?> <span class="required">*</span>
3173 + </label>
3174 + <input type="text"
3175 + id="mxchat_pinecone_host"
3176 + name="mxchat_pinecone_addon_options[mxchat_pinecone_host]"
3177 + value="<?php echo esc_attr($pinecone_options['mxchat_pinecone_host'] ?? ''); ?>"
3178 + placeholder="e.g., my-index-xyz123.svc.pinecone.io"
3179 + class="regular-text" />
3180 + <p class="description">
3181 + <?php esc_html_e('The hostname from your Pinecone index URL (exclude https://). Found in your index details.', 'mxchat'); ?>
3182 + </p>
3183 + </div>
3184 +
3185 + <div class="mxchat-setup-steps">
3186 + <h3><?php esc_html_e('Setup Instructions', 'mxchat'); ?></h3>
3187 + <div class="mxchat-setup-step">
3188 + <span class="step-number">1</span>
3189 + <div class="step-content">
3190 + <h4><?php esc_html_e('Create Account', 'mxchat'); ?></h4>
3191 + <p><?php esc_html_e('Create a free account at', 'mxchat'); ?> <a href="https://www.pinecone.io/" target="_blank">pinecone.io</a></p>
3192 + </div>
3193 + </div>
3194 + <div class="mxchat-setup-step">
3195 + <span class="step-number">2</span>
3196 + <div class="step-content">
3197 + <h4><?php esc_html_e('Create Index', 'mxchat'); ?></h4>
3198 + <p><?php esc_html_e('Create a new index with these settings:', 'mxchat'); ?></p>
3199 + <ul>
3200 + <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>
3201 + <li><?php esc_html_e('Metric: Cosine', 'mxchat'); ?></li>
3202 + <li><?php esc_html_e('Cloud & Region: Your preferred region', 'mxchat'); ?></li>
3203 + </ul>
3204 + </div>
3205 + </div>
3206 + <div class="mxchat-setup-step">
3207 + <span class="step-number">3</span>
3208 + <div class="step-content">
3209 + <h4><?php esc_html_e('Get API Key', 'mxchat'); ?></h4>
3210 + <p><?php esc_html_e('Copy your API key from the API Keys section', 'mxchat'); ?></p>
3211 + </div>
3212 + </div>
3213 + <div class="mxchat-setup-step">
3214 + <span class="step-number">4</span>
3215 + <div class="step-content">
3216 + <h4><?php esc_html_e('Get Index Host', 'mxchat'); ?></h4>
3217 + <p><?php esc_html_e('Copy your index host URL from the index details', 'mxchat'); ?></p>
3218 + </div>
3219 + </div>
3220 + <div class="mxchat-setup-step">
3221 + <span class="step-number">5</span>
3222 + <div class="step-content">
3223 + <h4><?php esc_html_e('Save Settings', 'mxchat'); ?></h4>
3224 + <p><?php esc_html_e('Fill in the form above and save settings', 'mxchat'); ?></p>
3225 + </div>
3226 + </div>
3227 + </div>
3228 +
3229 + </div>
3230 +
3231 + </div>
3232 + </div>
3233 + </div>
3234 +
3235 + </div>
3236 +
3237 + </div>
3238 + </div>
3239 +
3240 + <!-- Content Selector Modal -->
3241 + <div id="mxchat-kb-content-selector-modal" class="mxchat-kb-modal">
3242 + <div class="mxchat-kb-modal-content">
3243 + <div class="mxchat-kb-modal-header">
3244 + <h3>
3245 + <?php esc_html_e('Select WordPress Content', 'mxchat'); ?><br>
3246 + <span class="mxchat-kb-header-note"><?php esc_html_e('(Content imported here will be tagged "In Knowledge Base")', 'mxchat'); ?></span>
3247 + </h3>
3248 + <span class="mxchat-kb-modal-close">&times;</span>
3249 + </div>
3250 + <div class="mxchat-kb-modal-filters">
3251 + <div class="mxchat-kb-search-group">
3252 + <input type="text" id="mxchat-kb-content-search" placeholder="<?php esc_attr_e('Search...', 'mxchat'); ?>">
3253 + </div>
3254 +
3255 + <div class="mxchat-kb-filter-group">
3256 + <select id="mxchat-kb-content-type-filter">
3257 + <option value="all"><?php esc_html_e('All Content Types', 'mxchat'); ?></option>
3258 + <option value="post"><?php esc_html_e('Posts', 'mxchat'); ?></option>
3259 + <option value="page"><?php esc_html_e('Pages', 'mxchat'); ?></option>
3260 + <?php
3261 + // Add other post types dynamically
3262 + $post_types = get_post_types(array('public' => true), 'objects');
3263 + foreach ($post_types as $post_type) {
3264 + // Skip post and page as they're already added
3265 + if (!in_array($post_type->name, array('post', 'page'))) {
3266 + echo '<option value="' . esc_attr($post_type->name) . '">' . esc_html($post_type->label) . '</option>';
3267 + }
3268 + }
3269 + ?>
3270 + </select>
3271 +
3272 + <select id="mxchat-kb-content-status-filter">
3273 + <option value="publish"><?php esc_html_e('Published', 'mxchat'); ?></option>
3274 + <option value="draft"><?php esc_html_e('Drafts', 'mxchat'); ?></option>
3275 + <option value="all"><?php esc_html_e('All Statuses', 'mxchat'); ?></option>
3276 + </select>
3277 +
3278 + <select id="mxchat-kb-processed-filter">
3279 + <option value="all"><?php esc_html_e('All Content', 'mxchat'); ?></option>
3280 + <option value="processed"><?php esc_html_e('In Knowledge Base', 'mxchat'); ?></option>
3281 + <option value="unprocessed"><?php esc_html_e('Not In Knowledge Base', 'mxchat'); ?></option>
3282 + </select>
3283 + </div>
3284 + </div>
3285 +
3286 + <div class="mxchat-kb-content-selection">
3287 + <div class="mxchat-kb-selection-header">
3288 + <label>
3289 + <input type="checkbox" id="mxchat-kb-select-all">
3290 + <?php esc_html_e('Select All', 'mxchat'); ?>
3291 + </label>
3292 + <span class="mxchat-kb-selection-count">0 <?php esc_html_e('selected', 'mxchat'); ?></span>
3293 + </div>
3294 +
3295 + <div class="mxchat-kb-content-list">
3296 + <!-- Content will be loaded here via AJAX -->
3297 + <div class="mxchat-kb-loading">
3298 + <span class="mxchat-kb-spinner is-active"></span>
3299 + <?php esc_html_e('Loading content...', 'mxchat'); ?>
3300 + </div>
3301 + </div>
3302 +
3303 + <div class="mxchat-kb-pagination">
3304 + <!-- Pagination will be added here -->
3305 + </div>
3306 + </div>
3307 +
3308 + <div class="mxchat-kb-modal-footer">
3309 + <button type="button" id="mxchat-kb-process-selected" class="mxchat-kb-button-primary" disabled>
3310 + <?php esc_html_e('Process Selected Content', 'mxchat'); ?>
3311 + <span class="mxchat-kb-selected-count">(0)</span>
3312 + </button>
3313 + <button type="button" class="mxchat-kb-button-secondary mxchat-kb-modal-close">
3314 + <?php esc_html_e('Cancel', 'mxchat'); ?>
3315 + </button>
3316 + </div>
3317 + </div>
3318 + </div>
3319 +
3320 +<?php
3579 3321 }
3580 3322
3323 +
3324 +
3325 +
3326 +
3327 +
3581 3328 /**
3582 3329 * Get bot-specific Pinecone configuration
3583 3330 * Used in the knowledge retrieval functions
3584 3331 */
@@ -3621,254 +3368,49 @@
3621 3368 echo wp_json_encode(['error' => esc_html__('You do not have sufficient permissions.', 'mxchat')]);
3622 3369 wp_die();
3623 3370 }
3624 3371 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 3372 global $wpdb;
3632 3373 $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 3374
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';
3375 + if (isset($_POST['delete_session_ids']) && is_array($_POST['delete_session_ids'])) {
3376 + $deleted_count = 0;
3640 3377
3641 - $deleted_count = 0;
3642 - $preserved_as_deleted_leads = 0;
3643 - $emails_to_fully_wipe = [];
3378 + foreach ($_POST['delete_session_ids'] as $session_id) {
3379 + $session_id_sanitized = sanitize_text_field($session_id);
3644 3380
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 - }
3381 + // Clear relevant cache before deletion
3382 + $cache_key = 'chat_session_' . $session_id_sanitized;
3383 + wp_cache_delete($cache_key, 'mxchat_chat_sessions');
3650 3384
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 - ));
3385 + // Perform the deletion from the database table
3386 + $wpdb->delete($table_name, ['session_id' => $session_id_sanitized]);
3660 3387
3661 - wp_cache_delete('chat_session_' . $session_id_sanitized, 'mxchat_chat_sessions');
3662 - $wpdb->delete($table_name, ['session_id' => $session_id_sanitized]);
3388 + // Delete the corresponding option entry from wp_options table
3389 + delete_option("mxchat_history_" . $session_id_sanitized);
3663 3390
3664 - if ($has_translations) {
3665 - $wpdb->delete($translations_table, ['session_id' => $session_id_sanitized]);
3666 - }
3391 + // Delete any associated metadata options
3392 + delete_option("mxchat_email_" . $session_id_sanitized);
3393 + delete_option("mxchat_agent_name_" . $session_id_sanitized);
3667 3394
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);
3395 + $deleted_count++;
3698 3396 }
3699 3397
3700 - $deleted_count++;
3701 - }
3398 + // Optionally, clear a general cache if you have one
3399 + wp_cache_delete('all_chat_sessions', 'mxchat_chat_sessions');
3702 3400
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));
3708 - }
3709 -
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 - wp_die();
3721 -}
3722 -
3723 -/**
3724 - * Format transcript message content with markdown processing
3725 - * Converts markdown links, bold, italic, code blocks, and plain URLs to HTML
3726 - */
3727 -private function format_transcript_message($text) {
3728 - if (empty($text)) {
3729 - return '';
3730 - }
3731 -
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 - // Process markdown headers (# Header)
3744 - $text = preg_replace_callback('/^(#{1,6})\s+(.+)$/m', function($matches) {
3745 - $level = strlen($matches[1]);
3746 - $content = esc_html(trim($matches[2]));
3747 - return "<h{$level}>{$content}</h{$level}>";
3748 - }, $text);
3749 -
3750 - // Process code blocks with triple backticks
3751 - $text = preg_replace_callback('/```(\w+)?\n?([\s\S]*?)```/', function($matches) {
3752 - $language = !empty($matches[1]) ? ' class="language-' . esc_attr($matches[1]) . '"' : '';
3753 - $code = esc_html($matches[2]);
3754 - return "<pre><code{$language}>{$code}</code></pre>";
3755 - }, $text);
3756 -
3757 - // Process inline code with single backticks
3758 - $text = preg_replace('/`([^`]+)`/', '<code>$1</code>', $text);
3759 -
3760 - // Process bold text **text** or __text__
3761 - $text = preg_replace('/\*\*(.+?)\*\*/', '<strong>$1</strong>', $text);
3762 - $text = preg_replace('/__(.+?)__/', '<strong>$1</strong>', $text);
3763 -
3764 - // Process italic text *text* or _text_ (but not if part of URL)
3765 - $text = preg_replace('/(?<![*_\w])\*([^*]+)\*(?![*\w])/', '<em>$1</em>', $text);
3766 - $text = preg_replace('/(?<![*_\w])_([^_]+)_(?![*\w])/', '<em>$1</em>', $text);
3767 -
3768 - // Process markdown links [text](url)
3769 - $text = preg_replace_callback('/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/', function($matches) {
3770 - $link_text = esc_html($matches[1]);
3771 - $url = esc_url($matches[2]);
3772 - return "<a href=\"{$url}\" target=\"_blank\" rel=\"noopener\">{$link_text}</a>";
3773 - }, $text);
3774 -
3775 - // Process citation-style brackets [URL]
3776 - $text = preg_replace_callback('/\[(https?:\/\/[^\]]+)\]/', function($matches) {
3777 - $url = esc_url($matches[1]);
3778 - return "<a href=\"{$url}\" target=\"_blank\" rel=\"noopener\">{$url}</a>";
3779 - }, $text);
3780 -
3781 - // Process standalone URLs (not already in links or img src)
3782 - $text = preg_replace_callback(
3783 - '/(?<!href="|src="|">)(https?:\/\/[^\s<>"]+)(?![^<]*<\/a>)/',
3784 - function($matches) {
3785 - $url = esc_url($matches[1]);
3786 - // Truncate display URL if too long
3787 - $display = strlen($matches[1]) > 50 ? substr($matches[1], 0, 47) . '...' : $matches[1];
3788 - return "<a href=\"{$url}\" target=\"_blank\" rel=\"noopener\">{$display}</a>";
3789 - },
3790 - $text
3791 - );
3792 -
3793 - // Process mailto links
3794 - $text = preg_replace_callback('/\[([^\]]+)\]\((mailto:[^)]+)\)/', function($matches) {
3795 - $link_text = esc_html($matches[1]);
3796 - $mailto = esc_url($matches[2]);
3797 - return "<a href=\"{$mailto}\">{$link_text}</a>";
3798 - }, $text);
3799 -
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);
3803 -
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>';
3401 + echo wp_json_encode([
3402 + 'success' => sprintf(
3403 + esc_html__('%d chat session(s) have been deleted from all storage locations.', 'mxchat'),
3404 + $deleted_count
3405 + )
3406 + ]);
3818 3407 } else {
3819 - // Single paragraph - just convert newlines to <br>
3820 - $text = nl2br($paragraphs[0]);
3408 + echo wp_json_encode(['error' => esc_html__('No chat sessions selected for deletion.', 'mxchat')]);
3821 3409 }
3822 3410
3823 - return $text;
3824 -}
3825 -
3826 -/**
3827 - * AJAX handler to fetch RAG context for a specific message
3828 - * Used by the transcript viewer to show retrieved documents
3829 - */
3830 -public function mxchat_get_rag_context() {
3831 - // Check permissions
3832 - if (!current_user_can('manage_options')) {
3833 - wp_send_json_error(['message' => esc_html__('You do not have sufficient permissions.', 'mxchat')]);
3834 - wp_die();
3835 - }
3836 -
3837 - // Validate message ID
3838 - if (!isset($_POST['message_id']) || empty($_POST['message_id'])) {
3839 - wp_send_json_error(['message' => esc_html__('Message ID is required.', 'mxchat')]);
3840 - wp_die();
3841 - }
3842 -
3843 - $message_id = absint($_POST['message_id']);
3844 -
3845 - global $wpdb;
3846 - $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
3847 -
3848 - // Fetch the RAG context for this message
3849 - $result = $wpdb->get_row($wpdb->prepare(
3850 - "SELECT rag_context FROM {$table_name} WHERE id = %d",
3851 - $message_id
3852 - ));
3853 -
3854 - if (!$result || empty($result->rag_context)) {
3855 - wp_send_json_error(['message' => esc_html__('No RAG context found for this message.', 'mxchat')]);
3856 - wp_die();
3857 - }
3858 -
3859 - // Decode the JSON data
3860 - $rag_context = json_decode($result->rag_context, true);
3861 -
3862 - if (json_last_error() !== JSON_ERROR_NONE) {
3863 - wp_send_json_error(['message' => esc_html__('Invalid RAG context data.', 'mxchat')]);
3864 - wp_die();
3865 - }
3866 -
3867 - wp_send_json_success($rag_context);
3868 3411 wp_die();
3869 3412 }
3870 -
3871 3413 public function display_admin_notices() {
3872 3414 // Check if we're on a MXChat admin page
3873 3415 $screen = get_current_screen();
3874 3416 if (!$screen || strpos($screen->base, 'mxchat') === false) {
@@ -3874,44 +3416,173 @@
3874 3416 if (!$screen || strpos($screen->base, 'mxchat') === false) {
3875 3417 return;
3876 3418 }
3877 3419
3878 - $dismiss_button = '<button type="button" class="notice-dismiss"><span class="screen-reader-text">' . esc_html__('Dismiss this notice.', 'mxchat') . '</span></button>';
3420 + //error_log('MxChat admin_notices hook fired on screen: ' . $screen->base);
3879 3421
3880 3422 // Check for error notices
3881 3423 $error_notice = get_transient('mxchat_admin_notice_error');
3882 3424 if ($error_notice) {
3883 - echo '<div class="notice notice-error is-dismissible"><p>' . wp_kses_post($error_notice) . '</p>' . $dismiss_button . '</div>';
3425 + //error_log('Found error transient: ' . $error_notice);
3426 + echo '<div class="notice notice-error is-dismissible"><p>' . wp_kses_post($error_notice) . '</p></div>';
3884 3427 delete_transient('mxchat_admin_notice_error');
3428 + //error_log('Displayed and deleted error transient');
3429 + } else {
3430 + //error_log('No error transient found');
3885 3431 }
3886 3432
3887 3433 // Check for success notices
3888 3434 $success_notice = get_transient('mxchat_admin_notice_success');
3889 3435 if ($success_notice) {
3890 - echo '<div class="notice notice-success is-dismissible"><p>' . wp_kses_post($success_notice) . '</p>' . $dismiss_button . '</div>';
3436 + //error_log('Found success transient: ' . $success_notice);
3437 + echo '<div class="notice notice-success is-dismissible"><p>' . wp_kses_post($success_notice) . '</p></div>';
3891 3438 delete_transient('mxchat_admin_notice_success');
3439 + //error_log('Displayed and deleted success transient');
3892 3440 }
3893 3441
3894 3442 // Check for info notices
3895 3443 $info_notice = get_transient('mxchat_admin_notice_info');
3896 3444 if ($info_notice) {
3897 - echo '<div class="notice notice-info is-dismissible"><p>' . wp_kses_post($info_notice) . '</p>' . $dismiss_button . '</div>';
3445 + //error_log('Found info transient: ' . $info_notice);
3446 + echo '<div class="notice notice-info is-dismissible"><p>' . wp_kses_post($info_notice) . '</p></div>';
3898 3447 delete_transient('mxchat_admin_notice_info');
3448 + //error_log('Displayed and deleted info transient');
3899 3449 }
3450 +
3900 3451 }
3901 3452
3902 3453
3903 3454 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';
3455 + $license_status = get_option('mxchat_license_status', 'inactive');
3456 + $license_error = get_option('mxchat_license_error', '');
3457 + $current_domain = parse_url(home_url(), PHP_URL_HOST);
3906 3458
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();
3459 + $license_management_url = 'https://mxchat.ai/my-account/orders/';
3911 3460
3912 - // Render the consolidated Pro & Extensions page
3913 - mxchat_render_pro_page($this, $addons_config);
3461 + // Check if this activation has been linked to a domain
3462 + $is_domain_linked = $this->is_current_activation_linked($current_domain);
3463 + ?>
3464 + <div class="wrap mxchat-admin-activation">
3465 + <div class="mxchat-pro-hero">
3466 + <h1 class="pro-title">
3467 + <span class="pro-gradient-text">Manage</span> MxChat Pro
3468 + </h1>
3469 + <p class="pro-subtitle">
3470 + <?php if ($license_status === 'active'): ?>
3471 + <?php esc_html_e('Your pro license is active. Manage your activation below.', 'mxchat'); ?>
3472 + <?php else: ?>
3473 + <?php esc_html_e('Enter your license key to unlock premium features, advanced AI capabilities, and priority support.', 'mxchat'); ?>
3474 + <?php endif; ?>
3475 + </p>
3476 + </div>
3477 +
3478 + <?php if ($license_status === 'inactive' && !empty($license_error)): ?>
3479 + <div class="error notice">
3480 + <p><?php echo esc_html($license_error); ?></p>
3481 + </div>
3482 + <?php endif; ?>
3483 +
3484 + <!-- Domain Linking Notice for Existing Users -->
3485 + <?php if ($license_status === 'active' && !$is_domain_linked): ?>
3486 + <div class="notice notice-info">
3487 + <p>
3488 + <strong><?php esc_html_e('New Feature: Domain Tracking', 'mxchat'); ?></strong><br>
3489 + <?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'); ?>
3490 + </p>
3491 + <p>
3492 + <button type="button" id="link-domain-button" class="button button-secondary">
3493 + <?php esc_html_e('Link This Domain', 'mxchat'); ?>
3494 + </button>
3495 + <span id="domain-link-status" style="margin-left: 10px;"></span>
3496 + </p>
3497 + </div>
3498 + <?php endif; ?>
3499 +
3500 + <!-- Activation Form -->
3501 + <form id="mxchat-activation-form" class="mxchat-pro-form" style="<?php echo $license_status === 'active' ? 'display: none;' : ''; ?>">
3502 + <div class="mxchat-pro-form-container">
3503 + <table class="form-table">
3504 + <tr valign="top">
3505 + <th scope="row"><?php esc_html_e('Email Address', 'mxchat'); ?></th>
3506 + <td>
3507 + <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 />
3508 + </td>
3509 + </tr>
3510 + <tr valign="top">
3511 + <th scope="row"><?php esc_html_e('Activation Key', 'mxchat'); ?></th>
3512 + <td>
3513 + <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 />
3514 + </td>
3515 + </tr>
3516 + </table>
3517 +
3518 + <!-- Hidden field for domain -->
3519 + <input type="hidden" id="mxchat_domain" name="domain" value="<?php echo esc_attr($current_domain); ?>" />
3520 +
3521 + <?php if ($license_status !== 'active'): ?>
3522 + <div class="mxchat-pro-button-container">
3523 + <button type="submit" id="activate_license_button" class="button button-primary mxchat-pro-button"><?php esc_html_e('Activate License', 'mxchat'); ?></button>
3524 + <div id="mxchat-activation-spinner" class="mxchat-activation-spinner" style="display: none;"></div>
3525 + </div>
3526 + <?php endif; ?>
3527 + </div>
3528 + </form>
3529 +
3530 + <!-- License Status Display -->
3531 + <div class="mxchat-pro-status">
3532 + <h3><?php esc_html_e('License Status:', 'mxchat'); ?>
3533 + <span id="mxchat-license-status" class="mxchat-status-badge <?php echo $license_status; ?>">
3534 + <?php echo $license_status === 'active' ? esc_html__('Active', 'mxchat') : esc_html__('Inactive', 'mxchat'); ?>
3535 + </span>
3536 + </h3>
3537 +
3538 + <?php if ($license_status === 'active'): ?>
3539 + <div class="mxchat-license-details">
3540 + <p><strong><?php esc_html_e('Domain:', 'mxchat'); ?></strong> <?php echo esc_html($current_domain); ?>
3541 + <?php if ($is_domain_linked): ?>
3542 + <span style="color: green; margin-left: 10px;">✓ <?php esc_html_e('Linked', 'mxchat'); ?></span>
3543 + <?php else: ?>
3544 + <span style="color: orange; margin-left: 10px;">⚠ <?php esc_html_e('Not Linked', 'mxchat'); ?></span>
3545 + <?php endif; ?>
3546 + </p>
3547 + <p><strong><?php esc_html_e('Email:', 'mxchat'); ?></strong> <?php echo esc_html(get_option('mxchat_pro_email')); ?></p>
3548 + <p><strong><?php esc_html_e('License Key:', 'mxchat'); ?></strong>
3549 + <code style="background: #f1f1f1; padding: 2px 6px; border-radius: 3px;">
3550 + <?php echo esc_html(get_option('mxchat_activation_key')); ?>
3551 + </code>
3552 + </p>
3553 + <p>
3554 + <small>
3555 + <?php esc_html_e('Manage all your licenses and domains on', 'mxchat'); ?>
3556 + <a href="<?php echo esc_url($license_management_url); ?>" target="_blank">
3557 + <?php esc_html_e('your account dashboard', 'mxchat'); ?>
3558 + </a>
3559 + </small>
3560 + </p>
3561 +
3562 + <!-- Deactivation Section -->
3563 + <div class="mxchat-deactivation-section" style="margin-top: 20px; padding: 15px; background: #fff3cd; border-left: 4px solid #ffc107; border-radius: 4px;">
3564 + <h4 style="margin-top: 0; color: #856404;">
3565 + <?php esc_html_e('License Management', 'mxchat'); ?>
3566 + </h4>
3567 + <p style="margin-bottom: 15px; color: #856404;">
3568 + <?php esc_html_e('Need to move this license to another site? Deactivate it here first, then activate it on your new site.', 'mxchat'); ?>
3569 + </p>
3570 + <button type="button" id="deactivate-license-button" class="button button-secondary" style="background: #dc3545; color: white; border-color: #dc3545;">
3571 + 🔓 <?php esc_html_e('Deactivate License', 'mxchat'); ?>
3572 + </button>
3573 + <span id="deactivate-status" style="margin-left: 10px;"></span>
3574 + </div>
3575 + </div>
3576 + <?php endif; ?>
3577 + </div>
3578 +
3579 + <!-- Hidden fields for JavaScript -->
3580 + <input type="hidden" id="mxchat-ajax-url" value="<?php echo admin_url('admin-ajax.php'); ?>" />
3581 + <input type="hidden" id="mxchat-nonce" value="<?php echo wp_create_nonce('mxchat_activate_license_nonce'); ?>" />
3582 + <input type="hidden" id="mxchat-api-url" value="https://mxchat.ai" />
3583 + </div>
3584 + <?php
3914 3585 }
3915 3586
3916 3587 /**
3917 3588 * Check if current activation is linked to a domain
@@ -3916,9 +3587,9 @@
3916 3587 /**
3917 3588 * Check if current activation is linked to a domain
3918 3589 * This checks YOUR website's database, not the user's local database
3919 3590 */
3920 -public function is_current_activation_linked($domain) {
3591 +private function is_current_activation_linked($domain) {
3921 3592 $license_key = get_option('mxchat_activation_key');
3922 3593 $email = get_option('mxchat_pro_email');
3923 3594
3924 3595 if (empty($license_key) || empty($email)) {
@@ -3949,54 +3620,50 @@
3949 3620 if (!current_user_can('manage_options')) {
3950 3621 return;
3951 3622 }
3952 3623
3624 + // Keep existing data fetching logic
3953 3625 global $wpdb;
3954 3626 $table_name = $wpdb->prefix . 'mxchat_intents';
3627 + $page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1;
3628 + $per_page = 20;
3629 + $offset = ($page - 1) * $per_page;
3955 3630
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;
3631 + // Success message
3632 + if (isset($_GET['updated']) && $_GET['updated'] === 'true') {
3633 + echo '<div class="notice notice-success is-dismissible"><p>' .
3634 + esc_html__('Action updated successfully.', 'mxchat') .
3635 + '</p></div>';
3636 + }
3960 3637
3961 - // Get unique action types count
3962 - $action_types_count = $wpdb->get_var("SELECT COUNT(DISTINCT callback_function) FROM $table_name");
3638 + // Filtering logic
3639 + $where = '1=1';
3640 + $search_term = isset($_GET['s']) ? trim($_GET['s']) : '';
3641 + $callback_filter = isset($_GET['callback_filter']) ? sanitize_text_field($_GET['callback_filter']) : '';
3963 3642
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);
3643 + if ($search_term) {
3644 + $search_term_like = '%' . $wpdb->esc_like($search_term) . '%';
3645 + $where .= $wpdb->prepare(' AND (intent_label LIKE %s OR phrases LIKE %s)',
3646 + $search_term_like, $search_term_like);
3647 + }
3968 3648
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;
3649 + if ($callback_filter) {
3650 + $where .= $wpdb->prepare(' AND callback_function = %s', $callback_filter);
3975 3651 }
3976 3652
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 - );
3653 + // Pagination
3654 + $total_intents = $wpdb->get_var("SELECT COUNT(*) FROM $table_name WHERE $where");
3655 + $total_pages = ceil($total_intents / $per_page);
3987 3656
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 -}
3657 + // Get intents (now called actions)
3658 + $actions = $wpdb->get_results($wpdb->prepare(
3659 + "SELECT * FROM $table_name WHERE $where LIMIT %d OFFSET %d",
3660 + $per_page, $offset
3661 + ));
3992 3662
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
3663 + // Get callbacks
3664 + $available_callbacks = $this->mxchat_get_available_callbacks();
3665 +
3999 3666 ?>
4000 3667 <div class="wrap mxchat-wrapper">
4001 3668 <!-- Hero Section -->
4002 3669 <div class="mxchat-hero">
@@ -4272,14 +3939,14 @@
4272 3939 $category_slug = sanitize_title($group_label);
4273 3940
4274 3941 foreach ($group_callbacks as $function => $data) :
4275 3942 $label = $data['label'];
3943 + $pro_only = $data['pro_only'];
4276 3944 $icon = isset($data['icon']) ? $data['icon'] : 'admin-generic';
4277 3945 $description = isset($data['description']) ? $data['description'] : '';
4278 3946 $is_addon = isset($data['addon']) && $data['addon'] !== false;
4279 3947 $addon_name = isset($data['addon_name']) ? $data['addon_name'] : '';
4280 3948 $is_installed = isset($data['installed']) ? $data['installed'] : true;
4281 - $is_promo = !empty($data['addon_promo']) && !$is_installed;
4282 3949
4283 3950 // Determine card status and styling
4284 3951 $card_class = 'mxchat-action-type-card';
4285 3952 $icon_class = 'mxchat-action-type-icon';
@@ -4284,13 +3951,15 @@
4284 3951 $card_class = 'mxchat-action-type-card';
4285 3952 $icon_class = 'mxchat-action-type-icon';
4286 3953 $status_badge = '';
4287 3954
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) {
3955 + if ($pro_only && !$this->is_activated) {
3956 + // Pro feature but no Pro license
3957 + $icon_class .= ' pro-feature';
3958 + $status_badge = '<span class="mxchat-pro-badge">' . esc_html__('Pro', 'mxchat') . '</span>';
3959 + }
3960 +
3961 + if ($is_addon && !$is_installed) {
4293 3962 // Add-on not installed
4294 3963 $card_class .= ' not-installed';
4295 3964 $status_badge .= '<span class="mxchat-addon-badge">' . esc_html__('Add-on Required', 'mxchat') . '</span>';
4296 3965 }
@@ -4304,16 +3973,13 @@
4304 3973 }
4305 3974 ?>
4306 3975 <div class="<?php echo esc_attr($card_class); ?>"
4307 3976 data-category="<?php echo esc_attr($category_slug); ?>"
4308 - <?php if (!$is_promo) : ?>
4309 3977 data-value="<?php echo esc_attr($function); ?>"
4310 3978 data-label="<?php echo esc_attr($label); ?>"
4311 - <?php endif; ?>
4312 - data-pro="false"
3979 + data-pro="<?php echo $pro_only ? 'true' : 'false'; ?>"
4313 3980 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; ?>>
3981 + data-installed="<?php echo $is_installed ? 'true' : 'false'; ?>">
4316 3982 <div class="<?php echo esc_attr($icon_class); ?>">
4317 3983 <span class="dashicons dashicons-<?php echo esc_attr($icon); ?>"></span>
4318 3984 </div>
4319 3985 <div class="mxchat-action-type-info">
@@ -4322,15 +3988,14 @@
4322 3988 <?php if (!empty($status_badge)) : ?>
4323 3989 <?php echo $status_badge; ?>
4324 3990 <?php endif; ?>
4325 3991
4326 - <?php if ($is_promo || ($is_addon && !$is_installed)) : ?>
3992 + <?php if ($is_addon && !$is_installed) : ?>
4327 3993 <div class="mxchat-addon-info">
4328 3994 <?php echo esc_html(sprintf(
4329 3995 __('Requires %s', 'mxchat'),
4330 3996 $addon_name
4331 3997 )); ?>
4332 - — <a href="https://mxchat.ai/" target="_blank"><?php esc_html_e('Get Add-on', 'mxchat'); ?></a>
4333 3998 </div>
4334 3999 <?php endif; ?>
4335 4000 </div>
4336 4001 </div>
@@ -4593,9 +4258,9 @@
4593 4258 'intent_label' => $intent_label,
4594 4259 'phrases' => implode(', ', $phrases_array),
4595 4260 'embedding_vector' => $serialized_vector,
4596 4261 'similarity_threshold' => $similarity_threshold,
4597 - 'enabled_bots' => $enabled_bots_json, // Include enabled_bots in update
4262 + 'enabled_bots' => $enabled_bots_json // Include enabled_bots in update
4598 4263 ),
4599 4264 array('id' => $intent_id),
4600 4265 array('%s', '%s', '%s', '%f', '%s'), // Format: string, string, string, float, string
4601 4266 array('%d') // Where format: integer
@@ -4657,15 +4322,12 @@
4657 4322 $this->handle_embedding_error(__('Invalid callback function selected.', 'mxchat'));
4658 4323 return;
4659 4324 }
4660 4325
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 - ));
4326 + // Check Pro requirements
4327 + $is_pro_only = $available_callbacks[$callback_function]['pro_only'];
4328 + if ($is_pro_only && !$this->is_activated) {
4329 + $this->handle_embedding_error(__('This callback function is available in the Pro version only.', 'mxchat'));
4668 4330 return;
4669 4331 }
4670 4332
4671 4333 // Process phrases
@@ -4712,9 +4374,9 @@
4712 4374 'phrases' => implode(', ', $phrases_array),
4713 4375 'embedding_vector' => $serialized_vector,
4714 4376 'callback_function' => $callback_function,
4715 4377 'similarity_threshold' => $similarity_threshold,
4716 - 'enabled_bots' => $enabled_bots_json, // NEW field
4378 + 'enabled_bots' => $enabled_bots_json // NEW field
4717 4379 ]);
4718 4380
4719 4381 if ($result === false) {
4720 4382 $this->handle_embedding_error(__('Database error: ', 'mxchat') . $wpdb->last_error);
@@ -4746,577 +4408,10 @@
4746 4408 exit;
4747 4409 }
4748 4410 }
4749 4411
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 4412
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 4413 /**
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 4414 * Enhanced get_available_callbacks function with form action exclusion
5320 4415 *
5321 4416 * @param bool $grouped Whether to return callbacks grouped by category
5322 4417 * @param bool $include_all Whether to include all potential actions (even if add-on not installed)
@@ -5354,10 +4449,9 @@
5354 4449 'mxchat_handle_product_recommendations',
5355 4450 'mxchat_handle_order_history',
5356 4451 'mxchat_show_product_card',
5357 4452 'mxchat_add_to_cart',
5358 - 'mxchat_checkout_redirect',
5359 - 'mxchat_handle_featured_products'
4453 + 'mxchat_checkout_redirect'
5360 4454 ),
5361 4455 'name' => __('WooCommerce Add-on', 'mxchat'),
5362 4456 'pro_required' => true
5363 4457 ),
@@ -5435,25 +4529,16 @@
5435 4529 'installed' => true
5436 4530 ),
5437 4531 // Pro core features - check is_activated property
5438 4532 'mxchat_generate_image' => array(
5439 - 'label' => __('Generate Image (OpenAI)', 'mxchat'),
4533 + 'label' => __('Generate Image', 'mxchat'),
5440 4534 'pro_only' => false,
5441 4535 'group' => __('Other Features', 'mxchat'),
5442 4536 'icon' => 'art',
5443 - 'description' => __('Create images with GPT Image from OpenAI (requires OpenAI API key)', 'mxchat'),
4537 + 'description' => __('Create images with DALL-E 3 from OpenAI (requires OpenAI API key)', 'mxchat'),
5444 4538 'addon' => false,
5445 4539 'installed' => true
5446 4540 ),
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 4541 'mxchat_handle_pdf_discussion' => array(
5457 4542 'label' => __('Chat with PDF', 'mxchat'),
5458 4543 'pro_only' => false,
5459 4544 'group' => __('Other Features', 'mxchat'),
@@ -5470,17 +4555,8 @@
5470 4555 'description' => __('Transfer conversation to a human support agent on Slack', 'mxchat'),
5471 4556 'addon' => false,
5472 4557 'installed' => true
5473 4558 ),
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 4559 'mxchat_handle_switch_to_chatbot_intent' => array(
5484 4560 'label' => __('Back to Chatbot', 'mxchat'),
5485 4561 'pro_only' => false,
5486 4562 'group' => __('Customer Engagement', 'mxchat'),
@@ -5491,15 +4567,13 @@
5491 4567 ),
5492 4568 );
5493 4569
5494 4570 // 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 4571 $addon_callbacks = array(
5497 4572 // WooCommerce Add-on
5498 4573 'mxchat_handle_product_recommendations' => array(
5499 4574 'label' => __('Product Recommendations', 'mxchat'),
5500 - 'pro_only' => false,
5501 - 'addon_promo' => true,
4575 + 'pro_only' => true,
5502 4576 'group' => __('WooCommerce Features', 'mxchat'),
5503 4577 'icon' => 'cart',
5504 4578 'description' => __('Suggest products based on customer preferences', 'mxchat'),
5505 4579 ),
@@ -5504,10 +4578,9 @@
5504 4578 'description' => __('Suggest products based on customer preferences', 'mxchat'),
5505 4579 ),
5506 4580 'mxchat_handle_order_history' => array(
5507 4581 'label' => __('Order History', 'mxchat'),
5508 - 'pro_only' => false,
5509 - 'addon_promo' => true,
4582 + 'pro_only' => true,
5510 4583 'group' => __('WooCommerce Features', 'mxchat'),
5511 4584 'icon' => 'clipboard',
5512 4585 'description' => __('Allow customers to check their order status', 'mxchat'),
5513 4586 ),
@@ -5512,10 +4585,9 @@
5512 4585 'description' => __('Allow customers to check their order status', 'mxchat'),
5513 4586 ),
5514 4587 'mxchat_show_product_card' => array(
5515 4588 'label' => __('Show Product Card', 'mxchat'),
5516 - 'pro_only' => false,
5517 - 'addon_promo' => true,
4589 + 'pro_only' => true,
5518 4590 'group' => __('WooCommerce Features', 'mxchat'),
5519 4591 'icon' => 'products',
5520 4592 'description' => __('Display product information in the chat', 'mxchat'),
5521 4593 ),
@@ -5520,10 +4592,9 @@
5520 4592 'description' => __('Display product information in the chat', 'mxchat'),
5521 4593 ),
5522 4594 'mxchat_add_to_cart' => array(
5523 4595 'label' => __('Add to Cart', 'mxchat'),
5524 - 'pro_only' => false,
5525 - 'addon_promo' => true,
4596 + 'pro_only' => true,
5526 4597 'group' => __('WooCommerce Features', 'mxchat'),
5527 4598 'icon' => 'plus-alt',
5528 4599 'description' => __('Add products to cart directly from chat', 'mxchat'),
5529 4600 ),
@@ -5528,38 +4599,27 @@
5528 4599 'description' => __('Add products to cart directly from chat', 'mxchat'),
5529 4600 ),
5530 4601 'mxchat_checkout_redirect' => array(
5531 4602 'label' => __('Proceed to Checkout', 'mxchat'),
5532 - 'pro_only' => false,
5533 - 'addon_promo' => true,
4603 + 'pro_only' => true,
5534 4604 'group' => __('WooCommerce Features', 'mxchat'),
5535 4605 'icon' => 'arrow-right-alt',
5536 4606 'description' => __('Redirect customer to checkout page', 'mxchat'),
5537 4607 ),
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 4608
5547 4609 // Perplexity Add-on
5548 4610 'mxchat_perplexity_research' => array(
5549 4611 'label' => __('Perplexity Research', 'mxchat'),
5550 - 'pro_only' => false,
5551 - 'addon_promo' => true,
4612 + 'pro_only' => true,
5552 4613 'group' => __('Search Features', 'mxchat'),
5553 4614 'icon' => 'book-alt',
5554 4615 'description' => __('Allows the chatbot to search the web for accurate, up-to-date answers', 'mxchat'),
5555 4616 ),
5556 4617
5557 - // Forms Add-on
4618 + // Forms Add-on (only shown when Pro is not activated)
5558 4619 'mxchat_handle_form_collection' => array(
5559 4620 'label' => __('Form Collection', 'mxchat'),
5560 - 'pro_only' => false,
5561 - 'addon_promo' => true,
4621 + 'pro_only' => true,
5562 4622 'group' => __('Form Features', 'mxchat'),
5563 4623 'icon' => 'feedback',
5564 4624 'description' => __('Collect user information through custom forms in chat', 'mxchat'),
5565 4625 ),
@@ -5813,27 +4873,9 @@
5813 4873 'mxchat-chatbot', // Page
5814 4874 'mxchat_chatbot_section' // Section
5815 4875 );
5816 4876
5817 - // RAG Sources Limit Slider
5818 4877 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 4878 'append_to_body',
5837 4879 esc_html__('Auto-Display Chatbot', 'mxchat'),
5838 4880 array($this, 'mxchat_append_to_body_callback'),
5839 4881 'mxchat-chatbot',
@@ -5848,16 +4890,8 @@
5848 4890 'mxchat_chatbot_section'
5849 4891 );
5850 4892
5851 4893 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 4894 'enable_streaming_toggle',
5861 4895 esc_html__('Enable Streaming', 'mxchat'),
5862 4896 array($this, 'enable_streaming_toggle_callback'),
5863 4897 'mxchat-chatbot',
@@ -6261,64 +5295,11 @@
6261 5295 'mxchat-embed',
6262 5296 'mxchat_live_agent_section'
6263 5297 );
6264 5298
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 5299
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 5300
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 5301
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 5302 // General Settings Section
6322 5303 add_settings_section(
6323 5304 'mxchat_general_section',
6324 5305 esc_html__('YouTube Tutorials', 'mxchat'),
@@ -6478,9 +5459,8 @@
6478 5459
6479 5460 ?>
6480 5461 <div class="notice notice-success is-dismissible">
6481 5462 <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 5463 </div>
6484 5464 <?php
6485 5465
6486 5466 }
@@ -6629,12 +5609,11 @@
6629 5609
6630 5610 // OpenAI API Key
6631 5611 public function api_key_callback() {
6632 5612 $apiKey = isset($this->options['api_key']) ? esc_attr($this->options['api_key']) : '';
6633 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
6634 5613
6635 5614 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 . '" />';
5615 + echo '<input type="password" id="api_key" name="api_key" value="' . $apiKey . '" class="regular-text" autocomplete="off" />';
6637 5616 echo '<button type="button" id="toggleApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6638 5617 echo '<p class="description">' . esc_html__('Required for OpenAI GPT models and OpenAI embeddings. Get your API key from OpenAI Platform.', 'mxchat') . '</p>';
6639 5618 echo '</div>';
6640 5619 }
@@ -6641,12 +5620,11 @@
6641 5620
6642 5621 // X.AI API Key
6643 5622 public function xai_api_key_callback() {
6644 5623 $xaiApiKey = isset($this->options['xai_api_key']) ? esc_attr($this->options['xai_api_key']) : '';
6645 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
6646 5624
6647 5625 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 . '" />';
5626 + echo '<input type="password" id="xai_api_key" name="xai_api_key" value="' . $xaiApiKey . '" class="regular-text" autocomplete="off" />';
6649 5627 echo '<button type="button" id="toggleXaiApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6650 5628 echo '<p class="description">' . esc_html__('Required for X.AI Grok models. Get your API key from X.AI Console.', 'mxchat') . '</p>';
6651 5629 echo '</div>';
6652 5630 }
@@ -6652,12 +5630,11 @@
6652 5630 }
6653 5631 // Claude API Key
6654 5632 public function claude_api_key_callback() {
6655 5633 $claudeApiKey = isset($this->options['claude_api_key']) ? esc_attr($this->options['claude_api_key']) : '';
6656 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
6657 5634
6658 5635 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 . '" />';
5636 + echo '<input type="password" id="claude_api_key" name="claude_api_key" value="' . $claudeApiKey . '" class="regular-text" autocomplete="off" />';
6660 5637 echo '<button type="button" id="toggleClaudeApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6661 5638 echo '<p class="description">' . esc_html__('Required for Anthropic Claude models. Get your API key from Anthropic Console.', 'mxchat') . '</p>';
6662 5639 echo '</div>';
6663 5640 }
@@ -6664,12 +5641,11 @@
6664 5641
6665 5642 // DeepSeek API Key
6666 5643 public function deepseek_api_key_callback() {
6667 5644 $apiKey = isset($this->options['deepseek_api_key']) ? esc_attr($this->options['deepseek_api_key']) : '';
6668 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
6669 5645
6670 5646 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 . '" />';
5647 + echo '<input type="password" id="deepseek_api_key" name="deepseek_api_key" value="' . $apiKey . '" class="regular-text" autocomplete="off" />';
6672 5648 echo '<button type="button" id="toggleDeepSeekApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6673 5649 echo '<p class="description">' . esc_html__('Required for DeepSeek models. Get your API key from DeepSeek Platform.', 'mxchat') . '</p>';
6674 5650 echo '</div>';
6675 5651 }
@@ -6676,12 +5652,11 @@
6676 5652
6677 5653 // Gemini API Key
6678 5654 public function gemini_api_key_callback() {
6679 5655 $geminiApiKey = isset($this->options['gemini_api_key']) ? esc_attr($this->options['gemini_api_key']) : '';
6680 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
6681 5656
6682 5657 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 . '" />';
5658 + echo '<input type="password" id="gemini_api_key" name="gemini_api_key" value="' . $geminiApiKey . '" class="regular-text" autocomplete="off" />';
6684 5659 echo '<button type="button" id="toggleGeminiApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6685 5660 echo '<p class="description">' . esc_html__('Required for Google Gemini models and embeddings. Get your API key from Google AI Studio.', 'mxchat') . '</p>';
6686 5661 echo '</div>';
6687 5662 }
@@ -6689,12 +5664,10 @@
6689 5664
6690 5665 // OpenRouter API Key
6691 5666 public function openrouter_api_key_callback() {
6692 5667 $openrouterApiKey = isset($this->options['openrouter_api_key']) ? esc_attr($this->options['openrouter_api_key']) : '';
6693 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
6694 -
6695 5668 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 . '" />';
5669 + echo '<input type="password" id="openrouter_api_key" name="openrouter_api_key" value="' . $openrouterApiKey . '" class="regular-text" autocomplete="off" />';
6697 5670 echo '<button type="button" id="toggleOpenRouterApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6698 5671 echo '<p class="description">' . esc_html__('Required for OpenRouter models. Get your API key from OpenRouter.ai', 'mxchat') . '</p>';
6699 5672 echo '</div>';
6700 5673 }
@@ -6701,12 +5674,11 @@
6701 5674
6702 5675 // Voyage API Key
6703 5676 public function voyage_api_key_callback() {
6704 5677 $apiKey = isset($this->options['voyage_api_key']) ? esc_attr($this->options['voyage_api_key']) : '';
6705 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
6706 5678
6707 5679 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 . '" />';
5680 + echo '<input type="password" id="voyage_api_key" name="voyage_api_key" value="' . $apiKey . '" class="regular-text" autocomplete="off" />';
6709 5681 echo '<button type="button" id="toggleVoyageAPIKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6710 5682 echo '<p class="description">' . esc_html__('Required for Voyage AI embedding models. Get your API key from Voyage AI.', 'mxchat') . '</p>';
6711 5683 echo '</div>';
6712 5684 }
@@ -6712,15 +5684,17 @@
6712 5684 }
6713 5685
6714 5686 public function mxchat_loops_api_key_callback() {
6715 5687 $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 5688
5689 + // Hidden fields to "trap" autofill
5690 + echo '<input type="text" style="display:none" autocomplete="username" />';
5691 + echo '<input type="password" style="display:none" autocomplete="current-password" />';
5692 +
6718 5693 echo '<div class="api-key-wrapper">';
6719 5694 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
5695 + '<input type="password" id="loops_api_key" name="loops_api_key" value="%s" class="regular-text" autocomplete="new-password" />',
5696 + $loops_api_key
6723 5697 );
6724 5698 echo '<button type="button" id="toggleLoopsApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6725 5699 echo '</div>';
6726 5700 echo '<p class="description">' . esc_html__('Required for Loops email integration. Get your API key from Loops.so', 'mxchat') . '</p>';
@@ -6728,9 +5702,8 @@
6728 5702 public function mxchat_loops_mailing_list_callback() {
6729 5703 // Add error handling and type checking
6730 5704 $loops_api_key = '';
6731 5705 $selected_list = '';
6732 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
6733 5706
6734 5707 // Safely get the API key
6735 5708 if (isset($this->options['loops_api_key']) && is_string($this->options['loops_api_key'])) {
6736 5709 $loops_api_key = $this->options['loops_api_key'];
@@ -6743,10 +5716,9 @@
6743 5716
6744 5717 if (!empty($loops_api_key)) {
6745 5718 $lists = $this->mxchat_fetch_loops_mailing_lists($loops_api_key);
6746 5719 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 . '">';
5720 + echo '<select id="loops_mailing_list" name="loops_mailing_list">';
6749 5721
6750 5722 // Add a default "Select a list" option
6751 5723 echo '<option value="" ' . selected($selected_list, '', false) . '>' . esc_html__('Select a list', 'mxchat') . '</option>';
6752 5724
@@ -6760,9 +5732,8 @@
6760 5732 );
6761 5733 }
6762 5734 }
6763 5735 echo '</select>';
6764 - echo '</div>';
6765 5736 echo '<p class="description">' . esc_html__('Please select a mailing list to use with Loops.', 'mxchat') . '</p>';
6766 5737 } else {
6767 5738 echo '<p class="description">' . esc_html__('No lists found. Please verify your API Key.', 'mxchat') . '</p>';
6768 5739 }
@@ -6774,17 +5745,12 @@
6774 5745 $default_response = __('Would you like to join our mailing list? Please provide your email below.', 'mxchat');
6775 5746 $triggered_response = isset($this->options['triggered_phrase_response'])
6776 5747 ? $this->options['triggered_phrase_response']
6777 5748 : $default_response;
6778 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
6779 -
6780 - echo '<div class="mxchat-field-wrapper">';
6781 5749 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,
5750 + '<textarea id="triggered_phrase_response" name="triggered_phrase_response" rows="3" cols="50">%s</textarea>',
6784 5751 esc_textarea($triggered_response)
6785 5752 );
6786 - echo '</div>';
6787 5753 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 5754 }
6789 5755
6790 5756 public function mxchat_email_capture_response_callback() {
@@ -6791,17 +5757,12 @@
6791 5757 $default_response = __('Thank you for providing your email! You\'ve been added to our list.', 'mxchat');
6792 5758 $email_capture_response = isset($this->options['email_capture_response'])
6793 5759 ? $this->options['email_capture_response']
6794 5760 : $default_response;
6795 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
6796 -
6797 - echo '<div class="mxchat-field-wrapper">';
6798 5761 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,
5762 + '<textarea id="email_capture_response" name="email_capture_response" rows="3" cols="50">%s</textarea>',
6801 5763 esc_textarea($email_capture_response)
6802 5764 );
6803 - echo '</div>';
6804 5765 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 5766 }
6806 5767 public function mxchat_pre_chat_message_callback() {
6807 5768 // Load the entire 'mxchat_options' array
@@ -6815,8 +5776,9 @@
6815 5776 printf(
6816 5777 '<textarea id="pre_chat_message" name="pre_chat_message" rows="5" cols="50">%s</textarea>',
6817 5778 esc_textarea($pre_chat_message)
6818 5779 );
5780 + 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 5781 }
6820 5782
6821 5783 // Callback for AI Instructions textarea
6822 5784 public function system_prompt_instructions_callback() {
@@ -6826,14 +5788,10 @@
6826 5788 printf(
6827 5789 '<textarea id="system_prompt_instructions" name="system_prompt_instructions" rows="5" cols="50">%s</textarea>',
6828 5790 $instructions
6829 5791 );
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
5792 + // Provide a helpful description with sample instructions button
5793 + 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 5794 echo '<div class="mxchat-instructions-container">';
6837 5795 echo '<button type="button" class="mxchat-instructions-btn" id="mxchatViewSampleBtn">';
6838 5796 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 5797 echo '<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/>';
@@ -6914,18 +5872,19 @@
6914 5872 esc_html__('OpenRouter (100+ Models)', 'mxchat') => array(
6915 5873 'openrouter' => esc_html__('OpenRouter - Select after entering API key', 'mxchat'),
6916 5874 ),
6917 5875 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'),
5876 + 'gemini-2.0-flash' => esc_html__('Gemini 2.0 Flash (Next-Gen Features)', 'mxchat'),
5877 + 'gemini-2.0-flash-lite' => esc_html__('Gemini 2.0 Flash-Lite (Cost-Efficient)', 'mxchat'),
5878 + 'gemini-1.5-pro' => esc_html__('Gemini 1.5 Pro (Complex Reasoning)', 'mxchat'),
5879 + 'gemini-1.5-flash' => esc_html__('Gemini 1.5 Flash (Fast & Versatile)', 'mxchat'),
6927 5880 ),
5881 + esc_html__('Google Gemini Models', 'mxchat') => array(
5882 + 'gemini-2.0-flash' => esc_html__('Gemini 2.0 Flash (Next-Gen Features)', 'mxchat'),
5883 + 'gemini-2.0-flash-lite' => esc_html__('Gemini 2.0 Flash-Lite (Cost-Efficient)', 'mxchat'),
5884 + 'gemini-1.5-pro' => esc_html__('Gemini 1.5 Pro (Complex Reasoning)', 'mxchat'),
5885 + 'gemini-1.5-flash' => esc_html__('Gemini 1.5 Flash (Fast & Versatile)', 'mxchat'),
5886 + ),
6928 5887 esc_html__('X.AI Models', 'mxchat') => array(
6929 5888 'grok-3-beta' => esc_html__('Grok-3 (Powerful)', 'mxchat'),
6930 5889 'grok-3-fast-beta' => esc_html__('Grok-3 Fast (High Performance)', 'mxchat'),
6931 5890 'grok-3-mini-beta' => esc_html__('Grok-3 Mini (Affordable)', 'mxchat'),
@@ -6938,33 +5897,37 @@
6938 5897 esc_html__('DeepSeek Models', 'mxchat') => array(
6939 5898 'deepseek-chat' => esc_html__('DeepSeek-V3', 'mxchat'),
6940 5899 ),
6941 5900 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 5901 'claude-sonnet-4-5-20250929' => esc_html__('Claude Sonnet 4.5 (Best for Agents & Coding)', 'mxchat'),
6946 5902 'claude-opus-4-1-20250805' => esc_html__('Claude Opus 4.1 (Exceptional for Complex Tasks)', 'mxchat'),
6947 5903 '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'),
5904 + 'claude-opus-4-20250514' => esc_html__('Claude 4 Opus (Most Capable)', 'mxchat'),
6949 5905 'claude-sonnet-4-20250514' => esc_html__('Claude 4 Sonnet (High Performance)', 'mxchat'),
5906 + 'claude-3-7-sonnet-20250219' => esc_html__('Claude 3.7 Sonnet (High Intelligence)', 'mxchat'),
5907 + 'claude-3-opus-20240229' => esc_html__('Claude 3 Opus (Complex Tasks)', 'mxchat'),
5908 + 'claude-3-sonnet-20240229' => esc_html__('Claude 3 Sonnet (Balanced)', 'mxchat'),
5909 + 'claude-3-haiku-20240307' => esc_html__('Claude 3 Haiku (Fastest)', 'mxchat'),
6950 5910 ),
6951 5911 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'),
6956 - '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'),
5912 + // GPT-5 family
6958 5913 'gpt-5.1-2025-11-13' => esc_html__('GPT-5.1 (Flagship for Coding & Agentic Tasks)', 'mxchat'),
6959 5914 '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'),
5915 + 'gpt-5-mini' => esc_html__('GPT-5 Mini (Faster, Cost-Efficient for Precise Prompts)', 'mxchat'),
6961 5916 'gpt-5-nano' => esc_html__('GPT-5 Nano (Fastest & Cheapest for Summarization/Classification)', 'mxchat'),
5917 +
5918 + // Existing OpenAI models
5919 + 'gpt-4.1-2025-04-14' => esc_html__('GPT-4.1 (Flagship for Complex Tasks)', 'mxchat'),
5920 + 'gpt-4o' => esc_html__('GPT-4o (Recommended)', 'mxchat'),
5921 + 'gpt-4o-mini' => esc_html__('GPT-4o Mini (Fast and Lightweight)', 'mxchat'),
5922 + 'gpt-4-turbo' => esc_html__('GPT-4 Turbo (High-Performance)', 'mxchat'),
5923 + 'gpt-4' => esc_html__('GPT-4 (High Intelligence)', 'mxchat'),
5924 + 'gpt-3.5-turbo' => esc_html__('GPT-3.5 Turbo (Affordable and Fast)', 'mxchat'),
6962 5925 ),
6963 5926 );
6964 5927
6965 5928 // 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';
5929 + $selected_model = isset($this->options['model']) ? esc_attr($this->options['model']) : 'gpt-4o';
6967 5930
6968 5931 // Begin the select dropdown
6969 5932 echo '<select id="model" name="model">';
6970 5933
@@ -6980,8 +5943,10 @@
6980 5943 }
6981 5944
6982 5945 echo '</select>';
6983 5946
5947 + echo '<p class="description">' . esc_html__('Select the AI model your chatbot will use for chatting.', 'mxchat') . '</p>';
5948 +
6984 5949 // Add a note for OpenRouter
6985 5950 echo '<p class="description" id="openrouter-model-note" style="display:none; color: #d63638; font-weight: 500;">';
6986 5951 echo '<span class="dashicons dashicons-info" style="font-size: 16px; vertical-align: middle;"></span> ';
6987 5952 echo esc_html__('After entering your OpenRouter API key above, click the button below to load available models.', 'mxchat');
@@ -7069,8 +6034,11 @@
7069 6034 esc_attr($checked)
7070 6035 );
7071 6036 echo '<span class="slider"></span>';
7072 6037 echo '</label>';
6038 + echo '<p class="description">' .
6039 + esc_html__('Enable real-time streaming responses for supported models (OpenAI, Claude, DeepSeek, and Grok). When disabled, responses will load all at once.', 'mxchat') .
6040 + '</p>';
7073 6041
7074 6042 // Test button with better styling
7075 6043 echo '<div style="margin-top: 15px;">';
7076 6044 echo '<button type="button" id="mxchat-test-streaming-btn" class="button button-secondary">';
@@ -7080,50 +6048,8 @@
7080 6048 echo '<p id="mxchat-test-streaming-result" style="margin-top:10px; font-weight: bold;"></p>';
7081 6049 echo '</div>';
7082 6050 }
7083 6051
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 6052 // AJAX handler to fetch OpenRouter models
7127 6053 public function fetch_openrouter_models() {
7128 6054 check_ajax_referer('mxchat_fetch_openrouter_models', 'nonce');
7129 6055
@@ -7180,9 +6106,9 @@
7180 6106 esc_html__('Voyage AI Embeddings', 'mxchat') => array(
7181 6107 'voyage-3-large' => esc_html__('Voyage-3 Large (2048, Most Capable)', 'mxchat'),
7182 6108 ),
7183 6109 esc_html__('Google Gemini Embeddings', 'mxchat') => array(
7184 - 'gemini-embedding-001' => esc_html__('Gemini Embedding (1536, Stable)', 'mxchat'),
6110 + 'gemini-embedding-exp-03-07' => esc_html__('Gemini Embedding (1536, Experimental)', 'mxchat'),
7185 6111 )
7186 6112 );
7187 6113 $selected_model = isset($this->options['embedding_model']) ? esc_attr($this->options['embedding_model']) : 'text-embedding-ada-002';
7188 6114 echo '<select id="embedding_model" name="embedding_model">';
@@ -7193,8 +6119,9 @@
7193 6119 }
7194 6120 echo '</optgroup>';
7195 6121 }
7196 6122 echo '</select>';
6123 + 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 6124
7198 6125 // API Key Status Messages for Embedding Models
7199 6126 $has_openai_key = !empty($this->options['api_key']);
7200 6127 $has_voyage_key = !empty($this->options['voyage_api_key']);
@@ -7225,9 +6152,8 @@
7225 6152 } else {
7226 6153 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 6154 }
7228 6155 echo '</p>';
7229 -
7230 6156 }
7231 6157
7232 6158
7233 6159 public function mxchat_top_bar_title_callback() {
@@ -7235,8 +6161,11 @@
7235 6161 $top_bar_title = isset($this->options['top_bar_title']) ? esc_attr($this->options['top_bar_title']) : '';
7236 6162
7237 6163 // Render the input field
7238 6164 echo '<input type="text" id="top_bar_title" name="top_bar_title" value="' . $top_bar_title . '" />';
6165 +
6166 + // Add a description
6167 + echo '<p class="description">' . esc_html__('Enter the title text that will appear on the top bar of the chatbot.', 'mxchat') . '</p>';
7239 6168 }
7240 6169 public function mxchat_ai_agent_text_callback() {
7241 6170 // Retrieve the current value of the AI agent text from saved options
7242 6171 $ai_agent_text = isset($this->options['ai_agent_text']) ? esc_attr($this->options['ai_agent_text']) : '';
@@ -7241,8 +6170,10 @@
7241 6170 // Retrieve the current value of the AI agent text from saved options
7242 6171 $ai_agent_text = isset($this->options['ai_agent_text']) ? esc_attr($this->options['ai_agent_text']) : '';
7243 6172 // Render the input field
7244 6173 echo '<input type="text" id="ai_agent_text" name="ai_agent_text" value="' . $ai_agent_text . '" />';
6174 + // Add a description
6175 + 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 6176 }
7246 6177
7247 6178
7248 6179 public function enable_email_block_callback() {
@@ -7261,8 +6192,9 @@
7261 6192 esc_attr($checked)
7262 6193 );
7263 6194 echo '<span class="slider"></span>';
7264 6195 echo '</label>';
6196 + 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 6197 }
7266 6198
7267 6199 public function email_blocker_header_content_callback() {
7268 6200 // Load the entire 'mxchat_options' array
@@ -7280,8 +6212,12 @@
7280 6212 rows="5"
7281 6213 cols="70"
7282 6214 data-setting="email_blocker_header_content"
7283 6215 >' . esc_textarea($content) . '</textarea>';
6216 +
6217 + echo '<p class="description">';
6218 + 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');
6219 + echo '</p>';
7284 6220 }
7285 6221
7286 6222 public function email_blocker_button_text_callback() {
7287 6223 // Load the entire 'mxchat_options' array
@@ -7293,8 +6229,12 @@
7293 6229 : '';
7294 6230
7295 6231 // Use esc_attr to safely render the existing text
7296 6232 echo '<input type="text" id="email_blocker_button_text" name="email_blocker_button_text" value="' . esc_attr($button_text) . '" style="width: 300px;" />';
6233 +
6234 + echo '<p class="description">';
6235 + echo esc_html__('Enter the text you want on the submit button, e.g. "Start Chat".', 'mxchat');
6236 + echo '</p>';
7297 6237 }
7298 6238
7299 6239 //Enable name field callback
7300 6240 public function enable_name_field_callback() {
@@ -7310,8 +6250,9 @@
7310 6250 esc_attr($checked)
7311 6251 );
7312 6252 echo '<span class="slider"></span>';
7313 6253 echo '</label>';
6254 + 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 6255 }
7315 6256 //Name field placeholder callback
7316 6257 public function name_field_placeholder_callback() {
7317 6258 $all_options = get_option('mxchat_options', []);
@@ -7319,8 +6260,11 @@
7319 6260 ? $all_options['name_field_placeholder']
7320 6261 : esc_html__('Enter your name', 'mxchat');
7321 6262
7322 6263 echo '<input type="text" id="name_field_placeholder" name="name_field_placeholder" value="' . esc_attr($placeholder) . '" style="width: 300px;" />';
6264 + echo '<p class="description">';
6265 + echo esc_html__('Placeholder text for the name field.', 'mxchat');
6266 + echo '</p>';
7323 6267 }
7324 6268
7325 6269
7326 6270
@@ -7332,11 +6276,10 @@
7332 6276 $saved_message = isset($all_options['intro_message']) ? $all_options['intro_message'] : $default_message;
7333 6277 // Output the textarea with the saved value without escaping HTML
7334 6278 ?>
7335 6279 <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>
6280 + <p class="description">
6281 + <?php esc_html_e('Enter your message. HTML tags and line breaks will be preserved.', 'mxchat'); ?>
7339 6282 </p>
7340 6283 <?php
7341 6284 }
7342 6285
@@ -7353,117 +6296,478 @@
7353 6296 '<input type="text" id="input_copy" name="input_copy" value="%s" placeholder="%s" />',
7354 6297 esc_attr($input_copy),
7355 6298 esc_attr__('How can I assist?', 'mxchat')
7356 6299 );
6300 +
6301 + // Output the description
6302 + echo '<p class="description">' . esc_html__('This is the placeholder text for the chat input field.', 'mxchat') . '</p>';
7357 6303 }
7358 6304
7359 6305
7360 -public function mxchat_append_to_body_callback() {
7361 - // Fetch fresh options to ensure we have the latest saved values
7362 - $options = get_option('mxchat_options', array());
7363 6306
7364 - // Get value from options array, default to 'off'
7365 - $append_to_body = isset($options['append_to_body']) ? $options['append_to_body'] : 'off';
7366 - $checked = ($append_to_body === 'on') ? 'checked' : '';
6307 +public function mxchat_user_message_font_color_callback() {
6308 + $disabled = $this->is_activated ? '' : 'disabled';
6309 + $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7367 6310
7368 - // Get post type visibility settings
7369 - $visibility_mode = isset($options['post_type_visibility_mode']) ? $options['post_type_visibility_mode'] : 'all';
7370 - $visibility_list = isset($options['post_type_visibility_list']) ? $options['post_type_visibility_list'] : array();
7371 - if (!is_array($visibility_list)) {
7372 - $visibility_list = array();
6311 + echo '<div class="' . esc_attr($class) . '">';
6312 + echo sprintf(
6313 + '<input type="text"
6314 + id="user_message_font_color"
6315 + name="user_message_font_color"
6316 + value="%s"
6317 + class="my-color-field"
6318 + data-default-color="#ffffff"
6319 + %s />',
6320 + isset($this->options['user_message_font_color']) ? esc_attr($this->options['user_message_font_color']) : '#ffffff',
6321 + esc_attr($disabled)
6322 + );
6323 +
6324 + if (!$this->is_activated) {
6325 + echo '<div class="pro-feature-overlay">';
6326 + echo '<a href="https://mxchat.ai/" target="_blank">';
6327 + echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6328 + echo '</a>';
6329 + echo '</div>';
7373 6330 }
6331 + echo '</div>';
6332 +}
7374 6333
7375 - echo '<div class="mxchat-autosave-section">';
6334 +public function mxchat_bot_message_bg_color_callback() {
6335 + $disabled = $this->is_activated ? '' : 'disabled';
6336 + $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7376 6337
7377 - // Main toggle
7378 - echo '<label class="toggle-switch">';
6338 + echo '<div class="' . esc_attr($class) . '">';
7379 6339 echo sprintf(
7380 - '<input type="checkbox" id="append_to_body" name="append_to_body" value="on" %s />',
7381 - esc_attr($checked)
6340 + '<input type="text"
6341 + id="bot_message_bg_color"
6342 + name="bot_message_bg_color"
6343 + value="%s"
6344 + class="my-color-field"
6345 + data-default-color="#e1e1e1"
6346 + %s />',
6347 + isset($this->options['bot_message_bg_color']) ? esc_attr($this->options['bot_message_bg_color']) : '#e1e1e1',
6348 + esc_attr($disabled)
7382 6349 );
7383 - echo '<span class="slider"></span>';
7384 - echo '</label>';
7385 6350
7386 - // Post Type Visibility Options (only visible when auto-display is ON)
7387 - $display_style = ($append_to_body === 'on') ? '' : 'display: none;';
7388 - echo '<div id="post-type-visibility-options" class="mxchat-sub-options" style="' . esc_attr($display_style) . '">';
6351 + if (!$this->is_activated) {
6352 + echo '<div class="pro-feature-overlay">';
6353 + echo '<a href="https://mxchat.ai/" target="_blank">';
6354 + echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6355 + echo '</a>';
6356 + echo '</div>';
6357 + }
6358 + echo '</div>';
6359 +}
7389 6360
7390 - echo '<div class="mxchat-post-type-visibility-header">';
7391 - echo '<h4>' . esc_html__('Post Type Visibility', 'mxchat') . '</h4>';
6361 +public function mxchat_bot_message_font_color_callback() {
6362 + $disabled = $this->is_activated ? '' : 'disabled';
6363 + $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6364 +
6365 + echo '<div class="' . esc_attr($class) . '">';
6366 + echo sprintf(
6367 + '<input type="text"
6368 + id="bot_message_font_color"
6369 + name="bot_message_font_color"
6370 + value="%s"
6371 + class="my-color-field"
6372 + data-default-color="#333333"
6373 + %s />',
6374 + isset($this->options['bot_message_font_color']) ? esc_attr($this->options['bot_message_font_color']) : '#333333',
6375 + esc_attr($disabled)
6376 + );
6377 +
6378 + if (!$this->is_activated) {
6379 + echo '<div class="pro-feature-overlay">';
6380 + echo '<a href="https://mxchat.ai/" target="_blank">';
6381 + echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6382 + echo '</a>';
6383 + echo '</div>';
6384 + }
7392 6385 echo '</div>';
6386 +}
7393 6387
7394 - // Mode selector (radio buttons)
7395 - echo '<div class="mxchat-visibility-mode">';
6388 +public function mxchat_live_agent_message_bg_color_callback() {
6389 + $disabled = $this->is_activated ? '' : 'disabled';
6390 + $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7396 6391
7397 - echo '<label class="mxchat-radio-label">';
7398 - echo '<input type="radio" name="post_type_visibility_mode" value="all" ' . checked($visibility_mode, 'all', false) . ' />';
7399 - echo '<span>' . esc_html__('Show on all post types', 'mxchat') . '</span>';
7400 - echo '</label>';
6392 + echo '<div class="' . esc_attr($class) . '">';
6393 + echo sprintf(
6394 + '<input type="text"
6395 + id="live_agent_message_bg_color"
6396 + name="live_agent_message_bg_color"
6397 + value="%s"
6398 + class="my-color-field"
6399 + data-default-color="#ffffff"
6400 + %s />',
6401 + isset($this->options['live_agent_message_bg_color']) ? esc_attr($this->options['live_agent_message_bg_color']) : '#ffffff',
6402 + esc_attr($disabled)
6403 + );
7401 6404
7402 - echo '<label class="mxchat-radio-label">';
7403 - echo '<input type="radio" name="post_type_visibility_mode" value="include" ' . checked($visibility_mode, 'include', false) . ' />';
7404 - echo '<span>' . esc_html__('Only show on selected post types', 'mxchat') . '</span>';
7405 - echo '</label>';
6405 + if (!$this->is_activated) {
6406 + echo '<div class="pro-feature-overlay">';
6407 + echo '<a href="https://mxchat.ai/" target="_blank">';
6408 + echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6409 + echo '</a>';
6410 + echo '</div>';
6411 + }
6412 + echo '</div>';
6413 +}
7406 6414
7407 - echo '<label class="mxchat-radio-label">';
7408 - echo '<input type="radio" name="post_type_visibility_mode" value="exclude" ' . checked($visibility_mode, 'exclude', false) . ' />';
7409 - echo '<span>' . esc_html__('Hide on selected post types', 'mxchat') . '</span>';
7410 - echo '</label>';
6415 +public function mxchat_live_agent_message_font_color_callback() {
6416 + $disabled = $this->is_activated ? '' : 'disabled';
6417 + $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7411 6418
6419 + echo '<div class="' . esc_attr($class) . '">';
6420 + echo sprintf(
6421 + '<input type="text"
6422 + id="live_agent_message_font_color"
6423 + name="live_agent_message_font_color"
6424 + value="%s"
6425 + class="my-color-field"
6426 + data-default-color="#333333"
6427 + %s />',
6428 + isset($this->options['live_agent_message_font_color']) ? esc_attr($this->options['live_agent_message_font_color']) : '#333333',
6429 + esc_attr($disabled)
6430 + );
6431 +
6432 + if (!$this->is_activated) {
6433 + echo '<div class="pro-feature-overlay">';
6434 + echo '<a href="https://mxchat.ai/" target="_blank">';
6435 + echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6436 + echo '</a>';
6437 + echo '</div>';
6438 + }
7412 6439 echo '</div>';
6440 +}
7413 6441
7414 - // Post type checkboxes (only visible when mode is include or exclude)
7415 - $list_display = ($visibility_mode !== 'all') ? '' : 'display: none;';
7416 - echo '<div id="post-type-list" class="mxchat-post-type-list" style="' . esc_attr($list_display) . '">';
6442 +public function mxchat_mode_indicator_bg_color_callback() {
6443 + $disabled = $this->is_activated ? '' : 'disabled';
6444 + $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7417 6445
7418 - // Get all public post types
7419 - $post_types = get_post_types(array('public' => true), 'objects');
6446 + echo '<div class="' . esc_attr($class) . '">';
6447 + echo sprintf(
6448 + '<input type="text"
6449 + id="mode_indicator_bg_color"
6450 + name="mode_indicator_bg_color"
6451 + value="%s"
6452 + class="my-color-field"
6453 + data-default-color="#767676"
6454 + %s />',
6455 + isset($this->options['mode_indicator_bg_color']) ? esc_attr($this->options['mode_indicator_bg_color']) : '#767676',
6456 + esc_attr($disabled)
6457 + );
7420 6458
7421 - foreach ($post_types as $post_type) {
7422 - // Skip attachments
7423 - if ($post_type->name === 'attachment') {
7424 - continue;
7425 - }
6459 + if (!$this->is_activated) {
6460 + echo '<div class="pro-feature-overlay">';
6461 + echo '<a href="https://mxchat.ai/" target="_blank">';
6462 + echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6463 + echo '</a>';
6464 + echo '</div>';
6465 + }
6466 + echo '</div>';
6467 +}
7426 6468
7427 - $is_checked = in_array($post_type->name, $visibility_list) ? 'checked' : '';
6469 +public function mxchat_mode_indicator_font_color_callback() {
6470 + $disabled = $this->is_activated ? '' : 'disabled';
6471 + $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7428 6472
7429 - echo '<label class="mxchat-checkbox-label">';
7430 - echo '<input type="checkbox" name="post_type_visibility_list[]" value="' . esc_attr($post_type->name) . '" ' . $is_checked . ' />';
7431 - echo '<span>' . esc_html($post_type->label) . '</span>';
7432 - echo '</label>';
6473 + echo '<div class="' . esc_attr($class) . '">';
6474 + echo sprintf(
6475 + '<input type="text"
6476 + id="mode_indicator_font_color"
6477 + name="mode_indicator_font_color"
6478 + value="%s"
6479 + class="my-color-field"
6480 + data-default-color="#ffffff"
6481 + %s />',
6482 + isset($this->options['mode_indicator_font_color']) ? esc_attr($this->options['mode_indicator_font_color']) : '#ffffff',
6483 + esc_attr($disabled)
6484 + );
6485 +
6486 + if (!$this->is_activated) {
6487 + echo '<div class="pro-feature-overlay">';
6488 + echo '<a href="https://mxchat.ai/" target="_blank">';
6489 + echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6490 + echo '</a>';
6491 + echo '</div>';
7433 6492 }
6493 + echo '</div>';
6494 +}
7434 6495
7435 - echo '</div>'; // End post-type-list
7436 - echo '</div>'; // End post-type-visibility-options
7437 - echo '</div>'; // End mxchat-autosave-section
6496 +public function mxchat_toolbar_icon_color_callback() {
6497 + $disabled = $this->is_activated ? '' : 'disabled';
6498 + $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6499 +
6500 + echo '<div class="' . esc_attr($class) . '">';
6501 + echo sprintf(
6502 + '<input type="text"
6503 + id="toolbar_icon_color"
6504 + name="toolbar_icon_color"
6505 + value="%s"
6506 + class="my-color-field"
6507 + data-default-color="#212121"
6508 + %s />',
6509 + isset($this->options['toolbar_icon_color']) ? esc_attr($this->options['toolbar_icon_color']) : '#212121',
6510 + esc_attr($disabled)
6511 + );
6512 +
6513 + if (!$this->is_activated) {
6514 + echo '<div class="pro-feature-overlay">';
6515 + echo '<a href="https://mxchat.ai/" target="_blank">';
6516 + echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6517 + echo '</a>';
6518 + echo '</div>';
6519 + }
6520 + echo '</div>';
7438 6521 }
7439 6522
7440 -public function mxchat_contextual_awareness_callback() {
6523 +public function mxchat_top_bar_bg_color_callback() {
6524 + $disabled = $this->is_activated ? '' : 'disabled';
6525 + $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6526 +
6527 + echo '<div class="' . esc_attr($class) . '">';
6528 + echo sprintf(
6529 + '<input type="text"
6530 + id="top_bar_bg_color"
6531 + name="top_bar_bg_color"
6532 + value="%s"
6533 + class="my-color-field"
6534 + data-default-color="#00b294"
6535 + %s />',
6536 + isset($this->options['top_bar_bg_color']) ? esc_attr($this->options['top_bar_bg_color']) : '#00b294',
6537 + esc_attr($disabled)
6538 + );
6539 +
6540 + if (!$this->is_activated) {
6541 + echo '<div class="pro-feature-overlay">';
6542 + echo '<a href="https://mxchat.ai/" target="_blank">';
6543 + echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6544 + echo '</a>';
6545 + echo '</div>';
6546 + }
6547 + echo '</div>';
6548 +}
6549 +
6550 +public function mxchat_send_button_font_color_callback() {
6551 + $disabled = $this->is_activated ? '' : 'disabled';
6552 + $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6553 +
6554 + echo '<div class="' . esc_attr($class) . '">';
6555 + echo sprintf(
6556 + '<input type="text"
6557 + id="send_button_font_color"
6558 + name="send_button_font_color"
6559 + value="%s"
6560 + class="my-color-field"
6561 + data-default-color="#ffffff"
6562 + %s />',
6563 + isset($this->options['send_button_font_color']) ? esc_attr($this->options['send_button_font_color']) : '#ffffff',
6564 + esc_attr($disabled)
6565 + );
6566 +
6567 + if (!$this->is_activated) {
6568 + echo '<div class="pro-feature-overlay">';
6569 + echo '<a href="https://mxchat.ai/" target="_blank">';
6570 + echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6571 + echo '</a>';
6572 + echo '</div>';
6573 + }
6574 + echo '</div>';
6575 +}
6576 +
6577 +public function mxchat_chatbot_background_color_callback() {
6578 + $disabled = $this->is_activated ? '' : 'disabled';
6579 + $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6580 +
6581 + echo '<div class="' . esc_attr($class) . '">';
6582 + echo sprintf(
6583 + '<input type="text"
6584 + id="chatbot_background_color"
6585 + name="chatbot_background_color"
6586 + value="%s"
6587 + class="my-color-field"
6588 + data-default-color="#000000"
6589 + %s />',
6590 + isset($this->options['chatbot_background_color']) ? esc_attr($this->options['chatbot_background_color']) : '#000000',
6591 + esc_attr($disabled)
6592 + );
6593 +
6594 + if (!$this->is_activated) {
6595 + echo '<div class="pro-feature-overlay">';
6596 + echo '<a href="https://mxchat.ai/" target="_blank">';
6597 + echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6598 + echo '</a>';
6599 + echo '</div>';
6600 + }
6601 + echo '</div>';
6602 +}
6603 +
6604 +public function mxchat_icon_color_callback() {
6605 + $disabled = $this->is_activated ? '' : 'disabled';
6606 + $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6607 +
6608 + echo '<div class="' . esc_attr($class) . '">';
6609 + echo sprintf(
6610 + '<input type="text"
6611 + id="icon_color"
6612 + name="icon_color"
6613 + value="%s"
6614 + class="my-color-field"
6615 + data-default-color="#ffffff"
6616 + %s />',
6617 + isset($this->options['icon_color']) ? esc_attr($this->options['icon_color']) : '#ffffff',
6618 + esc_attr($disabled)
6619 + );
6620 +
6621 + if (!$this->is_activated) {
6622 + echo '<div class="pro-feature-overlay">';
6623 + echo '<a href="https://mxchat.ai/" target="_blank">';
6624 + echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6625 + echo '</a>';
6626 + echo '</div>';
6627 + }
6628 + echo '</div>';
6629 +}
6630 +
6631 +public function mxchat_custom_icon_callback() {
6632 + $disabled = $this->is_activated ? '' : 'disabled';
6633 + $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6634 + $custom_icon_url = isset($this->options['custom_icon']) ? esc_url($this->options['custom_icon']) : '';
6635 +
6636 + echo '<div class="' . esc_attr($class) . '">';
6637 + echo sprintf(
6638 + '<input type="url"
6639 + id="custom_icon"
6640 + name="custom_icon"
6641 + value="%s"
6642 + placeholder="%s"
6643 + class="regular-text"
6644 + %s />',
6645 + $custom_icon_url,
6646 + esc_attr__('Enter PNG URL', 'mxchat'),
6647 + esc_attr($disabled)
6648 + );
6649 +
6650 + // Preview container for the icon
6651 + if (!empty($custom_icon_url)) {
6652 + echo '<div class="icon-preview" style="margin-top: 10px;">';
6653 + echo '<img src="' . esc_url($custom_icon_url) . '" alt="' . esc_attr__('Custom Icon Preview', 'mxchat') . '" style="max-width: 48px; height: auto;" />';
6654 + echo '</div>';
6655 + }
6656 +
6657 + echo '<p class="description">' . esc_html__('Upload your PNG icon and paste the URL here. Recommended size: 48x48 pixels.', 'mxchat') . '</p>';
6658 +
6659 + if (!$this->is_activated) {
6660 + echo '<div class="pro-feature-overlay">';
6661 + echo '<a href="https://mxchat.ai/" target="_blank">';
6662 + echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6663 + echo '</a>';
6664 + echo '</div>';
6665 + }
6666 + echo '</div>';
6667 +}
6668 +
6669 +public function mxchat_title_icon_callback() {
6670 + $disabled = $this->is_activated ? '' : 'disabled';
6671 + $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6672 + // Fixed the variable reference - it was using custom_icon instead of title_icon
6673 + $title_icon_url = isset($this->options['title_icon']) ? esc_url($this->options['title_icon']) : '';
6674 +
6675 + echo '<div class="' . esc_attr($class) . '">';
6676 + echo sprintf(
6677 + '<input type="url"
6678 + id="title_icon"
6679 + name="title_icon"
6680 + value="%s"
6681 + placeholder="%s"
6682 + class="regular-text"
6683 + %s />',
6684 + $title_icon_url,
6685 + esc_attr__('Enter PNG URL', 'mxchat'),
6686 + esc_attr($disabled)
6687 + );
6688 +
6689 + // Preview container for the icon
6690 + if (!empty($title_icon_url)) {
6691 + echo '<div class="icon-preview" style="margin-top: 10px;">';
6692 + echo '<img src="' . esc_url($title_icon_url) . '" alt="' . esc_attr__('Title Icon Preview', 'mxchat') . '" style="max-width: 48px; height: auto;" />';
6693 + echo '</div>';
6694 + }
6695 +
6696 + echo '<p class="description">' . esc_html__('Upload your PNG icon and paste the URL here. Recommended size: 48x48 pixels.', 'mxchat') . '</p>';
6697 +
6698 + if (!$this->is_activated) {
6699 + echo '<div class="pro-feature-overlay">';
6700 + echo '<a href="https://mxchat.ai/" target="_blank">';
6701 + echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6702 + echo '</a>';
6703 + echo '</div>';
6704 + }
6705 + echo '</div>';
6706 +}
6707 +
6708 +public function mxchat_chat_input_font_color_callback() {
6709 + $disabled = $this->is_activated ? '' : 'disabled';
6710 + $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6711 +
6712 + echo '<div class="' . esc_attr($class) . '">';
6713 + echo sprintf(
6714 + '<input type="text"
6715 + id="chat_input_font_color"
6716 + name="chat_input_font_color"
6717 + value="%s"
6718 + class="my-color-field"
6719 + data-default-color="#555555"
6720 + %s />',
6721 + isset($this->options['chat_input_font_color']) ? esc_attr($this->options['chat_input_font_color']) : '#555555',
6722 + esc_attr($disabled)
6723 + );
6724 +
6725 + if (!$this->is_activated) {
6726 + echo '<div class="pro-feature-overlay">';
6727 + echo '<a href="https://mxchat.ai/" target="_blank">';
6728 + echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6729 + echo '</a>';
6730 + echo '</div>';
6731 + }
6732 + echo '</div>';
6733 +}
6734 +
6735 +public function mxchat_append_to_body_callback() {
7441 6736 // Get value from options array, default to 'off'
7442 - $contextual_awareness = isset($this->options['contextual_awareness_toggle']) ? $this->options['contextual_awareness_toggle'] : 'off';
7443 - $checked = ($contextual_awareness === 'on') ? 'checked' : '';
6737 + $append_to_body = isset($this->options['append_to_body']) ? $this->options['append_to_body'] : 'off';
6738 + $checked = ($append_to_body === 'on') ? 'checked' : '';
6739 +
7444 6740 echo '<label class="toggle-switch">';
7445 6741 echo sprintf(
7446 - '<input type="checkbox" id="contextual_awareness_toggle" name="contextual_awareness_toggle" value="on" %s />',
6742 + '<input type="checkbox" id="append_to_body" name="append_to_body" value="on" %s />',
7447 6743 esc_attr($checked)
7448 6744 );
7449 6745 echo '<span class="slider"></span>';
7450 6746 echo '</label>';
6747 + echo '<p class="description">' .
6748 + esc_html__('Show chatbot automatically on all pages. When disabled, you can place the chatbot manually using shortcode [mxchat_chatbot floating="yes"].', 'mxchat') .
6749 + '</p>';
6750 +
7451 6751 }
7452 6752
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' : '';
6753 +public function mxchat_contextual_awareness_callback() {
6754 + // Get value from options array, default to 'off'
6755 + $contextual_awareness = isset($this->options['contextual_awareness_toggle']) ? $this->options['contextual_awareness_toggle'] : 'off';
6756 + $checked = ($contextual_awareness === 'on') ? 'checked' : '';
7457 6757 echo '<label class="toggle-switch">';
7458 6758 echo sprintf(
7459 - '<input type="checkbox" id="citation_links_toggle" name="citation_links_toggle" value="on" %s />',
6759 + '<input type="checkbox" id="contextual_awareness_toggle" name="contextual_awareness_toggle" value="on" %s />',
7460 6760 esc_attr($checked)
7461 6761 );
7462 6762 echo '<span class="slider"></span>';
7463 6763 echo '</label>';
6764 + echo '<p class="description">' .
6765 + 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') .
6766 + '</p>';
7464 6767 }
7465 6768
6769 +
7466 6770 public function mxchat_privacy_toggle_callback() {
7467 6771 // Load from mxchat_options array
7468 6772 $options = get_option('mxchat_options', []);
7469 6773
@@ -7483,8 +6787,9 @@
7483 6787 esc_attr($checked)
7484 6788 );
7485 6789 echo '<span class="slider"></span>';
7486 6790 echo '</label>';
6791 + echo '<p class="description">' . esc_html__('Enable this option to display a privacy notice below the chat widget.', 'mxchat') . '</p>';
7487 6792
7488 6793 // Output the custom text input field
7489 6794 echo sprintf(
7490 6795 '<textarea id="privacy_text" name="privacy_text" rows="5" cols="50" class="regular-text">%s</textarea>',
@@ -7489,8 +6794,9 @@
7489 6794 echo sprintf(
7490 6795 '<textarea id="privacy_text" name="privacy_text" rows="5" cols="50" class="regular-text">%s</textarea>',
7491 6796 esc_textarea($privacy_text)
7492 6797 );
6798 + echo '<p class="description">' . esc_html__('Enter the privacy policy text. You can include HTML links.', 'mxchat') . '</p>';
7493 6799 }
7494 6800
7495 6801
7496 6802 public function mxchat_complianz_toggle_callback() {
@@ -7508,8 +6814,10 @@
7508 6814 esc_attr($checked)
7509 6815 );
7510 6816 echo '<span class="slider"></span>';
7511 6817 echo '</label>';
6818 +
6819 + echo '<p class="description">' . esc_html__('Enable this option to apply Complianz consent logic to the chatbot (must have Complianz Plugin).', 'mxchat') . '</p>';
7512 6820 }
7513 6821
7514 6822 public function mxchat_link_target_toggle_callback() {
7515 6823 // Load from mxchat_options array
@@ -7526,8 +6834,9 @@
7526 6834 esc_attr($checked)
7527 6835 );
7528 6836 echo '<span class="slider"></span>';
7529 6837 echo '</label>';
6838 + 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 6839 }
7531 6840
7532 6841 public function mxchat_chat_persistence_toggle_callback() {
7533 6842 // Load from mxchat_options array
@@ -7544,8 +6853,10 @@
7544 6853 esc_attr($checked)
7545 6854 );
7546 6855 echo '<span class="slider"></span>';
7547 6856 echo '</label>';
6857 +
6858 + 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 6859 }
7549 6860
7550 6861 public function mxchat_popular_question_1_callback() {
7551 6862 // Load the full plugin options array
@@ -7559,8 +6870,11 @@
7559 6870 '<input type="text" id="popular_question_1" name="popular_question_1" value="%s" placeholder="%s" class="regular-text" />',
7560 6871 esc_attr($popular_question_1),
7561 6872 esc_attr__('Enter Quick Question 1', 'mxchat')
7562 6873 );
6874 +
6875 + // Add a description for the field
6876 + echo '<p class="description">' . esc_html__('This will be the first Quick Question in the chatbot, displayed above the input field.', 'mxchat') . '</p>';
7563 6877 }
7564 6878
7565 6879
7566 6880 public function mxchat_popular_question_2_callback() {
@@ -7575,8 +6889,11 @@
7575 6889 '<input type="text" id="popular_question_2" name="popular_question_2" value="%s" placeholder="%s" class="regular-text" />',
7576 6890 esc_attr($popular_question_2),
7577 6891 esc_attr__('Enter Quick Question 2', 'mxchat')
7578 6892 );
6893 +
6894 + // Add a description for the field
6895 + echo '<p class="description">' . esc_html__('This will be the second Quick Question in the chatbot.', 'mxchat') . '</p>';
7579 6896 }
7580 6897
7581 6898
7582 6899 public function mxchat_popular_question_3_callback() {
@@ -7591,8 +6908,11 @@
7591 6908 '<input type="text" id="popular_question_3" name="popular_question_3" value="%s" placeholder="%s" class="regular-text" />',
7592 6909 esc_attr($popular_question_3),
7593 6910 esc_attr(__('Enter Quick Question 3', 'mxchat'))
7594 6911 );
6912 +
6913 + // Add a description for the field
6914 + echo '<p class="description">' . esc_html__('This will be the third Quick Question in the chatbot.', 'mxchat') . '</p>';
7595 6915 }
7596 6916
7597 6917 public function mxchat_additional_popular_questions_callback() {
7598 6918 $options = get_option('mxchat_options', []);
@@ -7641,19 +6961,19 @@
7641 6961 '<button type="button" class="button mxchat-add-question" aria-label="%s">%s</button>',
7642 6962 esc_attr(__('Add question', 'mxchat')),
7643 6963 esc_html__('Add Question', 'mxchat')
7644 6964 );
6965 + echo '<p class="description">' . esc_html__('Add as many Quick Questions as you need.', 'mxchat') . '</p>';
6966 + echo '</div>';
7645 6967 }
7646 6968
7647 6969 public function mxchat_brave_api_key_callback() {
7648 6970 $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 6971
7651 6972 echo '<div class="api-key-wrapper">';
7652 6973 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
6974 + '<input type="password" id="brave_api_key" name="brave_api_key" value="%s" class="regular-text" />',
6975 + $brave_api_key
7656 6976 );
7657 6977 echo '<button type="button" id="toggleBraveApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
7658 6978 echo '</div>';
7659 6979 echo '<p class="description">' . __('Required for Brave Search integration. Get your API key from Brave Search API.', 'mxchat') . '</p>';
@@ -7662,18 +6982,14 @@
7662 6982 public function mxchat_brave_image_count_callback() {
7663 6983 $brave_image_count = isset($this->options['brave_image_count'])
7664 6984 ? intval($this->options['brave_image_count'])
7665 6985 : 4;
7666 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
7667 6986
7668 - echo '<div class="mxchat-field-wrapper">';
7669 6987 echo sprintf(
7670 6988 '<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
6989 + value="%d" min="1" max="6" class="small-text" />',
6990 + $brave_image_count
7674 6991 );
7675 - echo '</div>';
7676 6992 echo '<p class="description">' . __('Select the number of images to return (1-6).', 'mxchat') . '</p>';
7677 6993 }
7678 6994
7679 6995 public function mxchat_brave_safe_search_callback() {
@@ -7679,12 +6995,10 @@
7679 6995 public function mxchat_brave_safe_search_callback() {
7680 6996 $brave_safe_search = isset($this->options['brave_safe_search'])
7681 6997 ? esc_attr($this->options['brave_safe_search'])
7682 6998 : 'strict';
7683 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
7684 6999
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 . '">';
7000 + echo '<select id="brave_safe_search" name="brave_safe_search">';
7687 7001 echo sprintf(
7688 7002 '<option value="strict" %s>%s</option>',
7689 7003 selected($brave_safe_search, 'strict', false),
7690 7004 __('Strict', 'mxchat')
@@ -7694,9 +7008,8 @@
7694 7008 selected($brave_safe_search, 'off', false),
7695 7009 __('Off', 'mxchat')
7696 7010 );
7697 7011 echo '</select>';
7698 - echo '</div>';
7699 7012 echo '<p class="description">' .
7700 7013 esc_html__('Set the Safe Search level for image searches. Brave Search only supports "Strict" and "Off" options.', 'mxchat') .
7701 7014 '</p>';
7702 7015 }
@@ -7704,18 +7017,14 @@
7704 7017 public function mxchat_brave_news_count_callback() {
7705 7018 $brave_news_count = isset($this->options['brave_news_count'])
7706 7019 ? intval($this->options['brave_news_count'])
7707 7020 : 3;
7708 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
7709 7021
7710 - echo '<div class="mxchat-field-wrapper">';
7711 7022 echo sprintf(
7712 7023 '<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
7024 + value="%d" min="1" max="10" class="small-text" />',
7025 + $brave_news_count
7716 7026 );
7717 - echo '</div>';
7718 7027 echo '<p class="description">' . esc_html__('Select the number of news articles to retrieve (1-10).', 'mxchat') . '</p>';
7719 7028 }
7720 7029
7721 7030 public function mxchat_brave_country_callback() {
@@ -7721,18 +7030,14 @@
7721 7030 public function mxchat_brave_country_callback() {
7722 7031 $brave_country = isset($this->options['brave_country'])
7723 7032 ? esc_attr($this->options['brave_country'])
7724 7033 : 'us';
7725 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
7726 7034
7727 - echo '<div class="mxchat-field-wrapper">';
7728 7035 echo sprintf(
7729 7036 '<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
7037 + value="%s" maxlength="2" class="small-text" />',
7038 + $brave_country
7733 7039 );
7734 - echo '</div>';
7735 7040 echo '<p class="description">' . esc_html__('Enter the country code (e.g., "us" for United States).', 'mxchat') . '</p>';
7736 7041 }
7737 7042
7738 7043 public function mxchat_brave_language_callback() {
@@ -7738,18 +7043,14 @@
7738 7043 public function mxchat_brave_language_callback() {
7739 7044 $brave_language = isset($this->options['brave_language'])
7740 7045 ? esc_attr($this->options['brave_language'])
7741 7046 : 'en';
7742 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
7743 7047
7744 - echo '<div class="mxchat-field-wrapper">';
7745 7048 echo sprintf(
7746 7049 '<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
7050 + value="%s" maxlength="2" class="small-text" />',
7051 + $brave_language
7750 7052 );
7751 - echo '</div>';
7752 7053 echo '<p class="description">' . esc_html__('Enter the language code (e.g., "en" for English).', 'mxchat') . '</p>';
7753 7054 }
7754 7055
7755 7056
@@ -7969,97 +7270,13 @@
7969 7270 );
7970 7271 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 7272 }
7972 7273
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 7274 public function mxchat_similarity_threshold_callback() {
8058 7275 // Load from mxchat_options array
8059 7276 $options = get_option('mxchat_options', []);
8060 7277
8061 - // Get value from options array with default of 35
7278 + // Get value from options array with default of 80
8062 7279 $threshold = isset($options['similarity_threshold']) ? $options['similarity_threshold'] : 35;
8063 7280
8064 7281 echo '<div class="slider-container">';
8065 7282 echo sprintf(
@@ -8077,62 +7294,13 @@
8077 7294 '<span id="threshold_value" class="range-value">%s</span>',
8078 7295 esc_html($threshold)
8079 7296 );
8080 7297 echo '</div>';
7298 + echo '<p class="description">';
7299 + 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');
7300 + echo '</p>';
8081 7301 }
8082 7302
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 7303 public function mxchat_enqueue_admin_assets() {
8136 7304 // Get plugin version
8137 7305 $version = MXCHAT_VERSION;
8138 7306
@@ -8158,11 +7326,8 @@
8158 7326 // Core admin styles
8159 7327 wp_enqueue_style('mxchat-admin-css', $plugin_url . 'css/admin-style.css', array(), $version);
8160 7328 wp_enqueue_style('mxchat-knowledge-css', $plugin_url . 'css/knowledge-style.css', array(), $version);
8161 7329
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 7330 // Core admin scripts
8166 7331 wp_enqueue_script('mxchat-admin-js', $plugin_url . 'js/mxchat-admin.js', array('jquery'), $version, true);
8167 7332 }
8168 7333 private function enqueue_page_specific_assets($current_page, $plugin_url, $version) {
@@ -8170,72 +7335,24 @@
8170 7335 case 'mxchat-prompts':
8171 7336 // Knowledge processing page assets
8172 7337 wp_enqueue_style('mxchat-content-selector-css', $plugin_url . 'css/content-selector.css', array(), $version);
8173 7338 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);
7339 + // Add the knowledge processing script
7340 + wp_enqueue_script('mxchat-knowledge-processing', $plugin_url . 'js/knowledge-processing.js', array('jquery'), $version, true);
8176 7341 break;
8177 7342
8178 7343 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);
7344 + wp_enqueue_style('mxchat-chat-transcripts-css', $plugin_url . 'css/chat-transcripts.css', array(), $version);
8183 7345 wp_enqueue_script('mxchat-transcripts-js', $plugin_url . 'js/mxchat_transcripts.js', array('jquery'), $version, true);
8184 7346 break;
8185 7347
8186 7348 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 - ));
7349 + wp_enqueue_style('mxchat-intent-css', $plugin_url . 'css/intent-style.css', array(), $version);
8217 7350 break;
8218 7351
8219 7352 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 7353 wp_enqueue_script('mxchat-activation-js', $plugin_url . 'js/activation-script.js', array('jquery'), $version, true);
8228 7354 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 7355 default:
8239 7356 wp_enqueue_script(
8240 7357 'mxchat-test-streaming-js',
8241 7358 $plugin_url . 'js/mxchat-test-streaming.js',
@@ -8245,80 +7362,10 @@
8245 7362 );
8246 7363
8247 7364 wp_localize_script('mxchat-test-streaming-js', 'mxchatTestStreamingAjax', [
8248 7365 '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')
7366 + 'nonce' => wp_create_nonce('mxchat_test_streaming_nonce')
8251 7367 ]);
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 7368 break;
8322 7369 }
8323 7370 }
8324 7371 private function localize_admin_scripts($current_page) {
@@ -8324,9 +7371,9 @@
8324 7371 private function localize_admin_scripts($current_page) {
8325 7372 // Base localization data for main admin script
8326 7373 $base_data = array(
8327 7374 'ajax_url' => admin_url('admin-ajax.php'),
8328 - 'nonce' => wp_create_nonce('mxchat_admin_nonce'),
7375 + 'nonce' => wp_create_nonce('mxchat_status_nonce'),
8329 7376 'admin_url' => admin_url(),
8330 7377 'license_nonce' => wp_create_nonce('mxchat_activate_license_nonce'),
8331 7378 'inline_edit_nonce' => wp_create_nonce('mxchat_save_inline_nonce'),
8332 7379 'setting_nonce' => wp_create_nonce('mxchat_save_setting_nonce'),
@@ -8333,14 +7380,10 @@
8333 7380 'export_nonce' => wp_create_nonce('mxchat_export_transcripts'),
8334 7381 'actions_nonce' => wp_create_nonce('mxchat_actions_nonce'),
8335 7382 'add_intent_nonce' => wp_create_nonce('mxchat_add_intent_nonce'),
8336 7383 '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 7384 'toggle_action_nonce' => wp_create_nonce('mxchat_actions_nonce'),
8342 - 'fetch_openrouter_models_nonce' => wp_create_nonce('mxchat_fetch_openrouter_models'),
7385 + 'fetch_openrouter_models_nonce' => wp_create_nonce('mxchat_fetch_openrouter_models'), // ADD THIS LINE
8343 7386 'is_activated' => $this->is_activated ? '1' : '0',
8344 7387 'status_refresh_interval' => 5000,
8345 7388 'prompts_setting_nonce' => wp_create_nonce('mxchat_prompts_setting_nonce'),
8346 7389 'ajaxurl' => admin_url('admin-ajax.php')
@@ -8380,13 +7423,11 @@
8380 7423 'queue_nonce' => wp_create_nonce('mxchat_queue_nonce'),
8381 7424 'stop_nonce' => wp_create_nonce('mxchat_stop_processing_action'),
8382 7425 'settings_nonce' => wp_create_nonce('mxchat_prompts_setting_nonce'),
8383 7426 'setting_nonce' => wp_create_nonce('mxchat_save_setting_nonce'),
8384 - 'entries_nonce' => wp_create_nonce('mxchat_entries_nonce'),
8385 7427 'admin_url' => admin_url(),
8386 7428 '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'
7429 + 'status_refresh_interval' => 2000
8389 7430 ));
8390 7431 break;
8391 7432
8392 7433 case 'mxchat-activation':
@@ -8395,60 +7436,8 @@
8395 7436 'license_nonce' => wp_create_nonce('mxchat_activate_license_nonce')
8396 7437 ));
8397 7438 break;
8398 7439
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 7440 case 'mxchat-settings':
8452 7441 default:
8453 7442 // Color picker and settings localization
8454 7443 wp_localize_script('mxchat-color-picker', 'mxchatStyleSettings', array(
@@ -8507,18 +7496,8 @@
8507 7496 $new_input['similarity_threshold'] = absint($input['similarity_threshold']); // Ensure it's an integer
8508 7497 $new_input['similarity_threshold'] = min(max($new_input['similarity_threshold'], 20), 85); // Enforce range
8509 7498 }
8510 7499
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 7500 if (isset($input['xai_api_key'])) {
8522 7501 $new_input['xai_api_key'] = sanitize_text_field($input['xai_api_key']);
8523 7502 }
8524 7503
@@ -8532,14 +7511,8 @@
8532 7511 // If checkbox not checked, it won't be in $input, so set to 'off'
8533 7512 $new_input['enable_streaming_toggle'] = 'off';
8534 7513 }
8535 7514
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 7515
8543 7516 if (isset($input['deepseek_api_key'])) {
8544 7517 $new_input['deepseek_api_key'] = sanitize_text_field($input['deepseek_api_key']);
8545 7518 }
@@ -8581,32 +7554,12 @@
8581 7554 if (isset($input['append_to_body'])) {
8582 7555 $new_input['append_to_body'] = $input['append_to_body'] === 'on' ? 'on' : 'off';
8583 7556 }
8584 7557
8585 - // Post type visibility settings
8586 - if (isset($input['post_type_visibility_mode'])) {
8587 - $allowed_modes = array('all', 'include', 'exclude');
8588 - $new_input['post_type_visibility_mode'] = in_array($input['post_type_visibility_mode'], $allowed_modes)
8589 - ? $input['post_type_visibility_mode']
8590 - : 'all';
8591 - }
8592 -
8593 - if (isset($input['post_type_visibility_list'])) {
8594 - if (is_array($input['post_type_visibility_list'])) {
8595 - $new_input['post_type_visibility_list'] = array_map('sanitize_key', $input['post_type_visibility_list']);
8596 - } else {
8597 - $new_input['post_type_visibility_list'] = array();
8598 - }
8599 - }
8600 -
8601 7558 if (isset($input['contextual_awareness_toggle'])) {
8602 7559 $new_input['contextual_awareness_toggle'] = $input['contextual_awareness_toggle'] === 'on' ? 'on' : 'off';
8603 7560 }
8604 7561
8605 - if (isset($input['citation_links_toggle'])) {
8606 - $new_input['citation_links_toggle'] = $input['citation_links_toggle'] === 'on' ? 'on' : 'off';
8607 -}
8608 -
8609 7562 if (isset($input['top_bar_title'])) {
8610 7563 $new_input['top_bar_title'] = sanitize_text_field($input['top_bar_title']);
8611 7564 }
8612 7565
@@ -8697,9 +7650,9 @@
8697 7650 'text-embedding-ada-002',
8698 7651 'text-embedding-3-small',
8699 7652 'text-embedding-3-large',
8700 7653 'voyage-3-large',
8701 - 'gemini-embedding-001'
7654 + 'gemini-embedding-exp-03-07'
8702 7655 );
8703 7656 if (in_array($input['embedding_model'], $allowed_models)) {
8704 7657 $new_input['embedding_model'] = sanitize_text_field($input['embedding_model']);
8705 7658 }
@@ -8709,13 +7662,8 @@
8709 7662 if ($input['model'] === 'openrouter') {
8710 7663 $new_input['model'] = 'openrouter';
8711 7664 } else {
8712 7665 $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 7666 'gemini-2.0-flash',
8719 7667 'gemini-2.0-flash-lite',
8720 7668 'gemini-1.5-pro',
8721 7669 'gemini-1.5-flash',
@@ -8727,33 +7675,35 @@
8727 7675 'grok-3-mini-beta',
8728 7676 'grok-3-mini-fast-beta',
8729 7677 'grok-2',
8730 7678 'deepseek-chat',
8731 - 'claude-opus-4-6',
8732 - 'claude-sonnet-4-6',
8733 - 'claude-opus-4-5',
8734 7679 'claude-sonnet-4-5-20250929',
8735 7680 'claude-opus-4-1-20250805',
8736 7681 'claude-haiku-4-5-20251001',
8737 7682 'claude-opus-4-20250514',
8738 7683 'claude-sonnet-4-20250514',
8739 - 'gpt-5.2',
8740 - 'gpt-5.1-chat-latest',
7684 + 'claude-3-7-sonnet-20250219',
7685 + // REMOVED: 'claude-3-5-sonnet-20241022',
7686 + 'claude-3-opus-20240229',
7687 + 'claude-3-sonnet-20240229',
7688 + 'claude-3-haiku-20240307',
8741 7689 'gpt-5.1-2025-11-13',
8742 7690 'gpt-5',
8743 7691 'gpt-5-mini',
8744 7692 'gpt-5-nano',
8745 - 'gpt-5.4',
8746 - 'gpt-5.4-mini',
8747 - 'gpt-5.4-nano',
8748 - 'gpt-5.3-chat-latest',
7693 + 'gpt-4.1-2025-04-14',
7694 + 'gpt-4o',
7695 + 'gpt-4o-mini',
7696 + 'gpt-4-turbo',
7697 + 'gpt-4',
7698 + 'gpt-3.5-turbo',
8749 7699 );
8750 -
7700 +
8751 7701 if (in_array($input['model'], $allowed_models)) {
8752 7702 $new_input['model'] = sanitize_text_field($input['model']);
8753 7703 } else {
8754 7704 // Fallback for any deprecated model
8755 - $new_input['model'] = 'gpt-5.1-chat-latest';
7705 + $new_input['model'] = 'claude-3-7-sonnet-20250219';
8756 7706 }
8757 7707 }
8758 7708 }
8759 7709
@@ -8771,9 +7721,16 @@
8771 7721 $new_input['openrouter_api_key'] = sanitize_text_field($input['openrouter_api_key']);
8772 7722 }
8773 7723
8774 7724
7725 + if (isset($input['close_button_color'])) {
7726 + $new_input['close_button_color'] = sanitize_hex_color($input['close_button_color']);
7727 + }
8775 7728
7729 + if (isset($input['chatbot_bg_color'])) {
7730 + $new_input['chatbot_bg_color'] = sanitize_hex_color($input['chatbot_bg_color']);
7731 + }
7732 +
8776 7733 if (isset($input['woocommerce_consumer_key'])) {
8777 7734 $new_input['woocommerce_consumer_key'] = sanitize_text_field($input['woocommerce_consumer_key']);
8778 7735 }
8779 7736
@@ -8780,9 +7737,76 @@
8780 7737 if (isset($input['woocommerce_consumer_secret'])) {
8781 7738 $new_input['woocommerce_consumer_secret'] = sanitize_text_field($input['woocommerce_consumer_secret']);
8782 7739 }
8783 7740
7741 + if (isset($input['user_message_bg_color'])) {
7742 + $new_input['user_message_bg_color'] = sanitize_hex_color($input['user_message_bg_color']);
7743 + }
8784 7744
7745 + if (isset($input['user_message_font_color'])) {
7746 + $new_input['user_message_font_color'] = sanitize_hex_color($input['user_message_font_color']);
7747 + }
7748 +
7749 + if (isset($input['bot_message_bg_color'])) {
7750 + $new_input['bot_message_bg_color'] = sanitize_hex_color($input['bot_message_bg_color']);
7751 + }
7752 +
7753 + if (isset($input['bot_message_font_color'])) {
7754 + $new_input['bot_message_font_color'] = sanitize_hex_color($input['bot_message_font_color']);
7755 + }
7756 +
7757 + if (isset($input['live_agent_message_bg_color'])) {
7758 + $new_input['live_agent_message_bg_color'] = sanitize_hex_color($input['live_agent_message_bg_color']);
7759 + }
7760 +
7761 + if (isset($input['live_agent_message_font_color'])) {
7762 + $new_input['live_agent_message_font_color'] = sanitize_hex_color($input['live_agent_message_font_color']);
7763 + }
7764 +
7765 + if (isset($input['mode_indicator_bg_color'])) {
7766 + $new_input['mode_indicator_bg_color'] = sanitize_hex_color($input['mode_indicator_bg_color']);
7767 + }
7768 +
7769 + if (isset($input['mode_indicator_font_color'])) {
7770 + $new_input['mode_indicator_font_color'] = sanitize_hex_color($input['mode_indicator_font_color']);
7771 + }
7772 +
7773 + if (isset($input['toolbar_icon_color'])) {
7774 + $new_input['toolbar_icon_color'] = sanitize_hex_color($input['toolbar_icon_color']);
7775 + }
7776 +
7777 + if (isset($input['top_bar_bg_color'])) {
7778 + $new_input['top_bar_bg_color'] = sanitize_hex_color($input['top_bar_bg_color']);
7779 + }
7780 +
7781 + if (isset($input['quick_questions_toggle_color'])) {
7782 + $new_input['quick_questions_toggle_color'] = sanitize_hex_color($input['quick_questions_toggle_color']);
7783 + }
7784 +
7785 + if (isset($input['send_button_font_color'])) {
7786 + $new_input['send_button_font_color'] = sanitize_hex_color($input['send_button_font_color']);
7787 + }
7788 +
7789 + if (isset($input['chatbot_background_color'])) {
7790 + $new_input['chatbot_background_color'] = sanitize_hex_color($input['chatbot_background_color']);
7791 + }
7792 +
7793 + if (isset($input['icon_color'])) {
7794 + $new_input['icon_color'] = sanitize_hex_color($input['icon_color']);
7795 + }
7796 +
7797 + if (isset($input['custom_icon'])) {
7798 + $new_input['custom_icon'] = esc_url_raw($input['custom_icon']);
7799 + }
7800 +
7801 + if (isset($input['title_icon'])) {
7802 + $new_input['title_icon'] = esc_url_raw($input['title_icon']);
7803 + }
7804 +
7805 + if (isset($input['chat_input_font_color'])) {
7806 + $new_input['chat_input_font_color'] = sanitize_hex_color($input['chat_input_font_color']);
7807 + }
7808 +
8785 7809 // Sanitize link_target_toggle
8786 7810 if (isset($input['link_target_toggle'])) {
8787 7811 $new_input['link_target_toggle'] = $input['link_target_toggle'] === 'on' ? 'on' : 'off';
8788 7812 }
@@ -8915,226 +7939,12 @@
8915 7939 if (isset($input['live_agent_notification_message'])) {
8916 7940 $new_input['live_agent_notification_message'] = sanitize_textarea_field($input['live_agent_notification_message']);
8917 7941 }
8918 7942
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 7943 return $new_input;
8989 7944 }
8990 7945
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 7946
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 7947 // Method to append the chatbot to the body
9138 7948 public function mxchat_append_chatbot_to_body() {
9139 7949 $options = get_option('mxchat_options');
9140 7950 if (isset($options['append_to_body']) && $options['append_to_body'] === 'on') {
@@ -9204,65 +8014,73 @@
9204 8014 //error_log('DEBUG: Permission check failed');
9205 8015 wp_die(__('You do not have sufficient permissions to delete all prompts.', 'mxchat'));
9206 8016 }
9207 8017
9208 - // Get bot_id and content type filter from POST data
8018 + // Get bot_id from POST data
9209 8019 $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 -
8020 + //error_log('DEBUG: Bot ID from POST: ' . $bot_id);
8021 +
9212 8022 $success = true;
9213 8023 $error_messages = array();
9214 -
8024 +
9215 8025 // Get bot-specific Pinecone configuration
9216 8026 $pinecone_manager = MxChat_Pinecone_Manager::get_instance();
9217 8027 $pinecone_options = $pinecone_manager->mxchat_get_bot_pinecone_options($bot_id);
9218 -
8028 +
8029 + //error_log('DEBUG: Pinecone options retrieved:');
8030 + //error_log(' - Use Pinecone: ' . ($pinecone_options['mxchat_use_pinecone'] ?? 'NOT SET'));
8031 + //error_log(' - API Key: ' . (empty($pinecone_options['mxchat_pinecone_api_key']) ? 'EMPTY' : 'SET'));
8032 + //error_log(' - Host: ' . ($pinecone_options['mxchat_pinecone_host'] ?? 'NOT SET'));
8033 +
9219 8034 $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
9220 -
8035 +
9221 8036 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 -
8037 + //error_log('[MXCHAT-DELETE] Pinecone is enabled for bot ' . $bot_id . ' - deleting all from Pinecone');
8038 +
8039 + // Delete from bot-specific Pinecone index
8040 + $result = $pinecone_manager->mxchat_delete_all_from_pinecone($pinecone_options);
8041 +
8042 + //error_log('DEBUG: Delete all result: ' . ($result['success'] ? 'SUCCESS' : 'FAILED'));
9225 8043 if (!$result['success']) {
8044 + //error_log('DEBUG: Delete all error: ' . $result['message']);
8045 + }
8046 +
8047 + if (!$result['success']) {
9226 8048 $success = false;
9227 8049 $error_messages[] = $result['message'];
9228 8050 }
9229 -
8051 +
8052 + // No cache clearing needed since we removed caching
8053 +
9230 8054 } else {
8055 + //error_log('[MXCHAT-DELETE] Pinecone not enabled for bot ' . $bot_id . ' - deleting from WordPress database');
8056 +
9231 8057 // Delete from WordPress database
9232 8058 global $wpdb;
9233 8059 $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
8060 +
8061 + // If multi-bot is active and we have a specific bot, delete only that bot's content
9240 8062 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));
8063 + $result = $wpdb->delete(
8064 + $table_name,
8065 + array('bot_id' => $bot_id),
8066 + array('%s')
8067 + );
9254 8068 } else {
9255 - // No filters — delete all
8069 + // Delete all content for default bot
9256 8070 $result = $wpdb->query("DELETE FROM {$table_name}");
9257 8071 }
9258 -
8072 +
9259 8073 if ($result === false) {
9260 8074 $success = false;
9261 8075 $error_messages[] = 'Failed to delete from WordPress database';
9262 8076 }
9263 8077 }
8078 +
8079 + // No cache clearing needed since we removed caching
9264 8080
8081 + //error_log('=== DELETE ALL DEBUG END ===');
8082 +
9265 8083 // Redirect back with a success message and bot_id
9266 8084 $redirect_url = add_query_arg(array(
9267 8085 'page' => 'mxchat-prompts',
9268 8086 'bot_id' => $bot_id,
@@ -9567,9 +8385,8 @@
9567 8385 if ($embedding_dimensions !== 1536) {
9568 8386 //error_log('[MXCHAT-EMBED] Warning: Unexpected Gemini embedding dimensions: ' . $embedding_dimensions);
9569 8387 }
9570 8388
9571 - MxChat_Utils::stamp_active_embedding_model($selected_model);
9572 8389 return $response_data['embedding']['values'];
9573 8390 } else {
9574 8391 //error_log('[MXCHAT-EMBED] Error: No embedding found in Gemini response');
9575 8392 //error_log('[MXCHAT-EMBED] Response structure: ' . wp_json_encode(array_keys($response_data)));
@@ -9595,9 +8412,8 @@
9595 8412 ($selected_model === 'voyage-3-large' && $embedding_dimensions !== 2048)) {
9596 8413 //error_log('[MXCHAT-EMBED] Warning: Unexpected embedding dimensions');
9597 8414 }
9598 8415
9599 - MxChat_Utils::stamp_active_embedding_model($selected_model);
9600 8416 return $response_data['data'][0]['embedding'];
9601 8417 } else {
9602 8418 //error_log('[MXCHAT-EMBED] Error: No embedding found in response');
9603 8419 //error_log('[MXCHAT-EMBED] Response structure: ' . wp_json_encode(array_keys($response_data)));