PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 2.1.5
MxChat – AI Chatbot & Content Generation for WordPress v2.1.5
3.2.21 3.2.20 3.2.19 3.2.18 3.2.17 3.2.16 3.2.15 3.2.14 3.2.12 3.2.13 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 3.2.6 3.2.5 3.2.4 3.2.3 3.2.2 3.2.1 2.0.3 2.0.4 2.0.5 2.0.6 All 152 releases
← All changes | includes/class-mxchat-admin.php +3315 -1294 2.0.42.1.5 View file →
@@ -37,16 +37,18 @@
37 37 add_action('wp_ajax_mxchat_save_inline_prompt', array($this, 'mxchat_save_inline_prompt'));
38 38 add_action('admin_post_mxchat_delete_all_prompts', array($this, 'mxchat_handle_delete_all_prompts'));
39 39 add_action('admin_post_mxchat_add_intent', array($this, 'mxchat_handle_add_intent'));
40 40 add_action('admin_post_mxchat_delete_intent', array($this, 'mxchat_handle_delete_intent'));
41 - add_action('admin_post_mxchat_update_intent_threshold', array($this, 'mxchat_handle_update_intent_threshold'));
41 + add_action('wp_ajax_mxchat_toggle_action', array($this, 'mxchat_toggle_action'));
42 + add_action('wp_ajax_mxchat_update_intent_threshold', array($this, 'mxchat_update_intent_threshold'));
42 43 add_action('admin_post_mxchat_stop_processing', array($this, 'mxchat_stop_processing'));
43 - add_action('admin_post_mxchat_edit_intent', array($this, 'handle_edit_intent'));
44 + add_action('admin_post_mxchat_edit_intent', array($this, 'mxchat_handle_edit_intent'));
44 45 add_action('save_post', array($this, 'handle_post_update'), 10, 3);
45 46 add_action('post_updated', array($this, 'handle_post_update'), 10, 3);
46 47 add_action('wp_ajax_mxchat_save_setting', array($this, 'mxchat_save_setting_callback'));
47 48 add_action('wp_trash_post', array($this, 'mxchat_handle_post_delete'));
48 49 add_action('before_delete_post', array($this, 'mxchat_handle_post_delete'));
50 + add_action('wp_ajax_mxchat_export_transcripts', array($this, 'export_chat_transcripts'));
49 51 add_action('wp_ajax_mxchat_save_prompts_setting', array($this, 'mxchat_save_prompts_setting_callback'));
50 52 if (isset($this->options['enable_woocommerce_integration']) &&
51 53 ($this->options['enable_woocommerce_integration'] === '1' ||
52 54 $this->options['enable_woocommerce_integration'] === 'on')) {
@@ -55,8 +57,12 @@
55 57 add_action('wp_insert_post', array($this, 'mxchat_handle_product_change'), 10, 3);
56 58 add_action('wp_trash_post', array($this, 'mxchat_handle_product_delete'));
57 59 add_action('before_delete_post', array($this, 'mxchat_handle_product_delete'));
58 60 }
61 +
62 + add_action('wp_ajax_mxchat_get_status_updates', array($this, 'ajax_get_status_updates'));
63 + add_action('admin_notices', array($this, 'display_admin_notices'));
64 +
59 65 }
60 66
61 67 // Method to check if the license is active
62 68 private function is_license_active() {
@@ -70,8 +76,11 @@
70 76 'api_key' => '',
71 77 'xai_api_key' => '',
72 78 'claude_api_key' => '',
73 79 'deepseek_api_key' => '',
80 + 'voyage_api_key' => '',
81 + 'gemini_api_key' => '',
82 + 'embedding_model' => 'text-embedding-ada-002',
74 83 'system_prompt_instructions' => esc_html__('[EXAMPLE INSTRUCTIONS] You are an AI Chatbot assistant for this website. The primary subject you should focus on is [insert proper subject here]. Your main goal is to assist visitors with questions related to this specific topic. Here are some key things to keep in mind:
75 84 - Your name is [Chatbot Name].
76 85 - Stay focused on topics related to [insert proper subject here]. If a visitor asks about an unrelated topic, politely redirect the conversation to how you can assist them with this subject.
77 86 - Do not make up links to pages. Only use specific links you see in your knowledgebase from [insert your domain]. Do not make assumptions or make up URLs.
@@ -81,12 +90,13 @@
81 90 'rate_limit_logged_out' => esc_html__('100', 'mxchat'),
82 91 'role_rate_limits' => array(),
83 92 'rate_limit_message' => esc_html__('Rate limit exceeded. Please try again later.', 'mxchat'),
84 93 'enable_email_block' => '',
85 - 'email_blocker_header_content' => esc_html__("<h2>Welcome to Our Chat!</h2>\n<p>Let's get started. Enter your email to begin chatting with us.</p>", 'mxchat'),
94 + 'email_blocker_header_content' => __("<h2>Welcome to Our Chat!</h2>\n<p>Let's get started. Enter your email to begin chatting with us.</p>", 'mxchat'),
86 95 'email_blocker_button_text' => esc_html__('Start Chat', 'mxchat'),
87 96 'top_bar_title' => esc_html__('MxChat', 'mxchat'),
88 - 'intro_message' => esc_html__('Hello! How can I assist you today?', 'mxchat'),
97 + 'intro_message' => __('Hello! How can I assist you today?', 'mxchat'),
98 + 'ai_agent_text' => esc_html__('AI Agent', 'mxchat'),
89 99 'input_copy' => esc_html__('How can I assist?', 'mxchat'),
90 100 'append_to_body' => esc_html__('off', 'mxchat'),
91 101 'close_button_color' => esc_html__('#fff', 'mxchat'),
92 102 'chatbot_bg_color' => esc_html__('#fff', 'mxchat'),
@@ -114,8 +124,10 @@
114 124 'pdf_intent_trigger_text' => esc_html__("Please provide the URL to the PDF you'd like to discuss.", 'mxchat'),
115 125 'pdf_intent_success_text' => esc_html__("I've processed the PDF. What questions do you have about it?", 'mxchat'),
116 126 'pdf_intent_error_text' => esc_html__("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat'),
117 127 'pdf_max_pages' => 69,
128 + 'show_pdf_upload_button' => 'on',
129 + 'show_word_upload_button' => 'on',
118 130
119 131 // Live Agent Integration
120 132 'live_agent_webhook_url' => '',
121 133 'live_agent_secret_key' => '',
@@ -173,26 +185,24 @@
173 185 'mxchat-prompts',
174 186 array($this, 'mxchat_create_prompts_page')
175 187 );
176 188
177 - // Submenu page for Chat Transcripts
178 189 add_submenu_page(
179 - 'mxchat-max', // Corrected parent slug to match the main menu
190 + 'mxchat-max',
180 191 esc_html__('Chat Transcripts', 'mxchat'),
181 192 esc_html__('Transcripts', 'mxchat'),
182 193 'manage_options',
183 194 'mxchat-transcripts',
184 - array($this, 'mxchat_create_transcripts_page') // Prefixed function name with mxchat_
195 + array($this, 'mxchat_create_transcripts_page')
185 196 );
186 197
187 - // Submenu page for Intents
188 198 add_submenu_page(
189 - 'mxchat-max', // Parent slug
190 - esc_html__('MxChat Intents', 'mxchat'), // Page title
191 - esc_html__('Intents', 'mxchat'), // Menu title
192 - 'manage_options', // Capability
193 - 'mxchat-intents', // Menu slug
194 - array($this, 'mxchat_intents_page_html') // Callback function
199 + 'mxchat-max',
200 + esc_html__('MxChat Actions', 'mxchat'),
201 + esc_html__('Actions', 'mxchat'),
202 + 'manage_options',
203 + 'mxchat-actions',
204 + array($this, 'mxchat_actions_page_html')
195 205 );
196 206
197 207 add_submenu_page(
198 208 'mxchat-max',
@@ -224,16 +234,20 @@
224 234 check_ajax_referer('mxchat_save_setting_nonce');
225 235 if (!current_user_can('manage_options')) {
226 236 wp_send_json_error(['message' => esc_html__('Unauthorized', 'mxchat')]);
227 237 }
238 +
228 239 $name = isset($_POST['name']) ? $_POST['name'] : '';
229 240 // Strip slashes from the value before saving
230 241 $value = isset($_POST['value']) ? stripslashes($_POST['value']) : '';
242 +
231 243 if (empty($name)) {
232 244 wp_send_json_error(['message' => esc_html__('Invalid field name', 'mxchat')]);
233 245 }
246 +
234 247 // Load the full options array
235 248 $options = get_option('mxchat_options', []);
249 +
236 250 // Handle special cases
237 251 switch ($name) {
238 252 case 'additional_popular_questions':
239 253 $questions = json_decode($value, true); // No need for stripslashes here
@@ -242,8 +256,16 @@
242 256 // Also update old option for backwards compatibility
243 257 update_option('additional_popular_questions', $questions);
244 258 }
245 259 break;
260 + case 'email_blocker_header_content':
261 + // Allow HTML content but sanitize it safely
262 + $options[$name] = wp_kses_post($value);
263 + break;
264 + case 'similarity_threshold':
265 + // Save to the options array
266 + $options[$name] = $value;
267 + break;
246 268 case 'user_message_bg_color':
247 269 case 'user_message_font_color':
248 270 case 'bot_message_bg_color':
249 271 case 'bot_message_font_color':
@@ -288,9 +310,11 @@
288 310 if (strpos($name, 'toggle') !== false || in_array($name, [
289 311 'chat_persistence_toggle',
290 312 'privacy_toggle',
291 313 'complianz_toggle',
292 - 'chat_toolbar_toggle' // Removed live_agent_status from here
314 + 'chat_toolbar_toggle',
315 + 'show_pdf_upload_button',
316 + 'show_word_upload_button'
293 317 ])) {
294 318 $options[$name] = ($value === 'on') ? 'on' : 'off';
295 319 } else {
296 320 // Store all other values directly
@@ -297,22 +321,44 @@
297 321 $options[$name] = $value;
298 322 }
299 323 break;
300 324 }
301 - // Save all updates
325 +
326 + // Save all updates to the options array
302 327 $updated = update_option('mxchat_options', $options);
303 - // Handle backwards compatibility for certain fields
304 - $legacy_fields = ['brave_api_key', 'brave_image_count', 'brave_safe_search', 'brave_news_count',
305 - 'brave_country', 'brave_language', 'similarity_threshold'];
306 - if (in_array($name, $legacy_fields)) {
307 - // Also update the individual option for backwards compatibility
308 - update_option($name, $value);
328 +
329 + // Always return success even if WordPress says nothing changed
330 + // (which happens when the value is the same as before)
331 + wp_send_json_success(['message' => esc_html__('Setting saved', 'mxchat')]);
332 +}
333 +
334 +
335 +/**
336 + * Helper function to compare if a value has changed
337 + * Handles various data types appropriately
338 + */
339 +private function has_value_changed($old_value, $new_value) {
340 + // Handle null values
341 + if ($old_value === null && $new_value === '') {
342 + return false;
309 343 }
310 - if ($updated) {
311 - wp_send_json_success(['message' => esc_html__('Setting saved', 'mxchat')]);
312 - } else {
313 - wp_send_json_error(['message' => esc_html__('Update failed or no changes', 'mxchat')]);
344 +
345 + // Handle array values (like additional_popular_questions)
346 + if (is_array($old_value) && is_array($new_value)) {
347 + // Convert both to JSON for comparison to handle ordering differences
348 + return json_encode($old_value) !== json_encode($new_value);
314 349 }
350 +
351 + // Handle toggle/checkbox values consistently
352 + if (in_array($old_value, ['on', '1', 1, true]) && in_array($new_value, ['on', '1', 1, true])) {
353 + return false;
354 + }
355 + if (in_array($old_value, ['off', '0', 0, false, '']) && in_array($new_value, ['off', '0', 0, false, ''])) {
356 + return false;
357 + }
358 +
359 + // Default direct comparison
360 + return $old_value !== $new_value;
315 361 }
316 362
317 363 /**
318 364 * Handles AJAX auto-save for prompts and auto-sync settings.
@@ -317,30 +363,41 @@
317 363 /**
318 364 * Handles AJAX auto-save for prompts and auto-sync settings.
319 365 */
320 366 public function mxchat_save_prompts_setting_callback() {
321 - // Check the correct nonce for this action.
322 - check_ajax_referer( 'mxchat_prompts_setting_nonce', '_ajax_nonce' );
323 -
324 - if ( ! current_user_can( 'manage_options' ) ) {
325 - wp_send_json_error( [ 'message' => esc_html__( 'Unauthorized', 'mxchat' ) ] );
367 + check_ajax_referer('mxchat_prompts_setting_nonce', '_ajax_nonce');
368 +
369 + if (!current_user_can('manage_options')) {
370 + wp_send_json_error(['message' => esc_html__('Unauthorized', 'mxchat')]);
326 371 }
327 -
328 - $name = isset( $_POST['name'] ) ? sanitize_text_field( wp_unslash( $_POST['name'] ) ) : '';
329 - $value = isset( $_POST['value'] ) ? sanitize_text_field( wp_unslash( $_POST['value'] ) ) : '';
330 -
331 - if ( empty( $name ) ) {
332 - wp_send_json_error( [ 'message' => esc_html__( 'Invalid field name', 'mxchat' ) ] );
372 +
373 + $name = isset($_POST['name']) ? sanitize_text_field($_POST['name']) : '';
374 + $value = isset($_POST['value']) ? sanitize_text_field($_POST['value']) : '';
375 +
376 + if (empty($name)) {
377 + wp_send_json_error(['message' => esc_html__('Invalid field name', 'mxchat')]);
333 378 }
334 -
335 - // Handle standalone auto-sync settings.
336 - if ( in_array( $name, [ 'mxchat_auto_sync_posts', 'mxchat_auto_sync_pages' ], true ) ) {
337 - // Convert checkbox value to "1" if checked, "0" if not.
338 - $value = ( $value === 'on' ) ? '1' : '0';
339 - update_option( $name, $value );
340 - wp_send_json_success( [ 'message' => esc_html__( 'Setting saved', 'mxchat' ) ] );
379 +
380 + // Log the values we're trying to save (for debugging)
381 + //error_log('Attempting to save setting: ' . $name . ' = ' . $value);
382 +
383 + // For all auto-sync options
384 + if (strpos($name, 'mxchat_auto_sync_') === 0) {
385 + // Convert 'on'/'off' to '1'/'0' for consistency (if needed)
386 + $option_value = ($value === 'on') ? '1' : '0';
387 +
388 + $result = update_option($name, $option_value);
389 +
390 + if ($result) {
391 + //error_log('Successfully saved setting: ' . $name . ' = ' . $option_value);
392 + wp_send_json_success(['message' => esc_html__('Setting saved', 'mxchat')]);
393 + } else {
394 + //error_log('Failed to save setting: ' . $name . ' (no changes or error)');
395 + wp_send_json_error(['message' => esc_html__('Update failed or no changes', 'mxchat')]);
396 + }
397 + return;
341 398 }
342 -
399 +
343 400 // Handle fields stored in the 'mxchat_prompts_options' array.
344 401 // Handle fields stored in the 'mxchat_prompts_options' array.
345 402 if ( false !== strpos( $name, 'mxchat_prompts_options[' ) ) {
346 403 // Extract the key name using regex.
@@ -366,9 +423,8 @@
366 423
367 424
368 425 wp_send_json_error( [ 'message' => esc_html__( 'Field not recognized', 'mxchat' ) ] );
369 426 }
370 -
371 427 public function mxchat_display_admin_notice() {
372 428 // Success notice
373 429 if ($message = get_transient('mxchat_admin_notice_success')) {
374 430 ?>
@@ -398,181 +454,215 @@
398 454
399 455 public function mxchat_create_admin_page() {
400 456
401 457 ?>
402 - <div class="wrap mxchat-admin">
403 - <?php if (!$this->is_activated): ?>
404 - <div class="mxchat-pro-banner">
405 - <p>
406 - <?php echo esc_html__('For a limited time, get lifetime access and save $20 on MxChat Pro!', 'mxchat'); ?>
407 - <a href="https://mxchat.ai/" target="_blank"><?php echo esc_html__('Upgrade to Pro today', 'mxchat'); ?></a>
408 - </p>
458 + <div class="wrap mxchat-wrapper">
459 + <!-- Hero Section -->
460 + <div class="mxchat-hero">
461 + <h1 class="mxchat-main-title">
462 + <span class="mxchat-gradient-text">MxChat</span> Settings
463 + </h1>
464 + <p class="mxchat-hero-subtitle">
465 + <?php esc_html_e('Configure your AI chatbot, manage integrations and explore tutorials to get the most out of MxChat.', 'mxchat'); ?>
466 + </p>
409 467 </div>
410 - <?php endif; ?>
411 468
412 - <div class="mxchat-agents-banner">
413 - <p>
414 - <strong><?php echo esc_html__('The MxChat Add-Ons Platform', 'mxchat'); ?></strong><?php echo esc_html__(' is here! Pro users get unlimited access to our growing collection of powerful add-ons. ', 'mxchat'); ?><a href="<?php echo admin_url('admin.php?page=mxchat-addons'); ?>"><?php echo esc_html__('Explore all add-ons', 'mxchat'); ?></a><?php echo esc_html__(' including Forms Builder, Theme Customizer, Content Moderation, and more – all included with your lifetime Pro license!', 'mxchat'); ?>
415 - </p>
416 - </div>
469 + <div class="mxchat-content">
470 + <?php if (!$this->is_activated): ?>
471 + <div class="mxchat-pro-card">
472 + <div class="mxchat-pro-notification">
473 + <div class="mxchat-pro-content">
474 + <h3>🚀 Limited Time Offer: Save $20 on MxChat Pro Lifetime Access!</h3>
475 + <p>Unlock <strong>unlimited access</strong> to our growing collection of powerful add-ons including Forms Builder, Theme Customizer, WooCommerce, Perplexity, and more – all included with your lifetime license!</p>
476 + </div>
477 + <div class="mxchat-pro-cta">
478 + <a href="https://mxchat.ai/" target="_blank" class="mxchat-button"><?php echo esc_html__('Upgrade to Pro Today', 'mxchat'); ?></a>
479 + <a href="<?php echo admin_url('admin.php?page=mxchat-addons'); ?>" class="mxchat-link"><?php echo esc_html__('Preview Add-ons', 'mxchat'); ?></a>
480 + </div>
481 + </div>
482 + </div>
483 + <?php endif; ?>
417 484
418 - <h2 class="mxchat-nav-tab-wrapper">
419 - <a href="#chatbot" class="mxchat-nav-tab mxchat-nav-tab-active" data-tab="chatbot"><?php echo esc_html__('Chatbot', 'mxchat'); ?></a>
420 - <a href="#embed" class="mxchat-nav-tab" data-tab="embed"><?php echo esc_html__('Integrations', 'mxchat'); ?></a>
421 - <a href="#general" class="mxchat-nav-tab" data-tab="general"><?php echo esc_html__('FAQ', 'mxchat'); ?></a>
422 - </h2>
485 + <!-- Tabs Navigation -->
486 + <div class="mxchat-tabs">
487 + <button class="mxchat-tab-button active" data-tab="chatbot"><?php echo esc_html__('Chatbot', 'mxchat'); ?></button>
488 + <button class="mxchat-tab-button" data-tab="embed"><?php echo esc_html__('Integrations', 'mxchat'); ?></button>
489 + <button class="mxchat-tab-button" data-tab="general"><?php echo esc_html__('YouTube Tutorials', 'mxchat'); ?></button>
490 + </div>
423 491
424 - <div id="chatbot" class="mxchat-tab-content active">
425 - <div class="mxchat-autosave-section">
426 - <?php do_settings_sections('mxchat-chatbot'); ?>
492 + <!-- Tab Contents -->
493 + <div id="chatbot" class="mxchat-tab-content active">
494 + <div class="mxchat-card">
495 + <div class="mxchat-autosave-section">
496 + <?php do_settings_sections('mxchat-chatbot'); ?>
497 + </div>
498 + </div>
427 499 </div>
428 - </div>
429 500
430 - <div id="embed" class="mxchat-tab-content">
431 - <div class="mxchat-autosave-section">
432 -
433 - <div class="mxchat-settings-section">
434 - <h2><?php echo esc_html__('Loops Settings', 'mxchat'); ?></h2>
435 - <table class="form-table">
436 - <?php do_settings_fields('mxchat-embed', 'mxchat_loops_section'); ?>
437 - </table>
501 + <div id="embed" class="mxchat-tab-content">
502 + <div class="mxchat-card">
503 + <h2><?php esc_html_e('Loops Settings', 'mxchat'); ?></h2>
504 + <div class="mxchat-autosave-section">
505 + <table class="form-table">
506 + <?php do_settings_fields('mxchat-embed', 'mxchat_loops_section'); ?>
507 + </table>
508 + </div>
438 509 </div>
439 510
440 - <div class="section-divider"></div>
441 -
442 - <div class="mxchat-settings-section">
443 - <h2><?php echo esc_html__('Brave Search Settings', 'mxchat'); ?></h2>
444 - <table class="form-table">
445 - <?php do_settings_fields('mxchat-embed', 'mxchat_brave_section'); ?>
446 - </table>
511 + <div class="mxchat-card">
512 + <h2><?php esc_html_e('Brave Search Settings', 'mxchat'); ?></h2>
513 + <div class="mxchat-autosave-section">
514 + <table class="form-table">
515 + <?php do_settings_fields('mxchat-embed', 'mxchat_brave_section'); ?>
516 + </table>
517 + </div>
447 518 </div>
448 519
449 - <div class="section-divider"></div>
450 -
451 - <div class="mxchat-settings-section">
452 - <h2><?php echo esc_html__('Toolbar Settings & Intents', 'mxchat'); ?></h2>
453 - <table class="form-table">
454 - <?php do_settings_fields('mxchat-embed', 'mxchat_pdf_intent_section'); ?>
455 - </table>
520 + <div class="mxchat-card">
521 + <h2><?php esc_html_e('Toolbar Settings & Intents', 'mxchat'); ?></h2>
522 + <div class="mxchat-autosave-section">
523 + <table class="form-table">
524 + <?php do_settings_fields('mxchat-embed', 'mxchat_pdf_intent_section'); ?>
525 + </table>
526 + </div>
456 527 </div>
457 528
458 - <div class="section-divider"></div>
459 -
460 - <!-- Live Agent Settings Section -->
461 - <div class="mxchat-settings-section">
462 - <h2><?php echo esc_html__('Live Agent Settings', 'mxchat'); ?></h2>
463 - <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__('to set up live agent transfer via Slack.', 'mxchat'); ?></p>
464 - <table class="form-table">
465 - <?php do_settings_fields('mxchat-embed', 'mxchat_live_agent_section'); ?>
466 - </table>
529 + <div class="mxchat-card">
530 + <h2><?php esc_html_e('Live Agent Settings', 'mxchat'); ?></h2>
531 + <div class="mxchat-autosave-section">
532 + <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__('to set up live agent transfer via Slack.', 'mxchat'); ?></p>
533 + <table class="form-table">
534 + <?php do_settings_fields('mxchat-embed', 'mxchat_live_agent_section'); ?>
535 + </table>
536 + </div>
467 537 </div>
468 538 </div>
469 - </div>
470 539
540 + <div id="general" class="mxchat-tab-content">
541 + <div class="mxchat-card">
542 + <?php do_settings_sections('mxchat-general'); ?>
543 + <div class="video-tutorials-section">
471 544
472 - <div id="general" class="mxchat-tab-content">
473 - <?php do_settings_sections('mxchat-general'); ?>
474 - <p>
475 - <?php echo esc_html__('If you’re having trouble with setup or getting the responses you need, we encourage you to review our', 'mxchat'); ?>
476 - <a href="https://mxchat.ai/documentation/" target="_blank" rel="noopener noreferrer"><?php echo esc_html__('documentation', 'mxchat'); ?></a> <?php echo esc_html__('or', 'mxchat'); ?>
477 - <a href="https://wordpress.org/support/plugin/mxchat-basic/" target="_blank" rel="noopener noreferrer"><?php echo esc_html__('create a support ticket', 'mxchat'); ?></a>.
478 - </p>
479 - <p>
480 - <?php echo esc_html__('If you like our plugin, please consider', 'mxchat'); ?> <a href="https://wordpress.org/plugins/mxchat-basic/#reviews" target="_blank" rel="noopener noreferrer"><?php echo esc_html__('leaving us a review', 'mxchat'); ?></a>.
481 - </p>
482 -
483 - <div class="faq-item">
484 - <h3><?php echo esc_html__('How does the Claude API integration work?', 'mxchat'); ?></h3>
485 - <p>
486 - <?php echo esc_html__('The Claude API, provided by Anthropic, allows for intelligent, context-aware chatbot responses in MxChat. To use it, you will need both an OpenAI API key and a Claude API key. This is necessary because the system utilizes OpenAI for vector embedding in the Retrieval-Augmented Generation (RAG) process, as Claude does not currently offer an embedding API.', 'mxchat'); ?>
487 - </p>
488 - <p>
489 - <?php echo esc_html__('When using the Claude API, your custom content is sent to Claude for generating responses, while the embeddings are processed through OpenAI. This ensures your chatbot provides accurate and engaging responses while maintaining knowledge relevant to your website.', 'mxchat'); ?>
490 - </p>
491 - <p>
492 - <?php echo esc_html__('You can obtain your Claude API key by signing up on the', 'mxchat'); ?> <a href="https://www.anthropic.com/api" target="_blank" rel="noopener"><?php echo esc_html__('Anthropic API page', 'mxchat'); ?></a>.
493 - </p>
545 + <div class="tutorial-grid">
546 + <div class="tutorial-item">
547 + <h3><?php echo esc_html__('MxChat Forms Tutorial', 'mxchat'); ?></h3>
548 + <div class="video-description">
549 + <p><?php echo esc_html__('Learn how to create and manage smart forms that automatically trigger during chat conversations.', 'mxchat'); ?></p>
550 + <a href="https://www.youtube.com/watch?v=3MrWy5dRalA" target="_blank" rel="noopener" class="video-link">
551 + <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>
552 + <?php echo esc_html__('Watch MxChat Forms Tutorial', 'mxchat'); ?>
553 + </a>
494 554 </div>
555 + </div>
495 556
496 - <div class="faq-item">
497 - <h3><?php echo esc_html__('How does the X.AI API integration work?', 'mxchat'); ?></h3>
498 - <p>
499 - <?php echo esc_html__('The X.AI API, released on 10.21.24, is currently in beta. To use it, you will need both an OpenAI API key and an X.AI API key. This is because OpenAI handles vector embeddings in the Retrieval-Augmented Generation (RAG) process, as X.AI does not yet provide an embedding API.', 'mxchat'); ?>
500 - </p>
501 - <p>
502 - <?php echo esc_html__('Custom content is sent to the X.AI API for generating responses, while OpenAI processes the embeddings. This allows the chatbot to provide advanced responses while maintaining context from your website. You can get your X.AI API key from the', 'mxchat'); ?> <a href="https://docs.x.ai/docs" target="_blank" rel="noopener"><?php echo esc_html__('X.AI API documentation', 'mxchat'); ?></a>.
503 - </p>
557 + <div class="tutorial-item">
558 + <h3><?php echo esc_html__('Intent Tester Guide', 'mxchat'); ?></h3>
559 + <div class="video-description">
560 + <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>
561 + <a href="https://www.youtube.com/watch?v=uTr14tn59Hc" target="_blank" rel="noopener" class="video-link">
562 + <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>
563 + <?php echo esc_html__('Watch Intent Tester Tutorial', 'mxchat'); ?>
564 + </a>
504 565 </div>
566 + </div>
505 567
506 - <div class="faq-item">
507 - <h3><?php echo esc_html__('Do I need an OpenAI API key to use the chatbot?', 'mxchat'); ?></h3>
508 - <p>
509 - <?php echo esc_html__('Yes, you will need an OpenAI API key to power the chatbot. You can obtain an API key by signing up on the', 'mxchat'); ?> <a href="https://platform.openai.com/signup" target="_blank" rel="noopener"><?php echo esc_html__('OpenAI platform', 'mxchat'); ?></a>. <?php echo esc_html__('After signing up, you must add credits to your account—typically, $5 in credits is sufficient to get started.', 'mxchat'); ?>
510 - </p>
511 - <p>
512 - <?php echo esc_html__('Once you have your API key, simply enter it in the chatbot\'s settings to enable functionality. The chatbot relies on OpenAI’s models for generating responses, so having sufficient credits in your OpenAI account is essential for smooth operation.', 'mxchat'); ?>
513 - </p>
568 + <div class="tutorial-item">
569 + <h3><?php echo esc_html__('WooCommerce Integration', 'mxchat'); ?></h3>
570 + <div class="video-description">
571 + <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>
572 + <a href="https://www.youtube.com/watch?v=WsqAppHRGdA" target="_blank" rel="noopener" class="video-link">
573 + <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>
574 + <?php echo esc_html__('Watch WooCommerce Integration Tutorial', 'mxchat'); ?>
575 + </a>
514 576 </div>
577 + </div>
515 578
516 - <div class="faq-item">
517 - <h3><?php echo esc_html__('How do I add the chatbot to my site?', 'mxchat'); ?></h3>
518 - <p>
519 - <?php echo esc_html__('You can add the chatbot using the shortcode', 'mxchat'); ?> <code>[mxchat_chatbot floating="yes"]</code> <?php echo esc_html__('or', 'mxchat'); ?> <code>[mxchat_chatbot floating="no"]</code>. <?php echo esc_html__('For initial testing and styling, it\'s best to use the shortcode on a draft or non-public page. Once you’re ready to go live, enable the “Append Chat Widget to Body” option in the settings for site-wide integration (recommended) or add shortcode to the footer.', 'mxchat'); ?>
520 - </p>
579 + <div class="tutorial-item">
580 + <h3><?php echo esc_html__('Knowledge Base Setup', 'mxchat'); ?></h3>
581 + <div class="video-description">
582 + <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>
583 + <p><small><?php echo esc_html__('Note: This tutorial uses an older UI, but the process remains the same.', 'mxchat'); ?></small></p>
584 + <a href="https://www.youtube.com/watch?v=8Ztjs66-VTo" target="_blank" rel="noopener" class="video-link">
585 + <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>
586 + <?php echo esc_html__('Watch Knowledge Base Setup Tutorial', 'mxchat'); ?>
587 + </a>
521 588 </div>
589 + </div>
522 590
523 - <div class="faq-item">
524 - <h3><?php echo esc_html__('How does the chatbot use my content to generate responses?', 'mxchat'); ?></h3>
525 - <p>
526 - <?php echo esc_html__('The chatbot uses AI and vector embeddings to connect users with relevant information. When you submit content, it converts it into a mathematical format. When a user asks a question, the bot matches it to your stored content and generates a response based on relevance. For example, submitting "Our phone number is 910-123-4567" allows the bot to retrieve this information when asked about contact details. To ensure related information is provided together, submit it in one entry.', 'mxchat'); ?>
527 - </p>
591 + <div class="tutorial-item">
592 + <h3><?php echo esc_html__('Toolbar Chat with Documents', 'mxchat'); ?></h3>
593 + <div class="video-description">
594 + <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>
595 + <a href="https://www.youtube.com/watch?v=j_c45WWCTG0" target="_blank" rel="noopener" class="video-link">
596 + <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>
597 + <?php echo esc_html__('Watch Document Chat Tutorial', 'mxchat'); ?>
598 + </a>
528 599 </div>
529 -
530 - <div class="faq-item">
531 - <h3><?php echo esc_html__('Why does the chatbot sometimes make up links or information?', 'mxchat'); ?></h3>
532 - <p>
533 - <?php echo esc_html__('Occasionally, the chatbot may generate inaccurate links or information, known as "hallucinations." To minimize this, you can add system instructions to guide its behavior. For example, include an instruction like: "Only provide links you directly have access to or retrieve from the knowledge base. Do not make up links or information that you do not have direct access to." This helps the bot stay aligned with your content.', 'mxchat'); ?>
534 - </p>
600 + </div>
601 +
602 + <div class="tutorial-item">
603 + <h3><?php echo esc_html__('Perplexity Integration', 'mxchat'); ?></h3>
604 + <div class="video-description">
605 + <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>
606 + <a href="https://youtu.be/wpKkbt24-bo" target="_blank" rel="noopener" class="video-link">
607 + <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>
608 + <?php echo esc_html__('Watch Perplexity Integration Tutorial', 'mxchat'); ?>
609 + </a>
535 610 </div>
611 + </div>
536 612
537 - <div class="faq-item">
538 - <h3><?php echo esc_html__('How do intents work in MxChat?', 'mxchat'); ?></h3>
539 - <p>
540 - <?php echo esc_html__('Intents allow MxChat to recognize user requests and trigger specific actions, such as capturing emails or displaying product information. This helps provide a more interactive and responsive experience. For a detailed explanation of each intent, please refer to our', 'mxchat'); ?> <a href="https://mxchat.ai/documentation/#intents" target="_blank" rel="noopener noreferrer"><?php echo esc_html__('Intents Documentation', 'mxchat'); ?></a>.
541 - </p>
613 + <div class="tutorial-item">
614 + <h3><?php echo esc_html__('Brave Search Intent', 'mxchat'); ?></h3>
615 + <div class="video-description">
616 + <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>
617 + <a href="https://www.youtube.com/watch?v=7vDL5H7vToc" target="_blank" rel="noopener" class="video-link">
618 + <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>
619 + <?php echo esc_html__('Watch Brave Search Intent Tutorial', 'mxchat'); ?>
620 + </a>
542 621 </div>
622 + </div>
543 623
544 - <div class="faq-item">
545 - <h3><?php echo esc_html__('How does the Complianz integration work?', 'mxchat'); ?></h3>
546 - <p>
547 - <?php echo esc_html__('The Pro version offers direct integration with the Complianz GDPR plugin, making it easy to stay compliant with GDPR. If Complianz is enabled on your website, users must accept consent before the chatbot widget appears. The chatbot will only display once the user has accepted, ensuring compliance with data privacy regulations.', 'mxchat'); ?>
548 - </p>
624 + <div class="tutorial-item">
625 + <h3><?php echo esc_html__('Loops Email Capture', 'mxchat'); ?></h3>
626 + <div class="video-description">
627 + <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>
628 + <p><small><?php echo esc_html__('Note: This tutorial uses an older UI, but the process remains the same.', 'mxchat'); ?></small></p>
629 + <a href="https://www.youtube.com/watch?v=CNgm5TYDyTc" target="_blank" rel="noopener" class="video-link">
630 + <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>
631 + <?php echo esc_html__('Watch Loops Email Capture Tutorial', 'mxchat'); ?>
632 + </a>
549 633 </div>
634 + </div>
550 635
551 - <div class="faq-item">
552 - <h3><?php echo esc_html__('How does the WooCommerce integration work?', 'mxchat'); ?></h3>
553 - <p>
554 - <?php echo esc_html__('With WooCommerce integration, the chatbot automatically embeds new products and updates existing ones. For already-published products, you can click update or submit the product sitemap. The “Order History Access” setting allows the bot to access users\' order history (requires login) and assist with order inquiries. To enable the “Add to Cart” feature, add this system instruction: “After discussing a product, ask if the user wants to add it to their cart. The user must say \'Add to cart\' exactly.” Currently, this feature supports English only, with more languages coming soon.', 'mxchat'); ?>
555 - </p>
636 + <div class="tutorial-item">
637 + <h3><?php echo esc_html__('MxChat AI Agent Testing Service', 'mxchat'); ?></h3>
638 + <div class="video-description">
639 + <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>
640 + <p><small><?php echo esc_html__('Note: This tutorial uses an older UI, but the process remains the same.', 'mxchat'); ?></small></p>
641 + <a href="https://www.youtube.com/watch?v=A0jowbpyX54" target="_blank" rel="noopener" class="video-link">
642 + <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>
643 + <?php echo esc_html__('Watch AI Agent Testing Tutorial', 'mxchat'); ?>
644 + </a>
556 645 </div>
646 + </div>
647 + </div>
557 648
558 - <div class="faq-item">
559 - <h3><?php echo esc_html__('Why isn\'t my chatbot responding as expected?', 'mxchat'); ?></h3>
560 - <p>
561 - <?php echo esc_html__('AI chatbots can sometimes behave in unexpected ways, especially if you\'re new to configuring AI for specific tasks. We\'re committed to helping you get the most out of your chatbot experience. While we’re working on comprehensive guides and video tutorials, our team is here to assist you directly. If your chatbot isn\'t delivering the responses you need, please don’t hesitate to', 'mxchat'); ?> <a href="https://mxchat.ai/contact/" target="_blank" rel="noopener noreferrer"><?php echo esc_html__('contact us', 'mxchat'); ?></a> <?php echo esc_html__('for personalized support.', 'mxchat'); ?>
562 - </p>
563 - <p>
564 - <?php echo esc_html__('We’re dedicated to your success and ready to guide you in aligning the AI\'s behavior to meet your goals.', 'mxchat'); ?>
565 - </p>
649 + <div class="support-section">
650 + <h3><?php echo esc_html__('Need Help?', 'mxchat'); ?></h3>
651 + <div class="support-content">
652 + <p>
653 + <?php echo esc_html__('If you\'re having trouble with setup or getting the responses you need, we encourage you to review our', 'mxchat'); ?>
654 + <a href="https://mxchat.ai/documentation/" target="_blank" rel="noopener noreferrer"><?php echo esc_html__('documentation', 'mxchat'); ?></a> <?php echo esc_html__('or', 'mxchat'); ?>
655 + <a href="https://wordpress.org/support/plugin/mxchat-basic/" target="_blank" rel="noopener noreferrer"><?php echo esc_html__('create a support ticket', 'mxchat'); ?></a>.
656 + </p>
657 + <p>
658 + <?php echo esc_html__('If you like our plugin, please consider', 'mxchat'); ?> <a href="https://wordpress.org/plugins/mxchat-basic/#reviews" target="_blank" rel="noopener noreferrer"><?php echo esc_html__('leaving us a review', 'mxchat'); ?></a>.
659 + </p>
660 + </div>
661 + </div>
662 + </div>
663 + </div>
566 664 </div>
567 -
568 - <div class="faq-item">
569 - <h3><?php echo esc_html__('What is Loops, and how do I get an API key?', 'mxchat'); ?></h3>
570 - <p>
571 - <?php echo esc_html__('Loops is a powerful SaaS email service that helps you automate and enhance your email marketing campaigns, making it easy to reach and engage with your audience. To integrate Loops with MxChat, you’ll need an API key from Loops. You can obtain this key by logging into your Loops account and navigating to the API settings. Visit the', 'mxchat'); ?> <a href="https://loops.so" target="_blank" rel="noopener noreferrer"><?php echo esc_html__('Loops website', 'mxchat'); ?></a> <?php echo esc_html__('to get started or to sign up for an account.', 'mxchat'); ?>
572 - </p>
573 - </div>
574 -
575 665 </div>
576 666 </div>
577 667 <?php
578 668 }
@@ -577,73 +667,348 @@
577 667 <?php
578 668 }
579 669
580 670
581 -
582 671 public function mxchat_create_transcripts_page() {
583 672 global $wpdb;
584 673 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
585 -
674 +
586 675 // Get basic stats
587 676 $total_chats = $wpdb->get_var("SELECT COUNT(DISTINCT session_id) FROM $table_name");
588 677 $total_messages = $wpdb->get_var("SELECT COUNT(*) FROM $table_name");
589 - $total_users = $wpdb->get_var("SELECT COUNT(DISTINCT user_email) FROM $table_name WHERE user_email != ''");
678 +
679 + // Count unique users with detailed breakdown
680 + $total_users = $wpdb->get_var("
681 + SELECT COUNT(DISTINCT
682 + CASE
683 + WHEN user_email != '' AND user_email IS NOT NULL THEN user_email
684 + WHEN user_id != 0 THEN CONCAT('user_', user_id)
685 + WHEN user_identifier NOT LIKE 'Tech-Savvy User'
686 + AND user_identifier NOT LIKE 'Detail-Oriented User'
687 + AND user_identifier NOT LIKE 'Language Learner'
688 + AND user_identifier NOT LIKE 'Casual Browser'
689 + AND user_identifier NOT LIKE 'Policy Enforcer'
690 + AND user_identifier NOT LIKE 'Researcher'
691 + AND user_identifier NOT LIKE 'Loyalty Member'
692 + AND user_identifier NOT LIKE 'Gift Buyer'
693 + AND user_identifier NOT LIKE 'Parent or Caregiver'
694 + THEN user_identifier
695 + ELSE session_id
696 + END
697 + )
698 + FROM $table_name
699 + WHERE role != 'assistant'
700 + ");
701 +
702 + // Get user type breakdown
703 + $registered_users = $wpdb->get_var("
704 + SELECT COUNT(DISTINCT user_email)
705 + FROM $table_name
706 + WHERE user_email != '' AND user_email IS NOT NULL
707 + ");
708 +
709 + $guest_users = $wpdb->get_var("
710 + SELECT COUNT(DISTINCT user_identifier)
711 + FROM $table_name
712 + WHERE (user_email = '' OR user_email IS NULL)
713 + AND role != 'assistant'
714 + AND user_identifier NOT LIKE 'Tech-Savvy User'
715 + AND user_identifier NOT LIKE 'Detail-Oriented User'
716 + AND user_identifier NOT LIKE 'Language Learner'
717 + AND user_identifier NOT LIKE 'Casual Browser'
718 + AND user_identifier NOT LIKE 'Policy Enforcer'
719 + AND user_identifier NOT LIKE 'Researcher'
720 + AND user_identifier NOT LIKE 'Loyalty Member'
721 + AND user_identifier NOT LIKE 'Gift Buyer'
722 + AND user_identifier NOT LIKE 'Parent or Caregiver'
723 + ");
724 +
725 + // Get agent test messages count
726 + $agent_tests = $wpdb->get_var("
727 + SELECT COUNT(DISTINCT session_id)
728 + FROM $table_name
729 + WHERE user_identifier IN (
730 + 'Tech-Savvy User',
731 + 'Detail-Oriented User',
732 + 'Language Learner',
733 + 'Casual Browser',
734 + 'Policy Enforcer',
735 + 'Researcher',
736 + 'Loyalty Member',
737 + 'Gift Buyer',
738 + 'Parent or Caregiver'
739 + )
740 + ");
590 741 ?>
591 - <div class="wrap mxchat-admin">
592 - <!-- Stats Cards -->
593 - <div class="mxchat-stats-grid">
594 - <div class="mxchat-stat-card">
595 - <div class="stat-icon">💬</div>
596 - <div class="stat-content">
597 - <span class="stat-value"><?php echo esc_html($total_chats); ?></span>
598 - <span class="stat-label"><?php esc_html_e('Total Chats', 'mxchat'); ?></span>
742 + <div class="wrap mxchat-transcripts-wrapper">
743 + <!-- Hero Section -->
744 + <div class="mxchat-transcripts-hero">
745 + <h1 class="mxchat-main-title">
746 + Chat <span class="mxchat-gradient-text">Transcripts</span>
747 + </h1>
748 + <p class="mxchat-hero-subtitle">
749 + <?php esc_html_e('Review and manage your chatbot conversations with detailed message history.', 'mxchat'); ?>
750 + </p>
751 + </div>
752 + <div class="mxchat-content">
753 + <!-- Stats Cards -->
754 + <div class="mxchat-stats-grid">
755 + <div class="mxchat-stat-card">
756 + <div class="stat-icon">💬</div>
757 + <div class="stat-content">
758 + <span class="stat-value"><?php echo esc_html($total_chats); ?></span>
759 + <span class="stat-label"><?php esc_html_e('Total Chats', 'mxchat'); ?></span>
760 + </div>
599 761 </div>
600 - </div>
601 - <div class="mxchat-stat-card">
602 - <div class="stat-icon">📝</div>
603 - <div class="stat-content">
604 - <span class="stat-value"><?php echo esc_html($total_messages); ?></span>
605 - <span class="stat-label"><?php esc_html_e('Total Messages', 'mxchat'); ?></span>
762 + <div class="mxchat-stat-card">
763 + <div class="stat-icon">📝</div>
764 + <div class="stat-content">
765 + <span class="stat-value"><?php echo esc_html($total_messages); ?></span>
766 + <span class="stat-label"><?php esc_html_e('Total Messages', 'mxchat'); ?></span>
767 + </div>
606 768 </div>
769 + <div class="mxchat-stat-card">
770 + <div class="stat-icon">👥</div>
771 + <div class="stat-content">
772 + <span class="stat-value"><?php echo esc_html($total_users); ?></span>
773 + <span class="stat-label"><?php esc_html_e('Unique Users', 'mxchat'); ?></span>
774 + <span class="stat-sublabel">
775 + <?php
776 + echo sprintf(
777 + esc_html__('%d registered, %d guests, %d agent tests', 'mxchat'),
778 + $registered_users,
779 + $guest_users,
780 + $agent_tests
781 + );
782 + ?>
783 + </span>
784 + </div>
785 + </div>
607 786 </div>
608 - <div class="mxchat-stat-card">
609 - <div class="stat-icon">👥</div>
610 - <div class="stat-content">
611 - <span class="stat-value"><?php echo esc_html($total_users); ?></span>
612 - <span class="stat-label"><?php esc_html_e('Unique Users', 'mxchat'); ?></span>
787 + <!-- Search and Filter Controls -->
788 + <div class="mxchat-controls-wrapper">
789 + <div class="mxchat-search-box">
790 + <input type="text" id="mxchat-search-transcripts"
791 + placeholder="<?php esc_attr_e('Search transcripts...', 'mxchat'); ?>"
792 + class="regular-text">
613 793 </div>
794 + <form id="mxchat-delete-form" method="post">
795 + <?php wp_nonce_field('mxchat_delete_chat_history', 'mxchat_delete_chat_nonce'); ?>
796 + <div class="mxchat-controls">
797 + <button type="button" id="mxchat-export-transcripts" class="mxchat-action-button">
798 + <span class="dashicons dashicons-download"></span>
799 + <?php esc_html_e('Export All Chats', 'mxchat'); ?>
800 + </button>
801 + <button type="button" id="mxchat-select-all-transcripts" class="mxchat-select-button">
802 + <span class="dashicons dashicons-yes-alt"></span>
803 + <span class="button-text"><?php esc_html_e('Select All', 'mxchat'); ?></span>
804 + </button>
805 + <button type="submit" class="button delete-chats-button">
806 + <span class="dashicons dashicons-trash"></span>
807 + <?php esc_html_e('Delete Selected', 'mxchat'); ?>
808 + </button>
809 + </div>
810 + </form>
614 811 </div>
812 + <!-- Transcripts Container -->
813 + <div id="mxchat-transcripts"></div>
615 814 </div>
815 + </div>
816 + <?php
817 +}
616 818
617 - <!-- Search and Filter Controls -->
618 - <div class="mxchat-controls-wrapper">
619 - <div class="mxchat-search-box">
620 - <input type="text" id="mxchat-search-transcripts"
621 - placeholder="<?php esc_attr_e('Search transcripts...', 'mxchat'); ?>"
622 - class="regular-text">
623 - </div>
819 +public function export_chat_transcripts() {
820 + if (!current_user_can('manage_options')) {
821 + wp_die(esc_html__('You do not have sufficient permissions to access this page.', 'mxchat'));
822 + }
624 823
625 - <form id="mxchat-delete-form" method="post">
626 - <?php wp_nonce_field('mxchat_delete_chat_history', 'mxchat_delete_chat_nonce'); ?>
627 - <div class="mxchat-controls">
628 - <button type="button" id="mxchat-select-all-transcripts" class="mxchat-select-button">
629 - <span class="dashicons dashicons-yes-alt"></span>
630 - <span class="button-text"><?php esc_html_e('Select All', 'mxchat'); ?></span>
631 - </button>
632 - <button type="submit" class="button delete-chats-button">
633 - <span class="dashicons dashicons-trash"></span>
634 - <?php esc_html_e('Delete Selected', 'mxchat'); ?>
635 - </button>
636 - </div>
637 - </form>
638 - </div>
824 + check_ajax_referer('mxchat_export_transcripts', 'security');
639 825
640 - <!-- Transcripts Container -->
641 - <div id="mxchat-transcripts"></div>
642 - </div>
643 - <?php
826 + global $wpdb;
827 + $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
828 +
829 + // Get all transcripts ordered by session and timestamp
830 + $results = $wpdb->get_results(
831 + "SELECT session_id, user_email, user_identifier, role, message, timestamp
832 + FROM {$table_name}
833 + ORDER BY session_id, timestamp ASC"
834 + );
835 +
836 + if (empty($results)) {
837 + wp_send_json_error(array('message' => 'No transcripts found.'));
838 + wp_die();
839 + }
840 +
841 + // Set headers for CSV download
842 + header('Content-Type: text/csv');
843 + header('Content-Disposition: attachment; filename="chat-transcripts-' . date('Y-m-d') . '.csv"');
844 + header('Pragma: no-cache');
845 + header('Expires: 0');
846 +
847 + // Create output stream
848 + $output = fopen('php://output', 'w');
849 +
850 + // Add UTF-8 BOM for proper Excel encoding
851 + fputs($output, "\xEF\xBB\xBF");
852 +
853 + // Add CSV headers
854 + fputcsv($output, array(
855 + 'Session ID',
856 + 'Email',
857 + 'User Identifier',
858 + 'Role',
859 + 'Message',
860 + 'Timestamp'
861 + ));
862 +
863 + // Add data rows
864 + foreach ($results as $row) {
865 + fputcsv($output, array(
866 + $row->session_id,
867 + $row->user_email,
868 + $row->user_identifier,
869 + $row->role,
870 + $row->message,
871 + $row->timestamp
872 + ));
873 + }
874 +
875 + fclose($output);
876 + wp_die();
644 877 }
645 878
879 +public function mxchat_fetch_chat_history() {
880 + global $wpdb;
881 + $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
882 +
883 + if (!current_user_can('manage_options')) {
884 + wp_die(esc_html__('You do not have sufficient permissions to view this page.', 'mxchat'));
885 + }
886 +
887 + // First get unique session IDs ordered by most recent message in each session
888 + $session_ids = $wpdb->get_col(
889 + $wpdb->prepare(
890 + "SELECT DISTINCT session_id
891 + FROM {$table_name}
892 + GROUP BY session_id
893 + ORDER BY MAX(timestamp) DESC"
894 + )
895 + );
896 +
897 + if (empty($session_ids)) {
898 + wp_die(esc_html__('No chat history available.', 'mxchat'));
899 + }
900 +
901 + ob_start();
902 + echo '<div class="mxchat-transcript">';
903 +
904 + // Iterate through sessions from newest to oldest
905 + foreach ($session_ids as $session_id) {
906 + // Get the email associated with this session (if available)
907 + $email = $wpdb->get_var(
908 + $wpdb->prepare(
909 + "SELECT user_email
910 + FROM {$table_name}
911 + WHERE session_id = %s AND user_email != ''
912 + ORDER BY timestamp ASC
913 + LIMIT 1",
914 + $session_id
915 + )
916 + );
917 +
918 + // Debug: Check email retrieval
919 + if (empty($email)) {
920 + //error_log("No email found for session {$session_id}");
921 + } else {
922 + //error_log("Email for session {$session_id}: " . $email);
923 + }
924 +
925 + // Get messages for this session ordered by timestamp
926 + $messages = $wpdb->get_results(
927 + $wpdb->prepare(
928 + "SELECT * FROM {$table_name}
929 + WHERE session_id = %s
930 + ORDER BY timestamp ASC",
931 + $session_id
932 + )
933 + );
934 +
935 + // Start session block
936 + echo '<div class="mxchat-session">';
937 + echo '<div class="mxchat-session-header">';
938 + // Wrap checkbox and session ID in one block
939 + echo '<div class="mxchat-session-id">';
940 + echo '<input type="checkbox" name="delete_session_ids[]" value="' . esc_attr($session_id) . '"> ';
941 + echo '<strong>' . esc_html__('Session ID:', 'mxchat') . '</strong> ' . esc_html($session_id);
942 + echo '</div>';
943 +
944 + // Place email directly below the session ID block
945 + if (!empty($email)) {
946 + echo '<div class="mxchat-session-email">';
947 + echo '<strong>' . esc_html__('Email:', 'mxchat') . '</strong> ' . esc_html($email);
948 + echo '</div>';
949 + }
950 + echo '</div>';
951 +
952 + echo '<div class="mxchat-messages">';
953 +
954 + // Display messages for this session
955 + foreach ($messages as $transcript) {
956 + $formatted_timestamp = date_i18n('F j, Y g:i a', strtotime($transcript->timestamp));
957 +
958 + // Determine message styling
959 + switch ($transcript->role) {
960 + case 'assistant':
961 + case 'bot':
962 + $message_class = 'bot-message';
963 + $display_role = esc_html__('Chatbot', 'mxchat');
964 + break;
965 + case 'user':
966 + $message_class = 'user-message';
967 + $display_role = !empty($transcript->user_identifier)
968 + ? sanitize_text_field($transcript->user_identifier)
969 + : esc_html__('User', 'mxchat');
970 + break;
971 + case 'agent':
972 + $message_class = 'agent-message';
973 + $display_role = esc_html__('Agent', 'mxchat');
974 + break;
975 + default:
976 + $message_class = 'unknown-message';
977 + $display_role = esc_html__('Unknown', 'mxchat');
978 + }
979 +
980 + // Process message content
981 + $message_content = wp_kses(
982 + stripslashes($transcript->message),
983 + [
984 + 'b' => [], 'strong' => [], 'i' => [], 'em' => [], 'u' => [],
985 + 'br' => [], 'p' => [], 'ul' => [], 'ol' => [], 'li' => [],
986 + 'a' => ['href' => [], 'title' => []]
987 + ]
988 + );
989 + $message_content = nl2br($message_content);
990 +
991 + // Render message
992 + echo '<div class="mxchat-message ' . esc_attr($message_class) . '">';
993 + echo '<div class="mxchat-message-header">' . esc_html($display_role) . '</div>';
994 + echo '<div class="mxchat-message-content">' . $message_content . '</div>';
995 + echo '<div class="mxchat-timestamp">' . esc_html($formatted_timestamp) . '</div>';
996 + echo '</div>';
997 + }
998 +
999 + // Close session block
1000 + echo '</div></div>';
1001 + }
1002 +
1003 + echo '</div>';
1004 + $output = ob_get_clean();
1005 + echo $output;
1006 + wp_die();
1007 +}
1008 +
1009 +
1010 +
646 1011 public function mxchat_create_prompts_page() {
647 1012 global $wpdb;
648 1013 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
649 1014
@@ -724,10 +1089,39 @@
724 1089 <div class="mxchat-content">
725 1090 <!-- Import Settings Card -->
726 1091 <div class="mxchat-card">
727 1092 <h2><?php esc_html_e('Knowledge Import Settings', 'mxchat'); ?></h2>
728 - <p><?php esc_html_e('Using the free Pinecone DB Manager add-on is recommended for high performance and a knowledge database of more than 500.', 'mxchat'); ?></p>
1093 +<?php
1094 +// Check if the appropriate embedding API key exists
1095 +$embedding_model = isset($this->options['embedding_model']) ? esc_attr($this->options['embedding_model']) : 'text-embedding-ada-002';
1096 +$has_openai_key = !empty($this->options['api_key']);
1097 +$has_voyage_key = !empty($this->options['voyage_api_key']);
729 1098
1099 +// Determine if they have the needed API key for their selected embedding model
1100 +$has_required_key = false;
1101 +$required_key_type = '';
1102 +
1103 +if (strpos($embedding_model, 'text-embedding-') !== false && $has_openai_key) {
1104 + $has_required_key = true;
1105 + $required_key_type = 'OpenAI';
1106 +} elseif (strpos($embedding_model, 'voyage-') !== false && $has_voyage_key) {
1107 + $has_required_key = true;
1108 + $required_key_type = 'Voyage AI';
1109 +} elseif (strpos($embedding_model, 'text-embedding-') !== false) {
1110 + $required_key_type = 'OpenAI';
1111 +} elseif (strpos($embedding_model, 'voyage-') !== false) {
1112 + $required_key_type = 'Voyage AI';
1113 +}
1114 +?>
1115 +
1116 +<div class="mxchat-knowledge-warning <?php echo $has_required_key ? 'success' : 'warning'; ?>">
1117 + <?php if ($has_required_key): ?>
1118 + <p><span class="dashicons dashicons-yes-alt"></span> <?php echo wp_kses_post(sprintf(__('We detected your %s API key. <strong>You must have added credits to your %s account</strong> before using the knowledgebase.', 'mxchat'), $required_key_type, $required_key_type)); ?></p>
1119 + <?php else: ?>
1120 + <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>
1121 + <?php endif; ?>
1122 +</div>
1123 +
730 1124 <!-- Tab Contents -->
731 1125 <div class="mxchat-tab-contents">
732 1126 <!-- Default Database Tab -->
733 1127 <div id="default-db" class="mxchat-tab-content active">
@@ -752,8 +1146,9 @@
752 1146 <span class="mxchat-toggle-label">
753 1147 <?php esc_html_e('Auto-sync Posts', 'mxchat'); ?>
754 1148 </span>
755 1149 </div>
1150 +
756 1151 <div class="mxchat-toggle-container">
757 1152 <label class="mxchat-toggle-switch">
758 1153 <input type="checkbox"
759 1154 name="mxchat_auto_sync_pages"
@@ -766,8 +1161,64 @@
766 1161 <span class="mxchat-toggle-label">
767 1162 <?php esc_html_e('Auto-sync Pages', 'mxchat'); ?>
768 1163 </span>
769 1164 </div>
1165 +
1166 +
1167 +
1168 +
1169 + <!-- Replace the existing custom post types section with this code -->
1170 +<div class="mxchat-section-content">
1171 + <div class="mxchat-custom-post-types-header">
1172 + <button id="mxchat-custom-post-types-toggle" class="mxchat-button-secondary">
1173 + <?php esc_html_e('Advanced Custom Post Sync Settings', 'mxchat'); ?>
1174 + <span class="mxchat-toggle-icon">▼</span>
1175 + </button>
1176 + </div>
1177 +
1178 + <div id="mxchat-custom-post-types-container" class="mxchat-custom-post-types-container" style="display: none;">
1179 + <h3><?php esc_html_e('Sync Custom Post Types', 'mxchat'); ?></h3>
1180 + <p><?php esc_html_e('Select additional custom post types to automatically sync with the chatbot.', 'mxchat'); ?></p>
1181 +
1182 + <div class="mxchat-custom-post-types">
1183 + <?php
1184 + $post_types = $this->get_public_post_types();
1185 +
1186 + // Skip post and page as they're handled separately
1187 + unset($post_types['post']);
1188 + unset($post_types['page']);
1189 +
1190 + if (!empty($post_types)) {
1191 + foreach ($post_types as $post_type => $label) {
1192 + $option_name = 'mxchat_auto_sync_' . $post_type;
1193 + $is_enabled = get_option($option_name, '0');
1194 + ?>
1195 + <div class="mxchat-toggle-container">
1196 + <label class="mxchat-toggle-switch">
1197 + <input type="checkbox"
1198 + name="<?php echo esc_attr($option_name); ?>"
1199 + class="mxchat-autosave-field"
1200 + value="1"
1201 + data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>"
1202 + <?php checked($is_enabled, '1'); ?>>
1203 + <span class="mxchat-toggle-slider"></span>
1204 + </label>
1205 + <span class="mxchat-toggle-label">
1206 + <?php echo esc_html($label); ?> (<?php echo esc_html($post_type); ?>)
1207 + </span>
1208 + </div>
1209 + <?php
1210 + }
1211 + } else {
1212 + echo '<p>' . esc_html__('No custom post types found.', 'mxchat') . '</p>';
1213 + }
1214 + ?>
1215 + </div>
1216 + </div>
1217 +</div>
1218 +
1219 +
1220 +
770 1221 </div>
771 1222 </div>
772 1223 </div>
773 1224
@@ -774,10 +1225,9 @@
774 1225 <!-- Import Methods -->
775 1226 <div class="mxchat-import-methods">
776 1227 <div class="mxchat-method-card">
777 1228 <h4><?php esc_html_e('Sitemap Import', 'mxchat'); ?></h4>
778 - <p><?php esc_html_e('Import all content from your sitemap automatically.', 'mxchat'); ?></p>
779 - </div>
1229 + <p><span class="red-warning">IMPORTANT:</span> <?php esc_html_e('Use a content-specific sub-sitemap, not the sitemap index.', 'mxchat'); ?></p> </div>
780 1230 <div class="mxchat-method-card">
781 1231 <h4><?php esc_html_e('PDF Import', 'mxchat'); ?></h4>
782 1232 <p><?php esc_html_e('Import knowledge directly from PDF documents.', 'mxchat'); ?></p>
783 1233 </div>
@@ -813,85 +1263,177 @@
813 1263 </div>
814 1264 <?php endif; ?>
815 1265
816 1266 <!-- Processing Status -->
817 - <?php if ($pdf_status && $pdf_status['status'] !== 'complete') : ?>
818 - <div class="mxchat-status-card">
819 - <div class="mxchat-status-header">
820 - <h4><?php esc_html_e('PDF Processing Status', 'mxchat'); ?></h4>
821 - <?php if ($is_processing) : ?>
822 - <form method="post" class="mxchat-stop-form"
823 - action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_stop_processing')); ?>">
824 - <?php wp_nonce_field('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce'); ?>
825 - <button type="submit" name="stop_processing" class="mxchat-button-secondary">
826 - <?php esc_html_e('Stop Processing', 'mxchat'); ?>
827 - </button>
828 - </form>
829 - <?php endif; ?>
830 - </div>
831 - <div class="mxchat-progress-bar">
832 - <div class="mxchat-progress-fill" style="width: <?php echo esc_attr($pdf_status['percentage']); ?>%"></div>
833 - </div>
834 - <div class="mxchat-status-details">
835 - <p><?php printf(
836 - esc_html__('Progress: %1$d of %2$d pages (%3$d%%)', 'mxchat'),
837 - absint($pdf_status['processed_pages']),
838 - absint($pdf_status['total_pages']),
839 - absint($pdf_status['percentage'])
840 - ); ?></p>
841 - <p><?php printf(
842 - esc_html__('Status: %s', 'mxchat'),
843 - esc_html(ucfirst($pdf_status['status']))
844 - ); ?></p>
845 - <p><?php printf(
846 - esc_html__('Last update: %s', 'mxchat'),
847 - esc_html($pdf_status['last_update'])
848 - ); ?></p>
849 - </div>
850 - </div>
851 - <?php endif; ?>
1267 +<?php if ($pdf_status && $pdf_status['status'] !== 'complete') : ?>
1268 + <div class="mxchat-status-card">
1269 + <div class="mxchat-status-header">
1270 + <h4><?php esc_html_e('PDF Processing Status', 'mxchat'); ?></h4>
1271 + <?php if ($is_processing) : ?>
1272 + <form method="post" class="mxchat-stop-form"
1273 + action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_stop_processing')); ?>">
1274 + <?php wp_nonce_field('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce'); ?>
1275 + <button type="submit" name="stop_processing" class="mxchat-button-secondary">
1276 + <?php esc_html_e('Stop Processing', 'mxchat'); ?>
1277 + </button>
1278 + </form>
1279 + <?php endif; ?>
1280 +
1281 + <?php if ($pdf_status['status'] === 'error') : ?>
1282 + <span class="mxchat-status-badge mxchat-status-failed"><?php esc_html_e('Error', 'mxchat'); ?></span>
1283 + <?php endif; ?>
1284 + </div>
1285 + <div class="mxchat-progress-bar">
1286 + <div class="mxchat-progress-fill" style="width: <?php echo esc_attr($pdf_status['percentage']); ?>%"></div>
1287 + </div>
1288 + <div class="mxchat-status-details">
1289 + <p><?php printf(
1290 + esc_html__('Progress: %1$d of %2$d pages (%3$d%%)', 'mxchat'),
1291 + absint($pdf_status['processed_pages']),
1292 + absint($pdf_status['total_pages']),
1293 + absint($pdf_status['percentage'])
1294 + ); ?></p>
1295 + <p><?php printf(
1296 + esc_html__('Status: %s', 'mxchat'),
1297 + esc_html(ucfirst($pdf_status['status']))
1298 + ); ?></p>
1299 + <p><?php printf(
1300 + esc_html__('Last update: %s', 'mxchat'),
1301 + esc_html($pdf_status['last_update'])
1302 + ); ?></p>
1303 +
1304 + <?php if (!empty($pdf_status['error'])) : ?>
1305 + <div class="mxchat-error-notice">
1306 + <p class="error"><?php echo esc_html($pdf_status['error']); ?></p>
1307 + </div>
1308 + <?php endif; ?>
1309 + </div>
1310 + </div>
1311 +<?php endif; ?>
852 1312
853 - <?php if ($sitemap_status && $sitemap_status['status'] !== 'complete') : ?>
854 - <div class="mxchat-status-card">
855 - <div class="mxchat-status-header">
856 - <h4><?php esc_html_e('Sitemap Processing Status (Refresh for update)', 'mxchat'); ?></h4>
857 - <?php if ($is_processing) : ?>
858 - <form method="post" class="mxchat-stop-form"
859 - action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_stop_processing')); ?>">
860 - <?php wp_nonce_field('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce'); ?>
861 - <button type="submit" name="stop_processing" class="mxchat-button-secondary">
862 - <?php esc_html_e('Stop Processing', 'mxchat'); ?>
863 - </button>
864 - </form>
865 - <?php endif; ?>
866 - </div>
867 - <div class="mxchat-progress-bar">
868 - <div class="mxchat-progress-fill" style="width: <?php echo esc_attr($sitemap_status['percentage']); ?>%"></div>
869 - </div>
870 - <div class="mxchat-status-details">
871 - <p><?php printf(
872 - esc_html__('Progress: %1$d of %2$d URLs (%3$d%%)', 'mxchat'),
873 - absint($sitemap_status['processed_urls']),
874 - absint($sitemap_status['total_urls']),
875 - absint($sitemap_status['percentage'])
876 - ); ?></p>
877 - <?php if (!empty($sitemap_status['error']) || !empty($sitemap_status['last_error'])) : ?>
878 - <div class="mxchat-error-notice">
879 - <?php if (!empty($sitemap_status['error'])) : ?>
880 - <p class="error"><?php echo esc_html($sitemap_status['error']); ?></p>
881 - <?php endif; ?>
882 - <?php if (!empty($sitemap_status['last_error'])) : ?>
883 - <p class="last-error"><?php echo esc_html__('Last error:', 'mxchat') . ' ' . esc_html($sitemap_status['last_error']); ?></p>
884 - <?php endif; ?>
885 - </div>
886 - <?php endif; ?>
887 - </div>
888 - </div>
889 - <?php endif; ?>
1313 +
1314 +<!-- Single URL Submission Status -->
1315 +<?php
1316 +// Get single URL status
1317 +$single_url_status = $this->get_single_url_status();
1318 +$is_active_processing =
1319 + ($sitemap_status && $sitemap_status['status'] === 'processing') ||
1320 + ($pdf_status && $pdf_status['status'] === 'processing');
1321 +?>
1322 +
1323 +<div id="mxchat-single-url-status-container" <?php echo $is_active_processing ? 'style="display:none;"' : ''; ?>>
1324 + <?php if ($single_url_status && !$is_active_processing) : ?>
1325 + <div class="mxchat-status-card">
1326 + <div class="mxchat-status-header">
1327 + <h4><?php esc_html_e('Last URL Submission', 'mxchat'); ?></h4>
1328 + <?php if ($single_url_status['status'] === 'failed') : ?>
1329 + <span class="mxchat-status-badge mxchat-status-failed"><?php esc_html_e('Failed', 'mxchat'); ?></span>
1330 + <?php else : ?>
1331 + <span class="mxchat-status-badge mxchat-status-success"><?php esc_html_e('Success', 'mxchat'); ?></span>
1332 + <?php endif; ?>
1333 + </div>
1334 + <div class="mxchat-status-details">
1335 + <p><strong><?php esc_html_e('URL:', 'mxchat'); ?></strong>
1336 + <a href="<?php echo esc_url($single_url_status['url']); ?>" target="_blank">
1337 + <?php echo esc_html(strlen($single_url_status['url']) > 60 ? substr($single_url_status['url'], 0, 57) . '...' : $single_url_status['url']); ?>
1338 + </a>
1339 + </p>
1340 + <p><strong><?php esc_html_e('Submitted:', 'mxchat'); ?></strong> <?php echo esc_html($single_url_status['human_time']); ?></p>
1341 +
1342 + <?php if ($single_url_status['status'] === 'failed' && !empty($single_url_status['error'])) : ?>
1343 + <div class="mxchat-error-notice">
1344 + <p class="error"><?php echo esc_html($single_url_status['error']); ?></p>
890 1345 </div>
1346 + <?php endif; ?>
891 1347
1348 + <?php if ($single_url_status['status'] === 'complete') : ?>
1349 + <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>
1350 + <p><strong><?php esc_html_e('Embedding Dimensions:', 'mxchat'); ?></strong> <?php echo esc_html($single_url_status['embedding_dimensions']); ?></p>
1351 + <?php endif; ?>
1352 + </div>
1353 + </div>
1354 + <?php endif; ?>
1355 +</div>
892 1356
1357 +
1358 +<?php if ($sitemap_status && $sitemap_status['status'] !== 'complete') : ?>
1359 + <div class="mxchat-status-card">
1360 + <div class="mxchat-status-header">
1361 + <h4><?php esc_html_e('Sitemap Processing Status', 'mxchat'); ?></h4>
1362 + <?php if ($is_processing) : ?>
1363 + <form method="post" class="mxchat-stop-form"
1364 + action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_stop_processing')); ?>">
1365 + <?php wp_nonce_field('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce'); ?>
1366 + <button type="submit" name="stop_processing" class="mxchat-button-secondary">
1367 + <?php esc_html_e('Stop Processing', 'mxchat'); ?>
1368 + </button>
1369 + </form>
1370 + <?php endif; ?>
1371 + </div>
1372 + <div class="mxchat-progress-bar">
1373 + <div class="mxchat-progress-fill" style="width: <?php echo esc_attr($sitemap_status['percentage']); ?>%"></div>
1374 + </div>
1375 + <div class="mxchat-status-details">
1376 + <p><?php printf(
1377 + esc_html__('Progress: %1$d of %2$d URLs (%3$d%%)', 'mxchat'),
1378 + absint($sitemap_status['processed_urls']),
1379 + absint($sitemap_status['total_urls']),
1380 + absint($sitemap_status['percentage'])
1381 + ); ?></p>
1382 + <?php if (!empty($sitemap_status['error']) || !empty($sitemap_status['last_error'])) : ?>
1383 + <div class="mxchat-error-notice">
1384 + <?php if (!empty($sitemap_status['error'])) : ?>
1385 + <p class="error"><?php echo esc_html($sitemap_status['error']); ?></p>
1386 + <?php endif; ?>
1387 + <?php if (!empty($sitemap_status['last_error'])) : ?>
1388 + <p class="last-error"><?php echo esc_html__('Last error:', 'mxchat') . ' ' . esc_html($sitemap_status['last_error']); ?></p>
1389 + <?php endif; ?>
893 1390 </div>
1391 + <?php endif; ?>
1392 +
1393 + <?php if (!empty($sitemap_status['failed_urls']) && $sitemap_status['failed_urls'] > 0) : ?>
1394 + <div class="mxchat-failed-urls">
1395 + <h5><?php echo sprintf(esc_html__('Failed URLs (%d)', 'mxchat'), absint($sitemap_status['failed_urls'])); ?></h5>
1396 + <?php if (!empty($sitemap_status['failed_urls_list'])) : ?>
1397 + <div class="mxchat-failed-urls-list" style="max-height: 200px; overflow-y: auto; margin-top: 10px;">
1398 + <table class="widefat striped">
1399 + <thead>
1400 + <tr>
1401 + <th><?php esc_html_e('URL', 'mxchat'); ?></th>
1402 + <th><?php esc_html_e('Error', 'mxchat'); ?></th>
1403 + <th><?php esc_html_e('Time', 'mxchat'); ?></th>
1404 + </tr>
1405 + </thead>
1406 + <tbody>
1407 + <?php foreach ($sitemap_status['failed_urls_list'] as $failed_url) : ?>
1408 + <tr>
1409 + <td style="word-break: break-all;">
1410 + <a href="<?php echo esc_url($failed_url['url']); ?>" target="_blank" rel="noopener noreferrer">
1411 + <?php echo esc_html(strlen($failed_url['url']) > 60 ? substr($failed_url['url'], 0, 57) . '...' : $failed_url['url']); ?>
1412 + </a>
1413 + </td>
1414 + <td><?php echo esc_html($failed_url['error']); ?></td>
1415 + <td><?php echo esc_html(human_time_diff($failed_url['time'], time()) . ' ' . __('ago', 'mxchat')); ?></td>
1416 + </tr>
1417 + <?php endforeach; ?>
1418 + </tbody>
1419 + </table>
1420 + </div>
1421 + <?php endif; ?>
1422 + </div>
1423 + <?php endif; ?>
1424 + </div>
1425 + </div>
1426 +<?php endif; ?>
1427 +
1428 +
1429 + </div>
1430 +
1431 +
1432 + </div>
1433 +
1434 +
1435 +
894 1436 </div>
895 1437
896 1438 <!-- Direct Content Submission Card -->
897 1439 <div class="mxchat-card">
@@ -973,16 +1515,19 @@
973 1515 <?php echo esc_textarea($prompt->article_content); ?>
974 1516 </textarea>
975 1517 </td>
976 1518 <td class="mxchat-url-cell">
977 - <?php if (!empty($prompt->source_url)) : ?>
978 - <a href="<?php echo esc_url($prompt->source_url); ?>" target="_blank">
979 - <span class="dashicons dashicons-external"></span>
980 - <?php esc_html_e('View Source', 'mxchat'); ?>
981 - </a>
982 - <?php else : ?>
983 - <span class="mxchat-na"><?php esc_html_e('N/A', 'mxchat'); ?></span>
984 - <?php endif; ?>
1519 + <div class="url-view">
1520 + <?php if (!empty($prompt->source_url)) : ?>
1521 + <a href="<?php echo esc_url($prompt->source_url); ?>" target="_blank">
1522 + <span class="dashicons dashicons-external"></span>
1523 + <?php esc_html_e('View Source', 'mxchat'); ?>
1524 + </a>
1525 + <?php else : ?>
1526 + <span class="mxchat-na"><?php esc_html_e('N/A', 'mxchat'); ?></span>
1527 + <?php endif; ?>
1528 + </div>
1529 + <input type="text" class="url-edit" style="display:none;" value="<?php echo esc_attr($prompt->source_url); ?>" />
985 1530 </td>
986 1531 <td class="mxchat-actions-cell">
987 1532 <button class="mxchat-button-icon edit-button"
988 1533 data-id="<?php echo esc_attr($prompt->id); ?>">
@@ -1084,30 +1629,154 @@
1084 1629
1085 1630 wp_safe_redirect(add_query_arg(array('page' => 'mxchat-prompts', 'deleted' => 'true'), admin_url('admin.php')));
1086 1631 exit;
1087 1632 }
1088 -public function mxchat_generate_embedding($text) {
1089 - $options = get_option('mxchat_options');
1090 - $api_key = $options['api_key'] ?? esc_html__('default_api_key', 'mxchat');
1091 1633
1092 - $response = wp_remote_post('https://api.openai.com/v1/embeddings', array(
1093 - 'body' => wp_json_encode(array(
1094 - 'model' => esc_html__('text-embedding-ada-002', 'mxchat'),
1095 - 'input' => $text
1096 - )),
1097 - 'headers' => array(
1098 - 'Authorization' => 'Bearer ' . $api_key,
1099 - 'Content-Type' => esc_html__('application/json', 'mxchat')
1100 - ),
1101 - ));
1102 1634
1103 - if (is_wp_error($response)) {
1104 - return null;
1635 +public function mxchat_generate_embedding($text) {
1636 + // Enable detailed logging for debugging
1637 + //error_log('[MXCHAT-EMBED] Starting embedding generation. Text length: ' . strlen($text) . ' bytes');
1638 + //error_log('[MXCHAT-EMBED] Text preview: ' . substr($text, 0, 100) . '...');
1639 +
1640 + $options = get_option('mxchat_options');
1641 + $selected_model = $options['embedding_model'] ?? 'text-embedding-ada-002';
1642 + //error_log('[MXCHAT-EMBED] Selected embedding model: ' . $selected_model);
1643 +
1644 + // Determine provider and endpoint
1645 + if (strpos($selected_model, 'voyage') === 0) {
1646 + $api_key = $options['voyage_api_key'] ?? '';
1647 + $endpoint = 'https://api.voyageai.com/v1/embeddings';
1648 + $provider_name = 'Voyage AI';
1649 + //error_log('[MXCHAT-EMBED] Using Voyage AI API');
1650 + } else {
1651 + $api_key = $options['api_key'] ?? '';
1652 + $endpoint = 'https://api.openai.com/v1/embeddings';
1653 + $provider_name = 'OpenAI';
1654 + //error_log('[MXCHAT-EMBED] Using OpenAI API');
1655 + }
1656 +
1657 + //error_log('[MXCHAT-EMBED] Using endpoint: ' . $endpoint);
1658 +
1659 + if (empty($api_key)) {
1660 + $error_message = sprintf('Missing %s API key. Please configure your API key in the MxChat settings.', $provider_name);
1661 + //error_log('[MXCHAT-EMBED] Error: ' . $error_message);
1662 + return $error_message;
1663 + }
1664 +
1665 + // Check if text is too long (for OpenAI, roughly estimate tokens as words/0.75)
1666 + $estimated_tokens = ceil(str_word_count($text) / 0.75);
1667 + //error_log('[MXCHAT-EMBED] Estimated token count: ~' . $estimated_tokens);
1668 +
1669 + if ($estimated_tokens > 8000 && strpos($selected_model, 'voyage') === false) {
1670 + //error_log('[MXCHAT-EMBED] Warning: Text may exceed OpenAI token limits (8K for most models)');
1671 + // Consider truncating text here
1672 + }
1673 +
1674 + // Prepare request body
1675 + $request_body = array(
1676 + 'model' => $selected_model,
1677 + 'input' => $text
1678 + );
1679 +
1680 + // Add output_dimension for voyage-3-large model
1681 + if ($selected_model === 'voyage-3-large') {
1682 + $request_body['output_dimension'] = 2048;
1683 + }
1684 +
1685 + //error_log('[MXCHAT-EMBED] Request prepared with model: ' . $selected_model);
1686 +
1687 + // Make API request
1688 + //error_log('[MXCHAT-EMBED] Sending API request to: ' . $endpoint);
1689 + $response = wp_remote_post($endpoint, array(
1690 + 'body' => wp_json_encode($request_body),
1691 + 'headers' => array(
1692 + 'Authorization' => 'Bearer ' . $api_key,
1693 + 'Content-Type' => 'application/json'
1694 + ),
1695 + 'timeout' => 60 // Increased timeout for large inputs
1696 + ));
1697 +
1698 + // Handle wp_remote_post errors
1699 + if (is_wp_error($response)) {
1700 + $error_message = $response->get_error_message();
1701 + //error_log('[MXCHAT-EMBED] WP Remote Post Error: ' . $error_message);
1702 + return 'Connection error: ' . $error_message;
1703 + }
1704 +
1705 + // Get and check HTTP response code
1706 + $http_code = wp_remote_retrieve_response_code($response);
1707 + //error_log('[MXCHAT-EMBED] API Response Code: ' . $http_code);
1708 +
1709 + if ($http_code !== 200) {
1710 + $error_body = wp_remote_retrieve_body($response);
1711 + //error_log('[MXCHAT-EMBED] API Error Response Body: ' . $error_body);
1712 +
1713 + // Try to parse error for more details
1714 + $error_json = json_decode($error_body, true);
1715 + if (json_last_error() === JSON_ERROR_NONE && isset($error_json['error'])) {
1716 + $error_type = $error_json['error']['type'] ?? 'unknown';
1717 + $error_message = $error_json['error']['message'] ?? 'No message';
1718 + //error_log('[MXCHAT-EMBED] API Error Type: ' . $error_type);
1719 + //error_log('[MXCHAT-EMBED] API Error Message: ' . $error_message);
1720 +
1721 + // Customize error message for common API errors
1722 + if ($error_type === 'invalid_request_error' && strpos($error_message, 'API key') !== false) {
1723 + $error_message = sprintf('Invalid %s API key. Please check your API key in the MxChat settings.', $provider_name);
1724 + } elseif ($error_type === 'authentication_error') {
1725 + $error_message = sprintf('%s authentication failed. Please verify your API key in the MxChat settings.', $provider_name);
1726 + }
1727 +
1728 + //error_log('[MXCHAT-EMBED] Returning error: ' . $error_message);
1729 + return $error_message;
1105 1730 }
1731 +
1732 + $error_message = sprintf("API Error (HTTP %d): Unable to generate embedding", $http_code);
1733 + //error_log('[MXCHAT-EMBED] Returning error: ' . $error_message);
1734 + return $error_message;
1735 + }
1736 +
1737 + // Parse response body
1738 + $response_body = wp_remote_retrieve_body($response);
1739 + //error_log('[MXCHAT-EMBED] Received response length: ' . strlen($response_body) . ' bytes');
1740 +
1741 + $response_data = json_decode($response_body, true);
1742 +
1743 + if (json_last_error() !== JSON_ERROR_NONE) {
1744 + $error = json_last_error_msg();
1745 + //error_log('[MXCHAT-EMBED] JSON Parse Error: ' . $error);
1746 + //error_log('[MXCHAT-EMBED] Response preview: ' . substr($response_body, 0, 200));
1747 + return "Failed to parse API response: $error";
1748 + }
1749 +
1750 + // Both APIs use the same structure, so we can extract the embedding the same way
1751 + if (isset($response_data['data'][0]['embedding'])) {
1752 + $embedding_dimensions = count($response_data['data'][0]['embedding']);
1753 + //error_log('[MXCHAT-EMBED] Successfully extracted embedding with ' . $embedding_dimensions . ' dimensions');
1754 +
1755 + // Check if embedding dimensions are as expected
1756 + if (($selected_model === 'text-embedding-ada-002' && $embedding_dimensions !== 1536) ||
1757 + ($selected_model === 'voyage-3-large' && $embedding_dimensions !== 2048)) {
1758 + //error_log('[MXCHAT-EMBED] Warning: Unexpected embedding dimensions');
1759 + }
1760 +
1761 + return $response_data['data'][0]['embedding'];
1762 + } else {
1763 + //error_log('[MXCHAT-EMBED] Error: No embedding found in response');
1764 + //error_log('[MXCHAT-EMBED] Response structure: ' . wp_json_encode(array_keys($response_data)));
1765 +
1766 + if (isset($response_data['error'])) {
1767 + $error_message = "API Error in response: " . wp_json_encode($response_data['error']);
1768 + //error_log('[MXCHAT-EMBED] ' . $error_message);
1769 + return $error_message;
1770 + }
1771 +
1772 + $error_message = "Invalid API response format: No embedding found";
1773 + //error_log('[MXCHAT-EMBED] ' . $error_message);
1774 + return $error_message;
1775 + }
1776 +}
1106 1777
1107 - $response_data = json_decode(wp_remote_retrieve_body($response), true);
1108 - return $response_data['data'][0]['embedding'] ?? null;
1109 - }
1778 +
1110 1779 public function mxchat_delete_chat_history() {
1111 1780 if (!current_user_can('manage_options')) {
1112 1781 echo wp_json_encode(['error' => esc_html__('You do not have sufficient permissions.', 'mxchat')]);
1113 1782 wp_die();
@@ -1195,45 +1864,106 @@
1195 1864 }
1196 1865
1197 1866
1198 1867
1199 -// Add this method to handle post updates
1868 +/**
1869 + * Get all public post types
1870 + *
1871 + * @return array Associative array of post type names and labels
1872 + */
1873 +private function get_public_post_types() {
1874 + $post_types = get_post_types(array('public' => true), 'objects');
1875 + $post_type_options = array();
1876 +
1877 + foreach ($post_types as $post_type) {
1878 + $post_type_options[$post_type->name] = $post_type->label;
1879 + }
1880 +
1881 + return $post_type_options;
1882 +}
1883 +
1884 +/**
1885 + * Handle post updates and process content for the chatbot
1886 + *
1887 + * @param int $post_id The ID of the post being saved
1888 + * @param WP_Post $post The post object
1889 + * @param bool $update Whether this is an existing post being updated
1890 + */
1200 1891 public function handle_post_update($post_id, $post, $update) {
1201 1892 // Basic validation checks
1202 1893 if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE || wp_is_post_revision($post_id)) {
1203 1894 return;
1204 1895 }
1205 -
1896 +
1206 1897 // Only process published content
1207 - if (!in_array($post->post_status, array('publish'))) {
1898 + if ($post->post_status !== 'publish') {
1208 1899 return;
1209 1900 }
1210 -
1901 +
1902 + $post_type = $post->post_type;
1903 +
1211 1904 // Check if sync is enabled for this post type
1212 - $post_type = $post->post_type;
1213 - if (!in_array($post_type, array('post', 'page'))) {
1214 - return;
1905 + $should_sync = false;
1906 +
1907 + // Check built-in post types first
1908 + if ($post_type === 'post' && get_option('mxchat_auto_sync_posts') === '1') {
1909 + $should_sync = true;
1910 + } else if ($post_type === 'page' && get_option('mxchat_auto_sync_pages') === '1') {
1911 + $should_sync = true;
1912 + } else {
1913 + // Check custom post types
1914 + $option_name = 'mxchat_auto_sync_' . $post_type;
1915 + if (get_option($option_name) === '1') {
1916 + $should_sync = true;
1917 + }
1215 1918 }
1216 -
1217 - $sync_option = ($post_type === 'post') ? 'mxchat_auto_sync_posts' : 'mxchat_auto_sync_pages';
1218 - if (get_option($sync_option) != '1') {
1919 +
1920 + if (!$should_sync) {
1219 1921 return;
1220 1922 }
1221 -
1923 +
1222 1924 // Prepare content and URL
1223 1925 $content = wp_strip_all_tags($post->post_content);
1926 +
1927 + // For custom post types like job_listing, include additional fields
1928 + if ($post_type === 'job_listing') {
1929 + // Add title as it's important for job listings
1930 + $content = $post->post_title . "\n\n" . $content;
1931 +
1932 + // Add job-specific meta if available
1933 + $job_location = get_post_meta($post_id, '_job_location', true);
1934 + if (!empty($job_location)) {
1935 + $content .= "\n\nLocation: " . $job_location;
1936 + }
1937 +
1938 + // Get job type terms
1939 + $job_types = get_the_terms($post_id, 'job_listing_type');
1940 + if (!empty($job_types) && !is_wp_error($job_types)) {
1941 + $types = array();
1942 + foreach ($job_types as $type) {
1943 + $types[] = $type->name;
1944 + }
1945 + $content .= "\n\nJob Type: " . implode(', ', $types);
1946 + }
1947 +
1948 + // Get company name if available
1949 + $company_name = get_post_meta($post_id, '_company_name', true);
1950 + if (!empty($company_name)) {
1951 + $content .= "\n\nCompany: " . $company_name;
1952 + }
1953 + }
1954 +
1224 1955 $url = get_permalink($post_id);
1225 -
1956 +
1226 1957 // Generate embedding vector
1227 1958 $embedding_vector = $this->mxchat_generate_embedding($content);
1228 1959 if (!$embedding_vector) {
1229 1960 return;
1230 1961 }
1231 -
1962 +
1232 1963 // Check for Pinecone addon and its settings
1233 1964 $pinecone_settings = get_option('mxchat_pinecone_addon_options');
1234 1965 $use_pinecone = false;
1235 -
1236 1966 if ($pinecone_settings && is_array($pinecone_settings)) {
1237 1967 // Check if Pinecone is enabled and all required settings are present
1238 1968 $use_pinecone = (
1239 1969 isset($pinecone_settings['mxchat_use_pinecone']) &&
@@ -1243,9 +1973,9 @@
1243 1973 !empty($pinecone_settings['mxchat_pinecone_index']) &&
1244 1974 !empty($pinecone_settings['mxchat_pinecone_environment'])
1245 1975 );
1246 1976 }
1247 -
1977 +
1248 1978 if ($use_pinecone) {
1249 1979 // Use Pinecone
1250 1980 $pinecone_result = $this->store_in_pinecone_main(
1251 1981 $embedding_vector,
@@ -1254,11 +1984,10 @@
1254 1984 $pinecone_settings['mxchat_pinecone_api_key'],
1255 1985 $pinecone_settings['mxchat_pinecone_environment'],
1256 1986 $pinecone_settings['mxchat_pinecone_index']
1257 1987 );
1258 -
1259 1988 if (!$pinecone_result['success']) {
1260 - //error_log('MXChat: Pinecone sync failed - falling back to WordPress DB');
1989 + // Fallback to WordPress DB
1261 1990 $this->store_in_wordpress_db($content, $url, $embedding_vector);
1262 1991 }
1263 1992 } else {
1264 1993 // Fallback to WordPress DB storage
@@ -1263,9 +1992,122 @@
1263 1992 } else {
1264 1993 // Fallback to WordPress DB storage
1265 1994 $this->store_in_wordpress_db($content, $url, $embedding_vector);
1266 1995 }
1996 +
1267 1997 }
1998 +/**
1999 + * Handle deletion of posts from both Pinecone and WordPress DB
2000 + *
2001 + * @param int $post_id The ID of the post being deleted
2002 + * @return void
2003 + */
2004 +public function mxchat_handle_post_delete($post_id) {
2005 + // Get post data before it's deleted
2006 + $post = get_post($post_id);
2007 +
2008 + // Basic validation
2009 + if (!$post || wp_is_post_revision($post_id)) {
2010 + return;
2011 + }
2012 +
2013 + $post_type = $post->post_type;
2014 +
2015 + // Check if sync is enabled for this post type
2016 + $should_sync = false;
2017 +
2018 + // Check built-in post types first
2019 + if ($post_type === 'post' && get_option('mxchat_auto_sync_posts') === '1') {
2020 + $should_sync = true;
2021 + } else if ($post_type === 'page' && get_option('mxchat_auto_sync_pages') === '1') {
2022 + $should_sync = true;
2023 + } else {
2024 + // Check custom post types
2025 + $option_name = 'mxchat_auto_sync_' . $post_type;
2026 + if (get_option($option_name) === '1') {
2027 + $should_sync = true;
2028 + }
2029 + }
2030 +
2031 + if (!$should_sync) {
2032 + return;
2033 + }
2034 +
2035 + // Get the URL before post is deleted
2036 + $source_url = get_permalink($post_id);
2037 + if (!$source_url) {
2038 + //error_log('MXChat: Failed to get permalink for post ' . $post_id);
2039 + return;
2040 + }
2041 +
2042 + // Check for Pinecone addon and its settings
2043 + $pinecone_settings = get_option('mxchat_pinecone_addon_options');
2044 + $use_pinecone = false;
2045 + if ($pinecone_settings && is_array($pinecone_settings)) {
2046 + $use_pinecone = (
2047 + isset($pinecone_settings['mxchat_use_pinecone']) &&
2048 + $pinecone_settings['mxchat_use_pinecone'] === '1' &&
2049 + !empty($pinecone_settings['mxchat_pinecone_api_key']) &&
2050 + !empty($pinecone_settings['mxchat_pinecone_host']) &&
2051 + !empty($pinecone_settings['mxchat_pinecone_index']) &&
2052 + !empty($pinecone_settings['mxchat_pinecone_environment'])
2053 + );
2054 + }
2055 +
2056 + $deletion_successful = false;
2057 +
2058 + if ($use_pinecone) {
2059 + try {
2060 + $pinecone_result = $this->delete_from_pinecone(
2061 + array($source_url),
2062 + $pinecone_settings['mxchat_pinecone_api_key'],
2063 + $pinecone_settings['mxchat_pinecone_environment'],
2064 + $pinecone_settings['mxchat_pinecone_index']
2065 + );
2066 +
2067 + if (!$pinecone_result['success']) {
2068 + //error_log('MXChat: Pinecone deletion failed for URL: ' . $source_url . ' - ' . $pinecone_result['message']);
2069 + } else {
2070 + $deletion_successful = true;
2071 + }
2072 + } catch (Exception $e) {
2073 + //error_log('MXChat: Exception during Pinecone deletion - ' . $e->getMessage());
2074 + }
2075 + }
2076 +
2077 + // Always attempt WordPress DB deletion, regardless of Pinecone status
2078 + try {
2079 + global $wpdb;
2080 + $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
2081 +
2082 + $result = $wpdb->delete(
2083 + $table_name,
2084 + array('source_url' => $source_url),
2085 + array('%s')
2086 + );
2087 +
2088 + if ($result === false) {
2089 + //error_log('MXChat: WordPress DB deletion failed for URL: ' . $source_url);
2090 + } else {
2091 + $deletion_successful = true;
2092 + }
2093 + } catch (Exception $e) {
2094 + //error_log('MXChat: Exception during WordPress DB deletion - ' . $e->getMessage());
2095 + }
2096 +
2097 + if (!$deletion_successful) {
2098 + //error_log('MXChat: Complete deletion failure for post ID: ' . $post_id . ' URL: ' . $source_url);
2099 + } else {
2100 +
2101 + }
2102 +}
2103 +
2104 +
2105 +
2106 +
2107 +
2108 +
2109 +
1268 2110 // Modified storage function to add type field
1269 2111 private function store_in_pinecone_main($embedding_vector, $content, $url, $api_key, $environment, $index_name, $vector_id = null) {
1270 2112 $vector_id = $vector_id ?: md5($url);
1271 2113 $options = get_option('mxchat_pinecone_addon_options');
@@ -1332,8 +2174,10 @@
1332 2174 'success' => true,
1333 2175 'message' => 'Successfully stored in Pinecone'
1334 2176 );
1335 2177 }
2178 +
2179 +
1336 2180 // Helper method for WordPress DB storage
1337 2181 private function store_in_wordpress_db($content, $url, $embedding_vector) {
1338 2182 global $wpdb;
1339 2183 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
@@ -1365,103 +2209,10 @@
1365 2209 array('%s', '%s', '%s', '%s')
1366 2210 );
1367 2211 }
1368 2212 }
1369 -/**
1370 - * Handle deletion of posts and pages from both Pinecone and WordPress DB
1371 - *
1372 - * @param int $post_id The ID of the post being deleted
1373 - * @return void
1374 - */
1375 -public function mxchat_handle_post_delete($post_id) {
1376 - // Get post data before it's deleted
1377 - $post = get_post($post_id);
1378 2213
1379 - // Basic validation
1380 - if (!$post || wp_is_post_revision($post_id)) {
1381 - return;
1382 - }
1383 2214
1384 - // Check post type
1385 - $post_type = $post->post_type;
1386 - if (!in_array($post_type, array('post', 'page'))) {
1387 - return;
1388 - }
1389 -
1390 - // Check if sync is enabled for this post type
1391 - $sync_option = ($post_type === 'post') ? 'mxchat_auto_sync_posts' : 'mxchat_auto_sync_pages';
1392 - if (get_option($sync_option) != '1') {
1393 - return;
1394 - }
1395 -
1396 - // Get the URL before post is deleted
1397 - $source_url = get_permalink($post_id);
1398 - if (!$source_url) {
1399 - //error_log('MXChat: Failed to get permalink for post ' . $post_id);
1400 - return;
1401 - }
1402 -
1403 - // Check for Pinecone addon and its settings
1404 - $pinecone_settings = get_option('mxchat_pinecone_addon_options');
1405 - $use_pinecone = false;
1406 - if ($pinecone_settings && is_array($pinecone_settings)) {
1407 - $use_pinecone = (
1408 - isset($pinecone_settings['mxchat_use_pinecone']) &&
1409 - $pinecone_settings['mxchat_use_pinecone'] === '1' &&
1410 - !empty($pinecone_settings['mxchat_pinecone_api_key']) &&
1411 - !empty($pinecone_settings['mxchat_pinecone_host']) &&
1412 - !empty($pinecone_settings['mxchat_pinecone_index']) &&
1413 - !empty($pinecone_settings['mxchat_pinecone_environment'])
1414 - );
1415 - }
1416 -
1417 - $deletion_successful = false;
1418 -
1419 - if ($use_pinecone) {
1420 - try {
1421 - $pinecone_result = $this->delete_from_pinecone(
1422 - array($source_url),
1423 - $pinecone_settings['mxchat_pinecone_api_key'],
1424 - $pinecone_settings['mxchat_pinecone_environment'],
1425 - $pinecone_settings['mxchat_pinecone_index']
1426 - );
1427 -
1428 - if (!$pinecone_result['success']) {
1429 - //error_log('MXChat: Pinecone deletion failed for URL: ' . $source_url . ' - ' . $pinecone_result['message']);
1430 - } else {
1431 - $deletion_successful = true;
1432 - }
1433 - } catch (Exception $e) {
1434 - //error_log('MXChat: Exception during Pinecone deletion - ' . $e->getMessage());
1435 - }
1436 - }
1437 -
1438 - // Always attempt WordPress DB deletion, regardless of Pinecone status
1439 - try {
1440 - global $wpdb;
1441 - $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
1442 -
1443 - $result = $wpdb->delete(
1444 - $table_name,
1445 - array('source_url' => $source_url),
1446 - array('%s')
1447 - );
1448 -
1449 - if ($result === false) {
1450 - //error_log('MXChat: WordPress DB deletion failed for URL: ' . $source_url);
1451 - } else {
1452 - $deletion_successful = true;
1453 - }
1454 - } catch (Exception $e) {
1455 - //error_log('MXChat: Exception during WordPress DB deletion - ' . $e->getMessage());
1456 - }
1457 -
1458 - if (!$deletion_successful) {
1459 - //error_log('MXChat: Complete deletion failure for post ID: ' . $post_id . ' URL: ' . $source_url);
1460 - }
1461 -}
1462 -
1463 -
1464 2215 public function mxchat_handle_content_submission() {
1465 2216 // Check if the form was submitted and the user has permission.
1466 2217 if (!isset($_POST['submit_content']) || !current_user_can('manage_options')) {
1467 2218 return;
@@ -1633,31 +2384,59 @@
1633 2384 $start_page = absint($status['processed_pages']);
1634 2385 $end_page = min($start_page + $batch_size, $total_pages);
1635 2386
1636 2387 $instance = new self(); // Create an instance of the class
2388 + $options = get_option('mxchat_options');
2389 +
2390 + if (empty($options['api_key'])) {
2391 + throw new Exception('API key is missing or invalid');
2392 + }
1637 2393
1638 2394 for ($i = $start_page; $i < $end_page; $i++) {
1639 2395 $text = $pages[$i]->getText();
2396 +
2397 + if (empty($text)) {
2398 + // Log empty page but continue processing
2399 + //error_log(sprintf('[MXCHAT-PDF] Warning: Empty text on page %d of %s', $i + 1, $pdf_url));
2400 + continue;
2401 + }
2402 +
1640 2403 $sanitized_content = $instance->mxchat_sanitize_content_for_api($text); // Call via instance
1641 2404
1642 - if (!empty($sanitized_content)) {
1643 - $embedding_vector = $instance->mxchat_generate_embedding($sanitized_content); // Call via instance
1644 - if (is_array($embedding_vector)) {
1645 - $metadata = array(
1646 - 'document_type' => 'pdf',
1647 - 'total_pages' => $total_pages,
1648 - 'current_page' => $i + 1,
1649 - 'prev_page' => $i > 0 ? $i : null,
1650 - 'next_page' => $i < ($total_pages - 1) ? $i + 2 : null,
1651 - 'source_url' => $pdf_url
1652 - );
2405 + if (empty($sanitized_content)) {
2406 + // Log empty sanitized content but continue processing
2407 + //error_log(sprintf('[MXCHAT-PDF] Warning: No valid content after sanitization on page %d of %s', $i + 1, $pdf_url));
2408 + continue;
2409 + }
1653 2410
1654 - $content_with_metadata = wp_json_encode($metadata) . "\n---\n" . $sanitized_content;
1655 - $page_url = esc_url($pdf_url . "#page=" . ($i + 1));
2411 + $embedding_vector = $instance->mxchat_generate_embedding($sanitized_content); // Call via instance
2412 +
2413 + if (!is_array($embedding_vector)) {
2414 + // If embedding generation fails, log error and throw exception
2415 + $error_msg = is_string($embedding_vector) ? $embedding_vector : 'Unknown embedding generation error';
2416 + //error_log(sprintf('[MXCHAT-PDF] Error generating embedding for page %d: %s', $i + 1, $error_msg));
2417 + throw new Exception(sprintf('Failed to generate embedding for page %d: %s', $i + 1, $error_msg));
2418 + }
2419 +
2420 + $metadata = array(
2421 + 'document_type' => 'pdf',
2422 + 'total_pages' => $total_pages,
2423 + 'current_page' => $i + 1,
2424 + 'prev_page' => $i > 0 ? $i : null,
2425 + 'next_page' => $i < ($total_pages - 1) ? $i + 2 : null,
2426 + 'source_url' => $pdf_url
2427 + );
1656 2428
1657 - $options = get_option('mxchat_options');
1658 - MxChat_Utils::submit_content_to_db($content_with_metadata, $page_url, $options['api_key']);
1659 - }
2429 + $content_with_metadata = wp_json_encode($metadata) . "\n---\n" . $sanitized_content;
2430 + $page_url = esc_url($pdf_url . "#page=" . ($i + 1));
2431 +
2432 + $db_result = MxChat_Utils::submit_content_to_db($content_with_metadata, $page_url, $options['api_key']);
2433 +
2434 + if (is_wp_error($db_result)) {
2435 + error_log(sprintf('[MXCHAT-PDF] Error storing content in database for page %d: %s',
2436 + $i + 1, $db_result->get_error_message()));
2437 + throw new Exception(sprintf('Failed to store content in database for page %d: %s',
2438 + $i + 1, $db_result->get_error_message()));
1660 2439 }
1661 2440
1662 2441 // Update progress with sanitized data
1663 2442 $status['processed_pages'] = absint($i + 1);
@@ -1683,16 +2462,36 @@
1683 2462 }
1684 2463 }
1685 2464
1686 2465 } catch (\Exception $e) {
1687 - $status['status'] = 'error';
1688 - $status['error'] = sanitize_text_field($e->getMessage());
2466 + //error_log(sprintf('[MXCHAT-PDF] Error processing PDF: %s', $e->getMessage()));
2467 +
2468 + // Get current status to update it
2469 + $status_key = sanitize_key('mxchat_pdf_status_' . md5($pdf_url));
2470 + $status = get_transient($status_key);
2471 +
2472 + if (!$status || !is_array($status)) {
2473 + $status = array(
2474 + 'total_pages' => $total_pages,
2475 + 'processed_pages' => 0,
2476 + 'status' => 'error',
2477 + 'error' => sanitize_text_field($e->getMessage()),
2478 + 'last_update' => time()
2479 + );
2480 + } else {
2481 + $status['status'] = 'error';
2482 + $status['error'] = sanitize_text_field($e->getMessage());
2483 + $status['last_update'] = time();
2484 + }
2485 +
1689 2486 set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
2487 +
1690 2488 if (file_exists($pdf_path)) {
1691 2489 wp_delete_file($pdf_path);
1692 2490 }
1693 2491 }
1694 2492 }
2493 +
1695 2494 public function get_pdf_processing_status($pdf_url) {
1696 2495 $pdf_url = esc_url_raw($pdf_url);
1697 2496 $status = get_transient(sanitize_key('mxchat_pdf_status_' . md5($pdf_url)));
1698 2497
@@ -1699,29 +2498,57 @@
1699 2498 if (!$status || !is_array($status)) {
1700 2499 return false;
1701 2500 }
1702 2501
1703 - return array(
2502 + // Check for stalled processing (no updates for 5 minutes)
2503 + if ($status['status'] === 'processing' && (time() - absint($status['last_update'])) > 300) {
2504 + $status['status'] = 'error';
2505 + $status['error'] = __('PDF processing appears to be stalled. No updates for over 5 minutes.', 'mxchat');
2506 +
2507 + // Save the updated status
2508 + set_transient(
2509 + sanitize_key('mxchat_pdf_status_' . md5($pdf_url)),
2510 + array_map('sanitize_text_field', $status),
2511 + DAY_IN_SECONDS
2512 + );
2513 + }
2514 +
2515 + $result = array(
1704 2516 'total_pages' => absint($status['total_pages']),
1705 2517 'processed_pages' => absint($status['processed_pages']),
1706 2518 'percentage' => ($status['total_pages'] > 0)
1707 - ? round((absint($status['processed_pages']) / absint($status['total_pages'])) * 100)
1708 - : 0,
2519 + ? round((absint($status['processed_pages']) / absint($status['total_pages'])) * 100)
2520 + : 0,
1709 2521 'status' => sanitize_text_field($status['status']),
1710 2522 'last_update' => human_time_diff(absint($status['last_update']), time()) . ' ' . esc_html__('ago', 'mxchat')
1711 2523 );
2524 +
2525 + // Add error message if present
2526 + if (isset($status['error']) && !empty($status['error'])) {
2527 + $result['error'] = sanitize_text_field($status['error']);
2528 + }
2529 +
2530 + return $result;
1712 2531 }
2532 +
2533 +
1713 2534 public function mxchat_handle_sitemap_submission() {
2535 + // Start logging the submission process
2536 + //error_log('[MXCHAT-URL] ===== Starting URL submission process =====');
2537 +
1714 2538 // Check if the form was submitted and verify permissions
1715 2539 if (!isset($_POST['submit_sitemap']) || !current_user_can('manage_options')) {
2540 + //error_log('[MXCHAT-URL] Error: Unauthorized access or form not submitted properly');
1716 2541 wp_die(esc_html__('Unauthorized access', 'mxchat'));
1717 2542 }
1718 2543
1719 2544 // Verify nonce
2545 + //error_log('[MXCHAT-URL] Verifying nonce');
1720 2546 check_admin_referer('mxchat_submit_sitemap_action', 'mxchat_submit_sitemap_nonce');
1721 2547
1722 2548 // Validate URL
1723 2549 if (!isset($_POST['sitemap_url']) || empty($_POST['sitemap_url'])) {
2550 + //error_log('[MXCHAT-URL] Error: Empty or missing URL');
1724 2551 set_transient('mxchat_admin_notice_error',
1725 2552 esc_html__('Please provide a valid URL.', 'mxchat'),
1726 2553 30
1727 2554 );
@@ -1729,12 +2556,40 @@
1729 2556 exit;
1730 2557 }
1731 2558
1732 2559 $submitted_url = esc_url_raw($_POST['sitemap_url']);
2560 + //error_log('[MXCHAT-URL] Processing URL: ' . $submitted_url);
2561 +
2562 + // Validate API key first
2563 + $options = get_option('mxchat_options');
2564 + $selected_model = $options['embedding_model'] ?? 'text-embedding-ada-002';
2565 +
2566 + if (strpos($selected_model, 'voyage') === 0) {
2567 + $api_key = $options['voyage_api_key'] ?? '';
2568 + $provider_name = 'Voyage AI';
2569 + } else {
2570 + $api_key = $options['api_key'] ?? '';
2571 + $provider_name = 'OpenAI';
2572 + }
2573 +
2574 + if (empty($api_key)) {
2575 + $error_message = sprintf(
2576 + esc_html__('%s API key is not configured. Please add your API key in the settings before submitting content.', 'mxchat'),
2577 + $provider_name
2578 + );
2579 + //error_log('[MXCHAT-URL] Error: ' . $error_message);
2580 + set_transient('mxchat_admin_notice_error', $error_message, 30);
2581 + //error_log('[MXCHAT-URL] Set error transient: ' . $error_message);
2582 + wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2583 + exit;
2584 + }
2585 +
2586 + //error_log('[MXCHAT-URL] Fetching URL content');
1733 2587 $response = wp_remote_get($submitted_url, array('timeout' => 30));
1734 2588
1735 2589 if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) {
1736 - $error_message = is_wp_error($response) ? $response->get_error_message() : __('Failed to fetch URL', 'mxchat');
2590 + $error_message = is_wp_error($response) ? $response->get_error_message() : 'HTTP Status: ' . wp_remote_retrieve_response_code($response);
2591 + //error_log('[MXCHAT-URL] Error fetching URL: ' . $error_message);
1737 2592 set_transient('mxchat_admin_notice_error',
1738 2593 sprintf(
1739 2594 esc_html__('Failed to fetch the URL: %s', 'mxchat'),
1740 2595 esc_html($error_message)
@@ -1745,11 +2600,13 @@
1745 2600 exit;
1746 2601 }
1747 2602
1748 2603 $content_type = wp_remote_retrieve_header($response, 'content-type');
2604 + //error_log('[MXCHAT-URL] Content type: ' . $content_type);
1749 2605 $body_content = wp_remote_retrieve_body($response);
1750 2606
1751 2607 if (empty($body_content)) {
2608 + //error_log('[MXCHAT-URL] Error: Empty response body');
1752 2609 set_transient('mxchat_admin_notice_error',
1753 2610 esc_html__('Empty response received from URL.', 'mxchat'),
1754 2611 30
1755 2612 );
@@ -1755,12 +2612,15 @@
1755 2612 );
1756 2613 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
1757 2614 exit;
1758 2615 }
2616 + //error_log('[MXCHAT-URL] Retrieved body content length: ' . strlen($body_content) . ' bytes');
1759 2617
1760 2618 // Handle PDF URL
1761 2619 if ($this->is_pdf_url($submitted_url, $response)) {
2620 + //error_log('[MXCHAT-URL] Detected PDF URL, handling PDF for knowledge base');
1762 2621 $result = $this->handle_pdf_for_knowledge_base($submitted_url, $response);
2622 + //error_log('[MXCHAT-URL] PDF handling result: ' . $result);
1763 2623
1764 2624 if ($result === 'scheduled') {
1765 2625 set_transient(
1766 2626 'mxchat_last_pdf_url',
@@ -1772,9 +2632,9 @@
1772 2632 30
1773 2633 );
1774 2634 } else {
1775 2635 set_transient('mxchat_admin_notice_error',
1776 - esc_html__('Failed to start PDF processing. Please try again.', 'mxchat'),
2636 + esc_html__('Failed to start PDF processing: ', 'mxchat') . esc_html($result),
1777 2637 30
1778 2638 );
1779 2639 }
1780 2640
@@ -1783,8 +2643,9 @@
1783 2643 }
1784 2644
1785 2645 // Handle Sitemap XML
1786 2646 if (strpos($content_type, 'xml') !== false || strpos($body_content, '<urlset') !== false) {
2647 + //error_log('[MXCHAT-URL] Detected XML content, processing as sitemap');
1787 2648 libxml_use_internal_errors(true);
1788 2649 $xml = simplexml_load_string($body_content);
1789 2650 $xml_errors = libxml_get_errors();
1790 2651 libxml_clear_errors();
@@ -1789,8 +2650,15 @@
1789 2650 $xml_errors = libxml_get_errors();
1790 2651 libxml_clear_errors();
1791 2652
1792 2653 if ($xml === false || !empty($xml_errors)) {
2654 + //error_log('[MXCHAT-URL] Error: Invalid XML format');
2655 + if (!empty($xml_errors)) {
2656 + foreach ($xml_errors as $error) {
2657 + //error_log('[MXCHAT-URL] XML Error: ' . $error->message);
2658 + }
2659 + }
2660 +
1793 2661 set_transient('mxchat_admin_notice_error',
1794 2662 esc_html__('Invalid sitemap XML. Please provide a valid sitemap.', 'mxchat'),
1795 2663 30
1796 2664 );
@@ -1797,9 +2665,11 @@
1797 2665 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
1798 2666 exit;
1799 2667 }
1800 2668
2669 + //error_log('[MXCHAT-URL] Valid XML found, handling sitemap for knowledge base');
1801 2670 $result = $this->handle_sitemap_for_knowledge_base($xml, $submitted_url);
2671 + //error_log('[MXCHAT-URL] Sitemap handling result: ' . $result);
1802 2672
1803 2673 if ($result === 'scheduled') {
1804 2674 set_transient(
1805 2675 'mxchat_last_sitemap_url',
@@ -1810,10 +2680,12 @@
1810 2680 esc_html__('Sitemap processing has started in the background. You can check the progress in the Knowledge Base section.', 'mxchat'),
1811 2681 30
1812 2682 );
1813 2683 } else {
2684 + // Return to the admin page without a redirect for better error display
2685 + // The error is already stored in the sitemap status transient
1814 2686 set_transient('mxchat_admin_notice_error',
1815 - esc_html__('Failed to start sitemap processing. Please try again.', 'mxchat'),
2687 + esc_html__('Failed to start sitemap processing. Please check the status below for details.', 'mxchat'),
1816 2688 30
1817 2689 );
1818 2690 }
1819 2691
@@ -1821,42 +2693,147 @@
1821 2693 exit;
1822 2694 }
1823 2695
1824 2696 // Handle Regular URL
1825 - $page_content = $this->mxchat_extract_main_content($body_content);
1826 - $sanitized_content = $this->mxchat_sanitize_content_for_api($page_content);
2697 + // Handle Regular URL
2698 +//error_log('[MXCHAT-URL] Processing as regular webpage');
2699 +$page_content = $this->mxchat_extract_main_content($body_content);
2700 +//error_log('[MXCHAT-URL] Extracted content length: ' . strlen($page_content) . ' bytes');
1827 2701
1828 - if (empty($sanitized_content)) {
1829 - set_transient('mxchat_admin_notice_error',
1830 - esc_html__('No valid content found on the provided URL.', 'mxchat'),
1831 - 30
1832 - );
2702 +$sanitized_content = $this->mxchat_sanitize_content_for_api($page_content);
2703 +//error_log('[MXCHAT-URL] Sanitized content length: ' . strlen($sanitized_content) . ' bytes');
2704 +
2705 +if (empty($sanitized_content)) {
2706 + //error_log('[MXCHAT-URL] Error: No valid content after sanitization');
2707 +
2708 + // Set both transients - the error notice and the URL status
2709 + set_transient('mxchat_admin_notice_error',
2710 + esc_html__('No valid content found on the provided URL.', 'mxchat'),
2711 + 30
2712 + );
2713 +
2714 + // Set URL status transient
2715 + set_transient('mxchat_single_url_status', [
2716 + 'url' => $submitted_url,
2717 + 'timestamp' => current_time('mysql'),
2718 + 'status' => 'failed',
2719 + 'error' => esc_html__('No valid content found on the provided URL.', 'mxchat')
2720 + ], DAY_IN_SECONDS);
2721 +
2722 + wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2723 + exit;
2724 +}
2725 +
2726 +//error_log('[MXCHAT-URL] Generating embedding for content');
2727 +$embedding_vector = $this->mxchat_generate_embedding($sanitized_content);
2728 +
2729 +// Check if embedding_vector is a string (error message)
2730 +if (is_string($embedding_vector)) {
2731 + //error_log('[MXCHAT-URL] Error generating embedding: ' . $embedding_vector);
2732 + $error_message = esc_html__('Failed to generate embedding: ', 'mxchat') . esc_html($embedding_vector);
2733 + //error_log('[MXCHAT-URL] Setting error transient: ' . $error_message);
2734 +
2735 + // Set both transients
2736 + set_transient('mxchat_admin_notice_error', $error_message, 30);
2737 +
2738 + // Set URL status transient
2739 + set_transient('mxchat_single_url_status', [
2740 + 'url' => $submitted_url,
2741 + 'timestamp' => current_time('mysql'),
2742 + 'status' => 'failed',
2743 + 'error' => $error_message
2744 + ], DAY_IN_SECONDS);
2745 +
2746 + wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2747 + exit;
2748 +}
2749 +
2750 +if (is_array($embedding_vector)) {
2751 + //error_log('[MXCHAT-URL] Successfully generated embedding with ' . count($embedding_vector) . ' dimensions');
2752 +
2753 + $db_result = MxChat_Utils::submit_content_to_db(
2754 + $sanitized_content,
2755 + $submitted_url,
2756 + $this->options['api_key']
2757 + );
2758 +
2759 + if (is_wp_error($db_result)) {
2760 + //error_log('[MXCHAT-URL] Error: Failed to store content in database: ' . $db_result->get_error_message());
2761 + $error_message = esc_html__('Failed to store content in database: ', 'mxchat') . esc_html($db_result->get_error_message());
2762 + //error_log('[MXCHAT-URL] Setting error transient: ' . $error_message);
2763 +
2764 + // Set both transients
2765 + set_transient('mxchat_admin_notice_error', $error_message, 30);
2766 +
2767 + // Set URL status transient
2768 + set_transient('mxchat_single_url_status', [
2769 + 'url' => $submitted_url,
2770 + 'timestamp' => current_time('mysql'),
2771 + 'status' => 'failed',
2772 + 'error' => $error_message
2773 + ], DAY_IN_SECONDS);
2774 +
1833 2775 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
1834 2776 exit;
1835 2777 }
1836 2778
1837 - $embedding_vector = $this->mxchat_generate_embedding($sanitized_content);
1838 - if (is_array($embedding_vector)) {
1839 - MxChat_Utils::submit_content_to_db(
1840 - $sanitized_content,
1841 - $submitted_url,
1842 - $this->options['api_key']
1843 - );
1844 - set_transient('mxchat_admin_notice_success',
1845 - esc_html__('URL content successfully submitted!', 'mxchat'),
1846 - 30
1847 - );
1848 - } else {
1849 - set_transient('mxchat_admin_notice_error',
1850 - esc_html__('Failed to generate embedding for the URL content. Please check your API key and try again.', 'mxchat'),
1851 - 30
1852 - );
2779 + //error_log('[MXCHAT-URL] Successfully stored content in database');
2780 + $success_message = esc_html__('URL content successfully submitted!', 'mxchat');
2781 + //error_log('[MXCHAT-URL] Setting success transient: ' . $success_message);
2782 +
2783 + // Set both transients
2784 + set_transient('mxchat_admin_notice_success', $success_message, 30);
2785 +
2786 + // Set URL status transient with success
2787 + set_transient('mxchat_single_url_status', [
2788 + 'url' => $submitted_url,
2789 + 'timestamp' => current_time('mysql'),
2790 + 'status' => 'complete',
2791 + 'content_length' => strlen($sanitized_content),
2792 + 'embedding_dimensions' => count($embedding_vector)
2793 + ], DAY_IN_SECONDS);
2794 +
2795 +} else {
2796 + //error_log('[MXCHAT-URL] Error: Failed to generate embedding. Unexpected result type: ' . gettype($embedding_vector));
2797 + $error_message = esc_html__('Failed to generate embedding: Unexpected result type. Please check your API key and try again.', 'mxchat');
2798 + //error_log('[MXCHAT-URL] Setting error transient: ' . $error_message);
2799 +
2800 + // Set both transients
2801 + set_transient('mxchat_admin_notice_error', $error_message, 30);
2802 +
2803 + // Set URL status transient
2804 + set_transient('mxchat_single_url_status', [
2805 + 'url' => $submitted_url,
2806 + 'timestamp' => current_time('mysql'),
2807 + 'status' => 'failed',
2808 + 'error' => $error_message
2809 + ], DAY_IN_SECONDS);
2810 +}
2811 +
2812 +//error_log('[MXCHAT-URL] ===== Completed URL submission process =====');
2813 +wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2814 +exit;
2815 +}
2816 +/**
2817 + * Get the status of the last single URL submission
2818 + */
2819 +private function get_single_url_status() {
2820 + $status = get_transient('mxchat_single_url_status');
2821 + if (!$status) {
2822 + return null;
1853 2823 }
2824 +
2825 + // Add human-readable time
2826 + if (isset($status['timestamp'])) {
2827 + $status['human_time'] = human_time_diff(strtotime($status['timestamp']), current_time('timestamp')) . ' ' . __('ago', 'mxchat');
2828 + }
2829 +
2830 + return $status;
2831 +}
1854 2832
1855 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
1856 - exit;
1857 -}
1858 2833 private function handle_sitemap_for_knowledge_base($xml, $sitemap_url) {
2834 + // Clear any single URL status when starting sitemap processing
2835 + delete_transient('mxchat_single_url_status');
1859 2836 if (!current_user_can('manage_options')) {
1860 2837 //error_log(esc_html__('Unauthorized sitemap processing attempt', 'mxchat'));
1861 2838 return false;
1862 2839 }
@@ -1867,8 +2844,40 @@
1867 2844 if (!$xml || !is_object($xml)) {
1868 2845 throw new Exception(__('Invalid XML object provided', 'mxchat'));
1869 2846 }
1870 2847
2848 + // Add embedding validation before processing
2849 + // Test embedding with a small sample text to verify API key is working
2850 + $test_result = $this->mxchat_generate_embedding("This is a test to verify the embedding API key is working.");
2851 +
2852 + // Check if test_result is a string (error message) rather than an array (valid embedding)
2853 + if (is_string($test_result)) {
2854 + //error_log('[MXCHAT-URL] Embedding API validation failed: ' . $test_result);
2855 +
2856 + // Store the error in the status transient so it can be displayed later
2857 + $status_data = array(
2858 + 'total_urls' => 0,
2859 + 'processed_urls' => 0,
2860 + 'status' => 'error',
2861 + 'error' => __('Embedding API validation failed: ', 'mxchat') . $test_result,
2862 + 'last_update' => time()
2863 + );
2864 +
2865 + set_transient(
2866 + sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url)),
2867 + array_map('sanitize_text_field', $status_data),
2868 + DAY_IN_SECONDS
2869 + );
2870 +
2871 + throw new Exception(__('Embedding API validation failed: ', 'mxchat') . $test_result);
2872 + }
2873 +
2874 + // Make sure it's an array (valid embedding)
2875 + if (!is_array($test_result)) {
2876 + //error_log('[MXCHAT-URL] Embedding API returned unexpected result type: ' . gettype($test_result));
2877 + throw new Exception(__('Embedding API returned unexpected result type. Please check your configuration.', 'mxchat'));
2878 + }
2879 +
1871 2880 $urls = [];
1872 2881 foreach ($xml->url as $url_element) {
1873 2882 $url = esc_url_raw((string)$url_element->loc);
1874 2883 if ($url) {
@@ -1905,12 +2914,36 @@
1905 2914
1906 2915 return __('scheduled', 'mxchat');
1907 2916
1908 2917 } catch (\Exception $e) {
1909 - //error_log(sprintf(esc_html__('Error preparing sitemap for processing: %s', 'mxchat'), esc_html($e->getMessage())));
1910 - return false;
2918 + $error_message = $e->getMessage();
2919 + //error_log(sprintf(esc_html__('Error preparing sitemap for processing: %s', 'mxchat'), esc_html($error_message)));
2920 +
2921 + // Store the sitemap URL and error in transients so they can be displayed
2922 + set_transient(
2923 + 'mxchat_last_sitemap_url',
2924 + sanitize_text_field($sitemap_url),
2925 + DAY_IN_SECONDS
2926 + );
2927 +
2928 + $status_data = array(
2929 + 'total_urls' => 0,
2930 + 'processed_urls' => 0,
2931 + 'status' => 'error',
2932 + 'error' => $error_message,
2933 + 'last_update' => time()
2934 + );
2935 +
2936 + set_transient(
2937 + sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url)),
2938 + array_map('sanitize_text_field', $status_data),
2939 + DAY_IN_SECONDS
2940 + );
2941 +
2942 + return $error_message;
1911 2943 }
1912 2944 }
2945 +
1913 2946 public static function process_sitemap_urls_cron($urls, $sitemap_url, $total_urls, $batch_size, $batch_pause) {
1914 2947 // Validate inputs
1915 2948 $sitemap_url = esc_url_raw($sitemap_url);
1916 2949 $total_urls = absint($total_urls);
@@ -1928,8 +2961,13 @@
1928 2961 if (!$status || !is_array($status)) {
1929 2962 throw new Exception('Invalid status data retrieved from transient');
1930 2963 }
1931 2964
2965 + // Initialize failed_urls array if it doesn't exist
2966 + if (!isset($status['failed_urls_list']) || !is_array($status['failed_urls_list'])) {
2967 + $status['failed_urls_list'] = [];
2968 + }
2969 +
1932 2970 $start_url = absint($status['processed_urls']);
1933 2971 $end_url = min($start_url + $batch_size, $total_urls);
1934 2972 $instance = new self();
1935 2973
@@ -1936,11 +2974,38 @@
1936 2974 // Track failures in batch
1937 2975 $batch_stats = [
1938 2976 'processed' => 0,
1939 2977 'failed' => 0,
1940 - 'last_error' => ''
2978 + 'last_error' => '',
2979 + 'embedding_errors' => 0 // Track specifically embedding errors
1941 2980 ];
1942 2981
2982 + // Check embedding configuration with first URL
2983 + if ($start_url === 0) {
2984 + $page_url = esc_url_raw($urls[0]);
2985 + $page_response = wp_remote_get($page_url);
2986 +
2987 + if (!is_wp_error($page_response) && wp_remote_retrieve_response_code($page_response) === 200) {
2988 + $page_html = wp_remote_retrieve_body($page_response);
2989 + $page_content = $instance->mxchat_extract_main_content($page_html);
2990 + $sanitized_content = $instance->mxchat_sanitize_content_for_api($page_content);
2991 +
2992 + if (!empty($sanitized_content)) {
2993 + $embedding_vector = $instance->mxchat_generate_embedding($sanitized_content);
2994 +
2995 + // Check if embedding_vector is a string (error message)
2996 + if (is_string($embedding_vector)) {
2997 + throw new Exception('Embedding generation failed: ' . $embedding_vector);
2998 + }
2999 +
3000 + // Make sure it's an array (valid embedding)
3001 + if (!is_array($embedding_vector)) {
3002 + throw new Exception('Embedding generation returned unexpected result type: ' . gettype($embedding_vector));
3003 + }
3004 + }
3005 + }
3006 + }
3007 +
1943 3008 for ($i = $start_url; $i < $end_url; $i++) {
1944 3009 $page_url = esc_url_raw($urls[$i]);
1945 3010 $page_response = wp_remote_get($page_url);
1946 3011
@@ -1945,8 +3010,18 @@
1945 3010 $page_response = wp_remote_get($page_url);
1946 3011
1947 3012 if (is_wp_error($page_response) || wp_remote_retrieve_response_code($page_response) !== 200) {
1948 3013 $batch_stats['failed']++;
3014 + $error_message = is_wp_error($page_response) ? $page_response->get_error_message() : 'HTTP Status: ' . wp_remote_retrieve_response_code($page_response);
3015 + $batch_stats['last_error'] = 'Failed to fetch URL: ' . $error_message;
3016 +
3017 + // Add to failed URLs list with error message
3018 + $status['failed_urls_list'][] = [
3019 + 'url' => $page_url,
3020 + 'error' => $error_message,
3021 + 'time' => time()
3022 + ];
3023 +
1949 3024 continue;
1950 3025 }
1951 3026
1952 3027 $page_html = wp_remote_retrieve_body($page_response);
@@ -1955,8 +3030,29 @@
1955 3030
1956 3031 if (!empty($sanitized_content)) {
1957 3032 $embedding_vector = $instance->mxchat_generate_embedding($sanitized_content);
1958 3033
3034 + // Check if embedding_vector is a string (error message)
3035 + if (is_string($embedding_vector)) {
3036 + $batch_stats['failed']++;
3037 + $batch_stats['embedding_errors']++;
3038 + $batch_stats['last_error'] = 'Failed to generate embedding: ' . $embedding_vector;
3039 +
3040 + // Add to failed URLs list with error message
3041 + $status['failed_urls_list'][] = [
3042 + 'url' => $page_url,
3043 + 'error' => 'Embedding error: ' . $embedding_vector,
3044 + 'time' => time()
3045 + ];
3046 +
3047 + // If we have multiple embedding errors, stop processing
3048 + if ($batch_stats['embedding_errors'] >= 10) {
3049 + throw new Exception('Multiple embedding failures detected: ' . $embedding_vector);
3050 + }
3051 + continue;
3052 + }
3053 +
3054 + // Check if it's an array (valid embedding)
1959 3055 if (is_array($embedding_vector)) {
1960 3056 $options = get_option('mxchat_options');
1961 3057 $submission_result = MxChat_Utils::submit_content_to_db($sanitized_content, $page_url, $options['api_key']);
1962 3058
@@ -1962,8 +3058,16 @@
1962 3058
1963 3059 if (is_wp_error($submission_result)) {
1964 3060 $batch_stats['failed']++;
1965 3061 $batch_stats['last_error'] = $submission_result->get_error_message();
3062 +
3063 + // Add to failed URLs list with error message
3064 + $status['failed_urls_list'][] = [
3065 + 'url' => $page_url,
3066 + 'error' => 'Database submission error: ' . $submission_result->get_error_message(),
3067 + 'time' => time()
3068 + ];
3069 +
1966 3070 continue;
1967 3071 }
1968 3072
1969 3073 $batch_stats['processed']++;
@@ -1968,9 +3072,22 @@
1968 3072
1969 3073 $batch_stats['processed']++;
1970 3074 } else {
1971 3075 $batch_stats['failed']++;
1972 - $batch_stats['last_error'] = 'Failed to generate embedding. Please check OpenAI API key.';
3076 + $batch_stats['embedding_errors']++;
3077 + $batch_stats['last_error'] = 'Failed to generate embedding: Unexpected result type: ' . gettype($embedding_vector);
3078 +
3079 + // Add to failed URLs list with error message
3080 + $status['failed_urls_list'][] = [
3081 + 'url' => $page_url,
3082 + 'error' => 'Embedding error: Unexpected result type: ' . gettype($embedding_vector),
3083 + 'time' => time()
3084 + ];
3085 +
3086 + // If we have multiple embedding errors, stop processing
3087 + if ($batch_stats['embedding_errors'] >= 10) {
3088 + throw new Exception('Multiple embedding failures detected. Please check your embedding API configuration.');
3089 + }
1973 3090 }
1974 3091 }
1975 3092
1976 3093 $status['processed_urls'] = absint($i + 1);
@@ -1977,9 +3094,14 @@
1977 3094 $status['last_update'] = time();
1978 3095 $status['failed_urls'] = absint($status['failed_urls'] ?? 0) + $batch_stats['failed'];
1979 3096 $status['last_error'] = $batch_stats['last_error'];
1980 3097
1981 - set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
3098 + // Limit the number of failed URLs we store to prevent transient size issues
3099 + if (count($status['failed_urls_list']) > 100) {
3100 + $status['failed_urls_list'] = array_slice($status['failed_urls_list'], -100);
3101 + }
3102 +
3103 + set_transient($status_key, $status, DAY_IN_SECONDS);
1982 3104 }
1983 3105
1984 3106 // If all URLs in this batch failed, stop processing
1985 3107 if ($batch_stats['processed'] === 0 && $batch_stats['failed'] > 0) {
@@ -1988,15 +3110,15 @@
1988 3110 'Processing stopped: %d consecutive failures. Last error: %s',
1989 3111 $batch_stats['failed'],
1990 3112 $batch_stats['last_error']
1991 3113 );
1992 - set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
3114 + set_transient($status_key, $status, DAY_IN_SECONDS);
1993 3115 return;
1994 3116 }
1995 3117
1996 3118 if ($end_url < $total_urls) {
1997 3119 wp_schedule_single_event(time() + $batch_pause, 'mxchat_process_sitemap_urls', array(
1998 - 'urls' => array_map('esc_url_raw', $urls),
3120 + 'urls' => $urls,
1999 3121 'sitemap_url' => $sitemap_url,
2000 3122 'total_urls' => $total_urls,
2001 3123 'batch_size' => $batch_size,
2002 3124 'batch_pause' => $batch_pause,
@@ -2002,17 +3124,249 @@
2002 3124 'batch_pause' => $batch_pause,
2003 3125 ));
2004 3126 } else {
2005 3127 $status['status'] = 'complete';
2006 - set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
3128 + set_transient($status_key, $status, DAY_IN_SECONDS);
2007 3129 }
2008 3130 } catch (\Exception $e) {
2009 3131 $status['status'] = 'error';
2010 - $status['error'] = sanitize_text_field($e->getMessage());
2011 - set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
3132 + $status['error'] = $e->getMessage();
3133 + set_transient($status_key, $status, DAY_IN_SECONDS);
2012 3134 }
2013 3135 }
2014 3136
3137 +private function mxchat_sanitize_content_for_api($content) {
3138 + //error_log('[MXCHAT-SANITIZE] Original content preview: ' . substr($content, 0, 500) . '...');
3139 +
3140 + // Remove script, style tags, and HTML comments
3141 + $content = preg_replace('/<script\b[^>]*>(.*?)<\/script>/is', '', $content);
3142 + $content = preg_replace('/<style\b[^>]*>(.*?)<\/style>/is', '', $content);
3143 + $content = preg_replace('/<!--(.|\s)*?-->/', '', $content);
3144 +
3145 + // Remove all HTML tags and decode HTML entities
3146 + $content = wp_strip_all_tags($content);
3147 + $content = html_entity_decode($content, ENT_QUOTES | ENT_HTML5);
3148 +
3149 + // Trim and normalize whitespace
3150 + $content = trim(preg_replace('/\s+/', ' ', $content));
3151 +
3152 + // Remove control characters (which can cause database issues)
3153 + $content = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/u', '', $content);
3154 +
3155 + // Remove NULL bytes which can cause database errors
3156 + $content = str_replace("\0", "", $content);
3157 +
3158 + // Ensure valid UTF-8 encoding
3159 + $content = wp_check_invalid_utf8($content);
3160 +
3161 + // Remove any extremely long strings without spaces (often garbage)
3162 + $content = preg_replace('/\S{300,}/', ' ', $content);
3163 +
3164 + // Replace problematic characters that often cause database issues
3165 + $content = preg_replace('/[\x{10000}-\x{10FFFF}]/u', '', $content); // Remove emoji and other high Unicode characters
3166 +
3167 + // Replace any remaining potentially problematic characters with spaces
3168 + $content = preg_replace('/[^\p{L}\p{N}\p{P}\p{Z}\p{Sm}]/u', ' ', $content);
3169 +
3170 + // Limit to reasonable length if needed
3171 + $max_length = 65000; // Just under MySQL TEXT field limit
3172 + if (strlen($content) > $max_length) {
3173 + $content = substr($content, 0, $max_length);
3174 + }
3175 +
3176 + //error_log('[MXCHAT-SANITIZE] Sanitized content preview: ' . substr($content, 0, 500) . '...');
3177 + return $content;
3178 +}
3179 +private static function mxchat_extract_main_content($html) {
3180 + if (empty($html)) {
3181 + return '';
3182 + }
3183 + try {
3184 + $dom = new DOMDocument;
3185 + libxml_use_internal_errors(true); // Suppress HTML parsing errors
3186 + @$dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
3187 + $xpath = new DOMXPath($dom);
3188 +
3189 + // For debugging purposes
3190 + $debugEnabled = false; // Set to true to enable debugging output
3191 + $debug = function($message) use ($debugEnabled) {
3192 + if ($debugEnabled) {
3193 + //error_log('[MXCHAT-DEBUG] ' . $message);
3194 + }
3195 + };
3196 +
3197 + // Direct targeting for Gerow theme posts
3198 + $post_text = $xpath->query('//div[contains(@class, "post-text")]');
3199 + if ($post_text && $post_text->length > 0) {
3200 + $debug("Found post-text directly");
3201 + $content = '';
3202 + foreach ($post_text as $node) {
3203 + $content .= $dom->saveHTML($node);
3204 + }
3205 + if (!empty($content)) {
3206 + $debug("Returning post-text content");
3207 + return $content;
3208 + }
3209 + }
3210 +
3211 + // Try to get the blog details content which contains the post-text
3212 + $blog_details = $xpath->query('//div[contains(@class, "blog-details-content")]');
3213 + if ($blog_details && $blog_details->length > 0) {
3214 + $debug("Found blog-details-content");
3215 + $content = '';
3216 + foreach ($blog_details as $node) {
3217 + $content .= $dom->saveHTML($node);
3218 + }
3219 + if (!empty($content)) {
3220 + $debug("Returning blog-details-content");
3221 + return $content;
3222 + }
3223 + }
3224 +
3225 + // Try to get the article which contains the blog details
3226 + $article = $xpath->query('//article[contains(@class, "blog-details-wrap")]');
3227 + if ($article && $article->length > 0) {
3228 + $debug("Found article with blog-details-wrap");
3229 + $content = '';
3230 + foreach ($article as $node) {
3231 + $content .= $dom->saveHTML($node);
3232 + }
3233 + if (!empty($content)) {
3234 + $debug("Returning article content");
3235 + return $content;
3236 + }
3237 + }
3238 +
3239 + // Try even broader with the blog-item-wrap
3240 + $blog_item = $xpath->query('//div[contains(@class, "blog-item-wrap")]');
3241 + if ($blog_item && $blog_item->length > 0) {
3242 + $debug("Found blog-item-wrap");
3243 + $content = '';
3244 + foreach ($blog_item as $node) {
3245 + $content .= $dom->saveHTML($node);
3246 + }
3247 + if (!empty($content)) {
3248 + $debug("Returning blog-item-wrap content");
3249 + return $content;
3250 + }
3251 + }
3252 +
3253 + // Specific Gerow theme path
3254 + $gerow_path = $xpath->query('//section[contains(@class, "blog-area")]//div[contains(@class, "post-text")]');
3255 + if ($gerow_path && $gerow_path->length > 0) {
3256 + $debug("Found Gerow theme path to post-text");
3257 + $content = '';
3258 + foreach ($gerow_path as $node) {
3259 + $content .= $dom->saveHTML($node);
3260 + }
3261 + if (!empty($content)) {
3262 + $debug("Returning Gerow post-text content");
3263 + return $content;
3264 + }
3265 + }
3266 +
3267 + // Generic blog post selectors
3268 + $selectors = [
3269 + // Blog post specific selectors
3270 + '//div[contains(@class, "post-text")]',
3271 + '//article[contains(@class, "blog-post-item")]//div[contains(@class, "post-text")]',
3272 + '//div[contains(@class, "blog-details-content")]',
3273 + '//article[contains(@class, "blog-details-wrap")]',
3274 + '//div[contains(@class, "entry-content")]',
3275 + '//div[contains(@class, "blog-content")]',
3276 + '//div[contains(@class, "blog-item-wrap")]',
3277 +
3278 + // More general content selectors
3279 + '//div[contains(@class, "page__content")]',
3280 + '//div[contains(@class, "elementor-widget-container")]',
3281 + '//div[contains(@class, "elementor-text-editor")]',
3282 + '//div[contains(@class, "elementor-widget-text-editor")]',
3283 + '//*[contains(@class, "entry-content")]',
3284 + '//*[contains(@class, "post-content")]',
3285 + '//*[contains(@class, "article-content")]',
3286 + '//*[@id="content"]',
3287 + '//*[@id="main-content"]',
3288 + '//section[contains(@class, "blog-area")]',
3289 + '//article',
3290 + '//main',
3291 + '//div[contains(@class, "content")]'
3292 + ];
3293 +
3294 + // First handle Elementor content
3295 + $debug("Checking for Elementor content");
3296 + $elementor_widgets = $xpath->query('//div[contains(@class, "elementor-element")]//div[contains(@class, "elementor-widget-container")]');
3297 + if ($elementor_widgets && $elementor_widgets->length > 0) {
3298 + $debug("Found Elementor widgets");
3299 + $combined_content = '';
3300 + foreach ($elementor_widgets as $widget) {
3301 + $widget_content = $dom->saveHTML($widget);
3302 + if (!empty($widget_content)) {
3303 + $combined_content .= $widget_content;
3304 + }
3305 + }
3306 + if (!empty($combined_content)) {
3307 + $debug("Returning Elementor content");
3308 + return $combined_content;
3309 + }
3310 + }
3311 +
3312 + // Try standard selectors one by one
3313 + foreach ($selectors as $selector) {
3314 + $debug("Trying selector: " . $selector);
3315 + $nodes = $xpath->query($selector);
3316 + if ($nodes && $nodes->length > 0) {
3317 + $debug("Found matches for selector: " . $selector);
3318 + $content = '';
3319 + foreach ($nodes as $node) {
3320 + $content .= $dom->saveHTML($node);
3321 + }
3322 + if (!empty($content)) {
3323 + $debug("Returning content from selector: " . $selector);
3324 + return $content;
3325 + }
3326 + }
3327 + }
3328 +
3329 + // Manual regex fallback for post-text if DOM methods fail
3330 + $debug("Trying regex fallback");
3331 + if (preg_match('/<div class="post-text">(.*?)<\/div>\s*<\/div>\s*<\/div>/s', $html, $matches)) {
3332 + $debug("Found post-text via regex");
3333 + return '<div class="post-text">' . $matches[1] . '</div>';
3334 + }
3335 +
3336 + // Try to extract the blog section as a whole
3337 + $blog_section = $xpath->query('//section[contains(@class, "blog-area")]');
3338 + if ($blog_section && $blog_section->length > 0) {
3339 + $debug("Found blog-area section");
3340 + $content = '';
3341 + foreach ($blog_section as $node) {
3342 + $content .= $dom->saveHTML($node);
3343 + }
3344 + if (!empty($content)) {
3345 + $debug("Returning blog-area section content");
3346 + return $content;
3347 + }
3348 + }
3349 +
3350 + // Fallback: Return the body content if no specific selector matches
3351 + $debug("Using body fallback");
3352 + $body = $dom->getElementsByTagName('body');
3353 + if ($body->length > 0) {
3354 + return $dom->saveHTML($body->item(0));
3355 + }
3356 +
3357 + // Last resort: return the original HTML
3358 + $debug("Returning original HTML");
3359 + return $html;
3360 + } catch (Exception $e) {
3361 + //error_log('[MXCHAT-ERROR] Content extraction failed: ' . $e->getMessage());
3362 + return $html; // Return original HTML if parsing fails
3363 + } finally {
3364 + libxml_clear_errors();
3365 + }
3366 +}
3367 +
3368 +
2015 3369 public function get_sitemap_processing_status($sitemap_url) {
2016 3370 $sitemap_url = esc_url_raw($sitemap_url);
2017 3371 $status = get_transient(sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url)));
2018 3372
@@ -2024,17 +3378,53 @@
2024 3378 'total_urls' => absint($status['total_urls']),
2025 3379 'processed_urls' => absint($status['processed_urls']),
2026 3380 'failed_urls' => absint($status['failed_urls'] ?? 0),
2027 3381 'percentage' => ($status['total_urls'] > 0)
2028 - ? round((absint($status['processed_urls']) / absint($status['total_urls'])) * 100)
2029 - : 0,
3382 + ? round((absint($status['processed_urls']) / absint($status['total_urls'])) * 100)
3383 + : 0,
2030 3384 'status' => sanitize_text_field($status['status']),
2031 3385 'last_update' => human_time_diff(absint($status['last_update']), time()) . ' ' . esc_html__('ago', 'mxchat'),
2032 3386 'error' => isset($status['error']) ? sanitize_text_field($status['error']) : '',
2033 - 'last_error' => isset($status['last_error']) ? sanitize_text_field($status['last_error']) : ''
3387 + 'last_error' => isset($status['last_error']) ? sanitize_text_field($status['last_error']) : '',
3388 + 'failed_urls_list' => isset($status['failed_urls_list']) ? $status['failed_urls_list'] : array()
2034 3389 );
2035 3390 }
3391 +public function ajax_get_status_updates() {
3392 + // Verify the request
3393 + check_ajax_referer('mxchat_status_nonce', 'nonce');
2036 3394
3395 + // Get the status just like in your admin page
3396 + $pdf_url = get_transient('mxchat_last_pdf_url');
3397 + $sitemap_url = get_transient('mxchat_last_sitemap_url');
3398 + $pdf_status = $pdf_url ? $this->get_pdf_processing_status($pdf_url) : false;
3399 + $sitemap_status = $sitemap_url ? $this->get_sitemap_processing_status($sitemap_url) : false;
3400 +
3401 + // Check for true processing status, not just presence of status
3402 + $is_active_processing =
3403 + ($sitemap_status && $sitemap_status['status'] === 'processing') ||
3404 + ($pdf_status && $pdf_status['status'] === 'processing');
3405 +
3406 + // Get single URL status, but only if no sitemap/PDF is processing
3407 + $single_url_status = !$is_active_processing ? $this->get_single_url_status() : false;
3408 +
3409 + // Only clear transients for completed processes, not error states
3410 + if ($pdf_status && $pdf_status['status'] === 'complete') {
3411 + delete_transient('mxchat_last_pdf_url');
3412 + $pdf_status = false;
3413 + }
3414 + if ($sitemap_status && $sitemap_status['status'] === 'complete') {
3415 + delete_transient('mxchat_last_sitemap_url');
3416 + $sitemap_status = false;
3417 + }
3418 +
3419 + // Return JSON response with the status data
3420 + wp_send_json(array(
3421 + 'pdf_status' => $pdf_status,
3422 + 'sitemap_status' => $sitemap_status,
3423 + 'single_url_status' => $single_url_status,
3424 + 'is_processing' => $is_active_processing
3425 + ));
3426 +}
2037 3427 public function mxchat_stop_processing() {
2038 3428 // Verify permissions
2039 3429 if (!current_user_can('manage_options')) {
2040 3430 wp_die(esc_html__('Unauthorized access', 'mxchat'));
@@ -2070,27 +3460,10 @@
2070 3460 );
2071 3461 wp_safe_redirect(admin_url('admin.php?page=mxchat-prompts'));
2072 3462 exit;
2073 3463 }
2074 -private function mxchat_sanitize_content_for_api($content) {
2075 - // Remove script, style tags, and HTML comments
2076 - $content = preg_replace('/<script\b[^>]*>(.*?)<\/script>/is', '', $content);
2077 - $content = preg_replace('/<style\b[^>]*>(.*?)<\/style>/is', '', $content);
2078 - $content = preg_replace('/<!--(.|\s)*?-->/', '', $content);
2079 3464
2080 - // Remove all HTML tags and decode HTML entities
2081 - $content = wp_strip_all_tags($content);
2082 - $content = html_entity_decode($content, ENT_QUOTES | ENT_HTML5);
2083 3465
2084 - // Trim and normalize whitespace
2085 - $content = trim(preg_replace('/\s+/', ' ', $content));
2086 -
2087 - return $content;
2088 -}
2089 -
2090 -
2091 -
2092 -
2093 3466 public function mxchat_handle_product_change($post_id, $post, $update) {
2094 3467 if ($post->post_type !== 'product') {
2095 3468 return;
2096 3469 }
@@ -2103,9 +3476,8 @@
2103 3476 }
2104 3477 });
2105 3478 }
2106 3479 }
2107 -
2108 3480 private function mxchat_store_product_embedding($product) {
2109 3481 if (!isset($this->options['enable_woocommerce_integration']) ||
2110 3482 !in_array($this->options['enable_woocommerce_integration'], ['1', 'on'])) {
2111 3483 return;
@@ -2161,9 +3533,8 @@
2161 3533 // Use WordPress DB storage
2162 3534 $this->store_in_wordpress_db($description, $source_url, $embedding_vector);
2163 3535 }
2164 3536 }
2165 -
2166 3537 public function mxchat_handle_product_delete($post_id) {
2167 3538 if (get_post_type($post_id) !== 'product') {
2168 3539 return;
2169 3540 }
@@ -2290,139 +3661,11 @@
2290 3661 );
2291 3662 }
2292 3663
2293 3664
2294 -public function mxchat_fetch_chat_history() {
2295 - global $wpdb;
2296 - $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
2297 3665
2298 - if (!current_user_can('manage_options')) {
2299 - wp_die(esc_html__('You do not have sufficient permissions to view this page.', 'mxchat'));
2300 - }
2301 3666
2302 - // First get unique session IDs ordered by most recent message in each session
2303 - $session_ids = $wpdb->get_col(
2304 - $wpdb->prepare(
2305 - "SELECT DISTINCT session_id
2306 - FROM {$table_name}
2307 - GROUP BY session_id
2308 - ORDER BY MAX(timestamp) DESC"
2309 - )
2310 - );
2311 3667
2312 - if (empty($session_ids)) {
2313 - wp_die(esc_html__('No chat history available.', 'mxchat'));
2314 - }
2315 -
2316 - ob_start();
2317 - echo '<div class="mxchat-transcript">';
2318 -
2319 - // Iterate through sessions from newest to oldest
2320 - foreach ($session_ids as $session_id) {
2321 - // Get the email associated with this session (if available)
2322 - $email = $wpdb->get_var(
2323 - $wpdb->prepare(
2324 - "SELECT user_email
2325 - FROM {$table_name}
2326 - WHERE session_id = %s AND user_email != ''
2327 - ORDER BY timestamp ASC
2328 - LIMIT 1",
2329 - $session_id
2330 - )
2331 - );
2332 -
2333 - // Debug: Check email retrieval
2334 - if (empty($email)) {
2335 - //error_log("No email found for session {$session_id}");
2336 - } else {
2337 - //error_log("Email for session {$session_id}: " . $email);
2338 - }
2339 -
2340 - // Get messages for this session ordered by timestamp
2341 - $messages = $wpdb->get_results(
2342 - $wpdb->prepare(
2343 - "SELECT * FROM {$table_name}
2344 - WHERE session_id = %s
2345 - ORDER BY timestamp ASC",
2346 - $session_id
2347 - )
2348 - );
2349 -
2350 - // Start session block
2351 - echo '<div class="mxchat-session">';
2352 - echo '<div class="mxchat-session-header">';
2353 - // Wrap checkbox and session ID in one block
2354 - echo '<div class="mxchat-session-id">';
2355 - echo '<input type="checkbox" name="delete_session_ids[]" value="' . esc_attr($session_id) . '"> ';
2356 - echo '<strong>' . esc_html__('Session ID:', 'mxchat') . '</strong> ' . esc_html($session_id);
2357 - echo '</div>';
2358 -
2359 - // Place email directly below the session ID block
2360 - if (!empty($email)) {
2361 - echo '<div class="mxchat-session-email">';
2362 - echo '<strong>' . esc_html__('Email:', 'mxchat') . '</strong> ' . esc_html($email);
2363 - echo '</div>';
2364 - }
2365 - echo '</div>';
2366 -
2367 - echo '<div class="mxchat-messages">';
2368 -
2369 - // Display messages for this session
2370 - foreach ($messages as $transcript) {
2371 - $formatted_timestamp = date_i18n('F j, Y g:i a', strtotime($transcript->timestamp));
2372 -
2373 - // Determine message styling
2374 - switch ($transcript->role) {
2375 - case 'assistant':
2376 - case 'bot':
2377 - $message_class = 'bot-message';
2378 - $display_role = esc_html__('Chatbot', 'mxchat');
2379 - break;
2380 - case 'user':
2381 - $message_class = 'user-message';
2382 - $display_role = !empty($transcript->user_identifier)
2383 - ? sanitize_text_field($transcript->user_identifier)
2384 - : esc_html__('User', 'mxchat');
2385 - break;
2386 - case 'agent':
2387 - $message_class = 'agent-message';
2388 - $display_role = esc_html__('Agent', 'mxchat');
2389 - break;
2390 - default:
2391 - $message_class = 'unknown-message';
2392 - $display_role = esc_html__('Unknown', 'mxchat');
2393 - }
2394 -
2395 - // Process message content
2396 - $message_content = wp_kses(
2397 - stripslashes($transcript->message),
2398 - [
2399 - 'b' => [], 'strong' => [], 'i' => [], 'em' => [], 'u' => [],
2400 - 'br' => [], 'p' => [], 'ul' => [], 'ol' => [], 'li' => [],
2401 - 'a' => ['href' => [], 'title' => []]
2402 - ]
2403 - );
2404 - $message_content = nl2br($message_content);
2405 -
2406 - // Render message
2407 - echo '<div class="mxchat-message ' . esc_attr($message_class) . '">';
2408 - echo '<div class="mxchat-message-header">' . esc_html($display_role) . '</div>';
2409 - echo '<div class="mxchat-message-content">' . $message_content . '</div>';
2410 - echo '<div class="mxchat-timestamp">' . esc_html($formatted_timestamp) . '</div>';
2411 - echo '</div>';
2412 - }
2413 -
2414 - // Close session block
2415 - echo '</div></div>';
2416 - }
2417 -
2418 - echo '</div>';
2419 - $output = ob_get_clean();
2420 - echo $output;
2421 - wp_die();
2422 -}
2423 -
2424 -
2425 3668 public function mxchat_create_activation_page() {
2426 3669 $license_status = get_option('mxchat_license_status', 'inactive');
2427 3670 $license_error = get_option('mxchat_license_error', '');
2428 3671 ?>
@@ -2478,9 +3721,9 @@
2478 3721 </div>
2479 3722 <?php
2480 3723 }
2481 3724
2482 -public function mxchat_intents_page_html() {
3725 +public function mxchat_actions_page_html() {
2483 3726 if (!current_user_can('manage_options')) {
2484 3727 return;
2485 3728 }
2486 3729
@@ -2493,9 +3736,9 @@
2493 3736
2494 3737 // Success message
2495 3738 if (isset($_GET['updated']) && $_GET['updated'] === 'true') {
2496 3739 echo '<div class="notice notice-success is-dismissible"><p>' .
2497 - esc_html__('Intent updated successfully.', 'mxchat') .
3740 + esc_html__('Action updated successfully.', 'mxchat') .
2498 3741 '</p></div>';
2499 3742 }
2500 3743
2501 3744 // Filtering logic
@@ -2516,10 +3759,10 @@
2516 3759 // Pagination
2517 3760 $total_intents = $wpdb->get_var("SELECT COUNT(*) FROM $table_name WHERE $where");
2518 3761 $total_pages = ceil($total_intents / $per_page);
2519 3762
2520 - // Get intents
2521 - $intents = $wpdb->get_results($wpdb->prepare(
3763 + // Get intents (now called actions)
3764 + $actions = $wpdb->get_results($wpdb->prepare(
2522 3765 "SELECT * FROM $table_name WHERE $where LIMIT %d OFFSET %d",
2523 3766 $per_page, $offset
2524 3767 ));
2525 3768
@@ -2524,441 +3767,604 @@
2524 3767 ));
2525 3768
2526 3769 // Get callbacks
2527 3770 $available_callbacks = $this->mxchat_get_available_callbacks();
2528 -
3771 +
2529 3772 ?>
2530 3773 <div class="wrap mxchat-wrapper">
2531 3774 <!-- Hero Section -->
2532 3775 <div class="mxchat-hero">
2533 3776 <h1 class="mxchat-main-title">
2534 - <span class="mxchat-gradient-text">Intent</span> Manager
3777 + <span class="mxchat-gradient-text">Actions</span> Manager
2535 3778 </h1>
2536 3779 <p class="mxchat-hero-subtitle">
2537 - <?php esc_html_e('Create and manage custom intents to enhance your chatbots understanding and response capabilities.', 'mxchat'); ?>
3780 + <?php esc_html_e('Create and manage custom actions to enhance your chatbot\'s capabilities.', 'mxchat'); ?>
2538 3781 </p>
2539 3782 </div>
2540 3783
2541 - <div class="mxchat-content">
2542 - <!-- Add Intent Card -->
2543 - <div class="mxchat-card">
2544 - <div class="mxchat-card-header">
2545 - <h2><?php esc_html_e('Add New Intent', 'mxchat'); ?></h2>
2546 - </div>
2547 -
2548 - <div class="mxchat-intent-documentation">
2549 - <p>
2550 - <?php esc_html_e('We highly encourage users to quickly read our ', 'mxchat'); ?>
2551 - <a href="https://mxchat.ai/documentation/#intents" target="_blank" rel="noopener noreferrer">
2552 - <?php esc_html_e('documentation', 'mxchat'); ?>
2553 - </a>
2554 - <?php esc_html_e(' to better understand intents. Some intents require setup in the Integration tab. If your intent is not triggering lower similarity threshold test.', 'mxchat'); ?>
2555 - </p>
2556 - </div>
2557 -
2558 - <form id="mxchat-add-intent-form" method="post"
2559 - action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
2560 - <input type="hidden" name="action" value="mxchat_add_intent">
2561 - <?php wp_nonce_field('mxchat_add_intent_nonce'); ?>
2562 -
2563 - <div class="mxchat-form-group">
2564 - <label for="intent_label">
2565 - <?php esc_html_e('Intent Label (For your reference only)', 'mxchat'); ?>
2566 - </label>
2567 - <input name="intent_label" type="text" id="intent_label" required
2568 - class="mxchat-intent-input"
2569 - placeholder="<?php esc_attr_e('Example Email Capture: Newsletter Signup', 'mxchat'); ?>">
3784 + <!-- Actions Header with Search and Filter -->
3785 + <div class="mxchat-actions-header">
3786 + <div class="mxchat-actions-filters">
3787 + <form method="get" class="mxchat-search-form">
3788 + <input type="hidden" name="page" value="mxchat-actions">
3789 + <div class="mxchat-search-group">
3790 + <span class="dashicons dashicons-search"></span>
3791 + <input type="text" name="s" class="mxchat-search-input"
3792 + placeholder="<?php esc_attr_e('Search Actions', 'mxchat'); ?>"
3793 + value="<?php echo esc_attr($search_term); ?>">
2570 3794 </div>
2571 -
2572 - <div class="mxchat-form-group">
2573 - <label for="phrases">
2574 - <?php esc_html_e('Phrases (comma-separated)', 'mxchat'); ?>
2575 - </label>
2576 - <textarea name="phrases" id="phrases" rows="5" required
2577 - class="mxchat-intent-textarea"
2578 - placeholder="<?php esc_attr_e('Example Email Capture: sign me up, subscribe me, I want to join, add me to the newsletter, send me updates, keep me informed', 'mxchat'); ?>"></textarea>
2579 - </div>
2580 -
2581 - <div class="mxchat-form-group">
2582 - <label for="callback_function">
2583 - <?php esc_html_e('Callback Function', 'mxchat'); ?>
2584 - </label>
2585 - <select name="callback_function" id="callback_function"
2586 - class="mxchat-intent-select" required>
2587 - <option value="">
2588 - <?php esc_html_e('Select a Callback', 'mxchat'); ?>
3795 + <select name="callback_filter" class="mxchat-action-filter">
3796 + <option value=""><?php esc_html_e('All Action Types', 'mxchat'); ?></option>
3797 + <?php foreach ($available_callbacks as $function => $callback_data) :
3798 + $label = $callback_data['label']; ?>
3799 + <option value="<?php echo esc_attr($function); ?>"
3800 + <?php selected($callback_filter, $function); ?>>
3801 + <?php echo esc_html($label); ?>
2589 3802 </option>
2590 - <?php
2591 - $groups = $this->mxchat_get_available_callbacks(true);
2592 - foreach ($groups as $group_label => $group_callbacks) :
2593 - echo '<optgroup label="' . esc_attr($group_label) . '">';
2594 - foreach ($group_callbacks as $function => $data) :
2595 - $label = $data['label'];
2596 - $pro_only = $data['pro_only'];
2597 - $disabled = (!$this->is_activated && $pro_only) ? 'disabled' : '';
2598 - $label_suffix = (!$this->is_activated && $pro_only) ? ' (Pro Only)' : '';
2599 - ?>
2600 - <option value="<?php echo esc_attr($function); ?>"
2601 - <?php echo $disabled; ?>>
2602 - <?php echo esc_html($label . $label_suffix); ?>
2603 - </option>
2604 - <?php
2605 - endforeach;
2606 - echo '</optgroup>';
2607 - endforeach;
2608 - ?>
2609 - </select>
2610 - </div>
2611 -
2612 - <button type="submit" class="mxchat-button-primary">
2613 - <?php esc_html_e('Add Intent', 'mxchat'); ?>
3803 + <?php endforeach; ?>
3804 + </select>
3805 + <button type="submit" class="mxchat-button-secondary">
3806 + <?php esc_html_e('Filter', 'mxchat'); ?>
2614 3807 </button>
2615 3808 </form>
2616 3809 </div>
3810 + <div class="mxchat-actions-controls">
3811 + <button type="button" id="mxchat-add-action-btn" class="mxchat-button-primary">
3812 + <span class="dashicons dashicons-plus-alt"></span>
3813 + <?php esc_html_e('Add New Action', 'mxchat'); ?>
3814 + </button>
3815 + </div>
3816 + </div>
2617 3817
2618 - <!-- Manage Intents Card -->
2619 - <div class="mxchat-card">
2620 - <div class="mxchat-card-header">
2621 - <h2><?php esc_html_e('Manage Intents', 'mxchat'); ?></h2>
2622 - <div class="mxchat-header-actions">
2623 - <form method="get" class="mxchat-search-form">
2624 - <input type="hidden" name="page" value="mxchat-intents">
2625 - <div class="mxchat-search-group">
2626 - <span class="dashicons dashicons-search"></span>
2627 - <input type="text" name="s"
2628 - placeholder="<?php esc_attr_e('Search Intents', 'mxchat'); ?>"
2629 - value="<?php echo esc_attr($search_term); ?>">
2630 - <select name="callback_filter" class="mxchat-intent-filter">
2631 - <option value="">
2632 - <?php esc_html_e('All Callbacks', 'mxchat'); ?>
2633 - </option>
2634 - <?php foreach ($available_callbacks as $function => $callback_data) :
2635 - $label = $callback_data['label']; ?>
2636 - <option value="<?php echo esc_attr($function); ?>"
2637 - <?php selected($callback_filter, $function); ?>>
2638 - <?php echo esc_html($label); ?>
2639 - </option>
2640 - <?php endforeach; ?>
2641 - </select>
2642 - <button type="submit" class="mxchat-button-secondary">
2643 - <?php esc_html_e('Filter', 'mxchat'); ?>
2644 - </button>
3818 + <!-- Actions Grid Layout - All actions in a single grid -->
3819 + <div class="mxchat-actions-grid">
3820 + <div class="mxchat-cards-container">
3821 + <?php if (!empty($actions)) : ?>
3822 + <?php foreach ($actions as $action) :
3823 + $callback_function = $action->callback_function;
3824 + $callback_label = isset($available_callbacks[$callback_function]['label'])
3825 + ? $available_callbacks[$callback_function]['label']
3826 + : $callback_function;
3827 + $threshold_value = isset($action->similarity_threshold)
3828 + ? round($action->similarity_threshold * 100)
3829 + : 85;
3830 +
3831 + // Check if this is a form action
3832 + $is_form_action = strpos($action->intent_label, 'Form ') === 0;
3833 +
3834 + // Get action status (enabled/disabled) - default to true if column doesn't exist
3835 + $is_enabled = isset($action->enabled) ? (bool)$action->enabled : true;
3836 + ?>
3837 + <div class="mxchat-action-card <?php echo $is_form_action ? 'mxchat-form-action' : ''; ?>">
3838 + <div class="mxchat-card-header">
3839 + <div class="mxchat-card-title"><?php echo esc_html($action->intent_label); ?></div>
3840 + <div class="mxchat-card-toggle">
3841 + <label class="mxchat-switch">
3842 + <input type="checkbox" class="mxchat-action-toggle"
3843 + data-action-id="<?php echo esc_attr($action->id); ?>"
3844 + <?php checked($is_enabled); ?>>
3845 + <span class="mxchat-slider round"></span>
3846 + </label>
3847 + </div>
2645 3848 </div>
2646 - </form>
2647 - </div>
2648 - </div>
2649 -
2650 - <div class="mxchat-table-wrapper">
2651 - <table class="mxchat-records-table">
2652 - <thead>
2653 - <tr>
2654 - <th><?php esc_html_e('Intent Label', 'mxchat'); ?></th>
2655 - <th><?php esc_html_e('Phrases', 'mxchat'); ?></th>
2656 - <th><?php esc_html_e('Callback Function', 'mxchat'); ?></th>
2657 - <th><?php esc_html_e('Similarity Threshold', 'mxchat'); ?></th>
2658 - <th><?php esc_html_e('Actions', 'mxchat'); ?></th>
2659 - </tr>
2660 - </thead>
2661 - <tbody>
2662 - <?php if ($intents) :
2663 - foreach ($intents as $intent) :
2664 - $callback_function = $intent->callback_function;
2665 - $callback_label = isset($available_callbacks[$callback_function]['label'])
2666 - ? $available_callbacks[$callback_function]['label']
2667 - : $callback_function;
2668 - $threshold_value = isset($intent->similarity_threshold)
2669 - ? round($intent->similarity_threshold * 100)
2670 - : 85;
2671 -
2672 - // Check if this is a form intent (its intent_label starts with "Form ")
2673 - $is_form_intent = strpos($intent->intent_label, 'Form ') === 0;
3849 +
3850 + <div class="mxchat-card-body">
3851 + <div class="mxchat-card-description">
3852 + <strong><?php esc_html_e('Type:', 'mxchat'); ?></strong>
3853 + <?php echo esc_html($callback_label); ?>
3854 + </div>
3855 +
3856 + <div class="mxchat-card-phrases">
3857 + <strong><?php esc_html_e('Trigger phrases:', 'mxchat'); ?></strong>
3858 + <div class="mxchat-phrases-preview">
3859 + <?php
3860 + // Check if the helper function exists, otherwise use a simple substring
3861 + if (method_exists($this, 'get_trimmed_phrases')) {
3862 + echo esc_html($this->get_trimmed_phrases($action->phrases));
3863 + } else {
3864 + echo esc_html(strlen($action->phrases) > 100 ?
3865 + substr($action->phrases, 0, 97) . '...' :
3866 + $action->phrases);
3867 + }
2674 3868 ?>
2675 - <tr<?php echo $is_form_intent ? ' class="mxchat-form-intent"' : ''; ?>>
2676 - <td>
2677 - <?php
2678 - if ($is_form_intent) {
2679 - // Attempt to extract the form ID from the intent_label.
2680 - preg_match('/Form (\d+)/', $intent->intent_label, $matches);
2681 - $form_id = isset($matches[1]) ? intval($matches[1]) : 0;
2682 - if ($form_id) {
2683 - global $wpdb;
2684 - $forms_table = $wpdb->prefix . 'mxchat_forms';
2685 - $form = $wpdb->get_row($wpdb->prepare("SELECT title FROM $forms_table WHERE id = %d", $form_id));
2686 - if ($form && !empty($form->title)) {
2687 - echo esc_html($form->title);
2688 - } else {
2689 - echo esc_html($intent->intent_label);
2690 - }
2691 - } else {
2692 - echo esc_html($intent->intent_label);
2693 - }
2694 - echo ' <span class="mxchat-badge">Form Intent</span>';
2695 - } else {
2696 - echo esc_html($intent->intent_label);
2697 - }
2698 - ?>
2699 - </td>
2700 - <td class="mxchat-content-cell">
2701 - <?php echo esc_html($intent->phrases); ?>
2702 - </td>
2703 - <td><?php echo esc_html($callback_label); ?></td>
2704 - <td>
2705 - <!-- Similarity threshold adjustment (shown for all intents) -->
2706 - <form method="post"
2707 - action="<?php echo esc_url(admin_url('admin-post.php')); ?>"
2708 - class="mxchat-threshold-form">
2709 - <?php wp_nonce_field('mxchat_update_intent_threshold_nonce'); ?>
2710 - <input type="hidden" name="action"
2711 - value="mxchat_update_intent_threshold">
2712 - <input type="hidden" name="intent_id"
2713 - value="<?php echo esc_attr($intent->id); ?>">
2714 - <div class="mxchat-slider-group">
2715 - <input type="range"
2716 - name="intent_threshold"
2717 - id="intent_threshold_<?php echo esc_attr($intent->id); ?>"
2718 - min="70"
2719 - max="95"
2720 - value="<?php echo esc_attr($threshold_value); ?>"
2721 - class="mxchat-intent-slider"
2722 - oninput="document.getElementById('threshold_output_<?php echo esc_attr($intent->id); ?>').value = this.value + '%'">
2723 - <output id="threshold_output_<?php echo esc_attr($intent->id); ?>"
2724 - class="mxchat-intent-output">
2725 - <?php echo esc_html($threshold_value); ?>%
2726 - </output>
2727 - </div>
2728 - <button type="submit" class="mxchat-button-icon">
2729 - <span class="dashicons dashicons-saved"></span>
2730 - </button>
2731 - </form>
2732 - </td>
2733 - <td class="mxchat-actions-cell">
2734 - <?php if (!$is_form_intent) : ?>
2735 - <!-- Standard intents: allow edit and delete -->
2736 - <button type="button"
2737 - class="mxchat-button-icon mxchat-edit-button"
2738 - data-intent-id="<?php echo esc_attr($intent->id); ?>"
2739 - data-phrases="<?php echo esc_attr($intent->phrases); ?>">
2740 - <span class="dashicons dashicons-edit"></span>
2741 - </button>
2742 - <form method="post"
2743 - action="<?php echo esc_url(admin_url('admin-post.php')); ?>"
2744 - class="mxchat-delete-form"
2745 - onsubmit="return confirm('<?php esc_attr_e('Are you sure you want to delete this intent?', 'mxchat'); ?>');">
2746 - <?php wp_nonce_field('mxchat_delete_intent_nonce'); ?>
2747 - <input type="hidden" name="action" value="mxchat_delete_intent">
2748 - <input type="hidden" name="intent_id"
2749 - value="<?php echo esc_attr($intent->id); ?>">
2750 - <button type="submit" class="mxchat-button-icon">
2751 - <span class="dashicons dashicons-trash"></span>
2752 - </button>
2753 - </form>
2754 - <?php else : ?>
2755 - <!-- Form intents: show manage in forms button -->
2756 - <?php
2757 - preg_match('/Form (\d+)/', $intent->intent_label, $matches);
2758 - $form_id = isset($matches[1]) ? $matches[1] : '';
2759 - ?>
2760 - <a href="<?php echo esc_url(admin_url('admin.php?page=mxchat-forms&action=edit&form_id=' . $form_id)); ?>"
2761 - class="mxchat-button-secondary">
2762 - <?php esc_html_e('Manage in Forms', 'mxchat'); ?>
2763 - </a>
2764 - <?php endif; ?>
2765 - </td>
2766 - </tr>
2767 - <?php endforeach;
2768 - else : ?>
2769 - <tr>
2770 - <td colspan="5" class="mxchat-no-records">
2771 - <?php esc_html_e('No intents found.', 'mxchat'); ?>
2772 - </td>
2773 - </tr>
3869 + </div>
3870 + </div>
3871 +
3872 + <div class="mxchat-threshold-control">
3873 + <div class="mxchat-threshold-label">
3874 + <?php esc_html_e('Similarity Threshold:', 'mxchat'); ?>
3875 + <span class="mxchat-threshold-value"><?php echo esc_html($threshold_value); ?>%</span>
3876 + </div>
3877 + </div>
3878 + </div>
3879 +
3880 + <div class="mxchat-card-footer">
3881 + <?php if (!$is_form_action) : ?>
3882 + <button type="button"
3883 + class="mxchat-button-secondary mxchat-edit-button"
3884 + data-action-id="<?php echo esc_attr($action->id); ?>"
3885 + data-phrases="<?php echo esc_attr($action->phrases); ?>"
3886 + data-label="<?php echo esc_attr($action->intent_label); ?>"
3887 + data-threshold="<?php echo esc_attr(round($action->similarity_threshold * 100)); ?>"
3888 + data-callback-function="<?php echo esc_attr($action->callback_function); ?>">
3889 + <span class="dashicons dashicons-edit"></span>
3890 + <?php esc_html_e('Edit', 'mxchat'); ?>
3891 + </button>
3892 + <form method="post"
3893 + action="<?php echo esc_url(admin_url('admin-post.php')); ?>"
3894 + class="mxchat-delete-form"
3895 + onsubmit="return confirm('<?php esc_attr_e('Are you sure you want to delete this action?', 'mxchat'); ?>');">
3896 + <?php wp_nonce_field('mxchat_delete_intent_nonce'); ?>
3897 + <input type="hidden" name="action" value="mxchat_delete_intent">
3898 + <input type="hidden" name="intent_id" value="<?php echo esc_attr($action->id); ?>">
3899 + <button type="submit" class="mxchat-button-text mxchat-delete-button">
3900 + <span class="dashicons dashicons-trash"></span>
3901 + <?php esc_html_e('Delete', 'mxchat'); ?>
3902 + </button>
3903 + </form>
3904 + <?php else :
3905 + preg_match('/Form (\d+)/', $action->intent_label, $matches);
3906 + $form_id = isset($matches[1]) ? $matches[1] : '';
3907 + ?>
3908 + <a href="<?php echo esc_url(admin_url('admin.php?page=mxchat-forms&action=edit&form_id=' . $form_id)); ?>"
3909 + class="mxchat-button-primary">
3910 + <span class="dashicons dashicons-feedback"></span>
3911 + <?php esc_html_e('Edit Form', 'mxchat'); ?>
3912 + </a>
2774 3913 <?php endif; ?>
2775 - </tbody>
2776 - </table>
2777 - </div>
2778 -
2779 - <?php if ($total_pages > 1) : ?>
2780 - <div class="mxchat-pagination">
2781 - <?php
2782 - echo paginate_links(array(
2783 - 'base' => add_query_arg('paged', '%#%'),
2784 - 'format' => '',
2785 - 'prev_text' => __('&laquo; Previous', 'mxchat'),
2786 - 'next_text' => __('Next &raquo;', 'mxchat'),
2787 - 'total' => $total_pages,
2788 - 'current' => $page
2789 - ));
2790 - ?>
3914 + </div>
3915 + </div>
3916 + <?php endforeach; ?>
3917 + <?php else : ?>
3918 + <!-- If no actions found -->
3919 + <div class="mxchat-no-actions">
3920 + <div class="mxchat-empty-state">
3921 + <span class="dashicons dashicons-format-chat"></span>
3922 + <h2><?php esc_html_e('No actions found', 'mxchat'); ?></h2>
3923 + <p><?php esc_html_e('Get started by creating your first action to enhance your chatbot.', 'mxchat'); ?></p>
3924 + <button type="button" id="mxchat-create-first-action" class="mxchat-button-primary">
3925 + <?php esc_html_e('Create Your First Action', 'mxchat'); ?>
3926 + </button>
3927 + </div>
2791 3928 </div>
2792 3929 <?php endif; ?>
2793 3930 </div>
2794 3931 </div>
2795 -
2796 - <!-- Edit Modal -->
2797 -<div id="mxchat-edit-modal" class="mxchat-modal" style="display: none;">
3932 +
3933 + <?php if ($total_pages > 1) : ?>
3934 + <div class="mxchat-pagination">
3935 + <?php
3936 + echo paginate_links(array(
3937 + 'base' => add_query_arg('paged', '%#%'),
3938 + 'format' => '',
3939 + 'prev_text' => __('&laquo; Previous', 'mxchat'),
3940 + 'next_text' => __('Next &raquo;', 'mxchat'),
3941 + 'total' => $total_pages,
3942 + 'current' => $page
3943 + ));
3944 + ?>
3945 + </div>
3946 + <?php endif; ?>
3947 +
3948 +<!-- Add/Edit Action Modal with Step-Based Approach -->
3949 +<!-- Complete Modal HTML with Defined Groups Variable -->
3950 +<div id="mxchat-action-modal" class="mxchat-modal" style="display: none;">
2798 3951 <div class="mxchat-modal-content">
2799 3952 <span class="mxchat-modal-close">&times;</span>
2800 - <div class="mxchat-card-header">
2801 - <h2><?php esc_html_e('Edit Intent Phrases', 'mxchat'); ?></h2>
2802 - </div>
2803 3953
2804 - <form id="mxchat-edit-intent-form" method="post"
2805 - action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
2806 - <?php wp_nonce_field('mxchat_edit_intent_nonce'); ?>
2807 - <input type="hidden" name="action" value="mxchat_edit_intent">
2808 - <input type="hidden" name="intent_id" id="edit_intent_id">
3954 + <form id="mxchat-action-form" method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
3955 + <!-- Dynamic nonce field -->
3956 + <div id="action-nonce-container">
3957 + <?php wp_nonce_field('mxchat_add_intent_nonce', 'add_intent_nonce'); ?>
3958 + </div>
3959 + <input type="hidden" name="action" id="form_action_type" value="mxchat_add_intent">
3960 + <input type="hidden" name="intent_id" id="edit_action_id" value="">
3961 + <input type="hidden" name="callback_function" id="callback_function" value="">
2809 3962
2810 - <div class="mxchat-form-group">
2811 - <label for="edit_phrases">
2812 - <?php esc_html_e('Phrases (comma-separated)', 'mxchat'); ?>
2813 - </label>
2814 - <textarea name="phrases"
2815 - id="edit_phrases"
2816 - rows="5"
2817 - class="mxchat-intent-textarea"
2818 - required></textarea>
3963 + <!-- Step 1: Action Type Selection -->
3964 + <div id="mxchat-action-step-1" class="mxchat-action-step active">
3965 + <div class="mxchat-step-indicator">
3966 + <div class="mxchat-step-number">1</div>
3967 + <div class="mxchat-step-title"><?php esc_html_e('Select Action Type', 'mxchat'); ?></div>
3968 + </div>
3969 +
3970 + <div id="mxchat-action-type-selector" class="mxchat-action-type-selector">
3971 + <div class="mxchat-action-type-search">
3972 + <span class="dashicons dashicons-search"></span>
3973 + <input type="text" id="action-type-search" placeholder="<?php esc_attr_e('Search action types...', 'mxchat'); ?>" class="mxchat-action-type-search-input">
3974 + </div>
3975 +
3976 + <?php
3977 + // Get the callbacks - IMPORTANT: Define the $groups variable here
3978 + $groups = $this->mxchat_get_available_callbacks(true, true);
3979 + ?>
3980 +
3981 + <div class="mxchat-action-type-categories">
3982 + <button type="button" class="mxchat-category-button active" data-category="all"><?php esc_html_e('All', 'mxchat'); ?></button>
3983 + <?php
3984 + // Get unique categories from the defined groups
3985 + foreach ($groups as $group_label => $group_callbacks) :
3986 + $category_slug = sanitize_title($group_label);
3987 + ?>
3988 + <button type="button" class="mxchat-category-button" data-category="<?php echo esc_attr($category_slug); ?>"><?php echo esc_html($group_label); ?></button>
3989 + <?php endforeach; ?>
3990 + </div>
3991 +
3992 + <div class="mxchat-action-types-grid">
3993 + <?php
3994 + // Generate action cards from available callbacks
3995 + foreach ($groups as $group_label => $group_callbacks) :
3996 + $category_slug = sanitize_title($group_label);
3997 +
3998 + foreach ($group_callbacks as $function => $data) :
3999 + $label = $data['label'];
4000 + $pro_only = $data['pro_only'];
4001 + $icon = isset($data['icon']) ? $data['icon'] : 'admin-generic';
4002 + $description = isset($data['description']) ? $data['description'] : '';
4003 + $is_addon = isset($data['addon']) && $data['addon'] !== false;
4004 + $addon_name = isset($data['addon_name']) ? $data['addon_name'] : '';
4005 + $is_installed = isset($data['installed']) ? $data['installed'] : true;
4006 +
4007 + // Determine card status and styling
4008 + $card_class = 'mxchat-action-type-card';
4009 + $icon_class = 'mxchat-action-type-icon';
4010 + $status_badge = '';
4011 +
4012 + if ($pro_only && !$this->is_activated) {
4013 + // Pro feature but no Pro license
4014 + $icon_class .= ' pro-feature';
4015 + $status_badge = '<span class="mxchat-pro-badge">' . esc_html__('Pro', 'mxchat') . '</span>';
4016 + }
4017 +
4018 + if ($is_addon && !$is_installed) {
4019 + // Add-on not installed
4020 + $card_class .= ' not-installed';
4021 + $status_badge .= '<span class="mxchat-addon-badge">' . esc_html__('Add-on Required', 'mxchat') . '</span>';
4022 + }
4023 +
4024 + // Default description if none provided
4025 + if (empty($description)) {
4026 + $description = sprintf(
4027 + esc_html__('Use the %s action in your chatbot', 'mxchat'),
4028 + $label
4029 + );
4030 + }
4031 + ?>
4032 + <div class="<?php echo esc_attr($card_class); ?>"
4033 + data-category="<?php echo esc_attr($category_slug); ?>"
4034 + data-value="<?php echo esc_attr($function); ?>"
4035 + data-label="<?php echo esc_attr($label); ?>"
4036 + data-pro="<?php echo $pro_only ? 'true' : 'false'; ?>"
4037 + data-addon="<?php echo esc_attr($is_addon ? $data['addon'] : ''); ?>"
4038 + data-installed="<?php echo $is_installed ? 'true' : 'false'; ?>">
4039 + <div class="<?php echo esc_attr($icon_class); ?>">
4040 + <span class="dashicons dashicons-<?php echo esc_attr($icon); ?>"></span>
4041 + </div>
4042 + <div class="mxchat-action-type-info">
4043 + <h4><?php echo esc_html($label); ?></h4>
4044 + <p><?php echo esc_html($description); ?></p>
4045 + <?php if (!empty($status_badge)) : ?>
4046 + <?php echo $status_badge; ?>
4047 + <?php endif; ?>
4048 +
4049 + <?php if ($is_addon && !$is_installed) : ?>
4050 + <div class="mxchat-addon-info">
4051 + <?php echo esc_html(sprintf(
4052 + __('Requires %s', 'mxchat'),
4053 + $addon_name
4054 + )); ?>
4055 + </div>
4056 + <?php endif; ?>
4057 + </div>
4058 + </div>
4059 + <?php endforeach;
4060 + endforeach; ?>
4061 + </div>
4062 + </div>
4063 +
4064 + <div class="mxchat-modal-actions">
4065 + <button type="button" class="mxchat-button-secondary mxchat-modal-cancel">
4066 + <?php esc_html_e('Cancel', 'mxchat'); ?>
4067 + </button>
4068 + </div>
2819 4069 </div>
2820 4070
2821 - <div class="mxchat-modal-actions">
2822 - <button type="submit" class="mxchat-button-primary">
2823 - <?php esc_html_e('Update Phrases', 'mxchat'); ?>
2824 - </button>
2825 - <button type="button" class="mxchat-button-secondary mxchat-modal-cancel">
2826 - <?php esc_html_e('Cancel', 'mxchat'); ?>
2827 - </button>
4071 + <!-- Step 2: Action Configuration -->
4072 + <div id="mxchat-action-step-2" class="mxchat-action-step">
4073 + <div class="mxchat-step-indicator">
4074 + <div class="mxchat-step-number">2</div>
4075 + <div class="mxchat-step-title"><?php esc_html_e('Configure Action', 'mxchat'); ?></div>
4076 + </div>
4077 +
4078 + <div class="mxchat-selected-action">
4079 + <button type="button" class="mxchat-back-button" id="mxchat-back-to-step-1">
4080 + <span class="dashicons dashicons-arrow-left-alt"></span>
4081 + <?php esc_html_e('Back to Action Types', 'mxchat'); ?>
4082 + </button>
4083 + <div class="mxchat-selected-action-info">
4084 + <div id="selected-action-icon" class="mxchat-action-type-icon">
4085 + <span class="dashicons dashicons-admin-generic"></span>
4086 + </div>
4087 + <div class="mxchat-selected-action-details">
4088 + <h3 id="selected-action-title"><?php esc_html_e('Selected Action', 'mxchat'); ?></h3>
4089 + <p id="selected-action-description"><?php esc_html_e('Configure this action for your chatbot', 'mxchat'); ?></p>
4090 + </div>
4091 + </div>
4092 + </div>
4093 +
4094 + <div class="mxchat-form-group">
4095 + <label for="intent_label">
4096 + <?php esc_html_e('Action Label (For your reference only)', 'mxchat'); ?>
4097 + </label>
4098 + <input name="intent_label" type="text" id="intent_label" required
4099 + class="mxchat-intent-input"
4100 + placeholder="<?php esc_attr_e('Example: Newsletter Signup', 'mxchat'); ?>">
4101 + </div>
4102 +
4103 + <div class="mxchat-form-group">
4104 + <label for="phrases">
4105 + <?php esc_html_e('Trigger Phrases (comma-separated)', 'mxchat'); ?>
4106 + </label>
4107 + <textarea name="phrases" id="action_phrases" rows="5" required
4108 + class="mxchat-intent-textarea"
4109 + placeholder="<?php esc_attr_e('Example: sign me up, subscribe me, I want to join, add me to the newsletter', 'mxchat'); ?>"></textarea>
4110 + </div>
4111 +
4112 + <div class="mxchat-form-group">
4113 + <label for="similarity_threshold">
4114 + <?php esc_html_e('Similarity Threshold', 'mxchat'); ?>
4115 + <span class="mxchat-threshold-value-display">85%</span>
4116 + </label>
4117 + <div class="mxchat-slider-group modal-slider">
4118 + <input type="range"
4119 + name="similarity_threshold"
4120 + id="similarity_threshold"
4121 + min="70"
4122 + max="95"
4123 + value="85"
4124 + class="mxchat-intent-slider"
4125 + oninput="document.querySelector('.mxchat-threshold-value-display').textContent = this.value + '%'">
4126 + </div>
4127 + <div class="mxchat-threshold-hint">
4128 + <?php esc_html_e('Lower values make the action trigger more easily. Higher values require more exact matches.', 'mxchat'); ?>
4129 + </div>
4130 + </div>
4131 +
4132 + <div class="mxchat-modal-actions">
4133 + <button type="button" class="mxchat-button-secondary mxchat-modal-cancel">
4134 + <?php esc_html_e('Cancel', 'mxchat'); ?>
4135 + </button>
4136 + <button type="submit" class="mxchat-button-primary" id="mxchat-save-action-btn">
4137 + <?php esc_html_e('Save Action', 'mxchat'); ?>
4138 + </button>
4139 + </div>
2828 4140 </div>
2829 4141 </form>
2830 4142 </div>
2831 4143 </div>
2832 4144
2833 - </div><!-- .mxchat-content -->
4145 +<div id="mxchat-action-loading" class="mxchat-action-loading" style="display: none;">
4146 + <div class="mxchat-action-loading-spinner"></div>
4147 + <div class="mxchat-action-loading-text">
4148 + <?php esc_html_e('Saving action, please wait...', 'mxchat'); ?>
4149 + </div>
4150 +</div>
2834 4151 </div><!-- .mxchat-wrapper -->
4152 + <?php
4153 +}
2835 4154
2836 - <div id="mxchat-intent-loading" class="mxchat-intent-loading" style="display: none;">
2837 - <div class="mxchat-intent-loading-spinner"></div>
2838 - <div class="mxchat-intent-loading-text">
2839 - <?php esc_html_e('Saving intent, please wait...', 'mxchat'); ?>
2840 - </div>
2841 - </div>
2842 -<?php
4155 +/**
4156 + * Helper method to trim phrases for display
4157 + * Adding this in case it doesn't exist in your class
4158 + */
4159 +private function get_trimmed_phrases($phrases, $max_length = 100) {
4160 + if (strlen($phrases) <= $max_length) {
4161 + return $phrases;
4162 + }
4163 +
4164 + $trimmed = substr($phrases, 0, $max_length);
4165 + $last_comma = strrpos($trimmed, ',');
4166 +
4167 + if ($last_comma !== false) {
4168 + $trimmed = substr($trimmed, 0, $last_comma);
4169 + }
4170 +
4171 + return $trimmed . '...';
2843 4172 }
2844 4173
4174 +/**
4175 + * AJAX handler for toggling an action on/off
4176 + */
4177 +public function mxchat_toggle_action() {
4178 + // Check nonce
4179 + if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'mxchat_actions_nonce')) {
4180 + wp_send_json_error(array('message' => 'Security check failed'));
4181 + return;
4182 + }
4183 +
4184 + // Check permissions
4185 + if (!current_user_can('manage_options')) {
4186 + wp_send_json_error(array('message' => 'Permission denied'));
4187 + return;
4188 + }
4189 +
4190 + // Validate params
4191 + $intent_id = isset($_POST['intent_id']) ? intval($_POST['intent_id']) : 0;
4192 + $enabled = isset($_POST['enabled']) ? (bool)$_POST['enabled'] : false;
4193 +
4194 + if (!$intent_id) {
4195 + wp_send_json_error(array('message' => 'Invalid action ID'));
4196 + return;
4197 + }
4198 +
4199 + // Update the intent/action status in the database
4200 + global $wpdb;
4201 + $table_name = $wpdb->prefix . 'mxchat_intents';
4202 +
4203 + // Using the 'enabled' field - add this field if it doesn't exist
4204 + $result = $wpdb->update(
4205 + $table_name,
4206 + array('enabled' => $enabled ? 1 : 0),
4207 + array('id' => $intent_id),
4208 + array('%d'),
4209 + array('%d')
4210 + );
4211 +
4212 + if ($result === false) {
4213 + wp_send_json_error(array('message' => 'Database error'));
4214 + return;
4215 + }
4216 +
4217 + wp_send_json_success();
4218 +}
2845 4219
4220 +/**
4221 + * Add the 'enabled' column to the intents table if it doesn't exist
4222 + * Call this during plugin activation or update
4223 + */
4224 +public function mxchat_add_enabled_column_to_intents() {
4225 + global $wpdb;
4226 + $table_name = $wpdb->prefix . 'mxchat_intents';
4227 +
4228 + // Check if the column already exists
4229 + $columns = $wpdb->get_results("SHOW COLUMNS FROM $table_name LIKE 'enabled'");
4230 +
4231 + if (empty($columns)) {
4232 + // Add the column with default value of 1 (enabled)
4233 + $wpdb->query("ALTER TABLE $table_name ADD COLUMN enabled TINYINT(1) NOT NULL DEFAULT 1");
4234 + }
4235 +}
2846 4236
2847 4237 /**
2848 - * Handle editing of intent phrases
4238 + * Handle embedding generation errors using existing admin notice system
4239 + *
4240 + * @param string $message Error message to display
4241 + * @param bool $redirect Whether to redirect back to the actions page
4242 + * @return void
4243 + */
4244 +private function handle_embedding_error($message, $redirect = true) {
4245 + // Store the error message in the existing transient
4246 + set_transient('mxchat_admin_notice_error', $message, 60);
4247 +
4248 + if ($redirect) {
4249 + // Redirect back to the actions page
4250 + $redirect_url = add_query_arg(
4251 + array(
4252 + 'page' => 'mxchat-actions'
4253 + ),
4254 + admin_url('admin.php')
4255 + );
4256 + wp_safe_redirect($redirect_url);
4257 + exit;
4258 + }
4259 +}
4260 +
4261 +/**
4262 + * Handle editing of intent phrases - with improved error handling
2849 4263 *
2850 4264 * @since 1.0.0
2851 4265 * @return void
2852 4266 */
2853 -public function handle_edit_intent() {
2854 - // Verify nonce and user capabilities
2855 - if (!isset($_POST['_wpnonce']) || !wp_verify_nonce($_POST['_wpnonce'], 'mxchat_edit_intent_nonce')) {
2856 - wp_die(esc_html__('Security check failed.', 'mxchat'));
2857 - }
2858 -
4267 +public function mxchat_handle_edit_intent() {
4268 + // Security checks (nonce and permissions)
2859 4269 if (!current_user_can('manage_options')) {
2860 - wp_die(esc_html__('You do not have permission to perform this action.', 'mxchat'));
4270 + wp_die(esc_html__('Unauthorized user', 'mxchat'));
2861 4271 }
4272 + check_admin_referer('mxchat_edit_intent');
2862 4273
2863 - // Validate and sanitize input
4274 + // Get POST data
2864 4275 $intent_id = isset($_POST['intent_id']) ? absint($_POST['intent_id']) : 0;
2865 - if (!$intent_id) {
2866 - wp_die(esc_html__('Invalid intent ID.', 'mxchat'));
2867 - }
4276 + $intent_label = isset($_POST['intent_label']) ? sanitize_text_field($_POST['intent_label']) : '';
4277 + $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
4278 + $threshold_percentage = isset($_POST['similarity_threshold']) ? intval($_POST['similarity_threshold']) : 85;
4279 + $similarity_threshold = min(95, max(70, $threshold_percentage)) / 100; // Convert to 0.70–0.95
2868 4280
2869 - $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
2870 - if (empty($phrases_input)) {
2871 - wp_die(esc_html__('Phrases cannot be empty.', 'mxchat'));
4281 + // Validate inputs
4282 + if (!$intent_id || empty($intent_label) || empty($phrases_input)) {
4283 + $this->handle_embedding_error(__('Invalid input. Please ensure all fields are filled out.', 'mxchat'));
4284 + return;
2872 4285 }
2873 4286
2874 - // Process phrases the same way as in intent creation
2875 4287 $phrases_array = array_map('sanitize_text_field', array_filter(array_map('trim', explode(',', $phrases_input))));
2876 -
2877 4288 if (empty($phrases_array)) {
2878 - wp_die(esc_html__('Please enter at least one valid phrase.', 'mxchat'));
4289 + $this->handle_embedding_error(__('Please enter at least one valid phrase.', 'mxchat'));
4290 + return;
2879 4291 }
2880 4292
2881 - // Generate embeddings and combine them
4293 + // Generate embeddings with improved error handling
2882 4294 $vectors = [];
4295 + $failed_phrases = [];
4296 +
2883 4297 foreach ($phrases_array as $phrase) {
2884 4298 $embedding_vector = $this->mxchat_generate_embedding($phrase, $this->options['api_key']);
2885 4299 if (is_array($embedding_vector)) {
2886 4300 $vectors[] = $embedding_vector;
2887 4301 } else {
2888 - wp_die(esc_html__('Error generating embedding for phrase: ', 'mxchat') . esc_html($phrase));
4302 + $failed_phrases[] = $phrase;
2889 4303 }
2890 4304 }
2891 -
4305 +
4306 + if (!empty($failed_phrases)) {
4307 + $this->handle_embedding_error(
4308 + sprintf(
4309 + __('Error generating embeddings for phrases: %s. Check your embedding API.', 'mxchat'),
4310 + implode(', ', $failed_phrases)
4311 + )
4312 + );
4313 + return;
4314 + }
4315 +
2892 4316 if (empty($vectors)) {
2893 - wp_die(esc_html__('No valid embeddings generated. Please check your phrases.', 'mxchat'));
4317 + $this->handle_embedding_error(__('No valid embeddings generated. Please check your phrases.', 'mxchat'));
4318 + return;
2894 4319 }
2895 4320
2896 - // Create combined vector just like in intent creation
2897 4321 $combined_vector = $this->mxchat_average_vectors($vectors);
2898 4322 $serialized_vector = maybe_serialize($combined_vector);
2899 4323
4324 + // Update the database
2900 4325 global $wpdb;
2901 4326 $table_name = $wpdb->prefix . 'mxchat_intents';
2902 4327
2903 - // Update the intent with both new phrases and the combined vector
2904 4328 $result = $wpdb->update(
2905 4329 $table_name,
2906 4330 array(
4331 + 'intent_label' => $intent_label,
2907 4332 'phrases' => implode(', ', $phrases_array),
2908 - 'embedding_vector' => $serialized_vector
4333 + 'embedding_vector' => $serialized_vector,
4334 + 'similarity_threshold' => $similarity_threshold
2909 4335 ),
2910 4336 array('id' => $intent_id),
2911 - array('%s', '%s'),
2912 - array('%d')
4337 + array('%s', '%s', '%s', '%f'), // Format: string, string, string, float
4338 + array('%d') // Where format: integer
2913 4339 );
2914 4340
2915 4341 if (false === $result) {
2916 - wp_die(esc_html__('Failed to update intent.', 'mxchat'));
4342 + $this->handle_embedding_error(__('Failed to update action in database.', 'mxchat'));
4343 + return;
2917 4344 }
2918 4345
2919 - // Redirect back to the intents page with a success message
4346 + // Set success message and redirect
4347 + set_transient('mxchat_admin_notice_success', __('Intent updated successfully!', 'mxchat'), 60);
4348 +
2920 4349 $redirect_url = add_query_arg(
2921 4350 array(
2922 - 'page' => 'mxchat-intents',
2923 - 'updated' => 'true'
4351 + 'page' => 'mxchat-actions'
2924 4352 ),
2925 4353 admin_url('admin.php')
2926 4354 );
2927 -
2928 4355 wp_safe_redirect($redirect_url);
2929 4356 exit;
2930 4357 }
2931 -public function mxchat_handle_update_intent_threshold() {
2932 - if ( ! current_user_can( 'manage_options' ) ) {
2933 - wp_die( esc_html__('Unauthorized user', 'mxchat') );
2934 - }
2935 4358
2936 - check_admin_referer('mxchat_update_intent_threshold_nonce');
2937 -
2938 - if (isset($_POST['intent_id'], $_POST['intent_threshold'])) {
2939 - global $wpdb;
2940 - $table_name = $wpdb->prefix . 'mxchat_intents';
2941 - $intent_id = intval($_POST['intent_id']);
2942 - $threshold_percentage = max(70, min(95, intval($_POST['intent_threshold'])));
2943 - $similarity_threshold = $threshold_percentage / 100;
2944 -
2945 - $wpdb->update(
2946 - $table_name,
2947 - ['similarity_threshold' => $similarity_threshold],
2948 - ['id' => $intent_id],
2949 - ['%f'],
2950 - ['%d']
2951 - );
2952 - }
2953 -
2954 - wp_safe_redirect(admin_url('admin.php?page=mxchat-intents'));
2955 - exit;
2956 -}
2957 -
4359 +/**
4360 + * Handle adding new intent - with improved error handling
4361 + *
4362 + * @return void
4363 + */
2958 4364 public function mxchat_handle_add_intent() {
2959 - if ( ! current_user_can( 'manage_options' ) ) {
2960 - wp_die( esc_html__('Unauthorized user', 'mxchat') );
4365 + if (!current_user_can('manage_options')) {
4366 + wp_die(esc_html__('Unauthorized user', 'mxchat'));
2961 4367 }
2962 4368
2963 4369 check_admin_referer('mxchat_add_intent_nonce');
2964 4370
@@ -2970,120 +4376,444 @@
2970 4376 $callback_function = isset($_POST['callback_function']) ? sanitize_text_field($_POST['callback_function']) : '';
2971 4377 $default_threshold = 0.85;
2972 4378
2973 4379 if (empty($intent_label) || empty($callback_function) || empty($phrases_input)) {
2974 - wp_die( esc_html__('Invalid input. Please ensure all fields are filled out.', 'mxchat') );
4380 + $this->handle_embedding_error(__('Invalid input. Please ensure all fields are filled out.', 'mxchat'));
4381 + return;
2975 4382 }
2976 4383
2977 4384 $available_callbacks = $this->mxchat_get_available_callbacks();
2978 4385
2979 4386 if (!array_key_exists($callback_function, $available_callbacks)) {
2980 - wp_die( esc_html__('Invalid callback function selected.', 'mxchat') );
4387 + $this->handle_embedding_error(__('Invalid callback function selected.', 'mxchat'));
4388 + return;
2981 4389 }
2982 4390
2983 4391 $is_pro_only = $available_callbacks[$callback_function]['pro_only'];
2984 4392 if ($is_pro_only && !$this->is_activated) {
2985 - wp_die( esc_html__('This callback function is available in the Pro version only.', 'mxchat') );
4393 + $this->handle_embedding_error(__('This callback function is available in the Pro version only.', 'mxchat'));
4394 + return;
2986 4395 }
2987 4396
2988 4397 $phrases_array = array_map('sanitize_text_field', array_filter(array_map('trim', explode(',', $phrases_input))));
2989 4398
2990 4399 if (empty($phrases_array)) {
2991 - wp_die( esc_html__('Please enter at least one valid phrase.', 'mxchat') );
4400 + $this->handle_embedding_error(__('Please enter at least one valid phrase.', 'mxchat'));
4401 + return;
2992 4402 }
2993 4403
4404 + // Generate embeddings with improved error handling
2994 4405 $vectors = [];
4406 + $failed_phrases = [];
4407 +
2995 4408 foreach ($phrases_array as $phrase) {
2996 4409 $embedding_vector = $this->mxchat_generate_embedding($phrase, $this->options['api_key']);
2997 4410 if (is_array($embedding_vector)) {
2998 4411 $vectors[] = $embedding_vector;
2999 4412 } else {
3000 - wp_die( esc_html__('Error generating embedding for phrase: ', 'mxchat') . esc_html($phrase) );
4413 + $failed_phrases[] = $phrase;
3001 4414 }
3002 4415 }
4416 +
4417 + if (!empty($failed_phrases)) {
4418 + $this->handle_embedding_error(
4419 + sprintf(
4420 + __('Error generating embeddings for phrases: %s. Check your embedding API.', 'mxchat'),
4421 + implode(', ', $failed_phrases)
4422 + )
4423 + );
4424 + return;
4425 + }
3003 4426
3004 - if (!empty($vectors)) {
3005 - $combined_vector = $this->mxchat_average_vectors($vectors);
3006 - $serialized_vector = maybe_serialize($combined_vector);
4427 + if (empty($vectors)) {
4428 + $this->handle_embedding_error(__('No valid embeddings generated. Please check your phrases.', 'mxchat'));
4429 + return;
4430 + }
3007 4431
3008 - $result = $wpdb->insert($table_name, [
3009 - 'intent_label' => $intent_label,
3010 - 'phrases' => implode(', ', $phrases_array),
3011 - 'embedding_vector' => $serialized_vector,
3012 - 'callback_function' => $callback_function,
3013 - 'similarity_threshold' => $default_threshold,
3014 - ]);
4432 + $combined_vector = $this->mxchat_average_vectors($vectors);
4433 + $serialized_vector = maybe_serialize($combined_vector);
3015 4434
3016 - if ($result === false) {
3017 - wp_die( esc_html__('Database error: ', 'mxchat') . esc_html($wpdb->last_error) );
3018 - }
3019 - } else {
3020 - wp_die( esc_html__('No valid embeddings generated. Please check your phrases.', 'mxchat') );
4435 + $result = $wpdb->insert($table_name, [
4436 + 'intent_label' => $intent_label,
4437 + 'phrases' => implode(', ', $phrases_array),
4438 + 'embedding_vector' => $serialized_vector,
4439 + 'callback_function' => $callback_function,
4440 + 'similarity_threshold' => $default_threshold,
4441 + ]);
4442 +
4443 + if ($result === false) {
4444 + $this->handle_embedding_error(__('Database error: ', 'mxchat') . $wpdb->last_error);
4445 + return;
3021 4446 }
3022 4447
3023 - wp_safe_redirect(admin_url('admin.php?page=mxchat-intents'));
4448 + // Set success message
4449 + set_transient('mxchat_admin_notice_success', __('New intent added successfully!', 'mxchat'), 60);
4450 +
4451 + wp_safe_redirect(admin_url('admin.php?page=mxchat-actions'));
3024 4452 exit;
3025 4453 }
3026 4454
3027 4455
4456 +/**
4457 + * Update intent threshold with AJAX support
4458 + */
4459 +public function mxchat_update_intent_threshold() {
4460 + // Check permissions
4461 + if (!current_user_can('manage_options')) {
4462 + if (wp_doing_ajax()) {
4463 + wp_send_json_error(array('message' => 'Unauthorized user'));
4464 + return;
4465 + }
4466 + wp_die(esc_html__('Unauthorized user', 'mxchat'));
4467 + }
4468 +
4469 + // Verify nonce
4470 + check_admin_referer('mxchat_update_intent_threshold_nonce');
4471 +
4472 + // Process the update if we have valid data
4473 + if (isset($_POST['intent_id'], $_POST['intent_threshold'])) {
4474 + global $wpdb;
4475 + $table_name = $wpdb->prefix . 'mxchat_intents';
4476 + $intent_id = intval($_POST['intent_id']);
4477 + $threshold_percentage = max(70, min(95, intval($_POST['intent_threshold'])));
4478 + $similarity_threshold = $threshold_percentage / 100;
4479 +
4480 + $result = $wpdb->update(
4481 + $table_name,
4482 + ['similarity_threshold' => $similarity_threshold],
4483 + ['id' => $intent_id],
4484 + ['%f'],
4485 + ['%d']
4486 + );
4487 +
4488 + // Handle AJAX requests
4489 + if (wp_doing_ajax()) {
4490 + if ($result === false) {
4491 + wp_send_json_error(array('message' => 'Failed to update threshold'));
4492 + } else {
4493 + wp_send_json_success(array('threshold' => $threshold_percentage));
4494 + }
4495 + return;
4496 + }
4497 + }
4498 +
4499 + // Redirect for regular form submissions
4500 + wp_safe_redirect(admin_url('admin.php?page=mxchat-actions&updated=true'));
4501 + exit;
4502 +}
3028 4503
3029 4504
3030 4505
3031 -
3032 -private function mxchat_get_available_callbacks($grouped = false) {
3033 - $callbacks = [
3034 - 'mxchat_handle_email_capture' => [
3035 - 'label' => __('Email Capture', 'mxchat'),
3036 - 'pro_only' => false,
3037 - 'group' => __('Customer Engagement', 'mxchat'),
3038 - ],
3039 - 'mxchat_generate_image' => [
3040 - 'label' => __('Generate Image', 'mxchat'),
3041 - 'pro_only' => true,
3042 - 'group' => __('Other Features', 'mxchat'),
3043 - ],
3044 - 'mxchat_handle_search_request' => [
3045 - 'label' => __('Brave Web Search', 'mxchat'),
3046 - 'pro_only' => false,
3047 - 'group' => __('Search Features', 'mxchat'),
3048 - ],
3049 - 'mxchat_handle_image_search_request' => [
3050 - 'label' => __('Brave Image Search', 'mxchat'),
3051 - 'pro_only' => false,
3052 - 'group' => __('Search Features', 'mxchat'),
3053 - ],
3054 - 'mxchat_handle_pdf_discussion' => [
3055 - 'label' => __('Chat with PDF', 'mxchat'),
3056 - 'pro_only' => true,
3057 - 'group' => __('Other Features', 'mxchat'),
3058 - ],
3059 - 'mxchat_live_agent_handover' => [
3060 - 'label' => __('Live Agent', 'mxchat'),
3061 - 'pro_only' => true,
3062 - 'group' => __('Customer Engagement', 'mxchat'),
3063 - ],
3064 - 'mxchat_handle_switch_to_chatbot_intent' => [
3065 - 'label' => __('Back to Chatbot', 'mxchat'),
3066 - 'pro_only' => true,
3067 - 'group' => __('Customer Engagement', 'mxchat'),
3068 - ],
3069 - ];
3070 -
3071 - // Allow other plugins to add their callbacks
3072 - $callbacks = apply_filters('mxchat_available_callbacks', $callbacks);
3073 -
4506 +/**
4507 + * Enhanced get_available_callbacks function with form action exclusion
4508 + *
4509 + * @param bool $grouped Whether to return callbacks grouped by category
4510 + * @param bool $include_all Whether to include all potential actions (even if add-on not installed)
4511 + * @return array Callbacks data with icons, descriptions and availability status
4512 + */
4513 +private function mxchat_get_available_callbacks($grouped = false, $include_all = true) {
4514 + // Load WordPress plugin functions if needed
4515 + if (!function_exists('get_plugins')) {
4516 + require_once ABSPATH . 'wp-admin/includes/plugin.php';
4517 + }
4518 +
4519 + // Get active plugins
4520 + $active_plugins = get_option('active_plugins', array());
4521 +
4522 + // Functions to exclude from the action selector
4523 + $excluded_functions = array(
4524 + 'mxchat_handle_form_collection' // Exclude form actions
4525 + );
4526 +
4527 + // Define add-on plugin files and their corresponding action functions
4528 + $addon_plugins = array(
4529 + 'mxchat-woo/mxchat-woo.php' => array(
4530 + 'functions' => array(
4531 + 'mxchat_handle_product_recommendations',
4532 + 'mxchat_handle_order_history',
4533 + 'mxchat_show_product_card',
4534 + 'mxchat_add_to_cart',
4535 + 'mxchat_checkout_redirect'
4536 + ),
4537 + 'name' => __('WooCommerce Add-on', 'mxchat'),
4538 + 'pro_required' => true
4539 + ),
4540 + 'mxchat-perplexity/mxchat-perplexity.php' => array(
4541 + 'functions' => array('mxchat_perplexity_research'),
4542 + 'name' => __('Perplexity Add-on', 'mxchat'),
4543 + 'pro_required' => true
4544 + ),
4545 + // Add other add-ons and their functions here
4546 + );
4547 +
4548 + // Get the functions that are provided by active add-ons
4549 + $addon_provided_functions = array();
4550 + $addon_function_mapping = array(); // Maps functions to their add-on info
4551 +
4552 + // Check which add-ons are active
4553 + foreach ($addon_plugins as $plugin_file => $addon_info) {
4554 + $is_active = in_array($plugin_file, $active_plugins);
4555 +
4556 + // For each function in this addon
4557 + foreach ($addon_info['functions'] as $function) {
4558 + // Consider a function installed only if:
4559 + // 1. The add-on is active AND
4560 + // 2. Either it doesn't require Pro OR Pro is activated
4561 + $is_installed = $is_active && (!$addon_info['pro_required'] || $this->is_activated);
4562 +
4563 + // If the add-on is installed, mark this function as provided by an add-on
4564 + if ($is_installed) {
4565 + $addon_provided_functions[] = $function;
4566 + }
4567 +
4568 + // Store addon info for this function regardless of installation status
4569 + $addon_function_mapping[$function] = array(
4570 + 'addon' => basename(dirname($plugin_file)),
4571 + 'addon_name' => $addon_info['name'],
4572 + 'pro_required' => $addon_info['pro_required'],
4573 + 'is_active' => $is_active,
4574 + 'is_installed' => $is_installed
4575 + );
4576 + }
4577 + }
4578 +
4579 + // Core callbacks - always available in the base plugin
4580 + $core_callbacks = array(
4581 + 'mxchat_handle_email_capture' => array(
4582 + 'label' => __('Loops Email Capture', 'mxchat'),
4583 + 'pro_only' => false,
4584 + 'group' => __('Customer Engagement', 'mxchat'),
4585 + 'icon' => 'email-alt',
4586 + 'description' => __('Collect visitor emails for your mailing list in Loops', 'mxchat'),
4587 + 'addon' => false, // Not from an add-on
4588 + 'installed' => true // Always installed with base plugin
4589 + ),
4590 + 'mxchat_handle_search_request' => array(
4591 + 'label' => __('Brave Web Search', 'mxchat'),
4592 + 'pro_only' => false,
4593 + 'group' => __('Search Features', 'mxchat'),
4594 + 'icon' => 'search',
4595 + 'description' => __('Let users search the web directly from the chat', 'mxchat'),
4596 + 'addon' => false,
4597 + 'installed' => true
4598 + ),
4599 + 'mxchat_handle_image_search_request' => array(
4600 + 'label' => __('Brave Image Search', 'mxchat'),
4601 + 'pro_only' => false,
4602 + 'group' => __('Search Features', 'mxchat'),
4603 + 'icon' => 'format-image',
4604 + 'description' => __('Search and display images in the chat conversation', 'mxchat'),
4605 + 'addon' => false,
4606 + 'installed' => true
4607 + ),
4608 + // Pro core features - check is_activated property
4609 + 'mxchat_generate_image' => array(
4610 + 'label' => __('Generate Image', 'mxchat'),
4611 + 'pro_only' => false,
4612 + 'group' => __('Other Features', 'mxchat'),
4613 + 'icon' => 'art',
4614 + 'description' => __('Create images with DALL-E 3 from OpenAI (requires OpenAI API key)', 'mxchat'),
4615 + 'addon' => false,
4616 + 'installed' => $this->is_activated
4617 + ),
4618 + 'mxchat_handle_pdf_discussion' => array(
4619 + 'label' => __('Chat with PDF', 'mxchat'),
4620 + 'pro_only' => true,
4621 + 'group' => __('Other Features', 'mxchat'),
4622 + 'icon' => 'media-document',
4623 + 'description' => __('Answer questions about uploaded PDF documents', 'mxchat'),
4624 + 'addon' => false,
4625 + 'installed' => $this->is_activated
4626 + ),
4627 + 'mxchat_live_agent_handover' => array(
4628 + 'label' => __('Slack Live Agent', 'mxchat'),
4629 + 'pro_only' => true,
4630 + 'group' => __('Customer Engagement', 'mxchat'),
4631 + 'icon' => 'admin-users',
4632 + 'description' => __('Transfer conversation to a human support agent on Slack', 'mxchat'),
4633 + 'addon' => false,
4634 + 'installed' => $this->is_activated
4635 + ),
4636 + 'mxchat_handle_switch_to_chatbot_intent' => array(
4637 + 'label' => __('Back to Chatbot', 'mxchat'),
4638 + 'pro_only' => true,
4639 + 'group' => __('Customer Engagement', 'mxchat'),
4640 + 'icon' => 'backup',
4641 + 'description' => __('Return from live agent mode to AI chatbot', 'mxchat'),
4642 + 'addon' => false,
4643 + 'installed' => $this->is_activated
4644 + ),
4645 + );
4646 +
4647 + // Add-on callbacks with placeholders - only include if the add-on is NOT active
4648 + $addon_callbacks = array(
4649 + // WooCommerce Add-on
4650 + 'mxchat_handle_product_recommendations' => array(
4651 + 'label' => __('Product Recommendations', 'mxchat'),
4652 + 'pro_only' => true,
4653 + 'group' => __('WooCommerce Features', 'mxchat'),
4654 + 'icon' => 'cart',
4655 + 'description' => __('Suggest products based on customer preferences', 'mxchat'),
4656 + ),
4657 + 'mxchat_handle_order_history' => array(
4658 + 'label' => __('Order History', 'mxchat'),
4659 + 'pro_only' => true,
4660 + 'group' => __('WooCommerce Features', 'mxchat'),
4661 + 'icon' => 'clipboard',
4662 + 'description' => __('Allow customers to check their order status', 'mxchat'),
4663 + ),
4664 + 'mxchat_show_product_card' => array(
4665 + 'label' => __('Show Product Card', 'mxchat'),
4666 + 'pro_only' => true,
4667 + 'group' => __('WooCommerce Features', 'mxchat'),
4668 + 'icon' => 'products',
4669 + 'description' => __('Display product information in the chat', 'mxchat'),
4670 + ),
4671 + 'mxchat_add_to_cart' => array(
4672 + 'label' => __('Add to Cart', 'mxchat'),
4673 + 'pro_only' => true,
4674 + 'group' => __('WooCommerce Features', 'mxchat'),
4675 + 'icon' => 'plus-alt',
4676 + 'description' => __('Add products to cart directly from chat', 'mxchat'),
4677 + ),
4678 + 'mxchat_checkout_redirect' => array(
4679 + 'label' => __('Proceed to Checkout', 'mxchat'),
4680 + 'pro_only' => true,
4681 + 'group' => __('WooCommerce Features', 'mxchat'),
4682 + 'icon' => 'arrow-right-alt',
4683 + 'description' => __('Redirect customer to checkout page', 'mxchat'),
4684 + ),
4685 +
4686 + // Perplexity Add-on
4687 + 'mxchat_perplexity_research' => array(
4688 + 'label' => __('Perplexity Research', 'mxchat'),
4689 + 'pro_only' => true,
4690 + 'group' => __('Search Features', 'mxchat'),
4691 + 'icon' => 'book-alt',
4692 + 'description' => __('Allows the chatbot to search the web for accurate, up-to-date answers', 'mxchat'),
4693 + ),
4694 + );
4695 +
4696 + // Enhance add-on callbacks with installation status and addon info
4697 + foreach ($addon_callbacks as $function => $data) {
4698 + if (isset($addon_function_mapping[$function])) {
4699 + $addon_info = $addon_function_mapping[$function];
4700 +
4701 + $addon_callbacks[$function]['addon'] = $addon_info['addon'];
4702 + $addon_callbacks[$function]['addon_name'] = $addon_info['addon_name'];
4703 + $addon_callbacks[$function]['installed'] = $addon_info['is_installed'];
4704 +
4705 + // Set pro_only based on add-on configuration
4706 + $addon_callbacks[$function]['pro_only'] = $addon_info['pro_required'];
4707 + } else {
4708 + $addon_callbacks[$function]['addon'] = 'unknown';
4709 + $addon_callbacks[$function]['addon_name'] = __('Unknown Add-on', 'mxchat');
4710 + $addon_callbacks[$function]['installed'] = false;
4711 + }
4712 + }
4713 +
4714 + // Initialize callbacks with core features
4715 + $callbacks = $core_callbacks;
4716 +
4717 + // Get callbacks from active add-ons
4718 + $active_addon_callbacks = apply_filters('mxchat_available_callbacks', array());
4719 +
4720 + // Add placeholder callbacks only for add-ons that aren't active
4721 + if ($include_all) {
4722 + foreach ($addon_callbacks as $function => $data) {
4723 + // Skip placeholders for functions provided by active add-ons
4724 + if (in_array($function, $addon_provided_functions)) {
4725 + continue;
4726 + }
4727 +
4728 + // Skip excluded functions
4729 + if (in_array($function, $excluded_functions)) {
4730 + continue;
4731 + }
4732 +
4733 + // Add the placeholder
4734 + $callbacks[$function] = $data;
4735 + }
4736 + }
4737 +
4738 + // Add callbacks from active add-ons (will override placeholders)
4739 + foreach ($active_addon_callbacks as $function => $data) {
4740 + // Skip excluded functions
4741 + if (in_array($function, $excluded_functions)) {
4742 + continue;
4743 + }
4744 +
4745 + // Always include callbacks from add-ons
4746 + $callbacks[$function] = $data;
4747 +
4748 + // Ensure they have the proper add-on info
4749 + if (isset($addon_function_mapping[$function])) {
4750 + $addon_info = $addon_function_mapping[$function];
4751 + $callbacks[$function]['addon'] = $addon_info['addon'];
4752 + $callbacks[$function]['addon_name'] = $addon_info['addon_name'];
4753 + $callbacks[$function]['installed'] = $addon_info['is_installed'];
4754 + $callbacks[$function]['pro_only'] = $addon_info['pro_required'];
4755 + }
4756 + }
4757 +
4758 + // Just before returning callbacks, sort them to prioritize free features
4759 + if (!$grouped) {
4760 + // Create temporary arrays for sorting
4761 + $free_callbacks = array();
4762 + $pro_callbacks = array();
4763 +
4764 + // Split callbacks into free and pro
4765 + foreach ($callbacks as $key => $data) {
4766 + if (isset($data['pro_only']) && $data['pro_only']) {
4767 + $pro_callbacks[$key] = $data;
4768 + } else {
4769 + $free_callbacks[$key] = $data;
4770 + }
4771 + }
4772 +
4773 + // Merge with free callbacks first
4774 + $callbacks = array_merge($free_callbacks, $pro_callbacks);
4775 + }
4776 +
3074 4777 // Return grouped structure if requested
3075 4778 if ($grouped) {
3076 - $grouped_callbacks = [];
4779 + $grouped_callbacks = array();
3077 4780 foreach ($callbacks as $key => $data) {
3078 - $group_label = $data['group'] ?? __('Other Features', 'mxchat');
3079 - $grouped_callbacks[$group_label][$key] = [
3080 - 'label' => $data['label'],
3081 - 'pro_only' => $data['pro_only'],
3082 - ];
4781 + $group_label = isset($data['group']) ? $data['group'] : __('Other Features', 'mxchat');
4782 +
4783 + // Ensure we carry forward all the new fields in grouped mode
4784 + $callback_data = array(
4785 + 'label' => $data['label'],
4786 + 'pro_only' => isset($data['pro_only']) ? $data['pro_only'] : false,
4787 + 'icon' => isset($data['icon']) ? $data['icon'] : 'admin-generic',
4788 + 'description' => isset($data['description']) ? $data['description'] : __('Custom action for your chatbot', 'mxchat'),
4789 + 'addon' => isset($data['addon']) ? $data['addon'] : false,
4790 + 'addon_name' => isset($data['addon_name']) ? $data['addon_name'] : '',
4791 + 'installed' => isset($data['installed']) ? $data['installed'] : true
4792 + );
4793 +
4794 + $grouped_callbacks[$group_label][$key] = $callback_data;
3083 4795 }
4796 +
4797 + // Sort within each group to prioritize free features
4798 + foreach ($grouped_callbacks as $group => $items) {
4799 + $free_items = array();
4800 + $pro_items = array();
4801 +
4802 + foreach ($items as $key => $data) {
4803 + if (isset($data['pro_only']) && $data['pro_only']) {
4804 + $pro_items[$key] = $data;
4805 + } else {
4806 + $free_items[$key] = $data;
4807 + }
4808 + }
4809 +
4810 + $grouped_callbacks[$group] = array_merge($free_items, $pro_items);
4811 + }
4812 +
3084 4813 return $grouped_callbacks;
3085 4814 }
4815 +
3086 4816 return $callbacks;
3087 4817 }
3088 4818
3089 4819
@@ -3120,9 +4850,9 @@
3120 4850
3121 4851 $wpdb->delete($table_name, ['id' => $intent_id], ['%d']);
3122 4852 }
3123 4853
3124 - wp_safe_redirect(admin_url('admin.php?page=mxchat-intents'));
4854 + wp_safe_redirect(admin_url('admin.php?page=mxchat-actions'));
3125 4855 exit;
3126 4856 }
3127 4857
3128 4858
@@ -3173,41 +4903,98 @@
3173 4903 );
3174 4904
3175 4905
3176 4906 // Existing fields...
3177 - add_settings_field(
3178 - 'api_key',
3179 - esc_html__('OpenAI API Key', 'mxchat'),
3180 - array($this, 'api_key_callback'),
3181 - 'mxchat-chatbot',
3182 - 'mxchat_chatbot_section'
3183 - );
4907 + add_settings_field(
4908 + 'api_key',
4909 + esc_html__('OpenAI API Key', 'mxchat'),
4910 + array($this, 'api_key_callback'),
4911 + 'mxchat-chatbot',
4912 + 'mxchat_chatbot_section',
4913 + array(
4914 + 'class' => 'mxchat-setting-row',
4915 + 'data-provider' => 'openai'
4916 + )
4917 + );
4918 +
4919 + add_settings_field(
4920 + 'xai_api_key',
4921 + esc_html__('X.AI API Key', 'mxchat'),
4922 + array($this, 'xai_api_key_callback'),
4923 + 'mxchat-chatbot',
4924 + 'mxchat_chatbot_section',
4925 + array(
4926 + 'class' => 'mxchat-setting-row',
4927 + 'data-provider' => 'xai'
4928 + )
4929 + );
4930 +
4931 + add_settings_field(
4932 + 'claude_api_key',
4933 + esc_html__('Claude API Key', 'mxchat'),
4934 + array($this, 'claude_api_key_callback'),
4935 + 'mxchat-chatbot',
4936 + 'mxchat_chatbot_section',
4937 + array(
4938 + 'class' => 'mxchat-setting-row',
4939 + 'data-provider' => 'claude'
4940 + )
4941 + );
4942 +
4943 + add_settings_field(
4944 + 'deepseek_api_key',
4945 + esc_html__('DeepSeek API Key', 'mxchat'),
4946 + array($this, 'deepseek_api_key_callback'),
4947 + 'mxchat-chatbot',
4948 + 'mxchat_chatbot_section',
4949 + array(
4950 + 'class' => 'mxchat-setting-row',
4951 + 'data-provider' => 'deepseek'
4952 + )
4953 + );
4954 +
4955 + add_settings_field(
4956 + 'gemini_api_key',
4957 + esc_html__('Google Gemini API Key', 'mxchat'),
4958 + array($this, 'gemini_api_key_callback'),
4959 + 'mxchat-chatbot',
4960 + 'mxchat_chatbot_section',
4961 + array(
4962 + 'class' => 'mxchat-setting-row',
4963 + 'data-provider' => 'gemini'
4964 + )
4965 + );
4966 +
4967 + add_settings_field(
4968 + 'voyage_api_key',
4969 + esc_html__('Voyage AI API Key', 'mxchat'),
4970 + array($this, 'voyage_api_key_callback'),
4971 + 'mxchat-chatbot',
4972 + 'mxchat_chatbot_section',
4973 + array(
4974 + 'class' => 'mxchat-setting-row',
4975 + 'data-provider' => 'voyage'
4976 + )
4977 + );
3184 4978
3185 4979 add_settings_field(
3186 - 'xai_api_key',
3187 - esc_html__('X.AI API Key', 'mxchat'),
3188 - array($this, 'xai_api_key_callback'),
4980 + 'model',
4981 + esc_html__('Chat Model', 'mxchat'),
4982 + array($this, 'mxchat_model_callback'),
3189 4983 'mxchat-chatbot',
3190 4984 'mxchat_chatbot_section'
3191 4985 );
3192 -
4986 +
4987 + // Add the settings field
3193 4988 add_settings_field(
3194 - 'claude_api_key',
3195 - esc_html__('Claude API Key', 'mxchat'),
3196 - array($this, 'claude_api_key_callback'),
4989 + 'embedding_model',
4990 + esc_html__('Embedding Model', 'mxchat'),
4991 + array($this, 'embedding_model_callback'),
3197 4992 'mxchat-chatbot',
3198 4993 'mxchat_chatbot_section'
3199 4994 );
3200 -
4995 +
3201 4996 add_settings_field(
3202 - 'deepseek_api_key',
3203 - esc_html__('DeepSeek API Key', 'mxchat'),
3204 - array($this, 'deepseek_api_key_callback'),
3205 - 'mxchat-chatbot',
3206 - 'mxchat_chatbot_section'
3207 - );
3208 -
3209 - add_settings_field(
3210 4997 'system_prompt_instructions',
3211 4998 esc_html__('AI Instructions (Behavior)', 'mxchat'),
3212 4999 array($this, 'system_prompt_instructions_callback'),
3213 5000 'mxchat-chatbot',
@@ -3213,24 +5000,25 @@
3213 5000 'mxchat-chatbot',
3214 5001 'mxchat_chatbot_section'
3215 5002 );
3216 5003
5004 +
3217 5005 add_settings_field(
3218 - 'model',
3219 - esc_html__('Model', 'mxchat'),
3220 - array($this, 'mxchat_model_callback'),
5006 + 'top_bar_title',
5007 + esc_html__('Top Bar Title', 'mxchat'),
5008 + array($this, 'mxchat_top_bar_title_callback'),
3221 5009 'mxchat-chatbot',
3222 5010 'mxchat_chatbot_section'
3223 5011 );
3224 5012
3225 5013 add_settings_field(
3226 - 'top_bar_title',
3227 - esc_html__('Top Bar Title', 'mxchat'),
3228 - array($this, 'mxchat_top_bar_title_callback'),
5014 + 'ai_agent_text',
5015 + esc_html__('AI Agent Text', 'mxchat'),
5016 + array($this, 'mxchat_ai_agent_text_callback'),
3229 5017 'mxchat-chatbot',
3230 5018 'mxchat_chatbot_section'
3231 5019 );
3232 -
5020 +
3233 5021 add_settings_field(
3234 5022 'enable_email_block',
3235 5023 esc_html__('Require Email To Chat', 'mxchat'),
3236 5024 array($this, 'enable_email_block_callback'),
@@ -3335,9 +5123,9 @@
3335 5123 );
3336 5124
3337 5125 add_settings_field(
3338 5126 'popular_question_1',
3339 - esc_html__('Popular Question 1', 'mxchat'),
5127 + esc_html__('Quick Question 1', 'mxchat'),
3340 5128 array($this, 'mxchat_popular_question_1_callback'),
3341 5129 'mxchat-chatbot',
3342 5130 'mxchat_chatbot_section'
3343 5131 );
@@ -3343,9 +5131,9 @@
3343 5131 );
3344 5132
3345 5133 add_settings_field(
3346 5134 'popular_question_2',
3347 - esc_html__('Popular Question 2', 'mxchat'),
5135 + esc_html__('Quick Question 2', 'mxchat'),
3348 5136 array($this, 'mxchat_popular_question_2_callback'),
3349 5137 'mxchat-chatbot',
3350 5138 'mxchat_chatbot_section'
3351 5139 );
@@ -3351,9 +5139,9 @@
3351 5139 );
3352 5140
3353 5141 add_settings_field(
3354 5142 'popular_question_3',
3355 - esc_html__('Popular Question 3', 'mxchat'),
5143 + esc_html__('Quick Question 3', 'mxchat'),
3356 5144 array($this, 'mxchat_popular_question_3_callback'),
3357 5145 'mxchat-chatbot',
3358 5146 'mxchat_chatbot_section'
3359 5147 );
@@ -3359,9 +5147,9 @@
3359 5147 );
3360 5148
3361 5149 add_settings_field(
3362 5150 'additional_popular_questions',
3363 - esc_html__('Additional Popular Questions', 'mxchat'),
5151 + esc_html__('Additional Quick Questions', 'mxchat'),
3364 5152 array($this, 'mxchat_additional_popular_questions_callback'),
3365 5153 'mxchat-chatbot',
3366 5154 'mxchat_chatbot_section'
3367 5155 );
@@ -3478,8 +5266,26 @@
3478 5266 array($this, 'mxchat_chat_toolbar_toggle_callback'),
3479 5267 'mxchat-embed',
3480 5268 'mxchat_pdf_intent_section'
3481 5269 );
5270 +
5271 + // PDF Upload Button Toggle
5272 + add_settings_field(
5273 + 'show_pdf_upload_button',
5274 + __('Show PDF Upload Button', 'mxchat'),
5275 + array($this, 'mxchat_show_pdf_upload_button_callback'),
5276 + 'mxchat-embed',
5277 + 'mxchat_pdf_intent_section'
5278 + );
5279 +
5280 + // Word Upload Button Toggle
5281 + add_settings_field(
5282 + 'show_word_upload_button',
5283 + __('Show Word Upload Button', 'mxchat'),
5284 + array($this, 'mxchat_show_word_upload_button_callback'),
5285 + 'mxchat-embed',
5286 + 'mxchat_pdf_intent_section'
5287 + );
3482 5288
3483 5289 add_settings_field(
3484 5290 'pdf_intent_trigger_text',
3485 5291 __('Intent Trigger Text', 'mxchat'),
@@ -3576,9 +5382,9 @@
3576 5382
3577 5383 // General Settings Section
3578 5384 add_settings_section(
3579 5385 'mxchat_general_section',
3580 - esc_html__('Frequently Asked Questions (FAQ)', 'mxchat'),
5386 + esc_html__('YouTube Tutorials', 'mxchat'),
3581 5387 null,
3582 5388 'mxchat-general'
3583 5389 );
3584 5390 }
@@ -3831,95 +5637,92 @@
3831 5637 'mxchat_setting_section_id',
3832 5638 $id === 'model' ? ['label_for' => 'model'] : []
3833 5639 );
3834 5640 }
3835 -
5641 +
5642 +// OpenAI API Key
3836 5643 public function api_key_callback() {
3837 - // Retrieve from your stored 'api_key' in the mxchat_options array
3838 5644 $apiKey = isset($this->options['api_key']) ? esc_attr($this->options['api_key']) : '';
3839 -
3840 - // Notice we changed the name to "api_key" (no array notation).
3841 - echo '<input type="password" id="api_key" name="api_key" value="' . $apiKey . '" class="regular-text" />';
5645 +
5646 + echo '<div class="api-key-wrapper" data-provider="openai">';
5647 + echo '<input type="password" id="api_key" name="api_key" value="' . $apiKey . '" class="regular-text" autocomplete="off" />';
3842 5648 echo '<button type="button" id="toggleApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
3843 - echo '<p class="description">' . esc_html__('The OpenAI API key is required even when using other models as it is used for vector embedding functionality. You must add credits to your OpenAI billing account before use.', 'mxchat') . '</p>';
5649 + echo '<p class="description api-key-notice">' . esc_html__('Required for your selected chat model. Important: You must add credits before use.', 'mxchat') . '</p>';
5650 + echo '</div>';
3844 5651 }
5652 +
5653 +// X.AI API Key
3845 5654 public function xai_api_key_callback() {
3846 - // Check if the feature is activated (paid feature)
3847 - $disabled = $this->is_activated ? '' : 'disabled'; // Disable input if not activated
3848 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive'; // CSS class to style the wrapper based on activation status
3849 -
3850 - // Retrieve the X.AI API key value
3851 5655 $xaiApiKey = isset($this->options['xai_api_key']) ? esc_attr($this->options['xai_api_key']) : '';
3852 -
3853 - // Render the input field for the X.AI API key
3854 - echo '<div class="' . esc_attr($class) . '">';
3855 - printf(
3856 - '<input type="password" id="xai_api_key" name="xai_api_key" value="%s" class="regular-text" %s />',
3857 - $xaiApiKey,
3858 - $disabled
3859 - );
5656 +
5657 + echo '<div class="api-key-wrapper" data-provider="xai">';
5658 + echo '<input type="password" id="xai_api_key" name="xai_api_key" value="' . $xaiApiKey . '" class="regular-text" autocomplete="off" />';
3860 5659 echo '<button type="button" id="toggleXaiApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
3861 -
3862 - // If the feature is not activated, show the overlay with a "Pro Only" message
3863 - if (!$this->is_activated) {
3864 - echo '<div class="pro-feature-overlay">';
3865 - echo '<a href="https://mxchat.ai/" target="_blank"><img src="' . plugin_dir_url(__FILE__) . '../images/pro-only-dark.png" alt="' . esc_attr__('Pro Only', 'mxchat') . '" /></a>';
3866 - echo '</div>';
3867 - }
3868 -
5660 + echo '<p class="description api-key-notice">' . esc_html__('Required for your selected chat model. Important: You must add credits before use.', 'mxchat') . '</p>';
3869 5661 echo '</div>';
3870 5662 }
5663 +// Claude API Key
3871 5664 public function claude_api_key_callback() {
3872 - // Check if the feature is activated (paid feature)
3873 - $disabled = $this->is_activated ? '' : 'disabled';
3874 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
3875 -
3876 - // Retrieve the Claude API key value directly like the others
3877 5665 $claudeApiKey = isset($this->options['claude_api_key']) ? esc_attr($this->options['claude_api_key']) : '';
3878 5666
3879 - // Use direct name field like the other working API keys
3880 - echo '<div class="' . esc_attr($class) . '">';
3881 - printf(
3882 - '<input type="password" id="claude_api_key" name="claude_api_key" value="%s" class="regular-text" %s />',
3883 - $claudeApiKey,
3884 - $disabled
3885 - );
5667 + echo '<div class="api-key-wrapper" data-provider="claude">';
5668 + echo '<input type="password" id="claude_api_key" name="claude_api_key" value="' . $claudeApiKey . '" class="regular-text" autocomplete="off" />';
3886 5669 echo '<button type="button" id="toggleClaudeApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
3887 -
3888 - if (!$this->is_activated) {
3889 - echo '<div class="pro-feature-overlay">';
3890 - echo '<a href="https://mxchat.ai/" target="_blank"><img src="' . plugin_dir_url(__FILE__) . '../images/pro-only-dark.png" alt="' . esc_attr__('Pro Only', 'mxchat') . '" /></a>';
3891 - echo '</div>';
3892 - }
5670 + echo '<p class="description api-key-notice">' . esc_html__('Required for your selected chat model. Important: You must add credits before use.', 'mxchat') . '</p>';
3893 5671 echo '</div>';
3894 5672 }
5673 +
5674 +// DeepSeek API Key
3895 5675 public function deepseek_api_key_callback() {
3896 - // Retrieve from your stored 'deepseek_api_key' in the mxchat_options array
3897 5676 $apiKey = isset($this->options['deepseek_api_key']) ? esc_attr($this->options['deepseek_api_key']) : '';
3898 -
3899 - // Notice we changed the name to "deepseek_api_key" (no array notation).
3900 - echo '<input type="password" id="deepseek_api_key" name="deepseek_api_key" value="' . $apiKey . '" class="regular-text" />';
5677 +
5678 + echo '<div class="api-key-wrapper" data-provider="deepseek">';
5679 + echo '<input type="password" id="deepseek_api_key" name="deepseek_api_key" value="' . $apiKey . '" class="regular-text" autocomplete="off" />';
3901 5680 echo '<button type="button" id="toggleDeepSeekApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5681 + echo '<p class="description api-key-notice">' . esc_html__('Required for your selected chat model. Important: You must add credits before use.', 'mxchat') . '</p>';
5682 + echo '</div>';
3902 5683 }
3903 5684
5685 +// Gemini API Key
5686 +public function gemini_api_key_callback() {
5687 + $geminiApiKey = isset($this->options['gemini_api_key']) ? esc_attr($this->options['gemini_api_key']) : '';
5688 +
5689 + echo '<div class="api-key-wrapper" data-provider="gemini">';
5690 + echo '<input type="password" id="gemini_api_key" name="gemini_api_key" value="' . $geminiApiKey . '" class="regular-text" autocomplete="off" />';
5691 + echo '<button type="button" id="toggleGeminiApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5692 + echo '<p class="description api-key-notice">' . esc_html__('Required for Google Gemini models. Get your API key from Google AI Studio.', 'mxchat') . '</p>';
5693 + echo '</div>';
5694 +}
3904 5695
5696 +// Voyage API Key
5697 +public function voyage_api_key_callback() {
5698 + $apiKey = isset($this->options['voyage_api_key']) ? esc_attr($this->options['voyage_api_key']) : '';
5699 +
5700 + echo '<div class="api-key-wrapper" data-provider="voyage">';
5701 + echo '<input type="password" id="voyage_api_key" name="voyage_api_key" value="' . $apiKey . '" class="regular-text" autocomplete="off" />';
5702 + echo '<button type="button" id="toggleVoyageAPIKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5703 + echo '<p class="description api-key-notice">' . esc_html__('Required for your selected embedding model. Important: You must add credits before use.', 'mxchat') . '</p>';
5704 + echo '</div>';
5705 +}
3905 5706
3906 -
3907 -
3908 5707 public function mxchat_loops_api_key_callback() {
3909 - // Support both old and new format
3910 5708 $loops_api_key = isset($this->options['loops_api_key']) ? esc_attr($this->options['loops_api_key']) : '';
3911 -
3912 - echo '<div class="api-key-wrapper">';
5709 +
5710 + // Hidden fields to "trap" autofill
5711 + echo '<input type="text" style="display:none" autocomplete="username" />';
5712 + echo '<input type="password" style="display:none" autocomplete="current-password" />';
5713 +
5714 + echo '<div class="api-key-wrapper" data-provider="loops">';
3913 5715 echo sprintf(
3914 - '<input type="password" id="loops_api_key" name="loops_api_key" value="%s" class="regular-text" />',
5716 + '<input type="password" id="loops_api_key" name="loops_api_key" value="%s" class="regular-text" autocomplete="new-password" />',
3915 5717 $loops_api_key
3916 5718 );
3917 5719 echo '<button type="button" id="toggleLoopsApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
3918 5720 echo '</div>';
3919 - echo '<p class="description">' . esc_html__('Enter your Loops API Key here. (See FAQ for details)', 'mxchat') . '</p>';
5721 + echo '<p class="description">' . esc_html__('Enter your Loops API Key here. Once entered, refreshed page to load list (See FAQ for details)', 'mxchat') . '</p>';
3920 5722 }
3921 5723
5724 +
3922 5725 public function mxchat_loops_mailing_list_callback() {
3923 5726 // Add error handling and type checking
3924 5727 $loops_api_key = '';
3925 5728 $selected_list = '';
@@ -4018,8 +5821,14 @@
4018 5821
4019 5822 public function mxchat_model_callback() {
4020 5823 // Define available models grouped by provider
4021 5824 $models = array(
5825 + esc_html__('Google Gemini Models', 'mxchat') => array(
5826 + 'gemini-2.0-flash' => esc_html__('Gemini 2.0 Flash (Next-Gen Features)', 'mxchat'),
5827 + 'gemini-2.0-flash-lite' => esc_html__('Gemini 2.0 Flash-Lite (Cost-Efficient)', 'mxchat'),
5828 + 'gemini-1.5-pro' => esc_html__('Gemini 1.5 Pro (Complex Reasoning)', 'mxchat'),
5829 + 'gemini-1.5-flash' => esc_html__('Gemini 1.5 Flash (Fast & Versatile)', 'mxchat'),
5830 + ),
4022 5831 esc_html__('X.AI Models', 'mxchat') => array(
4023 5832 'grok-beta' => esc_html__('grok-beta (Early Beta)', 'mxchat'),
4024 5833 'grok-2' => esc_html__('Grok 2', 'mxchat')
4025 5834 ),
@@ -4026,9 +5835,10 @@
4026 5835 esc_html__('DeepSeek Models', 'mxchat') => array(
4027 5836 'deepseek-chat' => esc_html__('DeepSeek-V3', 'mxchat'),
4028 5837 ),
4029 5838 esc_html__('Claude Models', 'mxchat') => array(
4030 - 'claude-3-5-sonnet-20241022' => esc_html__('Claude 3.5 Sonnet (Most Intelligent)', 'mxchat'),
5839 + 'claude-3-7-sonnet-20250219' => esc_html__('Claude 3.7 Sonnet (Most Intelligent)', 'mxchat'),
5840 + 'claude-3-5-sonnet-20241022' => esc_html__('Claude 3.5 Sonnet (Intelligent)', 'mxchat'),
4031 5841 'claude-3-opus-20240229' => esc_html__('Claude 3 Opus (Highly Complex Tasks)', 'mxchat'),
4032 5842 'claude-3-sonnet-20240229' => esc_html__('Claude 3 Sonnet (Balanced)', 'mxchat'),
4033 5843 'claude-3-haiku-20240307' => esc_html__('Claude 3 Haiku (Fastest)', 'mxchat')
4034 5844 ),
@@ -4039,40 +5849,64 @@
4039 5849 'gpt-4' => esc_html__('GPT-4 (High Intelligence)', 'mxchat'),
4040 5850 'gpt-3.5-turbo' => esc_html__('GPT-3.5 Turbo (Affordable and Fast)', 'mxchat')
4041 5851 )
4042 5852 );
4043 -
5853 +
4044 5854 // Retrieve the currently selected model from saved options
4045 5855 $selected_model = isset($this->options['model']) ? esc_attr($this->options['model']) : 'gpt-4o';
4046 -
5856 +
4047 5857 // Begin the select dropdown
4048 - echo '<select id="model" name="model">'; // No array notation in name attribute
4049 -
5858 + echo '<select id="model" name="model">';
5859 +
4050 5860 // Iterate over groups of models
4051 5861 foreach ($models as $group_label => $group_models) {
4052 5862 echo '<optgroup label="' . esc_attr($group_label) . '">';
4053 -
5863 +
4054 5864 foreach ($group_models as $model_value => $model_label) {
4055 - // Disable Pro-only models for non-activated users
4056 - $disabled = (!$this->is_activated && ($group_label === esc_html__('X.AI Models', 'mxchat') || $group_label === esc_html__('Claude Models', 'mxchat'))) ? 'disabled' : '';
4057 - $label_suffix = (!$this->is_activated && ($group_label === esc_html__('X.AI Models', 'mxchat') || $group_label === esc_html__('Claude Models', 'mxchat'))) ? esc_html__(' (Pro Only)', 'mxchat') : '';
4058 -
4059 - // Output the option element
4060 - echo '<option value="' . esc_attr($model_value) . '" ' . selected($selected_model, $model_value, false) . ' ' . $disabled . '>' . esc_html($model_label . $label_suffix) . '</option>';
5865 + // All models enabled - no disabled attribute or Pro Only label
5866 + echo '<option value="' . esc_attr($model_value) . '" ' . selected($selected_model, $model_value, false) . '>' . esc_html($model_label) . '</option>';
4061 5867 }
4062 -
5868 +
4063 5869 echo '</optgroup>';
4064 5870 }
4065 -
5871 +
4066 5872 // Close the select dropdown
4067 5873 echo '</select>';
5874 +
5875 + // Updated description to remove mention of Pro-only models
5876 + echo '<p class="description">' . esc_html__('Select the AI model your chatbot will use for chatting.', 'mxchat') . '</p>';
5877 +}
4068 5878
4069 - // Add a description below the dropdown
4070 - echo '<p class="description">' . esc_html__('Select the AI model to use for your chatbot. Pro-only models are marked accordingly.', 'mxchat') . '</p>';
5879 +
5880 +// Callback function for embedding model selection
5881 +public function embedding_model_callback() {
5882 + $models = array(
5883 + esc_html__('OpenAI Embeddings', 'mxchat') => array(
5884 + 'text-embedding-3-small' => esc_html__('TE3 Small (1536, Efficient)', 'mxchat'),
5885 + 'text-embedding-ada-002' => esc_html__('Ada 2 (1536, Recommended)', 'mxchat'),
5886 + 'text-embedding-3-large' => esc_html__('TE3 Large (3072, Powerful)', 'mxchat'),
5887 + ),
5888 + esc_html__('Voyage AI Embeddings', 'mxchat') => array(
5889 + 'voyage-3-large' => esc_html__('Voyage-3 Large (2048, Most Capable)', 'mxchat'),
5890 + )
5891 + );
5892 +
5893 + $selected_model = isset($this->options['embedding_model']) ? esc_attr($this->options['embedding_model']) : 'text-embedding-ada-002';
5894 +
5895 + echo '<select id="embedding_model" name="embedding_model">';
5896 + foreach ($models as $group_label => $group_models) {
5897 + echo '<optgroup label="' . esc_attr($group_label) . '">';
5898 + foreach ($group_models as $model_value => $model_label) {
5899 + echo '<option value="' . esc_attr($model_value) . '" ' . selected($selected_model, $model_value, false) . '>' . esc_html($model_label) . '</option>';
5900 + }
5901 + echo '</optgroup>';
5902 + }
5903 + echo '</select>';
5904 +echo '<p class="description"><span class="red-warning">IMPORTANT:</span> Select the model for vector embeddings. Changing models is not recommended; if you do, you must delete all existing knowledge & intent data and reconfigure them.</p>';
5905 +
4071 5906 }
4072 5907
4073 5908
4074 -
4075 5909 public function mxchat_top_bar_title_callback() {
4076 5910 // Retrieve the current value of the top bar title from saved options
4077 5911 $top_bar_title = isset($this->options['top_bar_title']) ? esc_attr($this->options['top_bar_title']) : '';
4078 5912
@@ -4081,9 +5915,16 @@
4081 5915
4082 5916 // Add a description
4083 5917 echo '<p class="description">' . esc_html__('Enter the title text that will appear on the top bar of the chatbot.', 'mxchat') . '</p>';
4084 5918 }
4085 -
5919 +public function mxchat_ai_agent_text_callback() {
5920 + // Retrieve the current value of the AI agent text from saved options
5921 + $ai_agent_text = isset($this->options['ai_agent_text']) ? esc_attr($this->options['ai_agent_text']) : '';
5922 + // Render the input field
5923 + echo '<input type="text" id="ai_agent_text" name="ai_agent_text" value="' . $ai_agent_text . '" />';
5924 + // Add a description
5925 + echo '<p class="description">' . esc_html__('Enter the text that will appear for AI agents in the status indicator. Default: "AI Agent"', 'mxchat') . '</p>';
5926 +}
4086 5927 public function enable_email_block_callback() {
4087 5928 // Load full plugin options array
4088 5929 $all_options = get_option('mxchat_options', []);
4089 5930
@@ -4103,9 +5944,8 @@
4103 5944 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>';
4104 5945 }
4105 5946
4106 5947
4107 -
4108 5948 public function email_blocker_header_content_callback() {
4109 5949 // Load the entire 'mxchat_options' array
4110 5950 $all_options = get_option('mxchat_options', []);
4111 5951
@@ -4113,14 +5953,15 @@
4113 5953 $content = isset($all_options['email_blocker_header_content'])
4114 5954 ? $all_options['email_blocker_header_content']
4115 5955 : '';
4116 5956
4117 - // Render the textarea
5957 + // Render the textarea - IMPORTANT: name should be just "email_blocker_header_content"
4118 5958 echo '<textarea
4119 5959 id="email_blocker_header_content"
4120 5960 name="email_blocker_header_content"
4121 5961 rows="5"
4122 5962 cols="70"
5963 + data-setting="email_blocker_header_content"
4123 5964 >' . esc_textarea($content) . '</textarea>';
4124 5965
4125 5966 echo '<p class="description">';
4126 5967 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');
@@ -4126,9 +5967,8 @@
4126 5967 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');
4127 5968 echo '</p>';
4128 5969 }
4129 5970
4130 -
4131 5971 public function email_blocker_button_text_callback() {
4132 5972 // Load the entire 'mxchat_options' array
4133 5973 $all_options = get_option('mxchat_options', []);
4134 5974
@@ -4149,23 +5989,20 @@
4149 5989
4150 5990 public function mxchat_intro_message_callback() {
4151 5991 // Load the entire 'mxchat_options' array
4152 5992 $all_options = get_option('mxchat_options', []);
4153 -
4154 5993 // Retrieve the saved intro message or use the default
4155 5994 $default_message = __('Hello! How can I assist you today?', 'mxchat');
4156 5995 $saved_message = isset($all_options['intro_message']) ? $all_options['intro_message'] : $default_message;
4157 -
4158 - // Output the textarea with the saved value
5996 + // Output the textarea with the saved value without escaping HTML
4159 5997 ?>
4160 - <textarea id="intro_message" name="intro_message" rows="5" cols="50"><?php echo esc_textarea($saved_message); ?></textarea>
5998 + <textarea id="intro_message" name="intro_message" rows="5" cols="50"><?php echo $saved_message; ?></textarea>
4161 5999 <p class="description">
4162 - <?php esc_html_e('Enter your message. Line breaks will be preserved.', 'mxchat'); ?>
6000 + <?php esc_html_e('Enter your message. HTML tags and line breaks will be preserved.', 'mxchat'); ?>
4163 6001 </p>
4164 6002 <?php
4165 6003 }
4166 6004
4167 -
4168 6005 public function mxchat_input_copy_callback() {
4169 6006 // Load the entire 'mxchat_options' array
4170 6007 $all_options = get_option('mxchat_options', []);
4171 6008
@@ -4735,13 +6572,13 @@
4735 6572 // Render the input field
4736 6573 printf(
4737 6574 '<input type="text" id="popular_question_1" name="popular_question_1" value="%s" placeholder="%s" class="regular-text" />',
4738 6575 esc_attr($popular_question_1),
4739 - esc_attr__('Enter Popular Question 1', 'mxchat')
6576 + esc_attr__('Enter Quick Question 1', 'mxchat')
4740 6577 );
4741 6578
4742 6579 // Add a description for the field
4743 - echo '<p class="description">' . esc_html__('This will be the first popular question in the chatbot.', 'mxchat') . '</p>';
6580 + echo '<p class="description">' . esc_html__('This will be the first Quick Question in the chatbot, displayed above the input field.', 'mxchat') . '</p>';
4744 6581 }
4745 6582
4746 6583
4747 6584 public function mxchat_popular_question_2_callback() {
@@ -4754,13 +6591,13 @@
4754 6591 // Render the input field
4755 6592 printf(
4756 6593 '<input type="text" id="popular_question_2" name="popular_question_2" value="%s" placeholder="%s" class="regular-text" />',
4757 6594 esc_attr($popular_question_2),
4758 - esc_attr__('Enter Popular Question 2', 'mxchat')
6595 + esc_attr__('Enter Quick Question 2', 'mxchat')
4759 6596 );
4760 6597
4761 6598 // Add a description for the field
4762 - echo '<p class="description">' . esc_html__('This will be the second popular question in the chatbot.', 'mxchat') . '</p>';
6599 + echo '<p class="description">' . esc_html__('This will be the second Quick Question in the chatbot.', 'mxchat') . '</p>';
4763 6600 }
4764 6601
4765 6602
4766 6603 public function mxchat_popular_question_3_callback() {
@@ -4773,13 +6610,13 @@
4773 6610 // Render the input field
4774 6611 printf(
4775 6612 '<input type="text" id="popular_question_3" name="popular_question_3" value="%s" placeholder="%s" class="regular-text" />',
4776 6613 esc_attr($popular_question_3),
4777 - esc_attr(__('Enter Popular Question 3', 'mxchat'))
6614 + esc_attr(__('Enter Quick Question 3', 'mxchat'))
4778 6615 );
4779 6616
4780 6617 // Add a description for the field
4781 - echo '<p class="description">' . esc_html__('This will be the third popular question in the chatbot.', 'mxchat') . '</p>';
6618 + echo '<p class="description">' . esc_html__('This will be the third Quick Question in the chatbot.', 'mxchat') . '</p>';
4782 6619 }
4783 6620
4784 6621 public function mxchat_additional_popular_questions_callback() {
4785 6622 $options = get_option('mxchat_options', []);
@@ -4800,9 +6637,9 @@
4800 6637 <button type="button" class="button mxchat-remove-question"
4801 6638 aria-label="%s">%s</button>
4802 6639 </div>',
4803 6640 esc_attr($question),
4804 - esc_attr(sprintf(__('Enter Additional Popular Question %d', 'mxchat'), $index + 4)),
6641 + esc_attr(sprintf(__('Enter Additional Quick Question %d', 'mxchat'), $index + 4)),
4805 6642 $index,
4806 6643 esc_attr(__('Remove question', 'mxchat')),
4807 6644 esc_html__('Remove', 'mxchat')
4808 6645 );
@@ -4817,9 +6654,9 @@
4817 6654 data-question-index="0" />
4818 6655 <button type="button" class="button mxchat-remove-question"
4819 6656 aria-label="%s">%s</button>
4820 6657 </div>',
4821 - esc_attr(__('Enter Additional Popular Question 4', 'mxchat')),
6658 + esc_attr(__('Enter Additional Quick Question 4', 'mxchat')),
4822 6659 esc_attr(__('Remove question', 'mxchat')),
4823 6660 esc_html__('Remove', 'mxchat')
4824 6661 );
4825 6662 }
@@ -4828,9 +6665,9 @@
4828 6665 '<button type="button" class="button mxchat-add-question" aria-label="%s">%s</button>',
4829 6666 esc_attr(__('Add question', 'mxchat')),
4830 6667 esc_html__('Add Question', 'mxchat')
4831 6668 );
4832 - echo '<p class="description">' . esc_html__('Add as many popular questions as you need.', 'mxchat') . '</p>';
6669 + echo '<p class="description">' . esc_html__('Add as many Quick Questions as you need.', 'mxchat') . '</p>';
4833 6670 echo '</div>';
4834 6671 }
4835 6672
4836 6673 public function mxchat_brave_api_key_callback() {
@@ -4962,9 +6799,78 @@
4962 6799 echo '</div>';
4963 6800 echo '<p class="description">' . esc_html__('Enable to display the chat toolbar, adding two icons below the chatbot input field for uploading PDF and Word documents (default is hidden).', 'mxchat') . '</p>';
4964 6801 }
4965 6802
6803 +/**
6804 + * Callback for PDF upload button toggle setting
6805 + */
6806 +public function mxchat_show_pdf_upload_button_callback() {
6807 + // Get toggle value with fallback
6808 + $show_pdf_button = isset($this->options['show_pdf_upload_button']) ? $this->options['show_pdf_upload_button'] : 'on';
6809 + $checked = ($show_pdf_button === 'on') ? 'checked' : '';
6810 + // Check if the plugin is activated (paid feature)
6811 + $disabled = $this->is_activated ? '' : 'disabled';
6812 + $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6813 +
6814 + echo '<div class="' . esc_attr($class) . '">';
6815 + // Output the toggle switch
6816 + echo '<label class="toggle-switch">';
6817 + echo sprintf(
6818 + '<input type="checkbox" id="show_pdf_upload_button" name="show_pdf_upload_button" value="on" %s %s />',
6819 + esc_attr($checked),
6820 + esc_attr($disabled)
6821 + );
6822 + echo '<span class="slider"></span>';
6823 + echo '</label>';
6824 +
6825 + // Pro feature overlay if needed
6826 + if (!$this->is_activated) {
6827 + echo '<div class="pro-feature-overlay">';
6828 + echo '<a href="https://mxchat.ai/" target="_blank">';
6829 + echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6830 + echo '</a>';
6831 + echo '</div>';
6832 + }
6833 +
6834 + echo '</div>';
6835 + echo '<p class="description">' . esc_html__('Enable to show the PDF upload button in the chatbot toolbar. Disable to hide it.', 'mxchat') . '</p>';
6836 +}
4966 6837
6838 +/**
6839 + * Callback for Word upload button toggle setting
6840 + */
6841 +public function mxchat_show_word_upload_button_callback() {
6842 + // Get toggle value with fallback
6843 + $show_word_button = isset($this->options['show_word_upload_button']) ? $this->options['show_word_upload_button'] : 'on';
6844 + $checked = ($show_word_button === 'on') ? 'checked' : '';
6845 + // Check if the plugin is activated (paid feature)
6846 + $disabled = $this->is_activated ? '' : 'disabled';
6847 + $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6848 +
6849 + echo '<div class="' . esc_attr($class) . '">';
6850 + // Output the toggle switch
6851 + echo '<label class="toggle-switch">';
6852 + echo sprintf(
6853 + '<input type="checkbox" id="show_word_upload_button" name="show_word_upload_button" value="on" %s %s />',
6854 + esc_attr($checked),
6855 + esc_attr($disabled)
6856 + );
6857 + echo '<span class="slider"></span>';
6858 + echo '</label>';
6859 +
6860 + // Pro feature overlay if needed
6861 + if (!$this->is_activated) {
6862 + echo '<div class="pro-feature-overlay">';
6863 + echo '<a href="https://mxchat.ai/" target="_blank">';
6864 + echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6865 + echo '</a>';
6866 + echo '</div>';
6867 + }
6868 +
6869 + echo '</div>';
6870 + echo '<p class="description">' . esc_html__('Enable to show the Word document upload button in the chatbot toolbar. Disable to hide it.', 'mxchat') . '</p>';
6871 +}
6872 +
4967 6873 public function mxchat_pdf_intent_trigger_text_callback() {
4968 6874 $disabled = $this->is_activated ? '' : 'disabled';
4969 6875 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
4970 6876 $default_text = __("Please provide the URL to the PDF you'd like to discuss.", 'mxchat');
@@ -5185,24 +7091,21 @@
5185 7091 }
5186 7092 echo '</div>';
5187 7093 }
5188 7094
5189 -
5190 7095 public function mxchat_similarity_threshold_callback() {
5191 7096 // Load from mxchat_options array
5192 7097 $options = get_option('mxchat_options', []);
5193 -
5194 - // Get value with backwards compatibility
5195 - $threshold = isset($options['similarity_threshold'])
5196 - ? $options['similarity_threshold']
5197 - : get_option('mxchat_similarity_threshold', 80);
5198 -
7098 +
7099 + // Get value from options array with default of 80
7100 + $threshold = isset($options['similarity_threshold']) ? $options['similarity_threshold'] : 35;
7101 +
5199 7102 echo '<div class="slider-container">';
5200 7103 echo sprintf(
5201 7104 '<input type="range"
5202 7105 id="similarity_threshold"
5203 7106 name="similarity_threshold"
5204 - min="70"
7107 + min="20"
5205 7108 max="85"
5206 7109 step="1"
5207 7110 value="%s"
5208 7111 class="range-slider" />',
@@ -5212,16 +7115,14 @@
5212 7115 '<span id="threshold_value" class="range-value">%s</span>',
5213 7116 esc_html($threshold)
5214 7117 );
5215 7118 echo '</div>';
5216 -
5217 7119 echo '<p class="description">';
5218 - echo esc_html__('Set the similarity threshold (recommended: 75) to balance accuracy; too high might limit your bot\'s ability to find relevant knowledge.', 'mxchat');
7120 + echo esc_html__('Set the similarity threshold to balance accuracy; too high might limit your bot\'s ability to find relevant knowledge. If your bot is not matching content, move to lowest setting to test.', 'mxchat');
5219 7121 echo '</p>';
5220 7122 }
5221 7123
5222 7124
5223 -
5224 7125 public function mxchat_live_agent_secret_key_callback() {
5225 7126 $disabled = $this->is_activated ? '' : 'disabled';
5226 7127 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5227 7128
@@ -5266,9 +7167,9 @@
5266 7167 public function mxchat_enqueue_admin_assets() {
5267 7168 wp_enqueue_style('wp-color-picker');
5268 7169
5269 7170 // Get the plugin version or file modification time for cache busting
5270 - $plugin_version = '2.0.4'; // Replace this with your plugin's version
7171 + $plugin_version = '2.1.5'; // Replace this with your plugin's version
5271 7172
5272 7173 // File paths
5273 7174 $color_picker_js_path = plugin_dir_path(__FILE__) . '../js/my-color-picker.js';
5274 7175 $embedding_check_js_path = plugin_dir_path(__FILE__) . '../js/embedding-check.js';
@@ -5274,8 +7175,9 @@
5274 7175 $embedding_check_js_path = plugin_dir_path(__FILE__) . '../js/embedding-check.js';
5275 7176 $admin_css_path = plugin_dir_path(__FILE__) . '../css/admin-style.css';
5276 7177 $knowledge_css_path = plugin_dir_path(__FILE__) . '../css/knowledge-style.css';
5277 7178 $intent_css_path = plugin_dir_path(__FILE__) . '../css/intent-style.css';
7179 + $transcripts_css_path = plugin_dir_path(__FILE__) . '../css/chat-transcripts.css';
5278 7180 $transcripts_js_path = plugin_dir_path(__FILE__) . '../js/mxchat_transcripts.js';
5279 7181
5280 7182 // Check if files exist and get modification times
5281 7183 $color_picker_version = file_exists($color_picker_js_path) ? filemtime($color_picker_js_path) : $plugin_version;
@@ -5282,10 +7184,38 @@
5282 7184 $embedding_check_version = file_exists($embedding_check_js_path) ? filemtime($embedding_check_js_path) : $plugin_version;
5283 7185 $admin_css_version = file_exists($admin_css_path) ? filemtime($admin_css_path) : $plugin_version;
5284 7186 $knowledge_css_version = file_exists($knowledge_css_path) ? filemtime($knowledge_css_path) : $plugin_version;
5285 7187 $intent_css_version = file_exists($intent_css_path) ? filemtime($intent_css_path) : $plugin_version;
7188 + $transcripts_css_version = file_exists($transcripts_css_path) ? filemtime($transcripts_css_path) : $plugin_version;
5286 7189 $transcripts_js_version = file_exists($transcripts_js_path) ? filemtime($transcripts_js_path) : $plugin_version;
5287 -
7190 +
7191 + $admin_status_js_path = plugin_dir_path(__FILE__) . '../js/admin-status.js';
7192 + $admin_status_js_version = file_exists($admin_status_js_path) ? filemtime($admin_status_js_path) : $plugin_version;
7193 +
7194 + // Check current admin page
7195 + $current_page = isset($_GET['page']) ? $_GET['page'] : '';
7196 +
7197 + // Only enqueue on the prompts page
7198 + if ($current_page === 'mxchat-prompts') {
7199 + wp_enqueue_script(
7200 + 'mxchat-status-updater',
7201 + plugin_dir_url(__FILE__) . '../js/admin-status.js',
7202 + array('jquery'),
7203 + $admin_status_js_version,
7204 + true
7205 + );
7206 +
7207 + // Add the nonce for the status updater
7208 + wp_localize_script(
7209 + 'mxchat-status-updater',
7210 + 'mxchat_status_data',
7211 + array(
7212 + 'ajax_url' => admin_url('admin-ajax.php'),
7213 + 'nonce' => wp_create_nonce('mxchat_status_nonce')
7214 + )
7215 + );
7216 + }
7217 +
5288 7218 // Enqueue scripts and styles with corrected paths
5289 7219 wp_enqueue_script(
5290 7220 'mxchat-color-picker',
5291 7221 plugin_dir_url(__FILE__) . '../js/my-color-picker.js',
@@ -5302,16 +7232,8 @@
5302 7232 true
5303 7233 );
5304 7234
5305 7235 wp_enqueue_script(
5306 - 'mxchat-transcripts-js',
5307 - plugin_dir_url(__FILE__) . '../js/mxchat_transcripts.js',
5308 - array('jquery'),
5309 - $transcripts_js_version,
5310 - true
5311 - );
5312 -
5313 - wp_enqueue_script(
5314 7236 'mxchat-admin-js',
5315 7237 plugin_dir_url(__FILE__) . '../js/mxchat-admin.js',
5316 7238 array('jquery'),
5317 7239 $plugin_version,
@@ -5317,14 +7239,23 @@
5317 7239 $plugin_version,
5318 7240 true
5319 7241 );
5320 7242
5321 - wp_localize_script('mxchat-admin-js', 'mxchatAdmin', array(
5322 - 'ajax_url' => admin_url('admin-ajax.php'),
5323 - 'license_nonce' => wp_create_nonce('mxchat_activate_license_nonce'),
5324 - 'inline_edit_nonce' => wp_create_nonce('mxchat_save_inline_nonce'),
5325 - 'setting_nonce' => wp_create_nonce('mxchat_save_setting_nonce'),
5326 - ));
7243 +wp_localize_script('mxchat-admin-js', 'mxchatAdmin', array(
7244 + 'ajax_url' => admin_url('admin-ajax.php'),
7245 + 'license_nonce' => wp_create_nonce('mxchat_activate_license_nonce'),
7246 + 'inline_edit_nonce' => wp_create_nonce('mxchat_save_inline_nonce'),
7247 + 'setting_nonce' => wp_create_nonce('mxchat_save_setting_nonce'),
7248 + 'export_nonce' => wp_create_nonce('mxchat_export_transcripts'),
7249 + 'actions_nonce' => wp_create_nonce('mxchat_actions_nonce'),
7250 + 'add_intent_nonce' => wp_create_nonce('mxchat_add_intent_nonce'),
7251 + 'edit_intent_nonce' => wp_create_nonce('mxchat_edit_intent'),
7252 + 'toggle_action_nonce' => wp_create_nonce('mxchat_actions_nonce'),
7253 + 'is_activated' => $this->is_activated ? '1' : '0',
7254 + // Add these two new lines
7255 + 'status_nonce' => wp_create_nonce('mxchat_status_nonce'),
7256 + 'status_refresh_interval' => 5000, // Update every 5 seconds
7257 +));
5327 7258
5328 7259 // Enqueue the admin CSS
5329 7260 wp_enqueue_style(
5330 7261 'mxchat-admin-css',
@@ -5331,29 +7262,52 @@
5331 7262 plugin_dir_url(__FILE__) . '../css/admin-style.css',
5332 7263 array(),
5333 7264 $admin_css_version
5334 7265 );
7266 +
7267 + // Conditional enqueue for transcripts page
7268 + if ($current_page === 'mxchat-transcripts') {
7269 + wp_enqueue_style(
7270 + 'mxchat-chat-transcripts-css',
7271 + plugin_dir_url(__FILE__) . '../css/chat-transcripts.css',
7272 + array(),
7273 + $transcripts_css_version
7274 + );
7275 +
7276 + wp_enqueue_script(
7277 + 'mxchat-transcripts-js',
7278 + plugin_dir_url(__FILE__) . '../js/mxchat_transcripts.js',
7279 + array('jquery'),
7280 + $transcripts_js_version,
7281 + true
7282 + );
7283 + }
7284 +
7285 + // Only enqueue knowledge CSS on knowledge-related pages or all plugin pages
7286 + if (strpos($current_page, 'mxchat') !== false) {
7287 + wp_enqueue_style(
7288 + 'mxchat-knowledge-css',
7289 + plugin_dir_url(__FILE__) . '../css/knowledge-style.css',
7290 + array(),
7291 + $knowledge_css_version
7292 + );
7293 + }
5335 7294
5336 - wp_enqueue_style(
5337 - 'mxchat-knowledge-css',
5338 - plugin_dir_url(__FILE__) . '../css/knowledge-style.css',
5339 - array(),
5340 - $knowledge_css_version
5341 - );
7295 + // Only enqueue intent-style.css on the mxchat-actions page
7296 + if ($current_page === 'mxchat-actions') {
7297 + wp_enqueue_style(
7298 + 'mxchat-intent-css',
7299 + plugin_dir_url(__FILE__) . '../css/intent-style.css',
7300 + array(),
7301 + $intent_css_version
7302 + );
7303 + }
5342 7304
5343 -
5344 - wp_enqueue_style(
5345 - 'mxchat-intent-css',
5346 - plugin_dir_url(__FILE__) . '../css/intent-style.css',
5347 - array(),
5348 - $intent_css_version
5349 - );
5350 -
5351 7305 // IMPORTANT: Use the same script handle as above for localizing mxchatPromptsAdmin
5352 - wp_localize_script( 'mxchat-admin-js', 'mxchatPromptsAdmin', array(
5353 - 'ajax_url' => admin_url( 'admin-ajax.php' ),
5354 - 'prompts_setting_nonce' => wp_create_nonce( 'mxchat_prompts_setting_nonce' ),
5355 - ) );
7306 + wp_localize_script('mxchat-admin-js', 'mxchatPromptsAdmin', array(
7307 + 'ajax_url' => admin_url('admin-ajax.php'),
7308 + 'prompts_setting_nonce' => wp_create_nonce('mxchat_prompts_setting_nonce'),
7309 + ));
5356 7310
5357 7311 // Localize the script for color picker and settings
5358 7312 wp_localize_script('mxchat-color-picker', 'mxchatStyleSettings', array(
5359 7313 'ajax_url' => admin_url('admin-ajax.php'),
@@ -5383,8 +7337,10 @@
5383 7337 'live_agent_bot_token' => $this->options['live_agent_bot_token'] ?? '',
5384 7338 'live_agent_message_bg_color' => $this->options['live_agent_message_bg_color'] ?? '#ffffff',
5385 7339 'live_agent_message_font_color' => $this->options['live_agent_message_font_color'] ?? '#333333',
5386 7340 'chat_toolbar_toggle' => $this->options['chat_toolbar_toggle'] ?? 'off',
7341 + 'show_pdf_upload_button' => $this->options['show_pdf_upload_button'] ?? 'on',
7342 + 'show_word_upload_button' => $this->options['show_word_upload_button'] ?? 'on',
5387 7343 'mode_indicator_bg_color' => $this->options['mode_indicator_bg_color'] ?? '#767676',
5388 7344 'mode_indicator_font_color' => $this->options['mode_indicator_font_color'] ?? '#ffffff',
5389 7345 'toolbar_icon_color' => $this->options['toolbar_icon_color'] ?? '#212121',
5390 7346 ));
@@ -5399,9 +7355,9 @@
5399 7355 }
5400 7356
5401 7357 if (isset($input['similarity_threshold'])) {
5402 7358 $new_input['similarity_threshold'] = absint($input['similarity_threshold']); // Ensure it's an integer
5403 - $new_input['similarity_threshold'] = min(max($new_input['similarity_threshold'], 70), 85); // Enforce range
7359 + $new_input['similarity_threshold'] = min(max($new_input['similarity_threshold'], 20), 85); // Enforce range
5404 7360 }
5405 7361
5406 7362 if (isset($input['xai_api_key'])) {
5407 7363 $new_input['xai_api_key'] = sanitize_text_field($input['xai_api_key']);
@@ -5413,8 +7369,12 @@
5413 7369
5414 7370 if (isset($input['deepseek_api_key'])) {
5415 7371 $new_input['deepseek_api_key'] = sanitize_text_field($input['deepseek_api_key']);
5416 7372 }
7373 +
7374 + if (isset($input['gemini_api_key'])) {
7375 + $new_input['gemini_api_key'] = sanitize_text_field($input['gemini_api_key']);
7376 + }
5417 7377
5418 7378 if (isset($input['enable_woocommerce_integration'])) {
5419 7379 $new_input['enable_woocommerce_integration'] = $input['enable_woocommerce_integration'] === 'on' ? 'on' : 'off';
5420 7380 }
@@ -5451,8 +7411,12 @@
5451 7411
5452 7412 if (isset($input['top_bar_title'])) {
5453 7413 $new_input['top_bar_title'] = sanitize_text_field($input['top_bar_title']);
5454 7414 }
7415 +
7416 + if (isset($input['ai_agent_text'])) {
7417 + $new_input['ai_agent_text'] = sanitize_text_field($input['ai_agent_text']);
7418 + }
5455 7419
5456 7420 if (isset($input['enable_email_block'])) {
5457 7421 $new_input['enable_email_block'] = sanitize_text_field($input['enable_email_block']);
5458 7422 }
@@ -5465,9 +7429,9 @@
5465 7429 $new_input['email_blocker_button_text'] = sanitize_text_field($input['email_blocker_button_text']);
5466 7430 }
5467 7431
5468 7432 if (isset($input['intro_message'])) {
5469 - $new_input['intro_message'] = sanitize_textarea_field($input['intro_message']); // Changed from sanitize_text_field
7433 + $new_input['intro_message'] = wp_kses_post($input['intro_message']); // Use wp_kses_post instead
5470 7434 }
5471 7435
5472 7436 if (isset($input['input_copy'])) {
5473 7437 $new_input['input_copy'] = sanitize_text_field($input['input_copy']);
@@ -5505,14 +7469,36 @@
5505 7469
5506 7470 if (isset($input['pre_chat_message'])) {
5507 7471 $new_input['pre_chat_message'] = sanitize_textarea_field($input['pre_chat_message']);
5508 7472 }
7473 +
7474 + if (isset($input['voyage_api_key'])) {
7475 + $new_input['voyage_api_key'] = sanitize_text_field($input['voyage_api_key']);
7476 + }
7477 +
7478 + // Add to your sanitize function
7479 + if (isset($input['embedding_model'])) {
7480 + $allowed_models = array(
7481 + 'text-embedding-ada-002',
7482 + 'text-embedding-3-small',
7483 + 'text-embedding-3-large',
7484 + 'voyage-3-large'
7485 + );
7486 + if (in_array($input['embedding_model'], $allowed_models)) {
7487 + $new_input['embedding_model'] = sanitize_text_field($input['embedding_model']);
7488 + }
7489 + }
5509 7490
5510 - if (isset($input['model'])) {
7491 + if (isset($input['model'])) {
5511 7492 $allowed_models = array(
7493 + 'gemini-2.0-flash',
7494 + 'gemini-2.0-flash-lite',
7495 + 'gemini-1.5-pro',
7496 + 'gemini-1.5-flash',
5512 7497 'grok-beta',
5513 7498 'grok-2',
5514 7499 'deepseek-chat',
7500 + 'claude-3-7-sonnet-20250219',
5515 7501 'claude-3-5-sonnet-20241022',
5516 7502 'claude-3-opus-20240229',
5517 7503 'claude-3-sonnet-20240229',
5518 7504 'claude-3-haiku-20240307',
@@ -5681,8 +7667,22 @@
5681 7667
5682 7668 if (isset($input['chat_toolbar_toggle'])) {
5683 7669 $new_input['chat_toolbar_toggle'] = $input['chat_toolbar_toggle'] === 'on' ? 'on' : 'off';
5684 7670 }
7671 +
7672 + // Sanitize PDF upload button toggle
7673 + if (isset($input['show_pdf_upload_button'])) {
7674 + $new_input['show_pdf_upload_button'] = $input['show_pdf_upload_button'] === 'on' ? 'on' : 'off';
7675 + } else {
7676 + $new_input['show_pdf_upload_button'] = 'off'; // If checkbox is unchecked
7677 + }
7678 +
7679 + // Sanitize Word upload button toggle
7680 + if (isset($input['show_word_upload_button'])) {
7681 + $new_input['show_word_upload_button'] = $input['show_word_upload_button'] === 'on' ? 'on' : 'off';
7682 + } else {
7683 + $new_input['show_word_upload_button'] = 'off'; // If checkbox is unchecked
7684 + }
5685 7685
5686 7686 if (isset($input['pdf_intent_trigger_text'])) {
5687 7687 $new_input['pdf_intent_trigger_text'] = sanitize_text_field($input['pdf_intent_trigger_text']);
5688 7688 }
@@ -5737,62 +7737,10 @@
5737 7737 }
5738 7738
5739 7739
5740 7740
5741 -private static function mxchat_extract_main_content($html) {
5742 - if (empty($html)) {
5743 - //error_log('mxchat_extract_main_content: Empty HTML content received');
5744 - return '';
5745 - }
5746 7741
5747 - try {
5748 - $dom = new DOMDocument;
5749 - libxml_use_internal_errors(true); // Suppress HTML parsing errors
5750 7742
5751 - // Simple load of HTML, with @ to suppress warnings
5752 - @$dom->loadHTML($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
5753 -
5754 - $xpath = new DOMXPath($dom);
5755 -
5756 - // Simplified selectors focusing on common content areas
5757 - $selectors = [
5758 - '//article',
5759 - '//*[@id="content"]',
5760 - '//*[@class="entry-content"]',
5761 - '//main'
5762 - ];
5763 -
5764 - foreach ($selectors as $selector) {
5765 - $nodes = $xpath->query($selector);
5766 - if ($nodes && $nodes->length > 0) {
5767 - $content = '';
5768 - foreach ($nodes as $node) {
5769 - $content .= $dom->saveHTML($node);
5770 - }
5771 - if (!empty($content)) {
5772 - return $content;
5773 - }
5774 - }
5775 - }
5776 -
5777 - // Fallback: Return the entire body content if no specific selector matches
5778 - $body = $dom->getElementsByTagName('body');
5779 - if ($body->length > 0) {
5780 - return $dom->saveHTML($body->item(0));
5781 - }
5782 -
5783 - // Last resort: return the original HTML
5784 - return $html;
5785 -
5786 - } catch (Exception $e) {
5787 - //error_log('mxchat_extract_main_content error: ' . $e->getMessage());
5788 - return $html; // Return original HTML if parsing fails
5789 - } finally {
5790 - libxml_clear_errors();
5791 - }
5792 -}
5793 -
5794 -
5795 7743 private function mxchat_fetch_loops_mailing_lists($api_key) {
5796 7744 $url = 'https://app.loops.so/api/v1/lists';
5797 7745 $response = wp_remote_get($url, array(
5798 7746 'headers' => array(
@@ -5837,8 +7785,81 @@
5837 7785 }
5838 7786 }
5839 7787
5840 7788
7789 +/**
7790 + * Update your existing display_admin_notices function to show notices on all MXChat pages
7791 + */
7792 +public function display_admin_notices() {
7793 + // Check if we're on a MXChat admin page
7794 + $screen = get_current_screen();
7795 + if (!$screen || strpos($screen->base, 'mxchat') === false) {
7796 + return;
7797 + }
7798 +
7799 + //error_log('MxChat admin_notices hook fired on screen: ' . $screen->base);
7800 +
7801 + // Check for error notices
7802 + $error_notice = get_transient('mxchat_admin_notice_error');
7803 + if ($error_notice) {
7804 + //error_log('Found error transient: ' . $error_notice);
7805 + echo '<div class="notice notice-error is-dismissible"><p>' . wp_kses_post($error_notice) . '</p></div>';
7806 + delete_transient('mxchat_admin_notice_error');
7807 + //error_log('Displayed and deleted error transient');
7808 + } else {
7809 + //error_log('No error transient found');
7810 + }
7811 +
7812 + // Check for success notices
7813 + $success_notice = get_transient('mxchat_admin_notice_success');
7814 + if ($success_notice) {
7815 + //error_log('Found success transient: ' . $success_notice);
7816 + echo '<div class="notice notice-success is-dismissible"><p>' . wp_kses_post($success_notice) . '</p></div>';
7817 + delete_transient('mxchat_admin_notice_success');
7818 + //error_log('Displayed and deleted success transient');
7819 + }
7820 +
7821 + // Check for info notices
7822 + $info_notice = get_transient('mxchat_admin_notice_info');
7823 + if ($info_notice) {
7824 + //error_log('Found info transient: ' . $info_notice);
7825 + echo '<div class="notice notice-info is-dismissible"><p>' . wp_kses_post($info_notice) . '</p></div>';
7826 + delete_transient('mxchat_admin_notice_info');
7827 + //error_log('Displayed and deleted info transient');
7828 + }
7829 +
7830 + // Display active processing status
7831 + $this->display_processing_status();
7832 +}
7833 +
7834 +
7835 +
7836 +/**
7837 + * Display current processing status
7838 + */
7839 +private function display_processing_status() {
7840 + $pdf_url = get_transient('mxchat_last_pdf_url');
7841 + $sitemap_url = get_transient('mxchat_last_sitemap_url');
7842 +
7843 + if (!$pdf_url && !$sitemap_url) {
7844 + return;
7845 + }
7846 +
7847 + $pdf_status = $pdf_url ? $this->get_pdf_processing_status($pdf_url) : false;
7848 + $sitemap_status = $sitemap_url ? $this->get_sitemap_processing_status($sitemap_url) : false;
7849 +
7850 + if ($sitemap_status && isset($sitemap_status['error']) && !empty($sitemap_status['error'])) {
7851 + echo '<div class="notice notice-error is-dismissible">';
7852 + echo '<p><strong>' . esc_html__('Sitemap Processing Error:', 'mxchat') . '</strong> ' . esc_html($sitemap_status['error']) . '</p>';
7853 + echo '</div>';
7854 + }
7855 +
7856 + if ($pdf_status && isset($pdf_status['error']) && !empty($pdf_status['error'])) {
7857 + echo '<div class="notice notice-error is-dismissible">';
7858 + echo '<p><strong>' . esc_html__('PDF Processing Error:', 'mxchat') . '</strong> ' . esc_html($pdf_status['error']) . '</p>';
7859 + echo '</div>';
7860 + }
7861 +}
5841 7862
5842 7863
5843 7864 }
5844 7865 ?>