PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 2.1.7
MxChat – AI Chatbot & Content Generation for WordPress v2.1.7
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 +3275 -1470 2.0.62.1.7 View file →
@@ -37,11 +37,12 @@
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'));
@@ -56,16 +57,23 @@
56 57 add_action('wp_insert_post', array($this, 'mxchat_handle_product_change'), 10, 3);
57 58 add_action('wp_trash_post', array($this, 'mxchat_handle_product_delete'));
58 59 add_action('before_delete_post', array($this, 'mxchat_handle_product_delete'));
59 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 +
60 65 }
61 66
62 - // Method to check if the license is active
63 67 private function is_license_active() {
64 - $license_status = get_option('mxchat_license_status', esc_html__('inactive', 'mxchat'));
65 - return $license_status === esc_html__('active', 'mxchat');
66 - }
68 + // Get the raw value without translation
69 + $license_status = get_option('mxchat_license_status', 'inactive');
70 +
71 + // Check against multiple possible values, bypassing translation issues
72 + return ($license_status === 'active' || $license_status === esc_html__('active', 'mxchat'));
73 +}
67 74
75 +
68 76 // Initialize default options
69 77 private function initialize_default_options() {
70 78 $default_options = array(
71 79 'api_key' => '',
@@ -72,8 +80,9 @@
72 80 'xai_api_key' => '',
73 81 'claude_api_key' => '',
74 82 'deepseek_api_key' => '',
75 83 'voyage_api_key' => '',
84 + 'gemini_api_key' => '',
76 85 'embedding_model' => 'text-embedding-ada-002',
77 86 '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:
78 87 - Your name is [Chatbot Name].
79 88 - 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.
@@ -84,12 +93,13 @@
84 93 'rate_limit_logged_out' => esc_html__('100', 'mxchat'),
85 94 'role_rate_limits' => array(),
86 95 'rate_limit_message' => esc_html__('Rate limit exceeded. Please try again later.', 'mxchat'),
87 96 'enable_email_block' => '',
88 - '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'),
97 + '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'),
89 98 'email_blocker_button_text' => esc_html__('Start Chat', 'mxchat'),
90 99 'top_bar_title' => esc_html__('MxChat', 'mxchat'),
91 - 'intro_message' => esc_html__('Hello! How can I assist you today?', 'mxchat'),
100 + 'intro_message' => __('Hello! How can I assist you today?', 'mxchat'),
101 + 'ai_agent_text' => esc_html__('AI Agent', 'mxchat'),
92 102 'input_copy' => esc_html__('How can I assist?', 'mxchat'),
93 103 'append_to_body' => esc_html__('off', 'mxchat'),
94 104 'close_button_color' => esc_html__('#fff', 'mxchat'),
95 105 'chatbot_bg_color' => esc_html__('#fff', 'mxchat'),
@@ -108,17 +118,19 @@
108 118
109 119 // New fields for Loops Integration
110 120 'loops_api_key' => '',
111 121 'loops_mailing_list' => '',
112 - 'triggered_phrase_response' => esc_html__('Would you like to join our mailing list? Please provide your email below.', 'mxchat'),
113 - 'email_capture_response' => esc_html__('Thank you for providing your email! You\'ve been added to our list.', 'mxchat'),
122 + 'triggered_phrase_response' => __('Would you like to join our mailing list? Please provide your email below.', 'mxchat'),
123 + 'email_capture_response' => __('Thank you for providing your email! You\'ve been added to our list.', 'mxchat'),
114 124 'popular_question_1' => '',
115 125 'popular_question_2' => '',
116 126 'popular_question_3' => '',
117 - 'pdf_intent_trigger_text' => esc_html__("Please provide the URL to the PDF you'd like to discuss.", 'mxchat'),
118 - 'pdf_intent_success_text' => esc_html__("I've processed the PDF. What questions do you have about it?", 'mxchat'),
119 - 'pdf_intent_error_text' => esc_html__("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat'),
127 + 'pdf_intent_trigger_text' => __("Please provide the URL to the PDF you'd like to discuss.", 'mxchat'),
128 + 'pdf_intent_success_text' => __("I've processed the PDF. What questions do you have about it?", 'mxchat'),
129 + 'pdf_intent_error_text' => __("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat'),
120 130 'pdf_max_pages' => 69,
131 + 'show_pdf_upload_button' => 'on',
132 + 'show_word_upload_button' => 'on',
121 133
122 134 // Live Agent Integration
123 135 'live_agent_webhook_url' => '',
124 136 'live_agent_secret_key' => '',
@@ -186,14 +198,14 @@
186 198 array($this, 'mxchat_create_transcripts_page')
187 199 );
188 200
189 201 add_submenu_page(
190 - 'mxchat-max',
191 - esc_html__('MxChat Intents', 'mxchat'),
192 - esc_html__('Intents', 'mxchat'),
193 - 'manage_options',
194 - 'mxchat-intents',
195 - array($this, 'mxchat_intents_page_html')
202 + 'mxchat-max',
203 + esc_html__('MxChat Actions', 'mxchat'),
204 + esc_html__('Actions', 'mxchat'),
205 + 'manage_options',
206 + 'mxchat-actions',
207 + array($this, 'mxchat_actions_page_html')
196 208 );
197 209
198 210 add_submenu_page(
199 211 'mxchat-max',
@@ -223,28 +235,52 @@
223 235
224 236 public function mxchat_save_setting_callback() {
225 237 check_ajax_referer('mxchat_save_setting_nonce');
226 238 if (!current_user_can('manage_options')) {
239 + ('MXChat Save: Unauthorized access attempt');
227 240 wp_send_json_error(['message' => esc_html__('Unauthorized', 'mxchat')]);
228 241 }
242 +
229 243 $name = isset($_POST['name']) ? $_POST['name'] : '';
230 244 // Strip slashes from the value before saving
231 245 $value = isset($_POST['value']) ? stripslashes($_POST['value']) : '';
246 +
247 + //error_log('MXChat Save: Processing field name: ' . $name);
248 + //error_log('MXChat Save: Field value: ' . $value);
249 +
232 250 if (empty($name)) {
251 + //error_log('MXChat Save: Empty field name detected');
233 252 wp_send_json_error(['message' => esc_html__('Invalid field name', 'mxchat')]);
234 253 }
254 +
235 255 // Load the full options array
236 256 $options = get_option('mxchat_options', []);
257 + //error_log('MXChat Save: Current options array: ' . print_r($options, true));
258 +
237 259 // Handle special cases
238 260 switch ($name) {
239 261 case 'additional_popular_questions':
262 + //error_log('MXChat Save: Processing additional_popular_questions');
240 263 $questions = json_decode($value, true); // No need for stripslashes here
241 264 if (is_array($questions)) {
242 265 $options[$name] = $questions;
243 266 // Also update old option for backwards compatibility
244 267 update_option('additional_popular_questions', $questions);
268 + //error_log('MXChat Save: Saved ' . count($questions) . ' additional questions');
269 + } else {
270 + //error_log('MXChat Save: Failed to decode questions JSON');
245 271 }
246 272 break;
273 + case 'email_blocker_header_content':
274 + //error_log('MXChat Save: Processing email_blocker_header_content');
275 + // Allow HTML content but sanitize it safely
276 + $options[$name] = wp_kses_post($value);
277 + break;
278 + case 'similarity_threshold':
279 + //error_log('MXChat Save: Processing similarity_threshold');
280 + // Save to the options array
281 + $options[$name] = $value;
282 + break;
247 283 case 'user_message_bg_color':
248 284 case 'user_message_font_color':
249 285 case 'bot_message_bg_color':
250 286 case 'bot_message_font_color':
@@ -257,63 +293,136 @@
257 293 case 'live_agent_message_font_color':
258 294 case 'mode_indicator_bg_color':
259 295 case 'mode_indicator_font_color':
260 296 case 'toolbar_icon_color':
297 + //error_log('MXChat Save: Processing color value: ' . $name);
261 298 // Store color values directly
262 299 $options[$name] = $value;
263 300 break;
264 301 case 'live_agent_status':
302 + //error_log('MXChat Save: Processing live_agent_status');
265 303 // Set the new value
266 304 $options[$name] = ($value === 'on') ? 'on' : 'off';
267 305 break;
268 306 case 'enable_woocommerce_integration':
307 + //error_log('MXChat Save: Processing enable_woocommerce_integration');
269 308 // Handle values that used to be 1/0
270 309 $options[$name] = ($value === 'on' || $value === '1') ? 'on' : 'off';
271 310 break;
272 311 default:
273 - // Handle role rate limits
274 - if (strpos($name, 'mxchat_options[role_rate_limits]') !== false) {
312 + // First check for rate limits settings
313 + if (strpos($name, 'mxchat_options[rate_limits]') !== false) {
314 + //error_log('MXChat Save: Detected rate_limits field: ' . $name);
315 +
316 + // Extract role ID and setting from the name
317 + preg_match('/\[rate_limits\]\[(.*?)\]\[(.*?)\]/', $name, $matches);
318 + //error_log('MXChat Save: Regex matches: ' . print_r($matches, true));
319 +
320 + if (isset($matches[1]) && isset($matches[2])) {
321 + $role_id = $matches[1];
322 + $setting_key = $matches[2]; // limit, timeframe, or message
323 +
324 + //error_log('MXChat Save: Role ID = ' . $role_id . ', Setting Key = ' . $setting_key);
325 +
326 + // Initialize rate_limits if it doesn't exist
327 + if (!isset($options['rate_limits'])) {
328 + // //error_log('MXChat Save: Initializing rate_limits array');
329 + $options['rate_limits'] = [];
330 + }
331 +
332 + // Initialize role settings if it doesn't exist
333 + if (!isset($options['rate_limits'][$role_id])) {
334 + //error_log('MXChat Save: Initializing rate_limits for role: ' . $role_id);
335 + $options['rate_limits'][$role_id] = [
336 + 'limit' => ($role_id === 'logged_out') ? '10' : '100',
337 + 'timeframe' => 'daily',
338 + 'message' => 'Rate limit exceeded. Please try again later.'
339 + ];
340 + }
341 +
342 + // Update the specific setting
343 + $options['rate_limits'][$role_id][$setting_key] = $value;
344 + //error_log('MXChat Save: Updated rate_limits[' . $role_id . '][' . $setting_key . '] = ' . $value);
345 + } else {
346 + //error_log('MXChat Save: Failed to parse rate_limits pattern: ' . $name);
347 + }
348 + }
349 + // Then check for role rate limits (old format)
350 + else if (strpos($name, 'mxchat_options[role_rate_limits]') !== false) {
351 + //error_log('MXChat Save: Processing role_rate_limits field: ' . $name);
275 352 // Extract role ID from the name
276 353 preg_match('/\[role_rate_limits\]\[(.*?)\]/', $name, $matches);
354 + //error_log('MXChat Save: Regex matches: ' . print_r($matches, true));
355 +
277 356 if (isset($matches[1])) {
278 357 $role_id = $matches[1];
279 358 // Initialize role_rate_limits if it doesn't exist
280 359 if (!isset($options['role_rate_limits'])) {
360 + //error_log('MXChat Save: Initializing role_rate_limits array');
281 361 $options['role_rate_limits'] = [];
282 362 }
283 363 // Update the specific role's rate limit
284 364 $options['role_rate_limits'][$role_id] = sanitize_text_field($value);
285 - break;
365 + //error_log('MXChat Save: Updated role_rate_limits[' . $role_id . '] = ' . $value);
366 + } else {
367 + //error_log('MXChat Save: Failed to parse role_rate_limits pattern: ' . $name);
286 368 }
287 369 }
288 370 // Handle toggles
289 - if (strpos($name, 'toggle') !== false || in_array($name, [
371 + else if (strpos($name, 'toggle') !== false || in_array($name, [
290 372 'chat_persistence_toggle',
291 373 'privacy_toggle',
292 374 'complianz_toggle',
293 - 'chat_toolbar_toggle' // Removed live_agent_status from here
375 + 'chat_toolbar_toggle',
376 + 'show_pdf_upload_button',
377 + 'show_word_upload_button'
294 378 ])) {
379 + //error_log('MXChat Save: Processing toggle: ' . $name);
295 380 $options[$name] = ($value === 'on') ? 'on' : 'off';
296 381 } else {
382 + //error_log('MXChat Save: Processing standard field: ' . $name);
297 383 // Store all other values directly
298 384 $options[$name] = $value;
299 385 }
300 386 break;
301 387 }
302 - // Save all updates
388 +
389 + // Save all updates to the options array
303 390 $updated = update_option('mxchat_options', $options);
304 - // Handle backwards compatibility for certain fields
305 - $legacy_fields = ['brave_api_key', 'brave_image_count', 'brave_safe_search', 'brave_news_count',
306 - 'brave_country', 'brave_language', 'similarity_threshold'];
307 - if (in_array($name, $legacy_fields)) {
308 - // Also update the individual option for backwards compatibility
309 - update_option($name, $value);
391 + //error_log('MXChat Save: Update result: ' . ($updated ? 'success' : 'unchanged') . ' for field: ' . $name);
392 + //error_log('MXChat Save: Updated options array: ' . print_r($options, true));
393 +
394 + // Always return success even if WordPress says nothing changed
395 + // (which happens when the value is the same as before)
396 + wp_send_json_success(['message' => esc_html__('Setting saved', 'mxchat')]);
397 +}
398 +
399 +/**
400 + * Helper function to compare if a value has changed
401 + * Handles various data types appropriately
402 + */
403 +private function has_value_changed($old_value, $new_value) {
404 + // Handle null values
405 + if ($old_value === null && $new_value === '') {
406 + return false;
310 407 }
311 - if ($updated) {
312 - wp_send_json_success(['message' => esc_html__('Setting saved', 'mxchat')]);
313 - } else {
314 - wp_send_json_error(['message' => esc_html__('Update failed or no changes', 'mxchat')]);
408 +
409 + // Handle array values (like additional_popular_questions)
410 + if (is_array($old_value) && is_array($new_value)) {
411 + // Convert both to JSON for comparison to handle ordering differences
412 + return json_encode($old_value) !== json_encode($new_value);
315 413 }
414 +
415 + // Handle toggle/checkbox values consistently
416 + if (in_array($old_value, ['on', '1', 1, true]) && in_array($new_value, ['on', '1', 1, true])) {
417 + return false;
418 + }
419 + if (in_array($old_value, ['off', '0', 0, false, '']) && in_array($new_value, ['off', '0', 0, false, ''])) {
420 + return false;
421 + }
422 +
423 + // Default direct comparison
424 + return $old_value !== $new_value;
316 425 }
317 426
318 427 /**
319 428 * Handles AJAX auto-save for prompts and auto-sync settings.
@@ -318,30 +427,41 @@
318 427 /**
319 428 * Handles AJAX auto-save for prompts and auto-sync settings.
320 429 */
321 430 public function mxchat_save_prompts_setting_callback() {
322 - // Check the correct nonce for this action.
323 - check_ajax_referer( 'mxchat_prompts_setting_nonce', '_ajax_nonce' );
324 -
325 - if ( ! current_user_can( 'manage_options' ) ) {
326 - wp_send_json_error( [ 'message' => esc_html__( 'Unauthorized', 'mxchat' ) ] );
431 + check_ajax_referer('mxchat_prompts_setting_nonce', '_ajax_nonce');
432 +
433 + if (!current_user_can('manage_options')) {
434 + wp_send_json_error(['message' => esc_html__('Unauthorized', 'mxchat')]);
327 435 }
328 -
329 - $name = isset( $_POST['name'] ) ? sanitize_text_field( wp_unslash( $_POST['name'] ) ) : '';
330 - $value = isset( $_POST['value'] ) ? sanitize_text_field( wp_unslash( $_POST['value'] ) ) : '';
331 -
332 - if ( empty( $name ) ) {
333 - wp_send_json_error( [ 'message' => esc_html__( 'Invalid field name', 'mxchat' ) ] );
436 +
437 + $name = isset($_POST['name']) ? sanitize_text_field($_POST['name']) : '';
438 + $value = isset($_POST['value']) ? sanitize_text_field($_POST['value']) : '';
439 +
440 + if (empty($name)) {
441 + wp_send_json_error(['message' => esc_html__('Invalid field name', 'mxchat')]);
334 442 }
335 -
336 - // Handle standalone auto-sync settings.
337 - if ( in_array( $name, [ 'mxchat_auto_sync_posts', 'mxchat_auto_sync_pages' ], true ) ) {
338 - // Convert checkbox value to "1" if checked, "0" if not.
339 - $value = ( $value === 'on' ) ? '1' : '0';
340 - update_option( $name, $value );
341 - wp_send_json_success( [ 'message' => esc_html__( 'Setting saved', 'mxchat' ) ] );
443 +
444 + // Log the values we're trying to save (for debugging)
445 + //error_log('Attempting to save setting: ' . $name . ' = ' . $value);
446 +
447 + // For all auto-sync options
448 + if (strpos($name, 'mxchat_auto_sync_') === 0) {
449 + // Convert 'on'/'off' to '1'/'0' for consistency (if needed)
450 + $option_value = ($value === 'on') ? '1' : '0';
451 +
452 + $result = update_option($name, $option_value);
453 +
454 + if ($result) {
455 + //error_log('Successfully saved setting: ' . $name . ' = ' . $option_value);
456 + wp_send_json_success(['message' => esc_html__('Setting saved', 'mxchat')]);
457 + } else {
458 + //error_log('Failed to save setting: ' . $name . ' (no changes or error)');
459 + wp_send_json_error(['message' => esc_html__('Update failed or no changes', 'mxchat')]);
460 + }
461 + return;
342 462 }
343 -
463 +
344 464 // Handle fields stored in the 'mxchat_prompts_options' array.
345 465 // Handle fields stored in the 'mxchat_prompts_options' array.
346 466 if ( false !== strpos( $name, 'mxchat_prompts_options[' ) ) {
347 467 // Extract the key name using regex.
@@ -367,9 +487,8 @@
367 487
368 488
369 489 wp_send_json_error( [ 'message' => esc_html__( 'Field not recognized', 'mxchat' ) ] );
370 490 }
371 -
372 491 public function mxchat_display_admin_notice() {
373 492 // Success notice
374 493 if ($message = get_transient('mxchat_admin_notice_success')) {
375 494 ?>
@@ -399,181 +518,215 @@
399 518
400 519 public function mxchat_create_admin_page() {
401 520
402 521 ?>
403 - <div class="wrap mxchat-admin">
404 - <?php if (!$this->is_activated): ?>
405 - <div class="mxchat-pro-banner">
406 - <p>
407 - <?php echo esc_html__('For a limited time, get lifetime access and save $20 on MxChat Pro!', 'mxchat'); ?>
408 - <a href="https://mxchat.ai/" target="_blank"><?php echo esc_html__('Upgrade to Pro today', 'mxchat'); ?></a>
409 - </p>
522 + <div class="wrap mxchat-wrapper">
523 + <!-- Hero Section -->
524 + <div class="mxchat-hero">
525 + <h1 class="mxchat-main-title">
526 + <span class="mxchat-gradient-text">MxChat</span> Settings
527 + </h1>
528 + <p class="mxchat-hero-subtitle">
529 + <?php esc_html_e('Configure your AI chatbot, manage integrations and explore tutorials to get the most out of MxChat.', 'mxchat'); ?>
530 + </p>
410 531 </div>
411 - <?php endif; ?>
412 532
413 - <div class="mxchat-agents-banner">
414 - <p>
415 - <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'); ?>
416 - </p>
417 - </div>
533 + <div class="mxchat-content">
534 + <?php if (!$this->is_activated): ?>
535 + <div class="mxchat-pro-card">
536 + <div class="mxchat-pro-notification">
537 + <div class="mxchat-pro-content">
538 + <h3>🚀 Limited Time Offer: Save $20 on MxChat Pro Lifetime Access!</h3>
539 + <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>
540 + </div>
541 + <div class="mxchat-pro-cta">
542 + <a href="https://mxchat.ai/" target="_blank" class="mxchat-button"><?php echo esc_html__('Upgrade to Pro Today', 'mxchat'); ?></a>
543 + <a href="<?php echo admin_url('admin.php?page=mxchat-addons'); ?>" class="mxchat-link"><?php echo esc_html__('Preview Add-ons', 'mxchat'); ?></a>
544 + </div>
545 + </div>
546 + </div>
547 + <?php endif; ?>
418 548
419 - <h2 class="mxchat-nav-tab-wrapper">
420 - <a href="#chatbot" class="mxchat-nav-tab mxchat-nav-tab-active" data-tab="chatbot"><?php echo esc_html__('Chatbot', 'mxchat'); ?></a>
421 - <a href="#embed" class="mxchat-nav-tab" data-tab="embed"><?php echo esc_html__('Integrations', 'mxchat'); ?></a>
422 - <a href="#general" class="mxchat-nav-tab" data-tab="general"><?php echo esc_html__('FAQ', 'mxchat'); ?></a>
423 - </h2>
549 + <!-- Tabs Navigation -->
550 + <div class="mxchat-tabs">
551 + <button class="mxchat-tab-button active" data-tab="chatbot"><?php echo esc_html__('Chatbot', 'mxchat'); ?></button>
552 + <button class="mxchat-tab-button" data-tab="embed"><?php echo esc_html__('Integrations', 'mxchat'); ?></button>
553 + <button class="mxchat-tab-button" data-tab="general"><?php echo esc_html__('YouTube Tutorials', 'mxchat'); ?></button>
554 + </div>
424 555
425 - <div id="chatbot" class="mxchat-tab-content active">
426 - <div class="mxchat-autosave-section">
427 - <?php do_settings_sections('mxchat-chatbot'); ?>
556 + <!-- Tab Contents -->
557 + <div id="chatbot" class="mxchat-tab-content active">
558 + <div class="mxchat-card">
559 + <div class="mxchat-autosave-section">
560 + <?php do_settings_sections('mxchat-chatbot'); ?>
561 + </div>
562 + </div>
428 563 </div>
429 - </div>
430 564
431 - <div id="embed" class="mxchat-tab-content">
432 - <div class="mxchat-autosave-section">
433 -
434 - <div class="mxchat-settings-section">
435 - <h2><?php echo esc_html__('Loops Settings', 'mxchat'); ?></h2>
436 - <table class="form-table">
437 - <?php do_settings_fields('mxchat-embed', 'mxchat_loops_section'); ?>
438 - </table>
565 + <div id="embed" class="mxchat-tab-content">
566 + <div class="mxchat-card">
567 + <h2><?php esc_html_e('Loops Settings', 'mxchat'); ?></h2>
568 + <div class="mxchat-autosave-section">
569 + <table class="form-table">
570 + <?php do_settings_fields('mxchat-embed', 'mxchat_loops_section'); ?>
571 + </table>
572 + </div>
439 573 </div>
440 574
441 - <div class="section-divider"></div>
442 -
443 - <div class="mxchat-settings-section">
444 - <h2><?php echo esc_html__('Brave Search Settings', 'mxchat'); ?></h2>
445 - <table class="form-table">
446 - <?php do_settings_fields('mxchat-embed', 'mxchat_brave_section'); ?>
447 - </table>
575 + <div class="mxchat-card">
576 + <h2><?php esc_html_e('Brave Search Settings', 'mxchat'); ?></h2>
577 + <div class="mxchat-autosave-section">
578 + <table class="form-table">
579 + <?php do_settings_fields('mxchat-embed', 'mxchat_brave_section'); ?>
580 + </table>
581 + </div>
448 582 </div>
449 583
450 - <div class="section-divider"></div>
451 -
452 - <div class="mxchat-settings-section">
453 - <h2><?php echo esc_html__('Toolbar Settings & Intents', 'mxchat'); ?></h2>
454 - <table class="form-table">
455 - <?php do_settings_fields('mxchat-embed', 'mxchat_pdf_intent_section'); ?>
456 - </table>
584 + <div class="mxchat-card">
585 + <h2><?php esc_html_e('Toolbar Settings & Intents', 'mxchat'); ?></h2>
586 + <div class="mxchat-autosave-section">
587 + <table class="form-table">
588 + <?php do_settings_fields('mxchat-embed', 'mxchat_pdf_intent_section'); ?>
589 + </table>
590 + </div>
457 591 </div>
458 592
459 - <div class="section-divider"></div>
460 -
461 - <!-- Live Agent Settings Section -->
462 - <div class="mxchat-settings-section">
463 - <h2><?php echo esc_html__('Live Agent Settings', 'mxchat'); ?></h2>
464 - <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>
465 - <table class="form-table">
466 - <?php do_settings_fields('mxchat-embed', 'mxchat_live_agent_section'); ?>
467 - </table>
593 + <div class="mxchat-card">
594 + <h2><?php esc_html_e('Live Agent Settings', 'mxchat'); ?></h2>
595 + <div class="mxchat-autosave-section">
596 + <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>
597 + <table class="form-table">
598 + <?php do_settings_fields('mxchat-embed', 'mxchat_live_agent_section'); ?>
599 + </table>
600 + </div>
468 601 </div>
469 602 </div>
470 - </div>
471 603
604 + <div id="general" class="mxchat-tab-content">
605 + <div class="mxchat-card">
606 + <?php do_settings_sections('mxchat-general'); ?>
607 + <div class="video-tutorials-section">
472 608
473 - <div id="general" class="mxchat-tab-content">
474 - <?php do_settings_sections('mxchat-general'); ?>
475 - <p>
476 - <?php echo esc_html__('If you’re having trouble with setup or getting the responses you need, we encourage you to review our', 'mxchat'); ?>
477 - <a href="https://mxchat.ai/documentation/" target="_blank" rel="noopener noreferrer"><?php echo esc_html__('documentation', 'mxchat'); ?></a> <?php echo esc_html__('or', 'mxchat'); ?>
478 - <a href="https://wordpress.org/support/plugin/mxchat-basic/" target="_blank" rel="noopener noreferrer"><?php echo esc_html__('create a support ticket', 'mxchat'); ?></a>.
479 - </p>
480 - <p>
481 - <?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>.
482 - </p>
483 -
484 - <div class="faq-item">
485 - <h3><?php echo esc_html__('How does the Claude API integration work?', 'mxchat'); ?></h3>
486 - <p>
487 - <?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'); ?>
488 - </p>
489 - <p>
490 - <?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'); ?>
491 - </p>
492 - <p>
493 - <?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>.
494 - </p>
609 + <div class="tutorial-grid">
610 + <div class="tutorial-item">
611 + <h3><?php echo esc_html__('MxChat Forms Tutorial', 'mxchat'); ?></h3>
612 + <div class="video-description">
613 + <p><?php echo esc_html__('Learn how to create and manage smart forms that automatically trigger during chat conversations.', 'mxchat'); ?></p>
614 + <a href="https://www.youtube.com/watch?v=3MrWy5dRalA" target="_blank" rel="noopener" class="video-link">
615 + <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>
616 + <?php echo esc_html__('Watch MxChat Forms Tutorial', 'mxchat'); ?>
617 + </a>
495 618 </div>
619 + </div>
496 620
497 - <div class="faq-item">
498 - <h3><?php echo esc_html__('How does the X.AI API integration work?', 'mxchat'); ?></h3>
499 - <p>
500 - <?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'); ?>
501 - </p>
502 - <p>
503 - <?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>.
504 - </p>
621 + <div class="tutorial-item">
622 + <h3><?php echo esc_html__('Intent Tester Guide', 'mxchat'); ?></h3>
623 + <div class="video-description">
624 + <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>
625 + <a href="https://www.youtube.com/watch?v=uTr14tn59Hc" target="_blank" rel="noopener" class="video-link">
626 + <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>
627 + <?php echo esc_html__('Watch Intent Tester Tutorial', 'mxchat'); ?>
628 + </a>
505 629 </div>
630 + </div>
506 631
507 - <div class="faq-item">
508 - <h3><?php echo esc_html__('Do I need an OpenAI API key to use the chatbot?', 'mxchat'); ?></h3>
509 - <p>
510 - <?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'); ?>
511 - </p>
512 - <p>
513 - <?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'); ?>
514 - </p>
632 + <div class="tutorial-item">
633 + <h3><?php echo esc_html__('WooCommerce Integration', 'mxchat'); ?></h3>
634 + <div class="video-description">
635 + <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>
636 + <a href="https://www.youtube.com/watch?v=WsqAppHRGdA" target="_blank" rel="noopener" class="video-link">
637 + <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>
638 + <?php echo esc_html__('Watch WooCommerce Integration Tutorial', 'mxchat'); ?>
639 + </a>
515 640 </div>
641 + </div>
516 642
517 - <div class="faq-item">
518 - <h3><?php echo esc_html__('How do I add the chatbot to my site?', 'mxchat'); ?></h3>
519 - <p>
520 - <?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'); ?>
521 - </p>
643 + <div class="tutorial-item">
644 + <h3><?php echo esc_html__('Knowledge Base Setup', 'mxchat'); ?></h3>
645 + <div class="video-description">
646 + <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>
647 + <p><small><?php echo esc_html__('Note: This tutorial uses an older UI, but the process remains the same.', 'mxchat'); ?></small></p>
648 + <a href="https://www.youtube.com/watch?v=8Ztjs66-VTo" target="_blank" rel="noopener" class="video-link">
649 + <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>
650 + <?php echo esc_html__('Watch Knowledge Base Setup Tutorial', 'mxchat'); ?>
651 + </a>
522 652 </div>
653 + </div>
523 654
524 - <div class="faq-item">
525 - <h3><?php echo esc_html__('How does the chatbot use my content to generate responses?', 'mxchat'); ?></h3>
526 - <p>
527 - <?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'); ?>
528 - </p>
655 + <div class="tutorial-item">
656 + <h3><?php echo esc_html__('Toolbar Chat with Documents', 'mxchat'); ?></h3>
657 + <div class="video-description">
658 + <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>
659 + <a href="https://www.youtube.com/watch?v=j_c45WWCTG0" target="_blank" rel="noopener" class="video-link">
660 + <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>
661 + <?php echo esc_html__('Watch Document Chat Tutorial', 'mxchat'); ?>
662 + </a>
529 663 </div>
530 -
531 - <div class="faq-item">
532 - <h3><?php echo esc_html__('Why does the chatbot sometimes make up links or information?', 'mxchat'); ?></h3>
533 - <p>
534 - <?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'); ?>
535 - </p>
664 + </div>
665 +
666 + <div class="tutorial-item">
667 + <h3><?php echo esc_html__('Perplexity Integration', 'mxchat'); ?></h3>
668 + <div class="video-description">
669 + <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>
670 + <a href="https://youtu.be/wpKkbt24-bo" target="_blank" rel="noopener" class="video-link">
671 + <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>
672 + <?php echo esc_html__('Watch Perplexity Integration Tutorial', 'mxchat'); ?>
673 + </a>
536 674 </div>
675 + </div>
537 676
538 - <div class="faq-item">
539 - <h3><?php echo esc_html__('How do intents work in MxChat?', 'mxchat'); ?></h3>
540 - <p>
541 - <?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>.
542 - </p>
677 + <div class="tutorial-item">
678 + <h3><?php echo esc_html__('Brave Search Intent', 'mxchat'); ?></h3>
679 + <div class="video-description">
680 + <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>
681 + <a href="https://www.youtube.com/watch?v=7vDL5H7vToc" target="_blank" rel="noopener" class="video-link">
682 + <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>
683 + <?php echo esc_html__('Watch Brave Search Intent Tutorial', 'mxchat'); ?>
684 + </a>
543 685 </div>
686 + </div>
544 687
545 - <div class="faq-item">
546 - <h3><?php echo esc_html__('How does the Complianz integration work?', 'mxchat'); ?></h3>
547 - <p>
548 - <?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'); ?>
549 - </p>
688 + <div class="tutorial-item">
689 + <h3><?php echo esc_html__('Loops Email Capture', 'mxchat'); ?></h3>
690 + <div class="video-description">
691 + <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>
692 + <p><small><?php echo esc_html__('Note: This tutorial uses an older UI, but the process remains the same.', 'mxchat'); ?></small></p>
693 + <a href="https://www.youtube.com/watch?v=CNgm5TYDyTc" target="_blank" rel="noopener" class="video-link">
694 + <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>
695 + <?php echo esc_html__('Watch Loops Email Capture Tutorial', 'mxchat'); ?>
696 + </a>
550 697 </div>
698 + </div>
551 699
552 - <div class="faq-item">
553 - <h3><?php echo esc_html__('How does the WooCommerce integration work?', 'mxchat'); ?></h3>
554 - <p>
555 - <?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'); ?>
556 - </p>
700 + <div class="tutorial-item">
701 + <h3><?php echo esc_html__('MxChat AI Agent Testing Service', 'mxchat'); ?></h3>
702 + <div class="video-description">
703 + <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>
704 + <p><small><?php echo esc_html__('Note: This tutorial uses an older UI, but the process remains the same.', 'mxchat'); ?></small></p>
705 + <a href="https://www.youtube.com/watch?v=A0jowbpyX54" target="_blank" rel="noopener" class="video-link">
706 + <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>
707 + <?php echo esc_html__('Watch AI Agent Testing Tutorial', 'mxchat'); ?>
708 + </a>
557 709 </div>
710 + </div>
711 + </div>
558 712
559 - <div class="faq-item">
560 - <h3><?php echo esc_html__('Why isn\'t my chatbot responding as expected?', 'mxchat'); ?></h3>
561 - <p>
562 - <?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'); ?>
563 - </p>
564 - <p>
565 - <?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'); ?>
566 - </p>
713 + <div class="support-section">
714 + <h3><?php echo esc_html__('Need Help?', 'mxchat'); ?></h3>
715 + <div class="support-content">
716 + <p>
717 + <?php echo esc_html__('If you\'re having trouble with setup or getting the responses you need, we encourage you to review our', 'mxchat'); ?>
718 + <a href="https://mxchat.ai/documentation/" target="_blank" rel="noopener noreferrer"><?php echo esc_html__('documentation', 'mxchat'); ?></a> <?php echo esc_html__('or', 'mxchat'); ?>
719 + <a href="https://wordpress.org/support/plugin/mxchat-basic/" target="_blank" rel="noopener noreferrer"><?php echo esc_html__('create a support ticket', 'mxchat'); ?></a>.
720 + </p>
721 + <p>
722 + <?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>.
723 + </p>
724 + </div>
725 + </div>
726 + </div>
727 + </div>
567 728 </div>
568 -
569 - <div class="faq-item">
570 - <h3><?php echo esc_html__('What is Loops, and how do I get an API key?', 'mxchat'); ?></h3>
571 - <p>
572 - <?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'); ?>
573 - </p>
574 - </div>
575 -
576 729 </div>
577 730 </div>
578 731 <?php
579 732 }
@@ -581,20 +734,20 @@
581 734
582 735 public function mxchat_create_transcripts_page() {
583 736 global $wpdb;
584 737 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
585 -
738 +
586 739 // Get basic stats
587 740 $total_chats = $wpdb->get_var("SELECT COUNT(DISTINCT session_id) FROM $table_name");
588 741 $total_messages = $wpdb->get_var("SELECT COUNT(*) FROM $table_name");
589 -
742 +
590 743 // Count unique users with detailed breakdown
591 744 $total_users = $wpdb->get_var("
592 - SELECT COUNT(DISTINCT
593 - CASE
745 + SELECT COUNT(DISTINCT
746 + CASE
594 747 WHEN user_email != '' AND user_email IS NOT NULL THEN user_email
595 748 WHEN user_id != 0 THEN CONCAT('user_', user_id)
596 - WHEN user_identifier NOT LIKE 'Tech-Savvy User'
749 + WHEN user_identifier NOT LIKE 'Tech-Savvy User'
597 750 AND user_identifier NOT LIKE 'Detail-Oriented User'
598 751 AND user_identifier NOT LIKE 'Language Learner'
599 752 AND user_identifier NOT LIKE 'Casual Browser'
600 753 AND user_identifier NOT LIKE 'Policy Enforcer'
@@ -602,11 +755,11 @@
602 755 AND user_identifier NOT LIKE 'Loyalty Member'
603 756 AND user_identifier NOT LIKE 'Gift Buyer'
604 757 AND user_identifier NOT LIKE 'Parent or Caregiver'
605 758 THEN user_identifier
606 - ELSE session_id
759 + ELSE session_id
607 760 END
608 - )
761 + )
609 762 FROM $table_name
610 763 WHERE role != 'assistant'
611 764 ");
612 765
@@ -611,16 +764,16 @@
611 764 ");
612 765
613 766 // Get user type breakdown
614 767 $registered_users = $wpdb->get_var("
615 - SELECT COUNT(DISTINCT user_email)
616 - FROM $table_name
768 + SELECT COUNT(DISTINCT user_email)
769 + FROM $table_name
617 770 WHERE user_email != '' AND user_email IS NOT NULL
618 771 ");
619 772
620 773 $guest_users = $wpdb->get_var("
621 - SELECT COUNT(DISTINCT user_identifier)
622 - FROM $table_name
774 + SELECT COUNT(DISTINCT user_identifier)
775 + FROM $table_name
623 776 WHERE (user_email = '' OR user_email IS NULL)
624 777 AND role != 'assistant'
625 778 AND user_identifier NOT LIKE 'Tech-Savvy User'
626 779 AND user_identifier NOT LIKE 'Detail-Oriented User'
@@ -634,10 +787,10 @@
634 787 ");
635 788
636 789 // Get agent test messages count
637 790 $agent_tests = $wpdb->get_var("
638 - SELECT COUNT(DISTINCT session_id)
639 - FROM $table_name
791 + SELECT COUNT(DISTINCT session_id)
792 + FROM $table_name
640 793 WHERE user_identifier IN (
641 794 'Tech-Savvy User',
642 795 'Detail-Oriented User',
643 796 'Language Learner',
@@ -682,15 +835,15 @@
682 835 <div class="stat-content">
683 836 <span class="stat-value"><?php echo esc_html($total_users); ?></span>
684 837 <span class="stat-label"><?php esc_html_e('Unique Users', 'mxchat'); ?></span>
685 838 <span class="stat-sublabel">
686 - <?php
839 + <?php
687 840 echo sprintf(
688 841 esc_html__('%d registered, %d guests, %d agent tests', 'mxchat'),
689 842 $registered_users,
690 843 $guest_users,
691 844 $agent_tests
692 - );
845 + );
693 846 ?>
694 847 </span>
695 848 </div>
696 849 </div>
@@ -738,10 +891,10 @@
738 891 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
739 892
740 893 // Get all transcripts ordered by session and timestamp
741 894 $results = $wpdb->get_results(
742 - "SELECT session_id, user_email, user_identifier, role, message, timestamp
743 - FROM {$table_name}
895 + "SELECT session_id, user_email, user_identifier, role, message, timestamp
896 + FROM {$table_name}
744 897 ORDER BY session_id, timestamp ASC"
745 898 );
746 899
747 900 if (empty($results)) {
@@ -1000,10 +1153,39 @@
1000 1153 <div class="mxchat-content">
1001 1154 <!-- Import Settings Card -->
1002 1155 <div class="mxchat-card">
1003 1156 <h2><?php esc_html_e('Knowledge Import Settings', 'mxchat'); ?></h2>
1004 - <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>
1157 +<?php
1158 +// Check if the appropriate embedding API key exists
1159 +$embedding_model = isset($this->options['embedding_model']) ? esc_attr($this->options['embedding_model']) : 'text-embedding-ada-002';
1160 +$has_openai_key = !empty($this->options['api_key']);
1161 +$has_voyage_key = !empty($this->options['voyage_api_key']);
1005 1162
1163 +// Determine if they have the needed API key for their selected embedding model
1164 +$has_required_key = false;
1165 +$required_key_type = '';
1166 +
1167 +if (strpos($embedding_model, 'text-embedding-') !== false && $has_openai_key) {
1168 + $has_required_key = true;
1169 + $required_key_type = 'OpenAI';
1170 +} elseif (strpos($embedding_model, 'voyage-') !== false && $has_voyage_key) {
1171 + $has_required_key = true;
1172 + $required_key_type = 'Voyage AI';
1173 +} elseif (strpos($embedding_model, 'text-embedding-') !== false) {
1174 + $required_key_type = 'OpenAI';
1175 +} elseif (strpos($embedding_model, 'voyage-') !== false) {
1176 + $required_key_type = 'Voyage AI';
1177 +}
1178 +?>
1179 +
1180 +<div class="mxchat-knowledge-warning <?php echo $has_required_key ? 'success' : 'warning'; ?>">
1181 + <?php if ($has_required_key): ?>
1182 + <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>
1183 + <?php else: ?>
1184 + <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>
1185 + <?php endif; ?>
1186 +</div>
1187 +
1006 1188 <!-- Tab Contents -->
1007 1189 <div class="mxchat-tab-contents">
1008 1190 <!-- Default Database Tab -->
1009 1191 <div id="default-db" class="mxchat-tab-content active">
@@ -1028,8 +1210,9 @@
1028 1210 <span class="mxchat-toggle-label">
1029 1211 <?php esc_html_e('Auto-sync Posts', 'mxchat'); ?>
1030 1212 </span>
1031 1213 </div>
1214 +
1032 1215 <div class="mxchat-toggle-container">
1033 1216 <label class="mxchat-toggle-switch">
1034 1217 <input type="checkbox"
1035 1218 name="mxchat_auto_sync_pages"
@@ -1042,8 +1225,64 @@
1042 1225 <span class="mxchat-toggle-label">
1043 1226 <?php esc_html_e('Auto-sync Pages', 'mxchat'); ?>
1044 1227 </span>
1045 1228 </div>
1229 +
1230 +
1231 +
1232 +
1233 + <!-- Replace the existing custom post types section with this code -->
1234 +<div class="mxchat-section-content">
1235 + <div class="mxchat-custom-post-types-header">
1236 + <button id="mxchat-custom-post-types-toggle" class="mxchat-button-secondary">
1237 + <?php esc_html_e('Advanced Custom Post Sync Settings', 'mxchat'); ?>
1238 + <span class="mxchat-toggle-icon">▼</span>
1239 + </button>
1240 + </div>
1241 +
1242 + <div id="mxchat-custom-post-types-container" class="mxchat-custom-post-types-container" style="display: none;">
1243 + <h3><?php esc_html_e('Sync Custom Post Types', 'mxchat'); ?></h3>
1244 + <p><?php esc_html_e('Select additional custom post types to automatically sync with the chatbot.', 'mxchat'); ?></p>
1245 +
1246 + <div class="mxchat-custom-post-types">
1247 + <?php
1248 + $post_types = $this->get_public_post_types();
1249 +
1250 + // Skip post and page as they're handled separately
1251 + unset($post_types['post']);
1252 + unset($post_types['page']);
1253 +
1254 + if (!empty($post_types)) {
1255 + foreach ($post_types as $post_type => $label) {
1256 + $option_name = 'mxchat_auto_sync_' . $post_type;
1257 + $is_enabled = get_option($option_name, '0');
1258 + ?>
1259 + <div class="mxchat-toggle-container">
1260 + <label class="mxchat-toggle-switch">
1261 + <input type="checkbox"
1262 + name="<?php echo esc_attr($option_name); ?>"
1263 + class="mxchat-autosave-field"
1264 + value="1"
1265 + data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>"
1266 + <?php checked($is_enabled, '1'); ?>>
1267 + <span class="mxchat-toggle-slider"></span>
1268 + </label>
1269 + <span class="mxchat-toggle-label">
1270 + <?php echo esc_html($label); ?> (<?php echo esc_html($post_type); ?>)
1271 + </span>
1272 + </div>
1273 + <?php
1274 + }
1275 + } else {
1276 + echo '<p>' . esc_html__('No custom post types found.', 'mxchat') . '</p>';
1277 + }
1278 + ?>
1279 + </div>
1280 + </div>
1281 +</div>
1282 +
1283 +
1284 +
1046 1285 </div>
1047 1286 </div>
1048 1287 </div>
1049 1288
@@ -1088,85 +1327,177 @@
1088 1327 </div>
1089 1328 <?php endif; ?>
1090 1329
1091 1330 <!-- Processing Status -->
1092 - <?php if ($pdf_status && $pdf_status['status'] !== 'complete') : ?>
1093 - <div class="mxchat-status-card">
1094 - <div class="mxchat-status-header">
1095 - <h4><?php esc_html_e('PDF Processing Status', 'mxchat'); ?></h4>
1096 - <?php if ($is_processing) : ?>
1097 - <form method="post" class="mxchat-stop-form"
1098 - action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_stop_processing')); ?>">
1099 - <?php wp_nonce_field('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce'); ?>
1100 - <button type="submit" name="stop_processing" class="mxchat-button-secondary">
1101 - <?php esc_html_e('Stop Processing', 'mxchat'); ?>
1102 - </button>
1103 - </form>
1104 - <?php endif; ?>
1105 - </div>
1106 - <div class="mxchat-progress-bar">
1107 - <div class="mxchat-progress-fill" style="width: <?php echo esc_attr($pdf_status['percentage']); ?>%"></div>
1108 - </div>
1109 - <div class="mxchat-status-details">
1110 - <p><?php printf(
1111 - esc_html__('Progress: %1$d of %2$d pages (%3$d%%)', 'mxchat'),
1112 - absint($pdf_status['processed_pages']),
1113 - absint($pdf_status['total_pages']),
1114 - absint($pdf_status['percentage'])
1115 - ); ?></p>
1116 - <p><?php printf(
1117 - esc_html__('Status: %s', 'mxchat'),
1118 - esc_html(ucfirst($pdf_status['status']))
1119 - ); ?></p>
1120 - <p><?php printf(
1121 - esc_html__('Last update: %s', 'mxchat'),
1122 - esc_html($pdf_status['last_update'])
1123 - ); ?></p>
1124 - </div>
1125 - </div>
1126 - <?php endif; ?>
1331 +<?php if ($pdf_status && $pdf_status['status'] !== 'complete') : ?>
1332 + <div class="mxchat-status-card">
1333 + <div class="mxchat-status-header">
1334 + <h4><?php esc_html_e('PDF Processing Status', 'mxchat'); ?></h4>
1335 + <?php if ($is_processing) : ?>
1336 + <form method="post" class="mxchat-stop-form"
1337 + action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_stop_processing')); ?>">
1338 + <?php wp_nonce_field('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce'); ?>
1339 + <button type="submit" name="stop_processing" class="mxchat-button-secondary">
1340 + <?php esc_html_e('Stop Processing', 'mxchat'); ?>
1341 + </button>
1342 + </form>
1343 + <?php endif; ?>
1344 +
1345 + <?php if ($pdf_status['status'] === 'error') : ?>
1346 + <span class="mxchat-status-badge mxchat-status-failed"><?php esc_html_e('Error', 'mxchat'); ?></span>
1347 + <?php endif; ?>
1348 + </div>
1349 + <div class="mxchat-progress-bar">
1350 + <div class="mxchat-progress-fill" style="width: <?php echo esc_attr($pdf_status['percentage']); ?>%"></div>
1351 + </div>
1352 + <div class="mxchat-status-details">
1353 + <p><?php printf(
1354 + esc_html__('Progress: %1$d of %2$d pages (%3$d%%)', 'mxchat'),
1355 + absint($pdf_status['processed_pages']),
1356 + absint($pdf_status['total_pages']),
1357 + absint($pdf_status['percentage'])
1358 + ); ?></p>
1359 + <p><?php printf(
1360 + esc_html__('Status: %s', 'mxchat'),
1361 + esc_html(ucfirst($pdf_status['status']))
1362 + ); ?></p>
1363 + <p><?php printf(
1364 + esc_html__('Last update: %s', 'mxchat'),
1365 + esc_html($pdf_status['last_update'])
1366 + ); ?></p>
1367 +
1368 + <?php if (!empty($pdf_status['error'])) : ?>
1369 + <div class="mxchat-error-notice">
1370 + <p class="error"><?php echo esc_html($pdf_status['error']); ?></p>
1371 + </div>
1372 + <?php endif; ?>
1373 + </div>
1374 + </div>
1375 +<?php endif; ?>
1127 1376
1128 - <?php if ($sitemap_status && $sitemap_status['status'] !== 'complete') : ?>
1129 - <div class="mxchat-status-card">
1130 - <div class="mxchat-status-header">
1131 - <h4><?php esc_html_e('Sitemap Processing Status (Refresh for update)', 'mxchat'); ?></h4>
1132 - <?php if ($is_processing) : ?>
1133 - <form method="post" class="mxchat-stop-form"
1134 - action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_stop_processing')); ?>">
1135 - <?php wp_nonce_field('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce'); ?>
1136 - <button type="submit" name="stop_processing" class="mxchat-button-secondary">
1137 - <?php esc_html_e('Stop Processing', 'mxchat'); ?>
1138 - </button>
1139 - </form>
1140 - <?php endif; ?>
1141 - </div>
1142 - <div class="mxchat-progress-bar">
1143 - <div class="mxchat-progress-fill" style="width: <?php echo esc_attr($sitemap_status['percentage']); ?>%"></div>
1144 - </div>
1145 - <div class="mxchat-status-details">
1146 - <p><?php printf(
1147 - esc_html__('Progress: %1$d of %2$d URLs (%3$d%%)', 'mxchat'),
1148 - absint($sitemap_status['processed_urls']),
1149 - absint($sitemap_status['total_urls']),
1150 - absint($sitemap_status['percentage'])
1151 - ); ?></p>
1152 - <?php if (!empty($sitemap_status['error']) || !empty($sitemap_status['last_error'])) : ?>
1153 - <div class="mxchat-error-notice">
1154 - <?php if (!empty($sitemap_status['error'])) : ?>
1155 - <p class="error"><?php echo esc_html($sitemap_status['error']); ?></p>
1156 - <?php endif; ?>
1157 - <?php if (!empty($sitemap_status['last_error'])) : ?>
1158 - <p class="last-error"><?php echo esc_html__('Last error:', 'mxchat') . ' ' . esc_html($sitemap_status['last_error']); ?></p>
1159 - <?php endif; ?>
1160 - </div>
1161 - <?php endif; ?>
1162 - </div>
1163 - </div>
1164 - <?php endif; ?>
1377 +
1378 +<!-- Single URL Submission Status -->
1379 +<?php
1380 +// Get single URL status
1381 +$single_url_status = $this->get_single_url_status();
1382 +$is_active_processing =
1383 + ($sitemap_status && $sitemap_status['status'] === 'processing') ||
1384 + ($pdf_status && $pdf_status['status'] === 'processing');
1385 +?>
1386 +
1387 +<div id="mxchat-single-url-status-container" <?php echo $is_active_processing ? 'style="display:none;"' : ''; ?>>
1388 + <?php if ($single_url_status && !$is_active_processing) : ?>
1389 + <div class="mxchat-status-card">
1390 + <div class="mxchat-status-header">
1391 + <h4><?php esc_html_e('Last URL Submission', 'mxchat'); ?></h4>
1392 + <?php if ($single_url_status['status'] === 'failed') : ?>
1393 + <span class="mxchat-status-badge mxchat-status-failed"><?php esc_html_e('Failed', 'mxchat'); ?></span>
1394 + <?php else : ?>
1395 + <span class="mxchat-status-badge mxchat-status-success"><?php esc_html_e('Success', 'mxchat'); ?></span>
1396 + <?php endif; ?>
1397 + </div>
1398 + <div class="mxchat-status-details">
1399 + <p><strong><?php esc_html_e('URL:', 'mxchat'); ?></strong>
1400 + <a href="<?php echo esc_url($single_url_status['url']); ?>" target="_blank">
1401 + <?php echo esc_html(strlen($single_url_status['url']) > 60 ? substr($single_url_status['url'], 0, 57) . '...' : $single_url_status['url']); ?>
1402 + </a>
1403 + </p>
1404 + <p><strong><?php esc_html_e('Submitted:', 'mxchat'); ?></strong> <?php echo esc_html($single_url_status['human_time']); ?></p>
1405 +
1406 + <?php if ($single_url_status['status'] === 'failed' && !empty($single_url_status['error'])) : ?>
1407 + <div class="mxchat-error-notice">
1408 + <p class="error"><?php echo esc_html($single_url_status['error']); ?></p>
1165 1409 </div>
1410 + <?php endif; ?>
1166 1411
1412 + <?php if ($single_url_status['status'] === 'complete') : ?>
1413 + <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>
1414 + <p><strong><?php esc_html_e('Embedding Dimensions:', 'mxchat'); ?></strong> <?php echo esc_html($single_url_status['embedding_dimensions']); ?></p>
1415 + <?php endif; ?>
1416 + </div>
1417 + </div>
1418 + <?php endif; ?>
1419 +</div>
1167 1420
1421 +
1422 +<?php if ($sitemap_status && $sitemap_status['status'] !== 'complete') : ?>
1423 + <div class="mxchat-status-card">
1424 + <div class="mxchat-status-header">
1425 + <h4><?php esc_html_e('Sitemap Processing Status', 'mxchat'); ?></h4>
1426 + <?php if ($is_processing) : ?>
1427 + <form method="post" class="mxchat-stop-form"
1428 + action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_stop_processing')); ?>">
1429 + <?php wp_nonce_field('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce'); ?>
1430 + <button type="submit" name="stop_processing" class="mxchat-button-secondary">
1431 + <?php esc_html_e('Stop Processing', 'mxchat'); ?>
1432 + </button>
1433 + </form>
1434 + <?php endif; ?>
1435 + </div>
1436 + <div class="mxchat-progress-bar">
1437 + <div class="mxchat-progress-fill" style="width: <?php echo esc_attr($sitemap_status['percentage']); ?>%"></div>
1438 + </div>
1439 + <div class="mxchat-status-details">
1440 + <p><?php printf(
1441 + esc_html__('Progress: %1$d of %2$d URLs (%3$d%%)', 'mxchat'),
1442 + absint($sitemap_status['processed_urls']),
1443 + absint($sitemap_status['total_urls']),
1444 + absint($sitemap_status['percentage'])
1445 + ); ?></p>
1446 + <?php if (!empty($sitemap_status['error']) || !empty($sitemap_status['last_error'])) : ?>
1447 + <div class="mxchat-error-notice">
1448 + <?php if (!empty($sitemap_status['error'])) : ?>
1449 + <p class="error"><?php echo esc_html($sitemap_status['error']); ?></p>
1450 + <?php endif; ?>
1451 + <?php if (!empty($sitemap_status['last_error'])) : ?>
1452 + <p class="last-error"><?php echo esc_html__('Last error:', 'mxchat') . ' ' . esc_html($sitemap_status['last_error']); ?></p>
1453 + <?php endif; ?>
1168 1454 </div>
1455 + <?php endif; ?>
1456 +
1457 + <?php if (!empty($sitemap_status['failed_urls']) && $sitemap_status['failed_urls'] > 0) : ?>
1458 + <div class="mxchat-failed-urls">
1459 + <h5><?php echo sprintf(esc_html__('Failed URLs (%d)', 'mxchat'), absint($sitemap_status['failed_urls'])); ?></h5>
1460 + <?php if (!empty($sitemap_status['failed_urls_list'])) : ?>
1461 + <div class="mxchat-failed-urls-list" style="max-height: 200px; overflow-y: auto; margin-top: 10px;">
1462 + <table class="widefat striped">
1463 + <thead>
1464 + <tr>
1465 + <th><?php esc_html_e('URL', 'mxchat'); ?></th>
1466 + <th><?php esc_html_e('Error', 'mxchat'); ?></th>
1467 + <th><?php esc_html_e('Time', 'mxchat'); ?></th>
1468 + </tr>
1469 + </thead>
1470 + <tbody>
1471 + <?php foreach ($sitemap_status['failed_urls_list'] as $failed_url) : ?>
1472 + <tr>
1473 + <td style="word-break: break-all;">
1474 + <a href="<?php echo esc_url($failed_url['url']); ?>" target="_blank" rel="noopener noreferrer">
1475 + <?php echo esc_html(strlen($failed_url['url']) > 60 ? substr($failed_url['url'], 0, 57) . '...' : $failed_url['url']); ?>
1476 + </a>
1477 + </td>
1478 + <td><?php echo esc_html($failed_url['error']); ?></td>
1479 + <td><?php echo esc_html(human_time_diff($failed_url['time'], time()) . ' ' . __('ago', 'mxchat')); ?></td>
1480 + </tr>
1481 + <?php endforeach; ?>
1482 + </tbody>
1483 + </table>
1484 + </div>
1485 + <?php endif; ?>
1486 + </div>
1487 + <?php endif; ?>
1488 + </div>
1489 + </div>
1490 +<?php endif; ?>
1491 +
1492 +
1493 + </div>
1494 +
1495 +
1496 + </div>
1497 +
1498 +
1499 +
1169 1500 </div>
1170 1501
1171 1502 <!-- Direct Content Submission Card -->
1172 1503 <div class="mxchat-card">
@@ -1248,16 +1579,19 @@
1248 1579 <?php echo esc_textarea($prompt->article_content); ?>
1249 1580 </textarea>
1250 1581 </td>
1251 1582 <td class="mxchat-url-cell">
1252 - <?php if (!empty($prompt->source_url)) : ?>
1253 - <a href="<?php echo esc_url($prompt->source_url); ?>" target="_blank">
1254 - <span class="dashicons dashicons-external"></span>
1255 - <?php esc_html_e('View Source', 'mxchat'); ?>
1256 - </a>
1257 - <?php else : ?>
1258 - <span class="mxchat-na"><?php esc_html_e('N/A', 'mxchat'); ?></span>
1259 - <?php endif; ?>
1583 + <div class="url-view">
1584 + <?php if (!empty($prompt->source_url)) : ?>
1585 + <a href="<?php echo esc_url($prompt->source_url); ?>" target="_blank">
1586 + <span class="dashicons dashicons-external"></span>
1587 + <?php esc_html_e('View Source', 'mxchat'); ?>
1588 + </a>
1589 + <?php else : ?>
1590 + <span class="mxchat-na"><?php esc_html_e('N/A', 'mxchat'); ?></span>
1591 + <?php endif; ?>
1592 + </div>
1593 + <input type="text" class="url-edit" style="display:none;" value="<?php echo esc_attr($prompt->source_url); ?>" />
1260 1594 </td>
1261 1595 <td class="mxchat-actions-cell">
1262 1596 <button class="mxchat-button-icon edit-button"
1263 1597 data-id="<?php echo esc_attr($prompt->id); ?>">
@@ -1362,43 +1696,61 @@
1362 1696 }
1363 1697
1364 1698
1365 1699 public function mxchat_generate_embedding($text) {
1366 - //error_log('Starting embedding generation for text: ' . substr($text, 0, 100) . '...');
1367 -
1700 + // Enable detailed logging for debugging
1701 + //error_log('[MXCHAT-EMBED] Starting embedding generation. Text length: ' . strlen($text) . ' bytes');
1702 + //error_log('[MXCHAT-EMBED] Text preview: ' . substr($text, 0, 100) . '...');
1703 +
1368 1704 $options = get_option('mxchat_options');
1369 1705 $selected_model = $options['embedding_model'] ?? 'text-embedding-ada-002';
1370 - //error_log('Selected embedding model: ' . $selected_model);
1371 -
1706 + //error_log('[MXCHAT-EMBED] Selected embedding model: ' . $selected_model);
1707 +
1372 1708 // Determine provider and endpoint
1373 1709 if (strpos($selected_model, 'voyage') === 0) {
1374 1710 $api_key = $options['voyage_api_key'] ?? '';
1375 1711 $endpoint = 'https://api.voyageai.com/v1/embeddings';
1712 + $provider_name = 'Voyage AI';
1713 + //error_log('[MXCHAT-EMBED] Using Voyage AI API');
1376 1714 } else {
1377 1715 $api_key = $options['api_key'] ?? '';
1378 1716 $endpoint = 'https://api.openai.com/v1/embeddings';
1717 + $provider_name = 'OpenAI';
1718 + //error_log('[MXCHAT-EMBED] Using OpenAI API');
1379 1719 }
1380 - //error_log('Using endpoint: ' . $endpoint);
1381 -
1720 +
1721 + //error_log('[MXCHAT-EMBED] Using endpoint: ' . $endpoint);
1722 +
1382 1723 if (empty($api_key)) {
1383 - //error_log('Error: API key not configured');
1384 - return null;
1724 + $error_message = sprintf('Missing %s API key. Please configure your API key in the MxChat settings.', $provider_name);
1725 + //error_log('[MXCHAT-EMBED] Error: ' . $error_message);
1726 + return $error_message;
1385 1727 }
1386 -
1728 +
1729 + // Check if text is too long (for OpenAI, roughly estimate tokens as words/0.75)
1730 + $estimated_tokens = ceil(str_word_count($text) / 0.75);
1731 + //error_log('[MXCHAT-EMBED] Estimated token count: ~' . $estimated_tokens);
1732 +
1733 + if ($estimated_tokens > 8000 && strpos($selected_model, 'voyage') === false) {
1734 + //error_log('[MXCHAT-EMBED] Warning: Text may exceed OpenAI token limits (8K for most models)');
1735 + // Consider truncating text here
1736 + }
1737 +
1387 1738 // Prepare request body
1388 1739 $request_body = array(
1389 1740 'model' => $selected_model,
1390 1741 'input' => $text
1391 1742 );
1392 -
1743 +
1393 1744 // Add output_dimension for voyage-3-large model
1394 1745 if ($selected_model === 'voyage-3-large') {
1395 1746 $request_body['output_dimension'] = 2048;
1396 1747 }
1397 -
1398 - //error_log('Request body prepared: ' . wp_json_encode($request_body));
1399 -
1748 +
1749 + //error_log('[MXCHAT-EMBED] Request prepared with model: ' . $selected_model);
1750 +
1400 1751 // Make API request
1752 + //error_log('[MXCHAT-EMBED] Sending API request to: ' . $endpoint);
1401 1753 $response = wp_remote_post($endpoint, array(
1402 1754 'body' => wp_json_encode($request_body),
1403 1755 'headers' => array(
1404 1756 'Authorization' => 'Bearer ' . $api_key,
@@ -1403,42 +1755,88 @@
1403 1755 'headers' => array(
1404 1756 'Authorization' => 'Bearer ' . $api_key,
1405 1757 'Content-Type' => 'application/json'
1406 1758 ),
1407 - 'timeout' => 30
1759 + 'timeout' => 60 // Increased timeout for large inputs
1408 1760 ));
1409 -
1761 +
1410 1762 // Handle wp_remote_post errors
1411 1763 if (is_wp_error($response)) {
1412 - //error_log('WP Remote Post Error: ' . $response->get_error_message());
1413 - return null;
1764 + $error_message = $response->get_error_message();
1765 + //error_log('[MXCHAT-EMBED] WP Remote Post Error: ' . $error_message);
1766 + return 'Connection error: ' . $error_message;
1414 1767 }
1415 -
1768 +
1416 1769 // Get and check HTTP response code
1417 1770 $http_code = wp_remote_retrieve_response_code($response);
1418 - //error_log('API Response Code: ' . $http_code);
1419 -
1771 + //error_log('[MXCHAT-EMBED] API Response Code: ' . $http_code);
1772 +
1420 1773 if ($http_code !== 200) {
1421 - //error_log('API Error Response Body: ' . wp_remote_retrieve_body($response));
1422 - return null;
1774 + $error_body = wp_remote_retrieve_body($response);
1775 + //error_log('[MXCHAT-EMBED] API Error Response Body: ' . $error_body);
1776 +
1777 + // Try to parse error for more details
1778 + $error_json = json_decode($error_body, true);
1779 + if (json_last_error() === JSON_ERROR_NONE && isset($error_json['error'])) {
1780 + $error_type = $error_json['error']['type'] ?? 'unknown';
1781 + $error_message = $error_json['error']['message'] ?? 'No message';
1782 + //error_log('[MXCHAT-EMBED] API Error Type: ' . $error_type);
1783 + //error_log('[MXCHAT-EMBED] API Error Message: ' . $error_message);
1784 +
1785 + // Customize error message for common API errors
1786 + if ($error_type === 'invalid_request_error' && strpos($error_message, 'API key') !== false) {
1787 + $error_message = sprintf('Invalid %s API key. Please check your API key in the MxChat settings.', $provider_name);
1788 + } elseif ($error_type === 'authentication_error') {
1789 + $error_message = sprintf('%s authentication failed. Please verify your API key in the MxChat settings.', $provider_name);
1790 + }
1791 +
1792 + //error_log('[MXCHAT-EMBED] Returning error: ' . $error_message);
1793 + return $error_message;
1794 + }
1795 +
1796 + $error_message = sprintf("API Error (HTTP %d): Unable to generate embedding", $http_code);
1797 + //error_log('[MXCHAT-EMBED] Returning error: ' . $error_message);
1798 + return $error_message;
1423 1799 }
1424 -
1800 +
1425 1801 // Parse response body
1426 - $response_data = json_decode(wp_remote_retrieve_body($response), true);
1427 -
1802 + $response_body = wp_remote_retrieve_body($response);
1803 + //error_log('[MXCHAT-EMBED] Received response length: ' . strlen($response_body) . ' bytes');
1804 +
1805 + $response_data = json_decode($response_body, true);
1806 +
1428 1807 if (json_last_error() !== JSON_ERROR_NONE) {
1429 - //error_log('JSON Parse Error: ' . json_last_error_msg());
1430 - return null;
1808 + $error = json_last_error_msg();
1809 + //error_log('[MXCHAT-EMBED] JSON Parse Error: ' . $error);
1810 + //error_log('[MXCHAT-EMBED] Response preview: ' . substr($response_body, 0, 200));
1811 + return "Failed to parse API response: $error";
1431 1812 }
1432 -
1813 +
1433 1814 // Both APIs use the same structure, so we can extract the embedding the same way
1434 1815 if (isset($response_data['data'][0]['embedding'])) {
1435 - //error_log('Successfully extracted embedding');
1436 - //error_log('Embedding dimension: ' . count($response_data['data'][0]['embedding']));
1816 + $embedding_dimensions = count($response_data['data'][0]['embedding']);
1817 + //error_log('[MXCHAT-EMBED] Successfully extracted embedding with ' . $embedding_dimensions . ' dimensions');
1818 +
1819 + // Check if embedding dimensions are as expected
1820 + if (($selected_model === 'text-embedding-ada-002' && $embedding_dimensions !== 1536) ||
1821 + ($selected_model === 'voyage-3-large' && $embedding_dimensions !== 2048)) {
1822 + //error_log('[MXCHAT-EMBED] Warning: Unexpected embedding dimensions');
1823 + }
1824 +
1437 1825 return $response_data['data'][0]['embedding'];
1438 1826 } else {
1439 - //error_log('Error: No embedding found in response. Response structure: ' . wp_json_encode($response_data));
1440 - return null;
1827 + //error_log('[MXCHAT-EMBED] Error: No embedding found in response');
1828 + //error_log('[MXCHAT-EMBED] Response structure: ' . wp_json_encode(array_keys($response_data)));
1829 +
1830 + if (isset($response_data['error'])) {
1831 + $error_message = "API Error in response: " . wp_json_encode($response_data['error']);
1832 + //error_log('[MXCHAT-EMBED] ' . $error_message);
1833 + return $error_message;
1834 + }
1835 +
1836 + $error_message = "Invalid API response format: No embedding found";
1837 + //error_log('[MXCHAT-EMBED] ' . $error_message);
1838 + return $error_message;
1441 1839 }
1442 1840 }
1443 1841
1444 1842
@@ -1530,45 +1928,106 @@
1530 1928 }
1531 1929
1532 1930
1533 1931
1534 -// Add this method to handle post updates
1932 +/**
1933 + * Get all public post types
1934 + *
1935 + * @return array Associative array of post type names and labels
1936 + */
1937 +private function get_public_post_types() {
1938 + $post_types = get_post_types(array('public' => true), 'objects');
1939 + $post_type_options = array();
1940 +
1941 + foreach ($post_types as $post_type) {
1942 + $post_type_options[$post_type->name] = $post_type->label;
1943 + }
1944 +
1945 + return $post_type_options;
1946 +}
1947 +
1948 +/**
1949 + * Handle post updates and process content for the chatbot
1950 + *
1951 + * @param int $post_id The ID of the post being saved
1952 + * @param WP_Post $post The post object
1953 + * @param bool $update Whether this is an existing post being updated
1954 + */
1535 1955 public function handle_post_update($post_id, $post, $update) {
1536 1956 // Basic validation checks
1537 1957 if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE || wp_is_post_revision($post_id)) {
1538 1958 return;
1539 1959 }
1540 -
1960 +
1541 1961 // Only process published content
1542 - if (!in_array($post->post_status, array('publish'))) {
1962 + if ($post->post_status !== 'publish') {
1543 1963 return;
1544 1964 }
1545 -
1965 +
1966 + $post_type = $post->post_type;
1967 +
1546 1968 // Check if sync is enabled for this post type
1547 - $post_type = $post->post_type;
1548 - if (!in_array($post_type, array('post', 'page'))) {
1549 - return;
1969 + $should_sync = false;
1970 +
1971 + // Check built-in post types first
1972 + if ($post_type === 'post' && get_option('mxchat_auto_sync_posts') === '1') {
1973 + $should_sync = true;
1974 + } else if ($post_type === 'page' && get_option('mxchat_auto_sync_pages') === '1') {
1975 + $should_sync = true;
1976 + } else {
1977 + // Check custom post types
1978 + $option_name = 'mxchat_auto_sync_' . $post_type;
1979 + if (get_option($option_name) === '1') {
1980 + $should_sync = true;
1981 + }
1550 1982 }
1551 -
1552 - $sync_option = ($post_type === 'post') ? 'mxchat_auto_sync_posts' : 'mxchat_auto_sync_pages';
1553 - if (get_option($sync_option) != '1') {
1983 +
1984 + if (!$should_sync) {
1554 1985 return;
1555 1986 }
1556 -
1987 +
1557 1988 // Prepare content and URL
1558 1989 $content = wp_strip_all_tags($post->post_content);
1990 +
1991 + // For custom post types like job_listing, include additional fields
1992 + if ($post_type === 'job_listing') {
1993 + // Add title as it's important for job listings
1994 + $content = $post->post_title . "\n\n" . $content;
1995 +
1996 + // Add job-specific meta if available
1997 + $job_location = get_post_meta($post_id, '_job_location', true);
1998 + if (!empty($job_location)) {
1999 + $content .= "\n\nLocation: " . $job_location;
2000 + }
2001 +
2002 + // Get job type terms
2003 + $job_types = get_the_terms($post_id, 'job_listing_type');
2004 + if (!empty($job_types) && !is_wp_error($job_types)) {
2005 + $types = array();
2006 + foreach ($job_types as $type) {
2007 + $types[] = $type->name;
2008 + }
2009 + $content .= "\n\nJob Type: " . implode(', ', $types);
2010 + }
2011 +
2012 + // Get company name if available
2013 + $company_name = get_post_meta($post_id, '_company_name', true);
2014 + if (!empty($company_name)) {
2015 + $content .= "\n\nCompany: " . $company_name;
2016 + }
2017 + }
2018 +
1559 2019 $url = get_permalink($post_id);
1560 -
2020 +
1561 2021 // Generate embedding vector
1562 2022 $embedding_vector = $this->mxchat_generate_embedding($content);
1563 2023 if (!$embedding_vector) {
1564 2024 return;
1565 2025 }
1566 -
2026 +
1567 2027 // Check for Pinecone addon and its settings
1568 2028 $pinecone_settings = get_option('mxchat_pinecone_addon_options');
1569 2029 $use_pinecone = false;
1570 -
1571 2030 if ($pinecone_settings && is_array($pinecone_settings)) {
1572 2031 // Check if Pinecone is enabled and all required settings are present
1573 2032 $use_pinecone = (
1574 2033 isset($pinecone_settings['mxchat_use_pinecone']) &&
@@ -1578,9 +2037,9 @@
1578 2037 !empty($pinecone_settings['mxchat_pinecone_index']) &&
1579 2038 !empty($pinecone_settings['mxchat_pinecone_environment'])
1580 2039 );
1581 2040 }
1582 -
2041 +
1583 2042 if ($use_pinecone) {
1584 2043 // Use Pinecone
1585 2044 $pinecone_result = $this->store_in_pinecone_main(
1586 2045 $embedding_vector,
@@ -1589,11 +2048,10 @@
1589 2048 $pinecone_settings['mxchat_pinecone_api_key'],
1590 2049 $pinecone_settings['mxchat_pinecone_environment'],
1591 2050 $pinecone_settings['mxchat_pinecone_index']
1592 2051 );
1593 -
1594 2052 if (!$pinecone_result['success']) {
1595 - //error_log('MXChat: Pinecone sync failed - falling back to WordPress DB');
2053 + // Fallback to WordPress DB
1596 2054 $this->store_in_wordpress_db($content, $url, $embedding_vector);
1597 2055 }
1598 2056 } else {
1599 2057 // Fallback to WordPress DB storage
@@ -1598,9 +2056,122 @@
1598 2056 } else {
1599 2057 // Fallback to WordPress DB storage
1600 2058 $this->store_in_wordpress_db($content, $url, $embedding_vector);
1601 2059 }
2060 +
1602 2061 }
2062 +/**
2063 + * Handle deletion of posts from both Pinecone and WordPress DB
2064 + *
2065 + * @param int $post_id The ID of the post being deleted
2066 + * @return void
2067 + */
2068 +public function mxchat_handle_post_delete($post_id) {
2069 + // Get post data before it's deleted
2070 + $post = get_post($post_id);
2071 +
2072 + // Basic validation
2073 + if (!$post || wp_is_post_revision($post_id)) {
2074 + return;
2075 + }
2076 +
2077 + $post_type = $post->post_type;
2078 +
2079 + // Check if sync is enabled for this post type
2080 + $should_sync = false;
2081 +
2082 + // Check built-in post types first
2083 + if ($post_type === 'post' && get_option('mxchat_auto_sync_posts') === '1') {
2084 + $should_sync = true;
2085 + } else if ($post_type === 'page' && get_option('mxchat_auto_sync_pages') === '1') {
2086 + $should_sync = true;
2087 + } else {
2088 + // Check custom post types
2089 + $option_name = 'mxchat_auto_sync_' . $post_type;
2090 + if (get_option($option_name) === '1') {
2091 + $should_sync = true;
2092 + }
2093 + }
2094 +
2095 + if (!$should_sync) {
2096 + return;
2097 + }
2098 +
2099 + // Get the URL before post is deleted
2100 + $source_url = get_permalink($post_id);
2101 + if (!$source_url) {
2102 + //error_log('MXChat: Failed to get permalink for post ' . $post_id);
2103 + return;
2104 + }
2105 +
2106 + // Check for Pinecone addon and its settings
2107 + $pinecone_settings = get_option('mxchat_pinecone_addon_options');
2108 + $use_pinecone = false;
2109 + if ($pinecone_settings && is_array($pinecone_settings)) {
2110 + $use_pinecone = (
2111 + isset($pinecone_settings['mxchat_use_pinecone']) &&
2112 + $pinecone_settings['mxchat_use_pinecone'] === '1' &&
2113 + !empty($pinecone_settings['mxchat_pinecone_api_key']) &&
2114 + !empty($pinecone_settings['mxchat_pinecone_host']) &&
2115 + !empty($pinecone_settings['mxchat_pinecone_index']) &&
2116 + !empty($pinecone_settings['mxchat_pinecone_environment'])
2117 + );
2118 + }
2119 +
2120 + $deletion_successful = false;
2121 +
2122 + if ($use_pinecone) {
2123 + try {
2124 + $pinecone_result = $this->delete_from_pinecone(
2125 + array($source_url),
2126 + $pinecone_settings['mxchat_pinecone_api_key'],
2127 + $pinecone_settings['mxchat_pinecone_environment'],
2128 + $pinecone_settings['mxchat_pinecone_index']
2129 + );
2130 +
2131 + if (!$pinecone_result['success']) {
2132 + //error_log('MXChat: Pinecone deletion failed for URL: ' . $source_url . ' - ' . $pinecone_result['message']);
2133 + } else {
2134 + $deletion_successful = true;
2135 + }
2136 + } catch (Exception $e) {
2137 + //error_log('MXChat: Exception during Pinecone deletion - ' . $e->getMessage());
2138 + }
2139 + }
2140 +
2141 + // Always attempt WordPress DB deletion, regardless of Pinecone status
2142 + try {
2143 + global $wpdb;
2144 + $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
2145 +
2146 + $result = $wpdb->delete(
2147 + $table_name,
2148 + array('source_url' => $source_url),
2149 + array('%s')
2150 + );
2151 +
2152 + if ($result === false) {
2153 + //error_log('MXChat: WordPress DB deletion failed for URL: ' . $source_url);
2154 + } else {
2155 + $deletion_successful = true;
2156 + }
2157 + } catch (Exception $e) {
2158 + //error_log('MXChat: Exception during WordPress DB deletion - ' . $e->getMessage());
2159 + }
2160 +
2161 + if (!$deletion_successful) {
2162 + //error_log('MXChat: Complete deletion failure for post ID: ' . $post_id . ' URL: ' . $source_url);
2163 + } else {
2164 +
2165 + }
2166 +}
2167 +
2168 +
2169 +
2170 +
2171 +
2172 +
2173 +
1603 2174 // Modified storage function to add type field
1604 2175 private function store_in_pinecone_main($embedding_vector, $content, $url, $api_key, $environment, $index_name, $vector_id = null) {
1605 2176 $vector_id = $vector_id ?: md5($url);
1606 2177 $options = get_option('mxchat_pinecone_addon_options');
@@ -1667,8 +2238,10 @@
1667 2238 'success' => true,
1668 2239 'message' => 'Successfully stored in Pinecone'
1669 2240 );
1670 2241 }
2242 +
2243 +
1671 2244 // Helper method for WordPress DB storage
1672 2245 private function store_in_wordpress_db($content, $url, $embedding_vector) {
1673 2246 global $wpdb;
1674 2247 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
@@ -1700,103 +2273,10 @@
1700 2273 array('%s', '%s', '%s', '%s')
1701 2274 );
1702 2275 }
1703 2276 }
1704 -/**
1705 - * Handle deletion of posts and pages from both Pinecone and WordPress DB
1706 - *
1707 - * @param int $post_id The ID of the post being deleted
1708 - * @return void
1709 - */
1710 -public function mxchat_handle_post_delete($post_id) {
1711 - // Get post data before it's deleted
1712 - $post = get_post($post_id);
1713 2277
1714 - // Basic validation
1715 - if (!$post || wp_is_post_revision($post_id)) {
1716 - return;
1717 - }
1718 2278
1719 - // Check post type
1720 - $post_type = $post->post_type;
1721 - if (!in_array($post_type, array('post', 'page'))) {
1722 - return;
1723 - }
1724 -
1725 - // Check if sync is enabled for this post type
1726 - $sync_option = ($post_type === 'post') ? 'mxchat_auto_sync_posts' : 'mxchat_auto_sync_pages';
1727 - if (get_option($sync_option) != '1') {
1728 - return;
1729 - }
1730 -
1731 - // Get the URL before post is deleted
1732 - $source_url = get_permalink($post_id);
1733 - if (!$source_url) {
1734 - //error_log('MXChat: Failed to get permalink for post ' . $post_id);
1735 - return;
1736 - }
1737 -
1738 - // Check for Pinecone addon and its settings
1739 - $pinecone_settings = get_option('mxchat_pinecone_addon_options');
1740 - $use_pinecone = false;
1741 - if ($pinecone_settings && is_array($pinecone_settings)) {
1742 - $use_pinecone = (
1743 - isset($pinecone_settings['mxchat_use_pinecone']) &&
1744 - $pinecone_settings['mxchat_use_pinecone'] === '1' &&
1745 - !empty($pinecone_settings['mxchat_pinecone_api_key']) &&
1746 - !empty($pinecone_settings['mxchat_pinecone_host']) &&
1747 - !empty($pinecone_settings['mxchat_pinecone_index']) &&
1748 - !empty($pinecone_settings['mxchat_pinecone_environment'])
1749 - );
1750 - }
1751 -
1752 - $deletion_successful = false;
1753 -
1754 - if ($use_pinecone) {
1755 - try {
1756 - $pinecone_result = $this->delete_from_pinecone(
1757 - array($source_url),
1758 - $pinecone_settings['mxchat_pinecone_api_key'],
1759 - $pinecone_settings['mxchat_pinecone_environment'],
1760 - $pinecone_settings['mxchat_pinecone_index']
1761 - );
1762 -
1763 - if (!$pinecone_result['success']) {
1764 - //error_log('MXChat: Pinecone deletion failed for URL: ' . $source_url . ' - ' . $pinecone_result['message']);
1765 - } else {
1766 - $deletion_successful = true;
1767 - }
1768 - } catch (Exception $e) {
1769 - //error_log('MXChat: Exception during Pinecone deletion - ' . $e->getMessage());
1770 - }
1771 - }
1772 -
1773 - // Always attempt WordPress DB deletion, regardless of Pinecone status
1774 - try {
1775 - global $wpdb;
1776 - $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
1777 -
1778 - $result = $wpdb->delete(
1779 - $table_name,
1780 - array('source_url' => $source_url),
1781 - array('%s')
1782 - );
1783 -
1784 - if ($result === false) {
1785 - //error_log('MXChat: WordPress DB deletion failed for URL: ' . $source_url);
1786 - } else {
1787 - $deletion_successful = true;
1788 - }
1789 - } catch (Exception $e) {
1790 - //error_log('MXChat: Exception during WordPress DB deletion - ' . $e->getMessage());
1791 - }
1792 -
1793 - if (!$deletion_successful) {
1794 - //error_log('MXChat: Complete deletion failure for post ID: ' . $post_id . ' URL: ' . $source_url);
1795 - }
1796 -}
1797 -
1798 -
1799 2279 public function mxchat_handle_content_submission() {
1800 2280 // Check if the form was submitted and the user has permission.
1801 2281 if (!isset($_POST['submit_content']) || !current_user_can('manage_options')) {
1802 2282 return;
@@ -1968,31 +2448,57 @@
1968 2448 $start_page = absint($status['processed_pages']);
1969 2449 $end_page = min($start_page + $batch_size, $total_pages);
1970 2450
1971 2451 $instance = new self(); // Create an instance of the class
2452 + $options = get_option('mxchat_options');
2453 +
2454 + if (empty($options['api_key'])) {
2455 + throw new Exception('API key is missing or invalid');
2456 + }
1972 2457
1973 2458 for ($i = $start_page; $i < $end_page; $i++) {
1974 2459 $text = $pages[$i]->getText();
2460 +
2461 + if (empty($text)) {
2462 + // Log empty page but continue processing
2463 + //error_log(sprintf('[MXCHAT-PDF] Warning: Empty text on page %d of %s', $i + 1, $pdf_url));
2464 + continue;
2465 + }
2466 +
1975 2467 $sanitized_content = $instance->mxchat_sanitize_content_for_api($text); // Call via instance
1976 2468
1977 - if (!empty($sanitized_content)) {
1978 - $embedding_vector = $instance->mxchat_generate_embedding($sanitized_content); // Call via instance
1979 - if (is_array($embedding_vector)) {
1980 - $metadata = array(
1981 - 'document_type' => 'pdf',
1982 - 'total_pages' => $total_pages,
1983 - 'current_page' => $i + 1,
1984 - 'prev_page' => $i > 0 ? $i : null,
1985 - 'next_page' => $i < ($total_pages - 1) ? $i + 2 : null,
1986 - 'source_url' => $pdf_url
1987 - );
2469 + if (empty($sanitized_content)) {
2470 + // Log empty sanitized content but continue processing
2471 + //error_log(sprintf('[MXCHAT-PDF] Warning: No valid content after sanitization on page %d of %s', $i + 1, $pdf_url));
2472 + continue;
2473 + }
1988 2474
1989 - $content_with_metadata = wp_json_encode($metadata) . "\n---\n" . $sanitized_content;
1990 - $page_url = esc_url($pdf_url . "#page=" . ($i + 1));
2475 + $embedding_vector = $instance->mxchat_generate_embedding($sanitized_content); // Call via instance
2476 +
2477 + if (!is_array($embedding_vector)) {
2478 + // If embedding generation fails, log error and throw exception
2479 + $error_msg = is_string($embedding_vector) ? $embedding_vector : 'Unknown embedding generation error';
2480 + //error_log(sprintf('[MXCHAT-PDF] Error generating embedding for page %d: %s', $i + 1, $error_msg));
2481 + throw new Exception(sprintf('Failed to generate embedding for page %d: %s', $i + 1, $error_msg));
2482 + }
2483 +
2484 + $metadata = array(
2485 + 'document_type' => 'pdf',
2486 + 'total_pages' => $total_pages,
2487 + 'current_page' => $i + 1,
2488 + 'prev_page' => $i > 0 ? $i : null,
2489 + 'next_page' => $i < ($total_pages - 1) ? $i + 2 : null,
2490 + 'source_url' => $pdf_url
2491 + );
1991 2492
1992 - $options = get_option('mxchat_options');
1993 - MxChat_Utils::submit_content_to_db($content_with_metadata, $page_url, $options['api_key']);
1994 - }
2493 + $content_with_metadata = wp_json_encode($metadata) . "\n---\n" . $sanitized_content;
2494 + $page_url = esc_url($pdf_url . "#page=" . ($i + 1));
2495 +
2496 + $db_result = MxChat_Utils::submit_content_to_db($content_with_metadata, $page_url, $options['api_key']);
2497 +
2498 + if (is_wp_error($db_result)) {
2499 + throw new Exception(sprintf('Failed to store content in database for page %d: %s',
2500 + $i + 1, $db_result->get_error_message()));
1995 2501 }
1996 2502
1997 2503 // Update progress with sanitized data
1998 2504 $status['processed_pages'] = absint($i + 1);
@@ -2018,16 +2524,36 @@
2018 2524 }
2019 2525 }
2020 2526
2021 2527 } catch (\Exception $e) {
2022 - $status['status'] = 'error';
2023 - $status['error'] = sanitize_text_field($e->getMessage());
2528 + //error_log(sprintf('[MXCHAT-PDF] Error processing PDF: %s', $e->getMessage()));
2529 +
2530 + // Get current status to update it
2531 + $status_key = sanitize_key('mxchat_pdf_status_' . md5($pdf_url));
2532 + $status = get_transient($status_key);
2533 +
2534 + if (!$status || !is_array($status)) {
2535 + $status = array(
2536 + 'total_pages' => $total_pages,
2537 + 'processed_pages' => 0,
2538 + 'status' => 'error',
2539 + 'error' => sanitize_text_field($e->getMessage()),
2540 + 'last_update' => time()
2541 + );
2542 + } else {
2543 + $status['status'] = 'error';
2544 + $status['error'] = sanitize_text_field($e->getMessage());
2545 + $status['last_update'] = time();
2546 + }
2547 +
2024 2548 set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
2549 +
2025 2550 if (file_exists($pdf_path)) {
2026 2551 wp_delete_file($pdf_path);
2027 2552 }
2028 2553 }
2029 2554 }
2555 +
2030 2556 public function get_pdf_processing_status($pdf_url) {
2031 2557 $pdf_url = esc_url_raw($pdf_url);
2032 2558 $status = get_transient(sanitize_key('mxchat_pdf_status_' . md5($pdf_url)));
2033 2559
@@ -2034,29 +2560,57 @@
2034 2560 if (!$status || !is_array($status)) {
2035 2561 return false;
2036 2562 }
2037 2563
2038 - return array(
2564 + // Check for stalled processing (no updates for 5 minutes)
2565 + if ($status['status'] === 'processing' && (time() - absint($status['last_update'])) > 300) {
2566 + $status['status'] = 'error';
2567 + $status['error'] = __('PDF processing appears to be stalled. No updates for over 5 minutes.', 'mxchat');
2568 +
2569 + // Save the updated status
2570 + set_transient(
2571 + sanitize_key('mxchat_pdf_status_' . md5($pdf_url)),
2572 + array_map('sanitize_text_field', $status),
2573 + DAY_IN_SECONDS
2574 + );
2575 + }
2576 +
2577 + $result = array(
2039 2578 'total_pages' => absint($status['total_pages']),
2040 2579 'processed_pages' => absint($status['processed_pages']),
2041 2580 'percentage' => ($status['total_pages'] > 0)
2042 - ? round((absint($status['processed_pages']) / absint($status['total_pages'])) * 100)
2043 - : 0,
2581 + ? round((absint($status['processed_pages']) / absint($status['total_pages'])) * 100)
2582 + : 0,
2044 2583 'status' => sanitize_text_field($status['status']),
2045 2584 'last_update' => human_time_diff(absint($status['last_update']), time()) . ' ' . esc_html__('ago', 'mxchat')
2046 2585 );
2586 +
2587 + // Add error message if present
2588 + if (isset($status['error']) && !empty($status['error'])) {
2589 + $result['error'] = sanitize_text_field($status['error']);
2590 + }
2591 +
2592 + return $result;
2047 2593 }
2594 +
2595 +
2048 2596 public function mxchat_handle_sitemap_submission() {
2597 + // Start logging the submission process
2598 + //error_log('[MXCHAT-URL] ===== Starting URL submission process =====');
2599 +
2049 2600 // Check if the form was submitted and verify permissions
2050 2601 if (!isset($_POST['submit_sitemap']) || !current_user_can('manage_options')) {
2602 + //error_log('[MXCHAT-URL] Error: Unauthorized access or form not submitted properly');
2051 2603 wp_die(esc_html__('Unauthorized access', 'mxchat'));
2052 2604 }
2053 2605
2054 2606 // Verify nonce
2607 + //error_log('[MXCHAT-URL] Verifying nonce');
2055 2608 check_admin_referer('mxchat_submit_sitemap_action', 'mxchat_submit_sitemap_nonce');
2056 2609
2057 2610 // Validate URL
2058 2611 if (!isset($_POST['sitemap_url']) || empty($_POST['sitemap_url'])) {
2612 + //error_log('[MXCHAT-URL] Error: Empty or missing URL');
2059 2613 set_transient('mxchat_admin_notice_error',
2060 2614 esc_html__('Please provide a valid URL.', 'mxchat'),
2061 2615 30
2062 2616 );
@@ -2064,12 +2618,40 @@
2064 2618 exit;
2065 2619 }
2066 2620
2067 2621 $submitted_url = esc_url_raw($_POST['sitemap_url']);
2622 + //error_log('[MXCHAT-URL] Processing URL: ' . $submitted_url);
2623 +
2624 + // Validate API key first
2625 + $options = get_option('mxchat_options');
2626 + $selected_model = $options['embedding_model'] ?? 'text-embedding-ada-002';
2627 +
2628 + if (strpos($selected_model, 'voyage') === 0) {
2629 + $api_key = $options['voyage_api_key'] ?? '';
2630 + $provider_name = 'Voyage AI';
2631 + } else {
2632 + $api_key = $options['api_key'] ?? '';
2633 + $provider_name = 'OpenAI';
2634 + }
2635 +
2636 + if (empty($api_key)) {
2637 + $error_message = sprintf(
2638 + esc_html__('%s API key is not configured. Please add your API key in the settings before submitting content.', 'mxchat'),
2639 + $provider_name
2640 + );
2641 + //error_log('[MXCHAT-URL] Error: ' . $error_message);
2642 + set_transient('mxchat_admin_notice_error', $error_message, 30);
2643 + //error_log('[MXCHAT-URL] Set error transient: ' . $error_message);
2644 + wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2645 + exit;
2646 + }
2647 +
2648 + //error_log('[MXCHAT-URL] Fetching URL content');
2068 2649 $response = wp_remote_get($submitted_url, array('timeout' => 30));
2069 2650
2070 2651 if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) {
2071 - $error_message = is_wp_error($response) ? $response->get_error_message() : __('Failed to fetch URL', 'mxchat');
2652 + $error_message = is_wp_error($response) ? $response->get_error_message() : 'HTTP Status: ' . wp_remote_retrieve_response_code($response);
2653 + //error_log('[MXCHAT-URL] Error fetching URL: ' . $error_message);
2072 2654 set_transient('mxchat_admin_notice_error',
2073 2655 sprintf(
2074 2656 esc_html__('Failed to fetch the URL: %s', 'mxchat'),
2075 2657 esc_html($error_message)
@@ -2080,11 +2662,13 @@
2080 2662 exit;
2081 2663 }
2082 2664
2083 2665 $content_type = wp_remote_retrieve_header($response, 'content-type');
2666 + //error_log('[MXCHAT-URL] Content type: ' . $content_type);
2084 2667 $body_content = wp_remote_retrieve_body($response);
2085 2668
2086 2669 if (empty($body_content)) {
2670 + //error_log('[MXCHAT-URL] Error: Empty response body');
2087 2671 set_transient('mxchat_admin_notice_error',
2088 2672 esc_html__('Empty response received from URL.', 'mxchat'),
2089 2673 30
2090 2674 );
@@ -2090,12 +2674,15 @@
2090 2674 );
2091 2675 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2092 2676 exit;
2093 2677 }
2678 + //error_log('[MXCHAT-URL] Retrieved body content length: ' . strlen($body_content) . ' bytes');
2094 2679
2095 2680 // Handle PDF URL
2096 2681 if ($this->is_pdf_url($submitted_url, $response)) {
2682 + //error_log('[MXCHAT-URL] Detected PDF URL, handling PDF for knowledge base');
2097 2683 $result = $this->handle_pdf_for_knowledge_base($submitted_url, $response);
2684 + //error_log('[MXCHAT-URL] PDF handling result: ' . $result);
2098 2685
2099 2686 if ($result === 'scheduled') {
2100 2687 set_transient(
2101 2688 'mxchat_last_pdf_url',
@@ -2107,9 +2694,9 @@
2107 2694 30
2108 2695 );
2109 2696 } else {
2110 2697 set_transient('mxchat_admin_notice_error',
2111 - esc_html__('Failed to start PDF processing. Please try again.', 'mxchat'),
2698 + esc_html__('Failed to start PDF processing: ', 'mxchat') . esc_html($result),
2112 2699 30
2113 2700 );
2114 2701 }
2115 2702
@@ -2118,8 +2705,9 @@
2118 2705 }
2119 2706
2120 2707 // Handle Sitemap XML
2121 2708 if (strpos($content_type, 'xml') !== false || strpos($body_content, '<urlset') !== false) {
2709 + //error_log('[MXCHAT-URL] Detected XML content, processing as sitemap');
2122 2710 libxml_use_internal_errors(true);
2123 2711 $xml = simplexml_load_string($body_content);
2124 2712 $xml_errors = libxml_get_errors();
2125 2713 libxml_clear_errors();
@@ -2124,8 +2712,15 @@
2124 2712 $xml_errors = libxml_get_errors();
2125 2713 libxml_clear_errors();
2126 2714
2127 2715 if ($xml === false || !empty($xml_errors)) {
2716 + //error_log('[MXCHAT-URL] Error: Invalid XML format');
2717 + if (!empty($xml_errors)) {
2718 + foreach ($xml_errors as $error) {
2719 + //error_log('[MXCHAT-URL] XML Error: ' . $error->message);
2720 + }
2721 + }
2722 +
2128 2723 set_transient('mxchat_admin_notice_error',
2129 2724 esc_html__('Invalid sitemap XML. Please provide a valid sitemap.', 'mxchat'),
2130 2725 30
2131 2726 );
@@ -2132,9 +2727,11 @@
2132 2727 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2133 2728 exit;
2134 2729 }
2135 2730
2731 + //error_log('[MXCHAT-URL] Valid XML found, handling sitemap for knowledge base');
2136 2732 $result = $this->handle_sitemap_for_knowledge_base($xml, $submitted_url);
2733 + //error_log('[MXCHAT-URL] Sitemap handling result: ' . $result);
2137 2734
2138 2735 if ($result === 'scheduled') {
2139 2736 set_transient(
2140 2737 'mxchat_last_sitemap_url',
@@ -2145,10 +2742,12 @@
2145 2742 esc_html__('Sitemap processing has started in the background. You can check the progress in the Knowledge Base section.', 'mxchat'),
2146 2743 30
2147 2744 );
2148 2745 } else {
2746 + // Return to the admin page without a redirect for better error display
2747 + // The error is already stored in the sitemap status transient
2149 2748 set_transient('mxchat_admin_notice_error',
2150 - esc_html__('Failed to start sitemap processing. Please try again.', 'mxchat'),
2749 + esc_html__('Failed to start sitemap processing. Please check the status below for details.', 'mxchat'),
2151 2750 30
2152 2751 );
2153 2752 }
2154 2753
@@ -2156,42 +2755,147 @@
2156 2755 exit;
2157 2756 }
2158 2757
2159 2758 // Handle Regular URL
2160 - $page_content = $this->mxchat_extract_main_content($body_content);
2161 - $sanitized_content = $this->mxchat_sanitize_content_for_api($page_content);
2759 + // Handle Regular URL
2760 +//error_log('[MXCHAT-URL] Processing as regular webpage');
2761 +$page_content = $this->mxchat_extract_main_content($body_content);
2762 +//error_log('[MXCHAT-URL] Extracted content length: ' . strlen($page_content) . ' bytes');
2162 2763
2163 - if (empty($sanitized_content)) {
2164 - set_transient('mxchat_admin_notice_error',
2165 - esc_html__('No valid content found on the provided URL.', 'mxchat'),
2166 - 30
2167 - );
2764 +$sanitized_content = $this->mxchat_sanitize_content_for_api($page_content);
2765 +//error_log('[MXCHAT-URL] Sanitized content length: ' . strlen($sanitized_content) . ' bytes');
2766 +
2767 +if (empty($sanitized_content)) {
2768 + //error_log('[MXCHAT-URL] Error: No valid content after sanitization');
2769 +
2770 + // Set both transients - the error notice and the URL status
2771 + set_transient('mxchat_admin_notice_error',
2772 + esc_html__('No valid content found on the provided URL.', 'mxchat'),
2773 + 30
2774 + );
2775 +
2776 + // Set URL status transient
2777 + set_transient('mxchat_single_url_status', [
2778 + 'url' => $submitted_url,
2779 + 'timestamp' => current_time('mysql'),
2780 + 'status' => 'failed',
2781 + 'error' => esc_html__('No valid content found on the provided URL.', 'mxchat')
2782 + ], DAY_IN_SECONDS);
2783 +
2784 + wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2785 + exit;
2786 +}
2787 +
2788 +//error_log('[MXCHAT-URL] Generating embedding for content');
2789 +$embedding_vector = $this->mxchat_generate_embedding($sanitized_content);
2790 +
2791 +// Check if embedding_vector is a string (error message)
2792 +if (is_string($embedding_vector)) {
2793 + //error_log('[MXCHAT-URL] Error generating embedding: ' . $embedding_vector);
2794 + $error_message = esc_html__('Failed to generate embedding: ', 'mxchat') . esc_html($embedding_vector);
2795 + //error_log('[MXCHAT-URL] Setting error transient: ' . $error_message);
2796 +
2797 + // Set both transients
2798 + set_transient('mxchat_admin_notice_error', $error_message, 30);
2799 +
2800 + // Set URL status transient
2801 + set_transient('mxchat_single_url_status', [
2802 + 'url' => $submitted_url,
2803 + 'timestamp' => current_time('mysql'),
2804 + 'status' => 'failed',
2805 + 'error' => $error_message
2806 + ], DAY_IN_SECONDS);
2807 +
2808 + wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2809 + exit;
2810 +}
2811 +
2812 +if (is_array($embedding_vector)) {
2813 + //error_log('[MXCHAT-URL] Successfully generated embedding with ' . count($embedding_vector) . ' dimensions');
2814 +
2815 + $db_result = MxChat_Utils::submit_content_to_db(
2816 + $sanitized_content,
2817 + $submitted_url,
2818 + $this->options['api_key']
2819 + );
2820 +
2821 + if (is_wp_error($db_result)) {
2822 + //error_log('[MXCHAT-URL] Error: Failed to store content in database: ' . $db_result->get_error_message());
2823 + $error_message = esc_html__('Failed to store content in database: ', 'mxchat') . esc_html($db_result->get_error_message());
2824 + //error_log('[MXCHAT-URL] Setting error transient: ' . $error_message);
2825 +
2826 + // Set both transients
2827 + set_transient('mxchat_admin_notice_error', $error_message, 30);
2828 +
2829 + // Set URL status transient
2830 + set_transient('mxchat_single_url_status', [
2831 + 'url' => $submitted_url,
2832 + 'timestamp' => current_time('mysql'),
2833 + 'status' => 'failed',
2834 + 'error' => $error_message
2835 + ], DAY_IN_SECONDS);
2836 +
2168 2837 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2169 2838 exit;
2170 2839 }
2171 2840
2172 - $embedding_vector = $this->mxchat_generate_embedding($sanitized_content);
2173 - if (is_array($embedding_vector)) {
2174 - MxChat_Utils::submit_content_to_db(
2175 - $sanitized_content,
2176 - $submitted_url,
2177 - $this->options['api_key']
2178 - );
2179 - set_transient('mxchat_admin_notice_success',
2180 - esc_html__('URL content successfully submitted!', 'mxchat'),
2181 - 30
2182 - );
2183 - } else {
2184 - set_transient('mxchat_admin_notice_error',
2185 - esc_html__('Failed to generate embedding for the URL content. Please check your API key and try again.', 'mxchat'),
2186 - 30
2187 - );
2841 + //error_log('[MXCHAT-URL] Successfully stored content in database');
2842 + $success_message = esc_html__('URL content successfully submitted!', 'mxchat');
2843 + //error_log('[MXCHAT-URL] Setting success transient: ' . $success_message);
2844 +
2845 + // Set both transients
2846 + set_transient('mxchat_admin_notice_success', $success_message, 30);
2847 +
2848 + // Set URL status transient with success
2849 + set_transient('mxchat_single_url_status', [
2850 + 'url' => $submitted_url,
2851 + 'timestamp' => current_time('mysql'),
2852 + 'status' => 'complete',
2853 + 'content_length' => strlen($sanitized_content),
2854 + 'embedding_dimensions' => count($embedding_vector)
2855 + ], DAY_IN_SECONDS);
2856 +
2857 +} else {
2858 + //error_log('[MXCHAT-URL] Error: Failed to generate embedding. Unexpected result type: ' . gettype($embedding_vector));
2859 + $error_message = esc_html__('Failed to generate embedding: Unexpected result type. Please check your API key and try again.', 'mxchat');
2860 + //error_log('[MXCHAT-URL] Setting error transient: ' . $error_message);
2861 +
2862 + // Set both transients
2863 + set_transient('mxchat_admin_notice_error', $error_message, 30);
2864 +
2865 + // Set URL status transient
2866 + set_transient('mxchat_single_url_status', [
2867 + 'url' => $submitted_url,
2868 + 'timestamp' => current_time('mysql'),
2869 + 'status' => 'failed',
2870 + 'error' => $error_message
2871 + ], DAY_IN_SECONDS);
2872 +}
2873 +
2874 +//error_log('[MXCHAT-URL] ===== Completed URL submission process =====');
2875 +wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2876 +exit;
2877 +}
2878 +/**
2879 + * Get the status of the last single URL submission
2880 + */
2881 +private function get_single_url_status() {
2882 + $status = get_transient('mxchat_single_url_status');
2883 + if (!$status) {
2884 + return null;
2188 2885 }
2886 +
2887 + // Add human-readable time
2888 + if (isset($status['timestamp'])) {
2889 + $status['human_time'] = human_time_diff(strtotime($status['timestamp']), current_time('timestamp')) . ' ' . __('ago', 'mxchat');
2890 + }
2891 +
2892 + return $status;
2893 +}
2189 2894
2190 - wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2191 - exit;
2192 -}
2193 2895 private function handle_sitemap_for_knowledge_base($xml, $sitemap_url) {
2896 + // Clear any single URL status when starting sitemap processing
2897 + delete_transient('mxchat_single_url_status');
2194 2898 if (!current_user_can('manage_options')) {
2195 2899 //error_log(esc_html__('Unauthorized sitemap processing attempt', 'mxchat'));
2196 2900 return false;
2197 2901 }
@@ -2202,8 +2906,40 @@
2202 2906 if (!$xml || !is_object($xml)) {
2203 2907 throw new Exception(__('Invalid XML object provided', 'mxchat'));
2204 2908 }
2205 2909
2910 + // Add embedding validation before processing
2911 + // Test embedding with a small sample text to verify API key is working
2912 + $test_result = $this->mxchat_generate_embedding("This is a test to verify the embedding API key is working.");
2913 +
2914 + // Check if test_result is a string (error message) rather than an array (valid embedding)
2915 + if (is_string($test_result)) {
2916 + //error_log('[MXCHAT-URL] Embedding API validation failed: ' . $test_result);
2917 +
2918 + // Store the error in the status transient so it can be displayed later
2919 + $status_data = array(
2920 + 'total_urls' => 0,
2921 + 'processed_urls' => 0,
2922 + 'status' => 'error',
2923 + 'error' => __('Embedding API validation failed: ', 'mxchat') . $test_result,
2924 + 'last_update' => time()
2925 + );
2926 +
2927 + set_transient(
2928 + sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url)),
2929 + array_map('sanitize_text_field', $status_data),
2930 + DAY_IN_SECONDS
2931 + );
2932 +
2933 + throw new Exception(__('Embedding API validation failed: ', 'mxchat') . $test_result);
2934 + }
2935 +
2936 + // Make sure it's an array (valid embedding)
2937 + if (!is_array($test_result)) {
2938 + //error_log('[MXCHAT-URL] Embedding API returned unexpected result type: ' . gettype($test_result));
2939 + throw new Exception(__('Embedding API returned unexpected result type. Please check your configuration.', 'mxchat'));
2940 + }
2941 +
2206 2942 $urls = [];
2207 2943 foreach ($xml->url as $url_element) {
2208 2944 $url = esc_url_raw((string)$url_element->loc);
2209 2945 if ($url) {
@@ -2240,12 +2976,36 @@
2240 2976
2241 2977 return __('scheduled', 'mxchat');
2242 2978
2243 2979 } catch (\Exception $e) {
2244 - //error_log(sprintf(esc_html__('Error preparing sitemap for processing: %s', 'mxchat'), esc_html($e->getMessage())));
2245 - return false;
2980 + $error_message = $e->getMessage();
2981 + //error_log(sprintf(esc_html__('Error preparing sitemap for processing: %s', 'mxchat'), esc_html($error_message)));
2982 +
2983 + // Store the sitemap URL and error in transients so they can be displayed
2984 + set_transient(
2985 + 'mxchat_last_sitemap_url',
2986 + sanitize_text_field($sitemap_url),
2987 + DAY_IN_SECONDS
2988 + );
2989 +
2990 + $status_data = array(
2991 + 'total_urls' => 0,
2992 + 'processed_urls' => 0,
2993 + 'status' => 'error',
2994 + 'error' => $error_message,
2995 + 'last_update' => time()
2996 + );
2997 +
2998 + set_transient(
2999 + sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url)),
3000 + array_map('sanitize_text_field', $status_data),
3001 + DAY_IN_SECONDS
3002 + );
3003 +
3004 + return $error_message;
2246 3005 }
2247 3006 }
3007 +
2248 3008 public static function process_sitemap_urls_cron($urls, $sitemap_url, $total_urls, $batch_size, $batch_pause) {
2249 3009 // Validate inputs
2250 3010 $sitemap_url = esc_url_raw($sitemap_url);
2251 3011 $total_urls = absint($total_urls);
@@ -2263,8 +3023,13 @@
2263 3023 if (!$status || !is_array($status)) {
2264 3024 throw new Exception('Invalid status data retrieved from transient');
2265 3025 }
2266 3026
3027 + // Initialize failed_urls array if it doesn't exist
3028 + if (!isset($status['failed_urls_list']) || !is_array($status['failed_urls_list'])) {
3029 + $status['failed_urls_list'] = [];
3030 + }
3031 +
2267 3032 $start_url = absint($status['processed_urls']);
2268 3033 $end_url = min($start_url + $batch_size, $total_urls);
2269 3034 $instance = new self();
2270 3035
@@ -2271,11 +3036,38 @@
2271 3036 // Track failures in batch
2272 3037 $batch_stats = [
2273 3038 'processed' => 0,
2274 3039 'failed' => 0,
2275 - 'last_error' => ''
3040 + 'last_error' => '',
3041 + 'embedding_errors' => 0 // Track specifically embedding errors
2276 3042 ];
2277 3043
3044 + // Check embedding configuration with first URL
3045 + if ($start_url === 0) {
3046 + $page_url = esc_url_raw($urls[0]);
3047 + $page_response = wp_remote_get($page_url);
3048 +
3049 + if (!is_wp_error($page_response) && wp_remote_retrieve_response_code($page_response) === 200) {
3050 + $page_html = wp_remote_retrieve_body($page_response);
3051 + $page_content = $instance->mxchat_extract_main_content($page_html);
3052 + $sanitized_content = $instance->mxchat_sanitize_content_for_api($page_content);
3053 +
3054 + if (!empty($sanitized_content)) {
3055 + $embedding_vector = $instance->mxchat_generate_embedding($sanitized_content);
3056 +
3057 + // Check if embedding_vector is a string (error message)
3058 + if (is_string($embedding_vector)) {
3059 + throw new Exception('Embedding generation failed: ' . $embedding_vector);
3060 + }
3061 +
3062 + // Make sure it's an array (valid embedding)
3063 + if (!is_array($embedding_vector)) {
3064 + throw new Exception('Embedding generation returned unexpected result type: ' . gettype($embedding_vector));
3065 + }
3066 + }
3067 + }
3068 + }
3069 +
2278 3070 for ($i = $start_url; $i < $end_url; $i++) {
2279 3071 $page_url = esc_url_raw($urls[$i]);
2280 3072 $page_response = wp_remote_get($page_url);
2281 3073
@@ -2280,8 +3072,18 @@
2280 3072 $page_response = wp_remote_get($page_url);
2281 3073
2282 3074 if (is_wp_error($page_response) || wp_remote_retrieve_response_code($page_response) !== 200) {
2283 3075 $batch_stats['failed']++;
3076 + $error_message = is_wp_error($page_response) ? $page_response->get_error_message() : 'HTTP Status: ' . wp_remote_retrieve_response_code($page_response);
3077 + $batch_stats['last_error'] = 'Failed to fetch URL: ' . $error_message;
3078 +
3079 + // Add to failed URLs list with error message
3080 + $status['failed_urls_list'][] = [
3081 + 'url' => $page_url,
3082 + 'error' => $error_message,
3083 + 'time' => time()
3084 + ];
3085 +
2284 3086 continue;
2285 3087 }
2286 3088
2287 3089 $page_html = wp_remote_retrieve_body($page_response);
@@ -2290,8 +3092,29 @@
2290 3092
2291 3093 if (!empty($sanitized_content)) {
2292 3094 $embedding_vector = $instance->mxchat_generate_embedding($sanitized_content);
2293 3095
3096 + // Check if embedding_vector is a string (error message)
3097 + if (is_string($embedding_vector)) {
3098 + $batch_stats['failed']++;
3099 + $batch_stats['embedding_errors']++;
3100 + $batch_stats['last_error'] = 'Failed to generate embedding: ' . $embedding_vector;
3101 +
3102 + // Add to failed URLs list with error message
3103 + $status['failed_urls_list'][] = [
3104 + 'url' => $page_url,
3105 + 'error' => 'Embedding error: ' . $embedding_vector,
3106 + 'time' => time()
3107 + ];
3108 +
3109 + // If we have multiple embedding errors, stop processing
3110 + if ($batch_stats['embedding_errors'] >= 10) {
3111 + throw new Exception('Multiple embedding failures detected: ' . $embedding_vector);
3112 + }
3113 + continue;
3114 + }
3115 +
3116 + // Check if it's an array (valid embedding)
2294 3117 if (is_array($embedding_vector)) {
2295 3118 $options = get_option('mxchat_options');
2296 3119 $submission_result = MxChat_Utils::submit_content_to_db($sanitized_content, $page_url, $options['api_key']);
2297 3120
@@ -2297,8 +3120,16 @@
2297 3120
2298 3121 if (is_wp_error($submission_result)) {
2299 3122 $batch_stats['failed']++;
2300 3123 $batch_stats['last_error'] = $submission_result->get_error_message();
3124 +
3125 + // Add to failed URLs list with error message
3126 + $status['failed_urls_list'][] = [
3127 + 'url' => $page_url,
3128 + 'error' => 'Database submission error: ' . $submission_result->get_error_message(),
3129 + 'time' => time()
3130 + ];
3131 +
2301 3132 continue;
2302 3133 }
2303 3134
2304 3135 $batch_stats['processed']++;
@@ -2303,9 +3134,22 @@
2303 3134
2304 3135 $batch_stats['processed']++;
2305 3136 } else {
2306 3137 $batch_stats['failed']++;
2307 - $batch_stats['last_error'] = 'Failed to generate embedding. Please check embedding API key.';
3138 + $batch_stats['embedding_errors']++;
3139 + $batch_stats['last_error'] = 'Failed to generate embedding: Unexpected result type: ' . gettype($embedding_vector);
3140 +
3141 + // Add to failed URLs list with error message
3142 + $status['failed_urls_list'][] = [
3143 + 'url' => $page_url,
3144 + 'error' => 'Embedding error: Unexpected result type: ' . gettype($embedding_vector),
3145 + 'time' => time()
3146 + ];
3147 +
3148 + // If we have multiple embedding errors, stop processing
3149 + if ($batch_stats['embedding_errors'] >= 10) {
3150 + throw new Exception('Multiple embedding failures detected. Please check your embedding API configuration.');
3151 + }
2308 3152 }
2309 3153 }
2310 3154
2311 3155 $status['processed_urls'] = absint($i + 1);
@@ -2312,9 +3156,14 @@
2312 3156 $status['last_update'] = time();
2313 3157 $status['failed_urls'] = absint($status['failed_urls'] ?? 0) + $batch_stats['failed'];
2314 3158 $status['last_error'] = $batch_stats['last_error'];
2315 3159
2316 - set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
3160 + // Limit the number of failed URLs we store to prevent transient size issues
3161 + if (count($status['failed_urls_list']) > 100) {
3162 + $status['failed_urls_list'] = array_slice($status['failed_urls_list'], -100);
3163 + }
3164 +
3165 + set_transient($status_key, $status, DAY_IN_SECONDS);
2317 3166 }
2318 3167
2319 3168 // If all URLs in this batch failed, stop processing
2320 3169 if ($batch_stats['processed'] === 0 && $batch_stats['failed'] > 0) {
@@ -2323,15 +3172,15 @@
2323 3172 'Processing stopped: %d consecutive failures. Last error: %s',
2324 3173 $batch_stats['failed'],
2325 3174 $batch_stats['last_error']
2326 3175 );
2327 - set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
3176 + set_transient($status_key, $status, DAY_IN_SECONDS);
2328 3177 return;
2329 3178 }
2330 3179
2331 3180 if ($end_url < $total_urls) {
2332 3181 wp_schedule_single_event(time() + $batch_pause, 'mxchat_process_sitemap_urls', array(
2333 - 'urls' => array_map('esc_url_raw', $urls),
3182 + 'urls' => $urls,
2334 3183 'sitemap_url' => $sitemap_url,
2335 3184 'total_urls' => $total_urls,
2336 3185 'batch_size' => $batch_size,
2337 3186 'batch_pause' => $batch_pause,
@@ -2337,17 +3186,249 @@
2337 3186 'batch_pause' => $batch_pause,
2338 3187 ));
2339 3188 } else {
2340 3189 $status['status'] = 'complete';
2341 - set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
3190 + set_transient($status_key, $status, DAY_IN_SECONDS);
2342 3191 }
2343 3192 } catch (\Exception $e) {
2344 3193 $status['status'] = 'error';
2345 - $status['error'] = sanitize_text_field($e->getMessage());
2346 - set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
3194 + $status['error'] = $e->getMessage();
3195 + set_transient($status_key, $status, DAY_IN_SECONDS);
2347 3196 }
2348 3197 }
2349 3198
3199 +private function mxchat_sanitize_content_for_api($content) {
3200 + //error_log('[MXCHAT-SANITIZE] Original content preview: ' . substr($content, 0, 500) . '...');
3201 +
3202 + // Remove script, style tags, and HTML comments
3203 + $content = preg_replace('/<script\b[^>]*>(.*?)<\/script>/is', '', $content);
3204 + $content = preg_replace('/<style\b[^>]*>(.*?)<\/style>/is', '', $content);
3205 + $content = preg_replace('/<!--(.|\s)*?-->/', '', $content);
3206 +
3207 + // Remove all HTML tags and decode HTML entities
3208 + $content = wp_strip_all_tags($content);
3209 + $content = html_entity_decode($content, ENT_QUOTES | ENT_HTML5);
3210 +
3211 + // Trim and normalize whitespace
3212 + $content = trim(preg_replace('/\s+/', ' ', $content));
3213 +
3214 + // Remove control characters (which can cause database issues)
3215 + $content = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/u', '', $content);
3216 +
3217 + // Remove NULL bytes which can cause database errors
3218 + $content = str_replace("\0", "", $content);
3219 +
3220 + // Ensure valid UTF-8 encoding
3221 + $content = wp_check_invalid_utf8($content);
3222 +
3223 + // Remove any extremely long strings without spaces (often garbage)
3224 + $content = preg_replace('/\S{300,}/', ' ', $content);
3225 +
3226 + // Replace problematic characters that often cause database issues
3227 + $content = preg_replace('/[\x{10000}-\x{10FFFF}]/u', '', $content); // Remove emoji and other high Unicode characters
3228 +
3229 + // Replace any remaining potentially problematic characters with spaces
3230 + $content = preg_replace('/[^\p{L}\p{N}\p{P}\p{Z}\p{Sm}]/u', ' ', $content);
3231 +
3232 + // Limit to reasonable length if needed
3233 + $max_length = 65000; // Just under MySQL TEXT field limit
3234 + if (strlen($content) > $max_length) {
3235 + $content = substr($content, 0, $max_length);
3236 + }
3237 +
3238 + //error_log('[MXCHAT-SANITIZE] Sanitized content preview: ' . substr($content, 0, 500) . '...');
3239 + return $content;
3240 +}
3241 +private static function mxchat_extract_main_content($html) {
3242 + if (empty($html)) {
3243 + return '';
3244 + }
3245 + try {
3246 + $dom = new DOMDocument;
3247 + libxml_use_internal_errors(true); // Suppress HTML parsing errors
3248 + @$dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
3249 + $xpath = new DOMXPath($dom);
3250 +
3251 + // For debugging purposes
3252 + $debugEnabled = false; // Set to true to enable debugging output
3253 + $debug = function($message) use ($debugEnabled) {
3254 + if ($debugEnabled) {
3255 + //error_log('[MXCHAT-DEBUG] ' . $message);
3256 + }
3257 + };
3258 +
3259 + // Direct targeting for Gerow theme posts
3260 + $post_text = $xpath->query('//div[contains(@class, "post-text")]');
3261 + if ($post_text && $post_text->length > 0) {
3262 + $debug("Found post-text directly");
3263 + $content = '';
3264 + foreach ($post_text as $node) {
3265 + $content .= $dom->saveHTML($node);
3266 + }
3267 + if (!empty($content)) {
3268 + $debug("Returning post-text content");
3269 + return $content;
3270 + }
3271 + }
3272 +
3273 + // Try to get the blog details content which contains the post-text
3274 + $blog_details = $xpath->query('//div[contains(@class, "blog-details-content")]');
3275 + if ($blog_details && $blog_details->length > 0) {
3276 + $debug("Found blog-details-content");
3277 + $content = '';
3278 + foreach ($blog_details as $node) {
3279 + $content .= $dom->saveHTML($node);
3280 + }
3281 + if (!empty($content)) {
3282 + $debug("Returning blog-details-content");
3283 + return $content;
3284 + }
3285 + }
3286 +
3287 + // Try to get the article which contains the blog details
3288 + $article = $xpath->query('//article[contains(@class, "blog-details-wrap")]');
3289 + if ($article && $article->length > 0) {
3290 + $debug("Found article with blog-details-wrap");
3291 + $content = '';
3292 + foreach ($article as $node) {
3293 + $content .= $dom->saveHTML($node);
3294 + }
3295 + if (!empty($content)) {
3296 + $debug("Returning article content");
3297 + return $content;
3298 + }
3299 + }
3300 +
3301 + // Try even broader with the blog-item-wrap
3302 + $blog_item = $xpath->query('//div[contains(@class, "blog-item-wrap")]');
3303 + if ($blog_item && $blog_item->length > 0) {
3304 + $debug("Found blog-item-wrap");
3305 + $content = '';
3306 + foreach ($blog_item as $node) {
3307 + $content .= $dom->saveHTML($node);
3308 + }
3309 + if (!empty($content)) {
3310 + $debug("Returning blog-item-wrap content");
3311 + return $content;
3312 + }
3313 + }
3314 +
3315 + // Specific Gerow theme path
3316 + $gerow_path = $xpath->query('//section[contains(@class, "blog-area")]//div[contains(@class, "post-text")]');
3317 + if ($gerow_path && $gerow_path->length > 0) {
3318 + $debug("Found Gerow theme path to post-text");
3319 + $content = '';
3320 + foreach ($gerow_path as $node) {
3321 + $content .= $dom->saveHTML($node);
3322 + }
3323 + if (!empty($content)) {
3324 + $debug("Returning Gerow post-text content");
3325 + return $content;
3326 + }
3327 + }
3328 +
3329 + // Generic blog post selectors
3330 + $selectors = [
3331 + // Blog post specific selectors
3332 + '//div[contains(@class, "post-text")]',
3333 + '//article[contains(@class, "blog-post-item")]//div[contains(@class, "post-text")]',
3334 + '//div[contains(@class, "blog-details-content")]',
3335 + '//article[contains(@class, "blog-details-wrap")]',
3336 + '//div[contains(@class, "entry-content")]',
3337 + '//div[contains(@class, "blog-content")]',
3338 + '//div[contains(@class, "blog-item-wrap")]',
3339 +
3340 + // More general content selectors
3341 + '//div[contains(@class, "page__content")]',
3342 + '//div[contains(@class, "elementor-widget-container")]',
3343 + '//div[contains(@class, "elementor-text-editor")]',
3344 + '//div[contains(@class, "elementor-widget-text-editor")]',
3345 + '//*[contains(@class, "entry-content")]',
3346 + '//*[contains(@class, "post-content")]',
3347 + '//*[contains(@class, "article-content")]',
3348 + '//*[@id="content"]',
3349 + '//*[@id="main-content"]',
3350 + '//section[contains(@class, "blog-area")]',
3351 + '//article',
3352 + '//main',
3353 + '//div[contains(@class, "content")]'
3354 + ];
3355 +
3356 + // First handle Elementor content
3357 + $debug("Checking for Elementor content");
3358 + $elementor_widgets = $xpath->query('//div[contains(@class, "elementor-element")]//div[contains(@class, "elementor-widget-container")]');
3359 + if ($elementor_widgets && $elementor_widgets->length > 0) {
3360 + $debug("Found Elementor widgets");
3361 + $combined_content = '';
3362 + foreach ($elementor_widgets as $widget) {
3363 + $widget_content = $dom->saveHTML($widget);
3364 + if (!empty($widget_content)) {
3365 + $combined_content .= $widget_content;
3366 + }
3367 + }
3368 + if (!empty($combined_content)) {
3369 + $debug("Returning Elementor content");
3370 + return $combined_content;
3371 + }
3372 + }
3373 +
3374 + // Try standard selectors one by one
3375 + foreach ($selectors as $selector) {
3376 + $debug("Trying selector: " . $selector);
3377 + $nodes = $xpath->query($selector);
3378 + if ($nodes && $nodes->length > 0) {
3379 + $debug("Found matches for selector: " . $selector);
3380 + $content = '';
3381 + foreach ($nodes as $node) {
3382 + $content .= $dom->saveHTML($node);
3383 + }
3384 + if (!empty($content)) {
3385 + $debug("Returning content from selector: " . $selector);
3386 + return $content;
3387 + }
3388 + }
3389 + }
3390 +
3391 + // Manual regex fallback for post-text if DOM methods fail
3392 + $debug("Trying regex fallback");
3393 + if (preg_match('/<div class="post-text">(.*?)<\/div>\s*<\/div>\s*<\/div>/s', $html, $matches)) {
3394 + $debug("Found post-text via regex");
3395 + return '<div class="post-text">' . $matches[1] . '</div>';
3396 + }
3397 +
3398 + // Try to extract the blog section as a whole
3399 + $blog_section = $xpath->query('//section[contains(@class, "blog-area")]');
3400 + if ($blog_section && $blog_section->length > 0) {
3401 + $debug("Found blog-area section");
3402 + $content = '';
3403 + foreach ($blog_section as $node) {
3404 + $content .= $dom->saveHTML($node);
3405 + }
3406 + if (!empty($content)) {
3407 + $debug("Returning blog-area section content");
3408 + return $content;
3409 + }
3410 + }
3411 +
3412 + // Fallback: Return the body content if no specific selector matches
3413 + $debug("Using body fallback");
3414 + $body = $dom->getElementsByTagName('body');
3415 + if ($body->length > 0) {
3416 + return $dom->saveHTML($body->item(0));
3417 + }
3418 +
3419 + // Last resort: return the original HTML
3420 + $debug("Returning original HTML");
3421 + return $html;
3422 + } catch (Exception $e) {
3423 + //error_log('[MXCHAT-ERROR] Content extraction failed: ' . $e->getMessage());
3424 + return $html; // Return original HTML if parsing fails
3425 + } finally {
3426 + libxml_clear_errors();
3427 + }
3428 +}
3429 +
3430 +
2350 3431 public function get_sitemap_processing_status($sitemap_url) {
2351 3432 $sitemap_url = esc_url_raw($sitemap_url);
2352 3433 $status = get_transient(sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url)));
2353 3434
@@ -2359,17 +3440,53 @@
2359 3440 'total_urls' => absint($status['total_urls']),
2360 3441 'processed_urls' => absint($status['processed_urls']),
2361 3442 'failed_urls' => absint($status['failed_urls'] ?? 0),
2362 3443 'percentage' => ($status['total_urls'] > 0)
2363 - ? round((absint($status['processed_urls']) / absint($status['total_urls'])) * 100)
2364 - : 0,
3444 + ? round((absint($status['processed_urls']) / absint($status['total_urls'])) * 100)
3445 + : 0,
2365 3446 'status' => sanitize_text_field($status['status']),
2366 3447 'last_update' => human_time_diff(absint($status['last_update']), time()) . ' ' . esc_html__('ago', 'mxchat'),
2367 3448 'error' => isset($status['error']) ? sanitize_text_field($status['error']) : '',
2368 - 'last_error' => isset($status['last_error']) ? sanitize_text_field($status['last_error']) : ''
3449 + 'last_error' => isset($status['last_error']) ? sanitize_text_field($status['last_error']) : '',
3450 + 'failed_urls_list' => isset($status['failed_urls_list']) ? $status['failed_urls_list'] : array()
2369 3451 );
2370 3452 }
3453 +public function ajax_get_status_updates() {
3454 + // Verify the request
3455 + check_ajax_referer('mxchat_status_nonce', 'nonce');
2371 3456
3457 + // Get the status just like in your admin page
3458 + $pdf_url = get_transient('mxchat_last_pdf_url');
3459 + $sitemap_url = get_transient('mxchat_last_sitemap_url');
3460 + $pdf_status = $pdf_url ? $this->get_pdf_processing_status($pdf_url) : false;
3461 + $sitemap_status = $sitemap_url ? $this->get_sitemap_processing_status($sitemap_url) : false;
3462 +
3463 + // Check for true processing status, not just presence of status
3464 + $is_active_processing =
3465 + ($sitemap_status && $sitemap_status['status'] === 'processing') ||
3466 + ($pdf_status && $pdf_status['status'] === 'processing');
3467 +
3468 + // Get single URL status, but only if no sitemap/PDF is processing
3469 + $single_url_status = !$is_active_processing ? $this->get_single_url_status() : false;
3470 +
3471 + // Only clear transients for completed processes, not error states
3472 + if ($pdf_status && $pdf_status['status'] === 'complete') {
3473 + delete_transient('mxchat_last_pdf_url');
3474 + $pdf_status = false;
3475 + }
3476 + if ($sitemap_status && $sitemap_status['status'] === 'complete') {
3477 + delete_transient('mxchat_last_sitemap_url');
3478 + $sitemap_status = false;
3479 + }
3480 +
3481 + // Return JSON response with the status data
3482 + wp_send_json(array(
3483 + 'pdf_status' => $pdf_status,
3484 + 'sitemap_status' => $sitemap_status,
3485 + 'single_url_status' => $single_url_status,
3486 + 'is_processing' => $is_active_processing
3487 + ));
3488 +}
2372 3489 public function mxchat_stop_processing() {
2373 3490 // Verify permissions
2374 3491 if (!current_user_can('manage_options')) {
2375 3492 wp_die(esc_html__('Unauthorized access', 'mxchat'));
@@ -2405,27 +3522,10 @@
2405 3522 );
2406 3523 wp_safe_redirect(admin_url('admin.php?page=mxchat-prompts'));
2407 3524 exit;
2408 3525 }
2409 -private function mxchat_sanitize_content_for_api($content) {
2410 - // Remove script, style tags, and HTML comments
2411 - $content = preg_replace('/<script\b[^>]*>(.*?)<\/script>/is', '', $content);
2412 - $content = preg_replace('/<style\b[^>]*>(.*?)<\/style>/is', '', $content);
2413 - $content = preg_replace('/<!--(.|\s)*?-->/', '', $content);
2414 3526
2415 - // Remove all HTML tags and decode HTML entities
2416 - $content = wp_strip_all_tags($content);
2417 - $content = html_entity_decode($content, ENT_QUOTES | ENT_HTML5);
2418 3527
2419 - // Trim and normalize whitespace
2420 - $content = trim(preg_replace('/\s+/', ' ', $content));
2421 -
2422 - return $content;
2423 -}
2424 -
2425 -
2426 -
2427 -
2428 3528 public function mxchat_handle_product_change($post_id, $post, $update) {
2429 3529 if ($post->post_type !== 'product') {
2430 3530 return;
2431 3531 }
@@ -2438,9 +3538,8 @@
2438 3538 }
2439 3539 });
2440 3540 }
2441 3541 }
2442 -
2443 3542 private function mxchat_store_product_embedding($product) {
2444 3543 if (!isset($this->options['enable_woocommerce_integration']) ||
2445 3544 !in_array($this->options['enable_woocommerce_integration'], ['1', 'on'])) {
2446 3545 return;
@@ -2496,9 +3595,8 @@
2496 3595 // Use WordPress DB storage
2497 3596 $this->store_in_wordpress_db($description, $source_url, $embedding_vector);
2498 3597 }
2499 3598 }
2500 -
2501 3599 public function mxchat_handle_product_delete($post_id) {
2502 3600 if (get_post_type($post_id) !== 'product') {
2503 3601 return;
2504 3602 }
@@ -2685,9 +3783,9 @@
2685 3783 </div>
2686 3784 <?php
2687 3785 }
2688 3786
2689 -public function mxchat_intents_page_html() {
3787 +public function mxchat_actions_page_html() {
2690 3788 if (!current_user_can('manage_options')) {
2691 3789 return;
2692 3790 }
2693 3791
@@ -2700,9 +3798,9 @@
2700 3798
2701 3799 // Success message
2702 3800 if (isset($_GET['updated']) && $_GET['updated'] === 'true') {
2703 3801 echo '<div class="notice notice-success is-dismissible"><p>' .
2704 - esc_html__('Intent updated successfully.', 'mxchat') .
3802 + esc_html__('Action updated successfully.', 'mxchat') .
2705 3803 '</p></div>';
2706 3804 }
2707 3805
2708 3806 // Filtering logic
@@ -2723,10 +3821,10 @@
2723 3821 // Pagination
2724 3822 $total_intents = $wpdb->get_var("SELECT COUNT(*) FROM $table_name WHERE $where");
2725 3823 $total_pages = ceil($total_intents / $per_page);
2726 3824
2727 - // Get intents
2728 - $intents = $wpdb->get_results($wpdb->prepare(
3825 + // Get intents (now called actions)
3826 + $actions = $wpdb->get_results($wpdb->prepare(
2729 3827 "SELECT * FROM $table_name WHERE $where LIMIT %d OFFSET %d",
2730 3828 $per_page, $offset
2731 3829 ));
2732 3830
@@ -2731,441 +3829,619 @@
2731 3829 ));
2732 3830
2733 3831 // Get callbacks
2734 3832 $available_callbacks = $this->mxchat_get_available_callbacks();
2735 -
3833 +
2736 3834 ?>
2737 3835 <div class="wrap mxchat-wrapper">
2738 3836 <!-- Hero Section -->
2739 3837 <div class="mxchat-hero">
2740 3838 <h1 class="mxchat-main-title">
2741 - <span class="mxchat-gradient-text">Intent</span> Manager
3839 + <span class="mxchat-gradient-text">Actions</span> Manager
2742 3840 </h1>
2743 3841 <p class="mxchat-hero-subtitle">
2744 - <?php esc_html_e('Create and manage custom intents to enhance your chatbots understanding and response capabilities.', 'mxchat'); ?>
3842 + <?php esc_html_e('Create and manage custom actions to enhance your chatbot\'s capabilities.', 'mxchat'); ?>
2745 3843 </p>
2746 3844 </div>
2747 3845
2748 - <div class="mxchat-content">
2749 - <!-- Add Intent Card -->
2750 - <div class="mxchat-card">
2751 - <div class="mxchat-card-header">
2752 - <h2><?php esc_html_e('Add New Intent', 'mxchat'); ?></h2>
2753 - </div>
2754 -
2755 - <div class="mxchat-intent-documentation">
2756 - <p>
2757 - <?php esc_html_e('We highly encourage users to quickly read our ', 'mxchat'); ?>
2758 - <a href="https://mxchat.ai/documentation/#intents" target="_blank" rel="noopener noreferrer">
2759 - <?php esc_html_e('documentation', 'mxchat'); ?>
2760 - </a>
2761 - <?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'); ?>
2762 - </p>
2763 - </div>
2764 -
2765 - <form id="mxchat-add-intent-form" method="post"
2766 - action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
2767 - <input type="hidden" name="action" value="mxchat_add_intent">
2768 - <?php wp_nonce_field('mxchat_add_intent_nonce'); ?>
2769 -
2770 - <div class="mxchat-form-group">
2771 - <label for="intent_label">
2772 - <?php esc_html_e('Intent Label (For your reference only)', 'mxchat'); ?>
2773 - </label>
2774 - <input name="intent_label" type="text" id="intent_label" required
2775 - class="mxchat-intent-input"
2776 - placeholder="<?php esc_attr_e('Example Email Capture: Newsletter Signup', 'mxchat'); ?>">
3846 + <!-- Actions Header with Search and Filter -->
3847 + <div class="mxchat-actions-header">
3848 + <div class="mxchat-actions-filters">
3849 + <form method="get" class="mxchat-search-form">
3850 + <input type="hidden" name="page" value="mxchat-actions">
3851 + <div class="mxchat-search-group">
3852 + <span class="dashicons dashicons-search"></span>
3853 + <input type="text" name="s" class="mxchat-search-input"
3854 + placeholder="<?php esc_attr_e('Search Actions', 'mxchat'); ?>"
3855 + value="<?php echo esc_attr($search_term); ?>">
2777 3856 </div>
2778 -
2779 - <div class="mxchat-form-group">
2780 - <label for="phrases">
2781 - <?php esc_html_e('Phrases (comma-separated)', 'mxchat'); ?>
2782 - </label>
2783 - <textarea name="phrases" id="phrases" rows="5" required
2784 - class="mxchat-intent-textarea"
2785 - 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>
2786 - </div>
2787 -
2788 - <div class="mxchat-form-group">
2789 - <label for="callback_function">
2790 - <?php esc_html_e('Callback Function', 'mxchat'); ?>
2791 - </label>
2792 - <select name="callback_function" id="callback_function"
2793 - class="mxchat-intent-select" required>
2794 - <option value="">
2795 - <?php esc_html_e('Select a Callback', 'mxchat'); ?>
3857 + <select name="callback_filter" class="mxchat-action-filter">
3858 + <option value=""><?php esc_html_e('All Action Types', 'mxchat'); ?></option>
3859 + <?php foreach ($available_callbacks as $function => $callback_data) :
3860 + $label = $callback_data['label']; ?>
3861 + <option value="<?php echo esc_attr($function); ?>"
3862 + <?php selected($callback_filter, $function); ?>>
3863 + <?php echo esc_html($label); ?>
2796 3864 </option>
2797 - <?php
2798 - $groups = $this->mxchat_get_available_callbacks(true);
2799 - foreach ($groups as $group_label => $group_callbacks) :
2800 - echo '<optgroup label="' . esc_attr($group_label) . '">';
2801 - foreach ($group_callbacks as $function => $data) :
2802 - $label = $data['label'];
2803 - $pro_only = $data['pro_only'];
2804 - $disabled = (!$this->is_activated && $pro_only) ? 'disabled' : '';
2805 - $label_suffix = (!$this->is_activated && $pro_only) ? ' (Pro Only)' : '';
2806 - ?>
2807 - <option value="<?php echo esc_attr($function); ?>"
2808 - <?php echo $disabled; ?>>
2809 - <?php echo esc_html($label . $label_suffix); ?>
2810 - </option>
2811 - <?php
2812 - endforeach;
2813 - echo '</optgroup>';
2814 - endforeach;
2815 - ?>
2816 - </select>
2817 - </div>
2818 -
2819 - <button type="submit" class="mxchat-button-primary">
2820 - <?php esc_html_e('Add Intent', 'mxchat'); ?>
3865 + <?php endforeach; ?>
3866 + </select>
3867 + <button type="submit" class="mxchat-button-secondary">
3868 + <?php esc_html_e('Filter', 'mxchat'); ?>
2821 3869 </button>
2822 3870 </form>
2823 3871 </div>
3872 + <div class="mxchat-actions-controls">
3873 + <button type="button" id="mxchat-add-action-btn" class="mxchat-button-primary">
3874 + <span class="dashicons dashicons-plus-alt"></span>
3875 + <?php esc_html_e('Add New Action', 'mxchat'); ?>
3876 + </button>
3877 + </div>
3878 + </div>
2824 3879
2825 - <!-- Manage Intents Card -->
2826 - <div class="mxchat-card">
2827 - <div class="mxchat-card-header">
2828 - <h2><?php esc_html_e('Manage Intents', 'mxchat'); ?></h2>
2829 - <div class="mxchat-header-actions">
2830 - <form method="get" class="mxchat-search-form">
2831 - <input type="hidden" name="page" value="mxchat-intents">
2832 - <div class="mxchat-search-group">
2833 - <span class="dashicons dashicons-search"></span>
2834 - <input type="text" name="s"
2835 - placeholder="<?php esc_attr_e('Search Intents', 'mxchat'); ?>"
2836 - value="<?php echo esc_attr($search_term); ?>">
2837 - <select name="callback_filter" class="mxchat-intent-filter">
2838 - <option value="">
2839 - <?php esc_html_e('All Callbacks', 'mxchat'); ?>
2840 - </option>
2841 - <?php foreach ($available_callbacks as $function => $callback_data) :
2842 - $label = $callback_data['label']; ?>
2843 - <option value="<?php echo esc_attr($function); ?>"
2844 - <?php selected($callback_filter, $function); ?>>
2845 - <?php echo esc_html($label); ?>
2846 - </option>
2847 - <?php endforeach; ?>
2848 - </select>
2849 - <button type="submit" class="mxchat-button-secondary">
2850 - <?php esc_html_e('Filter', 'mxchat'); ?>
2851 - </button>
3880 + <!-- Actions Grid Layout - All actions in a single grid -->
3881 + <div class="mxchat-actions-grid">
3882 + <div class="mxchat-cards-container">
3883 + <?php if (!empty($actions)) : ?>
3884 + <?php foreach ($actions as $action) :
3885 + $callback_function = $action->callback_function;
3886 + $callback_label = isset($available_callbacks[$callback_function]['label'])
3887 + ? $available_callbacks[$callback_function]['label']
3888 + : $callback_function;
3889 + $threshold_value = isset($action->similarity_threshold)
3890 + ? round($action->similarity_threshold * 100)
3891 + : 85;
3892 +
3893 + // Check if this is a form action
3894 + $is_form_action = strpos($action->intent_label, 'Form ') === 0;
3895 +
3896 + // Get action status (enabled/disabled) - default to true if column doesn't exist
3897 + $is_enabled = isset($action->enabled) ? (bool)$action->enabled : true;
3898 + ?>
3899 + <div class="mxchat-action-card <?php echo $is_form_action ? 'mxchat-form-action' : ''; ?>">
3900 + <div class="mxchat-card-header">
3901 + <div class="mxchat-card-title"><?php echo esc_html($action->intent_label); ?></div>
3902 + <div class="mxchat-card-toggle">
3903 + <label class="mxchat-switch">
3904 + <input type="checkbox" class="mxchat-action-toggle"
3905 + data-action-id="<?php echo esc_attr($action->id); ?>"
3906 + <?php checked($is_enabled); ?>>
3907 + <span class="mxchat-slider round"></span>
3908 + </label>
3909 + </div>
2852 3910 </div>
2853 - </form>
3911 +
3912 + <div class="mxchat-card-body">
3913 + <div class="mxchat-card-description">
3914 + <strong><?php esc_html_e('Type:', 'mxchat'); ?></strong>
3915 + <?php echo esc_html($callback_label); ?>
3916 + </div>
3917 +
3918 + <div class="mxchat-card-phrases">
3919 + <strong><?php esc_html_e('Trigger phrases:', 'mxchat'); ?></strong>
3920 + <div class="mxchat-phrases-preview">
3921 + <?php
3922 + // Check if the helper function exists, otherwise use a simple substring
3923 + if (method_exists($this, 'get_trimmed_phrases')) {
3924 + echo esc_html($this->get_trimmed_phrases($action->phrases));
3925 + } else {
3926 + echo esc_html(strlen($action->phrases) > 100 ?
3927 + substr($action->phrases, 0, 97) . '...' :
3928 + $action->phrases);
3929 + }
3930 + ?>
3931 + </div>
3932 + </div>
3933 +
3934 + <div class="mxchat-threshold-control">
3935 + <div class="mxchat-threshold-label">
3936 + <?php esc_html_e('Similarity Threshold:', 'mxchat'); ?>
3937 + <span class="mxchat-threshold-value"><?php echo esc_html($threshold_value); ?>%</span>
3938 + </div>
3939 + </div>
3940 + </div>
3941 +
3942 + <div class="mxchat-card-footer">
3943 + <?php
3944 + // Check if it's a form action
3945 + $is_form_action = preg_match('/Form (\d+)/', $action->intent_label, $form_matches);
3946 +
3947 + // Check if it's a recommendation flow action
3948 + $is_flow_action = preg_match('/Recommendation Flow (\d+)/', $action->intent_label, $flow_matches);
3949 +
3950 + if ($is_form_action) {
3951 + $form_id = isset($form_matches[1]) ? $form_matches[1] : '';
3952 + ?>
3953 + <a href="<?php echo esc_url(admin_url('admin.php?page=mxchat-forms&action=edit&form_id=' . $form_id)); ?>"
3954 + class="mxchat-button-primary">
3955 + <span class="dashicons dashicons-feedback"></span>
3956 + <?php esc_html_e('Edit Form', 'mxchat'); ?>
3957 + </a>
3958 + <?php } elseif ($is_flow_action) {
3959 + ?>
3960 + <a href="<?php echo esc_url(admin_url('admin.php?page=mxchat-smart-recommender')); ?>"
3961 + class="mxchat-button-primary">
3962 + <span class="dashicons dashicons-list-view"></span>
3963 + <?php esc_html_e('Manage Flows', 'mxchat'); ?>
3964 + </a>
3965 + <?php } else { ?>
3966 + <button type="button"
3967 + class="mxchat-button-secondary mxchat-edit-button"
3968 + data-action-id="<?php echo esc_attr($action->id); ?>"
3969 + data-phrases="<?php echo esc_attr($action->phrases); ?>"
3970 + data-label="<?php echo esc_attr($action->intent_label); ?>"
3971 + data-threshold="<?php echo esc_attr(round($action->similarity_threshold * 100)); ?>"
3972 + data-callback-function="<?php echo esc_attr($action->callback_function); ?>">
3973 + <span class="dashicons dashicons-edit"></span>
3974 + <?php esc_html_e('Edit', 'mxchat'); ?>
3975 + </button>
3976 + <form method="post"
3977 + action="<?php echo esc_url(admin_url('admin-post.php')); ?>"
3978 + class="mxchat-delete-form"
3979 + onsubmit="return confirm('<?php esc_attr_e('Are you sure you want to delete this action?', 'mxchat'); ?>');">
3980 + <?php wp_nonce_field('mxchat_delete_intent_nonce'); ?>
3981 + <input type="hidden" name="action" value="mxchat_delete_intent">
3982 + <input type="hidden" name="intent_id" value="<?php echo esc_attr($action->id); ?>">
3983 + <button type="submit" class="mxchat-button-text mxchat-delete-button">
3984 + <span class="dashicons dashicons-trash"></span>
3985 + <?php esc_html_e('Delete', 'mxchat'); ?>
3986 + </button>
3987 + </form>
3988 + <?php } ?>
3989 + </div>
3990 +
3991 +
3992 + </div>
3993 + <?php endforeach; ?>
3994 + <?php else : ?>
3995 + <!-- If no actions found -->
3996 + <div class="mxchat-no-actions">
3997 + <div class="mxchat-empty-state">
3998 + <span class="dashicons dashicons-format-chat"></span>
3999 + <h2><?php esc_html_e('No actions found', 'mxchat'); ?></h2>
4000 + <p><?php esc_html_e('Get started by creating your first action to enhance your chatbot.', 'mxchat'); ?></p>
4001 + <button type="button" id="mxchat-create-first-action" class="mxchat-button-primary">
4002 + <?php esc_html_e('Create Your First Action', 'mxchat'); ?>
4003 + </button>
4004 + </div>
2854 4005 </div>
2855 - </div>
4006 + <?php endif; ?>
4007 + </div>
4008 + </div>
4009 +
4010 + <?php if ($total_pages > 1) : ?>
4011 + <div class="mxchat-pagination">
4012 + <?php
4013 + echo paginate_links(array(
4014 + 'base' => add_query_arg('paged', '%#%'),
4015 + 'format' => '',
4016 + 'prev_text' => __('&laquo; Previous', 'mxchat'),
4017 + 'next_text' => __('Next &raquo;', 'mxchat'),
4018 + 'total' => $total_pages,
4019 + 'current' => $page
4020 + ));
4021 + ?>
4022 + </div>
4023 + <?php endif; ?>
4024 +
4025 +<!-- Add/Edit Action Modal with Step-Based Approach -->
4026 +<!-- Complete Modal HTML with Defined Groups Variable -->
4027 +<div id="mxchat-action-modal" class="mxchat-modal" style="display: none;">
4028 + <div class="mxchat-modal-content">
4029 + <span class="mxchat-modal-close">&times;</span>
2856 4030
2857 - <div class="mxchat-table-wrapper">
2858 - <table class="mxchat-records-table">
2859 - <thead>
2860 - <tr>
2861 - <th><?php esc_html_e('Intent Label', 'mxchat'); ?></th>
2862 - <th><?php esc_html_e('Phrases', 'mxchat'); ?></th>
2863 - <th><?php esc_html_e('Callback Function', 'mxchat'); ?></th>
2864 - <th><?php esc_html_e('Similarity Threshold', 'mxchat'); ?></th>
2865 - <th><?php esc_html_e('Actions', 'mxchat'); ?></th>
2866 - </tr>
2867 - </thead>
2868 - <tbody>
2869 - <?php if ($intents) :
2870 - foreach ($intents as $intent) :
2871 - $callback_function = $intent->callback_function;
2872 - $callback_label = isset($available_callbacks[$callback_function]['label'])
2873 - ? $available_callbacks[$callback_function]['label']
2874 - : $callback_function;
2875 - $threshold_value = isset($intent->similarity_threshold)
2876 - ? round($intent->similarity_threshold * 100)
2877 - : 85;
4031 + <form id="mxchat-action-form" method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
4032 + <!-- Dynamic nonce field -->
4033 + <div id="action-nonce-container">
4034 + <?php wp_nonce_field('mxchat_add_intent_nonce', 'add_intent_nonce'); ?>
4035 + </div>
4036 + <input type="hidden" name="action" id="form_action_type" value="mxchat_add_intent">
4037 + <input type="hidden" name="intent_id" id="edit_action_id" value="">
4038 + <input type="hidden" name="callback_function" id="callback_function" value="">
2878 4039
2879 - // Check if this is a form intent (its intent_label starts with "Form ")
2880 - $is_form_intent = strpos($intent->intent_label, 'Form ') === 0;
2881 - ?>
2882 - <tr<?php echo $is_form_intent ? ' class="mxchat-form-intent"' : ''; ?>>
2883 - <td>
2884 - <?php
2885 - if ($is_form_intent) {
2886 - // Attempt to extract the form ID from the intent_label.
2887 - preg_match('/Form (\d+)/', $intent->intent_label, $matches);
2888 - $form_id = isset($matches[1]) ? intval($matches[1]) : 0;
2889 - if ($form_id) {
2890 - global $wpdb;
2891 - $forms_table = $wpdb->prefix . 'mxchat_forms';
2892 - $form = $wpdb->get_row($wpdb->prepare("SELECT title FROM $forms_table WHERE id = %d", $form_id));
2893 - if ($form && !empty($form->title)) {
2894 - echo esc_html($form->title);
2895 - } else {
2896 - echo esc_html($intent->intent_label);
2897 - }
2898 - } else {
2899 - echo esc_html($intent->intent_label);
2900 - }
2901 - echo ' <span class="mxchat-badge">Form Intent</span>';
2902 - } else {
2903 - echo esc_html($intent->intent_label);
2904 - }
2905 - ?>
2906 - </td>
2907 - <td class="mxchat-content-cell">
2908 - <?php echo esc_html($intent->phrases); ?>
2909 - </td>
2910 - <td><?php echo esc_html($callback_label); ?></td>
2911 - <td>
2912 - <!-- Similarity threshold adjustment (shown for all intents) -->
2913 - <form method="post"
2914 - action="<?php echo esc_url(admin_url('admin-post.php')); ?>"
2915 - class="mxchat-threshold-form">
2916 - <?php wp_nonce_field('mxchat_update_intent_threshold_nonce'); ?>
2917 - <input type="hidden" name="action"
2918 - value="mxchat_update_intent_threshold">
2919 - <input type="hidden" name="intent_id"
2920 - value="<?php echo esc_attr($intent->id); ?>">
2921 - <div class="mxchat-slider-group">
2922 - <input type="range"
2923 - name="intent_threshold"
2924 - id="intent_threshold_<?php echo esc_attr($intent->id); ?>"
2925 - min="70"
2926 - max="95"
2927 - value="<?php echo esc_attr($threshold_value); ?>"
2928 - class="mxchat-intent-slider"
2929 - oninput="document.getElementById('threshold_output_<?php echo esc_attr($intent->id); ?>').value = this.value + '%'">
2930 - <output id="threshold_output_<?php echo esc_attr($intent->id); ?>"
2931 - class="mxchat-intent-output">
2932 - <?php echo esc_html($threshold_value); ?>%
2933 - </output>
2934 - </div>
2935 - <button type="submit" class="mxchat-button-icon">
2936 - <span class="dashicons dashicons-saved"></span>
2937 - </button>
2938 - </form>
2939 - </td>
2940 - <td class="mxchat-actions-cell">
2941 - <?php if (!$is_form_intent) : ?>
2942 - <!-- Standard intents: allow edit and delete -->
2943 - <button type="button"
2944 - class="mxchat-button-icon mxchat-edit-button"
2945 - data-intent-id="<?php echo esc_attr($intent->id); ?>"
2946 - data-phrases="<?php echo esc_attr($intent->phrases); ?>">
2947 - <span class="dashicons dashicons-edit"></span>
2948 - </button>
2949 - <form method="post"
2950 - action="<?php echo esc_url(admin_url('admin-post.php')); ?>"
2951 - class="mxchat-delete-form"
2952 - onsubmit="return confirm('<?php esc_attr_e('Are you sure you want to delete this intent?', 'mxchat'); ?>');">
2953 - <?php wp_nonce_field('mxchat_delete_intent_nonce'); ?>
2954 - <input type="hidden" name="action" value="mxchat_delete_intent">
2955 - <input type="hidden" name="intent_id"
2956 - value="<?php echo esc_attr($intent->id); ?>">
2957 - <button type="submit" class="mxchat-button-icon">
2958 - <span class="dashicons dashicons-trash"></span>
2959 - </button>
2960 - </form>
2961 - <?php else : ?>
2962 - <!-- Form intents: show manage in forms button -->
2963 - <?php
2964 - preg_match('/Form (\d+)/', $intent->intent_label, $matches);
2965 - $form_id = isset($matches[1]) ? $matches[1] : '';
2966 - ?>
2967 - <a href="<?php echo esc_url(admin_url('admin.php?page=mxchat-forms&action=edit&form_id=' . $form_id)); ?>"
2968 - class="mxchat-button-secondary">
2969 - <?php esc_html_e('Manage in Forms', 'mxchat'); ?>
2970 - </a>
2971 - <?php endif; ?>
2972 - </td>
2973 - </tr>
2974 - <?php endforeach;
2975 - else : ?>
2976 - <tr>
2977 - <td colspan="5" class="mxchat-no-records">
2978 - <?php esc_html_e('No intents found.', 'mxchat'); ?>
2979 - </td>
2980 - </tr>
2981 - <?php endif; ?>
2982 - </tbody>
2983 - </table>
4040 + <!-- Step 1: Action Type Selection -->
4041 + <div id="mxchat-action-step-1" class="mxchat-action-step active">
4042 + <div class="mxchat-step-indicator">
4043 + <div class="mxchat-step-number">1</div>
4044 + <div class="mxchat-step-title"><?php esc_html_e('Select Action Type', 'mxchat'); ?></div>
2984 4045 </div>
2985 -
2986 - <?php if ($total_pages > 1) : ?>
2987 - <div class="mxchat-pagination">
4046 +
4047 + <div id="mxchat-action-type-selector" class="mxchat-action-type-selector">
4048 + <div class="mxchat-action-type-search">
4049 + <span class="dashicons dashicons-search"></span>
4050 + <input type="text" id="action-type-search" placeholder="<?php esc_attr_e('Search action types...', 'mxchat'); ?>" class="mxchat-action-type-search-input">
4051 + </div>
4052 +
4053 + <?php
4054 + // Get the callbacks - IMPORTANT: Define the $groups variable here
4055 + $groups = $this->mxchat_get_available_callbacks(true, true);
4056 + ?>
4057 +
4058 + <div class="mxchat-action-type-categories">
4059 + <button type="button" class="mxchat-category-button active" data-category="all"><?php esc_html_e('All', 'mxchat'); ?></button>
4060 + <?php
4061 + // Get unique categories from the defined groups
4062 + foreach ($groups as $group_label => $group_callbacks) :
4063 + $category_slug = sanitize_title($group_label);
4064 + ?>
4065 + <button type="button" class="mxchat-category-button" data-category="<?php echo esc_attr($category_slug); ?>"><?php echo esc_html($group_label); ?></button>
4066 + <?php endforeach; ?>
4067 + </div>
4068 +
4069 + <div class="mxchat-action-types-grid">
2988 4070 <?php
2989 - echo paginate_links(array(
2990 - 'base' => add_query_arg('paged', '%#%'),
2991 - 'format' => '',
2992 - 'prev_text' => __('&laquo; Previous', 'mxchat'),
2993 - 'next_text' => __('Next &raquo;', 'mxchat'),
2994 - 'total' => $total_pages,
2995 - 'current' => $page
2996 - ));
2997 - ?>
4071 + // Generate action cards from available callbacks
4072 + foreach ($groups as $group_label => $group_callbacks) :
4073 + $category_slug = sanitize_title($group_label);
4074 +
4075 + foreach ($group_callbacks as $function => $data) :
4076 + $label = $data['label'];
4077 + $pro_only = $data['pro_only'];
4078 + $icon = isset($data['icon']) ? $data['icon'] : 'admin-generic';
4079 + $description = isset($data['description']) ? $data['description'] : '';
4080 + $is_addon = isset($data['addon']) && $data['addon'] !== false;
4081 + $addon_name = isset($data['addon_name']) ? $data['addon_name'] : '';
4082 + $is_installed = isset($data['installed']) ? $data['installed'] : true;
4083 +
4084 + // Determine card status and styling
4085 + $card_class = 'mxchat-action-type-card';
4086 + $icon_class = 'mxchat-action-type-icon';
4087 + $status_badge = '';
4088 +
4089 + if ($pro_only && !$this->is_activated) {
4090 + // Pro feature but no Pro license
4091 + $icon_class .= ' pro-feature';
4092 + $status_badge = '<span class="mxchat-pro-badge">' . esc_html__('Pro', 'mxchat') . '</span>';
4093 + }
4094 +
4095 + if ($is_addon && !$is_installed) {
4096 + // Add-on not installed
4097 + $card_class .= ' not-installed';
4098 + $status_badge .= '<span class="mxchat-addon-badge">' . esc_html__('Add-on Required', 'mxchat') . '</span>';
4099 + }
4100 +
4101 + // Default description if none provided
4102 + if (empty($description)) {
4103 + $description = sprintf(
4104 + esc_html__('Use the %s action in your chatbot', 'mxchat'),
4105 + $label
4106 + );
4107 + }
4108 + ?>
4109 + <div class="<?php echo esc_attr($card_class); ?>"
4110 + data-category="<?php echo esc_attr($category_slug); ?>"
4111 + data-value="<?php echo esc_attr($function); ?>"
4112 + data-label="<?php echo esc_attr($label); ?>"
4113 + data-pro="<?php echo $pro_only ? 'true' : 'false'; ?>"
4114 + data-addon="<?php echo esc_attr($is_addon ? $data['addon'] : ''); ?>"
4115 + data-installed="<?php echo $is_installed ? 'true' : 'false'; ?>">
4116 + <div class="<?php echo esc_attr($icon_class); ?>">
4117 + <span class="dashicons dashicons-<?php echo esc_attr($icon); ?>"></span>
4118 + </div>
4119 + <div class="mxchat-action-type-info">
4120 + <h4><?php echo esc_html($label); ?></h4>
4121 + <p><?php echo esc_html($description); ?></p>
4122 + <?php if (!empty($status_badge)) : ?>
4123 + <?php echo $status_badge; ?>
4124 + <?php endif; ?>
4125 +
4126 + <?php if ($is_addon && !$is_installed) : ?>
4127 + <div class="mxchat-addon-info">
4128 + <?php echo esc_html(sprintf(
4129 + __('Requires %s', 'mxchat'),
4130 + $addon_name
4131 + )); ?>
4132 + </div>
4133 + <?php endif; ?>
4134 + </div>
4135 + </div>
4136 + <?php endforeach;
4137 + endforeach; ?>
2998 4138 </div>
2999 - <?php endif; ?>
4139 + </div>
4140 +
4141 + <div class="mxchat-modal-actions">
4142 + <button type="button" class="mxchat-button-secondary mxchat-modal-cancel">
4143 + <?php esc_html_e('Cancel', 'mxchat'); ?>
4144 + </button>
4145 + </div>
3000 4146 </div>
3001 - </div>
3002 4147
3003 - <!-- Edit Modal -->
3004 -<div id="mxchat-edit-modal" class="mxchat-modal" style="display: none;">
3005 - <div class="mxchat-modal-content">
3006 - <span class="mxchat-modal-close">&times;</span>
3007 - <div class="mxchat-card-header">
3008 - <h2><?php esc_html_e('Edit Intent Phrases', 'mxchat'); ?></h2>
3009 - </div>
4148 + <!-- Step 2: Action Configuration -->
4149 + <div id="mxchat-action-step-2" class="mxchat-action-step">
4150 + <div class="mxchat-step-indicator">
4151 + <div class="mxchat-step-number">2</div>
4152 + <div class="mxchat-step-title"><?php esc_html_e('Configure Action', 'mxchat'); ?></div>
4153 + </div>
4154 +
4155 + <div class="mxchat-selected-action">
4156 + <button type="button" class="mxchat-back-button" id="mxchat-back-to-step-1">
4157 + <span class="dashicons dashicons-arrow-left-alt"></span>
4158 + <?php esc_html_e('Back to Action Types', 'mxchat'); ?>
4159 + </button>
4160 + <div class="mxchat-selected-action-info">
4161 + <div id="selected-action-icon" class="mxchat-action-type-icon">
4162 + <span class="dashicons dashicons-admin-generic"></span>
4163 + </div>
4164 + <div class="mxchat-selected-action-details">
4165 + <h3 id="selected-action-title"><?php esc_html_e('Selected Action', 'mxchat'); ?></h3>
4166 + <p id="selected-action-description"><?php esc_html_e('Configure this action for your chatbot', 'mxchat'); ?></p>
4167 + </div>
4168 + </div>
4169 + </div>
4170 +
4171 + <div class="mxchat-form-group">
4172 + <label for="intent_label">
4173 + <?php esc_html_e('Action Label (For your reference only)', 'mxchat'); ?>
4174 + </label>
4175 + <input name="intent_label" type="text" id="intent_label" required
4176 + class="mxchat-intent-input"
4177 + placeholder="<?php esc_attr_e('Example: Newsletter Signup', 'mxchat'); ?>">
4178 + </div>
3010 4179
3011 - <form id="mxchat-edit-intent-form" method="post"
3012 - action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
3013 - <?php wp_nonce_field('mxchat_edit_intent_nonce'); ?>
3014 - <input type="hidden" name="action" value="mxchat_edit_intent">
3015 - <input type="hidden" name="intent_id" id="edit_intent_id">
4180 + <div class="mxchat-form-group">
4181 + <label for="phrases">
4182 + <?php esc_html_e('Trigger Phrases (comma-separated)', 'mxchat'); ?>
4183 + </label>
4184 + <textarea name="phrases" id="action_phrases" rows="5" required
4185 + class="mxchat-intent-textarea"
4186 + placeholder="<?php esc_attr_e('Example: sign me up, subscribe me, I want to join, add me to the newsletter', 'mxchat'); ?>"></textarea>
4187 + </div>
3016 4188
3017 - <div class="mxchat-form-group">
3018 - <label for="edit_phrases">
3019 - <?php esc_html_e('Phrases (comma-separated)', 'mxchat'); ?>
3020 - </label>
3021 - <textarea name="phrases"
3022 - id="edit_phrases"
3023 - rows="5"
3024 - class="mxchat-intent-textarea"
3025 - required></textarea>
3026 - </div>
4189 + <div class="mxchat-form-group">
4190 + <label for="similarity_threshold">
4191 + <?php esc_html_e('Similarity Threshold', 'mxchat'); ?>
4192 + <span class="mxchat-threshold-value-display">85%</span>
4193 + </label>
4194 + <div class="mxchat-slider-group modal-slider">
4195 + <input type="range"
4196 + name="similarity_threshold"
4197 + id="similarity_threshold"
4198 + min="70"
4199 + max="95"
4200 + value="85"
4201 + class="mxchat-intent-slider"
4202 + oninput="document.querySelector('.mxchat-threshold-value-display').textContent = this.value + '%'">
4203 + </div>
4204 + <div class="mxchat-threshold-hint">
4205 + <?php esc_html_e('Lower values make the action trigger more easily. Higher values require more exact matches.', 'mxchat'); ?>
4206 + </div>
4207 + </div>
3027 4208
3028 - <div class="mxchat-modal-actions">
3029 - <button type="submit" class="mxchat-button-primary">
3030 - <?php esc_html_e('Update Phrases', 'mxchat'); ?>
3031 - </button>
3032 - <button type="button" class="mxchat-button-secondary mxchat-modal-cancel">
3033 - <?php esc_html_e('Cancel', 'mxchat'); ?>
3034 - </button>
4209 + <div class="mxchat-modal-actions">
4210 + <button type="button" class="mxchat-button-secondary mxchat-modal-cancel">
4211 + <?php esc_html_e('Cancel', 'mxchat'); ?>
4212 + </button>
4213 + <button type="submit" class="mxchat-button-primary" id="mxchat-save-action-btn">
4214 + <?php esc_html_e('Save Action', 'mxchat'); ?>
4215 + </button>
4216 + </div>
3035 4217 </div>
3036 4218 </form>
3037 4219 </div>
3038 4220 </div>
3039 4221
3040 - </div><!-- .mxchat-content -->
4222 +<div id="mxchat-action-loading" class="mxchat-action-loading" style="display: none;">
4223 + <div class="mxchat-action-loading-spinner"></div>
4224 + <div class="mxchat-action-loading-text">
4225 + <?php esc_html_e('Saving action, please wait...', 'mxchat'); ?>
4226 + </div>
4227 +</div>
3041 4228 </div><!-- .mxchat-wrapper -->
4229 + <?php
4230 +}
3042 4231
3043 - <div id="mxchat-intent-loading" class="mxchat-intent-loading" style="display: none;">
3044 - <div class="mxchat-intent-loading-spinner"></div>
3045 - <div class="mxchat-intent-loading-text">
3046 - <?php esc_html_e('Saving intent, please wait...', 'mxchat'); ?>
3047 - </div>
3048 - </div>
3049 -<?php
4232 +/**
4233 + * Helper method to trim phrases for display
4234 + * Adding this in case it doesn't exist in your class
4235 + */
4236 +private function get_trimmed_phrases($phrases, $max_length = 100) {
4237 + if (strlen($phrases) <= $max_length) {
4238 + return $phrases;
4239 + }
4240 +
4241 + $trimmed = substr($phrases, 0, $max_length);
4242 + $last_comma = strrpos($trimmed, ',');
4243 +
4244 + if ($last_comma !== false) {
4245 + $trimmed = substr($trimmed, 0, $last_comma);
4246 + }
4247 +
4248 + return $trimmed . '...';
3050 4249 }
3051 4250
4251 +/**
4252 + * AJAX handler for toggling an action on/off
4253 + */
4254 +public function mxchat_toggle_action() {
4255 + // Check nonce
4256 + if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'mxchat_actions_nonce')) {
4257 + wp_send_json_error(array('message' => 'Security check failed'));
4258 + return;
4259 + }
4260 +
4261 + // Check permissions
4262 + if (!current_user_can('manage_options')) {
4263 + wp_send_json_error(array('message' => 'Permission denied'));
4264 + return;
4265 + }
4266 +
4267 + // Validate params
4268 + $intent_id = isset($_POST['intent_id']) ? intval($_POST['intent_id']) : 0;
4269 + $enabled = isset($_POST['enabled']) ? (bool)$_POST['enabled'] : false;
4270 +
4271 + if (!$intent_id) {
4272 + wp_send_json_error(array('message' => 'Invalid action ID'));
4273 + return;
4274 + }
4275 +
4276 + // Update the intent/action status in the database
4277 + global $wpdb;
4278 + $table_name = $wpdb->prefix . 'mxchat_intents';
4279 +
4280 + // Using the 'enabled' field - add this field if it doesn't exist
4281 + $result = $wpdb->update(
4282 + $table_name,
4283 + array('enabled' => $enabled ? 1 : 0),
4284 + array('id' => $intent_id),
4285 + array('%d'),
4286 + array('%d')
4287 + );
4288 +
4289 + if ($result === false) {
4290 + wp_send_json_error(array('message' => 'Database error'));
4291 + return;
4292 + }
4293 +
4294 + wp_send_json_success();
4295 +}
3052 4296
4297 +/**
4298 + * Add the 'enabled' column to the intents table if it doesn't exist
4299 + * Call this during plugin activation or update
4300 + */
4301 +public function mxchat_add_enabled_column_to_intents() {
4302 + global $wpdb;
4303 + $table_name = $wpdb->prefix . 'mxchat_intents';
4304 +
4305 + // Check if the column already exists
4306 + $columns = $wpdb->get_results("SHOW COLUMNS FROM $table_name LIKE 'enabled'");
4307 +
4308 + if (empty($columns)) {
4309 + // Add the column with default value of 1 (enabled)
4310 + $wpdb->query("ALTER TABLE $table_name ADD COLUMN enabled TINYINT(1) NOT NULL DEFAULT 1");
4311 + }
4312 +}
3053 4313
3054 4314 /**
3055 - * Handle editing of intent phrases
4315 + * Handle embedding generation errors using existing admin notice system
4316 + *
4317 + * @param string $message Error message to display
4318 + * @param bool $redirect Whether to redirect back to the actions page
4319 + * @return void
4320 + */
4321 +private function handle_embedding_error($message, $redirect = true) {
4322 + // Store the error message in the existing transient
4323 + set_transient('mxchat_admin_notice_error', $message, 60);
4324 +
4325 + if ($redirect) {
4326 + // Redirect back to the actions page
4327 + $redirect_url = add_query_arg(
4328 + array(
4329 + 'page' => 'mxchat-actions'
4330 + ),
4331 + admin_url('admin.php')
4332 + );
4333 + wp_safe_redirect($redirect_url);
4334 + exit;
4335 + }
4336 +}
4337 +
4338 +/**
4339 + * Handle editing of intent phrases - with improved error handling
3056 4340 *
3057 4341 * @since 1.0.0
3058 4342 * @return void
3059 4343 */
3060 -public function handle_edit_intent() {
3061 - // Verify nonce and user capabilities
3062 - if (!isset($_POST['_wpnonce']) || !wp_verify_nonce($_POST['_wpnonce'], 'mxchat_edit_intent_nonce')) {
3063 - wp_die(esc_html__('Security check failed.', 'mxchat'));
3064 - }
3065 -
4344 +public function mxchat_handle_edit_intent() {
4345 + // Security checks (nonce and permissions)
3066 4346 if (!current_user_can('manage_options')) {
3067 - wp_die(esc_html__('You do not have permission to perform this action.', 'mxchat'));
4347 + wp_die(esc_html__('Unauthorized user', 'mxchat'));
3068 4348 }
4349 + check_admin_referer('mxchat_edit_intent');
3069 4350
3070 - // Validate and sanitize input
4351 + // Get POST data
3071 4352 $intent_id = isset($_POST['intent_id']) ? absint($_POST['intent_id']) : 0;
3072 - if (!$intent_id) {
3073 - wp_die(esc_html__('Invalid intent ID.', 'mxchat'));
3074 - }
4353 + $intent_label = isset($_POST['intent_label']) ? sanitize_text_field($_POST['intent_label']) : '';
4354 + $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
4355 + $threshold_percentage = isset($_POST['similarity_threshold']) ? intval($_POST['similarity_threshold']) : 85;
4356 + $similarity_threshold = min(95, max(70, $threshold_percentage)) / 100; // Convert to 0.70–0.95
3075 4357
3076 - $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
3077 - if (empty($phrases_input)) {
3078 - wp_die(esc_html__('Phrases cannot be empty.', 'mxchat'));
4358 + // Validate inputs
4359 + if (!$intent_id || empty($intent_label) || empty($phrases_input)) {
4360 + $this->handle_embedding_error(__('Invalid input. Please ensure all fields are filled out.', 'mxchat'));
4361 + return;
3079 4362 }
3080 4363
3081 - // Process phrases the same way as in intent creation
3082 4364 $phrases_array = array_map('sanitize_text_field', array_filter(array_map('trim', explode(',', $phrases_input))));
3083 -
3084 4365 if (empty($phrases_array)) {
3085 - wp_die(esc_html__('Please enter at least one valid phrase.', 'mxchat'));
4366 + $this->handle_embedding_error(__('Please enter at least one valid phrase.', 'mxchat'));
4367 + return;
3086 4368 }
3087 4369
3088 - // Generate embeddings and combine them
4370 + // Generate embeddings with improved error handling
3089 4371 $vectors = [];
4372 + $failed_phrases = [];
4373 +
3090 4374 foreach ($phrases_array as $phrase) {
3091 4375 $embedding_vector = $this->mxchat_generate_embedding($phrase, $this->options['api_key']);
3092 4376 if (is_array($embedding_vector)) {
3093 4377 $vectors[] = $embedding_vector;
3094 4378 } else {
3095 - wp_die(esc_html__('Error generating embedding for phrase: ', 'mxchat') . esc_html($phrase));
4379 + $failed_phrases[] = $phrase;
3096 4380 }
3097 4381 }
3098 -
4382 +
4383 + if (!empty($failed_phrases)) {
4384 + $this->handle_embedding_error(
4385 + sprintf(
4386 + __('Error generating embeddings for phrases: %s. Check your embedding API.', 'mxchat'),
4387 + implode(', ', $failed_phrases)
4388 + )
4389 + );
4390 + return;
4391 + }
4392 +
3099 4393 if (empty($vectors)) {
3100 - wp_die(esc_html__('No valid embeddings generated. Please check your phrases.', 'mxchat'));
4394 + $this->handle_embedding_error(__('No valid embeddings generated. Please check your phrases.', 'mxchat'));
4395 + return;
3101 4396 }
3102 4397
3103 - // Create combined vector just like in intent creation
3104 4398 $combined_vector = $this->mxchat_average_vectors($vectors);
3105 4399 $serialized_vector = maybe_serialize($combined_vector);
3106 4400
4401 + // Update the database
3107 4402 global $wpdb;
3108 4403 $table_name = $wpdb->prefix . 'mxchat_intents';
3109 4404
3110 - // Update the intent with both new phrases and the combined vector
3111 4405 $result = $wpdb->update(
3112 4406 $table_name,
3113 4407 array(
4408 + 'intent_label' => $intent_label,
3114 4409 'phrases' => implode(', ', $phrases_array),
3115 - 'embedding_vector' => $serialized_vector
4410 + 'embedding_vector' => $serialized_vector,
4411 + 'similarity_threshold' => $similarity_threshold
3116 4412 ),
3117 4413 array('id' => $intent_id),
3118 - array('%s', '%s'),
3119 - array('%d')
4414 + array('%s', '%s', '%s', '%f'), // Format: string, string, string, float
4415 + array('%d') // Where format: integer
3120 4416 );
3121 4417
3122 4418 if (false === $result) {
3123 - wp_die(esc_html__('Failed to update intent.', 'mxchat'));
4419 + $this->handle_embedding_error(__('Failed to update action in database.', 'mxchat'));
4420 + return;
3124 4421 }
3125 4422
3126 - // Redirect back to the intents page with a success message
4423 + // Set success message and redirect
4424 + set_transient('mxchat_admin_notice_success', __('Intent updated successfully!', 'mxchat'), 60);
4425 +
3127 4426 $redirect_url = add_query_arg(
3128 4427 array(
3129 - 'page' => 'mxchat-intents',
3130 - 'updated' => 'true'
4428 + 'page' => 'mxchat-actions'
3131 4429 ),
3132 4430 admin_url('admin.php')
3133 4431 );
3134 -
3135 4432 wp_safe_redirect($redirect_url);
3136 4433 exit;
3137 4434 }
3138 -public function mxchat_handle_update_intent_threshold() {
3139 - if ( ! current_user_can( 'manage_options' ) ) {
3140 - wp_die( esc_html__('Unauthorized user', 'mxchat') );
3141 - }
3142 4435
3143 - check_admin_referer('mxchat_update_intent_threshold_nonce');
3144 -
3145 - if (isset($_POST['intent_id'], $_POST['intent_threshold'])) {
3146 - global $wpdb;
3147 - $table_name = $wpdb->prefix . 'mxchat_intents';
3148 - $intent_id = intval($_POST['intent_id']);
3149 - $threshold_percentage = max(70, min(95, intval($_POST['intent_threshold'])));
3150 - $similarity_threshold = $threshold_percentage / 100;
3151 -
3152 - $wpdb->update(
3153 - $table_name,
3154 - ['similarity_threshold' => $similarity_threshold],
3155 - ['id' => $intent_id],
3156 - ['%f'],
3157 - ['%d']
3158 - );
3159 - }
3160 -
3161 - wp_safe_redirect(admin_url('admin.php?page=mxchat-intents'));
3162 - exit;
3163 -}
3164 -
4436 +/**
4437 + * Handle adding new intent - with improved error handling
4438 + *
4439 + * @return void
4440 + */
3165 4441 public function mxchat_handle_add_intent() {
3166 - if ( ! current_user_can( 'manage_options' ) ) {
3167 - wp_die( esc_html__('Unauthorized user', 'mxchat') );
4442 + if (!current_user_can('manage_options')) {
4443 + wp_die(esc_html__('Unauthorized user', 'mxchat'));
3168 4444 }
3169 4445
3170 4446 check_admin_referer('mxchat_add_intent_nonce');
3171 4447
@@ -3177,120 +4453,484 @@
3177 4453 $callback_function = isset($_POST['callback_function']) ? sanitize_text_field($_POST['callback_function']) : '';
3178 4454 $default_threshold = 0.85;
3179 4455
3180 4456 if (empty($intent_label) || empty($callback_function) || empty($phrases_input)) {
3181 - wp_die( esc_html__('Invalid input. Please ensure all fields are filled out.', 'mxchat') );
4457 + $this->handle_embedding_error(__('Invalid input. Please ensure all fields are filled out.', 'mxchat'));
4458 + return;
3182 4459 }
3183 4460
3184 4461 $available_callbacks = $this->mxchat_get_available_callbacks();
3185 4462
3186 4463 if (!array_key_exists($callback_function, $available_callbacks)) {
3187 - wp_die( esc_html__('Invalid callback function selected.', 'mxchat') );
4464 + $this->handle_embedding_error(__('Invalid callback function selected.', 'mxchat'));
4465 + return;
3188 4466 }
3189 4467
3190 4468 $is_pro_only = $available_callbacks[$callback_function]['pro_only'];
3191 4469 if ($is_pro_only && !$this->is_activated) {
3192 - wp_die( esc_html__('This callback function is available in the Pro version only.', 'mxchat') );
4470 + $this->handle_embedding_error(__('This callback function is available in the Pro version only.', 'mxchat'));
4471 + return;
3193 4472 }
3194 4473
3195 4474 $phrases_array = array_map('sanitize_text_field', array_filter(array_map('trim', explode(',', $phrases_input))));
3196 4475
3197 4476 if (empty($phrases_array)) {
3198 - wp_die( esc_html__('Please enter at least one valid phrase.', 'mxchat') );
4477 + $this->handle_embedding_error(__('Please enter at least one valid phrase.', 'mxchat'));
4478 + return;
3199 4479 }
3200 4480
4481 + // Generate embeddings with improved error handling
3201 4482 $vectors = [];
4483 + $failed_phrases = [];
4484 +
3202 4485 foreach ($phrases_array as $phrase) {
3203 4486 $embedding_vector = $this->mxchat_generate_embedding($phrase, $this->options['api_key']);
3204 4487 if (is_array($embedding_vector)) {
3205 4488 $vectors[] = $embedding_vector;
3206 4489 } else {
3207 - wp_die( esc_html__('Error generating embedding for phrase: ', 'mxchat') . esc_html($phrase) );
4490 + $failed_phrases[] = $phrase;
3208 4491 }
3209 4492 }
4493 +
4494 + if (!empty($failed_phrases)) {
4495 + $this->handle_embedding_error(
4496 + sprintf(
4497 + __('Error generating embeddings for phrases: %s. Check your embedding API.', 'mxchat'),
4498 + implode(', ', $failed_phrases)
4499 + )
4500 + );
4501 + return;
4502 + }
3210 4503
3211 - if (!empty($vectors)) {
3212 - $combined_vector = $this->mxchat_average_vectors($vectors);
3213 - $serialized_vector = maybe_serialize($combined_vector);
4504 + if (empty($vectors)) {
4505 + $this->handle_embedding_error(__('No valid embeddings generated. Please check your phrases.', 'mxchat'));
4506 + return;
4507 + }
3214 4508
3215 - $result = $wpdb->insert($table_name, [
3216 - 'intent_label' => $intent_label,
3217 - 'phrases' => implode(', ', $phrases_array),
3218 - 'embedding_vector' => $serialized_vector,
3219 - 'callback_function' => $callback_function,
3220 - 'similarity_threshold' => $default_threshold,
3221 - ]);
4509 + $combined_vector = $this->mxchat_average_vectors($vectors);
4510 + $serialized_vector = maybe_serialize($combined_vector);
3222 4511
3223 - if ($result === false) {
3224 - wp_die( esc_html__('Database error: ', 'mxchat') . esc_html($wpdb->last_error) );
3225 - }
3226 - } else {
3227 - wp_die( esc_html__('No valid embeddings generated. Please check your phrases.', 'mxchat') );
4512 + $result = $wpdb->insert($table_name, [
4513 + 'intent_label' => $intent_label,
4514 + 'phrases' => implode(', ', $phrases_array),
4515 + 'embedding_vector' => $serialized_vector,
4516 + 'callback_function' => $callback_function,
4517 + 'similarity_threshold' => $default_threshold,
4518 + ]);
4519 +
4520 + if ($result === false) {
4521 + $this->handle_embedding_error(__('Database error: ', 'mxchat') . $wpdb->last_error);
4522 + return;
3228 4523 }
3229 4524
3230 - wp_safe_redirect(admin_url('admin.php?page=mxchat-intents'));
4525 + // Set success message
4526 + set_transient('mxchat_admin_notice_success', __('New intent added successfully!', 'mxchat'), 60);
4527 +
4528 + wp_safe_redirect(admin_url('admin.php?page=mxchat-actions'));
3231 4529 exit;
3232 4530 }
3233 4531
3234 4532
4533 +/**
4534 + * Update intent threshold with AJAX support
4535 + */
4536 +public function mxchat_update_intent_threshold() {
4537 + // Check permissions
4538 + if (!current_user_can('manage_options')) {
4539 + if (wp_doing_ajax()) {
4540 + wp_send_json_error(array('message' => 'Unauthorized user'));
4541 + return;
4542 + }
4543 + wp_die(esc_html__('Unauthorized user', 'mxchat'));
4544 + }
4545 +
4546 + // Verify nonce
4547 + check_admin_referer('mxchat_update_intent_threshold_nonce');
4548 +
4549 + // Process the update if we have valid data
4550 + if (isset($_POST['intent_id'], $_POST['intent_threshold'])) {
4551 + global $wpdb;
4552 + $table_name = $wpdb->prefix . 'mxchat_intents';
4553 + $intent_id = intval($_POST['intent_id']);
4554 + $threshold_percentage = max(70, min(95, intval($_POST['intent_threshold'])));
4555 + $similarity_threshold = $threshold_percentage / 100;
4556 +
4557 + $result = $wpdb->update(
4558 + $table_name,
4559 + ['similarity_threshold' => $similarity_threshold],
4560 + ['id' => $intent_id],
4561 + ['%f'],
4562 + ['%d']
4563 + );
4564 +
4565 + // Handle AJAX requests
4566 + if (wp_doing_ajax()) {
4567 + if ($result === false) {
4568 + wp_send_json_error(array('message' => 'Failed to update threshold'));
4569 + } else {
4570 + wp_send_json_success(array('threshold' => $threshold_percentage));
4571 + }
4572 + return;
4573 + }
4574 + }
4575 +
4576 + // Redirect for regular form submissions
4577 + wp_safe_redirect(admin_url('admin.php?page=mxchat-actions&updated=true'));
4578 + exit;
4579 +}
3235 4580
3236 4581
3237 4582
3238 -
3239 -private function mxchat_get_available_callbacks($grouped = false) {
3240 - $callbacks = [
3241 - 'mxchat_handle_email_capture' => [
3242 - 'label' => __('Email Capture', 'mxchat'),
3243 - 'pro_only' => false,
3244 - 'group' => __('Customer Engagement', 'mxchat'),
3245 - ],
3246 - 'mxchat_generate_image' => [
3247 - 'label' => __('Generate Image', 'mxchat'),
3248 - 'pro_only' => true,
3249 - 'group' => __('Other Features', 'mxchat'),
3250 - ],
3251 - 'mxchat_handle_search_request' => [
3252 - 'label' => __('Brave Web Search', 'mxchat'),
3253 - 'pro_only' => false,
3254 - 'group' => __('Search Features', 'mxchat'),
3255 - ],
3256 - 'mxchat_handle_image_search_request' => [
3257 - 'label' => __('Brave Image Search', 'mxchat'),
3258 - 'pro_only' => false,
3259 - 'group' => __('Search Features', 'mxchat'),
3260 - ],
3261 - 'mxchat_handle_pdf_discussion' => [
3262 - 'label' => __('Chat with PDF', 'mxchat'),
3263 - 'pro_only' => true,
3264 - 'group' => __('Other Features', 'mxchat'),
3265 - ],
3266 - 'mxchat_live_agent_handover' => [
3267 - 'label' => __('Live Agent', 'mxchat'),
3268 - 'pro_only' => true,
3269 - 'group' => __('Customer Engagement', 'mxchat'),
3270 - ],
3271 - 'mxchat_handle_switch_to_chatbot_intent' => [
3272 - 'label' => __('Back to Chatbot', 'mxchat'),
3273 - 'pro_only' => true,
3274 - 'group' => __('Customer Engagement', 'mxchat'),
3275 - ],
3276 - ];
3277 -
3278 - // Allow other plugins to add their callbacks
3279 - $callbacks = apply_filters('mxchat_available_callbacks', $callbacks);
3280 -
4583 +/**
4584 + * Enhanced get_available_callbacks function with form action exclusion
4585 + *
4586 + * @param bool $grouped Whether to return callbacks grouped by category
4587 + * @param bool $include_all Whether to include all potential actions (even if add-on not installed)
4588 + * @return array Callbacks data with icons, descriptions and availability status
4589 + */
4590 +private function mxchat_get_available_callbacks($grouped = false, $include_all = true) {
4591 + // Load WordPress plugin functions if needed
4592 + if (!function_exists('get_plugins')) {
4593 + require_once ABSPATH . 'wp-admin/includes/plugin.php';
4594 + }
4595 +
4596 + // Get active plugins
4597 + $active_plugins = get_option('active_plugins', array());
4598 +
4599 + // Functions to exclude from the action selector only if Pro is activated
4600 + // If user doesn't have Pro, show these so they can see what they're missing
4601 + $excluded_when_pro_active_functions = array(
4602 + 'mxchat_handle_form_collection', // Forms add-on action
4603 + 'mxchat_sr_recommendation_flow' // Smart Recommender flow actions
4604 + );
4605 +
4606 + // Always excluded functions (regardless of Pro status)
4607 + $always_excluded_functions = array();
4608 +
4609 + // Combine exclusion lists based on Pro activation status
4610 + $excluded_functions = $always_excluded_functions;
4611 + if ($this->is_activated) {
4612 + // Only exclude add-on managed functions if Pro is active
4613 + $excluded_functions = array_merge($excluded_functions, $excluded_when_pro_active_functions);
4614 + }
4615 +
4616 + // Define add-on plugin files and their corresponding action functions
4617 + $addon_plugins = array(
4618 + 'mxchat-woo/mxchat-woo.php' => array(
4619 + 'functions' => array(
4620 + 'mxchat_handle_product_recommendations',
4621 + 'mxchat_handle_order_history',
4622 + 'mxchat_show_product_card',
4623 + 'mxchat_add_to_cart',
4624 + 'mxchat_checkout_redirect'
4625 + ),
4626 + 'name' => __('WooCommerce Add-on', 'mxchat'),
4627 + 'pro_required' => true
4628 + ),
4629 + 'mxchat-perplexity/mxchat-perplexity.php' => array(
4630 + 'functions' => array('mxchat_perplexity_research'),
4631 + 'name' => __('Perplexity Add-on', 'mxchat'),
4632 + 'pro_required' => true
4633 + ),
4634 + 'mxchat-forms/mxchat-forms.php' => array(
4635 + 'functions' => array('mxchat_handle_form_collection'),
4636 + 'name' => __('Forms Add-on', 'mxchat'),
4637 + 'pro_required' => true
4638 + ),
4639 + 'mxchat-smart-recommender/mxchat-smart-recommender.php' => array(
4640 + 'functions' => array('mxchat_sr_recommendation_flow'),
4641 + 'name' => __('Smart Recommender Add-on', 'mxchat'),
4642 + 'pro_required' => true
4643 + ),
4644 + // Add other add-ons and their functions here
4645 + );
4646 +
4647 + // Get the functions that are provided by active add-ons
4648 + $addon_provided_functions = array();
4649 + $addon_function_mapping = array(); // Maps functions to their add-on info
4650 +
4651 + // Check which add-ons are active
4652 + foreach ($addon_plugins as $plugin_file => $addon_info) {
4653 + $is_active = in_array($plugin_file, $active_plugins);
4654 +
4655 + // For each function in this addon
4656 + foreach ($addon_info['functions'] as $function) {
4657 + // Consider a function installed only if:
4658 + // 1. The add-on is active AND
4659 + // 2. Either it doesn't require Pro OR Pro is activated
4660 + $is_installed = $is_active && (!$addon_info['pro_required'] || $this->is_activated);
4661 +
4662 + // If the add-on is installed, mark this function as provided by an add-on
4663 + if ($is_installed) {
4664 + $addon_provided_functions[] = $function;
4665 + }
4666 +
4667 + // Store addon info for this function regardless of installation status
4668 + $addon_function_mapping[$function] = array(
4669 + 'addon' => basename(dirname($plugin_file)),
4670 + 'addon_name' => $addon_info['name'],
4671 + 'pro_required' => $addon_info['pro_required'],
4672 + 'is_active' => $is_active,
4673 + 'is_installed' => $is_installed
4674 + );
4675 + }
4676 + }
4677 +
4678 + // Core callbacks - always available in the base plugin
4679 + $core_callbacks = array(
4680 + 'mxchat_handle_email_capture' => array(
4681 + 'label' => __('Loops Email Capture', 'mxchat'),
4682 + 'pro_only' => false,
4683 + 'group' => __('Customer Engagement', 'mxchat'),
4684 + 'icon' => 'email-alt',
4685 + 'description' => __('Collect visitor emails for your mailing list in Loops', 'mxchat'),
4686 + 'addon' => false, // Not from an add-on
4687 + 'installed' => true // Always installed with base plugin
4688 + ),
4689 + 'mxchat_handle_search_request' => array(
4690 + 'label' => __('Brave Web Search', 'mxchat'),
4691 + 'pro_only' => false,
4692 + 'group' => __('Search Features', 'mxchat'),
4693 + 'icon' => 'search',
4694 + 'description' => __('Let users search the web directly from the chat', 'mxchat'),
4695 + 'addon' => false,
4696 + 'installed' => true
4697 + ),
4698 + 'mxchat_handle_image_search_request' => array(
4699 + 'label' => __('Brave Image Search', 'mxchat'),
4700 + 'pro_only' => false,
4701 + 'group' => __('Search Features', 'mxchat'),
4702 + 'icon' => 'format-image',
4703 + 'description' => __('Search and display images in the chat conversation', 'mxchat'),
4704 + 'addon' => false,
4705 + 'installed' => true
4706 + ),
4707 + // Pro core features - check is_activated property
4708 + 'mxchat_generate_image' => array(
4709 + 'label' => __('Generate Image', 'mxchat'),
4710 + 'pro_only' => false,
4711 + 'group' => __('Other Features', 'mxchat'),
4712 + 'icon' => 'art',
4713 + 'description' => __('Create images with DALL-E 3 from OpenAI (requires OpenAI API key)', 'mxchat'),
4714 + 'addon' => false,
4715 + 'installed' => true
4716 + ),
4717 + 'mxchat_handle_pdf_discussion' => array(
4718 + 'label' => __('Chat with PDF', 'mxchat'),
4719 + 'pro_only' => false,
4720 + 'group' => __('Other Features', 'mxchat'),
4721 + 'icon' => 'media-document',
4722 + 'description' => __('Answer questions about uploaded PDF documents', 'mxchat'),
4723 + 'addon' => false,
4724 + 'installed' => true
4725 + ),
4726 + 'mxchat_live_agent_handover' => array(
4727 + 'label' => __('Slack Live Agent', 'mxchat'),
4728 + 'pro_only' => false,
4729 + 'group' => __('Customer Engagement', 'mxchat'),
4730 + 'icon' => 'admin-users',
4731 + 'description' => __('Transfer conversation to a human support agent on Slack', 'mxchat'),
4732 + 'addon' => false,
4733 + 'installed' => true
4734 + ),
4735 + 'mxchat_handle_switch_to_chatbot_intent' => array(
4736 + 'label' => __('Back to Chatbot', 'mxchat'),
4737 + 'pro_only' => false,
4738 + 'group' => __('Customer Engagement', 'mxchat'),
4739 + 'icon' => 'backup',
4740 + 'description' => __('Return from live agent mode to AI chatbot', 'mxchat'),
4741 + 'addon' => false,
4742 + 'installed' => true
4743 + ),
4744 + );
4745 +
4746 + // Add-on callbacks with placeholders - only include if the add-on is NOT active
4747 + $addon_callbacks = array(
4748 + // WooCommerce Add-on
4749 + 'mxchat_handle_product_recommendations' => array(
4750 + 'label' => __('Product Recommendations', 'mxchat'),
4751 + 'pro_only' => true,
4752 + 'group' => __('WooCommerce Features', 'mxchat'),
4753 + 'icon' => 'cart',
4754 + 'description' => __('Suggest products based on customer preferences', 'mxchat'),
4755 + ),
4756 + 'mxchat_handle_order_history' => array(
4757 + 'label' => __('Order History', 'mxchat'),
4758 + 'pro_only' => true,
4759 + 'group' => __('WooCommerce Features', 'mxchat'),
4760 + 'icon' => 'clipboard',
4761 + 'description' => __('Allow customers to check their order status', 'mxchat'),
4762 + ),
4763 + 'mxchat_show_product_card' => array(
4764 + 'label' => __('Show Product Card', 'mxchat'),
4765 + 'pro_only' => true,
4766 + 'group' => __('WooCommerce Features', 'mxchat'),
4767 + 'icon' => 'products',
4768 + 'description' => __('Display product information in the chat', 'mxchat'),
4769 + ),
4770 + 'mxchat_add_to_cart' => array(
4771 + 'label' => __('Add to Cart', 'mxchat'),
4772 + 'pro_only' => true,
4773 + 'group' => __('WooCommerce Features', 'mxchat'),
4774 + 'icon' => 'plus-alt',
4775 + 'description' => __('Add products to cart directly from chat', 'mxchat'),
4776 + ),
4777 + 'mxchat_checkout_redirect' => array(
4778 + 'label' => __('Proceed to Checkout', 'mxchat'),
4779 + 'pro_only' => true,
4780 + 'group' => __('WooCommerce Features', 'mxchat'),
4781 + 'icon' => 'arrow-right-alt',
4782 + 'description' => __('Redirect customer to checkout page', 'mxchat'),
4783 + ),
4784 +
4785 + // Perplexity Add-on
4786 + 'mxchat_perplexity_research' => array(
4787 + 'label' => __('Perplexity Research', 'mxchat'),
4788 + 'pro_only' => true,
4789 + 'group' => __('Search Features', 'mxchat'),
4790 + 'icon' => 'book-alt',
4791 + 'description' => __('Allows the chatbot to search the web for accurate, up-to-date answers', 'mxchat'),
4792 + ),
4793 +
4794 + // Forms Add-on (only shown when Pro is not activated)
4795 + 'mxchat_handle_form_collection' => array(
4796 + 'label' => __('Form Collection', 'mxchat'),
4797 + 'pro_only' => true,
4798 + 'group' => __('Form Features', 'mxchat'),
4799 + 'icon' => 'feedback',
4800 + 'description' => __('Collect user information through custom forms in chat', 'mxchat'),
4801 + ),
4802 +
4803 + // Smart Recommender Add-on (only shown when Pro is not activated)
4804 + 'mxchat_sr_recommendation_flow' => array(
4805 + 'label' => __('Smart Recommender Flow', 'mxchat'),
4806 + 'pro_only' => true,
4807 + 'group' => __('Recommendation Features', 'mxchat'),
4808 + 'icon' => 'cart',
4809 + 'description' => __('Create interactive conversation flows that collect user preferences and deliver personalized product or service recommendations', 'mxchat'),
4810 + ),
4811 + );
4812 +
4813 + // Enhance add-on callbacks with installation status and addon info
4814 + foreach ($addon_callbacks as $function => $data) {
4815 + if (isset($addon_function_mapping[$function])) {
4816 + $addon_info = $addon_function_mapping[$function];
4817 +
4818 + $addon_callbacks[$function]['addon'] = $addon_info['addon'];
4819 + $addon_callbacks[$function]['addon_name'] = $addon_info['addon_name'];
4820 + $addon_callbacks[$function]['installed'] = $addon_info['is_installed'];
4821 +
4822 + // Set pro_only based on add-on configuration
4823 + $addon_callbacks[$function]['pro_only'] = $addon_info['pro_required'];
4824 + } else {
4825 + $addon_callbacks[$function]['addon'] = 'unknown';
4826 + $addon_callbacks[$function]['addon_name'] = __('Unknown Add-on', 'mxchat');
4827 + $addon_callbacks[$function]['installed'] = false;
4828 + }
4829 + }
4830 +
4831 + // Initialize callbacks with core features
4832 + $callbacks = $core_callbacks;
4833 +
4834 + // Get callbacks from active add-ons
4835 + $active_addon_callbacks = apply_filters('mxchat_available_callbacks', array());
4836 +
4837 + // Add placeholder callbacks only for add-ons that aren't active
4838 + if ($include_all) {
4839 + foreach ($addon_callbacks as $function => $data) {
4840 + // Skip placeholders for functions provided by active add-ons
4841 + if (in_array($function, $addon_provided_functions)) {
4842 + continue;
4843 + }
4844 +
4845 + // Skip excluded functions
4846 + if (in_array($function, $excluded_functions)) {
4847 + continue;
4848 + }
4849 +
4850 + // Add the placeholder
4851 + $callbacks[$function] = $data;
4852 + }
4853 + }
4854 +
4855 + // Add callbacks from active add-ons (will override placeholders)
4856 + foreach ($active_addon_callbacks as $function => $data) {
4857 + // Skip excluded functions
4858 + if (in_array($function, $excluded_functions)) {
4859 + continue;
4860 + }
4861 +
4862 + // Always include callbacks from add-ons
4863 + $callbacks[$function] = $data;
4864 +
4865 + // Ensure they have the proper add-on info
4866 + if (isset($addon_function_mapping[$function])) {
4867 + $addon_info = $addon_function_mapping[$function];
4868 + $callbacks[$function]['addon'] = $addon_info['addon'];
4869 + $callbacks[$function]['addon_name'] = $addon_info['addon_name'];
4870 + $callbacks[$function]['installed'] = $addon_info['is_installed'];
4871 + $callbacks[$function]['pro_only'] = $addon_info['pro_required'];
4872 + }
4873 + }
4874 +
4875 + // Just before returning callbacks, sort them to prioritize free features
4876 + if (!$grouped) {
4877 + // Create temporary arrays for sorting
4878 + $free_callbacks = array();
4879 + $pro_callbacks = array();
4880 +
4881 + // Split callbacks into free and pro
4882 + foreach ($callbacks as $key => $data) {
4883 + if (isset($data['pro_only']) && $data['pro_only']) {
4884 + $pro_callbacks[$key] = $data;
4885 + } else {
4886 + $free_callbacks[$key] = $data;
4887 + }
4888 + }
4889 +
4890 + // Merge with free callbacks first
4891 + $callbacks = array_merge($free_callbacks, $pro_callbacks);
4892 + }
4893 +
3281 4894 // Return grouped structure if requested
3282 4895 if ($grouped) {
3283 - $grouped_callbacks = [];
4896 + $grouped_callbacks = array();
3284 4897 foreach ($callbacks as $key => $data) {
3285 - $group_label = $data['group'] ?? __('Other Features', 'mxchat');
3286 - $grouped_callbacks[$group_label][$key] = [
3287 - 'label' => $data['label'],
3288 - 'pro_only' => $data['pro_only'],
3289 - ];
4898 + $group_label = isset($data['group']) ? $data['group'] : __('Other Features', 'mxchat');
4899 +
4900 + // Ensure we carry forward all the new fields in grouped mode
4901 + $callback_data = array(
4902 + 'label' => $data['label'],
4903 + 'pro_only' => isset($data['pro_only']) ? $data['pro_only'] : false,
4904 + 'icon' => isset($data['icon']) ? $data['icon'] : 'admin-generic',
4905 + 'description' => isset($data['description']) ? $data['description'] : __('Custom action for your chatbot', 'mxchat'),
4906 + 'addon' => isset($data['addon']) ? $data['addon'] : false,
4907 + 'addon_name' => isset($data['addon_name']) ? $data['addon_name'] : '',
4908 + 'installed' => isset($data['installed']) ? $data['installed'] : true
4909 + );
4910 +
4911 + $grouped_callbacks[$group_label][$key] = $callback_data;
3290 4912 }
4913 +
4914 + // Sort within each group to prioritize free features
4915 + foreach ($grouped_callbacks as $group => $items) {
4916 + $free_items = array();
4917 + $pro_items = array();
4918 +
4919 + foreach ($items as $key => $data) {
4920 + if (isset($data['pro_only']) && $data['pro_only']) {
4921 + $pro_items[$key] = $data;
4922 + } else {
4923 + $free_items[$key] = $data;
4924 + }
4925 + }
4926 +
4927 + $grouped_callbacks[$group] = array_merge($free_items, $pro_items);
4928 + }
4929 +
3291 4930 return $grouped_callbacks;
3292 4931 }
4932 +
3293 4933 return $callbacks;
3294 4934 }
3295 4935
3296 4936
@@ -3327,9 +4967,9 @@
3327 4967
3328 4968 $wpdb->delete($table_name, ['id' => $intent_id], ['%d']);
3329 4969 }
3330 4970
3331 - wp_safe_redirect(admin_url('admin.php?page=mxchat-intents'));
4971 + wp_safe_redirect(admin_url('admin.php?page=mxchat-actions'));
3332 4972 exit;
3333 4973 }
3334 4974
3335 4975
@@ -3380,49 +5020,80 @@
3380 5020 );
3381 5021
3382 5022
3383 5023 // Existing fields...
3384 - add_settings_field(
3385 - 'api_key',
3386 - esc_html__('OpenAI API Key', 'mxchat'),
3387 - array($this, 'api_key_callback'),
3388 - 'mxchat-chatbot',
3389 - 'mxchat_chatbot_section'
3390 - );
3391 -
3392 - add_settings_field(
3393 - 'xai_api_key',
3394 - esc_html__('X.AI API Key', 'mxchat'),
3395 - array($this, 'xai_api_key_callback'),
3396 - 'mxchat-chatbot',
3397 - 'mxchat_chatbot_section'
3398 - );
3399 -
3400 - add_settings_field(
3401 - 'claude_api_key',
3402 - esc_html__('Claude API Key', 'mxchat'),
3403 - array($this, 'claude_api_key_callback'),
3404 - 'mxchat-chatbot',
3405 - 'mxchat_chatbot_section'
3406 - );
3407 -
3408 - add_settings_field(
3409 - 'deepseek_api_key',
3410 - esc_html__('DeepSeek API Key', 'mxchat'),
3411 - array($this, 'deepseek_api_key_callback'),
3412 - 'mxchat-chatbot',
3413 - 'mxchat_chatbot_section'
3414 - );
3415 -
3416 5024 add_settings_field(
5025 + 'api_key',
5026 + esc_html__('OpenAI API Key', 'mxchat'),
5027 + array($this, 'api_key_callback'),
5028 + 'mxchat-chatbot',
5029 + 'mxchat_chatbot_section',
5030 + array(
5031 + 'class' => 'mxchat-setting-row',
5032 + 'data-provider' => 'openai'
5033 + )
5034 + );
5035 +
5036 + add_settings_field(
5037 + 'xai_api_key',
5038 + esc_html__('X.AI API Key', 'mxchat'),
5039 + array($this, 'xai_api_key_callback'),
5040 + 'mxchat-chatbot',
5041 + 'mxchat_chatbot_section',
5042 + array(
5043 + 'class' => 'mxchat-setting-row',
5044 + 'data-provider' => 'xai'
5045 + )
5046 + );
5047 +
5048 + add_settings_field(
5049 + 'claude_api_key',
5050 + esc_html__('Claude API Key', 'mxchat'),
5051 + array($this, 'claude_api_key_callback'),
5052 + 'mxchat-chatbot',
5053 + 'mxchat_chatbot_section',
5054 + array(
5055 + 'class' => 'mxchat-setting-row',
5056 + 'data-provider' => 'claude'
5057 + )
5058 + );
5059 +
5060 + add_settings_field(
5061 + 'deepseek_api_key',
5062 + esc_html__('DeepSeek API Key', 'mxchat'),
5063 + array($this, 'deepseek_api_key_callback'),
5064 + 'mxchat-chatbot',
5065 + 'mxchat_chatbot_section',
5066 + array(
5067 + 'class' => 'mxchat-setting-row',
5068 + 'data-provider' => 'deepseek'
5069 + )
5070 + );
5071 +
5072 + add_settings_field(
5073 + 'gemini_api_key',
5074 + esc_html__('Google Gemini API Key', 'mxchat'),
5075 + array($this, 'gemini_api_key_callback'),
5076 + 'mxchat-chatbot',
5077 + 'mxchat_chatbot_section',
5078 + array(
5079 + 'class' => 'mxchat-setting-row',
5080 + 'data-provider' => 'gemini'
5081 + )
5082 + );
5083 +
5084 + add_settings_field(
3417 5085 'voyage_api_key',
3418 5086 esc_html__('Voyage AI API Key', 'mxchat'),
3419 5087 array($this, 'voyage_api_key_callback'),
3420 5088 'mxchat-chatbot',
3421 - 'mxchat_chatbot_section'
5089 + 'mxchat_chatbot_section',
5090 + array(
5091 + 'class' => 'mxchat-setting-row',
5092 + 'data-provider' => 'voyage'
5093 + )
3422 5094 );
3423 5095
3424 -
3425 5096 add_settings_field(
3426 5097 'model',
3427 5098 esc_html__('Chat Model', 'mxchat'),
3428 5099 array($this, 'mxchat_model_callback'),
@@ -3428,9 +5099,9 @@
3428 5099 array($this, 'mxchat_model_callback'),
3429 5100 'mxchat-chatbot',
3430 5101 'mxchat_chatbot_section'
3431 5102 );
3432 -
5103 +
3433 5104 // Add the settings field
3434 5105 add_settings_field(
3435 5106 'embedding_model',
3436 5107 esc_html__('Embedding Model', 'mxchat'),
@@ -3437,9 +5108,9 @@
3437 5108 array($this, 'embedding_model_callback'),
3438 5109 'mxchat-chatbot',
3439 5110 'mxchat_chatbot_section'
3440 5111 );
3441 -
5112 +
3442 5113 add_settings_field(
3443 5114 'system_prompt_instructions',
3444 5115 esc_html__('AI Instructions (Behavior)', 'mxchat'),
3445 5116 array($this, 'system_prompt_instructions_callback'),
@@ -3456,8 +5127,16 @@
3456 5127 'mxchat_chatbot_section'
3457 5128 );
3458 5129
3459 5130 add_settings_field(
5131 + 'ai_agent_text',
5132 + esc_html__('AI Agent Text', 'mxchat'),
5133 + array($this, 'mxchat_ai_agent_text_callback'),
5134 + 'mxchat-chatbot',
5135 + 'mxchat_chatbot_section'
5136 + );
5137 +
5138 + add_settings_field(
3460 5139 'enable_email_block',
3461 5140 esc_html__('Require Email To Chat', 'mxchat'),
3462 5141 array($this, 'enable_email_block_callback'),
3463 5142 'mxchat-chatbot',
@@ -3496,32 +5175,8 @@
3496 5175 'mxchat_chatbot_section'
3497 5176 );
3498 5177
3499 5178 add_settings_field(
3500 - 'rate_limit_logged_out',
3501 - __('Rate Limit for Logged-out Users', 'mxchat'),
3502 - array($this, 'mxchat_rate_limit_logged_out_callback'),
3503 - 'mxchat-chatbot',
3504 - 'mxchat_chatbot_section'
3505 - );
3506 -
3507 - add_settings_field(
3508 - 'rate_limit_roles',
3509 - __('Rate Limits by User Role', 'mxchat'),
3510 - array($this, 'mxchat_rate_limit_roles_callback'),
3511 - 'mxchat-chatbot',
3512 - 'mxchat_chatbot_section'
3513 - );
3514 -
3515 - add_settings_field(
3516 - 'rate_limit_message',
3517 - esc_html__('Rate Limit Message', 'mxchat'),
3518 - array($this, 'mxchat_rate_limit_message_callback'),
3519 - 'mxchat-chatbot',
3520 - 'mxchat_chatbot_section'
3521 - );
3522 -
3523 - add_settings_field(
3524 5179 'pre_chat_message',
3525 5180 esc_html__('Chat Teaser Pop-up', 'mxchat'),
3526 5181 array($this, 'mxchat_pre_chat_message_callback'),
3527 5182 'mxchat-chatbot',
@@ -3592,8 +5247,16 @@
3592 5247 'mxchat_chatbot_section'
3593 5248 );
3594 5249
3595 5250
5251 + add_settings_field(
5252 + 'rate_limits',
5253 + __('Rate Limits Settings', 'mxchat'),
5254 + array($this, 'mxchat_rate_limits_callback'),
5255 + 'mxchat-chatbot',
5256 + 'mxchat_chatbot_section'
5257 + );
5258 +
3596 5259 // Loops Settings Section
3597 5260 add_settings_section(
3598 5261 'mxchat_loops_section',
3599 5262 esc_html__('Loops Settings', 'mxchat'),
@@ -3704,8 +5367,26 @@
3704 5367 array($this, 'mxchat_chat_toolbar_toggle_callback'),
3705 5368 'mxchat-embed',
3706 5369 'mxchat_pdf_intent_section'
3707 5370 );
5371 +
5372 + // PDF Upload Button Toggle
5373 + add_settings_field(
5374 + 'show_pdf_upload_button',
5375 + __('Show PDF Upload Button', 'mxchat'),
5376 + array($this, 'mxchat_show_pdf_upload_button_callback'),
5377 + 'mxchat-embed',
5378 + 'mxchat_pdf_intent_section'
5379 + );
5380 +
5381 + // Word Upload Button Toggle
5382 + add_settings_field(
5383 + 'show_word_upload_button',
5384 + __('Show Word Upload Button', 'mxchat'),
5385 + array($this, 'mxchat_show_word_upload_button_callback'),
5386 + 'mxchat-embed',
5387 + 'mxchat_pdf_intent_section'
5388 + );
3708 5389
3709 5390 add_settings_field(
3710 5391 'pdf_intent_trigger_text',
3711 5392 __('Intent Trigger Text', 'mxchat'),
@@ -3802,9 +5483,9 @@
3802 5483
3803 5484 // General Settings Section
3804 5485 add_settings_section(
3805 5486 'mxchat_general_section',
3806 - esc_html__('Frequently Asked Questions (FAQ)', 'mxchat'),
5487 + esc_html__('YouTube Tutorials', 'mxchat'),
3807 5488 null,
3808 5489 'mxchat-general'
3809 5490 );
3810 5491 }
@@ -3975,80 +5656,207 @@
3975 5656 }
3976 5657 }
3977 5658
3978 5659
3979 -public function mxchat_rate_limit_logged_out_callback() {
3980 - // Load the entire 'mxchat_options' array
5660 +public function mxchat_rate_limits_callback() {
3981 5661 $all_options = get_option('mxchat_options', []);
3982 -
3983 - // Retrieve the saved rate limit or use the default value
3984 - $default_rate_limit = '10';
3985 - $selected_rate_limit = isset($all_options['rate_limit_logged_out']) ? $all_options['rate_limit_logged_out'] : $default_rate_limit;
3986 -
5662 +
3987 5663 // Define available rate limits
3988 - $rate_limits = array('1', '3', '5', '10', '15', '20', '100', 'unlimited');
3989 -
3990 - // Output the dropdown
5664 + $rate_limits = array('1', '3', '5', '10', '15', '20', '50', '100', 'unlimited');
5665 +
5666 + // Define available timeframes
5667 + $timeframes = array(
5668 + 'hourly' => __('Per Hour', 'mxchat'),
5669 + 'daily' => __('Per Day', 'mxchat'),
5670 + 'weekly' => __('Per Week', 'mxchat'),
5671 + 'monthly' => __('Per Month', 'mxchat')
5672 + );
5673 +
5674 + // Get all roles plus a "logged_out" pseudo-role
5675 + $roles = wp_roles()->get_names();
5676 + $roles['logged_out'] = __('Logged Out Users', 'mxchat');
5677 +
5678 + // Start the wrapper
3991 5679 echo '<div class="pro-feature-wrapper active">';
3992 - echo '<select id="rate_limit_logged_out" name="rate_limit_logged_out">';
3993 - foreach ($rate_limits as $limit) {
3994 - echo '<option value="' . esc_attr($limit) . '" ' . selected($selected_rate_limit, $limit, false) . '>' . esc_html($limit) . '</option>';
3995 - }
3996 - echo '</select>';
3997 - echo '<p class="description">' . esc_html__('Set the maximum number of messages a logged-out user can send within a 24-hour period.', 'mxchat') . '</p>';
3998 - echo '</div>';
3999 -}
4000 -
4001 -// Add this new callback function
4002 -public function mxchat_rate_limit_roles_callback() {
4003 - $all_options = get_option('mxchat_options', []);
4004 - $roles = wp_roles()->get_names();
4005 - $rate_limits = array('1', '3', '5', '10', '15', '20', '100', 'unlimited');
4006 -
4007 - echo '<div class="pro-feature-wrapper active mxchat-autosave-section">';
4008 -
5680 + echo '<div class="mxchat-rate-limits-container">';
5681 +
5682 + // Add improved styling
5683 + echo '<style>
5684 + .mxchat-rate-limits-container {
5685 + max-width: 900px;
5686 + }
5687 + .mxchat-rate-limit-row {
5688 + display: flex;
5689 + flex-wrap: wrap;
5690 + align-items: flex-start;
5691 + margin-bottom: 20px;
5692 + padding: 20px;
5693 + background: #fff;
5694 + border-radius: 8px;
5695 + border: 1px solid #e0e0e0;
5696 + box-shadow: 0 2px 4px rgba(0,0,0,0.04);
5697 + transition: all 0.2s ease;
5698 + }
5699 + .mxchat-rate-limit-row:hover {
5700 + box-shadow: 0 4px 8px rgba(0,0,0,0.08);
5701 + border-color: #c7c7c7;
5702 + }
5703 + .mxchat-rate-limit-role {
5704 + width: 160px;
5705 + font-weight: 600;
5706 + font-size: 15px;
5707 + margin-right: 20px;
5708 + padding-top: 4px;
5709 + color: #23282d;
5710 + }
5711 + .mxchat-rate-limit-controls-wrapper {
5712 + flex: 1;
5713 + }
5714 + .mxchat-rate-limit-controls {
5715 + display: flex;
5716 + flex-wrap: wrap;
5717 + gap: 15px;
5718 + align-items: center;
5719 + margin-bottom: 15px;
5720 + }
5721 + .mxchat-rate-limit-controls > div {
5722 + margin-bottom: 5px;
5723 + }
5724 + .mxchat-rate-limit-controls label {
5725 + display: block;
5726 + margin-bottom: 5px;
5727 + font-weight: 500;
5728 + color: #50575e;
5729 + }
5730 + .mxchat-rate-limit-message {
5731 + width: 100%;
5732 + margin-top: 15px;
5733 + }
5734 + .mxchat-rate-limit-message label {
5735 + display: block;
5736 + margin-bottom: 5px;
5737 + font-weight: 500;
5738 + color: #50575e;
5739 + }
5740 + .mxchat-rate-limit-message textarea {
5741 + width: 100%;
5742 + min-height: 70px;
5743 + padding: 8px 12px;
5744 + border-radius: 4px;
5745 + resize: vertical;
5746 + font-size: 14px;
5747 + }
5748 + .mxchat-rate-limit-controls select {
5749 + min-width: 120px;
5750 + padding: 6px 24px 6px 10px;
5751 + }
5752 + @media (max-width: 782px) {
5753 + .mxchat-rate-limit-row {
5754 + flex-direction: column;
5755 + }
5756 + .mxchat-rate-limit-role {
5757 + margin-bottom: 15px;
5758 + width: 100%;
5759 + font-size: 16px;
5760 + }
5761 + .mxchat-rate-limit-controls {
5762 + flex-direction: column;
5763 + align-items: flex-start;
5764 + gap: 12px;
5765 + }
5766 + .mxchat-rate-limit-controls > div {
5767 + width: 100%;
5768 + }
5769 + .mxchat-rate-limit-controls select {
5770 + width: 100%;
5771 + }
5772 + }
5773 + </style>';
5774 +
5775 + echo '<p class="description" style="margin-bottom: 20px;">' .
5776 + esc_html__('Set message limits for each user role and customize the experience when users reach those limits. You can use {limit}, {timeframe}, {count}, and {remaining} as placeholders.', 'mxchat') .
5777 + '</p>';
5778 +
5779 + // Output the controls for each role
4009 5780 foreach ($roles as $role_id => $role_name) {
4010 - $default_rate_limit = '100';
4011 - $selected_rate_limit = isset($all_options['role_rate_limits'][$role_id])
4012 - ? $all_options['role_rate_limits'][$role_id]
4013 - : $default_rate_limit;
4014 -
4015 - echo '<div style="margin-bottom: 10px;">';
4016 - echo '<label style="display: inline-block; width: 150px;">' . esc_html($role_name) . ':</label>';
4017 - echo '<select
4018 - id="role_rate_limits_' . esc_attr($role_id) . '"
4019 - name="mxchat_options[role_rate_limits][' . esc_attr($role_id) . ']"
5781 + // Get saved options or defaults
5782 + $default_limit = ($role_id === 'logged_out') ? '10' : '100';
5783 + $default_timeframe = 'daily';
5784 + $default_message = __('Rate limit exceeded. Please try again later.', 'mxchat');
5785 +
5786 + $selected_limit = isset($all_options['rate_limits'][$role_id]['limit'])
5787 + ? $all_options['rate_limits'][$role_id]['limit']
5788 + : $default_limit;
5789 +
5790 + $selected_timeframe = isset($all_options['rate_limits'][$role_id]['timeframe'])
5791 + ? $all_options['rate_limits'][$role_id]['timeframe']
5792 + : $default_timeframe;
5793 +
5794 + $custom_message = isset($all_options['rate_limits'][$role_id]['message'])
5795 + ? $all_options['rate_limits'][$role_id]['message']
5796 + : $default_message;
5797 +
5798 + // Output the row
5799 + echo '<div class="mxchat-rate-limit-row mxchat-autosave-section">';
5800 +
5801 + // Role label
5802 + echo '<div class="mxchat-rate-limit-role">' . esc_html($role_name) . '</div>';
5803 +
5804 + // Controls section
5805 + echo '<div class="mxchat-rate-limit-controls-wrapper">';
5806 +
5807 + // Rate limit and timeframe controls
5808 + echo '<div class="mxchat-rate-limit-controls">';
5809 +
5810 + // Limit dropdown
5811 + echo '<div>';
5812 + echo '<label for="rate_limits_' . esc_attr($role_id) . '_limit">' . esc_html__('Limit:', 'mxchat') . '</label>';
5813 + echo '<select
5814 + id="rate_limits_' . esc_attr($role_id) . '_limit"
5815 + name="mxchat_options[rate_limits][' . esc_attr($role_id) . '][limit]"
4020 5816 class="mxchat-autosave-field">';
4021 5817 foreach ($rate_limits as $limit) {
4022 - echo '<option value="' . esc_attr($limit) . '" ' . selected($selected_rate_limit, $limit, false) . '>' . esc_html($limit) . '</option>';
5818 + echo '<option value="' . esc_attr($limit) . '" ' . selected($selected_limit, $limit, false) . '>' . esc_html($limit) . '</option>';
4023 5819 }
4024 5820 echo '</select>';
4025 5821 echo '</div>';
5822 +
5823 + // Timeframe dropdown
5824 + echo '<div>';
5825 + echo '<label for="rate_limits_' . esc_attr($role_id) . '_timeframe">' . esc_html__('Timeframe:', 'mxchat') . '</label>';
5826 + echo '<select
5827 + id="rate_limits_' . esc_attr($role_id) . '_timeframe"
5828 + name="mxchat_options[rate_limits][' . esc_attr($role_id) . '][timeframe]"
5829 + class="mxchat-autosave-field">';
5830 + foreach ($timeframes as $value => $label) {
5831 + echo '<option value="' . esc_attr($value) . '" ' . selected($selected_timeframe, $value, false) . '>' . esc_html($label) . '</option>';
5832 + }
5833 + echo '</select>';
5834 + echo '</div>';
5835 +
5836 + echo '</div>'; // End controls
5837 +
5838 + // Custom message textarea
5839 + echo '<div class="mxchat-rate-limit-message">';
5840 + echo '<label for="rate_limits_' . esc_attr($role_id) . '_message">' . esc_html__('Custom Message:', 'mxchat') . '</label>';
5841 + echo '<textarea
5842 + id="rate_limits_' . esc_attr($role_id) . '_message"
5843 + name="mxchat_options[rate_limits][' . esc_attr($role_id) . '][message]"
5844 + class="mxchat-autosave-field"
5845 + placeholder="' . esc_attr__('Enter custom message when rate limit is exceeded', 'mxchat') . '">' .
5846 + esc_textarea($custom_message) .
5847 + '</textarea>';
5848 + echo '</div>'; // End message
5849 +
5850 + echo '</div>'; // End controls wrapper
5851 +
5852 + echo '</div>'; // End row
4026 5853 }
4027 -
4028 - echo '<p class="description">' . esc_html__('Set the maximum number of messages users in each role can send within a 24-hour period.', 'mxchat') . '</p>';
4029 - echo '</div>';
5854 +
5855 + echo '</div>'; // End container
5856 +
5857 + echo '</div>'; // End pro-feature-wrapper
4030 5858 }
4031 -
4032 -public function mxchat_rate_limit_message_callback() {
4033 - // Load the entire 'mxchat_options' array
4034 - $all_options = get_option('mxchat_options', []);
4035 -
4036 - // Retrieve the saved message or use the default value
4037 - $default_message = esc_html__('Rate limit exceeded. Please try again later.', 'mxchat');
4038 - $rate_limit_message = isset($all_options['rate_limit_message']) ? $all_options['rate_limit_message'] : $default_message;
4039 -
4040 - // Output the textarea
4041 - echo '<div class="pro-feature-wrapper active">';
4042 - printf(
4043 - '<textarea id="rate_limit_message" name="rate_limit_message" rows="3" cols="50">%s</textarea>',
4044 - esc_textarea($rate_limit_message)
4045 - );
4046 - echo '<p class="description">' . esc_html__('This message will be displayed when a user exceeds the rate limit.', 'mxchat') . '</p>';
4047 - echo '</div>';
4048 -}
4049 -
4050 -
4051 5859 private function mxchat_add_option_field($id, $title, $callback = '') {
4052 5860 add_settings_field(
4053 5861 $id,
4054 5862 __($title, 'mxchat'),
@@ -4057,113 +5865,113 @@
4057 5865 'mxchat_setting_section_id',
4058 5866 $id === 'model' ? ['label_for' => 'model'] : []
4059 5867 );
4060 5868 }
4061 -
5869 +
5870 +// OpenAI API Key
4062 5871 public function api_key_callback() {
4063 - // Retrieve from your stored 'api_key' in the mxchat_options array
4064 5872 $apiKey = isset($this->options['api_key']) ? esc_attr($this->options['api_key']) : '';
4065 -
4066 - // Notice we changed the name to "api_key" (no array notation).
4067 - echo '<input type="password" id="api_key" name="api_key" value="' . $apiKey . '" class="regular-text" />';
5873 +
5874 + echo '<div class="api-key-wrapper" data-provider="openai">';
5875 + echo '<input type="password" id="api_key" name="api_key" value="' . $apiKey . '" class="regular-text" autocomplete="off" />';
4068 5876 echo '<button type="button" id="toggleApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5877 + echo '<p class="description api-key-notice">' . esc_html__('Required for your selected chat model. Important: You must add credits before use.', 'mxchat') . '</p>';
5878 + echo '</div>';
4069 5879 }
5880 +
5881 +// X.AI API Key
4070 5882 public function xai_api_key_callback() {
4071 - // Check if the feature is activated (paid feature)
4072 - $disabled = $this->is_activated ? '' : 'disabled'; // Disable input if not activated
4073 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive'; // CSS class to style the wrapper based on activation status
4074 -
4075 - // Retrieve the X.AI API key value
4076 5883 $xaiApiKey = isset($this->options['xai_api_key']) ? esc_attr($this->options['xai_api_key']) : '';
4077 -
4078 - // Render the input field for the X.AI API key
4079 - echo '<div class="' . esc_attr($class) . '">';
4080 - printf(
4081 - '<input type="password" id="xai_api_key" name="xai_api_key" value="%s" class="regular-text" %s />',
4082 - $xaiApiKey,
4083 - $disabled
4084 - );
5884 +
5885 + echo '<div class="api-key-wrapper" data-provider="xai">';
5886 + echo '<input type="password" id="xai_api_key" name="xai_api_key" value="' . $xaiApiKey . '" class="regular-text" autocomplete="off" />';
4085 5887 echo '<button type="button" id="toggleXaiApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
4086 -
4087 - // If the feature is not activated, show the overlay with a "Pro Only" message
4088 - if (!$this->is_activated) {
4089 - echo '<div class="pro-feature-overlay">';
4090 - 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>';
4091 - echo '</div>';
4092 - }
4093 -
5888 + echo '<p class="description api-key-notice">' . esc_html__('Required for your selected chat model. Important: You must add credits before use.', 'mxchat') . '</p>';
4094 5889 echo '</div>';
4095 5890 }
5891 +// Claude API Key
4096 5892 public function claude_api_key_callback() {
4097 - // Check if the feature is activated (paid feature)
4098 - $disabled = $this->is_activated ? '' : 'disabled';
4099 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
4100 -
4101 - // Retrieve the Claude API key value directly like the others
4102 5893 $claudeApiKey = isset($this->options['claude_api_key']) ? esc_attr($this->options['claude_api_key']) : '';
4103 -
4104 - // Use direct name field like the other working API keys
4105 - echo '<div class="' . esc_attr($class) . '">';
4106 - printf(
4107 - '<input type="password" id="claude_api_key" name="claude_api_key" value="%s" class="regular-text" %s />',
4108 - $claudeApiKey,
4109 - $disabled
4110 - );
5894 +
5895 + echo '<div class="api-key-wrapper" data-provider="claude">';
5896 + echo '<input type="password" id="claude_api_key" name="claude_api_key" value="' . $claudeApiKey . '" class="regular-text" autocomplete="off" />';
4111 5897 echo '<button type="button" id="toggleClaudeApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
4112 -
4113 - if (!$this->is_activated) {
4114 - echo '<div class="pro-feature-overlay">';
4115 - 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>';
4116 - echo '</div>';
4117 - }
5898 + echo '<p class="description api-key-notice">' . esc_html__('Required for your selected chat model. Important: You must add credits before use.', 'mxchat') . '</p>';
4118 5899 echo '</div>';
4119 5900 }
5901 +
5902 +// DeepSeek API Key
4120 5903 public function deepseek_api_key_callback() {
4121 - // Retrieve from your stored 'deepseek_api_key' in the mxchat_options array
4122 5904 $apiKey = isset($this->options['deepseek_api_key']) ? esc_attr($this->options['deepseek_api_key']) : '';
4123 -
4124 - // Notice we changed the name to "deepseek_api_key" (no array notation).
4125 - echo '<input type="password" id="deepseek_api_key" name="deepseek_api_key" value="' . $apiKey . '" class="regular-text" />';
5905 +
5906 + echo '<div class="api-key-wrapper" data-provider="deepseek">';
5907 + echo '<input type="password" id="deepseek_api_key" name="deepseek_api_key" value="' . $apiKey . '" class="regular-text" autocomplete="off" />';
4126 5908 echo '<button type="button" id="toggleDeepSeekApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5909 + echo '<p class="description api-key-notice">' . esc_html__('Required for your selected chat model. Important: You must add credits before use.', 'mxchat') . '</p>';
5910 + echo '</div>';
4127 5911 }
4128 5912
5913 +// Gemini API Key
5914 +public function gemini_api_key_callback() {
5915 + $geminiApiKey = isset($this->options['gemini_api_key']) ? esc_attr($this->options['gemini_api_key']) : '';
5916 +
5917 + echo '<div class="api-key-wrapper" data-provider="gemini">';
5918 + echo '<input type="password" id="gemini_api_key" name="gemini_api_key" value="' . $geminiApiKey . '" class="regular-text" autocomplete="off" />';
5919 + echo '<button type="button" id="toggleGeminiApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5920 + echo '<p class="description api-key-notice">' . esc_html__('Required for Google Gemini models. Get your API key from Google AI Studio.', 'mxchat') . '</p>';
5921 + echo '</div>';
5922 +}
4129 5923
5924 +// Voyage API Key
5925 +public function voyage_api_key_callback() {
5926 + $apiKey = isset($this->options['voyage_api_key']) ? esc_attr($this->options['voyage_api_key']) : '';
5927 +
5928 + echo '<div class="api-key-wrapper" data-provider="voyage">';
5929 + echo '<input type="password" id="voyage_api_key" name="voyage_api_key" value="' . $apiKey . '" class="regular-text" autocomplete="off" />';
5930 + echo '<button type="button" id="toggleVoyageAPIKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5931 + echo '<p class="description api-key-notice">' . esc_html__('Required for your selected embedding model. Important: You must add credits before use.', 'mxchat') . '</p>';
5932 + echo '</div>';
5933 +}
4130 5934
4131 -
4132 -
4133 5935 public function mxchat_loops_api_key_callback() {
4134 - // Support both old and new format
4135 5936 $loops_api_key = isset($this->options['loops_api_key']) ? esc_attr($this->options['loops_api_key']) : '';
4136 -
4137 - echo '<div class="api-key-wrapper">';
5937 +
5938 + // Hidden fields to "trap" autofill
5939 + echo '<input type="text" style="display:none" autocomplete="username" />';
5940 + echo '<input type="password" style="display:none" autocomplete="current-password" />';
5941 +
5942 + echo '<div class="api-key-wrapper" data-provider="loops">';
4138 5943 echo sprintf(
4139 - '<input type="password" id="loops_api_key" name="loops_api_key" value="%s" class="regular-text" />',
5944 + '<input type="password" id="loops_api_key" name="loops_api_key" value="%s" class="regular-text" autocomplete="new-password" />',
4140 5945 $loops_api_key
4141 5946 );
4142 5947 echo '<button type="button" id="toggleLoopsApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
4143 5948 echo '</div>';
4144 - echo '<p class="description">' . esc_html__('Enter your Loops API Key here. (See FAQ for details)', 'mxchat') . '</p>';
5949 + 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>';
4145 5950 }
4146 -
4147 5951 public function mxchat_loops_mailing_list_callback() {
4148 5952 // Add error handling and type checking
4149 5953 $loops_api_key = '';
4150 5954 $selected_list = '';
4151 -
5955 +
4152 5956 // Safely get the API key
4153 5957 if (isset($this->options['loops_api_key']) && is_string($this->options['loops_api_key'])) {
4154 5958 $loops_api_key = $this->options['loops_api_key'];
4155 5959 }
4156 -
5960 +
4157 5961 // Safely get the selected list
4158 5962 if (isset($this->options['loops_mailing_list']) && is_string($this->options['loops_mailing_list'])) {
4159 5963 $selected_list = $this->options['loops_mailing_list'];
4160 5964 }
4161 -
5965 +
4162 5966 if (!empty($loops_api_key)) {
4163 5967 $lists = $this->mxchat_fetch_loops_mailing_lists($loops_api_key);
4164 5968 if (is_array($lists) && !empty($lists)) {
4165 5969 echo '<select id="loops_mailing_list" name="loops_mailing_list">';
5970 +
5971 + // Add a default "Select a list" option
5972 + echo '<option value="" ' . selected($selected_list, '', false) . '>' . esc_html__('Select a list', 'mxchat') . '</option>';
5973 +
4166 5974 foreach ($lists as $list) {
4167 5975 if (is_array($list) && isset($list['id']) && isset($list['name'])) {
4168 5976 echo sprintf(
4169 5977 '<option value="%s" %s>%s</option>',
@@ -4173,8 +5981,9 @@
4173 5981 );
4174 5982 }
4175 5983 }
4176 5984 echo '</select>';
5985 + echo '<p class="description">' . esc_html__('Please select a mailing list to use with Loops.', 'mxchat') . '</p>';
4177 5986 } else {
4178 5987 echo '<p class="description">' . esc_html__('No lists found. Please verify your API Key.', 'mxchat') . '</p>';
4179 5988 }
4180 5989 } else {
@@ -4243,10 +6052,19 @@
4243 6052
4244 6053 public function mxchat_model_callback() {
4245 6054 // Define available models grouped by provider
4246 6055 $models = array(
6056 + esc_html__('Google Gemini Models', 'mxchat') => array(
6057 + 'gemini-2.0-flash' => esc_html__('Gemini 2.0 Flash (Next-Gen Features)', 'mxchat'),
6058 + 'gemini-2.0-flash-lite' => esc_html__('Gemini 2.0 Flash-Lite (Cost-Efficient)', 'mxchat'),
6059 + 'gemini-1.5-pro' => esc_html__('Gemini 1.5 Pro (Complex Reasoning)', 'mxchat'),
6060 + 'gemini-1.5-flash' => esc_html__('Gemini 1.5 Flash (Fast & Versatile)', 'mxchat'),
6061 + ),
4247 6062 esc_html__('X.AI Models', 'mxchat') => array(
4248 - 'grok-beta' => esc_html__('grok-beta (Early Beta)', 'mxchat'),
6063 + 'grok-3-beta' => esc_html__('Grok-3 (Powerful)', 'mxchat'),
6064 + 'grok-3-fast-beta' => esc_html__('Grok-3 Fast (High Performance)', 'mxchat'),
6065 + 'grok-3-mini-beta' => esc_html__('Grok-3 Mini (Affordable)', 'mxchat'),
6066 + 'grok-3-mini-fast-beta' => esc_html__('Grok-3 Mini Fast (Quick Response)', 'mxchat'),
4249 6067 'grok-2' => esc_html__('Grok 2', 'mxchat')
4250 6068 ),
4251 6069 esc_html__('DeepSeek Models', 'mxchat') => array(
4252 6070 'deepseek-chat' => esc_html__('DeepSeek-V3', 'mxchat'),
@@ -4251,14 +6069,16 @@
4251 6069 esc_html__('DeepSeek Models', 'mxchat') => array(
4252 6070 'deepseek-chat' => esc_html__('DeepSeek-V3', 'mxchat'),
4253 6071 ),
4254 6072 esc_html__('Claude Models', 'mxchat') => array(
4255 - 'claude-3-5-sonnet-20241022' => esc_html__('Claude 3.5 Sonnet (Most Intelligent)', 'mxchat'),
6073 + 'claude-3-7-sonnet-20250219' => esc_html__('Claude 3.7 Sonnet (Most Intelligent)', 'mxchat'),
6074 + 'claude-3-5-sonnet-20241022' => esc_html__('Claude 3.5 Sonnet (Intelligent)', 'mxchat'),
4256 6075 'claude-3-opus-20240229' => esc_html__('Claude 3 Opus (Highly Complex Tasks)', 'mxchat'),
4257 6076 'claude-3-sonnet-20240229' => esc_html__('Claude 3 Sonnet (Balanced)', 'mxchat'),
4258 6077 'claude-3-haiku-20240307' => esc_html__('Claude 3 Haiku (Fastest)', 'mxchat')
4259 6078 ),
4260 6079 esc_html__('OpenAI Models', 'mxchat') => array(
6080 + 'gpt-4.1-2025-04-14' => esc_html__('GPT-4.1 (Flagship for Complex Tasks)', 'mxchat'),
4261 6081 'gpt-4o' => esc_html__('GPT-4o (Recommended)', 'mxchat'),
4262 6082 'gpt-4o-mini' => esc_html__('GPT-4o Mini (Fast and Lightweight)', 'mxchat'),
4263 6083 'gpt-4-turbo' => esc_html__('GPT-4 Turbo (High-Performance)', 'mxchat'),
4264 6084 'gpt-4' => esc_html__('GPT-4 (High Intelligence)', 'mxchat'),
@@ -4264,43 +6084,34 @@
4264 6084 'gpt-4' => esc_html__('GPT-4 (High Intelligence)', 'mxchat'),
4265 6085 'gpt-3.5-turbo' => esc_html__('GPT-3.5 Turbo (Affordable and Fast)', 'mxchat')
4266 6086 )
4267 6087 );
4268 -
6088 +
4269 6089 // Retrieve the currently selected model from saved options
4270 6090 $selected_model = isset($this->options['model']) ? esc_attr($this->options['model']) : 'gpt-4o';
4271 -
6091 +
4272 6092 // Begin the select dropdown
4273 - echo '<select id="model" name="model">'; // No array notation in name attribute
4274 -
6093 + echo '<select id="model" name="model">';
6094 +
4275 6095 // Iterate over groups of models
4276 6096 foreach ($models as $group_label => $group_models) {
4277 6097 echo '<optgroup label="' . esc_attr($group_label) . '">';
4278 -
6098 +
4279 6099 foreach ($group_models as $model_value => $model_label) {
4280 - // Disable Pro-only models for non-activated users
4281 - $disabled = (!$this->is_activated && ($group_label === esc_html__('X.AI Models', 'mxchat') || $group_label === esc_html__('Claude Models', 'mxchat'))) ? 'disabled' : '';
4282 - $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') : '';
4283 -
4284 - // Output the option element
4285 - echo '<option value="' . esc_attr($model_value) . '" ' . selected($selected_model, $model_value, false) . ' ' . $disabled . '>' . esc_html($model_label . $label_suffix) . '</option>';
6100 + // All models enabled - no disabled attribute or Pro Only label
6101 + echo '<option value="' . esc_attr($model_value) . '" ' . selected($selected_model, $model_value, false) . '>' . esc_html($model_label) . '</option>';
4286 6102 }
4287 -
6103 +
4288 6104 echo '</optgroup>';
4289 6105 }
4290 -
6106 +
4291 6107 // Close the select dropdown
4292 6108 echo '</select>';
4293 -
4294 - // Add a description below the dropdown
4295 - echo '<p class="description">' . esc_html__('Select the AI model your chatbot will use for chatting. Pro-only models are marked accordingly.', 'mxchat') . '</p>';
6109 +
6110 + // Updated description to remove mention of Pro-only models
6111 + echo '<p class="description">' . esc_html__('Select the AI model your chatbot will use for chatting.', 'mxchat') . '</p>';
4296 6112 }
4297 6113
4298 -public function voyage_api_key_callback() {
4299 - $apiKey = isset($this->options['voyage_api_key']) ? esc_attr($this->options['voyage_api_key']) : '';
4300 - echo '<input type="password" id="voyage_api_key" name="voyage_api_key" value="' . $apiKey . '" class="regular-text" />';
4301 - echo '<button type="button" id="toggleVoyageAPIKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
4302 -}
4303 6114
4304 6115 // Callback function for embedding model selection
4305 6116 public function embedding_model_callback() {
4306 6117 $models = array(
@@ -4305,9 +6116,9 @@
4305 6116 public function embedding_model_callback() {
4306 6117 $models = array(
4307 6118 esc_html__('OpenAI Embeddings', 'mxchat') => array(
4308 6119 'text-embedding-3-small' => esc_html__('TE3 Small (1536, Efficient)', 'mxchat'),
4309 - 'text-embedding-ada-002' => esc_html__('Ada 2 (1536, Original)', 'mxchat'),
6120 + 'text-embedding-ada-002' => esc_html__('Ada 2 (1536, Recommended)', 'mxchat'),
4310 6121 'text-embedding-3-large' => esc_html__('TE3 Large (3072, Powerful)', 'mxchat'),
4311 6122 ),
4312 6123 esc_html__('Voyage AI Embeddings', 'mxchat') => array(
4313 6124 'voyage-3-large' => esc_html__('Voyage-3 Large (2048, Most Capable)', 'mxchat'),
@@ -4325,9 +6136,9 @@
4325 6136 echo '</optgroup>';
4326 6137 }
4327 6138 echo '</select>';
4328 6139 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>';
4329 -
6140 +
4330 6141 }
4331 6142
4332 6143
4333 6144 public function mxchat_top_bar_title_callback() {
@@ -4339,9 +6150,16 @@
4339 6150
4340 6151 // Add a description
4341 6152 echo '<p class="description">' . esc_html__('Enter the title text that will appear on the top bar of the chatbot.', 'mxchat') . '</p>';
4342 6153 }
4343 -
6154 +public function mxchat_ai_agent_text_callback() {
6155 + // Retrieve the current value of the AI agent text from saved options
6156 + $ai_agent_text = isset($this->options['ai_agent_text']) ? esc_attr($this->options['ai_agent_text']) : '';
6157 + // Render the input field
6158 + echo '<input type="text" id="ai_agent_text" name="ai_agent_text" value="' . $ai_agent_text . '" />';
6159 + // Add a description
6160 + echo '<p class="description">' . esc_html__('Enter the text that will appear for AI agents in the status indicator. Default: "AI Agent"', 'mxchat') . '</p>';
6161 +}
4344 6162 public function enable_email_block_callback() {
4345 6163 // Load full plugin options array
4346 6164 $all_options = get_option('mxchat_options', []);
4347 6165
@@ -4361,9 +6179,8 @@
4361 6179 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>';
4362 6180 }
4363 6181
4364 6182
4365 -
4366 6183 public function email_blocker_header_content_callback() {
4367 6184 // Load the entire 'mxchat_options' array
4368 6185 $all_options = get_option('mxchat_options', []);
4369 6186
@@ -4371,14 +6188,15 @@
4371 6188 $content = isset($all_options['email_blocker_header_content'])
4372 6189 ? $all_options['email_blocker_header_content']
4373 6190 : '';
4374 6191
4375 - // Render the textarea
6192 + // Render the textarea - IMPORTANT: name should be just "email_blocker_header_content"
4376 6193 echo '<textarea
4377 6194 id="email_blocker_header_content"
4378 6195 name="email_blocker_header_content"
4379 6196 rows="5"
4380 6197 cols="70"
6198 + data-setting="email_blocker_header_content"
4381 6199 >' . esc_textarea($content) . '</textarea>';
4382 6200
4383 6201 echo '<p class="description">';
4384 6202 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');
@@ -4384,9 +6202,8 @@
4384 6202 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');
4385 6203 echo '</p>';
4386 6204 }
4387 6205
4388 -
4389 6206 public function email_blocker_button_text_callback() {
4390 6207 // Load the entire 'mxchat_options' array
4391 6208 $all_options = get_option('mxchat_options', []);
4392 6209
@@ -4407,23 +6224,20 @@
4407 6224
4408 6225 public function mxchat_intro_message_callback() {
4409 6226 // Load the entire 'mxchat_options' array
4410 6227 $all_options = get_option('mxchat_options', []);
4411 -
4412 6228 // Retrieve the saved intro message or use the default
4413 6229 $default_message = __('Hello! How can I assist you today?', 'mxchat');
4414 6230 $saved_message = isset($all_options['intro_message']) ? $all_options['intro_message'] : $default_message;
4415 -
4416 - // Output the textarea with the saved value
6231 + // Output the textarea with the saved value without escaping HTML
4417 6232 ?>
4418 - <textarea id="intro_message" name="intro_message" rows="5" cols="50"><?php echo esc_textarea($saved_message); ?></textarea>
6233 + <textarea id="intro_message" name="intro_message" rows="5" cols="50"><?php echo $saved_message; ?></textarea>
4419 6234 <p class="description">
4420 - <?php esc_html_e('Enter your message. Line breaks will be preserved.', 'mxchat'); ?>
6235 + <?php esc_html_e('Enter your message. HTML tags and line breaks will be preserved.', 'mxchat'); ?>
4421 6236 </p>
4422 6237 <?php
4423 6238 }
4424 6239
4425 -
4426 6240 public function mxchat_input_copy_callback() {
4427 6241 // Load the entire 'mxchat_options' array
4428 6242 $all_options = get_option('mxchat_options', []);
4429 6243
@@ -5187,141 +7001,116 @@
5187 7001 echo '<p>' . esc_html__('Configure the intent settings for the Chat with PDF feature.', 'mxchat') . '</p>';
5188 7002 }
5189 7003
5190 7004 public function mxchat_chat_toolbar_toggle_callback() {
5191 - // Get chat toolbar toggle value with fallback
5192 - $chat_toolbar_toggle = isset($this->options['chat_toolbar_toggle']) ? $this->options['chat_toolbar_toggle'] : 'off';
5193 - $checked = ($chat_toolbar_toggle === 'on') ? 'checked' : '';
5194 -
5195 - // Check if the plugin is activated (paid feature)
5196 - $disabled = $this->is_activated ? '' : 'disabled';
5197 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5198 -
5199 - echo '<div class="' . esc_attr($class) . '">';
5200 -
5201 - // Output the toggle switch
5202 - echo '<label class="toggle-switch">';
5203 - echo sprintf(
5204 - '<input type="checkbox" id="chat_toolbar_toggle" name="chat_toolbar_toggle" value="on" %s %s />',
5205 - esc_attr($checked),
5206 - esc_attr($disabled)
5207 - );
5208 - echo '<span class="slider"></span>';
5209 - echo '</label>';
5210 -
5211 - // Pro feature overlay
5212 - if (!$this->is_activated) {
5213 - echo '<div class="pro-feature-overlay">';
5214 - echo '<a href="https://mxchat.ai/" target="_blank">';
5215 - echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
5216 - echo '</a>';
5217 - echo '</div>';
5218 - }
5219 -
5220 - echo '</div>';
5221 - 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>';
7005 + // Get chat toolbar toggle value with fallback
7006 + $chat_toolbar_toggle = isset($this->options['chat_toolbar_toggle']) ? $this->options['chat_toolbar_toggle'] : 'off';
7007 + $checked = ($chat_toolbar_toggle === 'on') ? 'checked' : '';
7008 +
7009 + // Output the toggle switch
7010 + echo '<label class="toggle-switch">';
7011 + echo sprintf(
7012 + '<input type="checkbox" id="chat_toolbar_toggle" name="chat_toolbar_toggle" value="on" %s />',
7013 + esc_attr($checked)
7014 + );
7015 + echo '<span class="slider"></span>';
7016 + echo '</label>';
7017 +
7018 + 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>';
5222 7019 }
7020 +/**
7021 + * Callback for PDF upload button toggle setting
7022 + */
7023 +public function mxchat_show_pdf_upload_button_callback() {
7024 + // Get toggle value with fallback
7025 + $show_pdf_button = isset($this->options['show_pdf_upload_button']) ? $this->options['show_pdf_upload_button'] : 'on';
7026 + $checked = ($show_pdf_button === 'on') ? 'checked' : '';
7027 +
7028 + // Output the toggle switch
7029 + echo '<label class="toggle-switch">';
7030 + echo sprintf(
7031 + '<input type="checkbox" id="show_pdf_upload_button" name="show_pdf_upload_button" value="on" %s />',
7032 + esc_attr($checked)
7033 + );
7034 + echo '<span class="slider"></span>';
7035 + echo '</label>';
7036 +
7037 + echo '<p class="description">' . esc_html__('Enable to show the PDF upload button in the chatbot toolbar. Disable to hide it.', 'mxchat') . '</p>';
7038 +}
7039 +/**
7040 + * Callback for Word upload button toggle setting
7041 + */
7042 +public function mxchat_show_word_upload_button_callback() {
7043 + // Get toggle value with fallback
7044 + $show_word_button = isset($this->options['show_word_upload_button']) ? $this->options['show_word_upload_button'] : 'on';
7045 + $checked = ($show_word_button === 'on') ? 'checked' : '';
7046 +
7047 + // Output the toggle switch
7048 + echo '<label class="toggle-switch">';
7049 + echo sprintf(
7050 + '<input type="checkbox" id="show_word_upload_button" name="show_word_upload_button" value="on" %s />',
7051 + esc_attr($checked)
7052 + );
7053 + echo '<span class="slider"></span>';
7054 + echo '</label>';
7055 +
7056 + echo '<p class="description">' . esc_html__('Enable to show the Word document upload button in the chatbot toolbar. Disable to hide it.', 'mxchat') . '</p>';
7057 +}
5223 7058
5224 -
5225 7059 public function mxchat_pdf_intent_trigger_text_callback() {
5226 - $disabled = $this->is_activated ? '' : 'disabled';
5227 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5228 7060 $default_text = __("Please provide the URL to the PDF you'd like to discuss.", 'mxchat');
5229 7061
5230 - echo '<div class="' . esc_attr($class) . '">';
5231 7062 echo sprintf(
5232 7063 '<textarea id="pdf_intent_trigger_text"
5233 7064 name="pdf_intent_trigger_text"
5234 7065 rows="3"
5235 7066 cols="50"
5236 - placeholder="%s"
5237 - %s>%s</textarea>',
7067 + placeholder="%s">%s</textarea>',
5238 7068 esc_attr__('Enter trigger text', 'mxchat'),
5239 - esc_attr($disabled),
5240 7069 isset($this->options['pdf_intent_trigger_text'])
5241 7070 ? esc_textarea($this->options['pdf_intent_trigger_text'])
5242 7071 : esc_textarea($default_text)
5243 7072 );
5244 7073 echo '<p class="description">' . esc_html__('Text displayed when the intent is triggered.', 'mxchat') . '</p>';
5245 -
5246 - if (!$this->is_activated) {
5247 - echo '<div class="pro-feature-overlay">';
5248 - echo '<a href="https://mxchat.ai/" target="_blank">';
5249 - echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
5250 - echo '</a>';
5251 - echo '</div>';
5252 - }
5253 - echo '</div>';
5254 7074 }
5255 7075
5256 7076 public function mxchat_pdf_intent_success_text_callback() {
5257 - $disabled = $this->is_activated ? '' : 'disabled';
5258 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5259 7077 $default_text = __("I've processed the PDF. What questions do you have about it?", 'mxchat');
5260 7078
5261 - echo '<div class="' . esc_attr($class) . '">';
5262 7079 echo sprintf(
5263 7080 '<textarea id="pdf_intent_success_text"
5264 7081 name="pdf_intent_success_text"
5265 7082 rows="3"
5266 7083 cols="50"
5267 - placeholder="%s"
5268 - %s>%s</textarea>',
7084 + placeholder="%s">%s</textarea>',
5269 7085 esc_attr__('Enter success text', 'mxchat'),
5270 - esc_attr($disabled),
5271 7086 isset($this->options['pdf_intent_success_text'])
5272 7087 ? esc_textarea($this->options['pdf_intent_success_text'])
5273 7088 : esc_textarea($default_text)
5274 7089 );
5275 7090 echo '<p class="description">' . esc_html__('Text displayed when the intent is successful.', 'mxchat') . '</p>';
5276 -
5277 - if (!$this->is_activated) {
5278 - echo '<div class="pro-feature-overlay">';
5279 - echo '<a href="https://mxchat.ai/" target="_blank">';
5280 - echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
5281 - echo '</a>';
5282 - echo '</div>';
5283 - }
5284 - echo '</div>';
5285 7091 }
5286 7092
5287 7093 public function mxchat_pdf_intent_error_text_callback() {
5288 - $disabled = $this->is_activated ? '' : 'disabled';
5289 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5290 7094 $default_text = __("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat');
5291 7095
5292 - echo '<div class="' . esc_attr($class) . '">';
5293 7096 echo sprintf(
5294 7097 '<textarea id="pdf_intent_error_text"
5295 7098 name="pdf_intent_error_text"
5296 7099 rows="3"
5297 7100 cols="50"
5298 - placeholder="%s"
5299 - %s>%s</textarea>',
7101 + placeholder="%s">%s</textarea>',
5300 7102 esc_attr__('Enter error text', 'mxchat'),
5301 - esc_attr($disabled),
5302 7103 isset($this->options['pdf_intent_error_text'])
5303 7104 ? esc_textarea($this->options['pdf_intent_error_text'])
5304 7105 : esc_textarea($default_text)
5305 7106 );
5306 7107 echo '<p class="description">' . esc_html__('Text displayed when an error occurs during the intent.', 'mxchat') . '</p>';
5307 -
5308 - if (!$this->is_activated) {
5309 - echo '<div class="pro-feature-overlay">';
5310 - echo '<a href="https://mxchat.ai/" target="_blank">';
5311 - echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
5312 - echo '</a>';
5313 - echo '</div>';
5314 - }
5315 - echo '</div>';
5316 7108 }
5317 7109
5318 7110 public function mxchat_pdf_max_pages_callback() {
5319 - $disabled = $this->is_activated ? '' : 'disabled';
5320 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5321 7111 $max_pages = isset($this->options['pdf_max_pages']) ? intval($this->options['pdf_max_pages']) : 69;
5322 7112
5323 - echo '<div class="' . esc_attr($class) . '">';
5324 7113 echo sprintf(
5325 7114 '<input type="range"
5326 7115 id="pdf_max_pages"
5327 7116 name="pdf_max_pages"
@@ -5327,37 +7116,22 @@
5327 7116 name="pdf_max_pages"
5328 7117 min="1"
5329 7118 max="69"
5330 7119 value="%d"
5331 - %s
5332 7120 class="range-slider" />',
5333 - esc_attr($max_pages),
5334 - esc_attr($disabled)
7121 + esc_attr($max_pages)
5335 7122 );
5336 7123 echo '<span id="pdf_max_pages_output">' . esc_html($max_pages) . '</span>';
5337 7124 echo '<p class="description">' . esc_html__('Set the maximum number of document pages users can upload for processing. (1-69 pages)', 'mxchat') . '</p>';
5338 -
5339 - if (!$this->is_activated) {
5340 - echo '<div class="pro-feature-overlay">';
5341 - echo '<a href="https://mxchat.ai/" target="_blank">';
5342 - echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
5343 - echo '</a>';
5344 - echo '</div>';
5345 - }
5346 - echo '</div>';
5347 7125 }
5348 7126
5349 7127 public function mxchat_live_agent_status_callback() {
5350 - $disabled = $this->is_activated ? '' : 'disabled';
5351 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5352 7128 $status = isset($this->options['live_agent_status']) ? $this->options['live_agent_status'] : 'off';
5353 7129
5354 - echo '<div class="' . esc_attr($class) . '">';
5355 7130 echo '<label class="toggle-switch">';
5356 7131 echo sprintf(
5357 - '<input type="checkbox" id="live_agent_status" name="live_agent_status" value="on" %s %s />',
5358 - checked($status, 'on', false),
5359 - esc_attr($disabled)
7132 + '<input type="checkbox" id="live_agent_status" name="live_agent_status" value="on" %s />',
7133 + checked($status, 'on', false)
5360 7134 );
5361 7135 echo '<span class="slider"></span>';
5362 7136 echo '</label>';
5363 7137 echo '<label for="live_agent_status" class="mxchat-status-label">';
@@ -5362,105 +7136,78 @@
5362 7136 echo '</label>';
5363 7137 echo '<label for="live_agent_status" class="mxchat-status-label">';
5364 7138 echo '<span class="status-text">' . ($status === 'on' ? esc_html__('Online', 'mxchat') : esc_html__('Offline', 'mxchat')) . '</span>';
5365 7139 echo '</label>';
5366 -
5367 - if (!$this->is_activated) {
5368 - echo '<div class="pro-feature-overlay">';
5369 - 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>';
5370 - echo '</div>';
5371 - }
5372 - echo '</div>';
5373 7140 }
5374 7141
5375 -
5376 -// Away Message Callback
5377 7142 public function mxchat_live_agent_away_message_callback() {
5378 - $disabled = $this->is_activated ? '' : 'disabled';
5379 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5380 7143 $message = isset($this->options['live_agent_away_message'])
5381 7144 ? $this->options['live_agent_away_message']
5382 7145 : __('Sorry, live agents are currently unavailable. I can continue helping you as an AI assistant.', 'mxchat');
5383 7146
5384 - echo '<div class="' . esc_attr($class) . '">';
5385 7147 printf(
5386 - '<textarea id="live_agent_away_message" name="live_agent_away_message" rows="3" cols="50" %s>%s</textarea>',
5387 - esc_attr($disabled),
7148 + '<textarea id="live_agent_away_message" name="live_agent_away_message" rows="3" cols="50">%s</textarea>',
5388 7149 esc_textarea($message)
5389 7150 );
5390 7151 echo '<p class="description">' . esc_html__('Message shown when live agents are offline.', 'mxchat') . '</p>';
5391 -
5392 - if (!$this->is_activated) {
5393 - echo '<div class="pro-feature-overlay">';
5394 - 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>';
5395 - echo '</div>';
5396 - }
5397 - echo '</div>';
5398 7152 }
5399 7153
5400 7154 public function mxchat_live_agent_notification_message_callback() {
5401 - $disabled = $this->is_activated ? '' : 'disabled';
5402 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5403 7155 $message = isset($this->options['live_agent_notification_message'])
5404 7156 ? $this->options['live_agent_notification_message']
5405 7157 : __('Live agent has been notified.', 'mxchat');
5406 7158
5407 - echo '<div class="' . esc_attr($class) . '">';
5408 7159 printf(
5409 - '<textarea id="live_agent_notification_message" name="live_agent_notification_message" rows="3" cols="50" %s>%s</textarea>',
5410 - esc_attr($disabled),
7160 + '<textarea id="live_agent_notification_message" name="live_agent_notification_message" rows="3" cols="50">%s</textarea>',
5411 7161 esc_textarea($message)
5412 7162 );
5413 7163 echo '<p class="description">' . esc_html__('Message shown when live transfer activated.', 'mxchat') . '</p>';
5414 -
5415 - if (!$this->is_activated) {
5416 - echo '<div class="pro-feature-overlay">';
5417 - 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>';
5418 - echo '</div>';
5419 - }
5420 - echo '</div>';
5421 7164 }
5422 7165
5423 7166 public function mxchat_live_agent_webhook_url_callback() {
5424 - $disabled = $this->is_activated ? '' : 'disabled';
5425 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5426 7167 $webhook_url = isset($this->options['live_agent_webhook_url'])
5427 7168 ? esc_url($this->options['live_agent_webhook_url'])
5428 7169 : esc_url(get_option('live_agent_webhook_url', ''));
5429 7170
5430 - echo '<div class="' . esc_attr($class) . '">';
5431 7171 printf(
5432 - '<input type="password" id="live_agent_webhook_url" name="live_agent_webhook_url" value="%s" class="regular-text" %s />',
5433 - $webhook_url,
5434 - esc_attr($disabled)
7172 + '<input type="password" id="live_agent_webhook_url" name="live_agent_webhook_url" value="%s" class="regular-text" />',
7173 + $webhook_url
5435 7174 );
5436 7175 echo '<button type="button" id="toggleWebhookUrlVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5437 7176 echo '<p class="description">' . esc_html__('Enter your Slack webhook URL for live agent notifications.', 'mxchat') . '</p>';
7177 +}
5438 7178
5439 - if (!$this->is_activated) {
5440 - echo '<div class="pro-feature-overlay">';
5441 - 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>';
5442 - echo '</div>';
5443 - }
5444 - echo '</div>';
7179 +public function mxchat_live_agent_secret_key_callback() {
7180 + printf(
7181 + '<input type="password" id="live_agent_secret_key" name="live_agent_secret_key" value="%s" class="regular-text" />',
7182 + isset($this->options['live_agent_secret_key']) ? esc_attr($this->options['live_agent_secret_key']) : ''
7183 + );
7184 + echo '<button type="button" id="toggleSecretKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
7185 + echo '<p class="description">' . esc_html__('Secret key for validating Slack requests. Keep this secure.', 'mxchat') . '</p>';
5445 7186 }
5446 7187
7188 +public function mxchat_live_agent_bot_token_callback() {
7189 + printf(
7190 + '<input type="password" id="live_agent_bot_token" name="live_agent_bot_token" value="%s" class="regular-text" />',
7191 + isset($this->options['live_agent_bot_token']) ? esc_attr($this->options['live_agent_bot_token']) : ''
7192 + );
7193 + echo '<button type="button" id="toggleBotTokenVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
7194 + echo '<p class="description">' . esc_html__('Your Slack Bot OAuth Token (starts with xoxb-). Keep this secure.', 'mxchat') . '</p>';
7195 +}
5447 7196
5448 7197 public function mxchat_similarity_threshold_callback() {
5449 7198 // Load from mxchat_options array
5450 7199 $options = get_option('mxchat_options', []);
5451 -
5452 - // Get value with backwards compatibility
5453 - $threshold = isset($options['similarity_threshold'])
5454 - ? $options['similarity_threshold']
5455 - : get_option('mxchat_similarity_threshold', 80);
5456 -
7200 +
7201 + // Get value from options array with default of 80
7202 + $threshold = isset($options['similarity_threshold']) ? $options['similarity_threshold'] : 35;
7203 +
5457 7204 echo '<div class="slider-container">';
5458 7205 echo sprintf(
5459 7206 '<input type="range"
5460 7207 id="similarity_threshold"
5461 7208 name="similarity_threshold"
5462 - min="70"
7209 + min="20"
5463 7210 max="85"
5464 7211 step="1"
5465 7212 value="%s"
5466 7213 class="range-slider" />',
@@ -5470,63 +7217,18 @@
5470 7217 '<span id="threshold_value" class="range-value">%s</span>',
5471 7218 esc_html($threshold)
5472 7219 );
5473 7220 echo '</div>';
5474 -
5475 7221 echo '<p class="description">';
5476 - 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');
7222 + 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');
5477 7223 echo '</p>';
5478 7224 }
5479 7225
5480 -
5481 -
5482 -public function mxchat_live_agent_secret_key_callback() {
5483 - $disabled = $this->is_activated ? '' : 'disabled';
5484 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5485 -
5486 - echo '<div class="' . esc_attr($class) . '">';
5487 - printf(
5488 - '<input type="password" id="live_agent_secret_key" name="live_agent_secret_key" value="%s" class="regular-text" %s />',
5489 - isset($this->options['live_agent_secret_key']) ? esc_attr($this->options['live_agent_secret_key']) : '',
5490 - esc_attr($disabled)
5491 - );
5492 - echo '<button type="button" id="toggleSecretKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5493 - echo '<p class="description">' . esc_html__('Secret key for validating Slack requests. Keep this secure.', 'mxchat') . '</p>';
5494 -
5495 - if (!$this->is_activated) {
5496 - echo '<div class="pro-feature-overlay">';
5497 - 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>';
5498 - echo '</div>';
5499 - }
5500 - echo '</div>';
5501 -}
5502 -
5503 -public function mxchat_live_agent_bot_token_callback() {
5504 - $disabled = $this->is_activated ? '' : 'disabled';
5505 - $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
5506 -
5507 - echo '<div class="' . esc_attr($class) . '">';
5508 - printf(
5509 - '<input type="password" id="live_agent_bot_token" name="live_agent_bot_token" value="%s" class="regular-text" %s />',
5510 - isset($this->options['live_agent_bot_token']) ? esc_attr($this->options['live_agent_bot_token']) : '',
5511 - esc_attr($disabled)
5512 - );
5513 - echo '<button type="button" id="toggleBotTokenVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5514 - echo '<p class="description">' . esc_html__('Your Slack Bot OAuth Token (starts with xoxb-). Keep this secure.', 'mxchat') . '</p>';
5515 -
5516 - if (!$this->is_activated) {
5517 - echo '<div class="pro-feature-overlay">';
5518 - 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>';
5519 - echo '</div>';
5520 - }
5521 - echo '</div>';
5522 -}
5523 -
5524 7226 public function mxchat_enqueue_admin_assets() {
5525 7227 wp_enqueue_style('wp-color-picker');
5526 7228
5527 7229 // Get the plugin version or file modification time for cache busting
5528 - $plugin_version = '2.0.6'; // Replace this with your plugin's version
7230 + $plugin_version = '2.1.7'; // Replace this with your plugin's version
5529 7231
5530 7232 // File paths
5531 7233 $color_picker_js_path = plugin_dir_path(__FILE__) . '../js/my-color-picker.js';
5532 7234 $embedding_check_js_path = plugin_dir_path(__FILE__) . '../js/embedding-check.js';
@@ -5535,9 +7237,8 @@
5535 7237 $intent_css_path = plugin_dir_path(__FILE__) . '../css/intent-style.css';
5536 7238 $transcripts_css_path = plugin_dir_path(__FILE__) . '../css/chat-transcripts.css';
5537 7239 $transcripts_js_path = plugin_dir_path(__FILE__) . '../js/mxchat_transcripts.js';
5538 7240
5539 -
5540 7241 // Check if files exist and get modification times
5541 7242 $color_picker_version = file_exists($color_picker_js_path) ? filemtime($color_picker_js_path) : $plugin_version;
5542 7243 $embedding_check_version = file_exists($embedding_check_js_path) ? filemtime($embedding_check_js_path) : $plugin_version;
5543 7244 $admin_css_version = file_exists($admin_css_path) ? filemtime($admin_css_path) : $plugin_version;
@@ -5544,10 +7245,36 @@
5544 7245 $knowledge_css_version = file_exists($knowledge_css_path) ? filemtime($knowledge_css_path) : $plugin_version;
5545 7246 $intent_css_version = file_exists($intent_css_path) ? filemtime($intent_css_path) : $plugin_version;
5546 7247 $transcripts_css_version = file_exists($transcripts_css_path) ? filemtime($transcripts_css_path) : $plugin_version;
5547 7248 $transcripts_js_version = file_exists($transcripts_js_path) ? filemtime($transcripts_js_path) : $plugin_version;
5548 -
5549 -
7249 +
7250 + $admin_status_js_path = plugin_dir_path(__FILE__) . '../js/admin-status.js';
7251 + $admin_status_js_version = file_exists($admin_status_js_path) ? filemtime($admin_status_js_path) : $plugin_version;
7252 +
7253 + // Check current admin page
7254 + $current_page = isset($_GET['page']) ? $_GET['page'] : '';
7255 +
7256 + // Only enqueue on the prompts page
7257 + if ($current_page === 'mxchat-prompts') {
7258 + wp_enqueue_script(
7259 + 'mxchat-status-updater',
7260 + plugin_dir_url(__FILE__) . '../js/admin-status.js',
7261 + array('jquery'),
7262 + $admin_status_js_version,
7263 + true
7264 + );
7265 +
7266 + // Add the nonce for the status updater
7267 + wp_localize_script(
7268 + 'mxchat-status-updater',
7269 + 'mxchat_status_data',
7270 + array(
7271 + 'ajax_url' => admin_url('admin-ajax.php'),
7272 + 'nonce' => wp_create_nonce('mxchat_status_nonce')
7273 + )
7274 + );
7275 + }
7276 +
5550 7277 // Enqueue scripts and styles with corrected paths
5551 7278 wp_enqueue_script(
5552 7279 'mxchat-color-picker',
5553 7280 plugin_dir_url(__FILE__) . '../js/my-color-picker.js',
@@ -5571,15 +7298,23 @@
5571 7298 $plugin_version,
5572 7299 true
5573 7300 );
5574 7301
5575 - wp_localize_script('mxchat-admin-js', 'mxchatAdmin', array(
5576 - 'ajax_url' => admin_url('admin-ajax.php'),
5577 - 'license_nonce' => wp_create_nonce('mxchat_activate_license_nonce'),
5578 - 'inline_edit_nonce' => wp_create_nonce('mxchat_save_inline_nonce'),
5579 - 'setting_nonce' => wp_create_nonce('mxchat_save_setting_nonce'),
5580 - 'export_nonce' => wp_create_nonce('mxchat_export_transcripts'),
5581 - ));
7302 +wp_localize_script('mxchat-admin-js', 'mxchatAdmin', array(
7303 + 'ajax_url' => admin_url('admin-ajax.php'),
7304 + 'license_nonce' => wp_create_nonce('mxchat_activate_license_nonce'),
7305 + 'inline_edit_nonce' => wp_create_nonce('mxchat_save_inline_nonce'),
7306 + 'setting_nonce' => wp_create_nonce('mxchat_save_setting_nonce'),
7307 + 'export_nonce' => wp_create_nonce('mxchat_export_transcripts'),
7308 + 'actions_nonce' => wp_create_nonce('mxchat_actions_nonce'),
7309 + 'add_intent_nonce' => wp_create_nonce('mxchat_add_intent_nonce'),
7310 + 'edit_intent_nonce' => wp_create_nonce('mxchat_edit_intent'),
7311 + 'toggle_action_nonce' => wp_create_nonce('mxchat_actions_nonce'),
7312 + 'is_activated' => $this->is_activated ? '1' : '0',
7313 + // Add these two new lines
7314 + 'status_nonce' => wp_create_nonce('mxchat_status_nonce'),
7315 + 'status_refresh_interval' => 5000, // Update every 5 seconds
7316 +));
5582 7317
5583 7318 // Enqueue the admin CSS
5584 7319 wp_enqueue_style(
5585 7320 'mxchat-admin-css',
@@ -5586,10 +7321,11 @@
5586 7321 plugin_dir_url(__FILE__) . '../css/admin-style.css',
5587 7322 array(),
5588 7323 $admin_css_version
5589 7324 );
5590 -
5591 - if (isset($_GET['page']) && $_GET['page'] === 'mxchat-transcripts') {
7325 +
7326 + // Conditional enqueue for transcripts page
7327 + if ($current_page === 'mxchat-transcripts') {
5592 7328 wp_enqueue_style(
5593 7329 'mxchat-chat-transcripts-css',
5594 7330 plugin_dir_url(__FILE__) . '../css/chat-transcripts.css',
5595 7331 array(),
@@ -5594,9 +7330,9 @@
5594 7330 plugin_dir_url(__FILE__) . '../css/chat-transcripts.css',
5595 7331 array(),
5596 7332 $transcripts_css_version
5597 7333 );
5598 -
7334 +
5599 7335 wp_enqueue_script(
5600 7336 'mxchat-transcripts-js',
5601 7337 plugin_dir_url(__FILE__) . '../js/mxchat_transcripts.js',
5602 7338 array('jquery'),
@@ -5603,29 +7339,34 @@
5603 7339 $transcripts_js_version,
5604 7340 true
5605 7341 );
5606 7342 }
7343 +
7344 + // Only enqueue knowledge CSS on knowledge-related pages or all plugin pages
7345 + if (strpos($current_page, 'mxchat') !== false) {
7346 + wp_enqueue_style(
7347 + 'mxchat-knowledge-css',
7348 + plugin_dir_url(__FILE__) . '../css/knowledge-style.css',
7349 + array(),
7350 + $knowledge_css_version
7351 + );
7352 + }
5607 7353
5608 - wp_enqueue_style(
5609 - 'mxchat-knowledge-css',
5610 - plugin_dir_url(__FILE__) . '../css/knowledge-style.css',
5611 - array(),
5612 - $knowledge_css_version
5613 - );
7354 + // Only enqueue intent-style.css on the mxchat-actions page
7355 + if ($current_page === 'mxchat-actions') {
7356 + wp_enqueue_style(
7357 + 'mxchat-intent-css',
7358 + plugin_dir_url(__FILE__) . '../css/intent-style.css',
7359 + array(),
7360 + $intent_css_version
7361 + );
7362 + }
5614 7363
5615 -
5616 - wp_enqueue_style(
5617 - 'mxchat-intent-css',
5618 - plugin_dir_url(__FILE__) . '../css/intent-style.css',
5619 - array(),
5620 - $intent_css_version
5621 - );
5622 -
5623 7364 // IMPORTANT: Use the same script handle as above for localizing mxchatPromptsAdmin
5624 - wp_localize_script( 'mxchat-admin-js', 'mxchatPromptsAdmin', array(
5625 - 'ajax_url' => admin_url( 'admin-ajax.php' ),
5626 - 'prompts_setting_nonce' => wp_create_nonce( 'mxchat_prompts_setting_nonce' ),
5627 - ) );
7365 + wp_localize_script('mxchat-admin-js', 'mxchatPromptsAdmin', array(
7366 + 'ajax_url' => admin_url('admin-ajax.php'),
7367 + 'prompts_setting_nonce' => wp_create_nonce('mxchat_prompts_setting_nonce'),
7368 + ));
5628 7369
5629 7370 // Localize the script for color picker and settings
5630 7371 wp_localize_script('mxchat-color-picker', 'mxchatStyleSettings', array(
5631 7372 'ajax_url' => admin_url('admin-ajax.php'),
@@ -5655,8 +7396,10 @@
5655 7396 'live_agent_bot_token' => $this->options['live_agent_bot_token'] ?? '',
5656 7397 'live_agent_message_bg_color' => $this->options['live_agent_message_bg_color'] ?? '#ffffff',
5657 7398 'live_agent_message_font_color' => $this->options['live_agent_message_font_color'] ?? '#333333',
5658 7399 'chat_toolbar_toggle' => $this->options['chat_toolbar_toggle'] ?? 'off',
7400 + 'show_pdf_upload_button' => $this->options['show_pdf_upload_button'] ?? 'on',
7401 + 'show_word_upload_button' => $this->options['show_word_upload_button'] ?? 'on',
5659 7402 'mode_indicator_bg_color' => $this->options['mode_indicator_bg_color'] ?? '#767676',
5660 7403 'mode_indicator_font_color' => $this->options['mode_indicator_font_color'] ?? '#ffffff',
5661 7404 'toolbar_icon_color' => $this->options['toolbar_icon_color'] ?? '#212121',
5662 7405 ));
@@ -5671,9 +7414,9 @@
5671 7414 }
5672 7415
5673 7416 if (isset($input['similarity_threshold'])) {
5674 7417 $new_input['similarity_threshold'] = absint($input['similarity_threshold']); // Ensure it's an integer
5675 - $new_input['similarity_threshold'] = min(max($new_input['similarity_threshold'], 70), 85); // Enforce range
7418 + $new_input['similarity_threshold'] = min(max($new_input['similarity_threshold'], 20), 85); // Enforce range
5676 7419 }
5677 7420
5678 7421 if (isset($input['xai_api_key'])) {
5679 7422 $new_input['xai_api_key'] = sanitize_text_field($input['xai_api_key']);
@@ -5685,8 +7428,12 @@
5685 7428
5686 7429 if (isset($input['deepseek_api_key'])) {
5687 7430 $new_input['deepseek_api_key'] = sanitize_text_field($input['deepseek_api_key']);
5688 7431 }
7432 +
7433 + if (isset($input['gemini_api_key'])) {
7434 + $new_input['gemini_api_key'] = sanitize_text_field($input['gemini_api_key']);
7435 + }
5689 7436
5690 7437 if (isset($input['enable_woocommerce_integration'])) {
5691 7438 $new_input['enable_woocommerce_integration'] = $input['enable_woocommerce_integration'] === 'on' ? 'on' : 'off';
5692 7439 }
@@ -5723,8 +7470,12 @@
5723 7470
5724 7471 if (isset($input['top_bar_title'])) {
5725 7472 $new_input['top_bar_title'] = sanitize_text_field($input['top_bar_title']);
5726 7473 }
7474 +
7475 + if (isset($input['ai_agent_text'])) {
7476 + $new_input['ai_agent_text'] = sanitize_text_field($input['ai_agent_text']);
7477 + }
5727 7478
5728 7479 if (isset($input['enable_email_block'])) {
5729 7480 $new_input['enable_email_block'] = sanitize_text_field($input['enable_email_block']);
5730 7481 }
@@ -5737,9 +7488,9 @@
5737 7488 $new_input['email_blocker_button_text'] = sanitize_text_field($input['email_blocker_button_text']);
5738 7489 }
5739 7490
5740 7491 if (isset($input['intro_message'])) {
5741 - $new_input['intro_message'] = sanitize_textarea_field($input['intro_message']); // Changed from sanitize_text_field
7492 + $new_input['intro_message'] = wp_kses_post($input['intro_message']); // Use wp_kses_post instead
5742 7493 }
5743 7494
5744 7495 if (isset($input['input_copy'])) {
5745 7496 $new_input['input_copy'] = sanitize_text_field($input['input_copy']);
@@ -5748,49 +7499,59 @@
5748 7499 if (isset($input['rate_limit_message'])) {
5749 7500 $new_input['rate_limit_message'] = sanitize_text_field($input['rate_limit_message']);
5750 7501 }
5751 7502
5752 - if (isset($input['rate_limit_logged_out'])) {
5753 - $allowed_limits = array('1', '3', '5', '10', '15', '20', '100', 'unlimited'); // Add 'unlimited' to allowed values
5754 - $rate_limit = sanitize_text_field($input['rate_limit_logged_out']);
5755 -
5756 - if (in_array($rate_limit, $allowed_limits, true)) {
5757 - $new_input['rate_limit_logged_out'] = $rate_limit;
5758 - } else {
5759 - $new_input['rate_limit_logged_out'] = '10'; // Default for logged-out users
7503 +// Handle the new rate limits format
7504 +if (isset($input['rate_limits']) && is_array($input['rate_limits'])) {
7505 + $new_input['rate_limits'] = array();
7506 + $allowed_limits = array('1', '3', '5', '10', '15', '20', '50', '100', 'unlimited');
7507 + $allowed_timeframes = array('hourly', 'daily', 'weekly', 'monthly');
7508 +
7509 + foreach ($input['rate_limits'] as $role_id => $settings) {
7510 + $new_input['rate_limits'][$role_id] = array();
7511 +
7512 + // Sanitize limit
7513 + if (isset($settings['limit'])) {
7514 + $limit = sanitize_text_field($settings['limit']);
7515 + if (in_array($limit, $allowed_limits, true)) {
7516 + $new_input['rate_limits'][$role_id]['limit'] = $limit;
7517 + } else {
7518 + $new_input['rate_limits'][$role_id]['limit'] = ($role_id === 'logged_out') ? '10' : '100'; // Default
7519 + }
5760 7520 }
5761 - }
5762 -
5763 - // Handle role rate limits
5764 - if (isset($input['role_rate_limits']) && is_array($input['role_rate_limits'])) {
5765 - $allowed_limits = array('1', '3', '5', '10', '15', '20', '100', 'unlimited');
5766 - $new_input['role_rate_limits'] = array();
5767 -
5768 - foreach ($input['role_rate_limits'] as $role_id => $rate_limit) {
5769 - $rate_limit = sanitize_text_field($rate_limit);
5770 - if (in_array($rate_limit, $allowed_limits, true)) {
5771 - $new_input['role_rate_limits'][$role_id] = $rate_limit;
7521 +
7522 + // Sanitize timeframe
7523 + if (isset($settings['timeframe'])) {
7524 + $timeframe = sanitize_text_field($settings['timeframe']);
7525 + if (in_array($timeframe, $allowed_timeframes, true)) {
7526 + $new_input['rate_limits'][$role_id]['timeframe'] = $timeframe;
5772 7527 } else {
5773 - $new_input['role_rate_limits'][$role_id] = '100'; // Default
7528 + $new_input['rate_limits'][$role_id]['timeframe'] = 'daily'; // Default
5774 7529 }
5775 7530 }
7531 +
7532 + // Sanitize message
7533 + if (isset($settings['message'])) {
7534 + $new_input['rate_limits'][$role_id]['message'] = sanitize_textarea_field($settings['message']);
7535 + }
5776 7536 }
7537 +}
5777 7538
5778 7539 if (isset($input['pre_chat_message'])) {
5779 7540 $new_input['pre_chat_message'] = sanitize_textarea_field($input['pre_chat_message']);
5780 7541 }
5781 -
7542 +
5782 7543 if (isset($input['voyage_api_key'])) {
5783 7544 $new_input['voyage_api_key'] = sanitize_text_field($input['voyage_api_key']);
5784 7545 }
5785 -
7546 +
5786 7547 // Add to your sanitize function
5787 7548 if (isset($input['embedding_model'])) {
5788 7549 $allowed_models = array(
5789 - 'text-embedding-ada-002',
5790 - 'text-embedding-3-small',
5791 - 'text-embedding-3-large',
5792 - 'voyage-3-large'
7550 + 'text-embedding-ada-002',
7551 + 'text-embedding-3-small',
7552 + 'text-embedding-3-large',
7553 + 'voyage-3-large'
5793 7554 );
5794 7555 if (in_array($input['embedding_model'], $allowed_models)) {
5795 7556 $new_input['embedding_model'] = sanitize_text_field($input['embedding_model']);
5796 7557 }
@@ -5795,23 +7556,32 @@
5795 7556 $new_input['embedding_model'] = sanitize_text_field($input['embedding_model']);
5796 7557 }
5797 7558 }
5798 7559
5799 - if (isset($input['model'])) {
7560 + if (isset($input['model'])) {
5800 7561 $allowed_models = array(
5801 - 'grok-beta',
5802 - 'grok-2',
5803 - 'deepseek-chat',
5804 - 'claude-3-5-sonnet-20241022',
5805 - 'claude-3-opus-20240229',
5806 - 'claude-3-sonnet-20240229',
5807 - 'claude-3-haiku-20240307',
5808 - 'gpt-4o',
5809 - 'gpt-4o-mini',
5810 - 'gpt-4-turbo',
5811 - 'gpt-4',
5812 - 'gpt-3.5-turbo',
5813 - );
7562 + 'gemini-2.0-flash',
7563 + 'gemini-2.0-flash-lite',
7564 + 'gemini-1.5-pro',
7565 + 'gemini-1.5-flash',
7566 + 'grok-3-beta',
7567 + 'grok-3-fast-beta',
7568 + 'grok-3-mini-beta',
7569 + 'grok-3-mini-fast-beta',
7570 + 'grok-2',
7571 + 'deepseek-chat',
7572 + 'claude-3-7-sonnet-20250219',
7573 + 'claude-3-5-sonnet-20241022',
7574 + 'claude-3-opus-20240229',
7575 + 'claude-3-sonnet-20240229',
7576 + 'claude-3-haiku-20240307',
7577 + 'gpt-4o',
7578 + 'gpt-4.1-2025-04-14',
7579 + 'gpt-4o-mini',
7580 + 'gpt-4-turbo',
7581 + 'gpt-4',
7582 + 'gpt-3.5-turbo',
7583 + );
5814 7584 if (in_array($input['model'], $allowed_models)) {
5815 7585 $new_input['model'] = sanitize_text_field($input['model']);
5816 7586 }
5817 7587 }
@@ -5970,8 +7740,22 @@
5970 7740
5971 7741 if (isset($input['chat_toolbar_toggle'])) {
5972 7742 $new_input['chat_toolbar_toggle'] = $input['chat_toolbar_toggle'] === 'on' ? 'on' : 'off';
5973 7743 }
7744 +
7745 + // Sanitize PDF upload button toggle
7746 + if (isset($input['show_pdf_upload_button'])) {
7747 + $new_input['show_pdf_upload_button'] = $input['show_pdf_upload_button'] === 'on' ? 'on' : 'off';
7748 + } else {
7749 + $new_input['show_pdf_upload_button'] = 'off'; // If checkbox is unchecked
7750 + }
7751 +
7752 + // Sanitize Word upload button toggle
7753 + if (isset($input['show_word_upload_button'])) {
7754 + $new_input['show_word_upload_button'] = $input['show_word_upload_button'] === 'on' ? 'on' : 'off';
7755 + } else {
7756 + $new_input['show_word_upload_button'] = 'off'; // If checkbox is unchecked
7757 + }
5974 7758
5975 7759 if (isset($input['pdf_intent_trigger_text'])) {
5976 7760 $new_input['pdf_intent_trigger_text'] = sanitize_text_field($input['pdf_intent_trigger_text']);
5977 7761 }
@@ -6026,62 +7810,10 @@
6026 7810 }
6027 7811
6028 7812
6029 7813
6030 -private static function mxchat_extract_main_content($html) {
6031 - if (empty($html)) {
6032 - //error_log('mxchat_extract_main_content: Empty HTML content received');
6033 - return '';
6034 - }
6035 7814
6036 - try {
6037 - $dom = new DOMDocument;
6038 - libxml_use_internal_errors(true); // Suppress HTML parsing errors
6039 7815
6040 - // Simple load of HTML, with @ to suppress warnings
6041 - @$dom->loadHTML($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
6042 -
6043 - $xpath = new DOMXPath($dom);
6044 -
6045 - // Simplified selectors focusing on common content areas
6046 - $selectors = [
6047 - '//article',
6048 - '//*[@id="content"]',
6049 - '//*[@class="entry-content"]',
6050 - '//main'
6051 - ];
6052 -
6053 - foreach ($selectors as $selector) {
6054 - $nodes = $xpath->query($selector);
6055 - if ($nodes && $nodes->length > 0) {
6056 - $content = '';
6057 - foreach ($nodes as $node) {
6058 - $content .= $dom->saveHTML($node);
6059 - }
6060 - if (!empty($content)) {
6061 - return $content;
6062 - }
6063 - }
6064 - }
6065 -
6066 - // Fallback: Return the entire body content if no specific selector matches
6067 - $body = $dom->getElementsByTagName('body');
6068 - if ($body->length > 0) {
6069 - return $dom->saveHTML($body->item(0));
6070 - }
6071 -
6072 - // Last resort: return the original HTML
6073 - return $html;
6074 -
6075 - } catch (Exception $e) {
6076 - //error_log('mxchat_extract_main_content error: ' . $e->getMessage());
6077 - return $html; // Return original HTML if parsing fails
6078 - } finally {
6079 - libxml_clear_errors();
6080 - }
6081 -}
6082 -
6083 -
6084 7816 private function mxchat_fetch_loops_mailing_lists($api_key) {
6085 7817 $url = 'https://app.loops.so/api/v1/lists';
6086 7818 $response = wp_remote_get($url, array(
6087 7819 'headers' => array(
@@ -6126,8 +7858,81 @@
6126 7858 }
6127 7859 }
6128 7860
6129 7861
7862 +/**
7863 + * Update your existing display_admin_notices function to show notices on all MXChat pages
7864 + */
7865 +public function display_admin_notices() {
7866 + // Check if we're on a MXChat admin page
7867 + $screen = get_current_screen();
7868 + if (!$screen || strpos($screen->base, 'mxchat') === false) {
7869 + return;
7870 + }
7871 +
7872 + //error_log('MxChat admin_notices hook fired on screen: ' . $screen->base);
7873 +
7874 + // Check for error notices
7875 + $error_notice = get_transient('mxchat_admin_notice_error');
7876 + if ($error_notice) {
7877 + //error_log('Found error transient: ' . $error_notice);
7878 + echo '<div class="notice notice-error is-dismissible"><p>' . wp_kses_post($error_notice) . '</p></div>';
7879 + delete_transient('mxchat_admin_notice_error');
7880 + //error_log('Displayed and deleted error transient');
7881 + } else {
7882 + //error_log('No error transient found');
7883 + }
7884 +
7885 + // Check for success notices
7886 + $success_notice = get_transient('mxchat_admin_notice_success');
7887 + if ($success_notice) {
7888 + //error_log('Found success transient: ' . $success_notice);
7889 + echo '<div class="notice notice-success is-dismissible"><p>' . wp_kses_post($success_notice) . '</p></div>';
7890 + delete_transient('mxchat_admin_notice_success');
7891 + //error_log('Displayed and deleted success transient');
7892 + }
7893 +
7894 + // Check for info notices
7895 + $info_notice = get_transient('mxchat_admin_notice_info');
7896 + if ($info_notice) {
7897 + //error_log('Found info transient: ' . $info_notice);
7898 + echo '<div class="notice notice-info is-dismissible"><p>' . wp_kses_post($info_notice) . '</p></div>';
7899 + delete_transient('mxchat_admin_notice_info');
7900 + //error_log('Displayed and deleted info transient');
7901 + }
7902 +
7903 + // Display active processing status
7904 + $this->display_processing_status();
7905 +}
7906 +
7907 +
7908 +
7909 +/**
7910 + * Display current processing status
7911 + */
7912 +private function display_processing_status() {
7913 + $pdf_url = get_transient('mxchat_last_pdf_url');
7914 + $sitemap_url = get_transient('mxchat_last_sitemap_url');
7915 +
7916 + if (!$pdf_url && !$sitemap_url) {
7917 + return;
7918 + }
7919 +
7920 + $pdf_status = $pdf_url ? $this->get_pdf_processing_status($pdf_url) : false;
7921 + $sitemap_status = $sitemap_url ? $this->get_sitemap_processing_status($sitemap_url) : false;
7922 +
7923 + if ($sitemap_status && isset($sitemap_status['error']) && !empty($sitemap_status['error'])) {
7924 + echo '<div class="notice notice-error is-dismissible">';
7925 + echo '<p><strong>' . esc_html__('Sitemap Processing Error:', 'mxchat') . '</strong> ' . esc_html($sitemap_status['error']) . '</p>';
7926 + echo '</div>';
7927 + }
7928 +
7929 + if ($pdf_status && isset($pdf_status['error']) && !empty($pdf_status['error'])) {
7930 + echo '<div class="notice notice-error is-dismissible">';
7931 + echo '<p><strong>' . esc_html__('PDF Processing Error:', 'mxchat') . '</strong> ' . esc_html($pdf_status['error']) . '</p>';
7932 + echo '</div>';
7933 + }
7934 +}
6130 7935
6131 7936
6132 7937 }
6133 7938 ?>