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 +2881 -4161 3.2.52.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();
@@ -37,12 +21,8 @@
37 21 }
38 22
39 23 // Add admin menu and initialize settings
40 24 add_action('admin_menu', array($this, 'mxchat_add_plugin_page'));
41 - // Pro & Extensions registers at priority 30 so it always lands last in
42 - // the sidebar — below API Access (priority 20) and below any other
43 - // submenu that hooks at default priority 10.
44 - add_action('admin_menu', array($this, 'mxchat_add_pro_extensions_page'), 30);
45 25 add_action('admin_init', array($this, 'mxchat_page_init'));
46 26 add_action('admin_init', array($this, 'mxchat_prompts_page_init'));
47 27 add_action('admin_enqueue_scripts', array($this, 'mxchat_enqueue_admin_assets'));
48 28 add_action('wp_ajax_mxchat_delete_chat_history', array($this, 'mxchat_delete_chat_history'));
@@ -48,9 +28,8 @@
48 28 add_action('wp_ajax_mxchat_delete_chat_history', array($this, 'mxchat_delete_chat_history'));
49 29 add_action('admin_post_mxchat_delete_prompt', array($this, 'mxchat_handle_delete_prompt'));
50 30 add_action('wp_ajax_mxchat_fetch_chat_history', array($this, 'mxchat_fetch_chat_history'));
51 31 add_action('wp_ajax_nopriv_mxchat_fetch_chat_history', array($this, 'mxchat_fetch_chat_history'));
52 - add_action('wp_ajax_mxchat_fetch_conversation', array($this, 'mxchat_fetch_conversation'));
53 32 add_action('wp_footer', array($this, 'mxchat_append_chatbot_to_body'));
54 33 add_action('admin_head-mxchat-prompts', array($this, 'mxchat_enqueue_admin_assets'));
55 34 add_action('admin_head-toplevel_page_mxchat-max', array($this, 'mxchat_enqueue_admin_assets'));
56 35 add_action('admin_notices', array($this, 'mxchat_display_admin_notice'));
@@ -58,191 +37,29 @@
58 37 add_action('admin_post_mxchat_add_intent', array($this, 'mxchat_handle_add_intent'));
59 38 add_action('admin_post_mxchat_delete_intent', array($this, 'mxchat_handle_delete_intent'));
60 39 add_action('admin_post_mxchat_edit_intent', array($this, 'mxchat_handle_edit_intent'));
61 40 add_action('wp_ajax_mxchat_export_transcripts', array($this, 'export_chat_transcripts'));
62 -
63 - // Leads tab (inside Transcripts)
64 - add_action('wp_ajax_mxchat_fetch_leads', array($this, 'mxchat_fetch_leads'));
65 - add_action('wp_ajax_mxchat_delete_leads', array($this, 'mxchat_delete_leads'));
66 - add_action('wp_ajax_mxchat_export_leads', array($this, 'mxchat_export_leads'));
67 -
68 41 add_action('admin_init', array($this, 'mxchat_transcripts_page_init'));
69 42 add_action('wp_ajax_dismiss_live_agent_notice', array($this, 'dismiss_live_agent_notice'));
70 - add_action('wp_ajax_dismiss_theme_migration_notice', array($this, 'dismiss_theme_migration_notice'));
71 43 add_action('mxchat_cleanup_old_transcripts', array($this, 'cleanup_old_transcripts'));
72 44
73 45 add_action('admin_init', array($this, 'register_pinecone_settings'));
74 - add_action('admin_init', array($this, 'register_openai_vectorstore_settings'));
75 46
76 47 add_action('admin_notices', array($this, 'display_admin_notices'));
77 48
78 49 add_action('wp_ajax_mxchat_test_streaming_actual', [$this, 'mxchat_handle_test_streaming_actual']);
79 50 add_action('wp_ajax_mxchat_test_streaming', [$this, 'mxchat_handle_test_streaming']); // Keep existing as fallback
80 - add_action('wp_ajax_mxchat_test_vectorstore_connection', array($this, 'mxchat_test_vectorstore_connection'));
81 51
82 52 add_action('wp_ajax_mxchat_save_selected_bot', array($this, 'mxchat_save_selected_bot'));
83 53 add_action('wp_ajax_mxchat_fetch_openrouter_models', array($this, 'fetch_openrouter_models'));
84 - add_action('wp_ajax_mxchat_get_rag_context', array($this, 'mxchat_get_rag_context'));
85 -
86 - // Actions page AJAX handlers
87 - add_action('wp_ajax_mxchat_fetch_actions_list', array($this, 'mxchat_fetch_actions_list'));
88 - add_action('wp_ajax_mxchat_toggle_action_status', array($this, 'mxchat_toggle_action_status'));
89 - add_action('wp_ajax_mxchat_bulk_delete_actions', array($this, 'mxchat_bulk_delete_actions'));
90 - add_action('wp_ajax_mxchat_add_intent_ajax', array($this, 'mxchat_add_intent_ajax'));
91 - add_action('wp_ajax_mxchat_edit_intent_ajax', array($this, 'mxchat_edit_intent_ajax'));
92 - add_action('wp_ajax_mxchat_delete_intent_ajax', array($this, 'mxchat_delete_intent_ajax'));
93 - add_action('wp_ajax_mxchat_add_phrase', array($this, 'mxchat_add_phrase_ajax'));
94 - add_action('wp_ajax_mxchat_delete_phrase', array($this, 'mxchat_delete_phrase_ajax'));
95 - add_action('wp_ajax_mxchat_get_phrases', array($this, 'mxchat_get_phrases_ajax'));
96 - add_action('wp_ajax_mxchat_delete_legacy_phrases', array($this, 'mxchat_delete_legacy_phrases_ajax'));
97 -
98 - // Slack test connection
99 - add_action('wp_ajax_mxchat_test_slack_connection', array($this, 'mxchat_test_slack_connection'));
100 -
101 - // Translation handlers
102 - add_action('wp_ajax_mxchat_translate_messages', array($this, 'mxchat_translate_messages'));
103 - add_action('wp_ajax_mxchat_get_transcript_translation', array($this, 'mxchat_get_transcript_translation'));
104 -
105 - // 3.2.3: Embedding model switch protection
106 - add_action('wp_ajax_mxchat_check_embedding_switch', array($this, 'mxchat_check_embedding_switch_ajax'));
107 - add_action('wp_ajax_mxchat_dismiss_embedding_mismatch', array($this, 'mxchat_dismiss_embedding_mismatch_ajax'));
108 - add_action('admin_notices', array($this, 'mxchat_embedding_mismatch_notice'));
109 54 }
110 55
111 - /**
112 - * 3.2.3: Preflight check before allowing the embedding model dropdown to
113 - * switch. Pure option comparison — no counts, no DB queries beyond the
114 - * cached options. The dialog is shown whenever the user has previously
115 - * embedded with a different model than the one they're switching to.
116 - */
117 - public function mxchat_check_embedding_switch_ajax() {
118 - check_ajax_referer('mxchat_admin_nonce', 'security');
119 - if (!current_user_can('manage_options')) {
120 - wp_send_json_error(__('Unauthorized', 'mxchat'));
121 - }
122 -
123 - $new_model = isset($_POST['new_model']) ? sanitize_text_field(wp_unslash($_POST['new_model'])) : '';
124 - $active_model = MxChat_Utils::get_active_embedding_model();
125 -
126 - $is_mismatch = !empty($active_model) && !empty($new_model) && $active_model !== $new_model;
127 -
128 - $active_dims = MxChat_Utils::embedding_model_dimensions($active_model);
129 - $new_dims = MxChat_Utils::embedding_model_dimensions($new_model);
130 -
131 - wp_send_json_success(array(
132 - 'is_mismatch' => $is_mismatch,
133 - 'active_model' => $active_model,
134 - 'active_label' => MxChat_Utils::embedding_model_label($active_model),
135 - 'new_model' => $new_model,
136 - 'new_label' => MxChat_Utils::embedding_model_label($new_model),
137 - 'dims_differ' => ($active_dims > 0 && $new_dims > 0 && $active_dims !== $new_dims),
138 - 'active_dims' => $active_dims,
139 - 'new_dims' => $new_dims,
140 - ));
141 - }
142 -
143 - /**
144 - * 3.2.3: Dismiss the persistent mismatch banner. Tied to the active+selected
145 - * pair so the banner reappears on the next switch event.
146 - */
147 - public function mxchat_dismiss_embedding_mismatch_ajax() {
148 - check_ajax_referer('mxchat_admin_nonce', 'security');
149 - if (!current_user_can('manage_options')) {
150 - wp_send_json_error(__('Unauthorized', 'mxchat'));
151 - }
152 -
153 - $options = get_option('mxchat_options', array());
154 - $selected = $options['embedding_model'] ?? '';
155 - $active = MxChat_Utils::get_active_embedding_model();
156 - update_option('mxchat_dismissed_embedding_mismatch', $active . '|' . $selected, false);
157 - wp_send_json_success();
158 - }
159 -
160 - /**
161 - * 3.2.3: Persistent admin banner shown whenever the active embedding model
162 - * (last used to actually embed something) differs from the currently
163 - * selected model. Pure option comparison — no DB queries on every page
164 - * load. The banner auto-clears once both match again, i.e. after a delete
165 - * + re-embed cycle.
166 - */
167 - public function mxchat_embedding_mismatch_notice() {
168 - if (!current_user_can('manage_options')) {
169 - return;
170 - }
171 -
172 - $options = get_option('mxchat_options', array());
173 - $selected = $options['embedding_model'] ?? '';
174 - $active = MxChat_Utils::get_active_embedding_model();
175 -
176 - if (empty($active) || empty($selected) || $active === $selected) {
177 - return;
178 - }
179 -
180 - $dismissed = get_option('mxchat_dismissed_embedding_mismatch', '');
181 - if ($dismissed === $active . '|' . $selected) {
182 - return;
183 - }
184 -
185 - $active_label = MxChat_Utils::embedding_model_label($active);
186 - $selected_label = MxChat_Utils::embedding_model_label($selected);
187 - $active_dims = MxChat_Utils::embedding_model_dimensions($active);
188 - $selected_dims = MxChat_Utils::embedding_model_dimensions($selected);
189 - $dims_differ = ($active_dims > 0 && $selected_dims > 0 && $active_dims !== $selected_dims);
190 -
191 - $kb_url = admin_url('admin.php?page=mxchat-prompts');
192 - $actions_url = admin_url('admin.php?page=mxchat-actions');
193 -
194 - ?>
195 - <div class="notice notice-error is-dismissible mxchat-embedding-mismatch-notice"
196 - data-active="<?php echo esc_attr($active); ?>"
197 - data-selected="<?php echo esc_attr($selected); ?>">
198 - <p><strong><?php esc_html_e('MxChat: Embedding model mismatch detected', 'mxchat'); ?></strong></p>
199 - <p>
200 - <?php
201 - printf(
202 - /* translators: 1: previously-used model name, 2: currently-selected model name */
203 - 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'),
204 - '<code>' . esc_html($active_label) . '</code>',
205 - '<code>' . esc_html($selected_label) . '</code>'
206 - );
207 - ?>
208 - </p>
209 - <?php if ($dims_differ) : ?>
210 - <p>
211 - <strong><?php esc_html_e('Dimension mismatch:', 'mxchat'); ?></strong>
212 - <?php
213 - printf(
214 - /* translators: 1: old dim count, 2: new dim count */
215 - 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'),
216 - (int) $active_dims,
217 - (int) $selected_dims
218 - );
219 - ?>
220 - </p>
221 - <?php endif; ?>
222 - <p>
223 - <?php esc_html_e('To fix this:', 'mxchat'); ?>
224 - <a href="<?php echo esc_url($kb_url); ?>"><?php esc_html_e('Delete all knowledge base entries', 'mxchat'); ?></a> ·
225 - <a href="<?php echo esc_url($actions_url); ?>"><?php esc_html_e('Delete all actions', 'mxchat'); ?></a> ·
226 - <?php esc_html_e('then re-import / re-add them with the new model selected.', 'mxchat'); ?>
227 - </p>
228 - </div>
229 - <script>
230 - (function($){
231 - $(document).on('click', '.mxchat-embedding-mismatch-notice .notice-dismiss', function(){
232 - $.post(ajaxurl, {
233 - action: 'mxchat_dismiss_embedding_mismatch',
234 - security: '<?php echo esc_js(wp_create_nonce('mxchat_admin_nonce')); ?>'
235 - });
236 - });
237 - })(jQuery);
238 - </script>
239 - <?php
240 - }
241 -
242 56 private function is_license_active() {
57 + // Get the raw value without translation
243 58 $license_status = get_option('mxchat_license_status', 'inactive');
244 - 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'));
245 62 }
246 63
247 64 private function initialize_default_options() {
248 65 $default_options = array(
@@ -251,10 +68,9 @@
251 68 'claude_api_key' => '',
252 69 'deepseek_api_key' => '',
253 70 'voyage_api_key' => '',
254 71 'gemini_api_key' => '',
255 - 'enable_streaming_toggle' => 'off',
256 - 'enable_web_search' => 'off',
72 + 'enable_streaming_toggle' => 'on',
257 73 'embedding_model' => 'text-embedding-ada-002',
258 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:
259 75
260 76 # Response Style - CRITICALLY IMPORTANT
@@ -273,9 +89,9 @@
273 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."
274 90 - NEVER invent or hallucinate URLs, links, product specs, procedures, dates, statistics, names, contacts, or company information
275 91 - When knowledge base information is unclear or contradictory, acknowledge the limitation rather than guessing
276 92 - Better to admit insufficient information than provide inaccurate answers',
277 - 'model' => esc_html__('gpt-5.1-chat-latest', 'mxchat'),
93 + 'model' => esc_html__('gpt-4o', 'mxchat'),
278 94 'rate_limit_logged_out' => esc_html__('100', 'mxchat'),
279 95 'role_rate_limits' => array(),
280 96 'rate_limit_message' => esc_html__('Rate limit exceeded. Please try again later.', 'mxchat'),
281 97 'enable_email_block' => '',
@@ -287,12 +103,9 @@
287 103 'intro_message' => __('Hello! How can I assist you today?', 'mxchat'),
288 104 'ai_agent_text' => esc_html__('AI Agent', 'mxchat'),
289 105 'input_copy' => esc_html__('How can I assist?', 'mxchat'),
290 106 'append_to_body' => esc_html__('off', 'mxchat'),
291 - 'post_type_visibility_mode' => 'all', // 'all', 'include', 'exclude'
292 - 'post_type_visibility_list' => array(), // Array of post type slugs
293 107 'contextual_awareness_toggle' => 'off',
294 - 'citation_links_toggle' => 'on',
295 108 'close_button_color' => esc_html__('#fff', 'mxchat'),
296 109 'chatbot_bg_color' => esc_html__('#fff', 'mxchat'),
297 110 'user_message_bg_color' => esc_html__('#fff', 'mxchat'),
298 111 'user_message_font_color' => esc_html__('#212121', 'mxchat'),
@@ -321,33 +134,18 @@
321 134 'pdf_max_pages' => 69,
322 135 'show_pdf_upload_button' => 'on',
323 136 'show_word_upload_button' => 'on',
324 137
325 - // Live Agent Integration (Slack)
138 + // Live Agent Integration
326 139 'live_agent_webhook_url' => '',
327 140 'live_agent_secret_key' => '',
328 141 'live_agent_bot_token' => '',
329 142 'live_agent_message_bg_color' => esc_html__('#ffffff', 'mxchat'),
330 143 'live_agent_message_font_color' => esc_html__('#333333', 'mxchat'),
331 -
332 - // Telegram Integration
333 - 'telegram_status' => 'off',
334 - 'telegram_bot_token' => '',
335 - 'telegram_group_id' => '',
336 - 'telegram_webhook_secret' => '',
337 - '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'),
338 - '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'),
339 -
340 144 'chat_toolbar_toggle' => esc_html__('off', 'mxchat'),
341 145 'mode_indicator_bg_color' => esc_html__('#767676', 'mxchat'),
342 146 'mode_indicator_font_color' => esc_html__('#ffffff', 'mxchat'),
343 147 'toolbar_icon_color' => esc_html__('#212121', 'mxchat'),
344 -
345 - // Optimization settings
346 - 'script_loading_strategy' => 'default',
347 -
348 - // Debug settings
349 - 'debug_mode' => 'off',
350 148 );
351 149
352 150
353 151 // Merge existing options with defaults
@@ -382,18 +180,8 @@
382 180 'dashicons-testimonial',
383 181 6
384 182 );
385 183
386 - // Rename the first submenu item from "MxChat" to "Settings"
387 - add_submenu_page(
388 - 'mxchat-max',
389 - esc_html__('MxChat Settings', 'mxchat'),
390 - esc_html__('Settings', 'mxchat'),
391 - 'manage_options',
392 - 'mxchat-max',
393 - array($this, 'mxchat_create_admin_page')
394 - );
395 -
396 184 // Submenu page for Knowledge
397 185 add_submenu_page(
398 186 'mxchat-max',
399 187 esc_html__('Prompts', 'mxchat'),
@@ -420,30 +208,22 @@
420 208 'mxchat-actions',
421 209 array($this, 'mxchat_actions_page_html')
422 210 );
423 211
424 - // Content Generator page
425 - add_submenu_page(
426 - 'mxchat-max',
427 - esc_html__('Content', 'mxchat'),
428 - esc_html__('Content', 'mxchat'),
429 - 'manage_options',
430 - 'mxchat-content',
431 - array($this, 'mxchat_create_content_page')
432 - );
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 + );
433 220
434 -}
435 -
436 -/**
437 - * Register the Pro & Extensions submenu on a later admin_menu priority so it
438 - * always renders as the bottom-most item in the MxChat sidebar — below
439 - * configuration pages like API Access (priority 20).
440 - */
441 -public function mxchat_add_pro_extensions_page() {
221 + // Submenu page for Activation Key
442 222 add_submenu_page(
443 223 'mxchat-max',
444 - esc_html__('Pro & Extensions', 'mxchat'),
445 - esc_html__('Pro & Extensions', 'mxchat'),
224 + esc_html__('Pro Upgrade', 'mxchat'),
225 + esc_html__('Pro Upgrade', 'mxchat'),
446 226 'manage_options',
447 227 'mxchat-activation',
448 228 array($this, 'mxchat_create_activation_page')
449 229 );
@@ -455,16 +235,8 @@
455 235 $addons_page->render_page();
456 236 }
457 237
458 238 /**
459 - * Render the Content Generator admin page
460 - */
461 -public function mxchat_create_content_page() {
462 - require_once plugin_dir_path(__FILE__) . 'admin-content-page.php';
463 - mxchat_render_content_page($this);
464 -}
465 -
466 -/**
467 239 * Test actual streaming functionality in the WordPress environment
468 240 */
469 241 public function mxchat_handle_test_streaming_actual() {
470 242 check_ajax_referer('mxchat_test_streaming_nonce', 'nonce');
@@ -482,9 +254,9 @@
482 254 }
483 255
484 256 // Get user's selected model and API key
485 257 $options = get_option('mxchat_options', []);
486 - $selected_model = $options['model'] ?? 'gpt-5.1-chat-latest';
258 + $selected_model = $options['model'] ?? 'gpt-4o';
487 259
488 260 // Get the provider from the model
489 261 $model_parts = explode('-', $selected_model);
490 262 $provider = strtolower($model_parts[0]);
@@ -753,236 +525,8 @@
753 525
754 526 return $sanitized;
755 527 }
756 528
757 -public function register_openai_vectorstore_settings() {
758 - register_setting(
759 - 'mxchat_openai_vectorstore_options',
760 - 'mxchat_openai_vectorstore_options',
761 - array(
762 - 'type' => 'array',
763 - 'sanitize_callback' => array($this, 'sanitize_openai_vectorstore_settings'),
764 - 'default' => array(
765 - 'mxchat_use_openai_vectorstore' => '0',
766 - 'mxchat_vectorstore_ids' => '',
767 - 'mxchat_vectorstore_max_results' => 5
768 - )
769 - )
770 - );
771 -}
772 -
773 -public function sanitize_openai_vectorstore_settings($input) {
774 - $sanitized = array();
775 -
776 - $sanitized['mxchat_use_openai_vectorstore'] = isset($input['mxchat_use_openai_vectorstore']) ? '1' : '0';
777 - $sanitized['mxchat_vectorstore_ids'] = sanitize_text_field($input['mxchat_vectorstore_ids'] ?? '');
778 - $sanitized['mxchat_vectorstore_max_results'] = absint($input['mxchat_vectorstore_max_results'] ?? 5);
779 -
780 - // Ensure max results is within reasonable range
781 - if ($sanitized['mxchat_vectorstore_max_results'] < 1) {
782 - $sanitized['mxchat_vectorstore_max_results'] = 1;
783 - }
784 - if ($sanitized['mxchat_vectorstore_max_results'] > 20) {
785 - $sanitized['mxchat_vectorstore_max_results'] = 20;
786 - }
787 -
788 - return $sanitized;
789 -}
790 -
791 -/**
792 - * AJAX handler to test OpenAI Vector Store connection
793 - */
794 -public function mxchat_test_vectorstore_connection() {
795 - check_ajax_referer('mxchat_admin_nonce', 'nonce');
796 -
797 - if (!current_user_can('manage_options')) {
798 - wp_send_json_error(array('message' => __('Permission denied.', 'mxchat')));
799 - return;
800 - }
801 -
802 - $vectorstore_options = get_option('mxchat_openai_vectorstore_options', array());
803 - $vectorstore_ids = $vectorstore_options['mxchat_vectorstore_ids'] ?? '';
804 - $mxchat_options = get_option('mxchat_options', array());
805 - $api_key = $mxchat_options['api_key'] ?? '';
806 -
807 - if (empty($api_key)) {
808 - wp_send_json_error(array('message' => __('OpenAI API key is not configured.', 'mxchat')));
809 - return;
810 - }
811 -
812 - if (empty($vectorstore_ids)) {
813 - wp_send_json_error(array('message' => __('No Vector Store ID configured.', 'mxchat')));
814 - return;
815 - }
816 -
817 - // Get the first Vector Store ID for testing
818 - $ids_array = array_map('trim', explode(',', $vectorstore_ids));
819 - $test_id = $ids_array[0];
820 -
821 - // Test by retrieving the Vector Store info
822 - $response = wp_remote_get(
823 - 'https://api.openai.com/v1/vector_stores/' . $test_id,
824 - array(
825 - 'headers' => array(
826 - 'Authorization' => 'Bearer ' . $api_key,
827 - 'Content-Type' => 'application/json',
828 - 'OpenAI-Beta' => 'assistants=v2'
829 - ),
830 - 'timeout' => 30
831 - )
832 - );
833 -
834 - if (is_wp_error($response)) {
835 - wp_send_json_error(array('message' => __('Connection failed: ', 'mxchat') . $response->get_error_message()));
836 - return;
837 - }
838 -
839 - $status_code = wp_remote_retrieve_response_code($response);
840 - $body = json_decode(wp_remote_retrieve_body($response), true);
841 -
842 - if ($status_code === 200 && isset($body['id'])) {
843 - $file_count = $body['file_counts']['completed'] ?? 0;
844 - $name = $body['name'] ?? $test_id;
845 - wp_send_json_success(array(
846 - 'message' => sprintf(
847 - __('Connected successfully! Vector Store: %s (%d files)', 'mxchat'),
848 - esc_html($name),
849 - $file_count
850 - )
851 - ));
852 - } elseif ($status_code === 404) {
853 - wp_send_json_error(array('message' => __('Vector Store not found. Please check the ID.', 'mxchat')));
854 - } elseif ($status_code === 401) {
855 - wp_send_json_error(array('message' => __('Invalid API key.', 'mxchat')));
856 - } else {
857 - $error_message = $body['error']['message'] ?? __('Unknown error occurred.', 'mxchat');
858 - wp_send_json_error(array('message' => $error_message));
859 - }
860 -}
861 -
862 -/**
863 - * AJAX handler to test Slack connection and validate scopes
864 - */
865 -public function mxchat_test_slack_connection() {
866 - check_ajax_referer('mxchat_admin_nonce', 'nonce');
867 -
868 - if (!current_user_can('manage_options')) {
869 - wp_send_json_error(array('message' => __('Permission denied.', 'mxchat')));
870 - return;
871 - }
872 -
873 - $bot_token = $this->options['live_agent_bot_token'] ?? '';
874 -
875 - if (empty($bot_token)) {
876 - wp_send_json_error(array('message' => __('Slack Bot Token is not configured. Please enter your token and save settings first.', 'mxchat')));
877 - return;
878 - }
879 -
880 - // Test 1: Validate bot token with auth.test
881 - $auth_response = wp_remote_post('https://slack.com/api/auth.test', array(
882 - 'headers' => array(
883 - 'Authorization' => 'Bearer ' . $bot_token,
884 - 'Content-Type' => 'application/json'
885 - ),
886 - 'timeout' => 15
887 - ));
888 -
889 - if (is_wp_error($auth_response)) {
890 - wp_send_json_error(array('message' => __('Connection failed: ', 'mxchat') . $auth_response->get_error_message()));
891 - return;
892 - }
893 -
894 - $auth_body = json_decode(wp_remote_retrieve_body($auth_response), true);
895 -
896 - if (!isset($auth_body['ok']) || !$auth_body['ok']) {
897 - $error = $auth_body['error'] ?? 'unknown_error';
898 - $error_messages = array(
899 - 'invalid_auth' => __('Invalid bot token. Please check your token starts with xoxb-', 'mxchat'),
900 - 'not_authed' => __('No authentication token provided.', 'mxchat'),
901 - 'account_inactive' => __('The Slack workspace has been deactivated.', 'mxchat'),
902 - 'token_revoked' => __('The bot token has been revoked. Please generate a new one.', 'mxchat'),
903 - );
904 - $message = $error_messages[$error] ?? sprintf(__('Authentication failed: %s', 'mxchat'), $error);
905 - wp_send_json_error(array('message' => $message));
906 - return;
907 - }
908 -
909 - $team_name = $auth_body['team'] ?? 'Unknown Workspace';
910 - $bot_name = $auth_body['user'] ?? 'Unknown Bot';
911 -
912 - // Test 2: Check if we can list channels (tests channels:read scope)
913 - $channels_response = wp_remote_post('https://slack.com/api/conversations.list', array(
914 - 'headers' => array(
915 - 'Authorization' => 'Bearer ' . $bot_token,
916 - 'Content-Type' => 'application/json'
917 - ),
918 - 'body' => json_encode(array('limit' => 1)),
919 - 'timeout' => 15
920 - ));
921 -
922 - $channels_body = json_decode(wp_remote_retrieve_body($channels_response), true);
923 - $can_read_channels = isset($channels_body['ok']) && $channels_body['ok'];
924 -
925 - // Test 3: Check if we can create channels (tests channels:manage scope)
926 - // We'll just check the error message without actually creating
927 - $create_response = wp_remote_post('https://slack.com/api/conversations.create', array(
928 - 'headers' => array(
929 - 'Authorization' => 'Bearer ' . $bot_token,
930 - 'Content-Type' => 'application/json'
931 - ),
932 - 'body' => json_encode(array('name' => 'mxchat-test-' . time(), 'is_private' => false)),
933 - 'timeout' => 15
934 - ));
935 -
936 - $create_body = json_decode(wp_remote_retrieve_body($create_response), true);
937 -
938 - // If channel was created, delete it immediately
939 - if (isset($create_body['ok']) && $create_body['ok'] && isset($create_body['channel']['id'])) {
940 - wp_remote_post('https://slack.com/api/conversations.archive', array(
941 - 'headers' => array(
942 - 'Authorization' => 'Bearer ' . $bot_token,
943 - 'Content-Type' => 'application/json'
944 - ),
945 - 'body' => json_encode(array('channel' => $create_body['channel']['id'])),
946 - 'timeout' => 15
947 - ));
948 - $can_create_channels = true;
949 - } else {
950 - $create_error = $create_body['error'] ?? '';
951 - // name_taken means we have permission but channel exists
952 - $can_create_channels = ($create_error === 'name_taken' || (isset($create_body['ok']) && $create_body['ok']));
953 -
954 - // Check for missing scope errors
955 - if ($create_error === 'missing_scope') {
956 - $can_create_channels = false;
957 - }
958 - }
959 -
960 - // Build result message
961 - $results = array();
962 - $results[] = sprintf(__('Workspace: %s', 'mxchat'), esc_html($team_name));
963 - $results[] = sprintf(__('Bot: %s', 'mxchat'), esc_html($bot_name));
964 - $results[] = '';
965 - $results[] = ($can_read_channels ? '✓' : '✗') . ' ' . __('channels:read - List channels', 'mxchat');
966 - $results[] = ($can_create_channels ? '✓' : '✗') . ' ' . __('channels:manage - Create channels', 'mxchat');
967 -
968 - $missing_scopes = array();
969 - if (!$can_read_channels) $missing_scopes[] = 'channels:read';
970 - if (!$can_create_channels) $missing_scopes[] = 'channels:manage';
971 -
972 - if (!empty($missing_scopes)) {
973 - wp_send_json_error(array(
974 - 'message' => implode("\n", $results),
975 - 'missing_scopes' => $missing_scopes,
976 - 'partial' => true
977 - ));
978 - } else {
979 - wp_send_json_success(array(
980 - 'message' => implode("\n", $results)
981 - ));
982 - }
983 -}
984 -
985 529 public function mxchat_display_admin_notice() {
986 530 // Success notice
987 531 if ($message = get_transient('mxchat_admin_notice_success')) {
988 532 ?>
@@ -1029,13 +573,282 @@
1029 573 }
1030 574 }
1031 575 public function mxchat_create_admin_page() {
1032 576 $this->add_live_agent_nonce();
1033 - $this->add_theme_migration_nonce();
1034 577
1035 - // Include and render the new sidebar-based settings page
1036 - require_once plugin_dir_path(__FILE__) . 'admin-settings-page.php';
1037 - 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
1038 851 }
1039 852
1040 853 public function dismiss_live_agent_notice() {
1041 854 // Add debugging
@@ -1063,75 +876,16 @@
1063 876 ));
1064 877 }
1065 878 }
1066 879
1067 -/**
1068 - * Show theme migration notice for Pro users with AI-generated themes
1069 - * Only shown once - dismissible and stored in options
1070 - */
1071 -public function show_theme_migration_banner() {
1072 - // Only show if Pro is activated
1073 - if (!$this->is_activated) {
1074 - return;
1075 - }
1076 880
1077 - // Check if notice should be shown
1078 - $show_notice = get_option('mxchat_show_theme_migration_notice', false);
1079 -
1080 - if ($show_notice) {
1081 - ?>
1082 - <div class="mxchat-theme-migration-notice" id="mxchat-theme-migration-notice">
1083 - <div class="mxchat-pro-notification">
1084 - <button type="button" class="mxchat-dismiss-btn" onclick="dismissThemeMigrationNotice()" aria-label="Dismiss notification">
1085 - <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1086 - <line x1="18" y1="6" x2="6" y2="18"></line>
1087 - <line x1="6" y1="6" x2="18" y2="18"></line>
1088 - </svg>
1089 - </button>
1090 - <div class="mxchat-theme-migration-content">
1091 - <h3>🎨 AI Theme Migration Required</h3>
1092 - <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>
1093 - </div>
1094 - </div>
1095 - </div>
1096 - <?php
1097 - }
1098 -}
1099 -
1100 -/**
1101 - * Dismiss theme migration notice via AJAX
1102 - */
1103 -public function dismiss_theme_migration_notice() {
1104 - // Verify nonce
1105 - if (!wp_verify_nonce($_POST['nonce'], 'dismiss_theme_migration_notice')) {
1106 - wp_die('Security check failed');
1107 - }
1108 -
1109 - // Remove the notice flag
1110 - delete_option('mxchat_show_theme_migration_notice');
1111 -
1112 - wp_send_json_success();
1113 -}
1114 -
1115 -/**
1116 - * Add nonce for theme migration notice dismiss
1117 - */
1118 -public function add_theme_migration_nonce() {
1119 - if (get_option('mxchat_show_theme_migration_notice', false) && $this->is_activated) {
1120 - wp_localize_script('mxchat-admin-js', 'mxchatThemeMigration', array(
1121 - 'nonce' => wp_create_nonce('dismiss_theme_migration_notice'),
1122 - 'ajaxurl' => admin_url('admin-ajax.php')
1123 - ));
1124 - }
1125 -}
1126 -
1127 881 public function mxchat_create_transcripts_page() {
1128 882 global $wpdb;
1129 883 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1130 884
1131 885 // Get basic stats
1132 - $total_chats = $wpdb->get_var("SELECT COUNT(DISTINCT session_id) FROM $table_name") ?: 0;
1133 - $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");
1134 888
1135 889 // Count unique users with detailed breakdown
1136 890 $total_users = $wpdb->get_var("
1137 891 SELECT COUNT(DISTINCT
@@ -1269,89 +1023,298 @@
1269 1023 $chart_chats[] = 0;
1270 1024 $chart_messages[] = 0;
1271 1025 }
1272 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>
1273 1252
1274 - // Prepare page data for the template
1275 - $page_data = array(
1276 - 'total_chats' => $total_chats,
1277 - 'total_messages' => $total_messages,
1278 - 'total_users' => $total_users,
1279 - 'registered_users' => $registered_users,
1280 - 'guest_users' => $guest_users,
1281 - 'agent_tests' => $agent_tests,
1282 - 'today_chats' => $today_chats,
1283 - 'week_chats' => $week_chats,
1284 - 'month_chats' => $month_chats,
1285 - 'avg_messages' => $avg_messages,
1286 - 'busiest_hour' => $busiest_hour,
1287 - 'chart_labels' => $chart_labels,
1288 - 'chart_chats' => $chart_chats,
1289 - 'chart_messages' => $chart_messages,
1290 - );
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>
1291 1282
1292 - // Include and render the new template
1293 - require_once plugin_dir_path(__FILE__) . 'admin-transcripts-page.php';
1294 - 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
1295 1314 }
1296 1315
1297 -/**
1298 - * Get chart data for transcripts page
1299 - * Used by both page render and script localization
1300 - *
1301 - * @return array Chart data with labels, chats, and messages arrays
1302 - */
1303 -private function get_transcripts_chart_data() {
1304 - global $wpdb;
1305 - $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1306 1316
1307 - // Get daily chat data for last 7 days - same query as mxchat_transcripts_page()
1308 - $daily_stats = $wpdb->get_results("
1309 - SELECT
1310 - DATE(timestamp) as date,
1311 - COUNT(DISTINCT session_id) as chats,
1312 - COUNT(*) as messages
1313 - FROM $table_name
1314 - WHERE timestamp >= DATE_SUB(NOW(), INTERVAL 7 DAY)
1315 - GROUP BY DATE(timestamp)
1316 - ORDER BY date ASC
1317 - ");
1318 -
1319 - // Prepare chart data
1320 - $chart_labels = array();
1321 - $chart_chats = array();
1322 - $chart_messages = array();
1323 -
1324 - // Fill last 7 days with data - same logic as mxchat_transcripts_page()
1325 - for ($i = 6; $i >= 0; $i--) {
1326 - $date = date('Y-m-d', strtotime("-$i days"));
1327 - $day_name = date('D', strtotime("-$i days"));
1328 - $chart_labels[] = $day_name;
1329 -
1330 - $found = false;
1331 - if ($daily_stats) {
1332 - foreach ($daily_stats as $stat) {
1333 - if ($stat->date === $date) {
1334 - $chart_chats[] = (int)$stat->chats;
1335 - $chart_messages[] = (int)$stat->messages;
1336 - $found = true;
1337 - break;
1338 - }
1339 - }
1340 - }
1341 - if (!$found) {
1342 - $chart_chats[] = 0;
1343 - $chart_messages[] = 0;
1344 - }
1345 - }
1346 -
1347 - return array(
1348 - 'labels' => $chart_labels,
1349 - 'chats' => $chart_chats,
1350 - 'messages' => $chart_messages
1351 - );
1352 -}
1353 -
1354 1317 public function mxchat_transcripts_notification_section_callback() {
1355 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>';
1356 1319 }
1357 1320 public function mxchat_enable_notifications_callback() {
@@ -1407,39 +1370,8 @@
1407 1370 </p>
1408 1371 <?php
1409 1372 }
1410 1373
1411 -/**
1412 - * Custom retention-days input. When > 0 it overrides the bucket dropdown above
1413 - * and deletes transcripts older than the given number of days. Set to 0 to fall
1414 - * back to the dropdown (or "Never" if the dropdown is also Never).
1415 - *
1416 - * Devs can override the final day count via the `mxchat_transcript_retention_days`
1417 - * filter — runs in `cleanup_old_transcripts()` after this option is read.
1418 - *
1419 - * (plan-mxchat-20260509-9b80b1)
1420 - */
1421 -public function mxchat_retention_days_callback() {
1422 - $options = get_option('mxchat_transcripts_options', array());
1423 - $days = isset($options['mxchat_retention_days']) ? (int) $options['mxchat_retention_days'] : 0;
1424 - ?>
1425 - <input type="number"
1426 - id="mxchat_retention_days"
1427 - name="mxchat_transcripts_options[mxchat_retention_days]"
1428 - value="<?php echo esc_attr($days); ?>"
1429 - min="0"
1430 - max="3650"
1431 - step="1"
1432 - style="width: 90px;" />
1433 - <p class="description">
1434 - <?php esc_html_e('Number of days to retain transcripts. When set to a value greater than 0, this overrides the dropdown above. Set to 0 to use the dropdown. Maximum 3650 (10 years). A daily wp-cron task removes anything older, cascading to translations and click-tracking rows.', 'mxchat'); ?>
1435 - <br>
1436 - <code>apply_filters( 'mxchat_transcript_retention_days', $days )</code>
1437 - <?php esc_html_e('lets developers override the final day count programmatically.', 'mxchat'); ?>
1438 - </p>
1439 - <?php
1440 -}
1441 -
1442 1374 public function mxchat_auto_email_transcript_callback() {
1443 1375 $options = get_option('mxchat_transcripts_options', array());
1444 1376 $enabled = isset($options['mxchat_auto_email_transcript_enabled']) ? $options['mxchat_auto_email_transcript_enabled'] : 0;
1445 1377 $delay = isset($options['mxchat_auto_email_transcript_delay']) ? $options['mxchat_auto_email_transcript_delay'] : '30';
@@ -1515,29 +1447,15 @@
1515 1447 } else {
1516 1448 $sanitized['mxchat_auto_delete_transcripts'] = 'never';
1517 1449 }
1518 1450
1519 - // Sanitize custom retention-days override (plan-9b80b1).
1520 - if (isset($input['mxchat_retention_days'])) {
1521 - $days = (int) $input['mxchat_retention_days'];
1522 - $sanitized['mxchat_retention_days'] = max(0, min(3650, $days));
1523 - } else {
1524 - $sanitized['mxchat_retention_days'] = 0;
1525 - }
1526 -
1527 - // Get old values to check if auto-delete or retention-days changed.
1451 + // Get old value to check if auto-delete setting changed
1528 1452 $old_options = get_option('mxchat_transcripts_options');
1529 1453 $old_interval = isset($old_options['mxchat_auto_delete_transcripts']) ? $old_options['mxchat_auto_delete_transcripts'] : 'never';
1530 - $old_retention = isset($old_options['mxchat_retention_days']) ? (int) $old_options['mxchat_retention_days'] : 0;
1531 -
1532 - // If either setting changed, reschedule the cron job. The schedule_transcript_cleanup
1533 - // helper now treats "any active retention" (dropdown != never OR custom days > 0) as a
1534 - // reason to keep the daily cron registered.
1535 - $interval_changed = ($old_interval !== $sanitized['mxchat_auto_delete_transcripts']);
1536 - $retention_changed = ($old_retention !== $sanitized['mxchat_retention_days']);
1537 - if ($interval_changed || $retention_changed) {
1538 - $any_active = ($sanitized['mxchat_auto_delete_transcripts'] !== 'never') || ($sanitized['mxchat_retention_days'] > 0);
1539 - $this->schedule_transcript_cleanup($any_active ? 'active' : 'never');
1454 +
1455 + // If the auto-delete setting changed, reschedule the cron job
1456 + if ($old_interval !== $sanitized['mxchat_auto_delete_transcripts']) {
1457 + $this->schedule_transcript_cleanup($sanitized['mxchat_auto_delete_transcripts']);
1540 1458 }
1541 1459
1542 1460 // Sanitize auto-email transcript settings
1543 1461 $sanitized['mxchat_auto_email_transcript_enabled'] = isset($input['mxchat_auto_email_transcript_enabled']) ? 1 : 0;
@@ -1565,13 +1483,10 @@
1565 1483 $timestamp = wp_next_scheduled('mxchat_cleanup_old_transcripts');
1566 1484 if ($timestamp) {
1567 1485 wp_unschedule_event($timestamp, 'mxchat_cleanup_old_transcripts');
1568 1486 }
1569 -
1570 - // Schedule new event whenever retention is active. "active" is the canonical
1571 - // value passed by sanitize_transcripts_options when either the dropdown != never
1572 - // OR the custom retention-days > 0; "never" turns the cron off. Any other value
1573 - // (the legacy "1week" / "2weeks" / "1month" strings) is also treated as active.
1487 +
1488 + // Schedule new event if not set to "never"
1574 1489 if ($interval !== 'never') {
1575 1490 // Schedule to run daily at 3 AM
1576 1491 $next_run = strtotime('tomorrow 3:00 AM');
1577 1492 wp_schedule_event($next_run, 'daily', 'mxchat_cleanup_old_transcripts');
@@ -1581,94 +1496,50 @@
1581 1496 /**
1582 1497 * Delete old transcripts based on the configured interval
1583 1498 */
1584 1499 public function cleanup_old_transcripts() {
1585 - $options = get_option('mxchat_transcripts_options', array());
1586 - $interval = isset($options['mxchat_auto_delete_transcripts']) ? $options['mxchat_auto_delete_transcripts'] : 'never';
1587 - $custom_days = isset($options['mxchat_retention_days']) ? (int) $options['mxchat_retention_days'] : 0;
1588 -
1589 - // Custom retention-days (plan-9b80b1) takes precedence over the bucket dropdown.
1500 + $options = get_option('mxchat_transcripts_options', array());
1501 + $interval = isset($options['mxchat_auto_delete_transcripts']) ? $options['mxchat_auto_delete_transcripts'] : 'never';
1502 +
1503 + // If set to never, don't delete anything
1504 + if ($interval === 'never') {
1505 + return;
1506 + }
1507 +
1508 + // Calculate the cutoff date
1590 1509 $days = 0;
1591 - if ($custom_days > 0) {
1592 - $days = $custom_days;
1593 - } else {
1594 - switch ($interval) {
1595 - case '1week': $days = 7; break;
1596 - case '2weeks': $days = 14; break;
1597 - case '1month': $days = 30; break;
1598 - case 'never':
1599 - default:
1600 - $days = 0;
1601 - }
1510 + switch ($interval) {
1511 + case '1week':
1512 + $days = 7;
1513 + break;
1514 + case '2weeks':
1515 + $days = 14;
1516 + break;
1517 + case '1month':
1518 + $days = 30;
1519 + break;
1520 + default:
1521 + return; // Invalid interval, don't delete anything
1602 1522 }
1603 -
1604 - // Devs can override the final day count programmatically.
1605 - $days = (int) apply_filters('mxchat_transcript_retention_days', $days);
1606 -
1607 - if ($days <= 0) {
1608 - return; // Retention disabled — bail.
1609 - }
1610 -
1523 +
1611 1524 global $wpdb;
1612 - $transcripts_table = $wpdb->prefix . 'mxchat_chat_transcripts';
1613 - $translations_table = $wpdb->prefix . 'mxchat_transcript_translations';
1614 - $url_clicks_table = $wpdb->prefix . 'mxchat_url_clicks';
1615 -
1616 - // Defensive: if the main table doesn't exist (fresh-ish install), bail.
1617 - if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $transcripts_table)) !== $transcripts_table) {
1618 - return;
1619 - }
1620 -
1621 - $cutoff_date = gmdate('Y-m-d H:i:s', time() - ($days * DAY_IN_SECONDS));
1622 -
1623 - // Cap at 5000 session_ids per run so a large unattended site doesn't OOM —
1624 - // the cron will pick up where it left off on the next tick (within hours).
1625 - $batch_cap = (int) apply_filters('mxchat_transcript_retention_batch_cap', 5000);
1626 -
1627 - $sessions_to_delete = $wpdb->get_col(
1525 + $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1526 +
1527 + // Calculate the cutoff timestamp
1528 + $cutoff_date = date('Y-m-d H:i:s', strtotime("-{$days} days"));
1529 +
1530 + // Delete transcripts older than the cutoff date
1531 + $deleted = $wpdb->query(
1628 1532 $wpdb->prepare(
1629 - "SELECT DISTINCT session_id FROM {$transcripts_table} WHERE timestamp IS NOT NULL AND timestamp < %s LIMIT %d",
1630 - $cutoff_date,
1631 - $batch_cap
1533 + "DELETE FROM {$table_name} WHERE created_at < %s",
1534 + $cutoff_date
1632 1535 )
1633 1536 );
1634 -
1635 - if (empty($sessions_to_delete)) {
1636 - return;
1537 +
1538 + // Log the cleanup action
1539 + if ($deleted !== false && $deleted > 0) {
1540 + error_log(sprintf('MXChat: Auto-deleted %d transcripts older than %d days', $deleted, $days));
1637 1541 }
1638 -
1639 - $placeholders = implode(',', array_fill(0, count($sessions_to_delete), '%s'));
1640 -
1641 - // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
1642 - // $placeholders is a server-built list of literal "%s" tokens.
1643 - $deleted_transcripts = (int) $wpdb->query(
1644 - $wpdb->prepare(
1645 - "DELETE FROM {$transcripts_table} WHERE session_id IN ($placeholders)",
1646 - $sessions_to_delete
1647 - )
1648 - );
1649 -
1650 - if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $translations_table)) === $translations_table) {
1651 - $wpdb->query(
1652 - $wpdb->prepare(
1653 - "DELETE FROM {$translations_table} WHERE session_id IN ($placeholders)",
1654 - $sessions_to_delete
1655 - )
1656 - );
1657 - }
1658 -
1659 - if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $url_clicks_table)) === $url_clicks_table) {
1660 - $wpdb->query(
1661 - $wpdb->prepare(
1662 - "DELETE FROM {$url_clicks_table} WHERE session_id IN ($placeholders)",
1663 - $sessions_to_delete
1664 - )
1665 - );
1666 - }
1667 - // phpcs:enable
1668 -
1669 - update_option('mxchat_retention_last_swept_at', time(), false);
1670 - update_option('mxchat_retention_rows_last_deleted', $deleted_transcripts, false);
1671 1542 }
1672 1543
1673 1544 public function export_chat_transcripts() {
1674 1545 if (!current_user_can('manage_options')) {
@@ -1729,1236 +1600,9 @@
1729 1600 fclose($output);
1730 1601 wp_die();
1731 1602 }
1732 1603
1733 -// ============================================================================
1734 -// Leads tab (inside Transcripts)
1735 -//
1736 -// Leads are derived from existing data — no dedicated table. Primary source:
1737 -// wp_mxchat_chat_transcripts rows where user_email is populated. Secondary
1738 -// source: wp_options entries `mxchat_email_{session_id}` / `mxchat_name_{sid}`
1739 -// for "orphan" leads who submitted the pre-chat form but never chatted.
1740 -// ============================================================================
1741 1604
1742 -/**
1743 - * Fetch leads: dedup-by-email rows, stats strip, and top pages in one call.
1744 - */
1745 -public function mxchat_fetch_leads() {
1746 - if (!current_user_can('manage_options')) {
1747 - wp_send_json_error(['message' => 'Insufficient permissions']);
1748 - wp_die();
1749 - }
1750 -
1751 - global $wpdb;
1752 - $table = $wpdb->prefix . 'mxchat_chat_transcripts';
1753 -
1754 - $page = isset($_POST['page']) ? max(1, absint($_POST['page'])) : 1;
1755 - $per_page = isset($_POST['per_page']) ? min(100, max(10, absint($_POST['per_page']))) : 25;
1756 - $offset = ($page - 1) * $per_page;
1757 - $search = isset($_POST['search']) ? sanitize_text_field(wp_unslash($_POST['search'])) : '';
1758 - $date_range = isset($_POST['date_range']) ? sanitize_key($_POST['date_range']) : 'all';
1759 - $status = isset($_POST['status']) ? sanitize_key($_POST['status']) : 'all';
1760 - $page_filter = isset($_POST['page_url']) ? esc_url_raw(wp_unslash($_POST['page_url'])) : '';
1761 - $sort = isset($_POST['sort']) ? sanitize_key($_POST['sort']) : 'last_seen';
1762 - $sort_dir = (isset($_POST['sort_dir']) && $_POST['sort_dir'] === 'asc') ? 'ASC' : 'DESC';
1763 -
1764 - $date_cutoff = self::mxchat_leads_date_cutoff($date_range);
1765 - $has_page_url_column = !empty($wpdb->get_results("SHOW COLUMNS FROM $table LIKE 'originating_page_url'"));
1766 -
1767 - // Base WHERE for transcripts leads.
1768 - $where_clauses = ["user_email IS NOT NULL", "user_email != ''"];
1769 - $where_params = [];
1770 -
1771 - if ($date_cutoff) {
1772 - $where_clauses[] = 'timestamp >= %s';
1773 - $where_params[] = $date_cutoff;
1774 - }
1775 - if ($page_filter && $has_page_url_column) {
1776 - $where_clauses[] = 'originating_page_url = %s';
1777 - $where_params[] = $page_filter;
1778 - }
1779 - if ($search !== '') {
1780 - $like = '%' . $wpdb->esc_like($search) . '%';
1781 - $where_clauses[] = '(user_email LIKE %s OR user_name LIKE %s)';
1782 - $where_params[] = $like;
1783 - $where_params[] = $like;
1784 - }
1785 - $where_sql = 'WHERE ' . implode(' AND ', $where_clauses);
1786 -
1787 - // Aggregate query grouped by email.
1788 - $select_sql = $has_page_url_column
1789 - ? "SELECT user_email, MAX(timestamp) AS last_seen, MIN(timestamp) AS first_seen,
1790 - COUNT(DISTINCT session_id) AS conversation_count"
1791 - : "SELECT user_email, MAX(timestamp) AS last_seen, MIN(timestamp) AS first_seen,
1792 - COUNT(DISTINCT session_id) AS conversation_count";
1793 -
1794 - $order_column = in_array($sort, ['last_seen', 'conversation_count', 'first_seen'], true) ? $sort : 'last_seen';
1795 - $group_order_limit = " GROUP BY user_email ORDER BY {$order_column} {$sort_dir} LIMIT %d OFFSET %d";
1796 -
1797 - $transcripts_sql = $wpdb->prepare(
1798 - "{$select_sql} FROM {$table} {$where_sql}{$group_order_limit}",
1799 - array_merge($where_params, [$per_page, $offset])
1800 - );
1801 - $transcript_rows = $wpdb->get_results($transcripts_sql);
1802 -
1803 - // Count of unique transcript-based leads under the same filters.
1804 - $count_sql = $wpdb->prepare(
1805 - "SELECT COUNT(DISTINCT user_email) FROM {$table} {$where_sql}",
1806 - $where_params
1807 - );
1808 - $transcripts_lead_count = (int) $wpdb->get_var($count_sql);
1809 -
1810 - // Hydrate each row: name, latest_session_id, top page.
1811 - $leads = [];
1812 - foreach ($transcript_rows as $row) {
1813 - $detail = $has_page_url_column
1814 - ? $wpdb->get_row($wpdb->prepare(
1815 - "SELECT session_id, user_name, originating_page_url, originating_page_title
1816 - FROM {$table} WHERE user_email = %s ORDER BY timestamp DESC LIMIT 1",
1817 - $row->user_email
1818 - ))
1819 - : $wpdb->get_row($wpdb->prepare(
1820 - "SELECT session_id, user_name FROM {$table}
1821 - WHERE user_email = %s ORDER BY timestamp DESC LIMIT 1",
1822 - $row->user_email
1823 - ));
1824 -
1825 - $leads[] = [
1826 - 'email' => $row->user_email,
1827 - 'name' => isset($detail->user_name) ? (string) $detail->user_name : '',
1828 - 'conversation_count' => (int) $row->conversation_count,
1829 - 'last_seen' => $row->last_seen,
1830 - 'last_seen_display' => self::mxchat_leads_format_relative($row->last_seen),
1831 - 'first_seen' => $row->first_seen,
1832 - 'latest_session_id' => isset($detail->session_id) ? $detail->session_id : '',
1833 - 'top_page_url' => isset($detail->originating_page_url) ? $detail->originating_page_url : '',
1834 - 'top_page_title' => isset($detail->originating_page_title) ? $detail->originating_page_title : '',
1835 - 'is_orphan' => false,
1836 - 'status' => 'active',
1837 - ];
1838 - }
1839 -
1840 - // Non-transcript lead sources. Built once here, then filtered/merged based on the
1841 - // status filter below. Deduplication priority when the same email appears in multiple
1842 - // sources: transcripts > chat_deleted > orphan.
1843 - $transcripts_emails_seen = array_flip(array_map(
1844 - function ($r) { return strtolower($r['email']); },
1845 - $leads
1846 - ));
1847 -
1848 - // Chat-deleted leads: had a conversation that an admin removed. Preserved via
1849 - // mxchat_lead_del_* options, with timestamps so they respect date filters.
1850 - $chat_deleted_leads_all = [];
1851 - if ($status === 'all' || $status === 'chat_deleted') {
1852 - $chat_deleted_leads_all = self::mxchat_collect_chat_deleted_leads($search, $date_cutoff);
1853 - // Dedup: drop any chat_deleted row whose email is already in the transcripts set.
1854 - $chat_deleted_leads_all = array_values(array_filter(
1855 - $chat_deleted_leads_all,
1856 - function ($row) use ($transcripts_emails_seen) {
1857 - return !isset($transcripts_emails_seen[strtolower($row['email'])]);
1858 - }
1859 - ));
1860 - foreach ($chat_deleted_leads_all as $row) {
1861 - $transcripts_emails_seen[strtolower($row['email'])] = true;
1862 - }
1863 - }
1864 -
1865 - // Orphans: pre-chat form captures with no conversation. No timestamp, so skipped
1866 - // when a date filter is active.
1867 - $orphan_leads_all = [];
1868 - if (($status === 'all' || $status === 'orphan') && !$date_cutoff && !$page_filter) {
1869 - $orphan_leads_all = self::mxchat_collect_orphan_leads($search);
1870 - $orphan_leads_all = array_values(array_filter(
1871 - $orphan_leads_all,
1872 - function ($row) use ($transcripts_emails_seen) {
1873 - return !isset($transcripts_emails_seen[strtolower($row['email'])]);
1874 - }
1875 - ));
1876 - }
1877 -
1878 - // Apply status filter to the transcripts-derived list.
1879 - if ($status === 'orphan' || $status === 'chat_deleted') {
1880 - $leads = [];
1881 - $transcripts_lead_count = 0;
1882 - }
1883 -
1884 - // Stitch the current page from the three buckets in priority order.
1885 - $total_count = $transcripts_lead_count + count($chat_deleted_leads_all) + count($orphan_leads_all);
1886 - $remaining_slots = $per_page - count($leads);
1887 -
1888 - if ($remaining_slots > 0 && !empty($chat_deleted_leads_all)) {
1889 - $start = max(0, ($page - 1) * $per_page - $transcripts_lead_count);
1890 - if ($start < count($chat_deleted_leads_all)) {
1891 - $leads = array_merge($leads, array_slice($chat_deleted_leads_all, $start, $remaining_slots));
1892 - $remaining_slots = $per_page - count($leads);
1893 - }
1894 - }
1895 -
1896 - if ($remaining_slots > 0 && !empty($orphan_leads_all)) {
1897 - $before = $transcripts_lead_count + count($chat_deleted_leads_all);
1898 - $start = max(0, ($page - 1) * $per_page - $before);
1899 - if ($start < count($orphan_leads_all)) {
1900 - $leads = array_merge($leads, array_slice($orphan_leads_all, $start, $remaining_slots));
1901 - }
1902 - }
1903 -
1904 - $total_pages = $per_page > 0 ? (int) ceil($total_count / $per_page) : 1;
1905 -
1906 - // Stats strip: always computed over full dataset, unaffected by filters.
1907 - $stats = self::mxchat_leads_stats($table, $has_page_url_column);
1908 -
1909 - // Top pages: top 5 by distinct emails captured.
1910 - $top_pages = [];
1911 - if ($has_page_url_column) {
1912 - $top_pages_rows = $wpdb->get_results(
1913 - "SELECT originating_page_url AS url,
1914 - MAX(originating_page_title) AS title,
1915 - COUNT(DISTINCT user_email) AS lead_count
1916 - FROM {$table}
1917 - WHERE user_email IS NOT NULL AND user_email != ''
1918 - AND originating_page_url IS NOT NULL AND originating_page_url != ''
1919 - GROUP BY originating_page_url
1920 - ORDER BY lead_count DESC, url ASC
1921 - LIMIT 5"
1922 - );
1923 - foreach ($top_pages_rows as $p) {
1924 - $top_pages[] = [
1925 - 'url' => $p->url,
1926 - 'title' => $p->title ?: $p->url,
1927 - 'lead_count' => (int) $p->lead_count,
1928 - ];
1929 - }
1930 - }
1931 -
1932 - wp_send_json([
1933 - 'success' => true,
1934 - 'leads' => $leads,
1935 - 'page' => $page,
1936 - 'per_page' => $per_page,
1937 - 'total_count' => $total_count,
1938 - 'total_pages' => $total_pages,
1939 - 'showing_start' => $total_count === 0 ? 0 : ($offset + 1),
1940 - 'showing_end' => min($offset + $per_page, $total_count),
1941 - 'stats' => $stats,
1942 - 'top_pages' => $top_pages,
1943 - ]);
1944 - wp_die();
1945 -}
1946 -
1947 -/**
1948 - * Delete one or more leads by email. Removes every transcripts row for that
1949 - * email and cleans up related wp_options (mxchat_email_{sid}, mxchat_name_{sid},
1950 - * mxchat_history_{sid}) and any orphan option entries matching the email.
1951 - */
1952 -public function mxchat_delete_leads() {
1953 - if (!current_user_can('manage_options')) {
1954 - wp_send_json_error(['message' => 'Insufficient permissions']);
1955 - wp_die();
1956 - }
1957 - check_ajax_referer('mxchat_delete_leads', 'security');
1958 -
1959 - $emails_raw = isset($_POST['emails']) ? (array) wp_unslash($_POST['emails']) : [];
1960 - $emails = [];
1961 - foreach ($emails_raw as $e) {
1962 - $clean = sanitize_email((string) $e);
1963 - if ($clean) {
1964 - $emails[] = $clean;
1965 - }
1966 - }
1967 - if (empty($emails)) {
1968 - wp_send_json_error(['message' => 'No emails provided']);
1969 - wp_die();
1970 - }
1971 -
1972 - $summary = self::mxchat_wipe_leads_by_email($emails);
1973 -
1974 - wp_send_json([
1975 - 'success' => true,
1976 - 'deleted_leads' => count($emails),
1977 - 'deleted_sessions' => $summary['deleted_sessions'],
1978 - 'deleted_rows' => $summary['deleted_rows'],
1979 - ]);
1980 - wp_die();
1981 -}
1982 -
1983 -/**
1984 - * Fully wipe one or more leads by email: every transcripts row, every related wp_options
1985 - * entry (history, pre-chat capture, chat_deleted preservation, agent name, translations).
1986 - *
1987 - * Shared between the Leads-tab Delete button and the transcript-delete opt-in checkbox.
1988 - * Input emails must already be sanitized with sanitize_email().
1989 - */
1990 -private static function mxchat_wipe_leads_by_email(array $emails) {
1991 - global $wpdb;
1992 - $table = $wpdb->prefix . 'mxchat_chat_transcripts';
1993 - $translations_table = $wpdb->prefix . 'mxchat_transcript_translations';
1994 - $has_translations = $wpdb->get_var("SHOW TABLES LIKE '$translations_table'") === $translations_table;
1995 -
1996 - $deleted_sessions = 0;
1997 - $deleted_rows = 0;
1998 - $emails_lc = array_map('strtolower', $emails);
1999 -
2000 - foreach ($emails as $email) {
2001 - $session_ids = $wpdb->get_col($wpdb->prepare(
2002 - "SELECT DISTINCT session_id FROM {$table} WHERE user_email = %s",
2003 - $email
2004 - ));
2005 -
2006 - $rows_removed = $wpdb->delete($table, ['user_email' => $email], ['%s']);
2007 - if ($rows_removed !== false) {
2008 - $deleted_rows += (int) $rows_removed;
2009 - }
2010 -
2011 - foreach ($session_ids as $sid) {
2012 - $deleted_sessions++;
2013 - wp_cache_delete('chat_session_' . $sid, 'mxchat_chat_sessions');
2014 - delete_option('mxchat_history_' . $sid);
2015 - delete_option('mxchat_email_' . $sid);
2016 - delete_option('mxchat_name_' . $sid);
2017 - delete_option('mxchat_agent_name_' . $sid);
2018 - delete_option('mxchat_lead_del_email_' . $sid);
2019 - delete_option('mxchat_lead_del_name_' . $sid);
2020 - delete_option('mxchat_lead_del_ts_' . $sid);
2021 - if ($has_translations) {
2022 - $wpdb->delete($translations_table, ['session_id' => $sid], ['%s']);
2023 - }
2024 - }
2025 - }
2026 -
2027 - // Clean up any lingering option entries (orphan pre-chat captures + chat_deleted
2028 - // preservations) whose stored value matches one of the emails being wiped.
2029 - $lingering = $wpdb->get_results(
2030 - "SELECT option_name, option_value FROM {$wpdb->options}
2031 - WHERE option_name LIKE 'mxchat_email_%' OR option_name LIKE 'mxchat_lead_del_email_%'"
2032 - );
2033 - foreach ($lingering as $opt) {
2034 - if (!in_array(strtolower(trim($opt->option_value)), $emails_lc, true)) {
2035 - continue;
2036 - }
2037 - if (strpos($opt->option_name, 'mxchat_lead_del_email_') === 0) {
2038 - $sid = substr($opt->option_name, strlen('mxchat_lead_del_email_'));
2039 - delete_option('mxchat_lead_del_email_' . $sid);
2040 - delete_option('mxchat_lead_del_name_' . $sid);
2041 - delete_option('mxchat_lead_del_ts_' . $sid);
2042 - } else {
2043 - $sid = substr($opt->option_name, strlen('mxchat_email_'));
2044 - delete_option('mxchat_email_' . $sid);
2045 - delete_option('mxchat_name_' . $sid);
2046 - }
2047 - }
2048 -
2049 - wp_cache_delete('all_chat_sessions', 'mxchat_chat_sessions');
2050 -
2051 - return [
2052 - 'deleted_sessions' => $deleted_sessions,
2053 - 'deleted_rows' => $deleted_rows,
2054 - ];
2055 -}
2056 -
2057 -/**
2058 - * Stream a leads CSV. scope=all exports every lead under current filters is not
2059 - * supported to keep semantics simple; caller either exports all leads or a
2060 - * specific set of selected emails.
2061 - */
2062 -public function mxchat_export_leads() {
2063 - if (!current_user_can('manage_options')) {
2064 - wp_die(esc_html__('You do not have sufficient permissions to access this page.', 'mxchat'));
2065 - }
2066 - check_ajax_referer('mxchat_export_leads', 'security');
2067 -
2068 - $scope = isset($_POST['scope']) ? sanitize_key($_POST['scope']) : 'all';
2069 - $fields_mode = isset($_POST['fields']) ? sanitize_key($_POST['fields']) : 'email_and_name';
2070 - $emails_in = isset($_POST['emails']) ? (array) wp_unslash($_POST['emails']) : [];
2071 -
2072 - $emails_in_clean = [];
2073 - foreach ($emails_in as $e) {
2074 - $clean = sanitize_email((string) $e);
2075 - if ($clean) {
2076 - $emails_in_clean[] = $clean;
2077 - }
2078 - }
2079 -
2080 - global $wpdb;
2081 - $table = $wpdb->prefix . 'mxchat_chat_transcripts';
2082 - $has_page_url_column = !empty($wpdb->get_results("SHOW COLUMNS FROM $table LIKE 'originating_page_url'"));
2083 -
2084 - // Collect leads from transcripts.
2085 - $transcripts_sql = "SELECT user_email AS email,
2086 - MAX(timestamp) AS last_seen,
2087 - COUNT(DISTINCT session_id) AS conversation_count
2088 - FROM {$table}
2089 - WHERE user_email IS NOT NULL AND user_email != ''";
2090 - $params = [];
2091 - if ($scope === 'selected' && !empty($emails_in_clean)) {
2092 - $placeholders = implode(',', array_fill(0, count($emails_in_clean), '%s'));
2093 - $transcripts_sql .= " AND user_email IN ({$placeholders})";
2094 - $params = $emails_in_clean;
2095 - }
2096 - $transcripts_sql .= " GROUP BY user_email ORDER BY last_seen DESC";
2097 -
2098 - $rows = !empty($params)
2099 - ? $wpdb->get_results($wpdb->prepare($transcripts_sql, $params))
2100 - : $wpdb->get_results($transcripts_sql);
2101 -
2102 - // Hydrate each row with name + top page.
2103 - $export_rows = [];
2104 - foreach ($rows as $row) {
2105 - $detail = $has_page_url_column
2106 - ? $wpdb->get_row($wpdb->prepare(
2107 - "SELECT user_name, originating_page_url FROM {$table}
2108 - WHERE user_email = %s ORDER BY timestamp DESC LIMIT 1",
2109 - $row->email
2110 - ))
2111 - : $wpdb->get_row($wpdb->prepare(
2112 - "SELECT user_name FROM {$table}
2113 - WHERE user_email = %s ORDER BY timestamp DESC LIMIT 1",
2114 - $row->email
2115 - ));
2116 - $export_rows[] = [
2117 - 'email' => $row->email,
2118 - 'name' => isset($detail->user_name) ? (string) $detail->user_name : '',
2119 - 'conversation_count' => (int) $row->conversation_count,
2120 - 'last_seen' => $row->last_seen,
2121 - 'top_page_url' => isset($detail->originating_page_url) ? $detail->originating_page_url : '',
2122 - ];
2123 - }
2124 -
2125 - // Include orphan + chat_deleted leads when exporting all.
2126 - if ($scope === 'all') {
2127 - $transcripts_emails_lc = array_flip(array_map(
2128 - function ($r) { return strtolower($r['email']); },
2129 - $export_rows
2130 - ));
2131 - foreach (self::mxchat_collect_chat_deleted_leads('') as $cd) {
2132 - if (isset($transcripts_emails_lc[strtolower($cd['email'])])) continue;
2133 - $transcripts_emails_lc[strtolower($cd['email'])] = true;
2134 - $export_rows[] = [
2135 - 'email' => $cd['email'],
2136 - 'name' => $cd['name'],
2137 - 'conversation_count' => 0,
2138 - 'last_seen' => $cd['last_seen'],
2139 - 'top_page_url' => '',
2140 - ];
2141 - }
2142 - foreach (self::mxchat_collect_orphan_leads('') as $orphan) {
2143 - if (isset($transcripts_emails_lc[strtolower($orphan['email'])])) continue;
2144 - $transcripts_emails_lc[strtolower($orphan['email'])] = true;
2145 - $export_rows[] = [
2146 - 'email' => $orphan['email'],
2147 - 'name' => $orphan['name'],
2148 - 'conversation_count' => 0,
2149 - 'last_seen' => '',
2150 - 'top_page_url' => '',
2151 - ];
2152 - }
2153 - }
2154 -
2155 - if (empty($export_rows)) {
2156 - wp_send_json_error(['message' => 'No leads to export.']);
2157 - wp_die();
2158 - }
2159 -
2160 - $filename = 'mxchat-leads-' . date('Y-m-d') . '.csv';
2161 - header('Content-Type: text/csv');
2162 - header('Content-Disposition: attachment; filename="' . $filename . '"');
2163 - header('Pragma: no-cache');
2164 - header('Expires: 0');
2165 -
2166 - $output = fopen('php://output', 'w');
2167 - fputs($output, "\xEF\xBB\xBF"); // UTF-8 BOM for Excel
2168 -
2169 - if ($fields_mode === 'email_only') {
2170 - fputcsv($output, ['Email']);
2171 - foreach ($export_rows as $r) {
2172 - fputcsv($output, [$r['email']]);
2173 - }
2174 - } else {
2175 - fputcsv($output, ['Email', 'Name', 'Conversations', 'Last seen', 'Top page']);
2176 - foreach ($export_rows as $r) {
2177 - fputcsv($output, [
2178 - $r['email'],
2179 - $r['name'],
2180 - $r['conversation_count'],
2181 - $r['last_seen'],
2182 - $r['top_page_url'],
2183 - ]);
2184 - }
2185 - }
2186 -
2187 - fclose($output);
2188 - wp_die();
2189 -}
2190 -
2191 -/**
2192 - * Stats strip payload (independent of filters).
2193 - */
2194 -private static function mxchat_leads_stats($table, $has_page_url_column) {
2195 - global $wpdb;
2196 -
2197 - $total_transcripts_emails = (int) $wpdb->get_var(
2198 - "SELECT COUNT(DISTINCT user_email) FROM {$table}
2199 - WHERE user_email IS NOT NULL AND user_email != ''"
2200 - );
2201 -
2202 - $new_this_week = (int) $wpdb->get_var($wpdb->prepare(
2203 - "SELECT COUNT(*) FROM (
2204 - SELECT user_email FROM {$table}
2205 - WHERE user_email IS NOT NULL AND user_email != ''
2206 - GROUP BY user_email
2207 - HAVING MIN(timestamp) >= %s
2208 - ) AS new_leads",
2209 - gmdate('Y-m-d H:i:s', strtotime('-7 days'))
2210 - ));
2211 -
2212 - $total_convos = (int) $wpdb->get_var(
2213 - "SELECT COUNT(DISTINCT session_id) FROM {$table}
2214 - WHERE user_email IS NOT NULL AND user_email != ''"
2215 - );
2216 -
2217 - $orphan_count = count(self::mxchat_collect_orphan_leads(''));
2218 - $chat_deleted_count = self::mxchat_count_chat_deleted_leads();
2219 -
2220 - // Total leads = unique emails across all three sources (dedup priority: transcripts > chat_deleted > orphan
2221 - // is already enforced at collection time in mxchat_fetch_leads; stats re-apply it here).
2222 - $total_leads = $total_transcripts_emails + $chat_deleted_count + $orphan_count;
2223 -
2224 - $avg = $total_transcripts_emails > 0
2225 - ? round($total_convos / $total_transcripts_emails, 1)
2226 - : 0;
2227 -
2228 - // Orphan % reflects *true* orphans only (pre-chat dropoffs). Chat-deleted leads are
2229 - // excluded so the metric stays meaningful — admins shouldn't see their cleanups
2230 - // inflate this number.
2231 - $orphan_pct = $total_leads > 0
2232 - ? (int) round(($orphan_count / $total_leads) * 100)
2233 - : 0;
2234 -
2235 - return [
2236 - 'total_leads' => $total_leads,
2237 - 'new_this_week' => $new_this_week,
2238 - 'avg_convos' => $avg,
2239 - 'orphan_pct' => $orphan_pct,
2240 - 'orphan_count' => $orphan_count,
2241 - 'chat_deleted_count' => $chat_deleted_count,
2242 - ];
2243 -}
2244 -
2245 -/**
2246 - * Collect leads who had a conversation that an admin later deleted (preserved via
2247 - * mxchat_lead_del_* options). Returns rows tagged status='chat_deleted' with the
2248 - * original last-seen timestamp so they still sort and filter sensibly.
2249 - *
2250 - * @param string $search Optional email/name substring filter.
2251 - * @param string $date_cutoff Optional 'Y-m-d H:i:s' cutoff — only rows with last_ts >= cutoff.
2252 - * @return array
2253 - */
2254 -private static function mxchat_collect_chat_deleted_leads($search = '', $date_cutoff = '') {
2255 - global $wpdb;
2256 - $table = $wpdb->prefix . 'mxchat_chat_transcripts';
2257 -
2258 - $rows = $wpdb->get_results(
2259 - "SELECT option_name, option_value FROM {$wpdb->options}
2260 - WHERE option_name LIKE 'mxchat_lead_del_email_%'"
2261 - );
2262 - if (empty($rows)) {
2263 - return [];
2264 - }
2265 -
2266 - // Emails that currently have transcripts rows should not appear as chat_deleted —
2267 - // they've come back and chatted, so they're active leads again.
2268 - $emails_in_transcripts = array_map(
2269 - 'strtolower',
2270 - (array) $wpdb->get_col(
2271 - "SELECT DISTINCT user_email FROM {$table}
2272 - WHERE user_email IS NOT NULL AND user_email != ''"
2273 - )
2274 - );
2275 - $emails_in_transcripts = array_flip($emails_in_transcripts);
2276 -
2277 - $needle = strtolower(trim((string) $search));
2278 - $by_email = [];
2279 -
2280 - foreach ($rows as $opt) {
2281 - $email = sanitize_email(trim((string) $opt->option_value));
2282 - if (!$email) {
2283 - continue;
2284 - }
2285 - if (isset($emails_in_transcripts[strtolower($email)])) {
2286 - continue;
2287 - }
2288 - $sid = substr($opt->option_name, strlen('mxchat_lead_del_email_'));
2289 - if (!$sid) {
2290 - continue;
2291 - }
2292 - $name = (string) get_option('mxchat_lead_del_name_' . $sid, '');
2293 - $ts = (string) get_option('mxchat_lead_del_ts_' . $sid, '');
2294 -
2295 - if ($date_cutoff !== '' && ($ts === '' || $ts < $date_cutoff)) {
2296 - continue;
2297 - }
2298 - if ($needle !== '') {
2299 - $hay = strtolower($email . ' ' . $name);
2300 - if (strpos($hay, $needle) === false) {
2301 - continue;
2302 - }
2303 - }
2304 -
2305 - $key = strtolower($email);
2306 - if (!isset($by_email[$key]) || (isset($by_email[$key]['last_seen']) && $ts > $by_email[$key]['last_seen'])) {
2307 - $by_email[$key] = [
2308 - 'email' => $email,
2309 - 'name' => $name,
2310 - 'conversation_count' => 0,
2311 - 'last_seen' => $ts,
2312 - 'last_seen_display' => $ts ? self::mxchat_leads_format_relative($ts) : __('Chat deleted', 'mxchat'),
2313 - 'first_seen' => $ts,
2314 - 'latest_session_id' => '',
2315 - 'top_page_url' => '',
2316 - 'top_page_title' => '',
2317 - 'is_orphan' => false,
2318 - 'status' => 'chat_deleted',
2319 - ];
2320 - }
2321 - }
2322 -
2323 - // Newest chat_deleted first.
2324 - usort($by_email, function ($a, $b) {
2325 - return strcmp((string) $b['last_seen'], (string) $a['last_seen']);
2326 - });
2327 - return array_values($by_email);
2328 -}
2329 -
2330 -/**
2331 - * Count unique emails preserved as "chat deleted" (for the stats strip).
2332 - */
2333 -private static function mxchat_count_chat_deleted_leads() {
2334 - global $wpdb;
2335 - $table = $wpdb->prefix . 'mxchat_chat_transcripts';
2336 -
2337 - $emails = $wpdb->get_col(
2338 - "SELECT DISTINCT option_value FROM {$wpdb->options}
2339 - WHERE option_name LIKE 'mxchat_lead_del_email_%'"
2340 - );
2341 - if (empty($emails)) {
2342 - return 0;
2343 - }
2344 -
2345 - $transcripts_emails = array_map(
2346 - 'strtolower',
2347 - (array) $wpdb->get_col(
2348 - "SELECT DISTINCT user_email FROM {$table}
2349 - WHERE user_email IS NOT NULL AND user_email != ''"
2350 - )
2351 - );
2352 - $transcripts_emails = array_flip($transcripts_emails);
2353 -
2354 - $count = 0;
2355 - $seen = [];
2356 - foreach ($emails as $raw) {
2357 - $email = strtolower(trim((string) $raw));
2358 - if (!$email || isset($seen[$email]) || isset($transcripts_emails[$email])) {
2359 - continue;
2360 - }
2361 - $seen[$email] = true;
2362 - $count++;
2363 - }
2364 - return $count;
2365 -}
2366 -
2367 -/**
2368 - * Find leads who submitted the pre-chat form but never produced a transcripts row.
2369 - * Returned rows have no conversation_count, no timestamp.
2370 - *
2371 - * @param string $search Optional email/name substring filter.
2372 - * @return array
2373 - */
2374 -private static function mxchat_collect_orphan_leads($search = '') {
2375 - global $wpdb;
2376 - $table = $wpdb->prefix . 'mxchat_chat_transcripts';
2377 -
2378 - $option_rows = $wpdb->get_results(
2379 - "SELECT option_name, option_value FROM {$wpdb->options}
2380 - WHERE option_name LIKE 'mxchat_email_%'"
2381 - );
2382 - if (empty($option_rows)) {
2383 - return [];
2384 - }
2385 -
2386 - // Collect all session_ids that have real transcripts rows so we can exclude them.
2387 - $session_ids_with_rows = $wpdb->get_col(
2388 - "SELECT DISTINCT session_id FROM {$table}
2389 - WHERE user_email IS NOT NULL AND user_email != ''"
2390 - );
2391 - $session_ids_with_rows = array_flip($session_ids_with_rows);
2392 -
2393 - // Seen emails in transcripts (so orphans only include truly never-chatted leads).
2394 - $emails_in_transcripts = array_map(
2395 - 'strtolower',
2396 - (array) $wpdb->get_col(
2397 - "SELECT DISTINCT user_email FROM {$table}
2398 - WHERE user_email IS NOT NULL AND user_email != ''"
2399 - )
2400 - );
2401 - $emails_in_transcripts = array_flip($emails_in_transcripts);
2402 -
2403 - $orphans_by_email = [];
2404 - $needle = strtolower(trim((string) $search));
2405 -
2406 - foreach ($option_rows as $opt) {
2407 - $email = sanitize_email(trim((string) $opt->option_value));
2408 - if (!$email) {
2409 - continue;
2410 - }
2411 - $sid = substr($opt->option_name, strlen('mxchat_email_'));
2412 - if (!$sid) {
2413 - continue;
2414 - }
2415 - // Exclude leads who have any transcripts rows (they appear in the main list).
2416 - if (isset($emails_in_transcripts[strtolower($email)])) {
2417 - continue;
2418 - }
2419 - if (isset($session_ids_with_rows[$sid])) {
2420 - continue;
2421 - }
2422 -
2423 - $name_option = get_option('mxchat_name_' . $sid, '');
2424 - $name = is_string($name_option) ? trim($name_option) : '';
2425 -
2426 - if ($needle !== '') {
2427 - $hay = strtolower($email . ' ' . $name);
2428 - if (strpos($hay, $needle) === false) {
2429 - continue;
2430 - }
2431 - }
2432 -
2433 - $key = strtolower($email);
2434 - if (!isset($orphans_by_email[$key])) {
2435 - $orphans_by_email[$key] = [
2436 - 'email' => $email,
2437 - 'name' => $name,
2438 - 'conversation_count' => 0,
2439 - 'last_seen' => '',
2440 - 'last_seen_display' => __('No conversation yet', 'mxchat'),
2441 - 'first_seen' => '',
2442 - 'latest_session_id' => '',
2443 - 'top_page_url' => '',
2444 - 'top_page_title' => '',
2445 - 'is_orphan' => true,
2446 - 'status' => 'orphan',
2447 - ];
2448 - }
2449 - }
2450 -
2451 - return array_values($orphans_by_email);
2452 -}
2453 -
2454 -/**
2455 - * Map a date_range key to a SQL-comparable cutoff string, or '' for all-time.
2456 - */
2457 -private static function mxchat_leads_date_cutoff($date_range) {
2458 - switch ($date_range) {
2459 - case 'today': return gmdate('Y-m-d H:i:s', strtotime('-24 hours'));
2460 - case '7d': return gmdate('Y-m-d H:i:s', strtotime('-7 days'));
2461 - case '30d': return gmdate('Y-m-d H:i:s', strtotime('-30 days'));
2462 - case '90d': return gmdate('Y-m-d H:i:s', strtotime('-90 days'));
2463 - case 'all':
2464 - default: return '';
2465 - }
2466 -}
2467 -
2468 -/**
2469 - * Turn a UTC timestamp into a short relative display like "2h ago" or "Apr 12".
2470 - */
2471 -private static function mxchat_leads_format_relative($timestamp) {
2472 - if (!$timestamp) {
2473 - return '';
2474 - }
2475 - $ts = strtotime($timestamp . ' UTC');
2476 - if (!$ts) {
2477 - return '';
2478 - }
2479 - $diff = time() - $ts;
2480 - if ($diff < 60) return __('just now', 'mxchat');
2481 - if ($diff < 3600) return floor($diff / 60) . __('m ago', 'mxchat');
2482 - if ($diff < 86400) return floor($diff / 3600) . __('h ago', 'mxchat');
2483 - if ($diff < 604800) return floor($diff / 86400) . __('d ago', 'mxchat');
2484 - return wp_date('M j', $ts);
2485 -}
2486 -
2487 -/**
2488 - * Handle translation of chat messages via AJAX
2489 - */
2490 -public function mxchat_translate_messages() {
2491 - if (!current_user_can('manage_options')) {
2492 - wp_send_json_error(['error' => 'Insufficient permissions']);
2493 - wp_die();
2494 - }
2495 -
2496 - $session_id = isset($_POST['session_id']) ? sanitize_text_field($_POST['session_id']) : '';
2497 - $target_lang = isset($_POST['target_lang']) ? sanitize_text_field($_POST['target_lang']) : 'en';
2498 - $messages_json = isset($_POST['messages']) ? wp_unslash($_POST['messages']) : '[]';
2499 - $messages = json_decode($messages_json, true);
2500 -
2501 - if (empty($session_id)) {
2502 - wp_send_json_error(['error' => 'No session ID provided']);
2503 - wp_die();
2504 - }
2505 -
2506 - if (empty($messages) || !is_array($messages)) {
2507 - wp_send_json_error(['error' => 'No messages to translate']);
2508 - wp_die();
2509 - }
2510 -
2511 - // Language names for prompting
2512 - $languages = [
2513 - 'en' => 'English',
2514 - 'es' => 'Spanish',
2515 - 'fr' => 'French',
2516 - 'de' => 'German',
2517 - 'it' => 'Italian',
2518 - 'pt' => 'Portuguese',
2519 - 'nl' => 'Dutch',
2520 - 'ru' => 'Russian',
2521 - 'zh' => 'Chinese',
2522 - 'ja' => 'Japanese',
2523 - 'ko' => 'Korean',
2524 - 'ar' => 'Arabic',
2525 - 'hi' => 'Hindi',
2526 - 'tr' => 'Turkish',
2527 - 'pl' => 'Polish',
2528 - 'vi' => 'Vietnamese',
2529 - 'th' => 'Thai',
2530 - 'id' => 'Indonesian',
2531 - 'sv' => 'Swedish',
2532 - 'da' => 'Danish'
2533 - ];
2534 -
2535 - $target_lang_name = isset($languages[$target_lang]) ? $languages[$target_lang] : 'English';
2536 -
2537 - // Build combined text for translation (numbered for parsing)
2538 - $numbered_messages = [];
2539 - foreach ($messages as $i => $msg) {
2540 - $content = isset($msg['content']) ? trim($msg['content']) : '';
2541 - if (!empty($content)) {
2542 - $numbered_messages[] = "[MSG" . $i . "]" . $content . "[/MSG" . $i . "]";
2543 - }
2544 - }
2545 -
2546 - if (empty($numbered_messages)) {
2547 - wp_send_json_error(['error' => 'No valid messages to translate']);
2548 - wp_die();
2549 - }
2550 -
2551 - $combined_text = implode("\n\n", $numbered_messages);
2552 -
2553 - // Prepare the translation prompt
2554 - $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.";
2555 -
2556 - // Get user's selected model and determine provider
2557 - $options = get_option('mxchat_options', []);
2558 - $selected_model = $options['model'] ?? 'gpt-5.1-chat-latest';
2559 -
2560 - // Check if using OpenRouter
2561 - if ($selected_model === 'openrouter') {
2562 - $provider = 'openrouter';
2563 - $selected_model = $options['openrouter_selected_model'] ?? '';
2564 - $api_key = $options['openrouter_api_key'] ?? '';
2565 -
2566 - if (empty($selected_model)) {
2567 - wp_send_json_error(['error' => 'No OpenRouter model selected']);
2568 - wp_die();
2569 - }
2570 - } else {
2571 - // Determine provider from model name
2572 - $model_parts = explode('-', $selected_model);
2573 - $provider = strtolower($model_parts[0]);
2574 -
2575 - // Get the appropriate API key based on provider
2576 - $api_key = '';
2577 - switch ($provider) {
2578 - case 'gpt':
2579 - case 'o1':
2580 - $api_key = $options['api_key'] ?? '';
2581 - break;
2582 - case 'claude':
2583 - $api_key = $options['claude_api_key'] ?? '';
2584 - break;
2585 - case 'grok':
2586 - $api_key = $options['xai_api_key'] ?? '';
2587 - break;
2588 - case 'deepseek':
2589 - $api_key = $options['deepseek_api_key'] ?? '';
2590 - break;
2591 - case 'gemini':
2592 - $api_key = $options['gemini_api_key'] ?? '';
2593 - break;
2594 - default:
2595 - // Default to OpenAI
2596 - $api_key = $options['api_key'] ?? '';
2597 - $provider = 'gpt';
2598 - break;
2599 - }
2600 - }
2601 -
2602 - if (empty($api_key)) {
2603 - wp_send_json_error(['error' => 'No API key configured for ' . $provider]);
2604 - wp_die();
2605 - }
2606 -
2607 - // Make API request based on provider
2608 - $response = $this->translate_with_provider($provider, $selected_model, $api_key, $system_prompt, $combined_text);
2609 -
2610 - if (is_wp_error($response)) {
2611 - wp_send_json_error(['error' => $response->get_error_message()]);
2612 - wp_die();
2613 - }
2614 -
2615 - // Parse the response to extract translated messages
2616 - $translations = [];
2617 - foreach ($messages as $msg) {
2618 - $index = $msg['index'];
2619 - $pattern = '/\[MSG' . $index . '\](.*?)\[\/MSG' . $index . '\]/s';
2620 - if (preg_match($pattern, $response, $matches)) {
2621 - $translations[] = [
2622 - 'index' => $index,
2623 - 'translated' => trim($matches[1])
2624 - ];
2625 - }
2626 - }
2627 -
2628 - // Save translations to database
2629 - if (!empty($translations)) {
2630 - $this->save_transcript_translation($session_id, $target_lang, $translations);
2631 - }
2632 -
2633 - wp_send_json(['success' => true, 'translations' => $translations, 'language' => $target_lang]);
2634 - wp_die();
2635 -}
2636 -
2637 -/**
2638 - * Save transcript translation to database
2639 - */
2640 -private function save_transcript_translation($session_id, $language_code, $translations) {
2641 - global $wpdb;
2642 - $table_name = $wpdb->prefix . 'mxchat_transcript_translations';
2643 -
2644 - // Check if table exists, create if not
2645 - if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") !== $table_name) {
2646 - mxchat_create_translations_table();
2647 - }
2648 -
2649 - $now = current_time('mysql');
2650 - $translations_json = wp_json_encode($translations);
2651 -
2652 - // Use REPLACE to insert or update
2653 - $wpdb->query($wpdb->prepare(
2654 - "REPLACE INTO $table_name (session_id, language_code, translations, created_at, updated_at)
2655 - VALUES (%s, %s, %s, %s, %s)",
2656 - $session_id,
2657 - $language_code,
2658 - $translations_json,
2659 - $now,
2660 - $now
2661 - ));
2662 -}
2663 -
2664 -/**
2665 - * Get saved translation for a session
2666 - */
2667 -public function mxchat_get_transcript_translation() {
2668 - if (!current_user_can('manage_options')) {
2669 - wp_send_json_error(['error' => 'Insufficient permissions']);
2670 - wp_die();
2671 - }
2672 -
2673 - $session_id = isset($_POST['session_id']) ? sanitize_text_field($_POST['session_id']) : '';
2674 -
2675 - if (empty($session_id)) {
2676 - wp_send_json_error(['error' => 'No session ID provided']);
2677 - wp_die();
2678 - }
2679 -
2680 - global $wpdb;
2681 - $table_name = $wpdb->prefix . 'mxchat_transcript_translations';
2682 -
2683 - // Check if table exists
2684 - if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") !== $table_name) {
2685 - wp_send_json(['success' => true, 'has_translation' => false]);
2686 - wp_die();
2687 - }
2688 -
2689 - // Get the most recent translation for this session
2690 - $result = $wpdb->get_row($wpdb->prepare(
2691 - "SELECT language_code, translations FROM $table_name WHERE session_id = %s ORDER BY updated_at DESC LIMIT 1",
2692 - $session_id
2693 - ));
2694 -
2695 - if ($result) {
2696 - $translations = json_decode($result->translations, true);
2697 - wp_send_json([
2698 - 'success' => true,
2699 - 'has_translation' => true,
2700 - 'language' => $result->language_code,
2701 - 'translations' => $translations
2702 - ]);
2703 - } else {
2704 - wp_send_json(['success' => true, 'has_translation' => false]);
2705 - }
2706 - wp_die();
2707 -}
2708 -
2709 -/**
2710 - * Translate text using the user's selected provider and model
2711 - */
2712 -private function translate_with_provider($provider, $model, $api_key, $system_prompt, $text) {
2713 - switch ($provider) {
2714 - case 'claude':
2715 - return $this->translate_with_claude($api_key, $model, $system_prompt, $text);
2716 - case 'grok':
2717 - return $this->translate_with_xai($api_key, $model, $system_prompt, $text);
2718 - case 'deepseek':
2719 - return $this->translate_with_deepseek($api_key, $model, $system_prompt, $text);
2720 - case 'gemini':
2721 - return $this->translate_with_gemini($api_key, $model, $system_prompt, $text);
2722 - case 'openrouter':
2723 - return $this->translate_with_openrouter($api_key, $model, $system_prompt, $text);
2724 - case 'gpt':
2725 - case 'o1':
2726 - default:
2727 - return $this->translate_with_openai($api_key, $model, $system_prompt, $text);
2728 - }
2729 -}
2730 -
2731 -/**
2732 - * Translate text using OpenAI API
2733 - */
2734 -private function translate_with_openai($api_key, $model, $system_prompt, $text) {
2735 - $response = wp_remote_post('https://api.openai.com/v1/chat/completions', [
2736 - 'timeout' => 60,
2737 - 'headers' => [
2738 - 'Authorization' => 'Bearer ' . $api_key,
2739 - 'Content-Type' => 'application/json'
2740 - ],
2741 - 'body' => wp_json_encode([
2742 - 'model' => $model,
2743 - 'messages' => [
2744 - ['role' => 'system', 'content' => $system_prompt],
2745 - ['role' => 'user', 'content' => $text]
2746 - ],
2747 - 'temperature' => 0.3
2748 - ])
2749 - ]);
2750 -
2751 - if (is_wp_error($response)) {
2752 - return $response;
2753 - }
2754 -
2755 - $body = json_decode(wp_remote_retrieve_body($response), true);
2756 -
2757 - if (isset($body['error'])) {
2758 - return new WP_Error('api_error', $body['error']['message']);
2759 - }
2760 -
2761 - if (isset($body['choices'][0]['message']['content'])) {
2762 - return $body['choices'][0]['message']['content'];
2763 - }
2764 -
2765 - return new WP_Error('api_error', 'Invalid API response');
2766 -}
2767 -
2768 -/**
2769 - * Translate text using Claude API
2770 - */
2771 -private function translate_with_claude($api_key, $model, $system_prompt, $text) {
2772 - $response = wp_remote_post('https://api.anthropic.com/v1/messages', [
2773 - 'timeout' => 60,
2774 - 'headers' => [
2775 - 'x-api-key' => $api_key,
2776 - 'anthropic-version' => '2023-06-01',
2777 - 'Content-Type' => 'application/json'
2778 - ],
2779 - 'body' => wp_json_encode([
2780 - 'model' => $model,
2781 - 'max_tokens' => 4096,
2782 - 'system' => $system_prompt,
2783 - 'messages' => [
2784 - ['role' => 'user', 'content' => $text]
2785 - ]
2786 - ])
2787 - ]);
2788 -
2789 - if (is_wp_error($response)) {
2790 - return $response;
2791 - }
2792 -
2793 - $body = json_decode(wp_remote_retrieve_body($response), true);
2794 -
2795 - if (isset($body['error'])) {
2796 - return new WP_Error('api_error', $body['error']['message']);
2797 - }
2798 -
2799 - if (isset($body['content'][0]['text'])) {
2800 - return $body['content'][0]['text'];
2801 - }
2802 -
2803 - return new WP_Error('api_error', 'Invalid API response');
2804 -}
2805 -
2806 -/**
2807 - * Translate text using xAI (Grok) API
2808 - */
2809 -private function translate_with_xai($api_key, $model, $system_prompt, $text) {
2810 - $response = wp_remote_post('https://api.x.ai/v1/chat/completions', [
2811 - 'timeout' => 60,
2812 - 'headers' => [
2813 - 'Authorization' => 'Bearer ' . $api_key,
2814 - 'Content-Type' => 'application/json'
2815 - ],
2816 - 'body' => wp_json_encode([
2817 - 'model' => $model,
2818 - 'messages' => [
2819 - ['role' => 'system', 'content' => $system_prompt],
2820 - ['role' => 'user', 'content' => $text]
2821 - ],
2822 - 'temperature' => 0.3
2823 - ])
2824 - ]);
2825 -
2826 - if (is_wp_error($response)) {
2827 - return $response;
2828 - }
2829 -
2830 - $body = json_decode(wp_remote_retrieve_body($response), true);
2831 -
2832 - if (isset($body['error'])) {
2833 - return new WP_Error('api_error', $body['error']['message']);
2834 - }
2835 -
2836 - if (isset($body['choices'][0]['message']['content'])) {
2837 - return $body['choices'][0]['message']['content'];
2838 - }
2839 -
2840 - return new WP_Error('api_error', 'Invalid API response');
2841 -}
2842 -
2843 -/**
2844 - * Translate text using DeepSeek API
2845 - */
2846 -private function translate_with_deepseek($api_key, $model, $system_prompt, $text) {
2847 - $response = wp_remote_post('https://api.deepseek.com/v1/chat/completions', [
2848 - 'timeout' => 60,
2849 - 'headers' => [
2850 - 'Authorization' => 'Bearer ' . $api_key,
2851 - 'Content-Type' => 'application/json'
2852 - ],
2853 - 'body' => wp_json_encode([
2854 - 'model' => $model,
2855 - 'messages' => [
2856 - ['role' => 'system', 'content' => $system_prompt],
2857 - ['role' => 'user', 'content' => $text]
2858 - ],
2859 - 'temperature' => 0.3
2860 - ])
2861 - ]);
2862 -
2863 - if (is_wp_error($response)) {
2864 - return $response;
2865 - }
2866 -
2867 - $body = json_decode(wp_remote_retrieve_body($response), true);
2868 -
2869 - if (isset($body['error'])) {
2870 - return new WP_Error('api_error', $body['error']['message']);
2871 - }
2872 -
2873 - if (isset($body['choices'][0]['message']['content'])) {
2874 - return $body['choices'][0]['message']['content'];
2875 - }
2876 -
2877 - return new WP_Error('api_error', 'Invalid API response');
2878 -}
2879 -
2880 -/**
2881 - * Translate text using Google Gemini API
2882 - */
2883 -private function translate_with_gemini($api_key, $model, $system_prompt, $text) {
2884 - $url = 'https://generativelanguage.googleapis.com/v1beta/models/' . $model . ':generateContent?key=' . $api_key;
2885 -
2886 - $response = wp_remote_post($url, [
2887 - 'timeout' => 60,
2888 - 'headers' => [
2889 - 'Content-Type' => 'application/json'
2890 - ],
2891 - 'body' => wp_json_encode([
2892 - 'contents' => [
2893 - [
2894 - 'parts' => [
2895 - ['text' => $system_prompt . "\n\n" . $text]
2896 - ]
2897 - ]
2898 - ],
2899 - 'generationConfig' => [
2900 - 'temperature' => 0.3
2901 - ]
2902 - ])
2903 - ]);
2904 -
2905 - if (is_wp_error($response)) {
2906 - return $response;
2907 - }
2908 -
2909 - $body = json_decode(wp_remote_retrieve_body($response), true);
2910 -
2911 - if (isset($body['error'])) {
2912 - return new WP_Error('api_error', $body['error']['message']);
2913 - }
2914 -
2915 - if (isset($body['candidates'][0]['content']['parts'][0]['text'])) {
2916 - return $body['candidates'][0]['content']['parts'][0]['text'];
2917 - }
2918 -
2919 - return new WP_Error('api_error', 'Invalid API response');
2920 -}
2921 -
2922 -/**
2923 - * Translate text using OpenRouter API
2924 - */
2925 -private function translate_with_openrouter($api_key, $model, $system_prompt, $text) {
2926 - $response = wp_remote_post('https://openrouter.ai/api/v1/chat/completions', [
2927 - 'timeout' => 60,
2928 - 'headers' => [
2929 - 'Authorization' => 'Bearer ' . $api_key,
2930 - 'Content-Type' => 'application/json',
2931 - 'HTTP-Referer' => home_url(),
2932 - 'X-Title' => 'MxChat Translation'
2933 - ],
2934 - 'body' => wp_json_encode([
2935 - 'model' => $model,
2936 - 'messages' => [
2937 - ['role' => 'system', 'content' => $system_prompt],
2938 - ['role' => 'user', 'content' => $text]
2939 - ],
2940 - 'temperature' => 0.3
2941 - ])
2942 - ]);
2943 -
2944 - if (is_wp_error($response)) {
2945 - return $response;
2946 - }
2947 -
2948 - $body = json_decode(wp_remote_retrieve_body($response), true);
2949 -
2950 - if (isset($body['error'])) {
2951 - return new WP_Error('api_error', $body['error']['message']);
2952 - }
2953 -
2954 - if (isset($body['choices'][0]['message']['content'])) {
2955 - return $body['choices'][0]['message']['content'];
2956 - }
2957 -
2958 - return new WP_Error('api_error', 'Invalid API response');
2959 -}
2960 -
2961 1605 public function mxchat_fetch_chat_history() {
2962 1606 global $wpdb;
2963 1607 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
2964 1608 $url_clicks_table = $wpdb->prefix . 'mxchat_url_clicks';
@@ -2963,10 +1607,9 @@
2963 1607 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
2964 1608 $url_clicks_table = $wpdb->prefix . 'mxchat_url_clicks';
2965 1609
2966 1610 if (!current_user_can('manage_options')) {
2967 - wp_send_json_error(['message' => 'Insufficient permissions']);
2968 - wp_die();
1611 + wp_die(esc_html__('You do not have sufficient permissions to view this page.', 'mxchat'));
2969 1612 }
2970 1613
2971 1614 $page = isset($_POST['page']) ? absint($_POST['page']) : 1;
2972 1615 $per_page = isset($_POST['per_page']) ? absint($_POST['per_page']) : 50;
@@ -2971,13 +1614,12 @@
2971 1614 $page = isset($_POST['page']) ? absint($_POST['page']) : 1;
2972 1615 $per_page = isset($_POST['per_page']) ? absint($_POST['per_page']) : 50;
2973 1616 $offset = ($page - 1) * $per_page;
2974 1617 $search = isset($_POST['search']) ? sanitize_text_field($_POST['search']) : '';
2975 - $sort_order = isset($_POST['sort_order']) && $_POST['sort_order'] === 'asc' ? 'ASC' : 'DESC';
2976 1618
2977 - // Build search condition
2978 1619 $search_condition = '';
2979 1620 $search_params = [];
1621 +
2980 1622 if (!empty($search)) {
2981 1623 $search_condition = "WHERE (
2982 1624 session_id LIKE %s
2983 1625 OR user_email LIKE %s
@@ -2987,51 +1629,52 @@
2987 1629 )";
2988 1630 $search_params = array_fill(0, 5, '%' . $wpdb->esc_like($search) . '%');
2989 1631 }
2990 1632
2991 - // Get total count
2992 1633 $count_query = !empty($search)
2993 1634 ? $wpdb->prepare("SELECT COUNT(DISTINCT session_id) FROM {$table_name} {$search_condition}", $search_params)
2994 1635 : "SELECT COUNT(DISTINCT session_id) FROM {$table_name}";
2995 - $total_sessions = (int) $wpdb->get_var($count_query);
2996 1636
2997 - // Get session IDs for current page (sorted by newest or oldest)
1637 + $total_sessions = $wpdb->get_var($count_query);
1638 +
2998 1639 $session_query = !empty($search)
2999 1640 ? $wpdb->prepare(
3000 1641 "SELECT DISTINCT t.session_id FROM {$table_name} t {$search_condition}
3001 - 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",
3002 1643 array_merge($search_params, [$per_page, $offset])
3003 1644 )
3004 1645 : $wpdb->prepare(
3005 1646 "SELECT DISTINCT session_id FROM {$table_name}
3006 - 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",
3007 1648 $per_page, $offset
3008 1649 );
1650 +
3009 1651 $session_ids = $wpdb->get_col($session_query);
3010 1652
3011 - $total_pages = ceil($total_sessions / $per_page);
3012 -
3013 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>';
3014 1661 wp_send_json([
3015 - 'success' => true,
3016 - 'sessions' => [],
1662 + 'html' => ob_get_clean(),
3017 1663 'page' => $page,
3018 1664 'total_pages' => 0,
3019 - 'total_sessions' => 0,
3020 - 'showing_start' => 0,
3021 - 'showing_end' => 0
1665 + 'total_sessions' => 0
3022 1666 ]);
3023 1667 wp_die();
3024 1668 }
3025 1669
3026 - // Check for optional columns/tables
3027 1670 $url_table_exists = $wpdb->get_var("SHOW TABLES LIKE '$url_clicks_table'") === $url_clicks_table;
3028 1671 $originating_columns_exist = !empty($wpdb->get_results("SHOW COLUMNS FROM $table_name LIKE 'originating_page_url'"));
3029 1672
3030 - // Build session list data
3031 - $sessions = [];
1673 + ob_start();
1674 + echo '<div class="mxchat-transcript">';
1675 +
3032 1676 foreach ($session_ids as $session_id) {
3033 - // Get session metadata
3034 1677 $session_data = $originating_columns_exist
3035 1678 ? $wpdb->get_row($wpdb->prepare(
3036 1679 "SELECT user_email, user_name, user_identifier, originating_page_url, originating_page_title, timestamp
3037 1680 FROM {$table_name} WHERE session_id = %s ORDER BY timestamp ASC LIMIT 1",
@@ -3042,217 +1685,186 @@
3042 1685 WHERE session_id = %s LIMIT 1",
3043 1686 $session_id
3044 1687 ));
3045 1688
3046 - // Get message count and latest timestamp
3047 - $message_stats = $wpdb->get_row($wpdb->prepare(
3048 - "SELECT COUNT(*) as count, MAX(timestamp) as latest FROM {$table_name} WHERE session_id = %s",
3049 - $session_id
3050 - ));
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 + }
3051 1702
3052 - // Get first user message as preview
3053 - $first_user_msg = $wpdb->get_var($wpdb->prepare(
3054 - "SELECT message FROM {$table_name} WHERE session_id = %s AND role = 'user' ORDER BY timestamp ASC LIMIT 1",
3055 - $session_id
3056 - ));
3057 - $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 + );
3058 1710
3059 - // 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 +
3060 1714 $user_email = !empty($session_data->user_email) ? $session_data->user_email : '';
3061 - $user_name = !empty($session_data->user_name) ? $session_data->user_name : '';
1715 + $user_name = !empty($session_data->user_name) ? $session_data->user_name : ''; // NEW
3062 1716 $user_identifier = !empty($session_data->user_identifier) ? $session_data->user_identifier : 'Guest';
3063 1717
3064 - $display_name = $user_name ?: ($user_email ? explode('@', $user_email)[0] : $user_identifier);
3065 - $display_sub = $user_email ?: ('ID: ' . $user_identifier);
3066 -
3067 - // Format time - show relative for recent, date for older
3068 - $timestamp = strtotime($message_stats->latest . ' UTC');
3069 - $now = time();
3070 - $diff = $now - $timestamp;
3071 - if ($diff < 3600) {
3072 - $time_display = floor($diff / 60) . 'm ago';
3073 - } elseif ($diff < 86400) {
3074 - $time_display = floor($diff / 3600) . 'h ago';
3075 - } elseif ($diff < 604800) {
3076 - $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 + }
3077 1724 } else {
3078 - $time_display = wp_date('M j', $timestamp);
1725 + $user_display = $user_identifier;
3079 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>';
3080 1730
3081 - // Get initials for avatar
3082 - $initials = strtoupper(substr($display_name, 0, 2));
3083 - if (strlen($display_name) > 2 && strpos($display_name, ' ') !== false) {
3084 - $parts = explode(' ', $display_name);
3085 - $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>';
3086 1742 }
3087 1743
3088 - $sessions[] = [
3089 - 'session_id' => $session_id,
3090 - 'display_name' => $display_name,
3091 - 'display_sub' => $display_sub,
3092 - 'initials' => $initials,
3093 - 'preview' => $preview,
3094 - 'message_count' => (int) $message_stats->count,
3095 - 'time_display' => $time_display,
3096 - 'timestamp' => $message_stats->latest
3097 - ];
3098 - }
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 + }
3099 1761
3100 - wp_send_json([
3101 - 'success' => true,
3102 - 'sessions' => $sessions,
3103 - 'page' => $page,
3104 - 'total_pages' => $total_pages,
3105 - 'total_sessions' => $total_sessions,
3106 - 'showing_start' => $offset + 1,
3107 - 'showing_end' => min($offset + $per_page, $total_sessions)
3108 - ]);
3109 - wp_die();
3110 -}
1762 + echo '</div>'; // end session-header
1763 + echo '<div class="mxchat-messages">';
3111 1764
3112 -/**
3113 - * Fetch single conversation details for split-panel view
3114 - */
3115 -public function mxchat_fetch_conversation() {
3116 - global $wpdb;
3117 - $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
3118 - $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'));
3119 1767
3120 - if (!current_user_can('manage_options')) {
3121 - wp_send_json_error(['message' => 'Insufficient permissions']);
3122 - wp_die();
3123 - }
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 + }
3124 1788
3125 - $session_id = isset($_POST['session_id']) ? sanitize_text_field($_POST['session_id']) : '';
3126 - if (empty($session_id)) {
3127 - wp_send_json_error(['message' => 'No session ID provided']);
3128 - wp_die();
3129 - }
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);
3130 1798
3131 - // Check for optional columns/tables
3132 - $url_table_exists = $wpdb->get_var("SHOW TABLES LIKE '$url_clicks_table'") === $url_clicks_table;
3133 - $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 + }
3134 1805
3135 - // Get session metadata
3136 - $session_data = $originating_columns_exist
3137 - ? $wpdb->get_row($wpdb->prepare(
3138 - "SELECT user_email, user_name, user_identifier, originating_page_url, originating_page_title, timestamp
3139 - FROM {$table_name} WHERE session_id = %s ORDER BY timestamp ASC LIMIT 1",
3140 - $session_id
3141 - ))
3142 - : $wpdb->get_row($wpdb->prepare(
3143 - "SELECT user_email, user_name, user_identifier, timestamp FROM {$table_name}
3144 - WHERE session_id = %s LIMIT 1",
3145 - $session_id
3146 - ));
3147 -
3148 - if (!$session_data) {
3149 - wp_send_json_error(['message' => 'Session not found']);
3150 - wp_die();
1806 + echo '</div></div>'; // end messages and session
3151 1807 }
3152 1808
3153 - // Get clicked URLs
3154 - $clicked_urls = [];
3155 - if ($url_table_exists) {
3156 - $url_clicks = $wpdb->get_results($wpdb->prepare(
3157 - "SELECT DISTINCT clicked_url FROM {$url_clicks_table}
3158 - WHERE session_id = %s ORDER BY click_timestamp ASC",
3159 - $session_id
3160 - ));
3161 - foreach ($url_clicks as $click) {
3162 - $clicked_urls[] = $click->clicked_url;
3163 - }
3164 - }
1809 + echo '</div>'; // end transcript
3165 1810
3166 - // Get all messages
3167 - $messages = $wpdb->get_results($wpdb->prepare(
3168 - "SELECT * FROM {$table_name} WHERE session_id = %s ORDER BY timestamp ASC",
3169 - $session_id
3170 - ));
1811 + $total_pages = ceil($total_sessions / $per_page);
3171 1812
3172 - // Build user info
3173 - $user_email = !empty($session_data->user_email) ? $session_data->user_email : '';
3174 - $user_name = !empty($session_data->user_name) ? $session_data->user_name : '';
3175 - $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>';
3176 1822
3177 - $display_name = $user_name ?: ($user_email ? explode('@', $user_email)[0] : $user_identifier);
3178 - $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 + }
3179 1828
3180 - // Get initials
3181 - $initials = strtoupper(substr($display_name, 0, 2));
3182 - if (strlen($display_name) > 2 && strpos($display_name, ' ') !== false) {
3183 - $parts = explode(' ', $display_name);
3184 - $initials = strtoupper(substr($parts[0], 0, 1) . substr(end($parts), 0, 1));
3185 - }
1829 + $start_page = max(1, $page - 2);
1830 + $end_page = min($total_pages, $page + 2);
3186 1831
3187 - // Page info
3188 - $page_url = $originating_columns_exist && !empty($session_data->originating_page_url) ? $session_data->originating_page_url : '';
3189 - $page_title = '';
3190 - if ($page_url) {
3191 - $page_title = !empty($session_data->originating_page_title) ? $session_data->originating_page_title : parse_url($page_url, PHP_URL_PATH);
3192 - }
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 + }
3193 1838
3194 - // Format messages for output
3195 - $formatted_messages = [];
3196 - foreach ($messages as $msg) {
3197 - $is_user = ($msg->role === 'user');
3198 - $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 + }
3199 1843
3200 - $content = wp_kses(
3201 - stripslashes($msg->message),
3202 - [
3203 - 'b' => [], 'strong' => [], 'i' => [], 'em' => [], 'u' => [],
3204 - 'br' => [], 'p' => [], 'ul' => [], 'ol' => [], 'li' => [],
3205 - 'a' => ['href' => [], 'title' => [], 'target' => [], 'class' => []],
3206 - 'div' => ['class' => [], 'id' => [], 'data-nonce' => []],
3207 - 'img' => ['src' => [], 'alt' => [], 'class' => []],
3208 - 'h3' => ['class' => []],
3209 - 'h4' => ['class' => []],
3210 - 'button' => ['type' => [], 'class' => [], 'data-product-id' => [], 'data-nonce' => [], 'data-product-type' => [], 'data-original-text' => [], 'data-mxchat-action' => []],
3211 - 'select' => ['class' => [], 'data-attribute' => []],
3212 - 'option' => ['value' => []],
3213 - 'span' => ['class' => []],
3214 - 'del' => [], 'ins' => [],
3215 - ]
3216 - );
3217 - $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 + }
3218 1850
3219 - $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 + }
3220 1854
3221 - $formatted_messages[] = [
3222 - 'id' => $msg->id,
3223 - 'role' => $msg->role,
3224 - 'is_user' => $is_user,
3225 - 'is_bot' => $is_bot,
3226 - 'content' => $formatted_content,
3227 - 'timestamp' => wp_date('g:i A', strtotime($msg->timestamp . ' UTC')),
3228 - 'full_timestamp' => wp_date('F j, Y g:i A', strtotime($msg->timestamp . ' UTC')),
3229 - 'has_rag' => $has_rag
3230 - ];
1855 + echo '</div>';
3231 1856 }
3232 1857
3233 - // First message timestamp for "started" display
3234 - $started = !empty($messages) ? wp_date('M j, Y g:i A', strtotime($messages[0]->timestamp . ' UTC')) : '-';
1858 + echo '</div>';
3235 1859
3236 1860 wp_send_json([
3237 - 'success' => true,
3238 - 'session_id' => $session_id,
3239 - 'user' => [
3240 - 'name' => $display_name,
3241 - 'sub' => $display_sub,
3242 - 'initials' => $initials,
3243 - 'email' => $user_email,
3244 - 'identifier' => $user_identifier
3245 - ],
3246 - 'page' => [
3247 - 'url' => $page_url,
3248 - 'title' => $page_title
3249 - ],
3250 - 'clicked_urls' => $clicked_urls,
3251 - 'messages' => $formatted_messages,
3252 - 'message_count' => count($messages),
3253 - 'started' => $started
1861 + 'html' => ob_get_clean(),
1862 + 'page' => $page,
1863 + 'total_pages' => $total_pages,
1864 + 'total_sessions' => $total_sessions
3254 1865 ]);
1866 +
3255 1867 wp_die();
3256 1868 }
3257 1869
3258 1870
@@ -3348,9 +1960,9 @@
3348 1960 $pinecone_manager = MxChat_Pinecone_Manager::get_instance();
3349 1961
3350 1962 // Display success message if all prompts were deleted
3351 1963 if (isset($_GET['all_deleted']) && $_GET['all_deleted'] === 'true') {
3352 - 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>';
3353 1965 }
3354 1966
3355 1967 // Set up pagination, search query, and content type filter
3356 1968 $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field($_GET['_wpnonce']) : '';
@@ -3356,9 +1968,9 @@
3356 1968 $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field($_GET['_wpnonce']) : '';
3357 1969 $search_query = (!empty($nonce) && wp_verify_nonce($nonce, 'mxchat_prompts_search_nonce') && isset($_GET['search'])) ? sanitize_text_field($_GET['search']) : '';
3358 1970 $content_type_filter = isset($_GET['content_type']) ? sanitize_key($_GET['content_type']) : ''; // ADDED 2.5.6
3359 1971 $current_page = isset($_GET['paged']) ? absint($_GET['paged']) : 1;
3360 - $per_page = 25;
1972 + $per_page = 10;
3361 1973
3362 1974 //error_log('DEBUG: Search query: ' . $search_query);
3363 1975 //error_log('DEBUG: Content type filter: ' . $content_type_filter);
3364 1976 //error_log('DEBUG: Current page: ' . $current_page);
@@ -3396,12 +2008,8 @@
3396 2008 $pinecone_options = $pinecone_manager->mxchat_get_bot_pinecone_options($current_bot_id);
3397 2009 $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
3398 2010 $pinecone_api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
3399 2011
3400 - // Get OpenAI Vector Store settings
3401 - $vectorstore_options = get_option('mxchat_openai_vectorstore_options', array());
3402 - $use_vectorstore = ($vectorstore_options['mxchat_use_openai_vectorstore'] ?? '0') === '1';
3403 -
3404 2012 //error_log('DEBUG: Bot ' . $current_bot_id . ' - Use Pinecone: ' . ($use_pinecone ? 'YES' : 'NO'));
3405 2013 //error_log('DEBUG: Bot ' . $current_bot_id . ' - Has API Key: ' . (!empty($pinecone_api_key) ? 'YES' : 'NO'));
3406 2014 //error_log('DEBUG: Bot ' . $current_bot_id . ' - Host: ' . ($pinecone_options['mxchat_pinecone_host'] ?? 'NOT SET'));
3407 2015 //error_log('DEBUG: Bot ' . $current_bot_id . ' - Namespace: ' . ($pinecone_options['mxchat_pinecone_namespace'] ?? 'NOT SET'));
@@ -3467,111 +2075,25 @@
3467 2075 if (!empty($where_clauses)) {
3468 2076 $sql_where = "WHERE " . implode(" AND ", $where_clauses);
3469 2077 }
3470 2078
3471 - // UPDATED 2.6.3: Count unique entries (by source_url) instead of individual rows
3472 - // This ensures pagination shows X entries per page, not X chunks
3473 - // Entries with empty source_url are counted individually
2079 + // Retrieve total number of prompts, considering filters
3474 2080 if (!empty($where_values)) {
3475 - // Count unique source_urls + count of rows with empty source_url
3476 - $count_query = $wpdb->prepare(
3477 - "SELECT (SELECT COUNT(DISTINCT source_url) FROM {$table_name} {$sql_where} AND source_url != '') +
3478 - (SELECT COUNT(*) FROM {$table_name} {$sql_where} AND (source_url = '' OR source_url IS NULL))",
3479 - array_merge($where_values, $where_values)
3480 - );
2081 + $count_query = $wpdb->prepare("SELECT COUNT(*) FROM {$table_name} {$sql_where}", $where_values);
3481 2082 } else {
3482 - $count_query = "SELECT (SELECT COUNT(DISTINCT source_url) FROM {$table_name} WHERE source_url != '') +
3483 - (SELECT COUNT(*) FROM {$table_name} WHERE source_url = '' OR source_url IS NULL)";
2083 + $count_query = "SELECT COUNT(*) FROM {$table_name} {$sql_where}";
3484 2084 }
3485 2085 $total_records = $wpdb->get_var($count_query);
3486 2086 $total_pages = ceil($total_records / $per_page);
3487 2087
3488 - // UPDATED 2.6.3: Get unique source_urls for pagination, then fetch all their rows
3489 - // Step 1: Get the source_urls for this page (distinct URLs ordered by latest timestamp)
3490 - if (!empty($where_values)) {
3491 - $urls_query = $wpdb->prepare(
3492 - "SELECT source_url, MAX(timestamp) as latest_ts FROM {$table_name} {$sql_where}
3493 - GROUP BY source_url ORDER BY latest_ts DESC LIMIT %d OFFSET %d",
3494 - array_merge($where_values, array($per_page, $offset))
3495 - );
3496 - } else {
3497 - $urls_query = $wpdb->prepare(
3498 - "SELECT source_url, MAX(timestamp) as latest_ts FROM {$table_name}
3499 - GROUP BY source_url ORDER BY latest_ts DESC LIMIT %d OFFSET %d",
3500 - $per_page, $offset
3501 - );
3502 - }
3503 - $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 + );
3504 2094
3505 - // Step 2: Fetch all rows for these source_urls
3506 - $prompts = array();
3507 - if (!empty($page_urls)) {
3508 - // Build URL order map to preserve newest-first ordering from step 1
3509 - $url_order_map = array();
3510 - $url_list = array();
3511 - $has_empty_url = false;
3512 - $order_index = 0;
3513 - foreach ($page_urls as $url_row) {
3514 - if (empty($url_row->source_url)) {
3515 - $has_empty_url = true;
3516 - $url_order_map['__empty__'] = $order_index++;
3517 - } else {
3518 - $url_list[] = $url_row->source_url;
3519 - $url_order_map[$url_row->source_url] = $order_index++;
3520 - }
3521 - }
3522 -
3523 - // Build query to fetch all rows for these URLs
3524 - $url_conditions = array();
3525 - $url_values = array();
3526 -
3527 - if (!empty($url_list)) {
3528 - $placeholders = implode(',', array_fill(0, count($url_list), '%s'));
3529 - $url_conditions[] = "source_url IN ($placeholders)";
3530 - $url_values = array_merge($url_values, $url_list);
3531 - }
3532 -
3533 - if ($has_empty_url) {
3534 - $url_conditions[] = "(source_url = '' OR source_url IS NULL)";
3535 - }
3536 -
3537 - if (!empty($url_conditions)) {
3538 - $url_where = "WHERE (" . implode(" OR ", $url_conditions) . ")";
3539 -
3540 - // Add original filters back
3541 - if (!empty($where_clauses)) {
3542 - $url_where .= " AND " . implode(" AND ", $where_clauses);
3543 - $url_values = array_merge($url_values, $where_values);
3544 - }
3545 -
3546 - if (!empty($url_values)) {
3547 - $prompts_query = $wpdb->prepare(
3548 - "SELECT * FROM {$table_name} {$url_where} ORDER BY timestamp DESC",
3549 - $url_values
3550 - );
3551 - } else {
3552 - $prompts_query = "SELECT * FROM {$table_name} {$url_where} ORDER BY timestamp DESC";
3553 - }
3554 -
3555 - $prompts = $wpdb->get_results($prompts_query);
3556 -
3557 - // Sort prompts by the original URL order (newest first), then by timestamp within each URL group
3558 - usort($prompts, function($a, $b) use ($url_order_map) {
3559 - $url_a = empty($a->source_url) ? '__empty__' : $a->source_url;
3560 - $url_b = empty($b->source_url) ? '__empty__' : $b->source_url;
3561 - $order_a = $url_order_map[$url_a] ?? PHP_INT_MAX;
3562 - $order_b = $url_order_map[$url_b] ?? PHP_INT_MAX;
3563 -
3564 - // First sort by URL order (newest URLs first)
3565 - if ($order_a !== $order_b) {
3566 - return $order_a - $order_b;
3567 - }
3568 -
3569 - // Within same URL, sort by timestamp DESC (newest chunks first)
3570 - return strtotime($b->timestamp) - strtotime($a->timestamp);
3571 - });
3572 - }
3573 - }
2095 + $prompts = $wpdb->get_results($prompts_query);
3574 2096 }
3575 2097
3576 2098 // ================================
3577 2099 // PAGINATION GENERATION
@@ -3578,35 +2100,29 @@
3578 2100 // ================================
3579 2101
3580 2102 // Generate pagination links
3581 2103 if ($total_pages > 1) {
3582 - // Build a clean base URL with only necessary parameters
3583 - $pagination_args = array('page' => 'mxchat-prompts');
3584 -
2104 + // Build base URL with necessary parameters
2105 + $base_url = add_query_arg('paged', '%#%');
2106 +
3585 2107 // Preserve bot_id parameter if multi-bot is active
3586 2108 if ($multibot_active && !empty($current_bot_id) && $current_bot_id !== 'default') {
3587 - $pagination_args['bot_id'] = $current_bot_id;
2109 + $base_url = add_query_arg('bot_id', $current_bot_id, $base_url);
3588 2110 }
3589 2111
3590 - // Preserve search query and nonce if present
2112 + // Preserve search query if present
3591 2113 if ($search_query) {
3592 - $pagination_args['search'] = $search_query;
3593 - if (!empty($nonce)) {
3594 - $pagination_args['_wpnonce'] = $nonce;
3595 - }
2114 + $base_url = add_query_arg('search', $search_query, $base_url);
3596 2115 }
3597 2116
3598 - // Preserve content type filter if present
2117 + // UPDATED 2.5.6: Preserve content type filter if present
3599 2118 if ($content_type_filter) {
3600 - $pagination_args['content_type'] = $content_type_filter;
2119 + $base_url = add_query_arg('content_type', $content_type_filter, $base_url);
3601 2120 }
3602 -
3603 - // Build clean base URL
3604 - $base_url = add_query_arg($pagination_args, admin_url('admin.php'));
3605 -
2121 +
3606 2122 $page_links = paginate_links(array(
3607 - 'base' => $base_url . '%_%',
3608 - 'format' => '&paged=%#%',
2123 + 'base' => $base_url,
2124 + 'format' => '',
3609 2125 'prev_text' => __('&laquo; Previous', 'mxchat'),
3610 2126 'next_text' => __('Next &raquo;', 'mxchat'),
3611 2127 'total' => $total_pages,
3612 2128 'current' => $current_page,
@@ -3618,50 +2134,1198 @@
3618 2134
3619 2135 // ================================
3620 2136 // PROCESSING STATUS RETRIEVAL
3621 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;
3622 2144
3623 - // Retrieve processing statuses using queue-based method
3624 - $processing_statuses = $knowledge_manager->mxchat_get_processing_statuses();
3625 - $pdf_status = $processing_statuses['pdf_status'];
3626 - $sitemap_status = $processing_statuses['sitemap_status'];
3627 - $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'));
3628 2147
3629 2148 //error_log('=== DEBUG: mxchat_create_prompts_page data preparation completed ===');
3630 2149
3631 - // ================================
3632 - // RENDER PAGE WITH NEW SIDEBAR LAYOUT
3633 - // ================================
2150 + ?>
3634 2151
3635 - // Include the new knowledge page template
3636 - 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>
3637 2162
3638 - // Package all page data for the render function
3639 - $page_data = array(
3640 - 'prompts' => $prompts,
3641 - 'total_records' => $total_records,
3642 - 'total_pages' => $total_pages,
3643 - 'current_page' => $current_page,
3644 - 'per_page' => $per_page,
3645 - 'page_links' => $page_links,
3646 - 'search_query' => $search_query,
3647 - 'content_type_filter' => $content_type_filter,
3648 - 'data_source' => $data_source,
3649 - 'use_pinecone' => $use_pinecone,
3650 - 'use_vectorstore' => $use_vectorstore,
3651 - 'multibot_active' => $multibot_active,
3652 - 'current_bot_id' => $current_bot_id,
3653 - 'pdf_status' => $pdf_status,
3654 - 'sitemap_status' => $sitemap_status,
3655 - 'is_processing' => $is_processing,
3656 - 'total_in_database' => $total_in_database ?? 0,
3657 - 'showing_recent_only' => $showing_recent_only ?? false,
3658 - );
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>
3659 2200
3660 - // Render the new sidebar-based page
3661 - 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
3662 3321 }
3663 3322
3323 +
3324 +
3325 +
3326 +
3327 +
3664 3328 /**
3665 3329 * Get bot-specific Pinecone configuration
3666 3330 * Used in the knowledge retrieval functions
3667 3331 */
@@ -3704,254 +3368,49 @@
3704 3368 echo wp_json_encode(['error' => esc_html__('You do not have sufficient permissions.', 'mxchat')]);
3705 3369 wp_die();
3706 3370 }
3707 3371 check_ajax_referer('mxchat_delete_chat_history', 'security');
3708 -
3709 - if (!isset($_POST['delete_session_ids']) || !is_array($_POST['delete_session_ids'])) {
3710 - echo wp_json_encode(['error' => esc_html__('No chat sessions selected for deletion.', 'mxchat')]);
3711 - wp_die();
3712 - }
3713 -
3714 3372 global $wpdb;
3715 3373 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
3716 - $translations_table = $wpdb->prefix . 'mxchat_transcript_translations';
3717 - $has_translations = $wpdb->get_var("SHOW TABLES LIKE '$translations_table'") === $translations_table;
3718 3374
3719 - // When true, any lead attached to these sessions is fully wiped (all their sessions,
3720 - // across the whole table). Default false: the chat rows go away but the lead is
3721 - // preserved as a separate "chat deleted" lead in the Leads tab.
3722 - $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;
3723 3377
3724 - $deleted_count = 0;
3725 - $preserved_as_deleted_leads = 0;
3726 - $emails_to_fully_wipe = [];
3378 + foreach ($_POST['delete_session_ids'] as $session_id) {
3379 + $session_id_sanitized = sanitize_text_field($session_id);
3727 3380
3728 - foreach ((array) $_POST['delete_session_ids'] as $session_id) {
3729 - $session_id_sanitized = sanitize_text_field($session_id);
3730 - if ($session_id_sanitized === '') {
3731 - continue;
3732 - }
3381 + // Clear relevant cache before deletion
3382 + $cache_key = 'chat_session_' . $session_id_sanitized;
3383 + wp_cache_delete($cache_key, 'mxchat_chat_sessions');
3733 3384
3734 - // Capture the lead info attached to this session *before* we delete the rows.
3735 - $lead_row = $wpdb->get_row($wpdb->prepare(
3736 - "SELECT user_email, user_name, MAX(timestamp) AS last_ts
3737 - FROM {$table_name}
3738 - WHERE session_id = %s AND user_email IS NOT NULL AND user_email != ''
3739 - GROUP BY user_email, user_name
3740 - ORDER BY last_ts DESC LIMIT 1",
3741 - $session_id_sanitized
3742 - ));
3385 + // Perform the deletion from the database table
3386 + $wpdb->delete($table_name, ['session_id' => $session_id_sanitized]);
3743 3387
3744 - wp_cache_delete('chat_session_' . $session_id_sanitized, 'mxchat_chat_sessions');
3745 - $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);
3746 3390
3747 - if ($has_translations) {
3748 - $wpdb->delete($translations_table, ['session_id' => $session_id_sanitized]);
3749 - }
3391 + // Delete any associated metadata options
3392 + delete_option("mxchat_email_" . $session_id_sanitized);
3393 + delete_option("mxchat_agent_name_" . $session_id_sanitized);
3750 3394
3751 - delete_option('mxchat_history_' . $session_id_sanitized);
3752 - delete_option('mxchat_agent_name_' . $session_id_sanitized);
3753 -
3754 - if ($lead_row && !empty($lead_row->user_email)) {
3755 - if ($also_delete_lead) {
3756 - // Full-wipe requested — queue the email so that all their sessions and
3757 - // related options get swept below. Also clear this session's pre-chat
3758 - // capture options (they're no longer meaningful).
3759 - $emails_to_fully_wipe[strtolower($lead_row->user_email)] = $lead_row->user_email;
3760 - delete_option('mxchat_email_' . $session_id_sanitized);
3761 - delete_option('mxchat_name_' . $session_id_sanitized);
3762 - } else {
3763 - // Preserve the lead in a "Chat deleted" state via distinct option keys so
3764 - // they stay out of the orphan bucket (orphan = pre-chat form dropoff).
3765 - update_option('mxchat_lead_del_email_' . $session_id_sanitized, $lead_row->user_email, false);
3766 - if (!empty($lead_row->user_name)) {
3767 - update_option('mxchat_lead_del_name_' . $session_id_sanitized, $lead_row->user_name, false);
3768 - }
3769 - if (!empty($lead_row->last_ts)) {
3770 - update_option('mxchat_lead_del_ts_' . $session_id_sanitized, $lead_row->last_ts, false);
3771 - }
3772 - // Clean up pre-chat capture options for this session — chat_deleted supersedes.
3773 - delete_option('mxchat_email_' . $session_id_sanitized);
3774 - delete_option('mxchat_name_' . $session_id_sanitized);
3775 - $preserved_as_deleted_leads++;
3776 - }
3777 - } else {
3778 - // No lead attached — nothing to preserve. Clean up any orphan options anyway.
3779 - delete_option('mxchat_email_' . $session_id_sanitized);
3780 - delete_option('mxchat_name_' . $session_id_sanitized);
3395 + $deleted_count++;
3781 3396 }
3782 3397
3783 - $deleted_count++;
3784 - }
3398 + // Optionally, clear a general cache if you have one
3399 + wp_cache_delete('all_chat_sessions', 'mxchat_chat_sessions');
3785 3400
3786 - // Opt-in full-lead wipe: sweep every remaining row + every option key (including
3787 - // chat_deleted preservation) for each affected email. Reuses the same internal
3788 - // helper as the Leads-tab Delete button for consistency.
3789 - if (!empty($emails_to_fully_wipe)) {
3790 - self::mxchat_wipe_leads_by_email(array_values($emails_to_fully_wipe));
3791 - }
3792 -
3793 - wp_cache_delete('all_chat_sessions', 'mxchat_chat_sessions');
3794 -
3795 - echo wp_json_encode([
3796 - 'success' => sprintf(
3797 - esc_html__('%d chat session(s) have been deleted.', 'mxchat'),
3798 - $deleted_count
3799 - ),
3800 - 'preserved_as_deleted_leads' => $preserved_as_deleted_leads,
3801 - 'leads_fully_wiped' => count($emails_to_fully_wipe),
3802 - ]);
3803 - wp_die();
3804 -}
3805 -
3806 -/**
3807 - * Format transcript message content with markdown processing
3808 - * Converts markdown links, bold, italic, code blocks, and plain URLs to HTML
3809 - */
3810 -private function format_transcript_message($text) {
3811 - if (empty($text)) {
3812 - return '';
3813 - }
3814 -
3815 - // Normalize line endings and clean up excessive whitespace
3816 - $text = str_replace("\r\n", "\n", $text);
3817 - $text = str_replace("\r", "\n", $text);
3818 -
3819 - // Clean up existing <br> tags that may have been saved (legacy data)
3820 - // Convert <br>, <br/>, <br /> back to newlines for consistent processing
3821 - $text = preg_replace('/<br\s*\/?>\s*/i', "\n", $text);
3822 -
3823 - // Collapse 3+ consecutive newlines to just 2 (paragraph break)
3824 - $text = preg_replace('/\n{3,}/', "\n\n", $text);
3825 -
3826 - // Process markdown headers (# Header)
3827 - $text = preg_replace_callback('/^(#{1,6})\s+(.+)$/m', function($matches) {
3828 - $level = strlen($matches[1]);
3829 - $content = esc_html(trim($matches[2]));
3830 - return "<h{$level}>{$content}</h{$level}>";
3831 - }, $text);
3832 -
3833 - // Process code blocks with triple backticks
3834 - $text = preg_replace_callback('/```(\w+)?\n?([\s\S]*?)```/', function($matches) {
3835 - $language = !empty($matches[1]) ? ' class="language-' . esc_attr($matches[1]) . '"' : '';
3836 - $code = esc_html($matches[2]);
3837 - return "<pre><code{$language}>{$code}</code></pre>";
3838 - }, $text);
3839 -
3840 - // Process inline code with single backticks
3841 - $text = preg_replace('/`([^`]+)`/', '<code>$1</code>', $text);
3842 -
3843 - // Process bold text **text** or __text__
3844 - $text = preg_replace('/\*\*(.+?)\*\*/', '<strong>$1</strong>', $text);
3845 - $text = preg_replace('/__(.+?)__/', '<strong>$1</strong>', $text);
3846 -
3847 - // Process italic text *text* or _text_ (but not if part of URL)
3848 - $text = preg_replace('/(?<![*_\w])\*([^*]+)\*(?![*\w])/', '<em>$1</em>', $text);
3849 - $text = preg_replace('/(?<![*_\w])_([^_]+)_(?![*\w])/', '<em>$1</em>', $text);
3850 -
3851 - // Process markdown links [text](url)
3852 - $text = preg_replace_callback('/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/', function($matches) {
3853 - $link_text = esc_html($matches[1]);
3854 - $url = esc_url($matches[2]);
3855 - return "<a href=\"{$url}\" target=\"_blank\" rel=\"noopener\">{$link_text}</a>";
3856 - }, $text);
3857 -
3858 - // Process citation-style brackets [URL]
3859 - $text = preg_replace_callback('/\[(https?:\/\/[^\]]+)\]/', function($matches) {
3860 - $url = esc_url($matches[1]);
3861 - return "<a href=\"{$url}\" target=\"_blank\" rel=\"noopener\">{$url}</a>";
3862 - }, $text);
3863 -
3864 - // Process standalone URLs (not already in links or img src)
3865 - $text = preg_replace_callback(
3866 - '/(?<!href="|src="|">)(https?:\/\/[^\s<>"]+)(?![^<]*<\/a>)/',
3867 - function($matches) {
3868 - $url = esc_url($matches[1]);
3869 - // Truncate display URL if too long
3870 - $display = strlen($matches[1]) > 50 ? substr($matches[1], 0, 47) . '...' : $matches[1];
3871 - return "<a href=\"{$url}\" target=\"_blank\" rel=\"noopener\">{$display}</a>";
3872 - },
3873 - $text
3874 - );
3875 -
3876 - // Process mailto links
3877 - $text = preg_replace_callback('/\[([^\]]+)\]\((mailto:[^)]+)\)/', function($matches) {
3878 - $link_text = esc_html($matches[1]);
3879 - $mailto = esc_url($matches[2]);
3880 - return "<a href=\"{$mailto}\">{$link_text}</a>";
3881 - }, $text);
3882 -
3883 - // Convert paragraphs: split by double newlines, wrap in <p> tags
3884 - // This creates proper paragraph structure instead of excessive <br> tags
3885 - $paragraphs = preg_split('/\n\n+/', $text);
3886 -
3887 - // Filter out empty paragraphs but preserve content like "0"
3888 - $paragraphs = array_values(array_filter(array_map('trim', $paragraphs), function($p) {
3889 - return $p !== '';
3890 - }));
3891 -
3892 - if (empty($paragraphs)) {
3893 - // No content after filtering
3894 - return '';
3895 - } elseif (count($paragraphs) > 1) {
3896 - // Multiple paragraphs - wrap each in <p> tags, convert single newlines to <br>
3897 - $formatted_paragraphs = array_map(function($p) {
3898 - return nl2br($p);
3899 - }, $paragraphs);
3900 - $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 + ]);
3901 3407 } else {
3902 - // Single paragraph - just convert newlines to <br>
3903 - $text = nl2br($paragraphs[0]);
3408 + echo wp_json_encode(['error' => esc_html__('No chat sessions selected for deletion.', 'mxchat')]);
3904 3409 }
3905 3410
3906 - return $text;
3907 -}
3908 -
3909 -/**
3910 - * AJAX handler to fetch RAG context for a specific message
3911 - * Used by the transcript viewer to show retrieved documents
3912 - */
3913 -public function mxchat_get_rag_context() {
3914 - // Check permissions
3915 - if (!current_user_can('manage_options')) {
3916 - wp_send_json_error(['message' => esc_html__('You do not have sufficient permissions.', 'mxchat')]);
3917 - wp_die();
3918 - }
3919 -
3920 - // Validate message ID
3921 - if (!isset($_POST['message_id']) || empty($_POST['message_id'])) {
3922 - wp_send_json_error(['message' => esc_html__('Message ID is required.', 'mxchat')]);
3923 - wp_die();
3924 - }
3925 -
3926 - $message_id = absint($_POST['message_id']);
3927 -
3928 - global $wpdb;
3929 - $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
3930 -
3931 - // Fetch the RAG context for this message
3932 - $result = $wpdb->get_row($wpdb->prepare(
3933 - "SELECT rag_context FROM {$table_name} WHERE id = %d",
3934 - $message_id
3935 - ));
3936 -
3937 - if (!$result || empty($result->rag_context)) {
3938 - wp_send_json_error(['message' => esc_html__('No RAG context found for this message.', 'mxchat')]);
3939 - wp_die();
3940 - }
3941 -
3942 - // Decode the JSON data
3943 - $rag_context = json_decode($result->rag_context, true);
3944 -
3945 - if (json_last_error() !== JSON_ERROR_NONE) {
3946 - wp_send_json_error(['message' => esc_html__('Invalid RAG context data.', 'mxchat')]);
3947 - wp_die();
3948 - }
3949 -
3950 - wp_send_json_success($rag_context);
3951 3411 wp_die();
3952 3412 }
3953 -
3954 3413 public function display_admin_notices() {
3955 3414 // Check if we're on a MXChat admin page
3956 3415 $screen = get_current_screen();
3957 3416 if (!$screen || strpos($screen->base, 'mxchat') === false) {
@@ -3957,44 +3416,173 @@
3957 3416 if (!$screen || strpos($screen->base, 'mxchat') === false) {
3958 3417 return;
3959 3418 }
3960 3419
3961 - $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);
3962 3421
3963 3422 // Check for error notices
3964 3423 $error_notice = get_transient('mxchat_admin_notice_error');
3965 3424 if ($error_notice) {
3966 - 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>';
3967 3427 delete_transient('mxchat_admin_notice_error');
3428 + //error_log('Displayed and deleted error transient');
3429 + } else {
3430 + //error_log('No error transient found');
3968 3431 }
3969 3432
3970 3433 // Check for success notices
3971 3434 $success_notice = get_transient('mxchat_admin_notice_success');
3972 3435 if ($success_notice) {
3973 - 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>';
3974 3438 delete_transient('mxchat_admin_notice_success');
3439 + //error_log('Displayed and deleted success transient');
3975 3440 }
3976 3441
3977 3442 // Check for info notices
3978 3443 $info_notice = get_transient('mxchat_admin_notice_info');
3979 3444 if ($info_notice) {
3980 - 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>';
3981 3447 delete_transient('mxchat_admin_notice_info');
3448 + //error_log('Displayed and deleted info transient');
3982 3449 }
3450 +
3983 3451 }
3984 3452
3985 3453
3986 3454 public function mxchat_create_activation_page() {
3987 - // Include the new Pro & Extensions page template
3988 - 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);
3989 3458
3990 - // Get addons configuration from the MxChat_Addons class
3991 - require_once plugin_dir_path(__FILE__) . 'class-mxchat-addons.php';
3992 - $addons_instance = new MxChat_Addons();
3993 - $addons_config = $addons_instance->get_addons_config();
3459 + $license_management_url = 'https://mxchat.ai/my-account/orders/';
3994 3460
3995 - // Render the consolidated Pro & Extensions page
3996 - 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
3997 3585 }
3998 3586
3999 3587 /**
4000 3588 * Check if current activation is linked to a domain
@@ -3999,9 +3587,9 @@
3999 3587 /**
4000 3588 * Check if current activation is linked to a domain
4001 3589 * This checks YOUR website's database, not the user's local database
4002 3590 */
4003 -public function is_current_activation_linked($domain) {
3591 +private function is_current_activation_linked($domain) {
4004 3592 $license_key = get_option('mxchat_activation_key');
4005 3593 $email = get_option('mxchat_pro_email');
4006 3594
4007 3595 if (empty($license_key) || empty($email)) {
@@ -4032,54 +3620,50 @@
4032 3620 if (!current_user_can('manage_options')) {
4033 3621 return;
4034 3622 }
4035 3623
3624 + // Keep existing data fetching logic
4036 3625 global $wpdb;
4037 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;
4038 3630
4039 - // Get stats for dashboard
4040 - $total_actions = $wpdb->get_var("SELECT COUNT(*) FROM $table_name");
4041 - $enabled_actions = $wpdb->get_var("SELECT COUNT(*) FROM $table_name WHERE enabled = 1");
4042 - $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 + }
4043 3637
4044 - // Get unique action types count
4045 - $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']) : '';
4046 3642
4047 - // Get action type distribution
4048 - $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");
4049 - $available_callbacks = $this->mxchat_get_available_callbacks();
4050 - $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 + }
4051 3648
4052 - $action_type_distribution = array();
4053 - foreach ($type_distribution_raw as $row) {
4054 - $label = isset($available_callbacks[$row->callback_function]['label'])
4055 - ? $available_callbacks[$row->callback_function]['label']
4056 - : $row->callback_function;
4057 - $action_type_distribution[$label] = $row->count;
3649 + if ($callback_filter) {
3650 + $where .= $wpdb->prepare(' AND callback_function = %s', $callback_filter);
4058 3651 }
4059 3652
4060 - // Prepare page data
4061 - $page_data = array(
4062 - 'total_actions' => $total_actions,
4063 - 'enabled_actions' => $enabled_actions,
4064 - 'disabled_actions' => $disabled_actions,
4065 - 'action_types_count' => $action_types_count,
4066 - 'action_type_distribution' => $action_type_distribution,
4067 - 'available_callbacks' => $available_callbacks,
4068 - 'callback_groups' => $callback_groups,
4069 - );
3653 + // Pagination
3654 + $total_intents = $wpdb->get_var("SELECT COUNT(*) FROM $table_name WHERE $where");
3655 + $total_pages = ceil($total_intents / $per_page);
4070 3656
4071 - // Include and render the new template
4072 - require_once plugin_dir_path(__FILE__) . 'admin-actions-page.php';
4073 - mxchat_render_actions_page($this, $page_data);
4074 -}
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 + ));
4075 3662
4076 -/**
4077 - * LEGACY HTML - Preserved below for reference, to be removed in future update
4078 - */
4079 -function mxchat_actions_page_legacy_html() {
4080 - // This function is deprecated and no longer used
4081 - // The new template is in includes/admin-actions-page.php
3663 + // Get callbacks
3664 + $available_callbacks = $this->mxchat_get_available_callbacks();
3665 +
4082 3666 ?>
4083 3667 <div class="wrap mxchat-wrapper">
4084 3668 <!-- Hero Section -->
4085 3669 <div class="mxchat-hero">
@@ -4355,14 +3939,14 @@
4355 3939 $category_slug = sanitize_title($group_label);
4356 3940
4357 3941 foreach ($group_callbacks as $function => $data) :
4358 3942 $label = $data['label'];
3943 + $pro_only = $data['pro_only'];
4359 3944 $icon = isset($data['icon']) ? $data['icon'] : 'admin-generic';
4360 3945 $description = isset($data['description']) ? $data['description'] : '';
4361 3946 $is_addon = isset($data['addon']) && $data['addon'] !== false;
4362 3947 $addon_name = isset($data['addon_name']) ? $data['addon_name'] : '';
4363 3948 $is_installed = isset($data['installed']) ? $data['installed'] : true;
4364 - $is_promo = !empty($data['addon_promo']) && !$is_installed;
4365 3949
4366 3950 // Determine card status and styling
4367 3951 $card_class = 'mxchat-action-type-card';
4368 3952 $icon_class = 'mxchat-action-type-icon';
@@ -4367,13 +3951,15 @@
4367 3951 $card_class = 'mxchat-action-type-card';
4368 3952 $icon_class = 'mxchat-action-type-icon';
4369 3953 $status_badge = '';
4370 3954
4371 - if ($is_promo) {
4372 - // Promotional card — not selectable, just informational
4373 - $card_class .= ' not-installed mxchat-promo-card';
4374 - $status_badge = '<span class="mxchat-addon-badge">' . esc_html__('Add-on Required', 'mxchat') . '</span>';
4375 - } 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) {
4376 3962 // Add-on not installed
4377 3963 $card_class .= ' not-installed';
4378 3964 $status_badge .= '<span class="mxchat-addon-badge">' . esc_html__('Add-on Required', 'mxchat') . '</span>';
4379 3965 }
@@ -4387,16 +3973,13 @@
4387 3973 }
4388 3974 ?>
4389 3975 <div class="<?php echo esc_attr($card_class); ?>"
4390 3976 data-category="<?php echo esc_attr($category_slug); ?>"
4391 - <?php if (!$is_promo) : ?>
4392 3977 data-value="<?php echo esc_attr($function); ?>"
4393 3978 data-label="<?php echo esc_attr($label); ?>"
4394 - <?php endif; ?>
4395 - data-pro="false"
3979 + data-pro="<?php echo $pro_only ? 'true' : 'false'; ?>"
4396 3980 data-addon="<?php echo esc_attr($is_addon ? $data['addon'] : ''); ?>"
4397 - data-installed="<?php echo $is_installed ? 'true' : 'false'; ?>"
4398 - <?php if ($is_promo) : ?>data-promo="true"<?php endif; ?>>
3981 + data-installed="<?php echo $is_installed ? 'true' : 'false'; ?>">
4399 3982 <div class="<?php echo esc_attr($icon_class); ?>">
4400 3983 <span class="dashicons dashicons-<?php echo esc_attr($icon); ?>"></span>
4401 3984 </div>
4402 3985 <div class="mxchat-action-type-info">
@@ -4405,15 +3988,14 @@
4405 3988 <?php if (!empty($status_badge)) : ?>
4406 3989 <?php echo $status_badge; ?>
4407 3990 <?php endif; ?>
4408 3991
4409 - <?php if ($is_promo || ($is_addon && !$is_installed)) : ?>
3992 + <?php if ($is_addon && !$is_installed) : ?>
4410 3993 <div class="mxchat-addon-info">
4411 3994 <?php echo esc_html(sprintf(
4412 3995 __('Requires %s', 'mxchat'),
4413 3996 $addon_name
4414 3997 )); ?>
4415 - — <a href="https://mxchat.ai/" target="_blank"><?php esc_html_e('Get Add-on', 'mxchat'); ?></a>
4416 3998 </div>
4417 3999 <?php endif; ?>
4418 4000 </div>
4419 4001 </div>
@@ -4676,9 +4258,9 @@
4676 4258 'intent_label' => $intent_label,
4677 4259 'phrases' => implode(', ', $phrases_array),
4678 4260 'embedding_vector' => $serialized_vector,
4679 4261 'similarity_threshold' => $similarity_threshold,
4680 - 'enabled_bots' => $enabled_bots_json, // Include enabled_bots in update
4262 + 'enabled_bots' => $enabled_bots_json // Include enabled_bots in update
4681 4263 ),
4682 4264 array('id' => $intent_id),
4683 4265 array('%s', '%s', '%s', '%f', '%s'), // Format: string, string, string, float, string
4684 4266 array('%d') // Where format: integer
@@ -4740,15 +4322,12 @@
4740 4322 $this->handle_embedding_error(__('Invalid callback function selected.', 'mxchat'));
4741 4323 return;
4742 4324 }
4743 4325
4744 - // Check if this is an add-on promotional placeholder (not a real action)
4745 - if (!empty($available_callbacks[$callback_function]['addon_promo'])) {
4746 - $addon_name = isset($available_callbacks[$callback_function]['addon_name']) ? $available_callbacks[$callback_function]['addon_name'] : __('an add-on', 'mxchat');
4747 - $this->handle_embedding_error(sprintf(
4748 - __('This action requires the %s to be installed and activated.', 'mxchat'),
4749 - $addon_name
4750 - ));
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'));
4751 4330 return;
4752 4331 }
4753 4332
4754 4333 // Process phrases
@@ -4795,9 +4374,9 @@
4795 4374 'phrases' => implode(', ', $phrases_array),
4796 4375 'embedding_vector' => $serialized_vector,
4797 4376 'callback_function' => $callback_function,
4798 4377 'similarity_threshold' => $similarity_threshold,
4799 - 'enabled_bots' => $enabled_bots_json, // NEW field
4378 + 'enabled_bots' => $enabled_bots_json // NEW field
4800 4379 ]);
4801 4380
4802 4381 if ($result === false) {
4803 4382 $this->handle_embedding_error(__('Database error: ', 'mxchat') . $wpdb->last_error);
@@ -4829,577 +4408,10 @@
4829 4408 exit;
4830 4409 }
4831 4410 }
4832 4411
4833 -/**
4834 - * AJAX handler to fetch actions list for the new split-panel UI
4835 - */
4836 -public function mxchat_fetch_actions_list() {
4837 - check_ajax_referer('mxchat_actions_nonce', 'security');
4838 4412
4839 - if (!current_user_can('manage_options')) {
4840 - wp_send_json_error(__('Unauthorized', 'mxchat'));
4841 - }
4842 -
4843 - global $wpdb;
4844 - $table_name = $wpdb->prefix . 'mxchat_intents';
4845 -
4846 - $page = isset($_POST['page']) ? max(1, intval($_POST['page'])) : 1;
4847 - $per_page = isset($_POST['per_page']) ? min(100, max(1, intval($_POST['per_page']))) : 50;
4848 - $offset = ($page - 1) * $per_page;
4849 - $search = isset($_POST['search']) ? sanitize_text_field($_POST['search']) : '';
4850 - $callback_filter = isset($_POST['callback_filter']) ? sanitize_text_field($_POST['callback_filter']) : '';
4851 - $sort_order = isset($_POST['sort_order']) && $_POST['sort_order'] === 'asc' ? 'ASC' : 'DESC';
4852 -
4853 - // Build WHERE clause
4854 - $where = '1=1';
4855 - $params = array();
4856 -
4857 - if ($search) {
4858 - $search_like = '%' . $wpdb->esc_like($search) . '%';
4859 - $where .= ' AND (intent_label LIKE %s OR phrases LIKE %s)';
4860 - $params[] = $search_like;
4861 - $params[] = $search_like;
4862 - }
4863 -
4864 - if ($callback_filter) {
4865 - $where .= ' AND callback_function = %s';
4866 - $params[] = $callback_filter;
4867 - }
4868 -
4869 - // Get total count
4870 - $count_query = "SELECT COUNT(*) FROM $table_name WHERE $where";
4871 - if (!empty($params)) {
4872 - $count_query = $wpdb->prepare($count_query, $params);
4873 - }
4874 - $total_actions = $wpdb->get_var($count_query);
4875 -
4876 - // Get actions
4877 - $query = "SELECT * FROM $table_name WHERE $where ORDER BY id $sort_order LIMIT %d OFFSET %d";
4878 - $all_params = array_merge($params, array($per_page, $offset));
4879 - $actions = $wpdb->get_results($wpdb->prepare($query, $all_params));
4880 -
4881 - // Get available callbacks for labels/icons
4882 - $available_callbacks = $this->mxchat_get_available_callbacks();
4883 -
4884 - // Prefetch individual phrase counts for all fetched actions
4885 - $phrases_table = $wpdb->prefix . 'mxchat_intent_phrases';
4886 - $phrase_counts = array();
4887 - if ($wpdb->get_var("SHOW TABLES LIKE '$phrases_table'") === $phrases_table) {
4888 - $action_ids = wp_list_pluck($actions, 'id');
4889 - if (!empty($action_ids)) {
4890 - $id_placeholders = implode(',', array_fill(0, count($action_ids), '%d'));
4891 - $count_results = $wpdb->get_results($wpdb->prepare(
4892 - "SELECT intent_id, COUNT(*) as cnt FROM $phrases_table WHERE intent_id IN ($id_placeholders) GROUP BY intent_id",
4893 - $action_ids
4894 - ));
4895 - foreach ($count_results as $row) {
4896 - $phrase_counts[$row->intent_id] = intval($row->cnt);
4897 - }
4898 - }
4899 - }
4900 -
4901 - // Format actions for response
4902 - $formatted_actions = array();
4903 - foreach ($actions as $action) {
4904 - $callback_data = isset($available_callbacks[$action->callback_function])
4905 - ? $available_callbacks[$action->callback_function]
4906 - : array('label' => $action->callback_function, 'icon' => 'admin-generic');
4907 -
4908 - $enabled_bots = json_decode($action->enabled_bots, true);
4909 - if (!is_array($enabled_bots)) {
4910 - $enabled_bots = array('default');
4911 - }
4912 -
4913 - $formatted_actions[] = array(
4914 - 'id' => intval($action->id),
4915 - 'label' => $action->intent_label,
4916 - 'phrases' => $action->phrases,
4917 - 'callback_function' => $action->callback_function,
4918 - 'callback_label' => $callback_data['label'],
4919 - 'icon' => isset($callback_data['icon']) ? $callback_data['icon'] : 'admin-generic',
4920 - 'threshold' => round($action->similarity_threshold * 100),
4921 - 'enabled' => (bool) $action->enabled,
4922 - 'enabled_bots' => $enabled_bots,
4923 - 'has_legacy_vector' => !empty($action->embedding_vector),
4924 - 'individual_phrase_count' => isset($phrase_counts[$action->id]) ? $phrase_counts[$action->id] : 0,
4925 - );
4926 - }
4927 -
4928 - $total_pages = ceil($total_actions / $per_page);
4929 - $showing_start = $total_actions > 0 ? $offset + 1 : 0;
4930 - $showing_end = min($offset + $per_page, $total_actions);
4931 -
4932 - wp_send_json_success(array(
4933 - 'actions' => $formatted_actions,
4934 - 'page' => $page,
4935 - 'per_page' => $per_page,
4936 - 'total_actions' => intval($total_actions),
4937 - 'total_pages' => $total_pages,
4938 - 'showing_start' => $showing_start,
4939 - 'showing_end' => $showing_end,
4940 - ));
4941 -}
4942 -
4943 4413 /**
4944 - * AJAX handler to toggle action enabled status
4945 - */
4946 -public function mxchat_toggle_action_status() {
4947 - check_ajax_referer('mxchat_actions_nonce', 'security');
4948 -
4949 - if (!current_user_can('manage_options')) {
4950 - wp_send_json_error(__('Unauthorized', 'mxchat'));
4951 - }
4952 -
4953 - $action_id = isset($_POST['action_id']) ? intval($_POST['action_id']) : 0;
4954 - $enabled = isset($_POST['enabled']) ? intval($_POST['enabled']) : 0;
4955 -
4956 - if (!$action_id) {
4957 - wp_send_json_error(__('Invalid action ID', 'mxchat'));
4958 - }
4959 -
4960 - global $wpdb;
4961 - $table_name = $wpdb->prefix . 'mxchat_intents';
4962 -
4963 - $result = $wpdb->update(
4964 - $table_name,
4965 - array('enabled' => $enabled ? 1 : 0),
4966 - array('id' => $action_id),
4967 - array('%d'),
4968 - array('%d')
4969 - );
4970 -
4971 - if ($result === false) {
4972 - wp_send_json_error(__('Failed to update action status', 'mxchat'));
4973 - }
4974 -
4975 - wp_send_json_success(array('enabled' => (bool) $enabled));
4976 -}
4977 -
4978 -/**
4979 - * AJAX handler to bulk delete actions
4980 - */
4981 -public function mxchat_bulk_delete_actions() {
4982 - check_ajax_referer('mxchat_delete_intent_nonce', 'security');
4983 -
4984 - if (!current_user_can('manage_options')) {
4985 - wp_send_json_error(__('Unauthorized', 'mxchat'));
4986 - }
4987 -
4988 - $action_ids = isset($_POST['action_ids']) ? array_map('intval', (array) $_POST['action_ids']) : array();
4989 -
4990 - if (empty($action_ids)) {
4991 - wp_send_json_error(__('No actions selected', 'mxchat'));
4992 - }
4993 -
4994 - global $wpdb;
4995 - $table_name = $wpdb->prefix . 'mxchat_intents';
4996 -
4997 - $placeholders = implode(',', array_fill(0, count($action_ids), '%d'));
4998 - $query = $wpdb->prepare("DELETE FROM $table_name WHERE id IN ($placeholders)", $action_ids);
4999 - $result = $wpdb->query($query);
5000 -
5001 - if ($result === false) {
5002 - wp_send_json_error(__('Failed to delete actions', 'mxchat'));
5003 - }
5004 -
5005 - // Also delete individual phrases for these intents
5006 - $phrases_table = $wpdb->prefix . 'mxchat_intent_phrases';
5007 - if ($wpdb->get_var("SHOW TABLES LIKE '$phrases_table'") === $phrases_table) {
5008 - $wpdb->query($wpdb->prepare("DELETE FROM $phrases_table WHERE intent_id IN ($placeholders)", $action_ids));
5009 - }
5010 -
5011 - wp_send_json_success(array('deleted' => $result));
5012 -}
5013 -
5014 -/**
5015 - * AJAX handler to add a new intent/action
5016 - */
5017 -public function mxchat_add_intent_ajax() {
5018 - check_ajax_referer('mxchat_add_intent_nonce', 'security');
5019 -
5020 - if (!current_user_can('manage_options')) {
5021 - wp_send_json_error(__('Unauthorized', 'mxchat'));
5022 - }
5023 -
5024 - global $wpdb;
5025 - $table_name = $wpdb->prefix . 'mxchat_intents';
5026 -
5027 - // Sanitize input
5028 - $intent_label = isset($_POST['intent_label']) ? sanitize_text_field($_POST['intent_label']) : '';
5029 - $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
5030 - $callback_function = isset($_POST['callback_function']) ? sanitize_text_field($_POST['callback_function']) : '';
5031 - $similarity_threshold = isset($_POST['similarity_threshold']) ? floatval($_POST['similarity_threshold']) / 100 : 0.85;
5032 - $enabled_bots = isset($_POST['enabled_bots']) ? array_map('sanitize_text_field', (array) $_POST['enabled_bots']) : array('default');
5033 -
5034 - // Validate
5035 - // Check if using new individual phrases mode or legacy mode
5036 - $individual_phrases = isset($_POST['individual_phrases']) ? array_filter(array_map('sanitize_text_field', (array) $_POST['individual_phrases'])) : array();
5037 - $use_individual = !empty($individual_phrases);
5038 -
5039 - // Validate required fields (phrases not required when using individual mode)
5040 - if (empty($intent_label) || empty($callback_function)) {
5041 - wp_send_json_error(__('Please fill in all required fields.', 'mxchat'));
5042 - }
5043 - if (!$use_individual && empty($phrases_input)) {
5044 - wp_send_json_error(__('Please fill in all required fields.', 'mxchat'));
5045 - }
5046 -
5047 - // Ensure default bot is included
5048 - if (!in_array('default', $enabled_bots)) {
5049 - $enabled_bots[] = 'default';
5050 - }
5051 - $enabled_bots_json = json_encode($enabled_bots);
5052 -
5053 - if ($use_individual) {
5054 - // New mode: individual phrases each get their own vector
5055 - // Insert the intent row with empty legacy fields
5056 - $result = $wpdb->insert(
5057 - $table_name,
5058 - array(
5059 - 'intent_label' => $intent_label,
5060 - 'phrases' => '',
5061 - 'embedding_vector' => '',
5062 - 'similarity_threshold' => $similarity_threshold,
5063 - 'callback_function' => $callback_function,
5064 - 'enabled' => 1,
5065 - 'enabled_bots' => $enabled_bots_json,
5066 - )
5067 - );
5068 -
5069 - if ($result === false) {
5070 - wp_send_json_error(__('Failed to add action to database.', 'mxchat'));
5071 - }
5072 -
5073 - $intent_id = $wpdb->insert_id;
5074 -
5075 - // Insert each phrase individually with its own embedding
5076 - $phrases_table = $wpdb->prefix . 'mxchat_intent_phrases';
5077 - $failed_phrases = array();
5078 - foreach ($individual_phrases as $phrase) {
5079 - $phrase = trim($phrase);
5080 - if (empty($phrase)) continue;
5081 -
5082 - $embedding_vector = $this->mxchat_generate_embedding($phrase);
5083 - if (is_wp_error($embedding_vector)) {
5084 - $failed_phrases[] = $phrase;
5085 - continue;
5086 - }
5087 -
5088 - $wpdb->insert(
5089 - $phrases_table,
5090 - array(
5091 - 'intent_id' => $intent_id,
5092 - 'phrase' => $phrase,
5093 - 'embedding_vector' => maybe_serialize($embedding_vector),
5094 - )
5095 - );
5096 - }
5097 -
5098 - $response = array('id' => $intent_id);
5099 - if (!empty($failed_phrases)) {
5100 - $response['failed_phrases'] = $failed_phrases;
5101 - }
5102 - wp_send_json_success($response);
5103 -
5104 - } else {
5105 - // Legacy mode: combine all phrases into one embedding (backwards compatible)
5106 - $phrases_array = array_filter(array_map('trim', preg_split('/[\n,]+/', $phrases_input)));
5107 - if (empty($phrases_array)) {
5108 - wp_send_json_error(__('Please provide at least one trigger phrase.', 'mxchat'));
5109 - }
5110 -
5111 - // Generate embedding (combine phrases into single string for embedding)
5112 - $embedding_vector = $this->mxchat_generate_embedding(implode(' ', $phrases_array));
5113 - if (is_wp_error($embedding_vector)) {
5114 - // Fallback: store without embedding
5115 - $serialized_vector = null;
5116 - } else {
5117 - $serialized_vector = maybe_serialize($embedding_vector);
5118 - }
5119 -
5120 - // Insert
5121 - $result = $wpdb->insert(
5122 - $table_name,
5123 - array(
5124 - 'intent_label' => $intent_label,
5125 - 'phrases' => implode(', ', $phrases_array),
5126 - 'embedding_vector' => $serialized_vector,
5127 - 'similarity_threshold' => $similarity_threshold,
5128 - 'callback_function' => $callback_function,
5129 - 'enabled' => 1,
5130 - 'enabled_bots' => $enabled_bots_json,
5131 - )
5132 - );
5133 -
5134 - if ($result === false) {
5135 - wp_send_json_error(__('Failed to add action to database.', 'mxchat'));
5136 - }
5137 -
5138 - wp_send_json_success(array('id' => $wpdb->insert_id));
5139 - }
5140 -}
5141 -
5142 -/**
5143 - * AJAX handler to edit an existing intent/action
5144 - */
5145 -public function mxchat_edit_intent_ajax() {
5146 - check_ajax_referer('mxchat_edit_intent', 'security');
5147 -
5148 - if (!current_user_can('manage_options')) {
5149 - wp_send_json_error(__('Unauthorized', 'mxchat'));
5150 - }
5151 -
5152 - global $wpdb;
5153 - $table_name = $wpdb->prefix . 'mxchat_intents';
5154 -
5155 - // Sanitize input
5156 - $intent_id = isset($_POST['intent_id']) ? intval($_POST['intent_id']) : 0;
5157 - $intent_label = isset($_POST['intent_label']) ? sanitize_text_field($_POST['intent_label']) : '';
5158 - $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
5159 - $callback_function = isset($_POST['callback_function']) ? sanitize_text_field($_POST['callback_function']) : '';
5160 - $similarity_threshold = isset($_POST['similarity_threshold']) ? floatval($_POST['similarity_threshold']) / 100 : 0.85;
5161 - $enabled_bots = isset($_POST['enabled_bots']) ? array_map('sanitize_text_field', (array) $_POST['enabled_bots']) : array('default');
5162 -
5163 - // Validate
5164 - if (!$intent_id || empty($intent_label)) {
5165 - wp_send_json_error(__('Please fill in all required fields.', 'mxchat'));
5166 - }
5167 -
5168 - // Check if phrases are managed individually (empty phrases_input means individual mode)
5169 - $uses_individual_phrases = empty($phrases_input);
5170 -
5171 - if ($uses_individual_phrases) {
5172 - // Individual phrase mode: only update non-phrase fields, skip embedding regeneration
5173 - $update_data = array(
5174 - 'intent_label' => $intent_label,
5175 - 'similarity_threshold' => $similarity_threshold,
5176 - 'callback_function' => $callback_function,
5177 - 'enabled_bots' => json_encode($enabled_bots),
5178 - );
5179 - } else {
5180 - // Legacy mode: process phrases and regenerate embedding (backwards compatible for add-ons)
5181 - $phrases_array = array_filter(array_map('trim', preg_split('/[\n,]+/', $phrases_input)));
5182 - if (empty($phrases_array)) {
5183 - wp_send_json_error(__('Please provide at least one trigger phrase.', 'mxchat'));
5184 - }
5185 -
5186 - // Generate new embedding (combine phrases into single string for embedding)
5187 - $embedding_vector = $this->mxchat_generate_embedding(implode(' ', $phrases_array));
5188 - if (is_wp_error($embedding_vector)) {
5189 - // Keep existing embedding
5190 - $update_data = array(
5191 - 'intent_label' => $intent_label,
5192 - 'phrases' => implode(', ', $phrases_array),
5193 - 'similarity_threshold' => $similarity_threshold,
5194 - 'callback_function' => $callback_function,
5195 - 'enabled_bots' => json_encode($enabled_bots),
5196 - );
5197 - } else {
5198 - $serialized_vector = maybe_serialize($embedding_vector);
5199 - $update_data = array(
5200 - 'intent_label' => $intent_label,
5201 - 'phrases' => implode(', ', $phrases_array),
5202 - 'embedding_vector' => $serialized_vector,
5203 - 'similarity_threshold' => $similarity_threshold,
5204 - 'callback_function' => $callback_function,
5205 - 'enabled_bots' => json_encode($enabled_bots),
5206 - );
5207 - }
5208 - }
5209 -
5210 - // Ensure default bot is included
5211 - if (!in_array('default', $enabled_bots)) {
5212 - $enabled_bots[] = 'default';
5213 - }
5214 - $update_data['enabled_bots'] = json_encode($enabled_bots);
5215 -
5216 - $result = $wpdb->update(
5217 - $table_name,
5218 - $update_data,
5219 - array('id' => $intent_id),
5220 - null,
5221 - array('%d')
5222 - );
5223 -
5224 - if ($result === false) {
5225 - wp_send_json_error(__('Failed to update action.', 'mxchat'));
5226 - }
5227 -
5228 - wp_send_json_success(array('updated' => true));
5229 -}
5230 -
5231 -/**
5232 - * AJAX handler to delete a single intent/action
5233 - */
5234 -public function mxchat_delete_intent_ajax() {
5235 - check_ajax_referer('mxchat_delete_intent_nonce', 'security');
5236 -
5237 - if (!current_user_can('manage_options')) {
5238 - wp_send_json_error(__('Unauthorized', 'mxchat'));
5239 - }
5240 -
5241 - $intent_id = isset($_POST['intent_id']) ? intval($_POST['intent_id']) : 0;
5242 -
5243 - if (!$intent_id) {
5244 - wp_send_json_error(__('Invalid action ID', 'mxchat'));
5245 - }
5246 -
5247 - global $wpdb;
5248 - $table_name = $wpdb->prefix . 'mxchat_intents';
5249 -
5250 - $result = $wpdb->delete($table_name, array('id' => $intent_id), array('%d'));
5251 -
5252 - if ($result === false) {
5253 - wp_send_json_error(__('Failed to delete action', 'mxchat'));
5254 - }
5255 -
5256 - // Also delete individual phrases for this intent
5257 - $phrases_table = $wpdb->prefix . 'mxchat_intent_phrases';
5258 - if ($wpdb->get_var("SHOW TABLES LIKE '$phrases_table'") === $phrases_table) {
5259 - $wpdb->delete($phrases_table, array('intent_id' => $intent_id), array('%d'));
5260 - }
5261 -
5262 - wp_send_json_success(array('deleted' => true));
5263 -}
5264 -
5265 -/**
5266 - * AJAX handler to add a single phrase with its own embedding to an intent
5267 - */
5268 -public function mxchat_add_phrase_ajax() {
5269 - check_ajax_referer('mxchat_add_phrase_nonce', 'security');
5270 -
5271 - if (!current_user_can('manage_options')) {
5272 - wp_send_json_error(__('Unauthorized', 'mxchat'));
5273 - }
5274 -
5275 - $intent_id = isset($_POST['intent_id']) ? intval($_POST['intent_id']) : 0;
5276 - $phrase = isset($_POST['phrase']) ? sanitize_text_field($_POST['phrase']) : '';
5277 -
5278 - if (!$intent_id || empty($phrase)) {
5279 - wp_send_json_error(__('Please provide an intent ID and phrase.', 'mxchat'));
5280 - }
5281 -
5282 - // Verify the intent exists
5283 - global $wpdb;
5284 - $intents_table = $wpdb->prefix . 'mxchat_intents';
5285 - $intent = $wpdb->get_row($wpdb->prepare("SELECT id FROM $intents_table WHERE id = %d", $intent_id));
5286 - if (!$intent) {
5287 - wp_send_json_error(__('Action not found.', 'mxchat'));
5288 - }
5289 -
5290 - // Generate embedding for this single phrase
5291 - $embedding_vector = $this->mxchat_generate_embedding($phrase);
5292 - if (is_wp_error($embedding_vector)) {
5293 - wp_send_json_error(__('Failed to generate embedding: ', 'mxchat') . $embedding_vector->get_error_message());
5294 - }
5295 -
5296 - $phrases_table = $wpdb->prefix . 'mxchat_intent_phrases';
5297 - $result = $wpdb->insert(
5298 - $phrases_table,
5299 - array(
5300 - 'intent_id' => $intent_id,
5301 - 'phrase' => $phrase,
5302 - 'embedding_vector' => maybe_serialize($embedding_vector),
5303 - )
5304 - );
5305 -
5306 - if ($result === false) {
5307 - wp_send_json_error(__('Failed to add phrase.', 'mxchat'));
5308 - }
5309 -
5310 - wp_send_json_success(array('id' => $wpdb->insert_id, 'phrase' => $phrase));
5311 -}
5312 -
5313 -/**
5314 - * AJAX handler to delete a single phrase from wp_mxchat_intent_phrases
5315 - */
5316 -public function mxchat_delete_phrase_ajax() {
5317 - check_ajax_referer('mxchat_delete_phrase_nonce', 'security');
5318 -
5319 - if (!current_user_can('manage_options')) {
5320 - wp_send_json_error(__('Unauthorized', 'mxchat'));
5321 - }
5322 -
5323 - $phrase_id = isset($_POST['phrase_id']) ? intval($_POST['phrase_id']) : 0;
5324 - if (!$phrase_id) {
5325 - wp_send_json_error(__('Invalid phrase ID.', 'mxchat'));
5326 - }
5327 -
5328 - global $wpdb;
5329 - $phrases_table = $wpdb->prefix . 'mxchat_intent_phrases';
5330 - $result = $wpdb->delete($phrases_table, array('id' => $phrase_id), array('%d'));
5331 -
5332 - if ($result === false) {
5333 - wp_send_json_error(__('Failed to delete phrase.', 'mxchat'));
5334 - }
5335 -
5336 - wp_send_json_success(array('deleted' => true));
5337 -}
5338 -
5339 -/**
5340 - * AJAX handler to fetch individual phrases for an intent
5341 - */
5342 -public function mxchat_get_phrases_ajax() {
5343 - check_ajax_referer('mxchat_get_phrases_nonce', 'security');
5344 -
5345 - if (!current_user_can('manage_options')) {
5346 - wp_send_json_error(__('Unauthorized', 'mxchat'));
5347 - }
5348 -
5349 - $intent_id = isset($_POST['intent_id']) ? intval($_POST['intent_id']) : 0;
5350 - if (!$intent_id) {
5351 - wp_send_json_error(__('Invalid intent ID.', 'mxchat'));
5352 - }
5353 -
5354 - global $wpdb;
5355 - $phrases_table = $wpdb->prefix . 'mxchat_intent_phrases';
5356 -
5357 - $phrases = array();
5358 - if ($wpdb->get_var("SHOW TABLES LIKE '$phrases_table'") === $phrases_table) {
5359 - $phrases = $wpdb->get_results($wpdb->prepare(
5360 - "SELECT id, phrase, created_at FROM $phrases_table WHERE intent_id = %d ORDER BY created_at ASC",
5361 - $intent_id
5362 - ));
5363 - }
5364 -
5365 - wp_send_json_success(array('phrases' => $phrases));
5366 -}
5367 -
5368 -/**
5369 - * AJAX handler to clear legacy phrases and embedding from the main intents table
5370 - */
5371 -public function mxchat_delete_legacy_phrases_ajax() {
5372 - check_ajax_referer('mxchat_delete_legacy_nonce', 'security');
5373 -
5374 - if (!current_user_can('manage_options')) {
5375 - wp_send_json_error(__('Unauthorized', 'mxchat'));
5376 - }
5377 -
5378 - $intent_id = isset($_POST['intent_id']) ? intval($_POST['intent_id']) : 0;
5379 - if (!$intent_id) {
5380 - wp_send_json_error(__('Invalid intent ID.', 'mxchat'));
5381 - }
5382 -
5383 - global $wpdb;
5384 - $table_name = $wpdb->prefix . 'mxchat_intents';
5385 -
5386 - $result = $wpdb->update(
5387 - $table_name,
5388 - array('phrases' => '', 'embedding_vector' => ''),
5389 - array('id' => $intent_id),
5390 - array('%s', '%s'),
5391 - array('%d')
5392 - );
5393 -
5394 - if ($result === false) {
5395 - wp_send_json_error(__('Failed to clear legacy phrases.', 'mxchat'));
5396 - }
5397 -
5398 - wp_send_json_success(array('cleared' => true));
5399 -}
5400 -
5401 -/**
5402 4414 * Enhanced get_available_callbacks function with form action exclusion
5403 4415 *
5404 4416 * @param bool $grouped Whether to return callbacks grouped by category
5405 4417 * @param bool $include_all Whether to include all potential actions (even if add-on not installed)
@@ -5437,10 +4449,9 @@
5437 4449 'mxchat_handle_product_recommendations',
5438 4450 'mxchat_handle_order_history',
5439 4451 'mxchat_show_product_card',
5440 4452 'mxchat_add_to_cart',
5441 - 'mxchat_checkout_redirect',
5442 - 'mxchat_handle_featured_products'
4453 + 'mxchat_checkout_redirect'
5443 4454 ),
5444 4455 'name' => __('WooCommerce Add-on', 'mxchat'),
5445 4456 'pro_required' => true
5446 4457 ),
@@ -5518,25 +4529,16 @@
5518 4529 'installed' => true
5519 4530 ),
5520 4531 // Pro core features - check is_activated property
5521 4532 'mxchat_generate_image' => array(
5522 - 'label' => __('Generate Image (OpenAI)', 'mxchat'),
4533 + 'label' => __('Generate Image', 'mxchat'),
5523 4534 'pro_only' => false,
5524 4535 'group' => __('Other Features', 'mxchat'),
5525 4536 'icon' => 'art',
5526 - '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'),
5527 4538 'addon' => false,
5528 4539 'installed' => true
5529 4540 ),
5530 - 'mxchat_generate_gemini_image' => array(
5531 - 'label' => __('Generate Image (Gemini)', 'mxchat'),
5532 - 'pro_only' => false,
5533 - 'group' => __('Other Features', 'mxchat'),
5534 - 'icon' => 'art',
5535 - 'description' => __('Create images with Imagen from Google (requires Gemini API key)', 'mxchat'),
5536 - 'addon' => false,
5537 - 'installed' => true
5538 - ),
5539 4541 'mxchat_handle_pdf_discussion' => array(
5540 4542 'label' => __('Chat with PDF', 'mxchat'),
5541 4543 'pro_only' => false,
5542 4544 'group' => __('Other Features', 'mxchat'),
@@ -5553,17 +4555,8 @@
5553 4555 'description' => __('Transfer conversation to a human support agent on Slack', 'mxchat'),
5554 4556 'addon' => false,
5555 4557 'installed' => true
5556 4558 ),
5557 - 'mxchat_telegram_live_agent_handover' => array(
5558 - 'label' => __('Telegram Live Agent', 'mxchat'),
5559 - 'pro_only' => false,
5560 - 'group' => __('Customer Engagement', 'mxchat'),
5561 - 'icon' => 'format-chat',
5562 - 'description' => __('Transfer conversation to a human support agent on Telegram', 'mxchat'),
5563 - 'addon' => false,
5564 - 'installed' => true
5565 - ),
5566 4559 'mxchat_handle_switch_to_chatbot_intent' => array(
5567 4560 'label' => __('Back to Chatbot', 'mxchat'),
5568 4561 'pro_only' => false,
5569 4562 'group' => __('Customer Engagement', 'mxchat'),
@@ -5574,15 +4567,13 @@
5574 4567 ),
5575 4568 );
5576 4569
5577 4570 // Add-on callbacks with placeholders - only include if the add-on is NOT active
5578 - // These are promotional/informational only — not selectable as real actions
5579 4571 $addon_callbacks = array(
5580 4572 // WooCommerce Add-on
5581 4573 'mxchat_handle_product_recommendations' => array(
5582 4574 'label' => __('Product Recommendations', 'mxchat'),
5583 - 'pro_only' => false,
5584 - 'addon_promo' => true,
4575 + 'pro_only' => true,
5585 4576 'group' => __('WooCommerce Features', 'mxchat'),
5586 4577 'icon' => 'cart',
5587 4578 'description' => __('Suggest products based on customer preferences', 'mxchat'),
5588 4579 ),
@@ -5587,10 +4578,9 @@
5587 4578 'description' => __('Suggest products based on customer preferences', 'mxchat'),
5588 4579 ),
5589 4580 'mxchat_handle_order_history' => array(
5590 4581 'label' => __('Order History', 'mxchat'),
5591 - 'pro_only' => false,
5592 - 'addon_promo' => true,
4582 + 'pro_only' => true,
5593 4583 'group' => __('WooCommerce Features', 'mxchat'),
5594 4584 'icon' => 'clipboard',
5595 4585 'description' => __('Allow customers to check their order status', 'mxchat'),
5596 4586 ),
@@ -5595,10 +4585,9 @@
5595 4585 'description' => __('Allow customers to check their order status', 'mxchat'),
5596 4586 ),
5597 4587 'mxchat_show_product_card' => array(
5598 4588 'label' => __('Show Product Card', 'mxchat'),
5599 - 'pro_only' => false,
5600 - 'addon_promo' => true,
4589 + 'pro_only' => true,
5601 4590 'group' => __('WooCommerce Features', 'mxchat'),
5602 4591 'icon' => 'products',
5603 4592 'description' => __('Display product information in the chat', 'mxchat'),
5604 4593 ),
@@ -5603,10 +4592,9 @@
5603 4592 'description' => __('Display product information in the chat', 'mxchat'),
5604 4593 ),
5605 4594 'mxchat_add_to_cart' => array(
5606 4595 'label' => __('Add to Cart', 'mxchat'),
5607 - 'pro_only' => false,
5608 - 'addon_promo' => true,
4596 + 'pro_only' => true,
5609 4597 'group' => __('WooCommerce Features', 'mxchat'),
5610 4598 'icon' => 'plus-alt',
5611 4599 'description' => __('Add products to cart directly from chat', 'mxchat'),
5612 4600 ),
@@ -5611,38 +4599,27 @@
5611 4599 'description' => __('Add products to cart directly from chat', 'mxchat'),
5612 4600 ),
5613 4601 'mxchat_checkout_redirect' => array(
5614 4602 'label' => __('Proceed to Checkout', 'mxchat'),
5615 - 'pro_only' => false,
5616 - 'addon_promo' => true,
4603 + 'pro_only' => true,
5617 4604 'group' => __('WooCommerce Features', 'mxchat'),
5618 4605 'icon' => 'arrow-right-alt',
5619 4606 'description' => __('Redirect customer to checkout page', 'mxchat'),
5620 4607 ),
5621 - 'mxchat_handle_featured_products' => array(
5622 - 'label' => __('Featured Products Showcase', 'mxchat'),
5623 - 'pro_only' => false,
5624 - 'addon_promo' => true,
5625 - 'group' => __('WooCommerce Features', 'mxchat'),
5626 - 'icon' => 'star-filled',
5627 - 'description' => __('Display a curated selection of products with an AI-generated message', 'mxchat'),
5628 - ),
5629 4608
5630 4609 // Perplexity Add-on
5631 4610 'mxchat_perplexity_research' => array(
5632 4611 'label' => __('Perplexity Research', 'mxchat'),
5633 - 'pro_only' => false,
5634 - 'addon_promo' => true,
4612 + 'pro_only' => true,
5635 4613 'group' => __('Search Features', 'mxchat'),
5636 4614 'icon' => 'book-alt',
5637 4615 'description' => __('Allows the chatbot to search the web for accurate, up-to-date answers', 'mxchat'),
5638 4616 ),
5639 4617
5640 - // Forms Add-on
4618 + // Forms Add-on (only shown when Pro is not activated)
5641 4619 'mxchat_handle_form_collection' => array(
5642 4620 'label' => __('Form Collection', 'mxchat'),
5643 - 'pro_only' => false,
5644 - 'addon_promo' => true,
4621 + 'pro_only' => true,
5645 4622 'group' => __('Form Features', 'mxchat'),
5646 4623 'icon' => 'feedback',
5647 4624 'description' => __('Collect user information through custom forms in chat', 'mxchat'),
5648 4625 ),
@@ -5896,27 +4873,9 @@
5896 4873 'mxchat-chatbot', // Page
5897 4874 'mxchat_chatbot_section' // Section
5898 4875 );
5899 4876
5900 - // RAG Sources Limit Slider
5901 4877 add_settings_field(
5902 - 'rag_sources_limit', // Field ID
5903 - esc_html__('RAG Sources Limit', 'mxchat'), // Field title
5904 - array($this, 'mxchat_rag_sources_limit_callback'), // Callback function
5905 - 'mxchat-chatbot', // Page
5906 - 'mxchat_chatbot_section' // Section
5907 - );
5908 -
5909 - // RAG Chunks Limit Slider
5910 - add_settings_field(
5911 - 'rag_chunks_limit', // Field ID
5912 - esc_html__('RAG Chunks Limit', 'mxchat'), // Field title
5913 - array($this, 'mxchat_rag_chunks_limit_callback'), // Callback function
5914 - 'mxchat-chatbot', // Page
5915 - 'mxchat_chatbot_section' // Section
5916 - );
5917 -
5918 - add_settings_field(
5919 4878 'append_to_body',
5920 4879 esc_html__('Auto-Display Chatbot', 'mxchat'),
5921 4880 array($this, 'mxchat_append_to_body_callback'),
5922 4881 'mxchat-chatbot',
@@ -5931,16 +4890,8 @@
5931 4890 'mxchat_chatbot_section'
5932 4891 );
5933 4892
5934 4893 add_settings_field(
5935 - 'citation_links_toggle',
5936 - esc_html__('Citation Links', 'mxchat'),
5937 - array($this, 'mxchat_citation_links_toggle_callback'),
5938 - 'mxchat-chatbot',
5939 - 'mxchat_chatbot_section'
5940 - );
5941 -
5942 - add_settings_field(
5943 4894 'enable_streaming_toggle',
5944 4895 esc_html__('Enable Streaming', 'mxchat'),
5945 4896 array($this, 'enable_streaming_toggle_callback'),
5946 4897 'mxchat-chatbot',
@@ -6344,64 +5295,11 @@
6344 5295 'mxchat-embed',
6345 5296 'mxchat_live_agent_section'
6346 5297 );
6347 5298
6348 - // Telegram Integration Section
6349 - add_settings_section(
6350 - 'mxchat_telegram_section',
6351 - __('Telegram Settings', 'mxchat'),
6352 - array($this, 'mxchat_telegram_section_callback'),
6353 - 'mxchat-embed'
6354 - );
6355 5299
6356 - add_settings_field(
6357 - 'telegram_status',
6358 - __('Live Agent Status', 'mxchat'),
6359 - array($this, 'mxchat_telegram_status_callback'),
6360 - 'mxchat-embed',
6361 - 'mxchat_telegram_section'
6362 - );
6363 5300
6364 - add_settings_field(
6365 - 'telegram_notification_message',
6366 - __('Notification Message', 'mxchat'),
6367 - array($this, 'mxchat_telegram_notification_message_callback'),
6368 - 'mxchat-embed',
6369 - 'mxchat_telegram_section'
6370 - );
6371 5301
6372 - add_settings_field(
6373 - 'telegram_away_message',
6374 - __('Away Message', 'mxchat'),
6375 - array($this, 'mxchat_telegram_away_message_callback'),
6376 - 'mxchat-embed',
6377 - 'mxchat_telegram_section'
6378 - );
6379 -
6380 - add_settings_field(
6381 - 'telegram_bot_token',
6382 - __('Telegram Bot Token', 'mxchat'),
6383 - array($this, 'mxchat_telegram_bot_token_callback'),
6384 - 'mxchat-embed',
6385 - 'mxchat_telegram_section'
6386 - );
6387 -
6388 - add_settings_field(
6389 - 'telegram_group_id',
6390 - __('Telegram Group ID', 'mxchat'),
6391 - array($this, 'mxchat_telegram_group_id_callback'),
6392 - 'mxchat-embed',
6393 - 'mxchat_telegram_section'
6394 - );
6395 -
6396 - add_settings_field(
6397 - 'telegram_webhook_secret',
6398 - __('Webhook Secret Token', 'mxchat'),
6399 - array($this, 'mxchat_telegram_webhook_secret_callback'),
6400 - 'mxchat-embed',
6401 - 'mxchat_telegram_section'
6402 - );
6403 -
6404 5302 // General Settings Section
6405 5303 add_settings_section(
6406 5304 'mxchat_general_section',
6407 5305 esc_html__('YouTube Tutorials', 'mxchat'),
@@ -6480,16 +5378,8 @@
6480 5378 'mxchat_transcripts_notification_section'
6481 5379 );
6482 5380
6483 5381 add_settings_field(
6484 - 'mxchat_retention_days',
6485 - esc_html__('Custom Retention (Days)', 'mxchat'),
6486 - array($this, 'mxchat_retention_days_callback'),
6487 - 'mxchat-transcripts',
6488 - 'mxchat_transcripts_notification_section'
6489 - );
6490 -
6491 - add_settings_field(
6492 5382 'mxchat_auto_email_transcript',
6493 5383 esc_html__('Auto-Email Full Transcript', 'mxchat'),
6494 5384 array($this, 'mxchat_auto_email_transcript_callback'),
6495 5385 'mxchat-transcripts',
@@ -6569,9 +5459,8 @@
6569 5459
6570 5460 ?>
6571 5461 <div class="notice notice-success is-dismissible">
6572 5462 <p><?php esc_html_e('Sync settings updated successfully.', 'mxchat'); ?></p>
6573 - <button type="button" class="notice-dismiss"><span class="screen-reader-text"><?php esc_html_e('Dismiss this notice.', 'mxchat'); ?></span></button>
6574 5463 </div>
6575 5464 <?php
6576 5465
6577 5466 }
@@ -6720,12 +5609,11 @@
6720 5609
6721 5610 // OpenAI API Key
6722 5611 public function api_key_callback() {
6723 5612 $apiKey = isset($this->options['api_key']) ? esc_attr($this->options['api_key']) : '';
6724 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
6725 5613
6726 5614 echo '<div class="api-key-wrapper">';
6727 - 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" />';
6728 5616 echo '<button type="button" id="toggleApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6729 5617 echo '<p class="description">' . esc_html__('Required for OpenAI GPT models and OpenAI embeddings. Get your API key from OpenAI Platform.', 'mxchat') . '</p>';
6730 5618 echo '</div>';
6731 5619 }
@@ -6732,12 +5620,11 @@
6732 5620
6733 5621 // X.AI API Key
6734 5622 public function xai_api_key_callback() {
6735 5623 $xaiApiKey = isset($this->options['xai_api_key']) ? esc_attr($this->options['xai_api_key']) : '';
6736 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
6737 5624
6738 5625 echo '<div class="api-key-wrapper">';
6739 - 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" />';
6740 5627 echo '<button type="button" id="toggleXaiApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6741 5628 echo '<p class="description">' . esc_html__('Required for X.AI Grok models. Get your API key from X.AI Console.', 'mxchat') . '</p>';
6742 5629 echo '</div>';
6743 5630 }
@@ -6743,12 +5630,11 @@
6743 5630 }
6744 5631 // Claude API Key
6745 5632 public function claude_api_key_callback() {
6746 5633 $claudeApiKey = isset($this->options['claude_api_key']) ? esc_attr($this->options['claude_api_key']) : '';
6747 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
6748 5634
6749 5635 echo '<div class="api-key-wrapper">';
6750 - 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" />';
6751 5637 echo '<button type="button" id="toggleClaudeApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6752 5638 echo '<p class="description">' . esc_html__('Required for Anthropic Claude models. Get your API key from Anthropic Console.', 'mxchat') . '</p>';
6753 5639 echo '</div>';
6754 5640 }
@@ -6755,12 +5641,11 @@
6755 5641
6756 5642 // DeepSeek API Key
6757 5643 public function deepseek_api_key_callback() {
6758 5644 $apiKey = isset($this->options['deepseek_api_key']) ? esc_attr($this->options['deepseek_api_key']) : '';
6759 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
6760 5645
6761 5646 echo '<div class="api-key-wrapper">';
6762 - 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" />';
6763 5648 echo '<button type="button" id="toggleDeepSeekApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6764 5649 echo '<p class="description">' . esc_html__('Required for DeepSeek models. Get your API key from DeepSeek Platform.', 'mxchat') . '</p>';
6765 5650 echo '</div>';
6766 5651 }
@@ -6767,12 +5652,11 @@
6767 5652
6768 5653 // Gemini API Key
6769 5654 public function gemini_api_key_callback() {
6770 5655 $geminiApiKey = isset($this->options['gemini_api_key']) ? esc_attr($this->options['gemini_api_key']) : '';
6771 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
6772 5656
6773 5657 echo '<div class="api-key-wrapper">';
6774 - 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" />';
6775 5659 echo '<button type="button" id="toggleGeminiApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6776 5660 echo '<p class="description">' . esc_html__('Required for Google Gemini models and embeddings. Get your API key from Google AI Studio.', 'mxchat') . '</p>';
6777 5661 echo '</div>';
6778 5662 }
@@ -6780,12 +5664,10 @@
6780 5664
6781 5665 // OpenRouter API Key
6782 5666 public function openrouter_api_key_callback() {
6783 5667 $openrouterApiKey = isset($this->options['openrouter_api_key']) ? esc_attr($this->options['openrouter_api_key']) : '';
6784 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
6785 -
6786 5668 echo '<div class="api-key-wrapper">';
6787 - 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" />';
6788 5670 echo '<button type="button" id="toggleOpenRouterApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6789 5671 echo '<p class="description">' . esc_html__('Required for OpenRouter models. Get your API key from OpenRouter.ai', 'mxchat') . '</p>';
6790 5672 echo '</div>';
6791 5673 }
@@ -6792,12 +5674,11 @@
6792 5674
6793 5675 // Voyage API Key
6794 5676 public function voyage_api_key_callback() {
6795 5677 $apiKey = isset($this->options['voyage_api_key']) ? esc_attr($this->options['voyage_api_key']) : '';
6796 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
6797 5678
6798 5679 echo '<div class="api-key-wrapper">';
6799 - 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" />';
6800 5681 echo '<button type="button" id="toggleVoyageAPIKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6801 5682 echo '<p class="description">' . esc_html__('Required for Voyage AI embedding models. Get your API key from Voyage AI.', 'mxchat') . '</p>';
6802 5683 echo '</div>';
6803 5684 }
@@ -6803,15 +5684,17 @@
6803 5684 }
6804 5685
6805 5686 public function mxchat_loops_api_key_callback() {
6806 5687 $loops_api_key = isset($this->options['loops_api_key']) ? esc_attr($this->options['loops_api_key']) : '';
6807 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
6808 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 +
6809 5693 echo '<div class="api-key-wrapper">';
6810 5694 echo sprintf(
6811 - '<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" />',
6812 - $loops_api_key,
6813 - $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
6814 5697 );
6815 5698 echo '<button type="button" id="toggleLoopsApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6816 5699 echo '</div>';
6817 5700 echo '<p class="description">' . esc_html__('Required for Loops email integration. Get your API key from Loops.so', 'mxchat') . '</p>';
@@ -6819,9 +5702,8 @@
6819 5702 public function mxchat_loops_mailing_list_callback() {
6820 5703 // Add error handling and type checking
6821 5704 $loops_api_key = '';
6822 5705 $selected_list = '';
6823 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
6824 5706
6825 5707 // Safely get the API key
6826 5708 if (isset($this->options['loops_api_key']) && is_string($this->options['loops_api_key'])) {
6827 5709 $loops_api_key = $this->options['loops_api_key'];
@@ -6834,10 +5716,9 @@
6834 5716
6835 5717 if (!empty($loops_api_key)) {
6836 5718 $lists = $this->mxchat_fetch_loops_mailing_lists($loops_api_key);
6837 5719 if (is_array($lists) && !empty($lists)) {
6838 - echo '<div class="mxchat-field-wrapper">';
6839 - 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">';
6840 5721
6841 5722 // Add a default "Select a list" option
6842 5723 echo '<option value="" ' . selected($selected_list, '', false) . '>' . esc_html__('Select a list', 'mxchat') . '</option>';
6843 5724
@@ -6851,9 +5732,8 @@
6851 5732 );
6852 5733 }
6853 5734 }
6854 5735 echo '</select>';
6855 - echo '</div>';
6856 5736 echo '<p class="description">' . esc_html__('Please select a mailing list to use with Loops.', 'mxchat') . '</p>';
6857 5737 } else {
6858 5738 echo '<p class="description">' . esc_html__('No lists found. Please verify your API Key.', 'mxchat') . '</p>';
6859 5739 }
@@ -6865,17 +5745,12 @@
6865 5745 $default_response = __('Would you like to join our mailing list? Please provide your email below.', 'mxchat');
6866 5746 $triggered_response = isset($this->options['triggered_phrase_response'])
6867 5747 ? $this->options['triggered_phrase_response']
6868 5748 : $default_response;
6869 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
6870 -
6871 - echo '<div class="mxchat-field-wrapper">';
6872 5749 echo sprintf(
6873 - '<textarea id="triggered_phrase_response" name="triggered_phrase_response" rows="3" cols="50" class="mxchat-autosave-field" data-nonce="%s">%s</textarea>',
6874 - $nonce,
5750 + '<textarea id="triggered_phrase_response" name="triggered_phrase_response" rows="3" cols="50">%s</textarea>',
6875 5751 esc_textarea($triggered_response)
6876 5752 );
6877 - echo '</div>';
6878 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>';
6879 5754 }
6880 5755
6881 5756 public function mxchat_email_capture_response_callback() {
@@ -6882,17 +5757,12 @@
6882 5757 $default_response = __('Thank you for providing your email! You\'ve been added to our list.', 'mxchat');
6883 5758 $email_capture_response = isset($this->options['email_capture_response'])
6884 5759 ? $this->options['email_capture_response']
6885 5760 : $default_response;
6886 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
6887 -
6888 - echo '<div class="mxchat-field-wrapper">';
6889 5761 echo sprintf(
6890 - '<textarea id="email_capture_response" name="email_capture_response" rows="3" cols="50" class="mxchat-autosave-field" data-nonce="%s">%s</textarea>',
6891 - $nonce,
5762 + '<textarea id="email_capture_response" name="email_capture_response" rows="3" cols="50">%s</textarea>',
6892 5763 esc_textarea($email_capture_response)
6893 5764 );
6894 - echo '</div>';
6895 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>';
6896 5766 }
6897 5767 public function mxchat_pre_chat_message_callback() {
6898 5768 // Load the entire 'mxchat_options' array
@@ -6906,8 +5776,9 @@
6906 5776 printf(
6907 5777 '<textarea id="pre_chat_message" name="pre_chat_message" rows="5" cols="50">%s</textarea>',
6908 5778 esc_textarea($pre_chat_message)
6909 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>';
6910 5781 }
6911 5782
6912 5783 // Callback for AI Instructions textarea
6913 5784 public function system_prompt_instructions_callback() {
@@ -6917,14 +5788,10 @@
6917 5788 printf(
6918 5789 '<textarea id="system_prompt_instructions" name="system_prompt_instructions" rows="5" cols="50">%s</textarea>',
6919 5790 $instructions
6920 5791 );
6921 - // Personalization hint
6922 - echo '<p class="description" style="margin-top: 8px;">';
6923 - echo esc_html__('Use {visitor_name} to personalize AI responses when lead capture is enabled.', 'mxchat') . '<br>';
6924 - echo '<code style="font-size: 12px;">' . esc_html__('Example: The visitor\'s name is {visitor_name}. Address them by name.', 'mxchat') . '</code>';
6925 - echo '</p>';
6926 - // 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>';
6927 5794 echo '<div class="mxchat-instructions-container">';
6928 5795 echo '<button type="button" class="mxchat-instructions-btn" id="mxchatViewSampleBtn">';
6929 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">';
6930 5797 echo '<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/>';
@@ -7005,18 +5872,19 @@
7005 5872 esc_html__('OpenRouter (100+ Models)', 'mxchat') => array(
7006 5873 'openrouter' => esc_html__('OpenRouter - Select after entering API key', 'mxchat'),
7007 5874 ),
7008 5875 esc_html__('Google Gemini Models', 'mxchat') => array(
7009 - 'gemini-3-pro-preview' => esc_html__('Gemini 3 Pro (Most Intelligent, Multimodal)', 'mxchat'),
7010 - 'gemini-3-flash-preview' => esc_html__('Gemini 3 Flash (Balanced Speed & Scale)', 'mxchat'),
7011 - 'gemini-2.5-pro' => esc_html__('Gemini 2.5 Pro (Advanced Thinking)', 'mxchat'),
7012 - 'gemini-2.5-flash' => esc_html__('Gemini 2.5 Flash (Best Price-Performance)', 'mxchat'),
7013 - 'gemini-2.5-flash-lite' => esc_html__('Gemini 2.5 Flash-Lite (Ultra Fast)', 'mxchat'),
7014 - 'gemini-2.0-flash' => esc_html__('Gemini 2.0 Flash (Deprecated Mar 2026)', 'mxchat'),
7015 - 'gemini-2.0-flash-lite' => esc_html__('Gemini 2.0 Flash-Lite (Deprecated Mar 2026)', 'mxchat'),
7016 - 'gemini-1.5-pro' => esc_html__('Gemini 1.5 Pro (Deprecated Sep 2025)', 'mxchat'),
7017 - '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'),
7018 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 + ),
7019 5887 esc_html__('X.AI Models', 'mxchat') => array(
7020 5888 'grok-3-beta' => esc_html__('Grok-3 (Powerful)', 'mxchat'),
7021 5889 'grok-3-fast-beta' => esc_html__('Grok-3 Fast (High Performance)', 'mxchat'),
7022 5890 'grok-3-mini-beta' => esc_html__('Grok-3 Mini (Affordable)', 'mxchat'),
@@ -7029,33 +5897,37 @@
7029 5897 esc_html__('DeepSeek Models', 'mxchat') => array(
7030 5898 'deepseek-chat' => esc_html__('DeepSeek-V3', 'mxchat'),
7031 5899 ),
7032 5900 esc_html__('Claude Models', 'mxchat') => array(
7033 - 'claude-opus-4-6' => esc_html__('Claude Opus 4.6 (Most Capable - Recommended)', 'mxchat'),
7034 - 'claude-sonnet-4-6' => esc_html__('Claude Sonnet 4.6 (Latest Sonnet - Fast & Capable)', 'mxchat'),
7035 - 'claude-opus-4-5' => esc_html__('Claude Opus 4.5 (Highly Capable)', 'mxchat'),
7036 5901 'claude-sonnet-4-5-20250929' => esc_html__('Claude Sonnet 4.5 (Best for Agents & Coding)', 'mxchat'),
7037 5902 'claude-opus-4-1-20250805' => esc_html__('Claude Opus 4.1 (Exceptional for Complex Tasks)', 'mxchat'),
7038 5903 'claude-haiku-4-5-20251001' => esc_html__('Claude Haiku 4.5 (Fastest & Most Intelligent)', 'mxchat'),
7039 - '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'),
7040 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'),
7041 5910 ),
7042 5911 esc_html__('OpenAI Models', 'mxchat') => array(
7043 - 'gpt-5.4' => esc_html__('GPT-5.4 (Flagship Reasoning & Coding)', 'mxchat'),
7044 - 'gpt-5.4-mini' => esc_html__('GPT-5.4 Mini (Fast, 400K Context)', 'mxchat'),
7045 - 'gpt-5.4-nano' => esc_html__('GPT-5.4 Nano (Fastest & Cheapest)', 'mxchat'),
7046 - 'gpt-5.3-chat-latest' => esc_html__('GPT-5.3 Chat (Conversational)', 'mxchat'),
7047 - 'gpt-5.2' => esc_html__('GPT-5.2 (Best General-Purpose & Agentic Model)', 'mxchat'),
7048 - 'gpt-5.1-chat-latest' => esc_html__('GPT-5.1 Chat Latest (Recommended)', 'mxchat'),
5912 + // GPT-5 family
7049 5913 'gpt-5.1-2025-11-13' => esc_html__('GPT-5.1 (Flagship for Coding & Agentic Tasks)', 'mxchat'),
7050 5914 'gpt-5' => esc_html__('GPT-5 (Flagship for Coding, Reasoning & Agents)', 'mxchat'),
7051 - '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'),
7052 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'),
7053 5925 ),
7054 5926 );
7055 5927
7056 5928 // Retrieve the currently selected model from saved options
7057 - $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';
7058 5930
7059 5931 // Begin the select dropdown
7060 5932 echo '<select id="model" name="model">';
7061 5933
@@ -7071,8 +5943,10 @@
7071 5943 }
7072 5944
7073 5945 echo '</select>';
7074 5946
5947 + echo '<p class="description">' . esc_html__('Select the AI model your chatbot will use for chatting.', 'mxchat') . '</p>';
5948 +
7075 5949 // Add a note for OpenRouter
7076 5950 echo '<p class="description" id="openrouter-model-note" style="display:none; color: #d63638; font-weight: 500;">';
7077 5951 echo '<span class="dashicons dashicons-info" style="font-size: 16px; vertical-align: middle;"></span> ';
7078 5952 echo esc_html__('After entering your OpenRouter API key above, click the button below to load available models.', 'mxchat');
@@ -7160,8 +6034,11 @@
7160 6034 esc_attr($checked)
7161 6035 );
7162 6036 echo '<span class="slider"></span>';
7163 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>';
7164 6041
7165 6042 // Test button with better styling
7166 6043 echo '<div style="margin-top: 15px;">';
7167 6044 echo '<button type="button" id="mxchat-test-streaming-btn" class="button button-secondary">';
@@ -7171,50 +6048,8 @@
7171 6048 echo '<p id="mxchat-test-streaming-result" style="margin-top:10px; font-weight: bold;"></p>';
7172 6049 echo '</div>';
7173 6050 }
7174 6051
7175 -// Web Search toggle callback
7176 -public function enable_web_search_toggle_callback() {
7177 - // Get value from options array, default to 'off'
7178 - $enabled = isset($this->options['enable_web_search']) ? $this->options['enable_web_search'] : 'off';
7179 - $checked = ($enabled === 'on') ? 'checked' : '';
7180 -
7181 - // Get current model to determine if we should show/enable the toggle
7182 - $current_model = isset($this->options['model']) ? $this->options['model'] : 'gpt-5.1-chat-latest';
7183 -
7184 - // Models that DON'T support web search (per OpenAI docs)
7185 - // gpt-5 with minimal reasoning is handled at API level, gpt-4.1-nano doesn't support it
7186 - $unsupported_models = array('gpt-4.1-nano');
7187 -
7188 - // Check if current model is an OpenAI model that supports web search
7189 - $openai_models = array(
7190 - 'gpt-5.4', 'gpt-5.4-mini', 'gpt-5.4-nano', 'gpt-5.3-chat-latest',
7191 - 'gpt-5.2', 'gpt-5.1-chat-latest', 'gpt-5.1-2025-11-13', 'gpt-5', 'gpt-5-mini', 'gpt-5-nano'
7192 - );
7193 -
7194 - $is_openai = in_array($current_model, $openai_models);
7195 - $is_supported = $is_openai && !in_array($current_model, $unsupported_models);
7196 -
7197 - // Wrapper div with data attributes for JS to show/hide
7198 - 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;"' : '') . '>';
7199 -
7200 - echo '<label class="toggle-switch">';
7201 - echo sprintf(
7202 - '<input type="checkbox" id="enable_web_search" name="enable_web_search" value="on" %s />',
7203 - esc_attr($checked)
7204 - );
7205 - echo '<span class="slider"></span>';
7206 - echo '</label>';
7207 -
7208 - echo '</div>';
7209 -
7210 - // Message shown when non-OpenAI model is selected
7211 - echo '<p id="web-search-unavailable-message" class="description" style="color: #666;' . ($is_supported ? ' display:none;' : '') . '">';
7212 - echo '<span class="dashicons dashicons-info" style="font-size: 16px; vertical-align: middle; margin-right: 4px;"></span>';
7213 - echo esc_html__('Web search is only available for OpenAI models.', 'mxchat');
7214 - echo '</p>';
7215 -}
7216 -
7217 6052 // AJAX handler to fetch OpenRouter models
7218 6053 public function fetch_openrouter_models() {
7219 6054 check_ajax_referer('mxchat_fetch_openrouter_models', 'nonce');
7220 6055
@@ -7271,9 +6106,9 @@
7271 6106 esc_html__('Voyage AI Embeddings', 'mxchat') => array(
7272 6107 'voyage-3-large' => esc_html__('Voyage-3 Large (2048, Most Capable)', 'mxchat'),
7273 6108 ),
7274 6109 esc_html__('Google Gemini Embeddings', 'mxchat') => array(
7275 - 'gemini-embedding-001' => esc_html__('Gemini Embedding (1536, Stable)', 'mxchat'),
6110 + 'gemini-embedding-exp-03-07' => esc_html__('Gemini Embedding (1536, Experimental)', 'mxchat'),
7276 6111 )
7277 6112 );
7278 6113 $selected_model = isset($this->options['embedding_model']) ? esc_attr($this->options['embedding_model']) : 'text-embedding-ada-002';
7279 6114 echo '<select id="embedding_model" name="embedding_model">';
@@ -7284,8 +6119,9 @@
7284 6119 }
7285 6120 echo '</optgroup>';
7286 6121 }
7287 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>';
7288 6124
7289 6125 // API Key Status Messages for Embedding Models
7290 6126 $has_openai_key = !empty($this->options['api_key']);
7291 6127 $has_voyage_key = !empty($this->options['voyage_api_key']);
@@ -7316,9 +6152,8 @@
7316 6152 } else {
7317 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>';
7318 6154 }
7319 6155 echo '</p>';
7320 -
7321 6156 }
7322 6157
7323 6158
7324 6159 public function mxchat_top_bar_title_callback() {
@@ -7326,8 +6161,11 @@
7326 6161 $top_bar_title = isset($this->options['top_bar_title']) ? esc_attr($this->options['top_bar_title']) : '';
7327 6162
7328 6163 // Render the input field
7329 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>';
7330 6168 }
7331 6169 public function mxchat_ai_agent_text_callback() {
7332 6170 // Retrieve the current value of the AI agent text from saved options
7333 6171 $ai_agent_text = isset($this->options['ai_agent_text']) ? esc_attr($this->options['ai_agent_text']) : '';
@@ -7332,8 +6170,10 @@
7332 6170 // Retrieve the current value of the AI agent text from saved options
7333 6171 $ai_agent_text = isset($this->options['ai_agent_text']) ? esc_attr($this->options['ai_agent_text']) : '';
7334 6172 // Render the input field
7335 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>';
7336 6176 }
7337 6177
7338 6178
7339 6179 public function enable_email_block_callback() {
@@ -7352,8 +6192,9 @@
7352 6192 esc_attr($checked)
7353 6193 );
7354 6194 echo '<span class="slider"></span>';
7355 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>';
7356 6197 }
7357 6198
7358 6199 public function email_blocker_header_content_callback() {
7359 6200 // Load the entire 'mxchat_options' array
@@ -7371,8 +6212,12 @@
7371 6212 rows="5"
7372 6213 cols="70"
7373 6214 data-setting="email_blocker_header_content"
7374 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>';
7375 6220 }
7376 6221
7377 6222 public function email_blocker_button_text_callback() {
7378 6223 // Load the entire 'mxchat_options' array
@@ -7384,8 +6229,12 @@
7384 6229 : '';
7385 6230
7386 6231 // Use esc_attr to safely render the existing text
7387 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>';
7388 6237 }
7389 6238
7390 6239 //Enable name field callback
7391 6240 public function enable_name_field_callback() {
@@ -7401,8 +6250,9 @@
7401 6250 esc_attr($checked)
7402 6251 );
7403 6252 echo '<span class="slider"></span>';
7404 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>';
7405 6255 }
7406 6256 //Name field placeholder callback
7407 6257 public function name_field_placeholder_callback() {
7408 6258 $all_options = get_option('mxchat_options', []);
@@ -7410,8 +6260,11 @@
7410 6260 ? $all_options['name_field_placeholder']
7411 6261 : esc_html__('Enter your name', 'mxchat');
7412 6262
7413 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>';
7414 6267 }
7415 6268
7416 6269
7417 6270
@@ -7423,11 +6276,10 @@
7423 6276 $saved_message = isset($all_options['intro_message']) ? $all_options['intro_message'] : $default_message;
7424 6277 // Output the textarea with the saved value without escaping HTML
7425 6278 ?>
7426 6279 <textarea id="intro_message" name="intro_message" rows="5" cols="50"><?php echo $saved_message; ?></textarea>
7427 - <p class="description" style="margin-top: 8px;">
7428 - <?php esc_html_e('Use {visitor_name} to personalize greetings when lead capture is enabled.', 'mxchat'); ?><br>
7429 - <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'); ?>
7430 6282 </p>
7431 6283 <?php
7432 6284 }
7433 6285
@@ -7444,117 +6296,478 @@
7444 6296 '<input type="text" id="input_copy" name="input_copy" value="%s" placeholder="%s" />',
7445 6297 esc_attr($input_copy),
7446 6298 esc_attr__('How can I assist?', 'mxchat')
7447 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>';
7448 6303 }
7449 6304
7450 6305
7451 -public function mxchat_append_to_body_callback() {
7452 - // Fetch fresh options to ensure we have the latest saved values
7453 - $options = get_option('mxchat_options', array());
7454 6306
7455 - // Get value from options array, default to 'off'
7456 - $append_to_body = isset($options['append_to_body']) ? $options['append_to_body'] : 'off';
7457 - $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';
7458 6310
7459 - // Get post type visibility settings
7460 - $visibility_mode = isset($options['post_type_visibility_mode']) ? $options['post_type_visibility_mode'] : 'all';
7461 - $visibility_list = isset($options['post_type_visibility_list']) ? $options['post_type_visibility_list'] : array();
7462 - if (!is_array($visibility_list)) {
7463 - $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>';
7464 6330 }
6331 + echo '</div>';
6332 +}
7465 6333
7466 - 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';
7467 6337
7468 - // Main toggle
7469 - echo '<label class="toggle-switch">';
6338 + echo '<div class="' . esc_attr($class) . '">';
7470 6339 echo sprintf(
7471 - '<input type="checkbox" id="append_to_body" name="append_to_body" value="on" %s />',
7472 - 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)
7473 6349 );
7474 - echo '<span class="slider"></span>';
7475 - echo '</label>';
7476 6350
7477 - // Post Type Visibility Options (only visible when auto-display is ON)
7478 - $display_style = ($append_to_body === 'on') ? '' : 'display: none;';
7479 - 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 +}
7480 6360
7481 - echo '<div class="mxchat-post-type-visibility-header">';
7482 - 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 + }
7483 6385 echo '</div>';
6386 +}
7484 6387
7485 - // Mode selector (radio buttons)
7486 - 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';
7487 6391
7488 - echo '<label class="mxchat-radio-label">';
7489 - echo '<input type="radio" name="post_type_visibility_mode" value="all" ' . checked($visibility_mode, 'all', false) . ' />';
7490 - echo '<span>' . esc_html__('Show on all post types', 'mxchat') . '</span>';
7491 - 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 + );
7492 6404
7493 - echo '<label class="mxchat-radio-label">';
7494 - echo '<input type="radio" name="post_type_visibility_mode" value="include" ' . checked($visibility_mode, 'include', false) . ' />';
7495 - echo '<span>' . esc_html__('Only show on selected post types', 'mxchat') . '</span>';
7496 - 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 +}
7497 6414
7498 - echo '<label class="mxchat-radio-label">';
7499 - echo '<input type="radio" name="post_type_visibility_mode" value="exclude" ' . checked($visibility_mode, 'exclude', false) . ' />';
7500 - echo '<span>' . esc_html__('Hide on selected post types', 'mxchat') . '</span>';
7501 - 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';
7502 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 + }
7503 6439 echo '</div>';
6440 +}
7504 6441
7505 - // Post type checkboxes (only visible when mode is include or exclude)
7506 - $list_display = ($visibility_mode !== 'all') ? '' : 'display: none;';
7507 - 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';
7508 6445
7509 - // Get all public post types
7510 - $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 + );
7511 6458
7512 - foreach ($post_types as $post_type) {
7513 - // Skip attachments
7514 - if ($post_type->name === 'attachment') {
7515 - continue;
7516 - }
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 +}
7517 6468
7518 - $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';
7519 6472
7520 - echo '<label class="mxchat-checkbox-label">';
7521 - echo '<input type="checkbox" name="post_type_visibility_list[]" value="' . esc_attr($post_type->name) . '" ' . $is_checked . ' />';
7522 - echo '<span>' . esc_html($post_type->label) . '</span>';
7523 - 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>';
7524 6492 }
6493 + echo '</div>';
6494 +}
7525 6495
7526 - echo '</div>'; // End post-type-list
7527 - echo '</div>'; // End post-type-visibility-options
7528 - 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>';
7529 6521 }
7530 6522
7531 -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() {
7532 6736 // Get value from options array, default to 'off'
7533 - $contextual_awareness = isset($this->options['contextual_awareness_toggle']) ? $this->options['contextual_awareness_toggle'] : 'off';
7534 - $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 +
7535 6740 echo '<label class="toggle-switch">';
7536 6741 echo sprintf(
7537 - '<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 />',
7538 6743 esc_attr($checked)
7539 6744 );
7540 6745 echo '<span class="slider"></span>';
7541 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 +
7542 6751 }
7543 6752
7544 -public function mxchat_citation_links_toggle_callback() {
7545 - // Get value from options array, default to 'on' (enabled by default)
7546 - $citation_links = isset($this->options['citation_links_toggle']) ? $this->options['citation_links_toggle'] : 'on';
7547 - $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' : '';
7548 6757 echo '<label class="toggle-switch">';
7549 6758 echo sprintf(
7550 - '<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 />',
7551 6760 esc_attr($checked)
7552 6761 );
7553 6762 echo '<span class="slider"></span>';
7554 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>';
7555 6767 }
7556 6768
6769 +
7557 6770 public function mxchat_privacy_toggle_callback() {
7558 6771 // Load from mxchat_options array
7559 6772 $options = get_option('mxchat_options', []);
7560 6773
@@ -7574,8 +6787,9 @@
7574 6787 esc_attr($checked)
7575 6788 );
7576 6789 echo '<span class="slider"></span>';
7577 6790 echo '</label>';
6791 + echo '<p class="description">' . esc_html__('Enable this option to display a privacy notice below the chat widget.', 'mxchat') . '</p>';
7578 6792
7579 6793 // Output the custom text input field
7580 6794 echo sprintf(
7581 6795 '<textarea id="privacy_text" name="privacy_text" rows="5" cols="50" class="regular-text">%s</textarea>',
@@ -7580,8 +6794,9 @@
7580 6794 echo sprintf(
7581 6795 '<textarea id="privacy_text" name="privacy_text" rows="5" cols="50" class="regular-text">%s</textarea>',
7582 6796 esc_textarea($privacy_text)
7583 6797 );
6798 + echo '<p class="description">' . esc_html__('Enter the privacy policy text. You can include HTML links.', 'mxchat') . '</p>';
7584 6799 }
7585 6800
7586 6801
7587 6802 public function mxchat_complianz_toggle_callback() {
@@ -7599,8 +6814,10 @@
7599 6814 esc_attr($checked)
7600 6815 );
7601 6816 echo '<span class="slider"></span>';
7602 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>';
7603 6820 }
7604 6821
7605 6822 public function mxchat_link_target_toggle_callback() {
7606 6823 // Load from mxchat_options array
@@ -7617,8 +6834,9 @@
7617 6834 esc_attr($checked)
7618 6835 );
7619 6836 echo '<span class="slider"></span>';
7620 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>';
7621 6839 }
7622 6840
7623 6841 public function mxchat_chat_persistence_toggle_callback() {
7624 6842 // Load from mxchat_options array
@@ -7635,8 +6853,10 @@
7635 6853 esc_attr($checked)
7636 6854 );
7637 6855 echo '<span class="slider"></span>';
7638 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>';
7639 6859 }
7640 6860
7641 6861 public function mxchat_popular_question_1_callback() {
7642 6862 // Load the full plugin options array
@@ -7650,8 +6870,11 @@
7650 6870 '<input type="text" id="popular_question_1" name="popular_question_1" value="%s" placeholder="%s" class="regular-text" />',
7651 6871 esc_attr($popular_question_1),
7652 6872 esc_attr__('Enter Quick Question 1', 'mxchat')
7653 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>';
7654 6877 }
7655 6878
7656 6879
7657 6880 public function mxchat_popular_question_2_callback() {
@@ -7666,8 +6889,11 @@
7666 6889 '<input type="text" id="popular_question_2" name="popular_question_2" value="%s" placeholder="%s" class="regular-text" />',
7667 6890 esc_attr($popular_question_2),
7668 6891 esc_attr__('Enter Quick Question 2', 'mxchat')
7669 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>';
7670 6896 }
7671 6897
7672 6898
7673 6899 public function mxchat_popular_question_3_callback() {
@@ -7682,8 +6908,11 @@
7682 6908 '<input type="text" id="popular_question_3" name="popular_question_3" value="%s" placeholder="%s" class="regular-text" />',
7683 6909 esc_attr($popular_question_3),
7684 6910 esc_attr(__('Enter Quick Question 3', 'mxchat'))
7685 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>';
7686 6915 }
7687 6916
7688 6917 public function mxchat_additional_popular_questions_callback() {
7689 6918 $options = get_option('mxchat_options', []);
@@ -7732,19 +6961,19 @@
7732 6961 '<button type="button" class="button mxchat-add-question" aria-label="%s">%s</button>',
7733 6962 esc_attr(__('Add question', 'mxchat')),
7734 6963 esc_html__('Add Question', 'mxchat')
7735 6964 );
6965 + echo '<p class="description">' . esc_html__('Add as many Quick Questions as you need.', 'mxchat') . '</p>';
6966 + echo '</div>';
7736 6967 }
7737 6968
7738 6969 public function mxchat_brave_api_key_callback() {
7739 6970 $brave_api_key = isset($this->options['brave_api_key']) ? esc_attr($this->options['brave_api_key']) : '';
7740 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
7741 6971
7742 6972 echo '<div class="api-key-wrapper">';
7743 6973 echo sprintf(
7744 - '<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" />',
7745 - $brave_api_key,
7746 - $nonce
6974 + '<input type="password" id="brave_api_key" name="brave_api_key" value="%s" class="regular-text" />',
6975 + $brave_api_key
7747 6976 );
7748 6977 echo '<button type="button" id="toggleBraveApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
7749 6978 echo '</div>';
7750 6979 echo '<p class="description">' . __('Required for Brave Search integration. Get your API key from Brave Search API.', 'mxchat') . '</p>';
@@ -7753,18 +6982,14 @@
7753 6982 public function mxchat_brave_image_count_callback() {
7754 6983 $brave_image_count = isset($this->options['brave_image_count'])
7755 6984 ? intval($this->options['brave_image_count'])
7756 6985 : 4;
7757 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
7758 6986
7759 - echo '<div class="mxchat-field-wrapper">';
7760 6987 echo sprintf(
7761 6988 '<input type="number" id="brave_image_count" name="brave_image_count"
7762 - value="%d" min="1" max="6" class="small-text mxchat-autosave-field" data-nonce="%s" />',
7763 - $brave_image_count,
7764 - $nonce
6989 + value="%d" min="1" max="6" class="small-text" />',
6990 + $brave_image_count
7765 6991 );
7766 - echo '</div>';
7767 6992 echo '<p class="description">' . __('Select the number of images to return (1-6).', 'mxchat') . '</p>';
7768 6993 }
7769 6994
7770 6995 public function mxchat_brave_safe_search_callback() {
@@ -7770,12 +6995,10 @@
7770 6995 public function mxchat_brave_safe_search_callback() {
7771 6996 $brave_safe_search = isset($this->options['brave_safe_search'])
7772 6997 ? esc_attr($this->options['brave_safe_search'])
7773 6998 : 'strict';
7774 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
7775 6999
7776 - echo '<div class="mxchat-field-wrapper">';
7777 - 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">';
7778 7001 echo sprintf(
7779 7002 '<option value="strict" %s>%s</option>',
7780 7003 selected($brave_safe_search, 'strict', false),
7781 7004 __('Strict', 'mxchat')
@@ -7785,9 +7008,8 @@
7785 7008 selected($brave_safe_search, 'off', false),
7786 7009 __('Off', 'mxchat')
7787 7010 );
7788 7011 echo '</select>';
7789 - echo '</div>';
7790 7012 echo '<p class="description">' .
7791 7013 esc_html__('Set the Safe Search level for image searches. Brave Search only supports "Strict" and "Off" options.', 'mxchat') .
7792 7014 '</p>';
7793 7015 }
@@ -7795,18 +7017,14 @@
7795 7017 public function mxchat_brave_news_count_callback() {
7796 7018 $brave_news_count = isset($this->options['brave_news_count'])
7797 7019 ? intval($this->options['brave_news_count'])
7798 7020 : 3;
7799 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
7800 7021
7801 - echo '<div class="mxchat-field-wrapper">';
7802 7022 echo sprintf(
7803 7023 '<input type="number" id="brave_news_count" name="brave_news_count"
7804 - value="%d" min="1" max="10" class="small-text mxchat-autosave-field" data-nonce="%s" />',
7805 - $brave_news_count,
7806 - $nonce
7024 + value="%d" min="1" max="10" class="small-text" />',
7025 + $brave_news_count
7807 7026 );
7808 - echo '</div>';
7809 7027 echo '<p class="description">' . esc_html__('Select the number of news articles to retrieve (1-10).', 'mxchat') . '</p>';
7810 7028 }
7811 7029
7812 7030 public function mxchat_brave_country_callback() {
@@ -7812,18 +7030,14 @@
7812 7030 public function mxchat_brave_country_callback() {
7813 7031 $brave_country = isset($this->options['brave_country'])
7814 7032 ? esc_attr($this->options['brave_country'])
7815 7033 : 'us';
7816 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
7817 7034
7818 - echo '<div class="mxchat-field-wrapper">';
7819 7035 echo sprintf(
7820 7036 '<input type="text" id="brave_country" name="brave_country"
7821 - value="%s" maxlength="2" class="small-text mxchat-autosave-field" data-nonce="%s" />',
7822 - $brave_country,
7823 - $nonce
7037 + value="%s" maxlength="2" class="small-text" />',
7038 + $brave_country
7824 7039 );
7825 - echo '</div>';
7826 7040 echo '<p class="description">' . esc_html__('Enter the country code (e.g., "us" for United States).', 'mxchat') . '</p>';
7827 7041 }
7828 7042
7829 7043 public function mxchat_brave_language_callback() {
@@ -7829,18 +7043,14 @@
7829 7043 public function mxchat_brave_language_callback() {
7830 7044 $brave_language = isset($this->options['brave_language'])
7831 7045 ? esc_attr($this->options['brave_language'])
7832 7046 : 'en';
7833 - $nonce = wp_create_nonce('mxchat_autosave_nonce');
7834 7047
7835 - echo '<div class="mxchat-field-wrapper">';
7836 7048 echo sprintf(
7837 7049 '<input type="text" id="brave_language" name="brave_language"
7838 - value="%s" maxlength="2" class="small-text mxchat-autosave-field" data-nonce="%s" />',
7839 - $brave_language,
7840 - $nonce
7050 + value="%s" maxlength="2" class="small-text" />',
7051 + $brave_language
7841 7052 );
7842 - echo '</div>';
7843 7053 echo '<p class="description">' . esc_html__('Enter the language code (e.g., "en" for English).', 'mxchat') . '</p>';
7844 7054 }
7845 7055
7846 7056
@@ -8060,97 +7270,13 @@
8060 7270 );
8061 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>';
8062 7272 }
8063 7273
8064 -/**
8065 - * Telegram Integration Callbacks
8066 - */
8067 -public function mxchat_telegram_section_callback() {
8068 - echo '<p>' . esc_html__('Configure Telegram integration for live agent support.', 'mxchat') . '</p>';
8069 -}
8070 -
8071 -public function mxchat_telegram_status_callback() {
8072 - $fresh_options = get_option('mxchat_options');
8073 - $status = isset($fresh_options['telegram_status']) ? $fresh_options['telegram_status'] : 'off';
8074 -
8075 - echo '<label class="toggle-switch">';
8076 - echo sprintf(
8077 - '<input type="checkbox" id="telegram_status" name="telegram_status" value="on" %s />',
8078 - checked($status, 'on', false)
8079 - );
8080 - echo '<span class="slider"></span>';
8081 - echo '</label>';
8082 - echo '<label for="telegram_status" class="mxchat-status-label">';
8083 - echo '<span class="status-text">' . ($status === 'on' ? esc_html__('Online', 'mxchat') : esc_html__('Offline', 'mxchat')) . '</span>';
8084 - echo '</label>';
8085 -}
8086 -
8087 -public function mxchat_telegram_notification_message_callback() {
8088 - $message = isset($this->options['telegram_notification_message'])
8089 - ? $this->options['telegram_notification_message']
8090 - : __("I've notified a support agent. Please allow a moment for them to respond.", 'mxchat');
8091 -
8092 - printf(
8093 - '<textarea id="telegram_notification_message" name="telegram_notification_message" rows="3" cols="50">%s</textarea>',
8094 - esc_textarea($message)
8095 - );
8096 - echo '<p class="description">' . esc_html__('Message shown when live transfer activated.', 'mxchat') . '</p>';
8097 -}
8098 -
8099 -public function mxchat_telegram_away_message_callback() {
8100 - $message = isset($this->options['telegram_away_message'])
8101 - ? $this->options['telegram_away_message']
8102 - : __('Sorry, live agents are currently unavailable. I can continue helping you as an AI assistant.', 'mxchat');
8103 -
8104 - printf(
8105 - '<textarea id="telegram_away_message" name="telegram_away_message" rows="3" cols="50">%s</textarea>',
8106 - esc_textarea($message)
8107 - );
8108 - echo '<p class="description">' . esc_html__('Message shown when live agents are offline.', 'mxchat') . '</p>';
8109 -}
8110 -
8111 -public function mxchat_telegram_bot_token_callback() {
8112 - printf(
8113 - '<input type="password" id="telegram_bot_token" name="telegram_bot_token" value="%s" class="regular-text" />',
8114 - isset($this->options['telegram_bot_token']) ? esc_attr($this->options['telegram_bot_token']) : ''
8115 - );
8116 - 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>';
8117 - echo '<p class="description">' . esc_html__('Your Telegram Bot Token from @BotFather (e.g., 123456789:ABCdefGhIJKlmNoPQRsTUVwxyZ).', 'mxchat') . '</p>';
8118 -}
8119 -
8120 -public function mxchat_telegram_group_id_callback() {
8121 - printf(
8122 - '<input type="text" id="telegram_group_id" name="telegram_group_id" value="%s" class="regular-text" />',
8123 - isset($this->options['telegram_group_id']) ? esc_attr($this->options['telegram_group_id']) : ''
8124 - );
8125 - echo '<p class="description">' . esc_html__('Your Telegram supergroup ID (starts with -100). The group must have forum topics enabled.', 'mxchat') . '</p>';
8126 -}
8127 -
8128 -public function mxchat_telegram_webhook_secret_callback() {
8129 - $secret = isset($this->options['telegram_webhook_secret']) ? $this->options['telegram_webhook_secret'] : '';
8130 -
8131 - // Auto-generate secret if empty
8132 - if (empty($secret)) {
8133 - $secret = wp_generate_password(64, false, false);
8134 - $options = get_option('mxchat_options', []);
8135 - $options['telegram_webhook_secret'] = $secret;
8136 - update_option('mxchat_options', $options);
8137 - $this->options['telegram_webhook_secret'] = $secret;
8138 - }
8139 -
8140 - printf(
8141 - '<input type="password" id="telegram_webhook_secret" name="telegram_webhook_secret" value="%s" class="regular-text" />',
8142 - esc_attr($secret)
8143 - );
8144 - 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>';
8145 - 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>';
8146 -}
8147 -
8148 7274 public function mxchat_similarity_threshold_callback() {
8149 7275 // Load from mxchat_options array
8150 7276 $options = get_option('mxchat_options', []);
8151 7277
8152 - // Get value from options array with default of 35
7278 + // Get value from options array with default of 80
8153 7279 $threshold = isset($options['similarity_threshold']) ? $options['similarity_threshold'] : 35;
8154 7280
8155 7281 echo '<div class="slider-container">';
8156 7282 echo sprintf(
@@ -8168,62 +7294,13 @@
8168 7294 '<span id="threshold_value" class="range-value">%s</span>',
8169 7295 esc_html($threshold)
8170 7296 );
8171 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>';
8172 7301 }
8173 7302
8174 -public function mxchat_rag_sources_limit_callback() {
8175 - // Load from mxchat_options array
8176 - $options = get_option('mxchat_options', []);
8177 -
8178 - // Get value from options array with default of 3
8179 - $rag_sources_limit = isset($options['rag_sources_limit']) ? intval($options['rag_sources_limit']) : 3;
8180 -
8181 - echo '<div class="slider-container">';
8182 - echo sprintf(
8183 - '<input type="range"
8184 - id="rag_sources_limit"
8185 - name="rag_sources_limit"
8186 - min="3"
8187 - max="10"
8188 - step="1"
8189 - value="%s"
8190 - class="range-slider" />',
8191 - esc_attr($rag_sources_limit)
8192 - );
8193 - echo sprintf(
8194 - '<span id="rag_sources_limit_value" class="range-value">%s</span>',
8195 - esc_html($rag_sources_limit)
8196 - );
8197 - echo '</div>';
8198 -}
8199 -
8200 -public function mxchat_rag_chunks_limit_callback() {
8201 - // Load from mxchat_options array
8202 - $options = get_option('mxchat_options', []);
8203 -
8204 - // Get value from options array with default of 15
8205 - $rag_chunks_limit = isset($options['rag_chunks_limit']) ? intval($options['rag_chunks_limit']) : 15;
8206 -
8207 - echo '<div class="slider-container">';
8208 - echo sprintf(
8209 - '<input type="range"
8210 - id="rag_chunks_limit"
8211 - name="rag_chunks_limit"
8212 - min="8"
8213 - max="20"
8214 - step="1"
8215 - value="%s"
8216 - class="range-slider" />',
8217 - esc_attr($rag_chunks_limit)
8218 - );
8219 - echo sprintf(
8220 - '<span id="rag_chunks_limit_value" class="range-value">%s</span>',
8221 - esc_html($rag_chunks_limit)
8222 - );
8223 - echo '</div>';
8224 -}
8225 -
8226 7303 public function mxchat_enqueue_admin_assets() {
8227 7304 // Get plugin version
8228 7305 $version = MXCHAT_VERSION;
8229 7306
@@ -8249,11 +7326,8 @@
8249 7326 // Core admin styles
8250 7327 wp_enqueue_style('mxchat-admin-css', $plugin_url . 'css/admin-style.css', array(), $version);
8251 7328 wp_enqueue_style('mxchat-knowledge-css', $plugin_url . 'css/knowledge-style.css', array(), $version);
8252 7329
8253 - // New sidebar navigation styles (for main settings page)
8254 - wp_enqueue_style('mxchat-admin-sidebar-css', $plugin_url . 'css/admin-sidebar.css', array(), $version);
8255 -
8256 7330 // Core admin scripts
8257 7331 wp_enqueue_script('mxchat-admin-js', $plugin_url . 'js/mxchat-admin.js', array('jquery'), $version, true);
8258 7332 }
8259 7333 private function enqueue_page_specific_assets($current_page, $plugin_url, $version) {
@@ -8261,77 +7335,24 @@
8261 7335 case 'mxchat-prompts':
8262 7336 // Knowledge processing page assets
8263 7337 wp_enqueue_style('mxchat-content-selector-css', $plugin_url . 'css/content-selector.css', array(), $version);
8264 7338 wp_enqueue_script('mxchat-content-selector-js', $plugin_url . 'js/content-selector.js', array('jquery'), $version, true);
8265 - // Add the knowledge processing script (common script needed for WordPress dismiss functionality)
8266 - 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);
8267 7341 break;
8268 7342
8269 7343 case 'mxchat-transcripts':
8270 - // Load admin sidebar CSS first (shared styles for sidebar navigation)
8271 - wp_enqueue_style('mxchat-admin-sidebar-css', $plugin_url . 'css/admin-sidebar.css', array(), $version);
8272 - // Load transcripts-specific styles
8273 - 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);
8274 7345 wp_enqueue_script('mxchat-transcripts-js', $plugin_url . 'js/mxchat_transcripts.js', array('jquery'), $version, true);
8275 7346 break;
8276 7347
8277 7348 case 'mxchat-actions':
8278 - // Load admin sidebar CSS first (shared styles for sidebar navigation)
8279 - wp_enqueue_style('mxchat-admin-sidebar-css', $plugin_url . 'css/admin-sidebar.css', array(), $version);
8280 - // Load actions-specific styles
8281 - wp_enqueue_style('mxchat-actions-css', $plugin_url . 'css/actions.css', array('mxchat-admin-sidebar-css'), $version);
8282 - wp_enqueue_script('mxchat-actions-js', $plugin_url . 'js/mxchat_actions.js', array('jquery'), $version, true);
8283 -
8284 - // Localize script data for actions page
8285 - $is_activated = get_option('mxchat_pro_license_status') === 'active';
8286 - wp_localize_script('mxchat-actions-js', 'mxchActionsData', array(
8287 - 'ajaxUrl' => admin_url('admin-ajax.php'),
8288 - 'nonce' => wp_create_nonce('mxchat_actions_nonce'),
8289 - 'addNonce' => wp_create_nonce('mxchat_add_intent_nonce'),
8290 - 'editNonce' => wp_create_nonce('mxchat_edit_intent'),
8291 - 'deleteNonce' => wp_create_nonce('mxchat_delete_intent_nonce'),
8292 - 'toggleNonce' => wp_create_nonce('mxchat_actions_nonce'),
8293 - 'addPhraseNonce' => wp_create_nonce('mxchat_add_phrase_nonce'),
8294 - 'deletePhraseNonce' => wp_create_nonce('mxchat_delete_phrase_nonce'),
8295 - 'getPhrasesNonce' => wp_create_nonce('mxchat_get_phrases_nonce'),
8296 - 'deleteLegacyNonce' => wp_create_nonce('mxchat_delete_legacy_nonce'),
8297 - 'isActivated' => $is_activated,
8298 - 'i18n' => array(
8299 - 'confirmDelete' => __('Are you sure you want to delete this action?', 'mxchat'),
8300 - 'confirmBulkDelete' => __('Are you sure you want to delete the selected actions?', 'mxchat'),
8301 - 'saving' => __('Saving...', 'mxchat'),
8302 - 'saved' => __('Saved successfully!', 'mxchat'),
8303 - 'error' => __('An error occurred. Please try again.', 'mxchat'),
8304 - 'proRequired' => __('This feature requires MxChat Pro.', 'mxchat'),
8305 - 'addonRequired' => __('This feature requires an add-on.', 'mxchat')
8306 - )
8307 - ));
7349 + wp_enqueue_style('mxchat-intent-css', $plugin_url . 'css/intent-style.css', array(), $version);
8308 7350 break;
8309 7351
8310 7352 case 'mxchat-activation':
8311 - // Load admin sidebar CSS first (shared styles for sidebar navigation)
8312 - wp_enqueue_style('mxchat-admin-sidebar-css', $plugin_url . 'css/admin-sidebar.css', array(), $version);
8313 - // Load pro page-specific styles
8314 - wp_enqueue_style('mxchat-pro-css', $plugin_url . 'css/admin-pro.css', array('mxchat-admin-sidebar-css'), $version);
8315 - // Load pro page JavaScript
8316 - wp_enqueue_script('mxchat-pro-js', $plugin_url . 'js/mxchat_pro.js', array('jquery'), $version, true);
8317 - // Load activation script for license activation/deactivation
8318 7353 wp_enqueue_script('mxchat-activation-js', $plugin_url . 'js/activation-script.js', array('jquery'), $version, true);
8319 7354 break;
8320 -
8321 - case 'mxchat-content':
8322 - // Load admin sidebar CSS (shared styles for sidebar navigation)
8323 - wp_enqueue_style('mxchat-admin-sidebar-css', $plugin_url . 'css/admin-sidebar.css', array(), $version);
8324 - // Load content page-specific styles
8325 - wp_enqueue_style('mxchat-content-css', $plugin_url . 'css/admin-content.css', array('mxchat-admin-sidebar-css'), $version);
8326 - // Load content page JavaScript
8327 - wp_enqueue_script('mxchat-content-js', $plugin_url . 'js/mxchat-content.js', array('jquery'), $version, true);
8328 - break;
8329 -
8330 - case 'mxchat-api-access':
8331 - // Shared sidebar styles — page handles its own tab switching inline.
8332 - wp_enqueue_style('mxchat-admin-sidebar-css', $plugin_url . 'css/admin-sidebar.css', array(), $version);
8333 - break;
8334 7355 default:
8335 7356 wp_enqueue_script(
8336 7357 'mxchat-test-streaming-js',
8337 7358 $plugin_url . 'js/mxchat-test-streaming.js',
@@ -8341,80 +7362,10 @@
8341 7362 );
8342 7363
8343 7364 wp_localize_script('mxchat-test-streaming-js', 'mxchatTestStreamingAjax', [
8344 7365 'ajax_url' => admin_url('admin-ajax.php'),
8345 - 'nonce' => wp_create_nonce('mxchat_test_streaming_nonce'),
8346 - 'settings_nonce' => wp_create_nonce('mxchat_save_setting_nonce')
7366 + 'nonce' => wp_create_nonce('mxchat_test_streaming_nonce')
8347 7367 ]);
8348 -
8349 - // Testing Tab: Load chatbot assets for the embedded testing chatbot
8350 - wp_enqueue_style('mxchat-chat-css', $plugin_url . 'css/chat-style.css', array(), $version);
8351 - wp_enqueue_style('mxchat-admin-testing-css', $plugin_url . 'css/admin-testing-tab.css', array('mxchat-admin-sidebar-css', 'mxchat-chat-css'), $version);
8352 -
8353 - wp_enqueue_script('mxchat-chat-js', $plugin_url . 'js/chat-script.js', array('jquery'), $version, true);
8354 - wp_enqueue_script('mxchat-admin-testing-js', $plugin_url . 'js/admin-testing-tab.js', array('jquery', 'mxchat-chat-js'), $version, true);
8355 -
8356 - // Allow add-ons to enqueue their public CSS/JS for the testing chatbot
8357 - do_action('mxchat_enqueue_testing_tab_assets');
8358 -
8359 - // Localize mxchatChat for the chatbot JS (same settings as frontend)
8360 - $options = get_option('mxchat_options', array());
8361 - $prompts_options = get_option('mxchat_prompts_options', array());
8362 - $theme_options = get_option('mxchat_theme_options', array());
8363 - $ai_theme_active = !empty($theme_options['active_ai_theme_css']);
8364 - $has_bot_theme_assignments = !empty($theme_options['bot_theme_assignments']);
8365 - $skip_inline_colors = $ai_theme_active || $has_bot_theme_assignments;
8366 -
8367 - wp_localize_script('mxchat-chat-js', 'mxchatChat', array(
8368 - 'ajax_url' => admin_url('admin-ajax.php'),
8369 - 'nonce' => wp_create_nonce('mxchat_chat_nonce'),
8370 - 'model' => isset($options['model']) ? $options['model'] : 'gpt-5.1-chat-latest',
8371 - 'enable_streaming_toggle' => isset($options['enable_streaming_toggle']) ? $options['enable_streaming_toggle'] : 'on',
8372 - 'contextual_awareness_toggle' => isset($options['contextual_awareness_toggle']) ? $options['contextual_awareness_toggle'] : 'off',
8373 - 'link_target_toggle' => $options['link_target_toggle'] ?? 'off',
8374 - 'rate_limit_message' => $options['rate_limit_message'] ?? 'Rate limit exceeded. Please try again later.',
8375 - 'complianz_toggle' => isset($options['complianz_toggle']) && $options['complianz_toggle'] === 'on',
8376 - 'user_message_bg_color' => $options['user_message_bg_color'] ?? '#fff',
8377 - 'user_message_font_color' => $options['user_message_font_color'] ?? '#212121',
8378 - 'bot_message_bg_color' => $options['bot_message_bg_color'] ?? '#212121',
8379 - 'bot_message_font_color' => $options['bot_message_font_color'] ?? '#fff',
8380 - 'top_bar_bg_color' => $options['top_bar_bg_color'] ?? '#212121',
8381 - 'send_button_font_color' => $options['send_button_font_color'] ?? '#212121',
8382 - 'close_button_color' => $options['close_button_color'] ?? '#fff',
8383 - 'chatbot_background_color' => $options['chatbot_background_color'] ?? '#212121',
8384 - 'chatbot_bg_color' => $options['chatbot_bg_color'] ?? '#fff',
8385 - 'icon_color' => $options['icon_color'] ?? '#fff',
8386 - 'chat_input_font_color' => $options['chat_input_font_color'] ?? '#212121',
8387 - 'chat_persistence_toggle' => 'off', // Always off for testing chatbot
8388 - 'appendWidgetToBody' => 'off',
8389 - 'live_agent_message_bg_color' => $options['live_agent_message_bg_color'] ?? '#ffffff',
8390 - 'live_agent_message_font_color' => $options['live_agent_message_font_color'] ?? '#333333',
8391 - 'chat_toolbar_toggle' => $options['chat_toolbar_toggle'] ?? 'off',
8392 - 'mode_indicator_bg_color' => $options['mode_indicator_bg_color'] ?? '#767676',
8393 - 'mode_indicator_font_color' => $options['mode_indicator_font_color'] ?? '#ffffff',
8394 - 'toolbar_icon_color' => $options['toolbar_icon_color'] ?? '#212121',
8395 - 'use_pinecone' => $prompts_options['mxchat_use_pinecone'] ?? '0',
8396 - 'email_collection_enabled' => 'off', // No email collection in testing
8397 - 'initial_email_state' => null,
8398 - 'skip_email_check' => true,
8399 - 'pinecone_enabled' => isset($prompts_options['mxchat_use_pinecone']) && $prompts_options['mxchat_use_pinecone'] === '1',
8400 - 'skip_inline_colors' => $skip_inline_colors,
8401 - 'bot_theme_assignments' => $theme_options['bot_theme_assignments'] ?? array()
8402 - ));
8403 -
8404 - // Localize testing tab script data
8405 - wp_localize_script('mxchat-admin-testing-js', 'mxchatAdminTestData', array(
8406 - 'ajaxUrl' => admin_url('admin-ajax.php'),
8407 - 'nonce' => wp_create_nonce('mxchat_test_nonce'),
8408 - 'isAdmin' => true,
8409 - 'testingEnabled' => true
8410 - ));
8411 -
8412 - // Add testing enabled flag for the chatbot to return debug data
8413 - add_action('admin_footer', function() {
8414 - echo '<script>window.mxchatTestingEnabled = true;</script>';
8415 - });
8416 -
8417 7368 break;
8418 7369 }
8419 7370 }
8420 7371 private function localize_admin_scripts($current_page) {
@@ -8420,9 +7371,9 @@
8420 7371 private function localize_admin_scripts($current_page) {
8421 7372 // Base localization data for main admin script
8422 7373 $base_data = array(
8423 7374 'ajax_url' => admin_url('admin-ajax.php'),
8424 - 'nonce' => wp_create_nonce('mxchat_admin_nonce'),
7375 + 'nonce' => wp_create_nonce('mxchat_status_nonce'),
8425 7376 'admin_url' => admin_url(),
8426 7377 'license_nonce' => wp_create_nonce('mxchat_activate_license_nonce'),
8427 7378 'inline_edit_nonce' => wp_create_nonce('mxchat_save_inline_nonce'),
8428 7379 'setting_nonce' => wp_create_nonce('mxchat_save_setting_nonce'),
@@ -8429,14 +7380,10 @@
8429 7380 'export_nonce' => wp_create_nonce('mxchat_export_transcripts'),
8430 7381 'actions_nonce' => wp_create_nonce('mxchat_actions_nonce'),
8431 7382 'add_intent_nonce' => wp_create_nonce('mxchat_add_intent_nonce'),
8432 7383 'edit_intent_nonce' => wp_create_nonce('mxchat_edit_intent'),
8433 - 'add_phrase_nonce' => wp_create_nonce('mxchat_add_phrase_nonce'),
8434 - 'delete_phrase_nonce' => wp_create_nonce('mxchat_delete_phrase_nonce'),
8435 - 'get_phrases_nonce' => wp_create_nonce('mxchat_get_phrases_nonce'),
8436 - 'delete_legacy_nonce' => wp_create_nonce('mxchat_delete_legacy_nonce'),
8437 7384 'toggle_action_nonce' => wp_create_nonce('mxchat_actions_nonce'),
8438 - '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
8439 7386 'is_activated' => $this->is_activated ? '1' : '0',
8440 7387 'status_refresh_interval' => 5000,
8441 7388 'prompts_setting_nonce' => wp_create_nonce('mxchat_prompts_setting_nonce'),
8442 7389 'ajaxurl' => admin_url('admin-ajax.php')
@@ -8476,13 +7423,11 @@
8476 7423 'queue_nonce' => wp_create_nonce('mxchat_queue_nonce'),
8477 7424 'stop_nonce' => wp_create_nonce('mxchat_stop_processing_action'),
8478 7425 'settings_nonce' => wp_create_nonce('mxchat_prompts_setting_nonce'),
8479 7426 'setting_nonce' => wp_create_nonce('mxchat_save_setting_nonce'),
8480 - 'entries_nonce' => wp_create_nonce('mxchat_entries_nonce'),
8481 7427 'admin_url' => admin_url(),
8482 7428 'ajaxurl' => admin_url('admin-ajax.php'),
8483 - 'status_refresh_interval' => 2000,
8484 - 'bot_id' => isset($_GET['bot_id']) ? sanitize_text_field($_GET['bot_id']) : 'default'
7429 + 'status_refresh_interval' => 2000
8485 7430 ));
8486 7431 break;
8487 7432
8488 7433 case 'mxchat-activation':
@@ -8491,60 +7436,8 @@
8491 7436 'license_nonce' => wp_create_nonce('mxchat_activate_license_nonce')
8492 7437 ));
8493 7438 break;
8494 7439
8495 - case 'mxchat-content':
8496 - wp_localize_script('mxchat-content-js', 'mxchatContent', array(
8497 - 'ajaxUrl' => admin_url('admin-ajax.php'),
8498 - 'nonce' => wp_create_nonce('mxchat_content_nonce'),
8499 - 'settingNonce' => wp_create_nonce('mxchat_save_setting_nonce'),
8500 - 'previewUrl' => home_url('/?p='),
8501 - 'isActivated' => $this->is_activated(),
8502 - 'hasAdvancedContent' => apply_filters('mxchat_content_pro_feature', false, 'seo_readability'),
8503 - 'hasGSC' => apply_filters('mxchat_content_pro_feature', false, 'gsc_integration'),
8504 - 'seoOptimize' => array(
8505 - 'meta_description' => ($options['seo_optimize_meta_desc'] ?? 'on') === 'on',
8506 - 'seo_title' => ($options['seo_optimize_seo_title'] ?? 'on') === 'on',
8507 - 'slug' => ($options['seo_optimize_slug'] ?? 'on') === 'on',
8508 - 'readability' => ($options['seo_optimize_readability'] ?? 'on') === 'on',
8509 - 'internal_links' => ($options['seo_optimize_internal_links'] ?? 'on') === 'on',
8510 - 'img_alt' => ($options['seo_optimize_img_alt'] ?? 'on') === 'on',
8511 - 'featured_img' => ($options['seo_optimize_featured_img'] ?? 'on') === 'on',
8512 - ),
8513 - 'i18n' => array(
8514 - 'generating' => __('Generating...', 'mxchat'),
8515 - 'planning' => __('Planning content structure...', 'mxchat'),
8516 - 'images' => __('Generating images...', 'mxchat'),
8517 - 'writing' => __('Writing full content...', 'mxchat'),
8518 - 'creating' => __('Creating WordPress post...', 'mxchat'),
8519 - 'done' => __('Content generated successfully!', 'mxchat'),
8520 - 'error' => __('An error occurred. Please try again.', 'mxchat'),
8521 - 'editSuccess' => __('Content updated successfully.', 'mxchat'),
8522 - 'promptEmpty' => __('Please enter a prompt.', 'mxchat'),
8523 - )
8524 - ));
8525 - break;
8526 -
8527 - case 'mxchat-transcripts':
8528 - // Get chart data for localization
8529 - $chart_data = $this->get_transcripts_chart_data();
8530 -
8531 - wp_localize_script('mxchat-transcripts-js', 'mxchatAdmin', array(
8532 - 'ajax_url' => admin_url('admin-ajax.php'),
8533 - 'export_nonce' => wp_create_nonce('mxchat_export_transcripts'),
8534 - 'delete_nonce' => wp_create_nonce('mxchat_delete_chat_history'),
8535 - 'setting_nonce' => wp_create_nonce('mxchat_save_setting_nonce'),
8536 - 'translate_nonce' => wp_create_nonce('mxchat_translate_messages')
8537 - ));
8538 -
8539 - // Localize chart data separately - use array_values to ensure proper JSON array encoding
8540 - wp_localize_script('mxchat-transcripts-js', 'mxchatChartData', array(
8541 - 'labels' => array_values($chart_data['labels']),
8542 - 'chats' => array_values($chart_data['chats']),
8543 - 'messages' => array_values($chart_data['messages'])
8544 - ));
8545 - break;
8546 -
8547 7440 case 'mxchat-settings':
8548 7441 default:
8549 7442 // Color picker and settings localization
8550 7443 wp_localize_script('mxchat-color-picker', 'mxchatStyleSettings', array(
@@ -8603,18 +7496,8 @@
8603 7496 $new_input['similarity_threshold'] = absint($input['similarity_threshold']); // Ensure it's an integer
8604 7497 $new_input['similarity_threshold'] = min(max($new_input['similarity_threshold'], 20), 85); // Enforce range
8605 7498 }
8606 7499
8607 - if (isset($input['rag_sources_limit'])) {
8608 - $new_input['rag_sources_limit'] = absint($input['rag_sources_limit']); // Ensure it's an integer
8609 - $new_input['rag_sources_limit'] = min(max($new_input['rag_sources_limit'], 3), 10); // Enforce range 3-10
8610 - }
8611 -
8612 - if (isset($input['rag_chunks_limit'])) {
8613 - $new_input['rag_chunks_limit'] = absint($input['rag_chunks_limit']); // Ensure it's an integer
8614 - $new_input['rag_chunks_limit'] = min(max($new_input['rag_chunks_limit'], 8), 20); // Enforce range 8-20
8615 - }
8616 -
8617 7500 if (isset($input['xai_api_key'])) {
8618 7501 $new_input['xai_api_key'] = sanitize_text_field($input['xai_api_key']);
8619 7502 }
8620 7503
@@ -8628,14 +7511,8 @@
8628 7511 // If checkbox not checked, it won't be in $input, so set to 'off'
8629 7512 $new_input['enable_streaming_toggle'] = 'off';
8630 7513 }
8631 7514
8632 - if (isset($input['enable_web_search'])) {
8633 - $new_input['enable_web_search'] = ($input['enable_web_search'] === 'on') ? 'on' : 'off';
8634 - } else {
8635 - // If checkbox not checked, it won't be in $input, so set to 'off'
8636 - $new_input['enable_web_search'] = 'off';
8637 - }
8638 7515
8639 7516 if (isset($input['deepseek_api_key'])) {
8640 7517 $new_input['deepseek_api_key'] = sanitize_text_field($input['deepseek_api_key']);
8641 7518 }
@@ -8677,32 +7554,12 @@
8677 7554 if (isset($input['append_to_body'])) {
8678 7555 $new_input['append_to_body'] = $input['append_to_body'] === 'on' ? 'on' : 'off';
8679 7556 }
8680 7557
8681 - // Post type visibility settings
8682 - if (isset($input['post_type_visibility_mode'])) {
8683 - $allowed_modes = array('all', 'include', 'exclude');
8684 - $new_input['post_type_visibility_mode'] = in_array($input['post_type_visibility_mode'], $allowed_modes)
8685 - ? $input['post_type_visibility_mode']
8686 - : 'all';
8687 - }
8688 -
8689 - if (isset($input['post_type_visibility_list'])) {
8690 - if (is_array($input['post_type_visibility_list'])) {
8691 - $new_input['post_type_visibility_list'] = array_map('sanitize_key', $input['post_type_visibility_list']);
8692 - } else {
8693 - $new_input['post_type_visibility_list'] = array();
8694 - }
8695 - }
8696 -
8697 7558 if (isset($input['contextual_awareness_toggle'])) {
8698 7559 $new_input['contextual_awareness_toggle'] = $input['contextual_awareness_toggle'] === 'on' ? 'on' : 'off';
8699 7560 }
8700 7561
8701 - if (isset($input['citation_links_toggle'])) {
8702 - $new_input['citation_links_toggle'] = $input['citation_links_toggle'] === 'on' ? 'on' : 'off';
8703 -}
8704 -
8705 7562 if (isset($input['top_bar_title'])) {
8706 7563 $new_input['top_bar_title'] = sanitize_text_field($input['top_bar_title']);
8707 7564 }
8708 7565
@@ -8793,9 +7650,9 @@
8793 7650 'text-embedding-ada-002',
8794 7651 'text-embedding-3-small',
8795 7652 'text-embedding-3-large',
8796 7653 'voyage-3-large',
8797 - 'gemini-embedding-001'
7654 + 'gemini-embedding-exp-03-07'
8798 7655 );
8799 7656 if (in_array($input['embedding_model'], $allowed_models)) {
8800 7657 $new_input['embedding_model'] = sanitize_text_field($input['embedding_model']);
8801 7658 }
@@ -8805,13 +7662,8 @@
8805 7662 if ($input['model'] === 'openrouter') {
8806 7663 $new_input['model'] = 'openrouter';
8807 7664 } else {
8808 7665 $allowed_models = array(
8809 - 'gemini-3-pro-preview',
8810 - 'gemini-3-flash-preview',
8811 - 'gemini-2.5-pro',
8812 - 'gemini-2.5-flash',
8813 - 'gemini-2.5-flash-lite',
8814 7666 'gemini-2.0-flash',
8815 7667 'gemini-2.0-flash-lite',
8816 7668 'gemini-1.5-pro',
8817 7669 'gemini-1.5-flash',
@@ -8823,33 +7675,35 @@
8823 7675 'grok-3-mini-beta',
8824 7676 'grok-3-mini-fast-beta',
8825 7677 'grok-2',
8826 7678 'deepseek-chat',
8827 - 'claude-opus-4-6',
8828 - 'claude-sonnet-4-6',
8829 - 'claude-opus-4-5',
8830 7679 'claude-sonnet-4-5-20250929',
8831 7680 'claude-opus-4-1-20250805',
8832 7681 'claude-haiku-4-5-20251001',
8833 7682 'claude-opus-4-20250514',
8834 7683 'claude-sonnet-4-20250514',
8835 - 'gpt-5.2',
8836 - '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',
8837 7689 'gpt-5.1-2025-11-13',
8838 7690 'gpt-5',
8839 7691 'gpt-5-mini',
8840 7692 'gpt-5-nano',
8841 - 'gpt-5.4',
8842 - 'gpt-5.4-mini',
8843 - 'gpt-5.4-nano',
8844 - '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',
8845 7699 );
8846 -
7700 +
8847 7701 if (in_array($input['model'], $allowed_models)) {
8848 7702 $new_input['model'] = sanitize_text_field($input['model']);
8849 7703 } else {
8850 7704 // Fallback for any deprecated model
8851 - $new_input['model'] = 'gpt-5.1-chat-latest';
7705 + $new_input['model'] = 'claude-3-7-sonnet-20250219';
8852 7706 }
8853 7707 }
8854 7708 }
8855 7709
@@ -8867,9 +7721,16 @@
8867 7721 $new_input['openrouter_api_key'] = sanitize_text_field($input['openrouter_api_key']);
8868 7722 }
8869 7723
8870 7724
7725 + if (isset($input['close_button_color'])) {
7726 + $new_input['close_button_color'] = sanitize_hex_color($input['close_button_color']);
7727 + }
8871 7728
7729 + if (isset($input['chatbot_bg_color'])) {
7730 + $new_input['chatbot_bg_color'] = sanitize_hex_color($input['chatbot_bg_color']);
7731 + }
7732 +
8872 7733 if (isset($input['woocommerce_consumer_key'])) {
8873 7734 $new_input['woocommerce_consumer_key'] = sanitize_text_field($input['woocommerce_consumer_key']);
8874 7735 }
8875 7736
@@ -8876,9 +7737,76 @@
8876 7737 if (isset($input['woocommerce_consumer_secret'])) {
8877 7738 $new_input['woocommerce_consumer_secret'] = sanitize_text_field($input['woocommerce_consumer_secret']);
8878 7739 }
8879 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 + }
8880 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 +
8881 7809 // Sanitize link_target_toggle
8882 7810 if (isset($input['link_target_toggle'])) {
8883 7811 $new_input['link_target_toggle'] = $input['link_target_toggle'] === 'on' ? 'on' : 'off';
8884 7812 }
@@ -9011,226 +7939,12 @@
9011 7939 if (isset($input['live_agent_notification_message'])) {
9012 7940 $new_input['live_agent_notification_message'] = sanitize_textarea_field($input['live_agent_notification_message']);
9013 7941 }
9014 7942
9015 - // Telegram Integration
9016 - if (isset($input['telegram_status'])) {
9017 - $new_input['telegram_status'] = ($input['telegram_status'] === 'on') ? 'on' : 'off';
9018 - }
9019 - if (isset($input['telegram_bot_token'])) {
9020 - $new_input['telegram_bot_token'] = sanitize_text_field($input['telegram_bot_token']);
9021 - }
9022 - if (isset($input['telegram_group_id'])) {
9023 - $new_input['telegram_group_id'] = sanitize_text_field($input['telegram_group_id']);
9024 - }
9025 - if (isset($input['telegram_webhook_secret'])) {
9026 - $new_input['telegram_webhook_secret'] = sanitize_text_field($input['telegram_webhook_secret']);
9027 - }
9028 - if (isset($input['telegram_notification_message'])) {
9029 - $new_input['telegram_notification_message'] = sanitize_textarea_field($input['telegram_notification_message']);
9030 - }
9031 - if (isset($input['telegram_away_message'])) {
9032 - $new_input['telegram_away_message'] = sanitize_textarea_field($input['telegram_away_message']);
9033 - }
9034 -
9035 - // Sanitize script loading strategy
9036 - if (isset($input['script_loading_strategy'])) {
9037 - $allowed_strategies = array('default', 'defer', 'delay_1s', 'delay_3s', 'delay_5s', 'on_interaction');
9038 - $new_input['script_loading_strategy'] = in_array($input['script_loading_strategy'], $allowed_strategies)
9039 - ? $input['script_loading_strategy']
9040 - : 'default';
9041 - }
9042 -
9043 - // Sanitize debug mode
9044 - if (isset($input['debug_mode'])) {
9045 - $new_input['debug_mode'] = ($input['debug_mode'] === 'on') ? 'on' : 'off';
9046 - }
9047 -
9048 - // Content Generator Settings
9049 - if (isset($input['content_model'])) {
9050 - $new_input['content_model'] = sanitize_text_field($input['content_model']);
9051 - }
9052 - if (isset($input['content_image_model'])) {
9053 - $new_input['content_image_model'] = sanitize_text_field($input['content_image_model']);
9054 - }
9055 - if (isset($input['content_enable_images'])) {
9056 - $new_input['content_enable_images'] = ($input['content_enable_images'] === 'on') ? 'on' : 'off';
9057 - }
9058 - if (isset($input['content_use_placeholders'])) {
9059 - $new_input['content_use_placeholders'] = ($input['content_use_placeholders'] === 'on') ? 'on' : 'off';
9060 - }
9061 - if (isset($input['content_internal_linking'])) {
9062 - $new_input['content_internal_linking'] = ($input['content_internal_linking'] === 'on') ? 'on' : 'off';
9063 - }
9064 - if (isset($input['content_tool_use'])) {
9065 - $new_input['content_tool_use'] = ($input['content_tool_use'] === 'on') ? 'on' : 'off';
9066 - }
9067 -
9068 - // SEO Optimize toggle fields
9069 - 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) {
9070 - if (isset($input[$seo_key])) {
9071 - $new_input[$seo_key] = ($input[$seo_key] === 'on') ? 'on' : 'off';
9072 - }
9073 - }
9074 -
9075 - // Preserve content generator settings when saving from main settings page
9076 - // (where content fields are not in the form submission)
9077 - $existing = get_option('mxchat_options', array());
9078 - 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) {
9079 - if (!isset($new_input[$key]) && isset($existing[$key])) {
9080 - $new_input[$key] = $existing[$key];
9081 - }
9082 - }
9083 -
9084 7943 return $new_input;
9085 7944 }
9086 7945
9087 -/**
9088 - * Log a debug message if debug mode is enabled
9089 - *
9090 - * @param string $type The type of log entry (settings_save, api_error, activation, etc.)
9091 - * @param string $message The log message
9092 - * @param array $data Optional additional data to log
9093 - * @return bool Whether the message was logged
9094 - */
9095 -public static function mxchat_log_debug( $type, $message, $data = array() ) {
9096 - $options = get_option( 'mxchat_options', array() );
9097 7946
9098 - // Check if debug mode is enabled
9099 - if ( ! isset( $options['debug_mode'] ) || $options['debug_mode'] !== 'on' ) {
9100 - return false;
9101 - }
9102 -
9103 - // Get current log
9104 - $log = get_option( 'mxchat_debug_log', array() );
9105 - if ( ! is_array( $log ) ) {
9106 - $log = array();
9107 - }
9108 -
9109 - // Add new entry
9110 - $entry = array(
9111 - 'time' => current_time( 'Y-m-d H:i:s' ),
9112 - 'type' => sanitize_key( $type ),
9113 - 'message' => sanitize_text_field( $message ),
9114 - );
9115 -
9116 - if ( ! empty( $data ) ) {
9117 - $entry['data'] = $data;
9118 - }
9119 -
9120 - // Add to beginning of array (newest first)
9121 - array_unshift( $log, $entry );
9122 -
9123 - // Keep only last 100 entries
9124 - if ( count( $log ) > 100 ) {
9125 - $log = array_slice( $log, 0, 100 );
9126 - }
9127 -
9128 - // Save log
9129 - update_option( 'mxchat_debug_log', $log, false );
9130 -
9131 - return true;
9132 -}
9133 -
9134 -/**
9135 - * Get the debug log entries
9136 - *
9137 - * @return array The debug log entries
9138 - */
9139 -public static function mxchat_get_debug_log() {
9140 - $log = get_option( 'mxchat_debug_log', array() );
9141 - return is_array( $log ) ? $log : array();
9142 -}
9143 -
9144 -/**
9145 - * Clear the debug log
9146 - *
9147 - * @return bool Whether the log was cleared
9148 - */
9149 -public static function mxchat_clear_debug_log() {
9150 - return delete_option( 'mxchat_debug_log' );
9151 -}
9152 -
9153 -/**
9154 - * Export settings as JSON with masked API keys
9155 - *
9156 - * @return array The sanitized settings array
9157 - */
9158 -public static function mxchat_export_settings() {
9159 - $options = get_option( 'mxchat_options', array() );
9160 -
9161 - if ( ! is_array( $options ) ) {
9162 - return array();
9163 - }
9164 -
9165 - // List of API key fields to mask
9166 - $api_key_fields = array(
9167 - 'api_key',
9168 - 'xai_api_key',
9169 - 'claude_api_key',
9170 - 'deepseek_api_key',
9171 - 'voyage_api_key',
9172 - 'gemini_api_key',
9173 - 'openrouter_api_key',
9174 - 'loops_api_key',
9175 - 'brave_api_key',
9176 - 'live_agent_secret_key',
9177 - 'live_agent_bot_token',
9178 - 'telegram_bot_token',
9179 - 'telegram_webhook_secret',
9180 - 'woocommerce_consumer_key',
9181 - 'woocommerce_consumer_secret',
9182 - );
9183 -
9184 - // Mask API keys (show only last 4 characters)
9185 - foreach ( $api_key_fields as $field ) {
9186 - if ( isset( $options[ $field ] ) && ! empty( $options[ $field ] ) ) {
9187 - $value = $options[ $field ];
9188 - if ( strlen( $value ) > 4 ) {
9189 - $options[ $field ] = str_repeat( '*', strlen( $value ) - 4 ) . substr( $value, -4 );
9190 - } else {
9191 - $options[ $field ] = '****';
9192 - }
9193 - }
9194 - }
9195 -
9196 - // Add metadata
9197 - $export = array(
9198 - 'plugin_version' => defined( 'MXCHAT_VERSION' ) ? MXCHAT_VERSION : 'unknown',
9199 - 'export_date' => current_time( 'Y-m-d H:i:s' ),
9200 - 'wordpress_version' => get_bloginfo( 'version' ),
9201 - 'php_version' => phpversion(),
9202 - 'settings' => $options,
9203 - );
9204 -
9205 - return $export;
9206 -}
9207 -
9208 -/**
9209 - * Reset all settings to defaults
9210 - *
9211 - * @return bool Whether the reset was successful
9212 - */
9213 -public static function mxchat_reset_all_settings() {
9214 - // Delete main options
9215 - $deleted = delete_option( 'mxchat_options' );
9216 -
9217 - // Also clear the debug log
9218 - delete_option( 'mxchat_debug_log' );
9219 -
9220 - // Log the reset (will create new log since we just cleared it)
9221 - // We need to temporarily enable debug mode to log this
9222 - $temp_options = array( 'debug_mode' => 'on' );
9223 - update_option( 'mxchat_options', $temp_options );
9224 -
9225 - self::mxchat_log_debug( 'reset', 'All settings have been reset to defaults' );
9226 -
9227 - // Now delete again to trigger re-initialization
9228 - delete_option( 'mxchat_options' );
9229 -
9230 - return $deleted;
9231 -}
9232 -
9233 7947 // Method to append the chatbot to the body
9234 7948 public function mxchat_append_chatbot_to_body() {
9235 7949 $options = get_option('mxchat_options');
9236 7950 if (isset($options['append_to_body']) && $options['append_to_body'] === 'on') {
@@ -9300,65 +8014,73 @@
9300 8014 //error_log('DEBUG: Permission check failed');
9301 8015 wp_die(__('You do not have sufficient permissions to delete all prompts.', 'mxchat'));
9302 8016 }
9303 8017
9304 - // Get bot_id and content type filter from POST data
8018 + // Get bot_id from POST data
9305 8019 $bot_id = isset($_POST['bot_id']) ? sanitize_text_field($_POST['bot_id']) : 'default';
9306 - $content_type_filter = isset($_POST['content_type_filter']) ? sanitize_text_field($_POST['content_type_filter']) : '';
9307 -
8020 + //error_log('DEBUG: Bot ID from POST: ' . $bot_id);
8021 +
9308 8022 $success = true;
9309 8023 $error_messages = array();
9310 -
8024 +
9311 8025 // Get bot-specific Pinecone configuration
9312 8026 $pinecone_manager = MxChat_Pinecone_Manager::get_instance();
9313 8027 $pinecone_options = $pinecone_manager->mxchat_get_bot_pinecone_options($bot_id);
9314 -
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 +
9315 8034 $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
9316 -
8035 +
9317 8036 if ($use_pinecone && !empty($pinecone_options['mxchat_pinecone_api_key'])) {
9318 - // Delete from Pinecone (with optional content type filter)
9319 - $result = $pinecone_manager->mxchat_delete_all_from_pinecone($pinecone_options, $content_type_filter);
9320 -
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'));
9321 8043 if (!$result['success']) {
8044 + //error_log('DEBUG: Delete all error: ' . $result['message']);
8045 + }
8046 +
8047 + if (!$result['success']) {
9322 8048 $success = false;
9323 8049 $error_messages[] = $result['message'];
9324 8050 }
9325 -
8051 +
8052 + // No cache clearing needed since we removed caching
8053 +
9326 8054 } else {
8055 + //error_log('[MXCHAT-DELETE] Pinecone not enabled for bot ' . $bot_id . ' - deleting from WordPress database');
8056 +
9327 8057 // Delete from WordPress database
9328 8058 global $wpdb;
9329 8059 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
9330 -
9331 - // Build WHERE conditions
9332 - $where_clauses = array();
9333 - $where_values = array();
9334 -
9335 - // Bot filter
8060 +
8061 + // If multi-bot is active and we have a specific bot, delete only that bot's content
9336 8062 if ($bot_id !== 'default' && class_exists('MxChat_Multi_Bot_Manager')) {
9337 - $where_clauses[] = 'bot_id = %s';
9338 - $where_values[] = $bot_id;
9339 - }
9340 -
9341 - // Content type filter
9342 - if (!empty($content_type_filter)) {
9343 - $where_clauses[] = 'content_type = %s';
9344 - $where_values[] = $content_type_filter;
9345 - }
9346 -
9347 - if (!empty($where_clauses)) {
9348 - $where_sql = implode(' AND ', $where_clauses);
9349 - $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 + );
9350 8068 } else {
9351 - // No filters — delete all
8069 + // Delete all content for default bot
9352 8070 $result = $wpdb->query("DELETE FROM {$table_name}");
9353 8071 }
9354 -
8072 +
9355 8073 if ($result === false) {
9356 8074 $success = false;
9357 8075 $error_messages[] = 'Failed to delete from WordPress database';
9358 8076 }
9359 8077 }
8078 +
8079 + // No cache clearing needed since we removed caching
9360 8080
8081 + //error_log('=== DELETE ALL DEBUG END ===');
8082 +
9361 8083 // Redirect back with a success message and bot_id
9362 8084 $redirect_url = add_query_arg(array(
9363 8085 'page' => 'mxchat-prompts',
9364 8086 'bot_id' => $bot_id,
@@ -9663,9 +8385,8 @@
9663 8385 if ($embedding_dimensions !== 1536) {
9664 8386 //error_log('[MXCHAT-EMBED] Warning: Unexpected Gemini embedding dimensions: ' . $embedding_dimensions);
9665 8387 }
9666 8388
9667 - MxChat_Utils::stamp_active_embedding_model($selected_model);
9668 8389 return $response_data['embedding']['values'];
9669 8390 } else {
9670 8391 //error_log('[MXCHAT-EMBED] Error: No embedding found in Gemini response');
9671 8392 //error_log('[MXCHAT-EMBED] Response structure: ' . wp_json_encode(array_keys($response_data)));
@@ -9691,9 +8412,8 @@
9691 8412 ($selected_model === 'voyage-3-large' && $embedding_dimensions !== 2048)) {
9692 8413 //error_log('[MXCHAT-EMBED] Warning: Unexpected embedding dimensions');
9693 8414 }
9694 8415
9695 - MxChat_Utils::stamp_active_embedding_model($selected_model);
9696 8416 return $response_data['data'][0]['embedding'];
9697 8417 } else {
9698 8418 //error_log('[MXCHAT-EMBED] Error: No embedding found in response');
9699 8419 //error_log('[MXCHAT-EMBED] Response structure: ' . wp_json_encode(array_keys($response_data)));