PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 2.2.1
MxChat – AI Chatbot & Content Generation for WordPress v2.2.1
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-integrator.php +1940 -1301 2.0.32.2.1 View file →
@@ -10,49 +10,67 @@
10 10 private $fallbackResponse;
11 11 private $productCardHtml;
12 12 private $word_handler;
13 13
14 +/**
15 + * Setup the cron jobs for rate limits
16 + */
17 +public function setup_rate_limit_cron_jobs() {
18 + // Clear previous schedules
19 + wp_clear_scheduled_hook('mxchat_reset_rate_limits');
20 + wp_clear_scheduled_hook('mxchat_reset_hourly_rate_limits');
21 + wp_clear_scheduled_hook('mxchat_reset_daily_rate_limits');
22 + wp_clear_scheduled_hook('mxchat_reset_weekly_rate_limits');
23 + wp_clear_scheduled_hook('mxchat_reset_monthly_rate_limits');
24 +
25 + // Schedule the main rate limit reset check (runs hourly)
26 + if (!wp_next_scheduled('mxchat_reset_rate_limits')) {
27 + wp_schedule_event(time(), 'hourly', 'mxchat_reset_rate_limits');
28 + }
29 +}
30 +
31 +/**
32 + * Class constructor
33 + */
14 34 public function __construct() {
15 35 $this->options = get_option('mxchat_options');
16 36 $this->prompts_options = get_option('mxchat_prompts_options', array());
17 -
18 37 $this->chat_count = get_option('mxchat_chat_count', 0);
19 38 $this->word_handler = new MXChat_Word_Handler($this->options);
20 -
39 +
40 + // Setup the cron jobs for rate limits
41 + $this->setup_rate_limit_cron_jobs();
42 +
43 + // Add all action hooks
21 44 add_action('wp_enqueue_scripts', array($this, 'mxchat_enqueue_scripts_styles'));
22 45 add_action('wp_ajax_mxchat_handle_chat_request', array($this, 'mxchat_handle_chat_request'));
23 46 add_action('wp_ajax_nopriv_mxchat_handle_chat_request', array($this, 'mxchat_handle_chat_request'));
24 -
25 47 add_action('wp_ajax_mxchat_dismiss_pre_chat_message', array($this, 'mxchat_dismiss_pre_chat_message'));
26 48 add_action('wp_ajax_nopriv_mxchat_dismiss_pre_chat_message', array($this, 'mxchat_dismiss_pre_chat_message'));
49 +
27 50 // Add the AJAX actions for checking if the pre-chat message was dismissed
28 51 add_action('wp_ajax_mxchat_check_pre_chat_message_status', array($this, 'mxchat_check_pre_chat_message_status'));
29 52 add_action('wp_ajax_nopriv_mxchat_check_pre_chat_message_status', array($this, 'mxchat_check_pre_chat_message_status'));
30 -
31 53 add_action('wp_ajax_mxchat_fetch_conversation_history', [$this, 'mxchat_fetch_conversation_history']);
32 54 add_action('wp_ajax_nopriv_mxchat_fetch_conversation_history', [$this, 'mxchat_fetch_conversation_history']);
33 -
34 55 add_action('wp_ajax_mxchat_add_to_cart', [$this, 'mxchat_add_to_cart']);
35 56 add_action('wp_ajax_nopriv_mxchat_add_to_cart', [$this, 'mxchat_add_to_cart']);
36 -
37 - if (!wp_next_scheduled('mxchat_reset_rate_limits')) {
38 - wp_schedule_event(time(), 'daily', 'mxchat_reset_rate_limits');
39 - }
40 -
57 +
41 58 // Add REST API routes registration
42 59 add_action('rest_api_init', array($this, 'register_routes'));
43 -
44 60 add_action('wp_ajax_mxchat_fetch_new_messages', array($this, 'mxchat_fetch_new_messages'));
45 61 add_action('wp_ajax_nopriv_mxchat_fetch_new_messages', array($this, 'mxchat_fetch_new_messages'));
46 -
62 +
63 + // Rate limit action - notice we removed the old schedule setup
47 64 add_action('mxchat_reset_rate_limits', array($this, 'mxchat_reset_rate_limits'));
48 -
65 +
66 + // File upload and handling actions
49 67 add_action('wp_ajax_mxchat_upload_pdf', [$this, 'handle_pdf_upload']);
50 68 add_action('wp_ajax_nopriv_mxchat_upload_pdf', [$this, 'handle_pdf_upload']);
51 69 add_action('wp_ajax_mxchat_remove_pdf', [$this, 'handle_pdf_remove']);
52 70 add_action('wp_ajax_nopriv_mxchat_remove_pdf', [$this, 'handle_pdf_remove']);
53 -
54 - // Add these with your other add_action hooks
71 +
72 + // Word document handling actions
55 73 add_action('wp_ajax_mxchat_upload_word', array($this, 'mxchat_handle_word_upload'));
56 74 add_action('wp_ajax_nopriv_mxchat_upload_word', array($this, 'mxchat_handle_word_upload'));
57 75 add_action('wp_ajax_mxchat_remove_word', array($this, 'mxchat_handle_word_remove'));
58 76 add_action('wp_ajax_nopriv_mxchat_remove_word', array($this, 'mxchat_handle_word_remove'));
@@ -57,9 +75,10 @@
57 75 add_action('wp_ajax_mxchat_remove_word', array($this, 'mxchat_handle_word_remove'));
58 76 add_action('wp_ajax_nopriv_mxchat_remove_word', array($this, 'mxchat_handle_word_remove'));
59 77 add_action('wp_ajax_mxchat_check_word_status', array($this, 'mxchat_check_word_status'));
60 78 add_action('wp_ajax_nopriv_mxchat_check_word_status', array($this, 'mxchat_check_word_status'));
61 -
79 +
80 + // Email handling actions
62 81 add_action('wp_ajax_nopriv_mxchat_handle_save_email_and_response', [$this, 'mxchat_handle_save_email_and_response']);
63 82 add_action('wp_ajax_mxchat_handle_save_email_and_response', [$this, 'mxchat_handle_save_email_and_response']);
64 83 add_action('wp_ajax_nopriv_mxchat_check_email_provided', [$this, 'mxchat_check_email_provided']);
65 84 add_action('wp_ajax_mxchat_check_email_provided', [$this, 'mxchat_check_email_provided']);
@@ -260,8 +279,9 @@
260 279
261 280 // Compare signatures
262 281 return hash_equals($my_signature, $slack_signature);
263 282 }
283 +
264 284 public function mxchat_stream_events(WP_REST_Request $request) {
265 285 header('Content-Type: text/event-stream');
266 286 header('Cache-Control: no-cache');
267 287 header('Connection: keep-alive');
@@ -297,18 +317,26 @@
297 317
298 318
299 319 private function mxchat_save_chat_message($session_id, $role, $message) {
300 320 global $wpdb;
301 -
302 321 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
303 322 //error_log("[DEBUG] mxchat_save_chat_message -> START for session_id: {$session_id}, role: {$role}");
304 -
323 +
324 + // Check if this is the first message in a new session (before any other database operations)
325 + $is_new_session = false;
326 + if ($role === 'user') { // Only check for user messages, not bot responses
327 + $existing_messages = $wpdb->get_var($wpdb->prepare(
328 + "SELECT COUNT(*) FROM $table_name WHERE session_id = %s",
329 + $session_id
330 + ));
331 + $is_new_session = ($existing_messages == 0);
332 + }
333 +
305 334 // 1) Extract agent name if present
306 335 $agent_name = '';
307 336 if (preg_match('/^Agent: (.*?) - /', $message, $matches)) {
308 337 $agent_name = $matches[1];
309 338 $message = str_replace("Agent: $agent_name - ", '', $message);
310 -
311 339 $session_meta_key = "mxchat_agent_name_{$session_id}";
312 340 if (empty(get_option($session_meta_key))) {
313 341 update_option($session_meta_key, $agent_name);
314 342 //error_log("[DEBUG] mxchat_save_chat_message -> Stored agent_name in option: {$session_meta_key} => {$agent_name}");
@@ -313,30 +341,24 @@
313 341 update_option($session_meta_key, $agent_name);
314 342 //error_log("[DEBUG] mxchat_save_chat_message -> Stored agent_name in option: {$session_meta_key} => {$agent_name}");
315 343 }
316 344 }
317 -
318 345 // 2) Generate unique message_id
319 346 $message_id = uniqid();
320 347 //error_log("[DEBUG] mxchat_save_chat_message -> Generated message_id: {$message_id}");
321 -
322 348 // 3) Determine user_id
323 349 $user_id = is_user_logged_in() ? get_current_user_id() : 0;
324 -
325 350 // 4) Determine user_identifier
326 351 $user_identifier = $agent_name
327 352 ? $agent_name
328 353 : MxChat_User::mxchat_get_user_identifier();
329 -
330 354 // 5) Determine displayed_name
331 355 $user_email = MxChat_User::mxchat_get_user_email();
332 356 $displayed_name = $agent_name ? $agent_name : ($user_email ?: $user_identifier);
333 -
334 357 // 6) Check for a saved email in wp_options
335 358 $email_option_key = "mxchat_email_{$session_id}";
336 359 $saved_email = get_option($email_option_key);
337 360 //error_log("[DEBUG] mxchat_save_chat_message -> Checking wp_options for email_option_key: {$email_option_key}, found: {$saved_email}");
338 -
339 361 // If found, update DB user_email
340 362 if ($saved_email) {
341 363 $update_res = $wpdb->update(
342 364 $table_name,
@@ -346,9 +368,8 @@
346 368 ['%s']
347 369 );
348 370 //error_log("[DEBUG] mxchat_save_chat_message -> Attempted DB user_email update for session_id {$session_id}. update_res: {$update_res}");
349 371 }
350 -
351 372 // 7) Save to session history in wp_options
352 373 $history_key = "mxchat_history_{$session_id}";
353 374 $history = get_option($history_key, []);
354 375 $history[] = [
@@ -357,11 +378,10 @@
357 378 'content' => $message,
358 379 'timestamp' => round(microtime(true) * 1000),
359 380 'agent_name' => $displayed_name,
360 381 ];
361 - update_option($history_key, $history);
382 + update_option($history_key, $history, 'no');
362 383 //error_log("[DEBUG] mxchat_save_chat_message -> Updated session history in option: {$history_key}");
363 -
364 384 // 8) Save the message to DB (INSERT)
365 385 $insert_data = [
366 386 'user_id' => $user_id,
367 387 'user_identifier'=> $user_identifier,
@@ -372,12 +392,64 @@
372 392 'timestamp' => current_time('mysql', 1),
373 393 ];
374 394 $wpdb->insert($table_name, $insert_data);
375 395 //error_log("[DEBUG] mxchat_save_chat_message -> Inserted message into DB. row_id: {$wpdb->insert_id}, data: " . print_r($insert_data, true));
376 -
396 +
397 + // 9) Send notification email if this is the first user message in a new session
398 + if ($wpdb->insert_id && $is_new_session && $role === 'user') {
399 + $this->send_new_chat_notification($session_id, array(
400 + 'identifier' => $user_identifier,
401 + 'email' => $saved_email ?: $user_email,
402 + 'ip' => $_SERVER['REMOTE_ADDR']
403 + ));
404 + }
405 +
377 406 //error_log("[DEBUG] mxchat_save_chat_message -> END for session_id: {$session_id}");
378 407 return $message_id;
379 408 }
409 +private function send_new_chat_notification($session_id, $user_info = array()) {
410 + $options = get_option('mxchat_transcripts_options');
411 +
412 + // Check if notifications are enabled
413 + if (empty($options['mxchat_enable_notifications'])) {
414 + return false;
415 + }
416 +
417 + // Get notification email
418 + $to = !empty($options['mxchat_notification_email']) ?
419 + $options['mxchat_notification_email'] :
420 + get_option('admin_email');
421 +
422 + if (!is_email($to)) {
423 + return false;
424 + }
425 +
426 + // Prepare email content
427 + $subject = sprintf('[%s] New Chat Session Started', get_bloginfo('name'));
428 +
429 + $user_identifier = isset($user_info['identifier']) ? $user_info['identifier'] : 'Guest';
430 + $user_email = isset($user_info['email']) ? $user_info['email'] : 'Not provided';
431 + $user_ip = isset($user_info['ip']) ? $user_info['ip'] : $_SERVER['REMOTE_ADDR'];
432 +
433 + $message = sprintf(
434 + "A new chat session has started on your website.\n\n" .
435 + "Session ID: %s\n" .
436 + "User: %s\n" .
437 + "Email: %s\n" .
438 + "IP Address: %s\n" .
439 + "Time: %s\n\n" .
440 + "View transcripts: %s",
441 + $session_id,
442 + $user_identifier,
443 + $user_email,
444 + $user_ip,
445 + current_time('mysql'),
446 + admin_url('admin.php?page=mxchat-transcripts')
447 + );
448 +
449 + // Send email
450 + return wp_mail($to, $subject, $message);
451 +}
380 452
381 453 public function mxchat_handle_save_email_and_response() {
382 454 //error_log('[DEBUG] ---------- mxchat_handle_save_email_and_response START ----------');
383 455
@@ -382,9 +454,9 @@
382 454 //error_log('[DEBUG] ---------- mxchat_handle_save_email_and_response START ----------');
383 455
384 456 // Validate nonce
385 457 if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'mxchat_chat_nonce')) {
386 - error_log(esc_html__('[ERROR] Invalid nonce in mxchat_handle_save_email_and_response', 'mxchat'));
458 + //error_log(esc_html__('[ERROR] Invalid nonce in mxchat_handle_save_email_and_response', 'mxchat'));
387 459 wp_send_json_error(['message' => esc_html__('Invalid nonce.', 'mxchat')]);
388 460 wp_die();
389 461 }
390 462
@@ -468,52 +540,8 @@
468 540 wp_send_json_error(['message' => esc_html__('No email found', 'mxchat')]);
469 541 }
470 542 }
471 543
472 -
473 -// First, add this helper function to get the highest rate limit for a user's roles
474 -private function get_user_role_rate_limit($user_id) {
475 - //error_log(esc_html__("Checking rate limit for user ID: ", 'mxchat') . $user_id);
476 -
477 - if (!$user_id) {
478 - //error_log(esc_html__("No user ID provided, using logged-out limit: ", 'mxchat') . ($this->options['rate_limit_logged_out'] ?? '10'));
479 - return $this->options['rate_limit_logged_out'] ?? '10';
480 - }
481 -
482 - $user = get_userdata($user_id);
483 - if (!$user || !$user->roles) {
484 - //error_log(esc_html__("No user data or roles found, using logged-out limit: ", 'mxchat') . ($this->options['rate_limit_logged_out'] ?? '10'));
485 - return $this->options['rate_limit_logged_out'] ?? '10';
486 - }
487 -
488 - //error_log(esc_html__("User roles: ", 'mxchat') . print_r($user->roles, true));
489 - //error_log(esc_html__("Available role rate limits: ", 'mxchat') . print_r($this->options['role_rate_limits'] ?? [], true));
490 -
491 - $max_limit = 0;
492 - foreach ($user->roles as $role) {
493 - //error_log(esc_html__("Checking limit for role: ", 'mxchat') . $role);
494 - if (isset($this->options['role_rate_limits'][$role])) {
495 - $role_limit = $this->options['role_rate_limits'][$role];
496 - //error_log(esc_html__("Found limit for role ", 'mxchat') . $role . esc_html__(": ", 'mxchat') . $role_limit);
497 -
498 - if ($role_limit === 'unlimited') {
499 - //error_log(esc_html__("Returning unlimited for role: ", 'mxchat') . $role);
500 - return 'unlimited';
501 - }
502 -
503 - $max_limit = max($max_limit, (int)$role_limit);
504 - //error_log(esc_html__("Current max limit: ", 'mxchat') . $max_limit);
505 - } else {
506 - //error_log(esc_html__("No limit found for role: ", 'mxchat') . $role);
507 - }
508 - }
509 -
510 - $final_limit = $max_limit > 0 ? (string)$max_limit : '100';
511 - //error_log(esc_html__("Final rate limit: ", 'mxchat') . $final_limit);
512 - return $final_limit;
513 -}
514 -
515 -
516 544 // Add this to your plugin's main PHP file
517 545 public function mxchat_check_new_messages() {
518 546 if (!isset($_POST['session_id']) || !isset($_POST['last_seen_id'])) {
519 547 wp_send_json_error(['message' => 'Missing required parameters']);
@@ -591,76 +619,42 @@
591 619 wp_die();
592 620 }
593 621 }
594 622
623 +$this->fallbackResponse = ['text' => '', 'html' => '', 'images' => []];
624 +$this->productCardHtml = '';
595 625
596 - // Reset fallback response at the start of each request
597 - $this->fallbackResponse = ['text' => '', 'html' => '', 'images' => []];
598 - $this->productCardHtml = '';
626 +// Get the actual WordPress user ID if logged in
627 +$is_logged_in = is_user_logged_in();
628 +if ($is_logged_in) {
629 + $user_id = get_current_user_id(); // This will get the actual WordPress user ID
630 +} else {
631 + // For logged-out users, use your existing identifier method
632 + $user_id = $this->mxchat_get_user_identifier();
633 +}
599 634
600 - // Get the actual WordPress user ID if logged in
601 - $is_logged_in = is_user_logged_in();
602 - if ($is_logged_in) {
603 - $user_id = get_current_user_id(); // This will get the actual WordPress user ID
604 - } else {
605 - // For logged-out users, use your existing identifier method
606 - $user_id = $this->mxchat_get_user_identifier();
607 - }
635 +// Get and sanitize the user identifier
636 +$user_id = sanitize_key($user_id);
608 637
609 - // Get and sanitize the user identifier
610 - $user_id = sanitize_key($user_id);
638 +// Check rate limit using new settings structure
639 +$rate_limit_result = $this->check_rate_limit();
611 640
612 - // Determine if user is logged in
613 - $is_logged_in = is_user_logged_in();
614 - //error_log("User logged in status: " . ($is_logged_in ? 'true' : 'false'));
641 +// Add this at the start of your rate limit checking in mxchat_handle_chat_request()
642 +//error_log('MXChat Rate Limit: Starting rate limit check in handle_chat_request()');
615 643
616 - // Get rate limit based on user status
617 - // Get rate limit based on user status
618 - $rate_limit = $is_logged_in
619 - ? $this->get_user_role_rate_limit($user_id)
620 - : ($this->options['rate_limit_logged_out'] ?? '10');
644 +// Then right after checking the result:
645 +if ($rate_limit_result !== true) {
646 + //error_log('MXChat Rate Limit: Rate limit exceeded, returning error');
647 + wp_send_json([
648 + 'success' => false,
649 + 'message' => $rate_limit_result['message'],
650 + 'status' => 'rate_limit_exceeded'
651 + ]);
652 + wp_die();
653 +} else {
654 + //error_log('MXChat Rate Limit: Check passed successfully');
655 +}
621 656
622 - //error_log("Selected rate limit: " . $rate_limit);
623 -
624 - // Rest of your code remains the same
625 - // If rate limit is 'unlimited', skip rate limiting checks
626 - if ($rate_limit !== 'unlimited') {
627 - // Convert rate limit to integer
628 - $rate_limit = intval($rate_limit);
629 - // Setup rate limiting
630 - $rate_limit_transient_key = 'mxchat_chat_limit_' . $user_id;
631 - $chat_count = get_transient($rate_limit_transient_key);
632 - if ($chat_count === false) {
633 - // Initialize new counter if none exists
634 - $chat_count = 0;
635 - }
636 - // Check if user has exceeded their rate limit
637 - if ($chat_count >= $rate_limit) {
638 - // Get custom rate limit message or use default
639 - $rate_limit_message = isset($this->options['rate_limit_message'])
640 - ? $this->options['rate_limit_message']
641 - : esc_html__('Rate limit exceeded. Please try again later.', 'mxchat');
642 - // Replace placeholder if it exists in the message
643 - $rate_limit_message = str_replace(
644 - array('{limit}', '{count}', '{remaining}'),
645 - array($rate_limit, $chat_count, max(0, $rate_limit - $chat_count)),
646 - $rate_limit_message
647 - );
648 - wp_send_json([
649 - 'success' => false,
650 - 'message' => $rate_limit_message,
651 - 'status' => 'rate_limit_exceeded',
652 - 'limit' => $rate_limit,
653 - 'count' => $chat_count
654 - ]);
655 - wp_die();
656 - }
657 - // Increment the counter
658 - $chat_count++;
659 - // Store the updated count with 24-hour expiration
660 - set_transient($rate_limit_transient_key, $chat_count, DAY_IN_SECONDS);
661 - }
662 -
663 657 // Rest of your existing code...
664 658 $session_id = isset($_POST['session_id']) ? sanitize_text_field($_POST['session_id']) : '';
665 659 //error_log("Session ID: $session_id");
666 660
@@ -703,8 +697,30 @@
703 697
704 698 // Preserve code blocks from markdown conversion
705 699 $message = preg_replace('/```(\w+)?\s*([\s\S]+?)```/s', '<pre><code class="$1">$2</code></pre>', $message);
706 700
701 +// Check if any add-ons want to pre-process this message (for web search etc.)
702 +$pre_processed_result = apply_filters('mxchat_pre_process_message', $message, $user_id, $session_id);
703 +
704 +// If the pre-processing returned a result (not the original message), use it directly
705 +if (is_array($pre_processed_result) && isset($pre_processed_result['text'])) {
706 + // Save the AI response
707 + $this->mxchat_save_chat_message($session_id, 'bot', $pre_processed_result['text']);
708 +
709 + // Save HTML content if provided
710 + if (!empty($pre_processed_result['html'])) {
711 + $this->mxchat_save_chat_message($session_id, 'bot', $pre_processed_result['html']);
712 + }
713 +
714 + // Return the response
715 + wp_send_json([
716 + 'text' => $pre_processed_result['text'],
717 + 'html' => $pre_processed_result['html'] ?? '',
718 + 'session_id' => $session_id
719 + ]);
720 + wp_die();
721 +}
722 +
707 723 // Save the user's message
708 724 $this->mxchat_save_chat_message($session_id, 'user', $message);
709 725
710 726 // Check if the message is an email address
@@ -856,51 +872,130 @@
856 872 wp_die();
857 873 }
858 874 }
859 875 }
876 +
877 +
878 +// Add this before the intent check section (before Step 2) in mxchat_handle_chat_request
879 +// Check if there's an active recommendation flow session
880 +$flow_state = get_option("mxchat_sr_flow_state_{$session_id}", array());
881 +if (!empty($flow_state) && isset($flow_state['flow_id'])) {
882 + //error_log('MXCHAT DEBUG: Detected active recommendation flow, routing directly');
883 +
884 + // Create a dummy intent object that matches the original intent
885 + $dummy_intent = new stdClass();
886 + $dummy_intent->intent_label = 'Recommendation Flow ' . $flow_state['flow_id'];
887 + $dummy_intent->phrases = ''; // Empty phrases to avoid matching the original trigger
888 +
889 + // Call the recommendation flow handler directly
890 + $response_data = apply_filters('mxchat_sr_recommendation_flow', false, $message, $user_id, $session_id, $dummy_intent);
891 +
892 + // If the handler returned a response, send it
893 + if (is_array($response_data) && (isset($response_data['text']) || isset($response_data['html']))) {
894 + // Save the bot's response to the chat history
895 + if (!empty($response_data['text'])) {
896 + $this->mxchat_save_chat_message($session_id, 'bot', $response_data['text']);
897 + }
898 + if (!empty($response_data['html'])) {
899 + $this->mxchat_save_chat_message($session_id, 'bot', $response_data['html']);
900 + }
901 +
902 + // Send the response
903 + wp_send_json($response_data);
904 + wp_die();
905 + }
906 +
907 + // If we reach here, the flow handler didn't provide a usable response
908 + // We'll continue with regular processing
909 + //error_log('MXCHAT DEBUG: Recommendation flow handler did not provide a usable response');
910 +}
860 911
861 - // Step 2: Detect intent and handle intent-based responses
862 - $intent_matched = $this->mxchat_check_intent_and_invoke_callback($message, $user_id, $session_id);
863 - //error_log("Intent Matched: " . ($intent_matched ? "Yes" : "No"));
912 + // Step 2: Detect intent and handle intent-based responses
913 +$intent_result = $this->mxchat_check_intent_and_invoke_callback($message, $user_id, $session_id);
914 +//error_log("Intent Result Type: " . gettype($intent_result));
864 915
865 - // Step 3: If intent is matched and handled, respond immediately
866 - if ($intent_matched && (!empty($this->fallbackResponse['text']) || !empty($this->fallbackResponse['html']))) {
867 - //error_log("Intent response triggered.");
916 +// Step 3: Handle the intent result appropriately
917 +if ($intent_result !== false) {
918 + // The intent was matched and handled
919 + //error_log("Intent was matched and handled.");
920 +
921 + if (is_array($intent_result) && (isset($intent_result['text']) || isset($intent_result['html']))) {
922 + // Intent returned a direct response array
923 + //error_log("Intent returned a direct response.");
868 924 $response_data = [
869 - 'text' => $this->fallbackResponse['text'],
870 - 'html' => $this->fallbackResponse['html'],
925 + 'text' => $intent_result['text'] ?? '',
926 + 'html' => $intent_result['html'] ?? '',
871 927 'session_id' => $session_id
872 928 ];
873 - $this->mxchat_save_chat_message($session_id, 'bot', $this->fallbackResponse['text'] . $this->fallbackResponse['html']);
929 +
874 930 wp_send_json($response_data);
875 931 wp_die();
932 + }
933 + else if ($intent_result === true && (!empty($this->fallbackResponse['text']) || !empty($this->fallbackResponse['html']))) {
934 + // Intent returned true and set fallbackResponse
935 + //error_log("Intent returned true with fallbackResponse set.");
936 + $response_data = [
937 + 'text' => $this->fallbackResponse['text'] ?? '',
938 + 'html' => $this->fallbackResponse['html'] ?? '',
939 + 'session_id' => $session_id
940 + ];
941 +
942 + wp_send_json($response_data);
943 + wp_die();
876 944 }
945 +
946 + // Intent was matched but no usable response was provided
947 + // This shouldn't happen with proper intent implementation
948 + //error_log("Warning: Intent matched but no response provided.");
949 +}
877 950
878 - // If no intent matched or product not found, proceed with AI response
879 - //error_log("No matching intent or fallback. Generating AI response.");
951 + // If we get here, no intent matched OR the intent didn't provide a usable response
952 + //error_log("No matching intent or usable response. Generating AI response.");
953 +
954 + // Step 4: Generate AI response
955 + $conversation_history = $this->mxchat_fetch_conversation_history_for_ai($session_id);
956 + $this->mxchat_increment_chat_count();
957 +
958 + // Generate embedding for the user's query
959 + $user_message_embedding = $this->mxchat_generate_embedding($message, $this->options['api_key']);
960 +
961 + // Check if the embedding generation returned an error
962 + if (is_array($user_message_embedding) && isset($user_message_embedding['error'])) {
963 + $error_message = $user_message_embedding['error'];
964 + $error_code = $user_message_embedding['error_code'] ?? 'embedding_error';
965 +
966 + //error_log("Embedding error for session $session_id: $error_message (Code: $error_code)");
967 +
968 + // Important: Structure the error data correctly for wp_send_json_error
969 + wp_send_json_error([
970 + 'error_message' => $error_message,
971 + 'error_code' => $error_code
972 + ]);
973 + wp_die();
974 + }
975 +
976 + // Check if the embedding is valid
977 + if (!is_array($user_message_embedding) || empty($user_message_embedding)) {
978 + //error_log("Failed to generate message embedding for session $session_id");
979 + wp_send_json_error([
980 + 'error_message' => esc_html__('Unable to process your message. The embedding service is not responding correctly.', 'mxchat'),
981 + 'error_code' => 'invalid_embedding'
982 + ]);
983 + wp_die();
984 + }
880 985
881 - // Step 4: Generate AI response
882 - $conversation_history = $this->mxchat_fetch_conversation_history_for_ai($session_id);
883 - $this->mxchat_increment_chat_count();
986 + // Build context with both knowledge base and PDF content if available
987 + $context_content = "User asked: '{$message}'\n\n";
884 988
885 - // Generate embedding for the user's query
886 - $user_message_embedding = $this->mxchat_generate_embedding($message, $this->options['api_key']);
887 - if (!is_array($user_message_embedding)) {
888 - //error_log("Failed to generate message embedding for session $session_id");
889 - wp_send_json_error(esc_html__('Error processing your message.', 'mxchat'));
890 - wp_die();
891 - }
892 989
893 - // Build context with both knowledge base and PDF content if available
894 - $context_content = "User asked: '{$message}'\n\n";
990 + // Get relevant content from knowledge base
991 + $relevant_content = $this->mxchat_find_relevant_content($user_message_embedding);
992 + if (!empty($relevant_content)) {
993 + $context_content .= "===== OFFICIAL KNOWLEDGE DATABASE CONTENT =====\n" . $relevant_content . "\n===== END OF OFFICIAL KNOWLEDGE DATABASE CONTENT =====\n\n";
994 + } else {
995 + $context_content .= "===== NO RELEVANT CONTENT FOUND IN KNOWLEDGE DATABASE =====\n";
996 + }
895 997
896 - // Get relevant content from knowledge base
897 - $relevant_content = $this->mxchat_find_relevant_content($user_message_embedding);
898 - if (!empty($relevant_content)) {
899 - $context_content .= "Relevant content from knowledge database:\n" . $relevant_content . "\n\n";
900 - }
901 -
902 -
903 998 // Check for and include PDF content
904 999 $pdf_url = get_transient('mxchat_pdf_url_' . $session_id);
905 1000 $pdf_embeddings = get_transient('mxchat_pdf_embeddings_' . $session_id);
906 1001 $pdf_filename = get_transient('mxchat_pdf_filename_' . $session_id);
@@ -928,19 +1023,36 @@
928 1023 }
929 1024 $context_content .= "\n";
930 1025 }
931 1026 }
932 - // Generate the response using the full context
933 - $response = $this->mxchat_generate_response(
934 - $context_content,
935 - $this->options['api_key'],
936 - $this->options['xai_api_key'],
937 - $this->options['claude_api_key'],
938 - $this->options['deepseek_api_key'],
939 - $conversation_history
940 - );
1027 +
1028 + $context_content = apply_filters('mxchat_prepare_context', $context_content, $session_id);
941 1029
942 - $this->mxchat_save_chat_message($session_id, 'bot', $response);
1030 + // Generate the response using the full context
1031 + $response = $this->mxchat_generate_response(
1032 + $context_content,
1033 + $this->options['api_key'],
1034 + $this->options['xai_api_key'],
1035 + $this->options['claude_api_key'],
1036 + $this->options['deepseek_api_key'],
1037 + $this->options['gemini_api_key'],
1038 + $conversation_history
1039 + );
1040 +
1041 + // Check if the response is an error array
1042 + if (is_array($response) && isset($response['error'])) {
1043 + //error_log("AI Response Error: " . $response['error'] . " (Code: " . ($response['error_code'] ?? 'unknown') . ")");
1044 +
1045 + // Send a user-friendly error message
1046 + wp_send_json_error([
1047 + 'error_message' => $response['error'],
1048 + 'error_code' => $response['error_code'] ?? 'api_error'
1049 + ]);
1050 + wp_die();
1051 + }
1052 +
1053 + // If we get here, the response is valid text
1054 + $this->mxchat_save_chat_message($session_id, 'bot', $response);
943 1055
944 1056 // Step 5: Save additional content if available
945 1057 if (!empty($this->productCardHtml)) {
946 1058 $this->mxchat_save_chat_message($session_id, 'bot', $this->productCardHtml);
@@ -960,55 +1072,69 @@
960 1072 wp_send_json($response_data);
961 1073 wp_die();
962 1074 }
963 1075
964 -
965 -// Helper function to clear PDF and Word document related transients
966 -private function clear_pdf_transients($session_id) {
967 - // PDF transients
968 - delete_transient('mxchat_pdf_url_' . $session_id);
969 - delete_transient('mxchat_pdf_embeddings_' . $session_id);
970 - delete_transient('mxchat_include_pdf_in_context_' . $session_id);
971 - delete_transient('mxchat_waiting_for_pdf_url_' . $session_id);
972 -
973 - // Word document transients
974 - delete_transient('mxchat_word_url_' . $session_id);
975 - delete_transient('mxchat_word_filename_' . $session_id);
976 - delete_transient('mxchat_word_embeddings_' . $session_id);
977 - delete_transient('mxchat_include_word_in_context_' . $session_id);
978 - delete_transient('mxchat_waiting_for_word_' . $session_id);
979 -}
980 -
981 -// New function to check intents and invoke the callback function
1076 +// Updated function to check intents and invoke the callback function
982 1077 private function mxchat_check_intent_and_invoke_callback($message, $user_id, $session_id) {
983 1078 global $wpdb;
984 1079 $chat_mode = get_option("mxchat_mode_{$session_id}", 'ai');
985 1080
986 - //error_log("[MxChat] Checking intents for message: " . $message);
1081 + //error_log('🔍 MXCHAT DEBUG: Intent Check Started ==================');
1082 + //error_log("🔍 MXCHAT DEBUG: Message: '$message'");
1083 + //error_log("🔍 MXCHAT DEBUG: Chat Mode: $chat_mode");
987 1084
988 1085 // Generate the user embedding
1086 + //error_log('🔄 MXCHAT DEBUG: Generating user embedding');
989 1087 $user_embedding = $this->mxchat_generate_embedding($message, $this->options['api_key']);
990 - if (!is_array($user_embedding)) {
991 - //error_log("[MxChat] Failed to generate user embedding");
992 - return false;
1088 +
1089 + // Check if embedding generation returned an error
1090 + if (is_array($user_embedding) && isset($user_embedding['error'])) {
1091 + $error_message = $user_embedding['error'];
1092 + $error_code = $user_embedding['error_code'] ?? 'embedding_error';
1093 +
1094 + //error_log("❌ MXCHAT DEBUG: Embedding error: $error_message (Code: $error_code)");
1095 +
1096 + // Send the error to the frontend
1097 + wp_send_json_error([
1098 + 'error_message' => $error_message,
1099 + 'error_code' => $error_code
1100 + ]);
1101 + wp_die();
993 1102 }
994 1103
1104 + // Check if embedding is valid (not an error and is an array)
1105 + if (!is_array($user_embedding) || empty($user_embedding)) {
1106 + //error_log('❌ MXCHAT DEBUG: Failed to generate user embedding');
1107 +
1108 + // Send a generic error to the frontend
1109 + wp_send_json_error([
1110 + 'error_message' => esc_html__('Unable to process your message. The embedding service is not responding correctly.', 'mxchat'),
1111 + 'error_code' => 'invalid_embedding'
1112 + ]);
1113 + wp_die();
1114 + }
1115 +
1116 + //error_log('✅ MXCHAT DEBUG: User embedding generated successfully');
1117 +
995 1118 // Fetch intents from the database
996 1119 $table_name = $wpdb->prefix . 'mxchat_intents';
997 1120 if ($chat_mode === 'agent') {
1121 + //error_log('🔍 MXCHAT DEBUG: Agent mode - fetching only chatbot switch intent');
998 1122 $query = $wpdb->prepare(
999 - "SELECT * FROM $table_name WHERE callback_function = %s",
1123 + "SELECT * FROM $table_name WHERE callback_function = %s AND (enabled = 1 OR enabled IS NULL)",
1000 1124 'mxchat_handle_switch_to_chatbot_intent'
1001 1125 );
1002 1126 $intents = $wpdb->get_results($query);
1003 1127 } else {
1004 - $intents = $wpdb->get_results("SELECT * FROM $table_name");
1128 + //error_log('🔍 MXCHAT DEBUG: AI mode - fetching all enabled intents');
1129 + // Only fetch enabled intents (either explicitly enabled with 1 or implicitly enabled with NULL for backward compatibility)
1130 + $intents = $wpdb->get_results("SELECT * FROM $table_name WHERE enabled = 1 OR enabled IS NULL");
1005 1131 }
1006 1132
1007 - //error_log("[MxChat] Found " . count($intents) . " intents to check");
1133 + //error_log('🔍 MXCHAT DEBUG: Found ' . count($intents) . ' enabled intents to check');
1008 1134
1009 1135 if (empty($intents)) {
1010 - //error_log("[MxChat] No intents found in database");
1136 + //error_log('❌ MXCHAT DEBUG: No enabled intents found in database');
1011 1137 return false;
1012 1138 }
1013 1139
1014 1140 $highest_similarity = -INF;
@@ -1013,11 +1139,19 @@
1013 1139
1014 1140 $highest_similarity = -INF;
1015 1141 $matched_intent = null;
1016 1142
1143 + //error_log('📊 MXCHAT DEBUG: Intent Similarity Scores ==================');
1017 1144 foreach ($intents as $intent) {
1018 - //error_log("[MxChat] Checking intent: " . $intent->intent_label . " (callback: " . $intent->callback_function . ")");
1145 + //error_log("🔄 MXCHAT DEBUG: Checking intent: '{$intent->intent_label}' (callback: {$intent->callback_function})");
1019 1146
1147 + // Additional check for enabled state in case database structure was modified
1148 + $is_enabled = isset($intent->enabled) ? (bool)$intent->enabled : true;
1149 + if (!$is_enabled) {
1150 + //error_log("⚠️ MXCHAT DEBUG: Skipping disabled intent: {$intent->intent_label}");
1151 + continue;
1152 + }
1153 +
1020 1154 $intent_embedding_serialized = $intent->embedding_vector;
1021 1155 $intent_embedding = $intent_embedding_serialized
1022 1156 ? unserialize($intent_embedding_serialized, ['allowed_classes' => false])
1023 1157 : null;
@@ -1022,9 +1156,9 @@
1022 1156 ? unserialize($intent_embedding_serialized, ['allowed_classes' => false])
1023 1157 : null;
1024 1158
1025 1159 if (!is_array($intent_embedding)) {
1026 - //error_log("[MxChat] Invalid embedding for intent: " . $intent->intent_label);
1160 + //error_log("❌ MXCHAT DEBUG: Invalid embedding for intent: {$intent->intent_label}");
1027 1161 continue;
1028 1162 }
1029 1163
1030 1164 $similarity = $this->mxchat_calculate_cosine_similarity($user_embedding, $intent_embedding);
@@ -1029,30 +1163,35 @@
1029 1163
1030 1164 $similarity = $this->mxchat_calculate_cosine_similarity($user_embedding, $intent_embedding);
1031 1165 $intent_threshold = isset($intent->similarity_threshold) ? $intent->similarity_threshold : 0.85;
1032 1166
1033 - //error_log("[MxChat] Similarity for {$intent->intent_label}: {$similarity} (threshold: {$intent_threshold})");
1167 + //error_log("📊 MXCHAT DEBUG: Intent '{$intent->intent_label}' similarity: {$similarity}, threshold: {$intent_threshold}");
1034 1168
1035 1169 if ($similarity >= $intent_threshold && $similarity > $highest_similarity) {
1036 1170 $highest_similarity = $similarity;
1037 1171 $matched_intent = $intent;
1038 - //error_log("[MxChat] New best match: {$intent->intent_label} with similarity {$similarity}");
1172 + //error_log("✅ MXCHAT DEBUG: New best match: '{$intent->intent_label}' with similarity {$similarity}");
1039 1173 }
1040 1174 }
1175 + //error_log('📊 MXCHAT DEBUG: End Intent Scores ==================');
1041 1176
1042 1177 if ($matched_intent) {
1043 - //error_log("[MxChat] Invoking callback: " . $matched_intent->callback_function);
1178 + //error_log("🎯 MXCHAT DEBUG: Final Intent Match: '{$matched_intent->intent_label}'");
1179 + //error_log("🔄 MXCHAT DEBUG: Invoking callback: {$matched_intent->callback_function}");
1044 1180
1045 1181 // If the callback is a method on this instance (core callback), call it directly
1046 1182 if (method_exists($this, $matched_intent->callback_function)) {
1183 + //error_log('🔍 MXCHAT DEBUG: Using direct method call for core callback');
1047 1184 $callback_result = call_user_func(
1048 - [$this, $matched_intent->callback_function],
1049 - $message,
1050 - $user_id,
1051 - $session_id,
1052 - $matched_intent
1053 - );
1185 + [$this, $matched_intent->callback_function],
1186 + $message,
1187 + $user_id,
1188 + $session_id,
1189 + $matched_intent,
1190 + $user_context
1191 + );
1054 1192 } else {
1193 + //error_log('🔍 MXCHAT DEBUG: Using apply_filters for add-on callback');
1055 1194 // Otherwise, use apply_filters for add-on callbacks
1056 1195 $callback_result = apply_filters(
1057 1196 $matched_intent->callback_function,
1058 1197 false, // default return value
@@ -1062,159 +1201,41 @@
1062 1201 $matched_intent
1063 1202 );
1064 1203 }
1065 1204
1066 - //error_log("[MxChat] Callback result: " . print_r($callback_result, true));
1205 + //error_log('🔍 MXCHAT DEBUG: Callback result type: ' . gettype($callback_result));
1067 1206 if ($callback_result !== false) {
1207 + //error_log('✅ MXCHAT DEBUG: Intent handled successfully');
1068 1208 $this->fallbackResponse = $callback_result;
1069 1209 return true;
1070 1210 }
1211 + //error_log('❌ MXCHAT DEBUG: Callback returned false');
1212 + } else {
1213 + //error_log('❌ MXCHAT DEBUG: No matching intent found');
1071 1214 }
1072 1215
1073 - //error_log("[MxChat] No matching intent found");
1216 + //error_log('🔍 MXCHAT DEBUG: Intent Check Completed ==================');
1074 1217 return false;
1075 1218 }
1076 1219
1220 +// Helper function to clear PDF and Word document related transients
1221 +private function clear_pdf_transients($session_id) {
1222 + // PDF transients
1223 + delete_transient('mxchat_pdf_url_' . $session_id);
1224 + delete_transient('mxchat_pdf_embeddings_' . $session_id);
1225 + delete_transient('mxchat_include_pdf_in_context_' . $session_id);
1226 + delete_transient('mxchat_waiting_for_pdf_url_' . $session_id);
1077 1227
1078 -//verified good
1079 -public function mxchat_handle_order_history($message, $user_id, $session_id) {
1080 - if (!class_exists('WooCommerce')) {
1081 - $this->fallbackResponse['text'] = esc_html__("I can't access order information right now. The order system seems to be unavailable.", 'mxchat');
1082 - return true;
1083 - }
1084 -
1085 - $orderDetails = MxChat_WooCommerce::mxchat_fetch_user_orders_details('all');
1086 -
1087 - if (empty($orderDetails)) {
1088 - $this->fallbackResponse['text'] = esc_html__("I don't see any orders associated with your account. Are you logged in?", 'mxchat');
1089 - return true;
1090 - }
1091 -
1092 - // Generate AI prompt with context
1093 - $prompt = __("User asked about their orders:", 'mxchat') . " '{$message}'\n\n";
1094 - $prompt .= __("Order information:", 'mxchat') . "\n";
1095 - foreach ($orderDetails as $order) {
1096 - $items_list = array_map(function($item) {
1097 - return "{$item['name']} ({$item['quantity']})";
1098 - }, $order['items']);
1099 -
1100 - $prompt .= __("Order #", 'mxchat') . "{$order['order_id']}: {$order['formatted_total']} " . __("on", 'mxchat') . " {$order['date']}\n";
1101 - $prompt .= __("Items:", 'mxchat') . " " . implode(', ', $items_list) . "\n";
1102 - }
1103 -
1104 - $prompt .= __("\nProvide a natural, conversational response focusing on the specific information the user asked about. ", 'mxchat');
1105 - $prompt .= __("If they ask about a specific order or detail, provide just that information. ", 'mxchat');
1106 - $prompt .= __("If they ask about license keys or sensitive information, inform them to check their email or contact support.", 'mxchat');
1107 -
1108 - // Get AI response
1109 - $ai_response = $this->mxchat_call_ai_api($prompt);
1110 - $this->fallbackResponse['text'] = $ai_response['text'];
1111 -
1112 - return true;
1228 + // Word document transients
1229 + delete_transient('mxchat_word_url_' . $session_id);
1230 + delete_transient('mxchat_word_filename_' . $session_id);
1231 + delete_transient('mxchat_word_embeddings_' . $session_id);
1232 + delete_transient('mxchat_include_word_in_context_' . $session_id);
1233 + delete_transient('mxchat_waiting_for_word_' . $session_id);
1113 1234 }
1114 1235
1115 1236
1116 -public function mxchat_handle_product_inquiry($message, $user_id, $session_id) {
1117 - //error_log("PRODUCT INQUIRY START - Message: $message, User ID: $user_id");
1118 1237
1119 - // Sanitize user ID
1120 - $sanitized_user_id = sanitize_key($user_id);
1121 - //error_log("Sanitized User ID: $sanitized_user_id");
1122 -
1123 - // Find product
1124 - $product_id = $this->find_product_in_message($message);
1125 - //error_log("Initial product ID from message: " . ($product_id ?? 'null'));
1126 -
1127 - // Check transient if no product found
1128 - if (!$product_id) {
1129 - $product_id = get_transient('mxchat_last_discussed_product_' . $sanitized_user_id);
1130 - //error_log("Product ID from transient: " . ($product_id ?? 'null'));
1131 - }
1132 -
1133 - // Handle product inquiries
1134 - if ($product_id && class_exists('WooCommerce')) {
1135 - $product = wc_get_product($product_id);
1136 - if ($product) {
1137 - //error_log("Found product: " . $product->get_name() . " (ID: $product_id)");
1138 -
1139 - // Prepare product details
1140 - $product_name = esc_html($product->get_name());
1141 - $product_price = $product->get_price_html();
1142 - $product_image_url = esc_url(wp_get_attachment_url($product->get_image_id()));
1143 - $product_url = esc_url(get_permalink($product_id));
1144 - $product_id_attr = esc_attr($product_id);
1145 -
1146 - // Get product description
1147 - $product_description = $product->get_description() ?: $product->get_short_description();
1148 -
1149 - // Log embeddings process
1150 - //error_log("Generating embedding for message: $message");
1151 - $user_message_embedding = $this->mxchat_generate_embedding($message, $this->options['api_key']);
1152 - //error_log("Finding relevant content for product inquiry");
1153 - $relevant_content = $this->mxchat_find_relevant_content($user_message_embedding);
1154 -
1155 - // Build AI prompt
1156 - $ai_prompt = esc_html__("You are a knowledgeable product assistant. ", 'mxchat');
1157 - $ai_prompt .= esc_html__("Respond to this user query: '{$message}'\n\n", 'mxchat');
1158 -
1159 - if (!empty($relevant_content)) {
1160 - $ai_prompt .= esc_html__("Relevant information from our knowledge base:\n{$relevant_content}\n\n", 'mxchat');
1161 - }
1162 -
1163 - $ai_prompt .= esc_html__("Product details:\n", 'mxchat');
1164 - $ai_prompt .= esc_html__("Name: {$product_name}\n", 'mxchat');
1165 - $ai_prompt .= esc_html__("Price: ", 'mxchat') . strip_tags($product_price) . "\n";
1166 - if ($product_description) {
1167 - $ai_prompt .= esc_html__("Description: {$product_description}\n", 'mxchat');
1168 - }
1169 -
1170 - $ai_prompt .= esc_html__("\nInstructions:\n", 'mxchat');
1171 - $ai_prompt .= esc_html__("1. Address the user's specific question or concern about the product\n", 'mxchat');
1172 - $ai_prompt .= esc_html__("2. Incorporate relevant information from our knowledge base if provided\n", 'mxchat');
1173 - $ai_prompt .= esc_html__("3. Highlight key product features that relate to their query\n", 'mxchat');
1174 - $ai_prompt .= esc_html__("4. Include a natural suggestion to check out the product\n", 'mxchat');
1175 - $ai_prompt .= esc_html__("5. Keep the response conversational and helpful\n", 'mxchat');
1176 -
1177 - //error_log("Sending AI prompt: " . $ai_prompt);
1178 -
1179 - // Build and log AI response
1180 - $ai_response = $this->mxchat_call_ai_api($ai_prompt);
1181 - //error_log("AI Response received for product inquiry: " . print_r($ai_response, true));
1182 -
1183 - // Generate and log product card
1184 - $product_card_html = <<<HTML
1185 -<div class="mxchat-product-card">
1186 - <a href="{$product_url}" target="_blank">
1187 - <img src="{$product_image_url}" alt="{$product_name}" class="mxchat-product-image" />
1188 - <h3 class="mxchat-product-name">{$product_name}</h3>
1189 - </a>
1190 - <div class="mxchat-product-price">{$product_price}</div>
1191 - <button class="mxchat-add-to-cart-button" data-product-id="{$product_id_attr}">Add to Cart</button>
1192 -</div>
1193 -HTML;
1194 -
1195 - // Save response and set transient
1196 - $this->productCardHtml = $product_card_html;
1197 - $this->fallbackResponse = [
1198 - 'text' => $ai_response['text'],
1199 - 'html' => $this->productCardHtml,
1200 - ];
1201 -
1202 - //error_log("Setting transient for product: $product_id with key: mxchat_last_discussed_product_$sanitized_user_id");
1203 - set_transient('mxchat_last_discussed_product_' . $sanitized_user_id, $product_id, HOUR_IN_SECONDS);
1204 -
1205 - // Verify transient was set
1206 - $verify_transient = get_transient('mxchat_last_discussed_product_' . $sanitized_user_id);
1207 - //error_log("Verification - Retrieved transient value: " . ($verify_transient ?? 'null'));
1208 -
1209 - return true;
1210 - }
1211 - }
1212 -
1213 - //error_log("PRODUCT INQUIRY END - No product found or WooCommerce not available");
1214 - return false;
1215 -}
1216 -
1217 1238 //verified good
1218 1239 public function mxchat_handle_email_capture($message, $user_id, $session_id) {
1219 1240 // Log the message safely
1220 1241 //error_log("Triggered email capture intent for message: " . sanitize_text_field($message));
@@ -1229,47 +1250,63 @@
1229 1250 wp_send_json(['message' => $response]);
1230 1251 wp_die();
1231 1252 }
1232 1253
1233 -//very good
1234 1254 public function mxchat_generate_image($message, $user_id, $session_id) {
1255 + //error_log("Starting image generation for message: " . $message);
1256 +
1235 1257 // Prepare a prompt for DALL-E
1236 1258 $prompt = esc_html__('Create an image of ', 'mxchat') . sanitize_text_field($message);
1237 -
1259 +
1238 1260 // Use the existing OpenAI API key
1239 1261 $openai_api_key = sanitize_text_field($this->options['api_key']);
1240 -
1262 +
1241 1263 // Call DALL-E to generate an image
1242 1264 $image_response = $this->mxchat_generate_dalle_image($prompt, $openai_api_key);
1243 -
1265 +
1244 1266 // Check if the response contains an image URL
1245 1267 if (isset($image_response['imageUrl'])) {
1246 1268 $image_url = esc_url_raw($image_response['imageUrl']);
1247 -
1269 +
1248 1270 // Construct the HTML with a CSS class instead of inline styles
1249 1271 $response_html = '<img src="' . esc_url($image_url) . '" alt="' . esc_attr__('Generated Image', 'mxchat') . '" class="mxchat-generated-image" />';
1272 + $response_text = esc_html__('Here is the image I generated:', 'mxchat');
1273 +
1274 + // Save the bot message with both text and HTML
1275 + $this->mxchat_save_chat_message($session_id, 'bot', $response_text);
1276 + $this->mxchat_save_chat_message($session_id, 'bot', $response_html);
1277 +
1278 + // Set the fallback response for the chat handler
1279 + $this->fallbackResponse = [
1280 + 'text' => $response_text,
1281 + 'html' => $response_html,
1282 + 'images' => [$image_url]
1283 + ];
1284 +
1285 + // For debugging/verification - Use json_encode to verify what's being set
1286 + //error_log("Image generation successful - fallbackResponse set: " . json_encode($this->fallbackResponse));
1250 1287
1251 - $response_text = esc_html__('Here is the image I generated:', 'mxchat');
1288 + // Return the response directly instead of relying on the property
1289 + return $this->fallbackResponse;
1252 1290 } else {
1253 1291 $response_text = esc_html__("I'm sorry, but I couldn't generate an image based on your request.", 'mxchat');
1254 - $response_html = '';
1292 +
1293 + // Save the error message
1294 + $this->mxchat_save_chat_message($session_id, 'bot', $response_text);
1295 +
1296 + // Set the fallback response for the chat handler
1297 + $this->fallbackResponse = [
1298 + 'text' => $response_text,
1299 + 'html' => '',
1300 + 'images' => []
1301 + ];
1302 +
1255 1303 //error_log("DALL-E image generation error: " . esc_html($image_response['error'] ?? 'Unknown error.'));
1304 + //error_log("Error fallbackResponse set: " . json_encode($this->fallbackResponse));
1305 +
1306 + // Return the response directly instead of relying on the property
1307 + return $this->fallbackResponse;
1256 1308 }
1257 -
1258 - // Save both text and HTML responses
1259 - $this->mxchat_save_chat_message($session_id, 'bot', $response_text . "\n" . $response_html);
1260 -
1261 - // Prepare the response data
1262 - $response_data = [
1263 - 'message' => $response_text,
1264 - 'html' => $response_html,
1265 - 'image_url' => $image_url ?? '',
1266 - ];
1267 -
1268 - // Send the JSON response
1269 - header('Content-Type: application/json; charset=' . get_option('blog_charset'));
1270 - echo json_encode($response_data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
1271 - wp_die();
1272 1309 }
1273 1310 private function mxchat_generate_dalle_image($prompt, $api_key, $model = 'dall-e-3', $timeout = 60) {
1274 1311 $api_url = 'https://api.openai.com/v1/images/generations';
1275 1312 $body = json_encode([
@@ -1308,44 +1345,43 @@
1308 1345
1309 1346 /**
1310 1347 * Handle web search requests.
1311 1348 *
1312 - * Sends the refined search query to the Brave Search API and displays neatly formatted,
1313 - * styled search results. Results are cached for performance.
1349 + * Sends the refined search query to the Brave Search API and uses the
1350 + * results to generate a conversational response with the AI model.
1314 1351 *
1315 1352 * @since 1.0.0
1316 1353 * @param string $message The user's search query.
1317 1354 * @param string $user_id The user identifier.
1318 1355 * @param string $session_id The current session ID.
1319 - * @return void
1356 + * @return array Response array containing text with embedded HTML links
1320 1357 */
1321 -public function mxchat_handle_search_request( $message, $user_id, $session_id ) {
1358 +public function mxchat_handle_search_request($message, $user_id, $session_id) {
1322 1359 // Step 1: Interpret and refine the search query
1323 - $refined_search_query = $this->mxchat_interpret_search_query( $message );
1324 -
1325 - if ( empty( $refined_search_query ) ) {
1326 - $this->fallbackResponse = array(
1327 - 'text' => esc_html__( 'I apologize, but could you please rephrase your search request?', 'mxchat' ),
1360 + $refined_search_query = $this->mxchat_interpret_search_query($message);
1361 + if (empty($refined_search_query)) {
1362 + return array(
1363 + 'text' => esc_html__('I apologize, but could you please rephrase your search request?', 'mxchat'),
1364 + 'html' => ''
1328 1365 );
1329 - return;
1330 1366 }
1331 -
1367 +
1332 1368 // Retrieve and validate API settings
1333 - $options = get_option( 'mxchat_options' );
1334 - $api_key = isset( $options['brave_api_key'] ) ? sanitize_text_field( $options['brave_api_key'] ) : '';
1335 - $results_count = isset( $options['brave_results_count'] ) ? absint( $options['brave_results_count'] ) : 5;
1336 -
1337 - if ( empty( $api_key ) ) {
1338 - $this->fallbackResponse = array(
1339 - 'text' => esc_html__( 'Search functionality is temporarily unavailable. Please try again later.', 'mxchat' ),
1369 + $options = get_option('mxchat_options');
1370 + $api_key = isset($options['brave_api_key']) ? sanitize_text_field($options['brave_api_key']) : '';
1371 + $results_count = isset($options['brave_results_count']) ? absint($options['brave_results_count']) : 5;
1372 +
1373 + if (empty($api_key)) {
1374 + return array(
1375 + 'text' => esc_html__('Search functionality is temporarily unavailable. Please try again later.', 'mxchat'),
1376 + 'html' => ''
1340 1377 );
1341 - return;
1342 1378 }
1343 -
1379 +
1344 1380 // Build the API request URL
1345 1381 $api_url = add_query_arg(
1346 1382 array(
1347 - 'q' => rawurlencode( $refined_search_query ),
1383 + 'q' => rawurlencode($refined_search_query),
1348 1384 'count' => $results_count,
1349 1385 'text_decorations' => 'true',
1350 1386 'rich_data' => 'true',
1351 1387 ),
@@ -1350,14 +1386,14 @@
1350 1386 'rich_data' => 'true',
1351 1387 ),
1352 1388 'https://api.search.brave.com/res/v1/web/search'
1353 1389 );
1354 -
1390 +
1355 1391 // Attempt to retrieve cached results first
1356 - $transient_key = 'mxchat_search_' . md5( $refined_search_query );
1357 - $results = get_transient( $transient_key );
1358 -
1359 - if ( false === $results ) {
1392 + $transient_key = 'mxchat_search_' . md5($refined_search_query);
1393 + $results = get_transient($transient_key);
1394 +
1395 + if (false === $results) {
1360 1396 // Fetch new results from the Brave Search API
1361 1397 $response = wp_remote_get(
1362 1398 $api_url,
1363 1399 array(
@@ -1368,51 +1404,78 @@
1368 1404 ),
1369 1405 'timeout' => 10,
1370 1406 )
1371 1407 );
1372 -
1373 - if ( is_wp_error( $response ) ) {
1374 - $this->fallbackResponse = array(
1375 - 'text' => esc_html__( 'I encountered an error while searching. Please try again.', 'mxchat' ),
1408 +
1409 + if (is_wp_error($response)) {
1410 + return array(
1411 + 'text' => esc_html__('I encountered an error while searching. Please try again.', 'mxchat'),
1412 + 'html' => ''
1376 1413 );
1377 - return;
1378 1414 }
1379 -
1380 - $results = json_decode( wp_remote_retrieve_body( $response ), true );
1381 -
1382 - if ( json_last_error() !== JSON_ERROR_NONE ) {
1383 - $this->fallbackResponse = array(
1384 - 'text' => esc_html__( 'I received an invalid response from the search service.', 'mxchat' ),
1415 +
1416 + $results = json_decode(wp_remote_retrieve_body($response), true);
1417 +
1418 + if (json_last_error() !== JSON_ERROR_NONE) {
1419 + return array(
1420 + 'text' => esc_html__('I received an invalid response from the search service.', 'mxchat'),
1421 + 'html' => ''
1385 1422 );
1386 - return;
1387 1423 }
1388 -
1424 +
1389 1425 // Cache results for one hour
1390 - set_transient( $transient_key, $results, HOUR_IN_SECONDS );
1426 + set_transient($transient_key, $results, HOUR_IN_SECONDS);
1391 1427 }
1392 -
1393 - // Process and display results
1394 - if ( ! empty( $results['web']['results'] ) && is_array( $results['web']['results'] ) ) {
1395 - $html = $this->generate_search_results_html( $results['web']['results'], $refined_search_query );
1396 -
1397 - // Only return HTML (no large text summary)
1398 - $this->fallbackResponse = array(
1399 - 'html' => $html,
1428 +
1429 + // Process results
1430 + if (!empty($results['web']['results']) && is_array($results['web']['results'])) {
1431 + // Create a more straightforward summary with HTML links
1432 + $search_results_text = '';
1433 +
1434 + // Add a simple intro
1435 + $search_results_text .= sprintf(
1436 + esc_html__("Here's what I found about '%s':", 'mxchat'),
1437 + esc_html($refined_search_query)
1400 1438 );
1401 -
1439 +
1440 + // Add the top results with HTML links
1441 + foreach (array_slice($results['web']['results'], 0, 5) as $result) {
1442 + $title = isset($result['title']) ? wp_strip_all_tags($result['title']) : '';
1443 + $url = isset($result['url']) ? esc_url($result['url']) : '';
1444 + $description = isset($result['description']) ? wp_strip_all_tags($result['description']) : '';
1445 +
1446 + // Add a line break after the intro
1447 + $search_results_text .= '<br><br>';
1448 +
1449 + // Add title as a link
1450 + $search_results_text .= sprintf(
1451 + '<a href="%s" target="_blank" rel="noopener noreferrer">%s</a><br>',
1452 + $url,
1453 + $title
1454 + );
1455 +
1456 + // Add a condensed description
1457 + $search_results_text .= sprintf("%s", $description);
1458 + }
1459 +
1402 1460 // Save to chat history
1403 - $this->mxchat_save_chat_message( $session_id, 'bot', $html );
1461 + $this->mxchat_save_chat_message($session_id, 'bot', $search_results_text);
1462 +
1463 + // Return the formatted text with embedded HTML links
1464 + return array(
1465 + 'text' => $search_results_text,
1466 + 'html' => ''
1467 + );
1404 1468 } else {
1405 - $this->fallbackResponse = array(
1469 + return array(
1406 1470 'text' => sprintf(
1407 - esc_html__( 'I couldn\'t find any relevant results for "%s". Would you like to try different search terms?', 'mxchat' ),
1408 - esc_html( $refined_search_query )
1471 + esc_html__('I searched for "%s" but couldn\'t find any relevant results. Would you like to try different search terms?', 'mxchat'),
1472 + esc_html($refined_search_query)
1409 1473 ),
1474 + 'html' => ''
1410 1475 );
1411 1476 }
1412 1477 }
1413 -
1414 -
1415 1478 /**
1416 1479 * Format search results into a natural text summary.
1417 1480 *
1418 1481 * @since 1.0.0
@@ -1509,21 +1572,26 @@
1509 1572 }
1510 1573
1511 1574
1512 1575 //very good
1576 +/**
1577 + * Handle image search requests from the chatbot
1578 + *
1579 + * @param string $message The user's search query
1580 + * @param int $user_id The user's ID
1581 + * @param string $session_id The chat session ID
1582 + * @return array Response array with text and HTML content
1583 + */
1513 1584 public function mxchat_handle_image_search_request($message, $user_id, $session_id) {
1514 -
1515 - // Step 1: Interpret the search query for better results
1585 + // Step 1: Interpret the search query using the user's selected AI model
1516 1586 $refined_search_query = $this->mxchat_interpret_search_query($message);
1517 1587
1518 -
1519 1588 // If no query was interpreted, return a fallback message
1520 1589 if (empty($refined_search_query)) {
1521 - $this->fallbackResponse = [
1590 + return array(
1522 1591 'text' => __("I'm sorry, I couldn't interpret your search query. Please specify what you'd like to see images of.", 'mxchat'),
1523 1592 'html' => "",
1524 - ];
1525 - return;
1593 + );
1526 1594 }
1527 1595
1528 1596 // Brave API URL
1529 1597 $api_url = 'https://api.search.brave.com/res/v1/images/search';
@@ -1532,19 +1600,12 @@
1532 1600 $options = get_option('mxchat_options');
1533 1601 $api_key = isset($options['brave_api_key']) ? sanitize_text_field($options['brave_api_key']) : '';
1534 1602
1535 1603 if (empty($api_key)) {
1536 -/*
1537 - if (defined('WP_DEBUG') && WP_DEBUG) {
1538 - error_log("Brave API key is missing.");
1539 - }
1540 -*/
1541 -
1542 - $this->fallbackResponse = [
1604 + return array(
1543 1605 'text' => __("API key is not configured. Please set it in the Brave Search Settings.", 'mxchat'),
1544 1606 'html' => "",
1545 - ];
1546 - return;
1607 + );
1547 1608 }
1548 1609
1549 1610 $image_count = isset($options['brave_image_count']) ? intval($options['brave_image_count']) : 4;
1550 1611 $safe_search = isset($options['brave_safe_search']) ? sanitize_text_field($options['brave_safe_search']) : 'strict';
@@ -1555,16 +1616,8 @@
1555 1616 'count' => $image_count,
1556 1617 'safesearch' => $safe_search,
1557 1618 ], $api_url);
1558 1619
1559 -/*
1560 - // Log the final API URL for the search
1561 - if (defined('WP_DEBUG') && WP_DEBUG) {
1562 - error_log("Final API URL for Brave Image Search: " . esc_url_raw($api_url));
1563 - }
1564 -*/
1565 -
1566 -
1567 1620 // Implement caching
1568 1621 $transient_key = 'mxchat_image_search_' . md5($refined_search_query);
1569 1622 $body = get_transient($transient_key);
1570 1623
@@ -1580,19 +1633,12 @@
1580 1633
1581 1634 $response = wp_remote_get($api_url, $args);
1582 1635
1583 1636 if (is_wp_error($response)) {
1584 -/*
1585 - if (defined('WP_DEBUG') && WP_DEBUG) {
1586 - error_log("Brave Image API request failed: " . $response->get_error_message());
1587 - }
1588 -*/
1589 -
1590 - $this->fallbackResponse = [
1637 + return array(
1591 1638 'text' => __("I'm sorry, I couldn't retrieve any images based on your request.", 'mxchat'),
1592 1639 'html' => "",
1593 - ];
1594 - return;
1640 + );
1595 1641 }
1596 1642
1597 1643 $body = json_decode(wp_remote_retrieve_body($response), true);
1598 1644 set_transient($transient_key, $body, HOUR_IN_SECONDS);
@@ -1600,10 +1646,16 @@
1600 1646
1601 1647 // Process the API response
1602 1648 if (isset($body['results']) && is_array($body['results']) && count($body['results']) > 0) {
1603 1649 $html_output = '<div class="mxchat-image-gallery">';
1604 -
1605 - foreach ($body['results'] as $image) {
1650 +
1651 + // Get the configured image count (1-6)
1652 + $display_count = isset($options['brave_image_count']) ? intval($options['brave_image_count']) : 4;
1653 + $display_count = min($display_count, count($body['results'])); // Make sure we don't exceed available images
1654 +
1655 + // Use only the requested number of images
1656 + for ($i = 0; $i < $display_count; $i++) {
1657 + $image = $body['results'][$i];
1606 1658 $image_url = isset($image['url']) ? esc_url($image['url']) : '';
1607 1659 $thumbnail_url = isset($image['thumbnail']['src']) ? esc_url($image['thumbnail']['src']) : '';
1608 1660 $title = isset($image['title']) ? esc_html($image['title']) : esc_html__('Image', 'mxchat');
1609 1661
@@ -1617,47 +1669,95 @@
1617 1669 }
1618 1670
1619 1671 $html_output .= '</div>';
1620 1672
1621 - $this->fallbackResponse = [
1622 - 'text' => "",
1623 - 'html' => $html_output,
1624 - ];
1625 -
1626 - // Save response in chat history
1673 + // Create response text
1674 + $response_text = sprintf(__("Here are some images of %s:", 'mxchat'), $refined_search_query);
1675 +
1676 + // Save both response text and HTML to chat history
1677 + $this->mxchat_save_chat_message($session_id, 'bot', $response_text);
1627 1678 $this->mxchat_save_chat_message($session_id, 'bot', $html_output);
1628 1679
1680 + // Return the combined response
1681 + return array(
1682 + 'text' => $response_text,
1683 + 'html' => $html_output,
1684 + );
1629 1685 } else {
1630 -/*
1631 - if (defined('WP_DEBUG') && WP_DEBUG) {
1632 - error_log("Brave Image API response did not contain expected data structure or was empty: " . print_r($body, true));
1633 - }
1634 -*/
1635 -
1636 - $this->fallbackResponse = [
1637 - 'text' => __("I'm sorry, I couldn't retrieve any images based on your request.", 'mxchat'),
1686 + $response_text = __("I'm sorry, I couldn't retrieve any images based on your request.", 'mxchat');
1687 +
1688 + // Save the error message to chat history
1689 + $this->mxchat_save_chat_message($session_id, 'bot', $response_text);
1690 +
1691 + return array(
1692 + 'text' => $response_text,
1638 1693 'html' => "",
1639 - ];
1694 + );
1640 1695 }
1641 1696 }
1697 +
1698 +/**
1699 + * Interpret the search query using the user's selected AI model
1700 + *
1701 + * @param string $user_query The original query from the user
1702 + * @return string The refined search query
1703 + */
1642 1704 public function mxchat_interpret_search_query($user_query) {
1643 1705 $system_prompt = esc_html__("Interpret the user's request to provide only the essential keywords or phrases for image searching. Remove conversational language, politeness, or extra context. Return a concise search query that doesn't lose any of the original meaning.", 'mxchat');
1644 -
1645 - // Retrieve OpenAI API key using 'api_key' as the option key
1646 - $api_key = isset($this->options['api_key']) ? sanitize_text_field($this->options['api_key']) : sanitize_text_field(get_option('mxchat_options')['api_key']);
1647 -
1648 - /*
1649 - // Log the API key check, without exposing the key
1650 - if (defined('WP_DEBUG') && WP_DEBUG) {
1651 - error_log("Retrieved OpenAI API Key: " . ($api_key ? "Present" : "Missing"));
1706 +
1707 + // Get options and determine the selected model
1708 + $options = $this->options ?? get_option('mxchat_options');
1709 + $selected_model = isset($options['model']) ? $options['model'] : 'gpt-4o';
1710 +
1711 + // Extract model prefix to determine the provider
1712 + $model_parts = explode('-', $selected_model);
1713 + $provider = strtolower($model_parts[0]);
1714 +
1715 + // Determine which API key to use based on the provider
1716 + switch ($provider) {
1717 + case 'gemini':
1718 + $api_key = isset($options['gemini_api_key']) ? sanitize_text_field($options['gemini_api_key']) : '';
1719 + if (empty($api_key)) {
1720 + return sanitize_text_field($user_query); // Default to original query if API key missing
1721 + }
1722 + return $this->interpret_query_with_gemini($user_query, $system_prompt, $api_key, $selected_model);
1723 +
1724 + case 'claude':
1725 + $api_key = isset($options['claude_api_key']) ? sanitize_text_field($options['claude_api_key']) : '';
1726 + if (empty($api_key)) {
1727 + return sanitize_text_field($user_query);
1728 + }
1729 + return $this->interpret_query_with_claude($user_query, $system_prompt, $api_key, $selected_model);
1730 +
1731 + case 'grok':
1732 + $api_key = isset($options['xai_api_key']) ? sanitize_text_field($options['xai_api_key']) : '';
1733 + if (empty($api_key)) {
1734 + return sanitize_text_field($user_query);
1735 + }
1736 + return $this->interpret_query_with_xai($user_query, $system_prompt, $api_key, $selected_model);
1737 +
1738 + case 'deepseek':
1739 + $api_key = isset($options['deepseek_api_key']) ? sanitize_text_field($options['deepseek_api_key']) : '';
1740 + if (empty($api_key)) {
1741 + return sanitize_text_field($user_query);
1742 + }
1743 + return $this->interpret_query_with_deepseek($user_query, $system_prompt, $api_key, $selected_model);
1744 +
1745 + case 'gpt':
1746 + default:
1747 + // Default to OpenAI for custom models or unrecognized prefixes
1748 + $api_key = isset($options['api_key']) ? sanitize_text_field($options['api_key']) : '';
1749 + if (empty($api_key)) {
1750 + return sanitize_text_field($user_query);
1751 + }
1752 + return $this->interpret_query_with_openai($user_query, $system_prompt, $api_key, $selected_model);
1652 1753 }
1653 - */
1754 +}
1654 1755
1655 - if (empty($api_key)) {
1656 - //error_log("OpenAI API key is missing.");
1657 - return sanitize_text_field($user_query); // Default to the original query if API key is missing
1658 - }
1659 -
1756 +/**
1757 + * Interpret query using OpenAI models
1758 + */
1759 +private function interpret_query_with_openai($user_query, $system_prompt, $api_key, $model = 'gpt-4o') {
1660 1760 $url = 'https://api.openai.com/v1/chat/completions';
1661 1761 $args = [
1662 1762 'headers' => [
1663 1763 'Authorization' => 'Bearer ' . $api_key,
@@ -1663,9 +1763,9 @@
1663 1763 'Authorization' => 'Bearer ' . $api_key,
1664 1764 'Content-Type' => 'application/json',
1665 1765 ],
1666 1766 'body' => wp_json_encode([
1667 - 'model' => 'gpt-3.5-turbo',
1767 + 'model' => $model,
1668 1768 'messages' => [
1669 1769 ['role' => 'system', 'content' => $system_prompt],
1670 1770 ['role' => 'user', 'content' => sanitize_text_field($user_query)],
1671 1771 ],
@@ -1672,138 +1772,210 @@
1672 1772 'temperature' => 0.2,
1673 1773 'max_tokens' => 20,
1674 1774 ]),
1675 1775 'method' => 'POST',
1776 + 'timeout' => 15,
1676 1777 ];
1677 1778
1678 1779 $response = wp_remote_post($url, $args);
1679 -
1680 1780 if (is_wp_error($response)) {
1681 - //error_log("OpenAI request failed: " . $response->get_error_message());
1682 - return sanitize_text_field($user_query); // Fallback to the original query if there's an error
1781 + return sanitize_text_field($user_query);
1683 1782 }
1684 1783
1685 1784 $body = json_decode(wp_remote_retrieve_body($response), true);
1785 + return isset($body['choices'][0]['message']['content'])
1786 + ? sanitize_text_field(trim($body['choices'][0]['message']['content']))
1787 + : sanitize_text_field($user_query);
1788 +}
1686 1789
1687 - // Check for a valid response and sanitize output
1688 - if (isset($body['choices'][0]['message']['content'])) {
1689 - $interpreted_query = sanitize_text_field(trim($body['choices'][0]['message']['content']));
1790 +/**
1791 + * Interpret query using Claude models
1792 + */
1793 +private function interpret_query_with_claude($user_query, $system_prompt, $api_key, $model) {
1794 + $url = 'https://api.anthropic.com/v1/messages';
1795 +
1796 + $args = [
1797 + 'headers' => [
1798 + 'Content-Type' => 'application/json',
1799 + 'x-api-key' => $api_key,
1800 + 'anthropic-version' => '2023-06-01',
1801 + ],
1802 + 'body' => wp_json_encode([
1803 + 'model' => $model,
1804 + 'system' => $system_prompt,
1805 + 'messages' => [
1806 + ['role' => 'user', 'content' => sanitize_text_field($user_query)]
1807 + ],
1808 + 'max_tokens' => 20,
1809 + 'temperature' => 0.2,
1810 + ]),
1811 + 'method' => 'POST',
1812 + 'timeout' => 15,
1813 + ];
1690 1814
1691 - /*
1692 - // Log the interpreted query for debugging
1693 - if (defined('WP_DEBUG') && WP_DEBUG) {
1694 - error_log("Interpreted search query: " . $interpreted_query);
1695 - }
1696 - */
1815 + $response = wp_remote_post($url, $args);
1816 + if (is_wp_error($response)) {
1817 + return sanitize_text_field($user_query);
1818 + }
1697 1819
1698 - return $interpreted_query;
1699 - } else {
1700 - //error_log("Unexpected API response format: " . print_r($body, true));
1820 + $body = json_decode(wp_remote_retrieve_body($response), true);
1821 + if (!empty($body['content'][0]['text'])) {
1822 + return sanitize_text_field(trim($body['content'][0]['text']));
1823 + }
1824 +
1825 + return sanitize_text_field($user_query);
1826 +}
1827 +
1828 +/**
1829 + * Interpret query using Gemini models
1830 + */
1831 +private function interpret_query_with_gemini($user_query, $system_prompt, $api_key, $model) {
1832 + // Strip "gemini-" prefix for the API
1833 + $model_version = str_replace('gemini-', '', $model);
1834 +
1835 + $url = "https://generativelanguage.googleapis.com/v1/models/$model_version:generateContent?key=" . urlencode($api_key);
1836 +
1837 + $args = [
1838 + 'headers' => [
1839 + 'Content-Type' => 'application/json',
1840 + ],
1841 + 'body' => wp_json_encode([
1842 + 'contents' => [
1843 + [
1844 + 'role' => 'user',
1845 + 'parts' => [
1846 + ['text' => $system_prompt . "\n\nQuery: " . sanitize_text_field($user_query)]
1847 + ]
1848 + ]
1849 + ],
1850 + 'generationConfig' => [
1851 + 'temperature' => 0.2,
1852 + 'maxOutputTokens' => 20,
1853 + ],
1854 + ]),
1855 + 'method' => 'POST',
1856 + 'timeout' => 15,
1857 + ];
1858 +
1859 + $response = wp_remote_post($url, $args);
1860 + if (is_wp_error($response)) {
1701 1861 return sanitize_text_field($user_query);
1702 1862 }
1863 +
1864 + $body = json_decode(wp_remote_retrieve_body($response), true);
1865 + if (!empty($body['candidates'][0]['content']['parts'][0]['text'])) {
1866 + return sanitize_text_field(trim($body['candidates'][0]['content']['parts'][0]['text']));
1867 + }
1868 +
1869 + return sanitize_text_field($user_query);
1703 1870 }
1704 1871
1705 -public function mxchat_handle_add_to_cart_intent($message, $user_id, $session_id) {
1706 - //error_log("ADD TO CART START - Message: $message, User ID: $user_id");
1872 +/**
1873 + * Interpret query using X.AI (Grok) models
1874 + */
1875 +private function interpret_query_with_xai($user_query, $system_prompt, $api_key, $model) {
1876 + $url = 'https://api.xai.com/v1/chat/completions';
1877 +
1878 + $args = [
1879 + 'headers' => [
1880 + 'Content-Type' => 'application/json',
1881 + 'Authorization' => 'Bearer ' . $api_key,
1882 + ],
1883 + 'body' => wp_json_encode([
1884 + 'model' => $model,
1885 + 'messages' => [
1886 + ['role' => 'system', 'content' => $system_prompt],
1887 + ['role' => 'user', 'content' => sanitize_text_field($user_query)],
1888 + ],
1889 + 'temperature' => 0.2,
1890 + 'max_tokens' => 20,
1891 + ]),
1892 + 'method' => 'POST',
1893 + 'timeout' => 15,
1894 + ];
1895 +
1896 + $response = wp_remote_post($url, $args);
1897 + if (is_wp_error($response)) {
1898 + return sanitize_text_field($user_query);
1899 + }
1900 +
1901 + $body = json_decode(wp_remote_retrieve_body($response), true);
1902 + if (isset($body['choices'][0]['message']['content'])) {
1903 + return sanitize_text_field(trim($body['choices'][0]['message']['content']));
1904 + }
1905 +
1906 + return sanitize_text_field($user_query);
1907 +}
1707 1908
1708 - if (!class_exists('WooCommerce')) {
1709 - //error_log("WooCommerce not available");
1710 - return $this->generate_intent_response([
1711 - 'intent' => 'add_to_cart',
1712 - 'status' => 'error',
1713 - 'reason' => esc_html__('woocommerce_not_available', 'mxchat')
1714 - ], $session_id);
1715 - }
1909 +/**
1910 + * Interpret query using DeepSeek models
1911 + */
1912 +private function interpret_query_with_deepseek($user_query, $system_prompt, $api_key, $model) {
1913 + $url = 'https://api.deepseek.com/v1/chat/completions';
1914 +
1915 + $args = [
1916 + 'headers' => [
1917 + 'Content-Type' => 'application/json',
1918 + 'Authorization' => 'Bearer ' . $api_key,
1919 + ],
1920 + 'body' => wp_json_encode([
1921 + 'model' => $model,
1922 + 'messages' => [
1923 + ['role' => 'system', 'content' => $system_prompt],
1924 + ['role' => 'user', 'content' => sanitize_text_field($user_query)],
1925 + ],
1926 + 'temperature' => 0.2,
1927 + 'max_tokens' => 20,
1928 + ]),
1929 + 'method' => 'POST',
1930 + 'timeout' => 15,
1931 + ];
1932 +
1933 + $response = wp_remote_post($url, $args);
1934 + if (is_wp_error($response)) {
1935 + return sanitize_text_field($user_query);
1936 + }
1937 +
1938 + $body = json_decode(wp_remote_retrieve_body($response), true);
1939 + if (isset($body['choices'][0]['message']['content'])) {
1940 + return sanitize_text_field(trim($body['choices'][0]['message']['content']));
1941 + }
1942 +
1943 + return sanitize_text_field($user_query);
1944 +}
1716 1945
1717 - $sanitized_user_id = sanitize_key($user_id);
1718 - // error_log("Sanitized User ID: $sanitized_user_id");
1719 - $product_id = null;
1720 1946
1721 - // If button click, use transient
1722 - if ($message === '!addtocart') {
1723 - //error_log("Button click detected - using transient");
1724 - $product_id = get_transient('mxchat_last_discussed_product_' . $sanitized_user_id);
1725 - //error_log("Product ID from transient: " . ($product_id ?? 'null'));
1726 - } else {
1727 - // For text commands, search message first
1728 - //error_log("Text command detected - searching message first");
1729 - $product_id = $this->find_product_in_message($message);
1730 - //error_log("Product ID from message search: " . ($product_id ?? 'null'));
1731 1947
1732 - // Only use transient as fallback if no product found in message
1733 - if (!$product_id) {
1734 - //error_log("No product found in message, checking transient as fallback");
1735 - $product_id = get_transient('mxchat_last_discussed_product_' . $sanitized_user_id);
1736 - //error_log("Product ID from transient fallback: " . ($product_id ?? 'null'));
1737 - }
1738 - }
1739 -
1740 - // Handle no product found
1741 - if (!$product_id) {
1742 - //error_log("No product ID found through any method");
1743 - return $this->generate_intent_response([
1744 - 'intent' => 'add_to_cart',
1745 - 'status' => 'error',
1746 - 'reason' => esc_html__('no_product_context', 'mxchat'),
1747 - 'action_needed' => esc_html__('request_product_name', 'mxchat'),
1748 - 'searched_message' => $message
1749 - ], $session_id);
1750 - }
1751 -
1752 - // Get and verify product
1753 - $product = wc_get_product($product_id);
1754 - if (!$product) {
1755 - //error_log("Product not found with ID: $product_id");
1756 - return $this->generate_intent_response([
1757 - 'intent' => 'add_to_cart',
1758 - 'status' => 'error',
1759 - 'reason' => esc_html__('product_not_found', 'mxchat'),
1760 - 'product_id' => $product_id
1761 - ], $session_id);
1762 - }
1763 -
1764 - //error_log("Found product: " . $product->get_name() . " (ID: $product_id)");
1765 -
1766 - // Add to cart
1767 - $added = WC()->cart->add_to_cart($product_id);
1768 - if ($added) {
1769 - //error_log("Successfully added to cart: " . $product->get_name());
1770 - return $this->generate_intent_response([
1771 - 'intent' => 'add_to_cart',
1772 - 'status' => 'success',
1773 - 'product' => [
1774 - 'name' => $product->get_name(),
1775 - 'id' => $product_id
1776 - ],
1777 - 'cart_url' => wc_get_cart_url(),
1778 - 'available_actions' => [
1779 - esc_html__('view_cart', 'mxchat'),
1780 - esc_html__('checkout', 'mxchat'),
1781 - esc_html__('continue_shopping', 'mxchat')
1782 - ]
1783 - ], $session_id);
1784 - } else {
1785 - //error_log("Failed to add to cart: " . $product->get_name());
1786 - return $this->generate_intent_response([
1787 - 'intent' => 'add_to_cart',
1788 - 'status' => 'error',
1789 - 'reason' => esc_html__('add_to_cart_failed', 'mxchat'),
1790 - 'product' => [
1791 - 'name' => $product->get_name(),
1792 - 'id' => $product_id
1793 - ]
1794 - ], $session_id);
1795 - }
1796 -}
1797 1948 private function find_product_in_message($message) {
1798 1949 global $wpdb;
1799 1950
1800 1951 // Get embedding for the search query
1801 1952 $query_embedding = $this->mxchat_generate_embedding($message, $this->options['api_key']);
1802 - if (!is_array($query_embedding)) {
1953 +
1954 + // Check if embedding generation returned an error
1955 + if (is_array($query_embedding) && isset($query_embedding['error'])) {
1956 + $error_message = $query_embedding['error'];
1957 + $error_code = $query_embedding['error_code'] ?? 'embedding_error';
1958 +
1959 + //error_log("Product search embedding error: $error_message (Code: $error_code)");
1960 +
1961 + // Set a user-friendly fallback response
1962 + $this->fallbackResponse['text'] = esc_html__("I'm having trouble processing your product search. Please try again later or contact support if this persists.", 'mxchat');
1963 +
1964 + // Also store the technical error for admin users
1965 + $this->fallbackResponse['admin_error'] = $error_message;
1966 + $this->fallbackResponse['error_code'] = $error_code;
1967 +
1803 1968 return null;
1804 1969 }
1805 -
1970 +
1971 + // Check if embedding is valid
1972 + if (!is_array($query_embedding) || empty($query_embedding)) {
1973 + //error_log("Failed to generate embedding for product search");
1974 + $this->fallbackResponse['text'] = esc_html__("I couldn't process your product search. Please try again with different wording.", 'mxchat');
1975 + return null;
1976 + }
1977 +
1806 1978 // Get relevant content as string
1807 1979 $relevant_content = $this->mxchat_find_relevant_products($query_embedding);
1808 1980 if (empty($relevant_content)) {
1809 1981 // Return null to indicate no results and set fallback response
@@ -1810,8 +1982,9 @@
1810 1982 $this->fallbackResponse['text'] = esc_html__("I couldn't find any relevant products based on your query. Could you please be more specific about the product you're looking for?", 'mxchat');
1811 1983 return null;
1812 1984 }
1813 1985
1986 +
1814 1987 // Extract product URLs from the content
1815 1988 preg_match_all('/https?:\/\/[^\s<>"\']+?\/product\/[^\s<>"\']+/', $relevant_content, $matches);
1816 1989
1817 1990 if (!empty($matches[0])) {
@@ -1869,9 +2042,8 @@
1869 2042 // New method to handle intent responses
1870 2043 private function generate_intent_response($context_content, $session_id) {
1871 2044 // Convert the context array to a structured string for the AI
1872 2045 $context_string = $this->format_intent_context($context_content);
1873 -
1874 2046 // Generate AI response using the context
1875 2047 $response = $this->mxchat_generate_response(
1876 2048 $context_string,
1877 2049 $this->options['api_key'],
@@ -1877,14 +2049,15 @@
1877 2049 $this->options['api_key'],
1878 2050 $this->options['xai_api_key'],
1879 2051 $this->options['claude_api_key'],
1880 2052 $this->options['deepseek_api_key'],
2053 + $this->options['gemini_api_key'], // Added Gemini API key
1881 2054 $this->mxchat_fetch_conversation_history_for_ai($session_id)
1882 2055 );
1883 -
1884 2056 $this->fallbackResponse['text'] = $response;
1885 2057 return true;
1886 2058 }
2059 +
1887 2060 // Helper method to format intent context
1888 2061 private function format_intent_context($context) {
1889 2062 $context_string = esc_html__("INTENT CONTEXT:\n", 'mxchat');
1890 2063
@@ -1920,44 +2093,8 @@
1920 2093 return $context_string;
1921 2094 }
1922 2095
1923 2096
1924 -public function mxchat_handle_checkout_intent($message, $user_id, $session_id) {
1925 - if (!class_exists('WooCommerce')) {
1926 - $this->fallbackResponse['text'] = esc_html__("I apologize, but the checkout feature isn't available at the moment.", 'mxchat');
1927 - return true;
1928 - }
1929 -
1930 - // Check if cart has items
1931 - if (WC()->cart->is_empty()) {
1932 - $this->fallbackResponse['text'] = esc_html__("Your cart is empty at the moment. Would you like to see our products?", 'mxchat');
1933 - return true;
1934 - }
1935 -
1936 - // Get cart summary
1937 - $cart_count = WC()->cart->get_cart_contents_count();
1938 - $cart_total = WC()->cart->get_total();
1939 -
1940 - // Get and validate checkout URL
1941 - $checkout_url = wc_get_checkout_url();
1942 - if (!$checkout_url) {
1943 - $this->fallbackResponse['text'] = esc_html__("I'm having trouble accessing the checkout page. Please try again in a moment.", 'mxchat');
1944 - return true;
1945 - }
1946 -
1947 - wp_send_json([
1948 - 'text' => sprintf(
1949 - esc_html__("You have %d item%s in your cart totaling %s. I'll redirect you to checkout now.", 'mxchat'),
1950 - $cart_count,
1951 - $cart_count > 1 ? esc_html__('s', 'mxchat') : '',
1952 - strip_tags($cart_total)
1953 - ),
1954 - 'redirect_url' => esc_url_raw($checkout_url)
1955 - ]);
1956 - wp_die();
1957 -}
1958 -
1959 -
1960 2097 //very good
1961 2098 private function add_email_to_loops($email) {
1962 2099 // Sanitize the email
1963 2100 $email = sanitize_email($email);
@@ -2041,8 +2178,10 @@
2041 2178
2042 2179 // Default to proceeding with conversation if no specific PDF action is needed
2043 2180 $this->fallbackResponse['text'] = '';
2044 2181 }
2182 +
2183 +
2045 2184 private function fetch_and_split_pdf_pages($pdf_source, $max_pages) {
2046 2185 $upload_dir = wp_upload_dir();
2047 2186 $temp_file = null;
2048 2187
@@ -2118,9 +2257,9 @@
2118 2257
2119 2258 return $embeddings;
2120 2259
2121 2260 } catch (\Exception $e) {
2122 - // error_log(esc_html__("Error parsing or processing PDF: ", 'mxchat') . $e->getMessage());
2261 + // //error_log(esc_html__("Error parsing or processing PDF: ", 'mxchat') . $e->getMessage());
2123 2262
2124 2263 // Cleanup in case of exception
2125 2264 if (filter_var($pdf_source, FILTER_VALIDATE_URL) && $temp_file && file_exists($temp_file)) {
2126 2265 unlink($temp_file);
@@ -2250,408 +2389,10 @@
2250 2389 wp_die();
2251 2390 }
2252 2391
2253 2392
2254 -/**
2255 - * Calls the AI API with the provided prompt.
2256 - *
2257 - * @param string $prompt The prompt to send to the AI.
2258 - * @return array An array containing the AI's response text.
2259 - */
2260 -private function mxchat_call_ai_api( $prompt ) {
2261 - //error_log( esc_html__( 'Calling AI API with the provided prompt.', 'mxchat' ) );
2262 2393
2263 - $api_key = $this->options['api_key'];
2264 - if ( empty( $api_key ) ) {
2265 - //error_log( esc_html__( 'API key is not set.', 'mxchat' ) );
2266 - return [ 'text' => esc_html__( 'API key is not set.', 'mxchat' ) ];
2267 - }
2268 2394
2269 - $url = 'https://api.openai.com/v1/chat/completions';
2270 - $messages = [
2271 - [
2272 - 'role' => 'system',
2273 - 'content' => __( 'You are a helpful assistant that provides concise and personalized product recommendations. Someone has asked you for some recommendations please respond very concisely appropriate for an AI chatbot.', 'mxchat' ),
2274 - ],
2275 - [
2276 - 'role' => 'user',
2277 - 'content' => $prompt,
2278 - ],
2279 - ];
2280 -
2281 - $args = [
2282 - 'headers' => [
2283 - 'Authorization' => 'Bearer ' . $api_key,
2284 - 'Content-Type' => 'application/json',
2285 - ],
2286 - 'body' => wp_json_encode(
2287 - [
2288 - 'model' => 'gpt-4o',
2289 - 'messages' => $messages,
2290 - 'temperature' => 0.7,
2291 - ]
2292 - ),
2293 - 'timeout' => 10,
2294 - 'method' => 'POST',
2295 - ];
2296 -
2297 - $response = wp_remote_post( $url, $args );
2298 -
2299 - if ( is_wp_error( $response ) ) {
2300 - //error_log( esc_html__( 'Error communicating with AI API: ', 'mxchat' ) . $response->get_error_message() );
2301 - return [ 'text' => esc_html__( 'Error communicating with AI API.', 'mxchat' ) ];
2302 - }
2303 -
2304 - $body = wp_remote_retrieve_body( $response );
2305 - //error_log( esc_html__( 'API response body: ', 'mxchat' ) . $body );
2306 -
2307 - $decoded_body = json_decode( $body, true );
2308 - if ( isset( $decoded_body['choices'][0]['message']['content'] ) ) {
2309 - return [ 'text' => $decoded_body['choices'][0]['message']['content'] ];
2310 - } else {
2311 - //error_log( esc_html__( 'Unexpected API response format: ', 'mxchat' ) . wp_json_encode( $decoded_body ) );
2312 - return [ 'text' => esc_html__( 'No response received from AI.', 'mxchat' ) ];
2313 - }
2314 -}
2315 -
2316 -/**
2317 - * Fetches the AI response for the given prompt.
2318 - *
2319 - * @param string $prompt The prompt to send to the AI.
2320 - * @return string|null The AI's response text or null if not available.
2321 - */
2322 -private function mxchat_fetch_ai_response( $prompt ) {
2323 - $response = $this->mxchat_call_ai_api( $prompt );
2324 - return isset( $response['text'] ) ? $response['text'] : null;
2325 -}
2326 -/**
2327 - * Generates the AI prompt for product recommendations.
2328 - *
2329 - * @param array $recommendations An array of product recommendations.
2330 - * @return string The generated AI prompt.
2331 - */
2332 -private function mxchat_generate_ai_recommendation_prompt($recommendations) {
2333 - $recommendation_list = '';
2334 - foreach ($recommendations as $index => $rec) {
2335 - $number = $index + 1;
2336 - $name = $rec['name'];
2337 - $price = $rec['price'];
2338 - $url = $rec['url'];
2339 - $image = $rec['image'];
2340 - $recommendation_list .= "{$number}. " . esc_html__('Product:', 'mxchat') . " {$name} (" . esc_html__('Price:', 'mxchat') . " \\${$price})\n";
2341 - $recommendation_list .= " [Link]({$url})\n";
2342 - $recommendation_list .= " ![Image]({$image})\n\n";
2343 - }
2344 -
2345 - $prompt = esc_html__('Based on the following list of products, generate a unique, friendly, and personalized response to a user. ', 'mxchat');
2346 - $prompt .= esc_html__('If some products aren\'t exactly what the user asked for but share similar styles or patterns, acknowledge this and explain why you\'re suggesting them. ', 'mxchat');
2347 - $prompt .= esc_html__('For each product, provide a brief justification that clearly explains why it\'s relevant, especially if it\'s a different type of product than requested. ', 'mxchat');
2348 - $prompt .= esc_html__('Please number your responses to match the product numbers.', 'mxchat') . "\n\n";
2349 - $prompt .= esc_html__('Products:', 'mxchat') . "\n\n{$recommendation_list}";
2350 - $prompt .= esc_html__('Please ensure that the number of each product matches the order in which the products are listed.', 'mxchat');
2351 -
2352 - return $prompt;
2353 -}
2354 -private function mxchat_generate_recommendations($user_id, $message) {
2355 - // 1. Gather user context
2356 - $user_context = $this->mxchat_get_user_context($user_id);
2357 -
2358 - // Get cart item IDs for filtering
2359 - $cart_item_ids = [];
2360 - if (!empty($user_context['cart_items'])) {
2361 - $cart_item_ids = array_map(function($item) {
2362 - return $item['product_id'];
2363 - }, $user_context['cart_items']);
2364 - }
2365 -
2366 - // 2. Get AI recommendation based on context
2367 - $ai_suggestion = $this->mxchat_get_ai_shopping_suggestion($message, $user_context);
2368 - //error_log("AI Shopping Suggestion: " . $ai_suggestion);
2369 -
2370 - // 3. Generate embedding for the AI suggestion
2371 - $suggestion_embedding = $this->mxchat_generate_embedding($ai_suggestion, $this->options['api_key']);
2372 - if (!$suggestion_embedding) {
2373 - //error_log("Could not generate embedding for AI suggestion");
2374 - return ['recommendations' => [], 'sources' => []];
2375 - }
2376 -
2377 - // 4. Use existing relevant content function to find matches
2378 - $relevant_content = $this->mxchat_find_relevant_content($suggestion_embedding);
2379 -
2380 - // 5. Extract product URLs from the relevant content
2381 - preg_match_all('/https?:\/\/[^\s<>"\']+?\/product\/[^\s<>"\']+/', $relevant_content, $matches);
2382 -
2383 - $found_products = [];
2384 - if (!empty($matches[0])) {
2385 - foreach ($matches[0] as $url) {
2386 - // Clean the URL
2387 - $url = rtrim($url, '/."\']');
2388 -
2389 - // Get the product slug
2390 - $path = parse_url($url, PHP_URL_PATH);
2391 - $slug = basename(rtrim($path, '/'));
2392 -
2393 - // Find product by slug
2394 - $args = array(
2395 - 'post_type' => 'product',
2396 - 'post_status' => 'publish',
2397 - 'name' => $slug,
2398 - 'posts_per_page' => 1
2399 - );
2400 -
2401 - $products = get_posts($args);
2402 -
2403 - if (!empty($products)) {
2404 - $product_id = $products[0]->ID;
2405 -
2406 - // Skip if product is in cart
2407 - if (in_array($product_id, $cart_item_ids)) {
2408 - //error_log("Skipping product ID $product_id - already in cart");
2409 - continue;
2410 - }
2411 -
2412 - $product = wc_get_product($product_id);
2413 -
2414 - if ($product && $product->is_purchasable() && !in_array($product_id, array_map(function($p) {
2415 - return $p->get_id();
2416 - }, $found_products))) {
2417 - $found_products[] = $product;
2418 - }
2419 - }
2420 - }
2421 - }
2422 -
2423 - // If no products found by URLs, look for product names in the content
2424 - if (empty($found_products)) {
2425 - $products = wc_get_products([
2426 - 'status' => 'publish',
2427 - 'limit' => -1,
2428 - 'return' => 'objects'
2429 - ]);
2430 -
2431 - foreach ($products as $product) {
2432 - // Skip if product is in cart
2433 - if (in_array($product->get_id(), $cart_item_ids)) {
2434 - continue;
2435 - }
2436 -
2437 - $name = $product->get_name();
2438 - if (stripos($relevant_content, $name) !== false) {
2439 - if ($product->is_purchasable() && !in_array($product->get_id(), array_map(function($p) {
2440 - return $p->get_id();
2441 - }, $found_products))) {
2442 - $found_products[] = $product;
2443 - }
2444 - }
2445 - }
2446 - }
2447 -
2448 - // Keep searching until we have 4 products or run out of options
2449 - $formatted_recommendations = [];
2450 - foreach ($found_products as $product) {
2451 - if (count($formatted_recommendations) >= 4) break;
2452 -
2453 - $formatted_recommendations[] = [
2454 - 'name' => $product->get_name(),
2455 - 'price' => $product->get_price(),
2456 - 'url' => get_permalink($product->get_id()),
2457 - 'image' => wp_get_attachment_url($product->get_image_id())
2458 - ];
2459 - }
2460 -
2461 - return [
2462 - 'recommendations' => $formatted_recommendations,
2463 - 'sources' => [__('AI-powered personalized recommendations', 'mxchat')]
2464 - ];
2465 -}
2466 -private function mxchat_get_ai_shopping_suggestion($message, $user_context) {
2467 - $prompt = esc_html__("As a shopping assistant, analyze this user's context and generate a specific product search suggestion. ", 'mxchat');
2468 - $prompt .= esc_html__("User's Question: \"{$message}\"\n\n", 'mxchat');
2469 -
2470 - if (!empty($user_context['order_history'])) {
2471 - $prompt .= esc_html__("Their recent orders include:\n", 'mxchat');
2472 - foreach ($user_context['order_history'] as $order) {
2473 - $prompt .= esc_html__("- {$order['name']} ({$order['date']})\n", 'mxchat');
2474 - }
2475 - }
2476 -
2477 - if (!empty($user_context['cart_items'])) {
2478 - $prompt .= esc_html__("\nThey currently have in their cart:\n", 'mxchat');
2479 - foreach ($user_context['cart_items'] as $item) {
2480 - $prompt .= esc_html__("- {$item['name']}\n", 'mxchat');
2481 - }
2482 - }
2483 -
2484 - $prompt .= esc_html__("\nBased on their question and history, suggest a specific search query that would help find the most relevant products. ", 'mxchat');
2485 - $prompt .= esc_html__("Respond with ONLY the search query, nothing else.", 'mxchat');
2486 -
2487 - $response = $this->mxchat_call_ai_api($prompt);
2488 - return isset($response['text']) ? trim($response['text']) : $message;
2489 -}
2490 -public function mxchat_handle_product_recommendations($message, $user_id, $session_id) {
2491 - try {
2492 - //error_log("Starting product recommendations for user: $user_id, session: $session_id");
2493 -
2494 - // Pass the message to get context-aware recommendations
2495 - $recommendation_data = $this->mxchat_generate_recommendations($user_id, $message);
2496 - //error_log('Generated recommendation data: ' . wp_json_encode($recommendation_data));
2497 -
2498 - if (empty($recommendation_data['recommendations'])) {
2499 - //error_log("No recommendations found for user: $user_id");
2500 - $this->fallbackResponse = [
2501 - 'text' => __("I couldn't find any product recommendations for you right now. Please try again later!", 'mxchat'),
2502 - ];
2503 - return;
2504 - }
2505 -
2506 - // Remove duplicates and limit to top 4 recommendations
2507 - $unique_recommendations = [];
2508 - foreach ($recommendation_data['recommendations'] as $rec) {
2509 - $unique_recommendations[$rec['url']] = $rec;
2510 - }
2511 -
2512 - $unique_recommendations = array_slice($unique_recommendations, 0, 4);
2513 - //error_log('Top 4 recommendations: ' . wp_json_encode($unique_recommendations));
2514 -
2515 - $recommendations_summary = [];
2516 - foreach ($unique_recommendations as $rec) {
2517 - $recommendations_summary[] = [
2518 - 'name' => $rec['name'],
2519 - 'price' => strip_tags($rec['price']),
2520 - 'url' => $rec['url'],
2521 - 'image' => $rec['image'],
2522 - ];
2523 - }
2524 -
2525 - // Generate AI prompt and fetch response
2526 - $ai_prompt = $this->mxchat_generate_ai_recommendation_prompt($recommendations_summary);
2527 - $ai_response = $this->mxchat_fetch_ai_response($ai_prompt);
2528 -
2529 - if (empty($ai_response)) {
2530 - $this->fallbackResponse = [
2531 - 'text' => __('An unexpected error occurred while generating recommendations. Please try again later.', 'mxchat'),
2532 - ];
2533 - return;
2534 - }
2535 -
2536 - // Split the AI's response into lines
2537 - $ai_lines = preg_split('/\r\n|\r|\n/', $ai_response);
2538 -
2539 - // Initialize variables
2540 - $formatted_response = '';
2541 - $justifications = [];
2542 - $current_number = 0;
2543 - $in_introduction = true;
2544 - $introduction = '';
2545 -
2546 - // Parse the AI response to separate the introduction and the justifications
2547 - foreach ($ai_lines as $line) {
2548 - if (preg_match('/^\s*(\d+)\.\s*(.*)$/', $line, $matches)) {
2549 - // This line is a numbered justification
2550 - $current_number = intval($matches[1]) - 1;
2551 - $justifications[$current_number] = $matches[2];
2552 - $in_introduction = false;
2553 - } elseif ($in_introduction) {
2554 - // This line is part of the introduction
2555 - $introduction .= $line . ' ';
2556 - } else {
2557 - // This line is a continuation of the current justification
2558 - if (isset($justifications[$current_number])) {
2559 - $justifications[$current_number] .= ' ' . $line;
2560 - }
2561 - }
2562 - }
2563 -
2564 - // Build the formatted response
2565 - if (!empty($introduction)) {
2566 - $formatted_response .= esc_html(trim($introduction)) . "<br><br>";
2567 - }
2568 -
2569 - foreach ($recommendations_summary as $index => $rec) {
2570 - $name = esc_html($rec['name']);
2571 - $price = number_format((float)$rec['price'], 2, '.', '');
2572 - $url = esc_url($rec['url']);
2573 - $image = esc_url($rec['image']);
2574 -
2575 - $formatted_response .= ($index + 1) . ". <strong>" . $name . "</strong> - <strong>$" . $price . "</strong><br>";
2576 - $formatted_response .= "<img src=\"{$image}\" alt=\"{$name}\" style=\"max-width: 200px; height: auto; display: block; margin: 10px 0;\" /><br>";
2577 -
2578 - if (isset($justifications[$index])) {
2579 - $formatted_response .= "<em>" . esc_html(trim($justifications[$index])) . "</em><br><br>";
2580 - }
2581 - }
2582 -
2583 - $this->fallbackResponse = [
2584 - 'text' => $formatted_response,
2585 - ];
2586 - //error_log('Final formatted response set.');
2587 - } catch (Exception $e) {
2588 - //error_log('Error in mxchat_handle_product_recommendations: ' . $e->getMessage());
2589 - $this->fallbackResponse = [
2590 - 'text' => __('An unexpected error occurred while generating recommendations. Please try again later.', 'mxchat'),
2591 - ];
2592 - }
2593 -}
2594 -private function mxchat_get_user_context($user_id) {
2595 - $context = [
2596 - 'order_history' => [],
2597 - 'cart_items' => [],
2598 - 'recently_viewed' => [],
2599 - ];
2600 -
2601 - // Get order history
2602 - if (is_user_logged_in() && $user_id) {
2603 - $orders = wc_get_orders([
2604 - 'customer_id' => $user_id,
2605 - 'limit' => 5, // Last 5 orders
2606 - 'orderby' => 'date',
2607 - 'order' => 'DESC',
2608 - ]);
2609 -
2610 - foreach ($orders as $order) {
2611 - foreach ($order->get_items() as $item) {
2612 - $context['order_history'][] = [
2613 - 'name' => $item->get_name(),
2614 - 'product_id' => $item->get_product_id(),
2615 - 'date' => $order->get_date_created()->format('Y-m-d')
2616 - ];
2617 - }
2618 - }
2619 - }
2620 -
2621 - // Get cart items
2622 - if (WC()->cart && WC()->cart->get_cart_contents_count() > 0) {
2623 - foreach (WC()->cart->get_cart() as $cart_item) {
2624 - $product = wc_get_product($cart_item['product_id']);
2625 - if ($product) {
2626 - $context['cart_items'][] = [
2627 - 'name' => $product->get_name(),
2628 - 'product_id' => $product->get_id()
2629 - ];
2630 - }
2631 - }
2632 - }
2633 -
2634 - // Get recently viewed items from session
2635 - $viewed_products = WC()->session->get('recently_viewed_products', []);
2636 - foreach ($viewed_products as $product_id) {
2637 - $product = wc_get_product($product_id);
2638 - if ($product) {
2639 - $context['recently_viewed'][] = [
2640 - 'name' => $product->get_name(),
2641 - 'product_id' => $product->get_id()
2642 - ];
2643 - }
2644 - }
2645 -
2646 - return $context;
2647 -}
2648 -
2649 -
2650 -
2651 -
2652 -
2653 -
2654 2395 function mxchat_fetch_new_messages() {
2655 2396 $session_id = sanitize_text_field($_POST['session_id']);
2656 2397 $last_seen_id = sanitize_text_field($_POST['last_seen_id']);
2657 2398 $persistence_enabled = $_POST['persistence_enabled'] === 'true';
@@ -2988,13 +2729,13 @@
2988 2729
2989 2730 public function mxchat_handle_agent_response(WP_REST_Request $request) {
2990 2731 //error_log('Received agent response request');
2991 2732 //error_log('Request data: ' . print_r($request->get_params(), true));
2992 - // error_log('Raw body: ' . file_get_contents('php://input'));
2733 + // //error_log('Raw body: ' . file_get_contents('php://input'));
2993 2734
2994 2735 // Get the data from Slack's slash command format
2995 2736 $command_text = $request->get_param('text');
2996 - // error_log('Command text: ' . $command_text);
2737 + // //error_log('Command text: ' . $command_text);
2997 2738
2998 2739 if (empty($command_text)) {
2999 2740 //error_log(esc_html__('Agent response error: No command text received', 'mxchat'));
3000 2741 return new WP_REST_Response([
@@ -3019,9 +2760,9 @@
3019 2760 // Save the message
3020 2761 $message_id = $this->mxchat_save_chat_message($session_id, 'agent', $message);
3021 2762
3022 2763 if (!$message_id) {
3023 - // error_log('Failed to save agent message');
2764 + // //error_log('Failed to save agent message');
3024 2765 return new WP_REST_Response([
3025 2766 'error' => esc_html__('Failed to save message', 'mxchat')
3026 2767 ], 500);
3027 2768 }
@@ -3076,21 +2817,102 @@
3076 2817 return MxChat_User::mxchat_get_user_identifier();
3077 2818 }
3078 2819
3079 2820 private function mxchat_generate_embedding($text, $api_key) {
3080 - $endpoint = 'https://api.openai.com/v1/embeddings';
3081 -
3082 - $body = wp_json_encode([
3083 - 'input' => $text,
3084 - 'model' => 'text-embedding-ada-002'
3085 - ]);
3086 -
2821 + try {
2822 + // Get options and selected model
2823 + $options = get_option('mxchat_options');
2824 + $selected_model = $options['embedding_model'] ?? 'text-embedding-ada-002';
2825 +
2826 + // Determine endpoint and API key based on model
2827 + if (strpos($selected_model, 'voyage') === 0) {
2828 + $endpoint = 'https://api.voyageai.com/v1/embeddings';
2829 + $api_key = $options['voyage_api_key'] ?? '';
2830 +
2831 + // Check if Voyage API key is missing
2832 + if (empty($api_key)) {
2833 + //error_log('Voyage API key is missing');
2834 + return [
2835 + 'error' => esc_html__('Voyage AI API key is not configured', 'mxchat'),
2836 + 'error_code' => 'missing_voyage_api_key'
2837 + ];
2838 + }
2839 + } elseif (strpos($selected_model, 'gemini-embedding') === 0) {
2840 + $endpoint = 'https://generativelanguage.googleapis.com/v1beta/models/' . $selected_model . ':embedContent';
2841 + $api_key = $options['gemini_api_key'] ?? '';
2842 +
2843 + // Check if Gemini API key is missing
2844 + if (empty($api_key)) {
2845 + //error_log('Gemini API key is missing');
2846 + return [
2847 + 'error' => esc_html__('Google Gemini API key is not configured', 'mxchat'),
2848 + 'error_code' => 'missing_gemini_api_key'
2849 + ];
2850 + }
2851 + } else {
2852 + $endpoint = 'https://api.openai.com/v1/embeddings';
2853 + // Use the passed API key for OpenAI
2854 +
2855 + // Check if OpenAI API key is missing
2856 + if (empty($api_key)) {
2857 + //error_log('OpenAI API key is missing');
2858 + return [
2859 + 'error' => esc_html__('OpenAI API key is not configured', 'mxchat'),
2860 + 'error_code' => 'missing_openai_api_key'
2861 + ];
2862 + }
2863 + }
2864 +
2865 + // Check if text is empty
2866 + if (empty($text)) {
2867 + //error_log('Empty text provided for embedding generation');
2868 + return [
2869 + 'error' => esc_html__('No text provided for embedding generation', 'mxchat'),
2870 + 'error_code' => 'empty_embedding_text'
2871 + ];
2872 + }
2873 +
2874 + // Prepare request body based on provider
2875 + if (strpos($selected_model, 'gemini-embedding') === 0) {
2876 + // Gemini API format
2877 + $request_body = [
2878 + 'model' => 'models/' . $selected_model,
2879 + 'content' => [
2880 + 'parts' => [
2881 + ['text' => $text]
2882 + ]
2883 + ],
2884 + 'outputDimensionality' => 1536
2885 + ];
2886 +
2887 + // Prepare headers for Gemini (API key as query parameter)
2888 + $endpoint .= '?key=' . $api_key;
2889 + $headers = [
2890 + 'Content-Type' => 'application/json'
2891 + ];
2892 + } else {
2893 + // OpenAI/Voyage API format
2894 + $request_body = [
2895 + 'input' => $text,
2896 + 'model' => $selected_model
2897 + ];
2898 +
2899 + // Add output_dimension for voyage-3-large
2900 + if ($selected_model === 'voyage-3-large') {
2901 + $request_body['output_dimension'] = 2048;
2902 + }
2903 +
2904 + // Prepare headers for OpenAI/Voyage
2905 + $headers = [
2906 + 'Content-Type' => 'application/json',
2907 + 'Authorization' => 'Bearer ' . $api_key
2908 + ];
2909 + }
2910 +
2911 + // Prepare request arguments
3087 2912 $args = [
3088 - 'body' => $body,
3089 - 'headers' => [
3090 - 'Content-Type' => 'application/json',
3091 - 'Authorization' => 'Bearer ' . $api_key,
3092 - ],
2913 + 'body' => wp_json_encode($request_body),
2914 + 'headers' => $headers,
3093 2915 'timeout' => 60,
3094 2916 'redirection' => 5,
3095 2917 'blocking' => true,
3096 2918 'httpversion' => '1.0',
@@ -3095,25 +2917,109 @@
3095 2917 'blocking' => true,
3096 2918 'httpversion' => '1.0',
3097 2919 'sslverify' => true,
3098 2920 ];
3099 -
2921 +
2922 + // Make the request
3100 2923 $response = wp_remote_post($endpoint, $args);
3101 -
2924 +
2925 + // Handle WordPress errors
3102 2926 if (is_wp_error($response)) {
3103 - return null;
2927 + $error_message = $response->get_error_message();
2928 + //error_log('Embedding Generation Error: ' . $error_message);
2929 + return [
2930 + 'error' => esc_html__('Connection error when generating embeddings: ', 'mxchat') . esc_html($error_message),
2931 + 'error_code' => 'embedding_connection_error'
2932 + ];
3104 2933 }
3105 -
2934 +
2935 + // Check HTTP status code
2936 + $status_code = wp_remote_retrieve_response_code($response);
2937 + if ($status_code !== 200) {
2938 + $response_body = json_decode(wp_remote_retrieve_body($response), true);
2939 +
2940 + $error_message = isset($response_body['error']['message'])
2941 + ? $response_body['error']['message']
2942 + : 'HTTP Error ' . $status_code;
2943 +
2944 + $error_type = isset($response_body['error']['type'])
2945 + ? $response_body['error']['type']
2946 + : 'unknown';
2947 +
2948 + //error_log('Embedding API HTTP Error: ' . $status_code . ' - ' . $error_message);
2949 +
2950 + // Handle specific error types
2951 + switch ($error_type) {
2952 + case 'invalid_request_error':
2953 + if (strpos($error_message, 'API key') !== false) {
2954 + return [
2955 + 'error' => esc_html__('Invalid API key for embedding generation. Please check your API key configuration.', 'mxchat'),
2956 + 'error_code' => 'embedding_invalid_api_key'
2957 + ];
2958 + }
2959 + break;
2960 +
2961 + case 'authentication_error':
2962 + return [
2963 + 'error' => esc_html__('Authentication failed for embedding generation. Please check your API key.', 'mxchat'),
2964 + 'error_code' => 'embedding_auth_error'
2965 + ];
2966 +
2967 + case 'rate_limit_exceeded':
2968 + return [
2969 + 'error' => esc_html__('Rate limit exceeded for embedding generation. Please try again later.', 'mxchat'),
2970 + 'error_code' => 'embedding_rate_limit'
2971 + ];
2972 +
2973 + case 'quota_exceeded':
2974 + return [
2975 + 'error' => esc_html__('API quota exceeded for embedding generation. Please check your billing details.', 'mxchat'),
2976 + 'error_code' => 'embedding_quota_exceeded'
2977 + ];
2978 + }
2979 +
2980 + // Generic error fallback
2981 + return [
2982 + 'error' => esc_html__('Embedding API error - check embedding API key.: ', 'mxchat') . esc_html($error_message),
2983 + 'error_code' => 'embedding_api_error',
2984 + 'status_code' => $status_code
2985 + ];
2986 + }
2987 +
3106 2988 $response_body = json_decode(wp_remote_retrieve_body($response), true);
3107 -
3108 - if (isset($response_body['data'][0]['embedding']) && is_array($response_body['data'][0]['embedding'])) {
3109 - return $response_body['data'][0]['embedding'];
2989 +
2990 + // Handle different response formats based on provider
2991 + if (strpos($selected_model, 'gemini-embedding') === 0) {
2992 + // Gemini API response format
2993 + if (isset($response_body['embedding']['values']) && is_array($response_body['embedding']['values'])) {
2994 + return $response_body['embedding']['values'];
2995 + } else {
2996 + //error_log('Invalid Gemini embedding response: ' . wp_json_encode($response_body));
2997 + return [
2998 + 'error' => esc_html__('Received invalid embedding data from the Gemini API.', 'mxchat'),
2999 + 'error_code' => 'invalid_gemini_embedding_response'
3000 + ];
3001 + }
3110 3002 } else {
3111 - return null;
3003 + // OpenAI/Voyage API response format
3004 + if (isset($response_body['data'][0]['embedding']) && is_array($response_body['data'][0]['embedding'])) {
3005 + return $response_body['data'][0]['embedding'];
3006 + } else {
3007 + //error_log('Invalid embedding response: ' . wp_json_encode($response_body));
3008 + return [
3009 + 'error' => esc_html__('Received invalid embedding data from the API.', 'mxchat'),
3010 + 'error_code' => 'invalid_embedding_response'
3011 + ];
3012 + }
3112 3013 }
3014 + } catch (Exception $e) {
3015 + //error_log('Embedding Exception: ' . $e->getMessage());
3016 + return [
3017 + 'error' => esc_html__('System error when generating embeddings: ', 'mxchat') . esc_html($e->getMessage()),
3018 + 'error_code' => 'embedding_exception'
3019 + ];
3113 3020 }
3114 -
3115 -
3021 +}
3116 3022 private function mxchat_find_relevant_content($user_embedding) {
3117 3023 //error_log('MXChat Vector Search: Starting content search...');
3118 3024
3119 3025 // Retrieve the add-on settings from the database.
@@ -3132,9 +3038,8 @@
3132 3038 //error_log('MXChat Vector Search: Using WordPress database');
3133 3039 return $this->find_relevant_content_wordpress($user_embedding);
3134 3040 }
3135 3041 }
3136 -
3137 3042 private function find_relevant_content_wordpress($user_embedding) {
3138 3043 global $wpdb;
3139 3044 $system_prompt_table = $wpdb->prefix . 'mxchat_system_prompt_content';
3140 3045 $cache_key = 'mxchat_system_prompt_embeddings';
@@ -3142,8 +3047,9 @@
3142 3047
3143 3048 // Retrieve embeddings from cache or database
3144 3049 $embeddings = wp_cache_get($cache_key, 'mxchat_system_prompts');
3145 3050 if ($embeddings === false) {
3051 + // Cache miss - load embeddings from database
3146 3052 $embeddings = [];
3147 3053 $offset = 0;
3148 3054
3149 3055 // Load in batches and build cache
@@ -3162,62 +3068,82 @@
3162 3068 }
3163 3069
3164 3070 $embeddings = array_merge($embeddings, $batch);
3165 3071 $offset += $batch_size;
3166 -
3167 - // Free memory
3168 - unset($batch);
3169 -
3072 + unset($batch); // Free memory
3170 3073 } while (true);
3171 3074
3172 3075 if (empty($embeddings)) {
3173 - return ''; // Return an empty string if no embeddings found
3076 + return ''; // Return empty string if no embeddings found
3174 3077 }
3078 +
3079 + // Cache embeddings for future use
3175 3080 wp_cache_set($cache_key, $embeddings, 'mxchat_system_prompts', 3600);
3176 3081 }
3177 3082
3178 - // Initialize array to store relevant results with similarity scores
3083 + // Get configuration options
3084 + $main_options = get_option('mxchat_options', []);
3085 +
3086 + // Get base similarity threshold (default 75%)
3087 + $similarity_threshold = isset($main_options['similarity_threshold'])
3088 + ? ((int) $main_options['similarity_threshold']) / 100
3089 + : 0.75;
3090 +
3091 + // Calculate similarities and build results array
3179 3092 $relevant_results = [];
3180 - // Iterate through embeddings to calculate similarity
3181 3093 foreach ($embeddings as $embedding) {
3182 3094 $database_embedding = $embedding->embedding_vector
3183 3095 ? unserialize($embedding->embedding_vector, ['allowed_classes' => false])
3184 3096 : null;
3097 +
3185 3098 if (is_array($database_embedding) && is_array($user_embedding)) {
3186 3099 $similarity = $this->mxchat_calculate_cosine_similarity($user_embedding, $database_embedding);
3187 - $relevant_results[] = [
3188 - 'id' => $embedding->id,
3189 - 'similarity' => $similarity
3190 - ];
3100 +
3101 + // Only consider results above threshold
3102 + if ($similarity >= $similarity_threshold) {
3103 + $relevant_results[] = [
3104 + 'id' => $embedding->id,
3105 + 'similarity' => $similarity
3106 + ];
3107 + }
3191 3108 }
3192 - // Free memory
3193 - unset($database_embedding);
3109 +
3110 + unset($database_embedding); // Free memory
3194 3111 }
3195 3112
3196 - // Retrieve the similarity threshold
3197 - $similarity_threshold = ((int) get_option('mxchat_similarity_threshold', 80)) / 100;
3198 -
3199 - // Filter and sort relevant results by similarity
3200 - $relevant_results = array_filter($relevant_results, function ($result) use ($similarity_threshold) {
3201 - return $result['similarity'] >= $similarity_threshold;
3202 - });
3113 + // Sort by similarity (highest first)
3203 3114 usort($relevant_results, function ($a, $b) {
3204 3115 return $b['similarity'] <=> $a['similarity'];
3205 3116 });
3206 -
3207 - // Limit to the top 5 results
3117 +
3118 + // Get top 5 results (standard approach)
3208 3119 $top_results = array_slice($relevant_results, 0, 5);
3209 -
3210 - // Initialize the final content
3120 +
3121 + // Initialize final content
3211 3122 $content = '';
3212 -
3213 - // Fetch and combine content for the top results
3214 - foreach ($top_results as $result) {
3123 +
3124 + // Track document IDs to avoid duplicates
3125 + $added_document_ids = [];
3126 +
3127 + // Fetch and format content for each selected result
3128 + foreach ($top_results as $index => $result) {
3129 + // Skip if already added (for PDF surrounding pages)
3130 + if (in_array($result['id'], $added_document_ids)) {
3131 + continue;
3132 + }
3133 +
3215 3134 $chunk_content = $this->fetch_content_with_product_links($result['id']);
3216 - // Check if the content is PDF-related and add surrounding pages
3135 + $added_document_ids[] = $result['id'];
3136 +
3137 + // Add content with minimal markup
3138 + $content .= "## Reference " . ($index + 1) . " ##\n";
3139 + $content .= $chunk_content . "\n\n";
3140 +
3141 + // Check if content is PDF-related
3217 3142 if (strpos($chunk_content, '{"document_type":"pdf"') !== false) {
3143 + // Get surrounding pages
3218 3144 $surrounding_content = $wpdb->get_results($wpdb->prepare(
3219 - "SELECT article_content FROM {$system_prompt_table}
3145 + "SELECT id, article_content FROM {$system_prompt_table}
3220 3146 WHERE id IN (
3221 3147 (SELECT id FROM {$system_prompt_table} WHERE id < %d ORDER BY id DESC LIMIT 1),
3222 3148 (SELECT id FROM {$system_prompt_table} WHERE id > %d ORDER BY id ASC LIMIT 1)
3223 3149 )",
@@ -3223,52 +3149,66 @@
3223 3149 )",
3224 3150 $result['id'],
3225 3151 $result['id']
3226 3152 ));
3227 - // Add previous content if it exists
3153 +
3154 + // Add previous page content if it exists
3228 3155 if (!empty($surrounding_content[0])) {
3156 + $content .= "## Related Content ##\n";
3229 3157 $content .= $surrounding_content[0]->article_content . "\n\n";
3158 + $added_document_ids[] = $surrounding_content[0]->id;
3230 3159 }
3231 - // Add the main chunk content
3232 - $content .= $chunk_content . "\n\n";
3233 - // Add next content if it exists
3160 +
3161 + // Add next page content if it exists
3234 3162 if (!empty($surrounding_content[1])) {
3163 + $content .= "## Related Content ##\n";
3235 3164 $content .= $surrounding_content[1]->article_content . "\n\n";
3165 + $added_document_ids[] = $surrounding_content[1]->id;
3236 3166 }
3237 - } else {
3238 - // For non-PDF content, add directly
3239 - $content .= $chunk_content . "\n\n";
3240 3167 }
3241 3168 }
3242 -
3169 +
3170 + // Add response guidelines that encourage natural conversation but strict adherence to facts
3171 + if (empty($top_results)) {
3172 + $content = "No reference information was found for this query.\n\n" .
3173 + "Respond naturally but inform the user that you don't have specific information " .
3174 + "about their query and suggest they check the documentation, speak to a live agent, " .
3175 + "or create a support ticket.";
3176 + } else {
3177 + $content .= "\n## Response Guidelines ##\n" .
3178 + "Answer naturally as a helpful product representative would, without phrases like " .
3179 + "'based on my knowledge base' or 'according to the information.' Only use facts " .
3180 + "contained in the references above. If essential information is missing, politely " .
3181 + "guide the user to documentation or support.";
3182 + }
3183 +
3243 3184 return trim($content);
3244 3185 }
3245 -/**
3246 - * Find relevant content in Pinecone vector database
3247 - */
3248 3186 private function find_relevant_content_pinecone($user_embedding) {
3249 3187 $options = get_option('mxchat_pinecone_addon_options', array());
3250 3188 $api_key = $options['mxchat_pinecone_api_key'] ?? '';
3251 3189 $host = $options['mxchat_pinecone_host'] ?? '';
3252 -
3190 +
3253 3191 if (empty($host) || empty($api_key)) {
3254 - //error_log('Pinecone credentials not properly configured');
3255 3192 return '';
3256 3193 }
3257 -
3258 - // Get similarity threshold from WordPress settings
3259 - $similarity_threshold = ((int) get_option('mxchat_similarity_threshold', 80)) / 100;
3260 -
3194 +
3195 + // Get the similarity threshold from the main options
3196 + $main_options = get_option('mxchat_options', []);
3197 + $similarity_threshold = isset($main_options['similarity_threshold'])
3198 + ? ((int) $main_options['similarity_threshold']) / 100
3199 + : 0.75; // Default to 75%
3200 +
3261 3201 // Prepare the query request for Pinecone
3262 3202 $api_endpoint = "https://{$host}/query";
3263 -
3203 +
3264 3204 $request_body = array(
3265 3205 'vector' => $user_embedding,
3266 - 'topK' => 5,
3206 + 'topK' => 8, // Request more to ensure we have enough after filtering
3267 3207 'includeMetadata' => true,
3268 3208 'includeValues' => true
3269 3209 );
3270 -
3210 +
3271 3211 $response = wp_remote_post($api_endpoint, array(
3272 3212 'headers' => array(
3273 3213 'Api-Key' => $api_key,
3274 3214 'accept' => 'application/json',
@@ -3276,46 +3216,70 @@
3276 3216 ),
3277 3217 'body' => wp_json_encode($request_body),
3278 3218 'timeout' => 30
3279 3219 ));
3280 -
3220 +
3281 3221 if (is_wp_error($response)) {
3282 - //error_log('Pinecone query error: ' . $response->get_error_message());
3283 3222 return '';
3284 3223 }
3285 -
3224 +
3286 3225 $response_code = wp_remote_retrieve_response_code($response);
3287 3226 if ($response_code !== 200) {
3288 - //error_log('Pinecone API error: ' . wp_remote_retrieve_body($response));
3289 3227 return '';
3290 3228 }
3291 -
3229 +
3292 3230 $results = json_decode(wp_remote_retrieve_body($response), true);
3293 3231 if (empty($results['matches'])) {
3294 3232 return '';
3295 3233 }
3296 -
3234 +
3297 3235 // Initialize the final content
3298 3236 $content = '';
3299 -
3237 + $matches_used = 0;
3238 +
3300 3239 // Process each match
3301 - foreach ($results['matches'] as $match) {
3240 + foreach ($results['matches'] as $index => $match) {
3302 3241 // Skip if similarity is below threshold
3303 3242 if ($match['score'] < $similarity_threshold) {
3304 3243 continue;
3305 3244 }
3306 -
3307 - if (!empty($match['metadata']['text']) && !empty($match['metadata']['source_url'])) {
3308 - // Add content with citation
3309 - $content .= $match['metadata']['text'] . "\n";
3310 - $content .= "Source: " . $match['metadata']['source_url'] . "\n\n";
3245 +
3246 + // Limit to top 5 matches above threshold
3247 + if ($matches_used >= 5) {
3248 + break;
3311 3249 }
3250 +
3251 + if (!empty($match['metadata']['text'])) {
3252 + // Add content with minimal markup
3253 + $content .= "## Reference " . ($matches_used + 1) . " ##\n";
3254 + $content .= $match['metadata']['text'] . "\n\n";
3255 +
3256 + // Add source URL if available
3257 + if (!empty($match['metadata']['source_url'])) {
3258 + $content .= "URL: " . $match['metadata']['source_url'] . "\n\n";
3259 + }
3260 +
3261 + $matches_used++;
3262 + }
3312 3263 }
3313 -
3264 +
3265 + // Add response guidelines that encourage natural conversation but strict adherence to facts
3266 + if ($matches_used === 0) {
3267 + $content = "No reference information was found for this query.\n\n" .
3268 + "Respond naturally but inform the user that you don't have specific information " .
3269 + "about their query and suggest they check the documentation, speak to a live agent, " .
3270 + "or create a support ticket.";
3271 + } else {
3272 + $content .= "\n## Response Guidelines ##\n" .
3273 + "Answer naturally as a helpful product representative would, without phrases like " .
3274 + "'based on my knowledge base' or 'according to the information.' Only use facts " .
3275 + "contained in the references above. If essential information is missing, politely " .
3276 + "guide the user to documentation or support.";
3277 + }
3278 +
3314 3279 return trim($content);
3315 3280 }
3316 3281
3317 -
3318 3282 private function mxchat_find_relevant_products($user_embedding) {
3319 3283 //error_log('MXChat Vector Search: Starting product search...');
3320 3284
3321 3285 // Retrieve the add-on settings from the database
@@ -3333,9 +3297,8 @@
3333 3297 //error_log('MXChat Vector Search: Using WordPress database for products');
3334 3298 return $this->find_relevant_products_wordpress($user_embedding);
3335 3299 }
3336 3300 }
3337 -
3338 3301 private function find_relevant_products_wordpress($user_embedding) {
3339 3302 global $wpdb;
3340 3303 $system_prompt_table = $wpdb->prefix . 'mxchat_system_prompt_content';
3341 3304 $cache_key = 'mxchat_system_prompt_embeddings';
@@ -3409,10 +3372,8 @@
3409 3372 }
3410 3373
3411 3374 return trim($content);
3412 3375 }
3413 -
3414 -// Modified search function with correct filter syntax
3415 3376 private function find_relevant_products_pinecone($user_embedding) {
3416 3377 //error_log('Starting Pinecone product search...');
3417 3378
3418 3379 $options = get_option('mxchat_pinecone_addon_options', array());
@@ -3487,10 +3448,8 @@
3487 3448 }
3488 3449
3489 3450 return trim($content);
3490 3451 }
3491 -
3492 -
3493 3452 private function fetch_content_with_product_links($most_relevant_id) {
3494 3453 global $wpdb;
3495 3454 $system_prompt_table = $wpdb->prefix . 'mxchat_system_prompt_content';
3496 3455
@@ -3509,251 +3468,460 @@
3509 3468
3510 3469 return null;
3511 3470 }
3512 3471
3513 -// Function definition
3514 -private function mxchat_generate_response($relevant_content, $api_key, $xai_api_key, $claude_api_key, $deepseek_api_key, $conversation_history) {
3472 +private function mxchat_generate_response($relevant_content, $api_key, $xai_api_key, $claude_api_key, $deepseek_api_key, $gemini_api_key, $conversation_history) {
3515 3473 try {
3516 3474 if (!$relevant_content) {
3517 - return esc_html__("I'm sorry, I couldn't find relevant information on that topic.", 'mxchat');
3475 + return [
3476 + 'error' => esc_html__("I couldn't find relevant information on that topic.", 'mxchat'),
3477 + 'error_code' => 'no_relevant_content'
3478 + ];
3518 3479 }
3519 -
3480 +
3520 3481 // Ensure conversation_history is an array
3521 3482 if (!is_array($conversation_history)) {
3522 3483 $conversation_history = array();
3523 3484 }
3524 -
3485 +
3525 3486 // Get selected model with default fallback
3526 3487 $selected_model = isset($this->options['model']) ? $this->options['model'] : 'gpt-4o';
3527 -
3488 +
3528 3489 // Extract model prefix to determine the provider
3529 3490 $model_parts = explode('-', $selected_model);
3530 3491 $provider = strtolower($model_parts[0]);
3531 -
3492 +
3532 3493 // Handle model selection based on provider prefix
3533 3494 switch ($provider) {
3495 + case 'gemini':
3496 + if (empty($gemini_api_key)) {
3497 + return [
3498 + 'error' => esc_html__('Google Gemini API key is not configured', 'mxchat'),
3499 + 'error_code' => 'missing_gemini_api_key'
3500 + ];
3501 + }
3502 + $response = $this->mxchat_generate_response_gemini(
3503 + $selected_model,
3504 + $gemini_api_key,
3505 + $conversation_history,
3506 + $relevant_content
3507 + );
3508 + break;
3509 +
3534 3510 case 'claude':
3535 3511 if (empty($claude_api_key)) {
3536 - throw new Exception(esc_html__('Claude API key is not configured', 'mxchat'));
3512 + return [
3513 + 'error' => esc_html__('Claude API key is not configured', 'mxchat'),
3514 + 'error_code' => 'missing_claude_api_key'
3515 + ];
3537 3516 }
3538 - return $this->mxchat_generate_response_claude(
3517 + $response = $this->mxchat_generate_response_claude(
3539 3518 $selected_model,
3540 3519 $claude_api_key,
3541 3520 $conversation_history,
3542 3521 $relevant_content
3543 3522 );
3544 -
3523 + break;
3524 +
3545 3525 case 'grok':
3546 3526 if (empty($xai_api_key)) {
3547 - throw new Exception(esc_html__('X.AI API key is not configured', 'mxchat'));
3527 + return [
3528 + 'error' => esc_html__('X.AI API key is not configured', 'mxchat'),
3529 + 'error_code' => 'missing_xai_api_key'
3530 + ];
3548 3531 }
3549 - return $this->mxchat_generate_response_xai(
3532 + $response = $this->mxchat_generate_response_xai(
3550 3533 $selected_model,
3551 3534 $xai_api_key,
3552 3535 $conversation_history,
3553 3536 $relevant_content
3554 3537 );
3555 -
3538 + break;
3539 +
3556 3540 case 'deepseek':
3557 3541 if (empty($deepseek_api_key)) {
3558 - throw new Exception(esc_html__('DeepSeek API key is not configured', 'mxchat'));
3542 + return [
3543 + 'error' => esc_html__('DeepSeek API key is not configured', 'mxchat'),
3544 + 'error_code' => 'missing_deepseek_api_key'
3545 + ];
3559 3546 }
3560 - return $this->mxchat_generate_response_deepseek(
3547 + $response = $this->mxchat_generate_response_deepseek(
3561 3548 $selected_model,
3562 3549 $deepseek_api_key,
3563 3550 $conversation_history,
3564 3551 $relevant_content
3565 3552 );
3566 -
3553 + break;
3554 +
3567 3555 case 'gpt':
3568 3556 if (empty($api_key)) {
3569 - throw new Exception(esc_html__('OpenAI API key is not configured', 'mxchat'));
3557 + return [
3558 + 'error' => esc_html__('OpenAI API key is not configured', 'mxchat'),
3559 + 'error_code' => 'missing_openai_api_key'
3560 + ];
3570 3561 }
3571 - return $this->mxchat_generate_response_openai(
3562 + $response = $this->mxchat_generate_response_openai(
3572 3563 $selected_model,
3573 3564 $api_key,
3574 3565 $conversation_history,
3575 3566 $relevant_content
3576 3567 );
3577 -
3568 + break;
3569 +
3578 3570 default:
3579 3571 // Default to OpenAI for custom models or unrecognized prefixes
3580 3572 if (empty($api_key)) {
3581 - throw new Exception(esc_html__('OpenAI API key is not configured', 'mxchat'));
3573 + return [
3574 + 'error' => esc_html__('OpenAI API key is not configured', 'mxchat'),
3575 + 'error_code' => 'missing_openai_api_key'
3576 + ];
3582 3577 }
3583 - return $this->mxchat_generate_response_openai(
3578 + $response = $this->mxchat_generate_response_openai(
3584 3579 $selected_model,
3585 3580 $api_key,
3586 3581 $conversation_history,
3587 3582 $relevant_content
3588 3583 );
3584 + break;
3589 3585 }
3586 +
3587 + // Check if the response is an error array from the provider-specific function
3588 + if (is_array($response) && isset($response['error'])) {
3589 + return $response; // Pass through the error
3590 + }
3591 +
3592 + return $response;
3593 +
3590 3594 } catch (Exception $e) {
3591 3595 //error_log('MXChat Error: ' . $e->getMessage());
3592 - return sprintf(
3593 - esc_html__('An error occurred: %s', 'mxchat'),
3594 - esc_html($e->getMessage())
3595 - );
3596 + return [
3597 + 'error' => sprintf(esc_html__('An error occurred: %s', 'mxchat'), esc_html($e->getMessage())),
3598 + 'error_code' => 'system_exception',
3599 + 'exception_details' => $e->getMessage()
3600 + ];
3596 3601 }
3597 3602 }
3598 3603
3604 +private function mxchat_generate_response_deepseek($selected_model, $deepseek_api_key, $conversation_history, $relevant_content) {
3605 + try {
3606 + // Ensure conversation_history is an array
3607 + if (!is_array($conversation_history)) {
3608 + $conversation_history = array();
3609 + }
3599 3610
3600 -private function mxchat_generate_response_deepseek($selected_model, $deepseek_api_key, $conversation_history, $relevant_content) {
3601 - // Ensure conversation_history is an array
3602 - if (!is_array($conversation_history)) {
3603 - $conversation_history = array();
3604 - }
3611 + // Get system prompt instructions from options
3612 + $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : '';
3605 3613
3606 - // Get system prompt instructions from options
3607 - $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : '';
3614 + // Create a new array for the formatted conversation
3615 + $formatted_conversation = array();
3608 3616
3609 - // Create a new array for the formatted conversation
3610 - $formatted_conversation = array();
3617 + // Add system message first
3618 + $formatted_conversation[] = array(
3619 + 'role' => 'system',
3620 + 'content' => $system_prompt_instructions . " " . $relevant_content
3621 + );
3611 3622
3612 - // Add system message first
3613 - $formatted_conversation[] = array(
3614 - 'role' => 'system',
3615 - 'content' => $system_prompt_instructions . " " . $relevant_content
3616 - );
3623 + // Add the rest of the conversation history
3624 + foreach ($conversation_history as $message) {
3625 + if (is_array($message) && isset($message['role']) && isset($message['content'])) {
3626 + $role = $message['role'];
3617 3627
3618 - // Add the rest of the conversation history
3619 - foreach ($conversation_history as $message) {
3620 - if (is_array($message) && isset($message['role']) && isset($message['content'])) {
3621 - $role = $message['role'];
3628 + // Convert roles to supported format
3629 + if ($role === 'bot' || $role === 'agent') {
3630 + $role = 'assistant';
3631 + }
3632 + if (!in_array($role, ['system', 'assistant', 'user', 'function', 'tool'])) {
3633 + $role = 'user';
3634 + }
3622 3635
3623 - // Convert roles to supported format
3624 - if ($role === 'bot' || $role === 'agent') {
3625 - $role = 'assistant';
3636 + $formatted_conversation[] = array(
3637 + 'role' => $role,
3638 + 'content' => $message['content']
3639 + );
3626 3640 }
3627 - if (!in_array($role, ['system', 'assistant', 'user', 'function', 'tool'])) {
3628 - $role = 'user';
3629 - }
3641 + }
3630 3642
3631 - $formatted_conversation[] = array(
3632 - 'role' => $role,
3633 - 'content' => $message['content']
3634 - );
3643 + $body = json_encode([
3644 + 'model' => $selected_model,
3645 + 'messages' => $formatted_conversation,
3646 + 'temperature' => 0.8,
3647 + 'stream' => false
3648 + ]);
3649 +
3650 + $args = [
3651 + 'body' => $body,
3652 + 'headers' => [
3653 + 'Content-Type' => 'application/json',
3654 + 'Authorization' => 'Bearer ' . $deepseek_api_key,
3655 + ],
3656 + 'timeout' => 60,
3657 + 'redirection' => 5,
3658 + 'blocking' => true,
3659 + 'httpversion' => '1.0',
3660 + 'sslverify' => true,
3661 + ];
3662 +
3663 + $response = wp_remote_post('https://api.deepseek.com/v1/chat/completions', $args);
3664 +
3665 + if (is_wp_error($response)) {
3666 + $error_message = $response->get_error_message();
3667 + //error_log('DeepSeek API Error: ' . $error_message);
3668 + return [
3669 + 'error' => esc_html__('Connection error when contacting DeepSeek: ', 'mxchat') . esc_html($error_message),
3670 + 'error_code' => 'deepseek_connection_error',
3671 + 'provider' => 'deepseek'
3672 + ];
3635 3673 }
3636 - }
3637 3674
3638 - $body = json_encode([
3639 - 'model' => $selected_model,
3640 - 'messages' => $formatted_conversation,
3641 - 'temperature' => 0.8,
3642 - 'stream' => false
3643 - ]);
3675 + $status_code = wp_remote_retrieve_response_code($response);
3676 + if ($status_code !== 200) {
3677 + $response_body = wp_remote_retrieve_body($response);
3678 + $decoded_response = json_decode($response_body, true);
3644 3679
3645 - $args = [
3646 - 'body' => $body,
3647 - 'headers' => [
3648 - 'Content-Type' => 'application/json',
3649 - 'Authorization' => 'Bearer ' . $deepseek_api_key,
3650 - ],
3651 - 'timeout' => 60,
3652 - 'redirection' => 5,
3653 - 'blocking' => true,
3654 - 'httpversion' => '1.0',
3655 - 'sslverify' => true,
3656 - ];
3680 + $error_message = isset($decoded_response['error']['message'])
3681 + ? $decoded_response['error']['message']
3682 + : 'HTTP Error ' . $status_code;
3683 +
3684 + $error_type = isset($decoded_response['error']['type'])
3685 + ? $decoded_response['error']['type']
3686 + : 'unknown';
3687 +
3688 + //error_log('DeepSeek API HTTP Error: ' . $status_code . ' - ' . $error_message);
3657 3689
3658 - $response = wp_remote_post('https://api.deepseek.com/v1/chat/completions', $args);
3690 + // Handle specific error types
3691 + switch ($status_code) {
3692 + case 401:
3693 + return [
3694 + 'error' => esc_html__('Authentication failed with DeepSeek. Please check your API key.', 'mxchat'),
3695 + 'error_code' => 'deepseek_auth_error',
3696 + 'provider' => 'deepseek'
3697 + ];
3698 +
3699 + case 400:
3700 + if (strpos($error_message, 'API key') !== false) {
3701 + return [
3702 + 'error' => esc_html__('Invalid DeepSeek API key. Please check your API key configuration.', 'mxchat'),
3703 + 'error_code' => 'deepseek_invalid_api_key',
3704 + 'provider' => 'deepseek'
3705 + ];
3706 + }
3707 + break;
3708 +
3709 + case 429:
3710 + if (strpos($error_message, 'quota') !== false) {
3711 + return [
3712 + 'error' => esc_html__('DeepSeek API quota exceeded. Please check your billing details.', 'mxchat'),
3713 + 'error_code' => 'deepseek_quota_exceeded',
3714 + 'provider' => 'deepseek'
3715 + ];
3716 + } else {
3717 + return [
3718 + 'error' => esc_html__('DeepSeek rate limit exceeded. Please try again later.', 'mxchat'),
3719 + 'error_code' => 'deepseek_rate_limit',
3720 + 'provider' => 'deepseek'
3721 + ];
3722 + }
3723 +
3724 + case 500:
3725 + case 502:
3726 + case 503:
3727 + case 504:
3728 + return [
3729 + 'error' => esc_html__('DeepSeek service is currently unavailable. Please try again later.', 'mxchat'),
3730 + 'error_code' => 'deepseek_service_unavailable',
3731 + 'provider' => 'deepseek'
3732 + ];
3733 + }
3659 3734
3660 - if (is_wp_error($response)) {
3661 - //error_log('DeepSeek API Error: ' . $response->get_error_message());
3662 - return "Sorry, there was an error processing your request.";
3663 - }
3735 + // Generic error fallback
3736 + return [
3737 + 'error' => esc_html__('DeepSeek API error: ', 'mxchat') . esc_html($error_message),
3738 + 'error_code' => 'deepseek_api_error',
3739 + 'provider' => 'deepseek',
3740 + 'status_code' => $status_code
3741 + ];
3742 + }
3664 3743
3665 - $response_body = wp_remote_retrieve_body($response);
3666 - $decoded_response = json_decode($response_body, true);
3744 + $response_body = wp_remote_retrieve_body($response);
3745 + $decoded_response = json_decode($response_body, true);
3667 3746
3668 - if (isset($decoded_response['choices'][0]['message']['content'])) {
3669 - return trim($decoded_response['choices'][0]['message']['content']);
3670 - } else {
3671 - //error_log('DeepSeek API Response Format Error: ' . print_r($decoded_response, true));
3672 - return "Sorry, I couldn't process that request.";
3747 + if (isset($decoded_response['choices'][0]['message']['content'])) {
3748 + return trim($decoded_response['choices'][0]['message']['content']);
3749 + } else {
3750 + //error_log('DeepSeek API Response Format Error: ' . print_r($decoded_response, true));
3751 + return [
3752 + 'error' => esc_html__('Unexpected response format from DeepSeek.', 'mxchat'),
3753 + 'error_code' => 'deepseek_response_format_error',
3754 + 'provider' => 'deepseek'
3755 + ];
3756 + }
3757 + } catch (Exception $e) {
3758 + //error_log('DeepSeek Exception: ' . $e->getMessage());
3759 + return [
3760 + 'error' => esc_html__('System error when processing DeepSeek request: ', 'mxchat') . esc_html($e->getMessage()),
3761 + 'error_code' => 'deepseek_exception',
3762 + 'provider' => 'deepseek'
3763 + ];
3673 3764 }
3674 3765 }
3675 3766
3676 3767 private function mxchat_generate_response_openai($selected_model, $api_key, $conversation_history, $relevant_content) {
3677 - // Ensure conversation_history is an array
3678 - if (!is_array($conversation_history)) {
3679 - $conversation_history = array();
3680 - }
3768 + try {
3769 + // Ensure conversation_history is an array
3770 + if (!is_array($conversation_history)) {
3771 + $conversation_history = array();
3772 + }
3681 3773
3682 - // Get system prompt instructions from options
3683 - $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : '';
3774 + // Get system prompt instructions from options
3775 + $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : '';
3684 3776
3685 - // Create a new array for the formatted conversation
3686 - $formatted_conversation = array();
3777 + // Create a new array for the formatted conversation
3778 + $formatted_conversation = array();
3687 3779
3688 - // Add system message first
3689 - $formatted_conversation[] = array(
3690 - 'role' => 'system',
3691 - 'content' => $system_prompt_instructions . " " . $relevant_content
3692 - );
3780 + // Add system message first
3781 + $formatted_conversation[] = array(
3782 + 'role' => 'system',
3783 + 'content' => $system_prompt_instructions . " " . $relevant_content
3784 + );
3693 3785
3694 - // Add the rest of the conversation history
3695 - foreach ($conversation_history as $message) {
3696 - if (is_array($message) && isset($message['role']) && isset($message['content'])) {
3697 - $role = $message['role'];
3786 + // Add the rest of the conversation history
3787 + foreach ($conversation_history as $message) {
3788 + if (is_array($message) && isset($message['role']) && isset($message['content'])) {
3789 + $role = $message['role'];
3698 3790
3699 - // Convert roles to supported format
3700 - if ($role === 'bot' || $role === 'agent') {
3701 - $role = 'assistant';
3791 + // Convert roles to supported format
3792 + if ($role === 'bot' || $role === 'agent') {
3793 + $role = 'assistant';
3794 + }
3795 + if (!in_array($role, ['system', 'assistant', 'user', 'function', 'tool'])) {
3796 + $role = 'user';
3797 + }
3798 +
3799 + $formatted_conversation[] = array(
3800 + 'role' => $role,
3801 + 'content' => $message['content']
3802 + );
3702 3803 }
3703 - if (!in_array($role, ['system', 'assistant', 'user', 'function', 'tool'])) {
3704 - $role = 'user';
3705 - }
3804 + }
3706 3805
3707 - $formatted_conversation[] = array(
3708 - 'role' => $role,
3709 - 'content' => $message['content']
3710 - );
3711 - }
3712 - }
3806 + $body = json_encode([
3807 + 'model' => $selected_model,
3808 + 'messages' => $formatted_conversation,
3809 + 'temperature' => 0.8,
3810 + 'stream' => false
3811 + ]);
3713 3812
3714 - $body = json_encode([
3715 - 'model' => $selected_model,
3716 - 'messages' => $formatted_conversation,
3717 - 'temperature' => 0.8,
3718 - 'stream' => false
3719 - ]);
3813 + $args = [
3814 + 'body' => $body,
3815 + 'headers' => [
3816 + 'Content-Type' => 'application/json',
3817 + 'Authorization' => 'Bearer ' . $api_key,
3818 + ],
3819 + 'timeout' => 60,
3820 + 'redirection' => 5,
3821 + 'blocking' => true,
3822 + 'httpversion' => '1.0',
3823 + 'sslverify' => true,
3824 + ];
3720 3825
3721 - $args = [
3722 - 'body' => $body,
3723 - 'headers' => [
3724 - 'Content-Type' => 'application/json',
3725 - 'Authorization' => 'Bearer ' . $api_key,
3726 - ],
3727 - 'timeout' => 60,
3728 - 'redirection' => 5,
3729 - 'blocking' => true,
3730 - 'httpversion' => '1.0',
3731 - 'sslverify' => true,
3732 - ];
3826 + $response = wp_remote_post('https://api.openai.com/v1/chat/completions', $args);
3733 3827
3734 - $response = wp_remote_post('https://api.openai.com/v1/chat/completions', $args);
3828 + if (is_wp_error($response)) {
3829 + $error_message = $response->get_error_message();
3830 + //error_log('OpenAI API Error: ' . $error_message);
3831 + return [
3832 + 'error' => esc_html__('Connection error when contacting OpenAI: ', 'mxchat') . esc_html($error_message),
3833 + 'error_code' => 'openai_connection_error',
3834 + 'provider' => 'openai'
3835 + ];
3836 + }
3735 3837
3736 - if (is_wp_error($response)) {
3737 - //error_log('OpenAI API Error: ' . $response->get_error_message());
3738 - return "Sorry, there was an error processing your request.";
3739 - }
3838 + $status_code = wp_remote_retrieve_response_code($response);
3839 + if ($status_code !== 200) {
3840 + $response_body = wp_remote_retrieve_body($response);
3841 + $decoded_response = json_decode($response_body, true);
3842 +
3843 + $error_message = isset($decoded_response['error']['message'])
3844 + ? $decoded_response['error']['message']
3845 + : 'HTTP Error ' . $status_code;
3846 +
3847 + $error_type = isset($decoded_response['error']['type'])
3848 + ? $decoded_response['error']['type']
3849 + : 'unknown';
3850 +
3851 + //error_log('OpenAI API HTTP Error: ' . $status_code . ' - ' . $error_message);
3852 +
3853 + // Handle specific error types
3854 + switch ($error_type) {
3855 + case 'invalid_request_error':
3856 + if (strpos($error_message, 'API key') !== false) {
3857 + return [
3858 + 'error' => esc_html__('Invalid OpenAI API key. Please check your API key configuration.', 'mxchat'),
3859 + 'error_code' => 'openai_invalid_api_key',
3860 + 'provider' => 'openai'
3861 + ];
3862 + }
3863 + break;
3864 +
3865 + case 'authentication_error':
3866 + return [
3867 + 'error' => esc_html__('Authentication failed with OpenAI. Please check your API key.', 'mxchat'),
3868 + 'error_code' => 'openai_auth_error',
3869 + 'provider' => 'openai'
3870 + ];
3871 +
3872 + case 'rate_limit_exceeded':
3873 + return [
3874 + 'error' => esc_html__('OpenAI rate limit exceeded. Please try again later.', 'mxchat'),
3875 + 'error_code' => 'openai_rate_limit',
3876 + 'provider' => 'openai'
3877 + ];
3878 +
3879 + case 'quota_exceeded':
3880 + return [
3881 + 'error' => esc_html__('OpenAI API quota exceeded. Please check your billing details.', 'mxchat'),
3882 + 'error_code' => 'openai_quota_exceeded',
3883 + 'provider' => 'openai'
3884 + ];
3885 + }
3886 +
3887 + // Generic error fallback
3888 + return [
3889 + 'error' => esc_html__('OpenAI API error: ', 'mxchat') . esc_html($error_message),
3890 + 'error_code' => 'openai_api_error',
3891 + 'provider' => 'openai',
3892 + 'status_code' => $status_code
3893 + ];
3894 + }
3740 3895
3741 - $response_body = wp_remote_retrieve_body($response);
3742 - $decoded_response = json_decode($response_body, true);
3896 + $response_body = wp_remote_retrieve_body($response);
3897 + $decoded_response = json_decode($response_body, true);
3743 3898
3744 - if (isset($decoded_response['choices'][0]['message']['content'])) {
3745 - return trim($decoded_response['choices'][0]['message']['content']);
3746 - } else {
3747 - //error_log('OpenAI API Response Format Error: ' . print_r($decoded_response, true));
3748 - return "Sorry, I couldn't process that request.";
3899 + if (isset($decoded_response['choices'][0]['message']['content'])) {
3900 + return trim($decoded_response['choices'][0]['message']['content']);
3901 + } else {
3902 + //error_log('OpenAI API Response Format Error: ' . print_r($decoded_response, true));
3903 + return [
3904 + 'error' => esc_html__('Unexpected response format from OpenAI.', 'mxchat'),
3905 + 'error_code' => 'openai_response_format_error',
3906 + 'provider' => 'openai'
3907 + ];
3908 + }
3909 + } catch (Exception $e) {
3910 + //error_log('OpenAI Exception: ' . $e->getMessage());
3911 + return [
3912 + 'error' => esc_html__('System error when processing OpenAI request: ', 'mxchat') . esc_html($e->getMessage()),
3913 + 'error_code' => 'openai_exception',
3914 + 'provider' => 'openai'
3915 + ];
3749 3916 }
3750 3917 }
3751 3918 private function mxchat_generate_response_xai($selected_model, $xai_api_key, $conversation_history, $relevant_content) {
3752 - // Get system prompt instructions from options
3753 - $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : '';
3919 + try {
3920 + // Get system prompt instructions from options
3921 + $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : '';
3754 3922
3755 - // Add system prompt to relevant content
3923 + // Add system prompt to relevant content
3756 3924 $content_with_instructions = $system_prompt_instructions . " " . $relevant_content;
3757 3925
3758 3926 // Prepend system instructions to the conversation history
3759 3927 array_unshift($conversation_history, [
@@ -3778,9 +3946,8 @@
3778 3946 $message['role'] = 'user'; // Default to 'user'
3779 3947 }
3780 3948 }
3781 3949
3782 -
3783 3950 // Build the request body
3784 3951 $body = json_encode([
3785 3952 'model' => $selected_model,
3786 3953 'messages' => $conversation_history,
@@ -3806,20 +3973,138 @@
3806 3973 $response = wp_remote_post('https://api.x.ai/v1/chat/completions', $args);
3807 3974
3808 3975 // Process the response
3809 3976 if (is_wp_error($response)) {
3810 - return "Sorry, there was an error processing your request.";
3977 + $error_message = $response->get_error_message();
3978 + //error_log('X.AI API Error: ' . $error_message);
3979 + return [
3980 + 'error' => esc_html__('Connection error when contacting X.AI: ', 'mxchat') . esc_html($error_message),
3981 + 'error_code' => 'xai_connection_error',
3982 + 'provider' => 'xai'
3983 + ];
3811 3984 }
3812 3985
3813 - $response_body = json_decode(wp_remote_retrieve_body($response), true);
3986 + $status_code = wp_remote_retrieve_response_code($response);
3987 + if ($status_code !== 200) {
3988 + $response_body = wp_remote_retrieve_body($response);
3989 + $decoded_response = json_decode($response_body, true);
3814 3990
3815 - if (isset($response_body['choices'][0]['message']['content'])) {
3816 - return trim($response_body['choices'][0]['message']['content']);
3991 + // Log the full response for debugging
3992 + //error_log('X.AI Error Response: ' . print_r($decoded_response, true));
3993 +
3994 + // Extract error message from X.AI's specific format
3995 + $error_message = '';
3996 +
3997 + // Check for direct error string (as seen in your logs)
3998 + if (isset($decoded_response['error']) && is_string($decoded_response['error'])) {
3999 + $error_message = $decoded_response['error'];
4000 + }
4001 + // Check for nested error object (OpenAI style)
4002 + elseif (isset($decoded_response['error']['message'])) {
4003 + $error_message = $decoded_response['error']['message'];
4004 + }
4005 + // Check for top-level message
4006 + elseif (isset($decoded_response['message'])) {
4007 + $error_message = $decoded_response['message'];
4008 + }
4009 + // Fallback
4010 + else {
4011 + $error_message = 'HTTP Error ' . $status_code;
4012 + }
4013 +
4014 + //error_log('X.AI API HTTP Error: ' . $status_code . ' - ' . $error_message);
4015 +
4016 + // Check for API key errors using string matching
4017 + if (stripos($error_message, 'api key') !== false ||
4018 + stripos($error_message, 'incorrect api key') !== false ||
4019 + stripos($error_message, 'invalid api key') !== false) {
4020 + return [
4021 + 'error' => esc_html__('Invalid X.AI API key. Please check your API key configuration.', 'mxchat'),
4022 + 'error_code' => 'xai_invalid_api_key',
4023 + 'provider' => 'xai'
4024 + ];
4025 + }
4026 +
4027 + // Authentication errors
4028 + if ($status_code === 401 || $status_code === 403 ||
4029 + stripos($error_message, 'auth') !== false) {
4030 + return [
4031 + 'error' => esc_html__('Authentication failed with X.AI. Please check your API key.', 'mxchat'),
4032 + 'error_code' => 'xai_auth_error',
4033 + 'provider' => 'xai'
4034 + ];
4035 + }
4036 +
4037 + // Model errors
4038 + if (stripos($error_message, 'model') !== false) {
4039 + return [
4040 + 'error' => esc_html__('Invalid model specified for X.AI. Please check your model configuration.', 'mxchat'),
4041 + 'error_code' => 'xai_invalid_model',
4042 + 'provider' => 'xai'
4043 + ];
4044 + }
4045 +
4046 + // Rate limit errors
4047 + if ($status_code === 429 ||
4048 + stripos($error_message, 'rate') !== false ||
4049 + stripos($error_message, 'limit') !== false) {
4050 + return [
4051 + 'error' => esc_html__('X.AI rate limit exceeded. Please try again later.', 'mxchat'),
4052 + 'error_code' => 'xai_rate_limit',
4053 + 'provider' => 'xai'
4054 + ];
4055 + }
4056 +
4057 + // Quota errors
4058 + if (stripos($error_message, 'quota') !== false ||
4059 + stripos($error_message, 'billing') !== false) {
4060 + return [
4061 + 'error' => esc_html__('X.AI API quota exceeded. Please check your billing details.', 'mxchat'),
4062 + 'error_code' => 'xai_quota_exceeded',
4063 + 'provider' => 'xai'
4064 + ];
4065 + }
4066 +
4067 + // Server errors
4068 + if ($status_code >= 500) {
4069 + return [
4070 + 'error' => esc_html__('X.AI service is currently unavailable. Please try again later.', 'mxchat'),
4071 + 'error_code' => 'xai_service_unavailable',
4072 + 'provider' => 'xai'
4073 + ];
4074 + }
4075 +
4076 + // Generic error fallback with the actual error message
4077 + return [
4078 + 'error' => esc_html__('X.AI API error: ', 'mxchat') . esc_html($error_message),
4079 + 'error_code' => 'xai_api_error',
4080 + 'provider' => 'xai',
4081 + 'status_code' => $status_code
4082 + ];
4083 + }
4084 +
4085 + $response_body = wp_remote_retrieve_body($response);
4086 + $decoded_response = json_decode($response_body, true);
4087 +
4088 + if (isset($decoded_response['choices'][0]['message']['content'])) {
4089 + return trim($decoded_response['choices'][0]['message']['content']);
3817 4090 } else {
3818 - return "Sorry, I couldn't process that request.";
4091 + //error_log('X.AI API Response Format Error: ' . print_r($decoded_response, true));
4092 + return [
4093 + 'error' => esc_html__('Unexpected response format from X.AI.', 'mxchat'),
4094 + 'error_code' => 'xai_response_format_error',
4095 + 'provider' => 'xai'
4096 + ];
3819 4097 }
4098 +} catch (Exception $e) {
4099 + //error_log('X.AI Exception: ' . $e->getMessage());
4100 + return [
4101 + 'error' => esc_html__('System error when processing X.AI request: ', 'mxchat') . esc_html($e->getMessage()),
4102 + 'error_code' => 'xai_exception',
4103 + 'provider' => 'xai'
4104 + ];
3820 4105 }
3821 -
4106 +}
3822 4107 private function mxchat_generate_response_claude($selected_model, $claude_api_key, $conversation_history, $relevant_content) {
3823 4108 // Get system prompt instructions from options
3824 4109 $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : '';
3825 4110
@@ -3918,8 +4203,151 @@
3918 4203 // Log unexpected response format
3919 4204 //error_log("Claude API unexpected response format: " . print_r($response_body, true));
3920 4205 return "Sorry, I received an unexpected response format from the API.";
3921 4206 }
4207 +private function mxchat_generate_response_gemini($selected_model, $gemini_api_key, $conversation_history, $relevant_content) {
4208 + // Get system prompt instructions from options
4209 + $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : '';
4210 +
4211 + // Add system prompt to relevant content
4212 + $content_with_instructions = $system_prompt_instructions . " " . $relevant_content;
4213 +
4214 + // Format messages for Gemini API
4215 + $formatted_messages = [];
4216 +
4217 + // Add system message as the first user message with role prefix
4218 + // Note: Gemini doesn't have a dedicated system role, so we use a prefixed user message
4219 + $formatted_messages[] = [
4220 + 'role' => 'user',
4221 + 'parts' => [
4222 + ['text' => "[System Instructions] " . $content_with_instructions]
4223 + ]
4224 + ];
4225 +
4226 + // Add model response to acknowledge system instructions
4227 + $formatted_messages[] = [
4228 + 'role' => 'model',
4229 + 'parts' => [
4230 + ['text' => "I understand and will follow these instructions."]
4231 + ]
4232 + ];
4233 +
4234 + // Process the rest of the conversation history
4235 + $current_role = null;
4236 + $current_parts = [];
4237 +
4238 + foreach ($conversation_history as $message) {
4239 + // Skip the first system message as we already handled it
4240 + if ($message['role'] === 'system') {
4241 + continue;
4242 + }
4243 +
4244 + // Map roles to Gemini format
4245 + $gemini_role = '';
4246 + if ($message['role'] === 'user') {
4247 + $gemini_role = 'user';
4248 + } else if (in_array($message['role'], ['assistant', 'bot', 'agent'])) {
4249 + $gemini_role = 'model';
4250 + } else {
4251 + // Skip unsupported roles
4252 + continue;
4253 + }
4254 +
4255 + // If we have a new role, add the previous message
4256 + if ($current_role !== null && $current_role !== $gemini_role && !empty($current_parts)) {
4257 + $formatted_messages[] = [
4258 + 'role' => $current_role,
4259 + 'parts' => $current_parts
4260 + ];
4261 + $current_parts = [];
4262 + }
4263 +
4264 + // Set current role and add text to parts
4265 + $current_role = $gemini_role;
4266 + $current_parts[] = ['text' => $message['content']];
4267 + }
4268 +
4269 + // Add the last message if there's content
4270 + if ($current_role !== null && !empty($current_parts)) {
4271 + $formatted_messages[] = [
4272 + 'role' => $current_role,
4273 + 'parts' => $current_parts
4274 + ];
4275 + }
4276 +
4277 + // Build the request body
4278 + $body = json_encode([
4279 + 'contents' => $formatted_messages,
4280 + 'generationConfig' => [
4281 + 'temperature' => 0.7,
4282 + 'topP' => 0.95,
4283 + 'topK' => 40,
4284 + 'maxOutputTokens' => 8192,
4285 + ],
4286 + 'safetySettings' => [
4287 + [
4288 + 'category' => 'HARM_CATEGORY_HARASSMENT',
4289 + 'threshold' => 'BLOCK_MEDIUM_AND_ABOVE'
4290 + ],
4291 + [
4292 + 'category' => 'HARM_CATEGORY_HATE_SPEECH',
4293 + 'threshold' => 'BLOCK_MEDIUM_AND_ABOVE'
4294 + ],
4295 + [
4296 + 'category' => 'HARM_CATEGORY_SEXUALLY_EXPLICIT',
4297 + 'threshold' => 'BLOCK_MEDIUM_AND_ABOVE'
4298 + ],
4299 + [
4300 + 'category' => 'HARM_CATEGORY_DANGEROUS_CONTENT',
4301 + 'threshold' => 'BLOCK_MEDIUM_AND_ABOVE'
4302 + ]
4303 + ]
4304 + ]);
4305 +
4306 + // Prepare the API endpoint
4307 + $api_endpoint = 'https://generativelanguage.googleapis.com/v1/models/' . $selected_model . ':generateContent?key=' . $gemini_api_key;
4308 +
4309 + // Set up the API request
4310 + $args = [
4311 + 'body' => $body,
4312 + 'headers' => [
4313 + 'Content-Type' => 'application/json',
4314 + ],
4315 + 'timeout' => 60,
4316 + 'redirection' => 5,
4317 + 'blocking' => true,
4318 + 'httpversion' => '1.0',
4319 + 'sslverify' => true,
4320 + ];
4321 +
4322 + // Make the API request
4323 + $response = wp_remote_post($api_endpoint, $args);
4324 +
4325 + // Process the response
4326 + if (is_wp_error($response)) {
4327 + return "Sorry, there was an error processing your request: " . $response->get_error_message();
4328 + }
4329 +
4330 + $response_body = json_decode(wp_remote_retrieve_body($response), true);
4331 +
4332 + // Handle potential errors in the response
4333 + if (isset($response_body['error'])) {
4334 + //error_log('Gemini API Error: ' . json_encode($response_body['error']));
4335 + return "Sorry, there was an error with the Gemini API: " .
4336 + (isset($response_body['error']['message']) ? $response_body['error']['message'] : 'Unknown error');
4337 + }
4338 +
4339 + // Extract the response text
4340 + if (isset($response_body['candidates'][0]['content']['parts'][0]['text'])) {
4341 + return trim($response_body['candidates'][0]['content']['parts'][0]['text']);
4342 + } else {
4343 + //error_log('Unexpected Gemini API response format: ' . json_encode($response_body));
4344 + return "Sorry, I couldn't process that request. The response format was unexpected.";
4345 + }
4346 +}
4347 +
4348 +
4349 +
3922 4350 public function mxchat_dismiss_pre_chat_message() {
3923 4351 // Get and sanitize the user identifier
3924 4352 $user_id = $this->mxchat_get_user_identifier();
3925 4353 $user_id = sanitize_key($user_id);
@@ -3975,10 +4403,10 @@
3975 4403 }
3976 4404
3977 4405 public function mxchat_enqueue_scripts_styles() {
3978 4406 // Define version numbers for the styles and scripts
3979 - $chat_style_version = '2.0.3'; // Replace with your actual version
3980 - $chat_script_version = '2.0.3'; // Replace with your actual version
4407 + $chat_style_version = '2.2.1';
4408 + $chat_script_version = '2.2.1';
3981 4409
3982 4410 // Enqueue the script
3983 4411 wp_enqueue_script(
3984 4412 'mxchat-chat-js',
@@ -4036,30 +4464,72 @@
4036 4464 wp_localize_script('mxchat-chat-js', 'mxchatChat', $style_settings);
4037 4465 }
4038 4466
4039 4467
4468 +// Modify the mxchat_reset_rate_limits function to handle different timeframes
4040 4469 public function mxchat_reset_rate_limits() {
4041 - global $wpdb;
4042 -
4043 - // Define a cache key pattern for rate limits
4044 - $cache_key_pattern = 'mxchat_chat_limit_%';
4045 -
4046 - // Retrieve all option names matching the pattern
4047 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery
4048 - $option_names = $wpdb->get_col("SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE 'mxchat_chat_limit_%'");
4049 -
4050 - // db call ok; no-cache ok
4051 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- db call ok
4052 - $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE 'mxchat_chat_limit_%'");
4053 -
4054 - // Clear the relevant cache entries
4055 - foreach ($option_names as $option_name) {
4470 + global $wpdb;
4471 + $all_options = get_option('mxchat_options', []);
4472 + $current_time = time();
4473 +
4474 + // Get all rate limit options
4475 + $option_names = $wpdb->get_col("SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE 'mxchat_chat_limit_%'");
4476 +
4477 + foreach ($option_names as $option_name) {
4478 + // Parse the option name to extract role and user ID
4479 + // Format: mxchat_chat_limit_ROLE_USERID or mxchat_chat_limit_logged_out_IP
4480 + $parts = explode('_', $option_name);
4481 +
4482 + // Skip if the option name doesn't match our expected format
4483 + if (count($parts) < 4) {
4484 + continue;
4485 + }
4486 +
4487 + // Extract role (may be multiple parts like 'shop_manager')
4488 + $role_parts = array_slice($parts, 3, -1); // Get all parts between 'mxchat_chat_limit_' and the last part (user ID)
4489 + $role = implode('_', $role_parts);
4490 +
4491 + // Skip if role doesn't exist in our settings
4492 + if (!isset($all_options['rate_limits'][$role])) {
4493 + continue;
4494 + }
4495 +
4496 + $timeframe = $all_options['rate_limits'][$role]['timeframe'];
4497 + $limit_data = get_option($option_name);
4498 +
4499 + if (!$limit_data || !is_array($limit_data) || !isset($limit_data['timestamp'])) {
4500 + continue;
4501 + }
4502 +
4503 + $timestamp = $limit_data['timestamp'];
4504 + $should_reset = false;
4505 +
4506 + // Determine if we should reset based on the timeframe
4507 + switch ($timeframe) {
4508 + case 'hourly':
4509 + $should_reset = ($current_time - $timestamp) >= 3600; // 1 hour
4510 + break;
4511 + case 'daily':
4512 + $should_reset = ($current_time - $timestamp) >= 86400; // 24 hours
4513 + break;
4514 + case 'weekly':
4515 + $should_reset = ($current_time - $timestamp) >= 604800; // 7 days
4516 + break;
4517 + case 'monthly':
4518 + $should_reset = ($current_time - $timestamp) >= 2592000; // 30 days
4519 + break;
4520 + }
4521 +
4522 + // Reset the counter if the timeframe has passed
4523 + if ($should_reset) {
4524 + delete_option($option_name);
4056 4525 wp_cache_delete($option_name, 'options');
4057 4526 }
4058 -
4059 - // Optionally, clear a general cache if you have one
4060 - wp_cache_delete('mxchat_all_chat_limits', 'options');
4061 4527 }
4528 +
4529 + // Clean up any orphaned entries
4530 + wp_cache_delete('mxchat_all_chat_limits', 'options');
4531 +}
4062 4532
4063 4533 private function mxchat_fetch_woocommerce_products() {
4064 4534 // Ensure WooCommerce is active
4065 4535 if (!class_exists('WooCommerce')) {
@@ -4094,8 +4564,177 @@
4094 4564 );
4095 4565 }
4096 4566
4097 4567 return $product_data;
4568 +}
4569 +
4570 +/**
4571 + * Check if the current user has exceeded their rate limit based on role
4572 + *
4573 + * @return true|array True if limit not exceeded, or array with error message if exceeded
4574 + */
4575 +public function check_rate_limit() {
4576 + $all_options = get_option('mxchat_options', []);
4577 + //error_log('MXChat Rate Limit: Starting check');
4578 + //error_log('MXChat Rate Limit: Options: ' . print_r($all_options, true));
4579 +
4580 + // Determine user role or if logged out
4581 + if (is_user_logged_in()) {
4582 + $user = wp_get_current_user();
4583 + $user_id = $user->ID;
4584 +
4585 + // Get the user's primary role using reset() to safely get the first element
4586 + $user_roles = $user->roles;
4587 +
4588 + // Safely get the first role regardless of array key structure
4589 + if (!empty($user_roles) && is_array($user_roles)) {
4590 + $role = reset($user_roles); // This safely gets the first element regardless of key
4591 + } else {
4592 + $role = 'subscriber'; // Default to subscriber if no role found
4593 + }
4594 +
4595 + //error_log('MXChat Rate Limit: User ID: ' . $user_id . ', Role: ' . $role);
4596 + } else {
4597 + $role = 'logged_out';
4598 + // Use IP address for non-logged-in users
4599 + $user_id = $this->get_client_ip();
4600 + //error_log('MXChat Rate Limit: Logged out user IP: ' . $user_id);
4601 + }
4602 +
4603 + // Check if rate limits are configured for this role
4604 + if (!isset($all_options['rate_limits'][$role])) {
4605 + //error_log('MXChat Rate Limit: No rate limit configured for role: ' . $role);
4606 + return true; // No limit set for this role
4607 + }
4608 +
4609 + $limit = $all_options['rate_limits'][$role]['limit'];
4610 + //error_log('MXChat Rate Limit: Limit for role ' . $role . ': ' . $limit);
4611 +
4612 + // If unlimited, return true immediately
4613 + if ($limit === 'unlimited') {
4614 + //error_log('MXChat Rate Limit: Unlimited setting, no limit applied');
4615 + return true;
4616 + }
4617 +
4618 + // Get the option name for this user/role
4619 + $option_name = 'mxchat_chat_limit_' . $role . '_' . $user_id;
4620 + //error_log('MXChat Rate Limit: Option name: ' . $option_name);
4621 +
4622 + // Get the counter data
4623 + $limit_data = get_option($option_name, ['count' => 0, 'timestamp' => time()]);
4624 + //error_log('MXChat Rate Limit: Current limit data: ' . print_r($limit_data, true));
4625 +
4626 + // If first request or counter reset needed, set the initial timestamp
4627 + if ($limit_data['count'] === 0) {
4628 + $limit_data['timestamp'] = time();
4629 + update_option($option_name, $limit_data);
4630 + //error_log('MXChat Rate Limit: First request, initialized timestamp');
4631 + }
4632 +
4633 + // Get the timeframe
4634 + $timeframe = isset($all_options['rate_limits'][$role]['timeframe']) ?
4635 + $all_options['rate_limits'][$role]['timeframe'] : 'daily';
4636 + //error_log('MXChat Rate Limit: Timeframe: ' . $timeframe);
4637 +
4638 + // Check if the counter needs to be reset based on timeframe
4639 + $current_time = time();
4640 + $timestamp = $limit_data['timestamp'];
4641 + $should_reset = false;
4642 +
4643 + switch ($timeframe) {
4644 + case 'hourly':
4645 + $should_reset = ($current_time - $timestamp) >= 3600; // 1 hour
4646 + break;
4647 + case 'daily':
4648 + $should_reset = ($current_time - $timestamp) >= 86400; // 24 hours
4649 + break;
4650 + case 'weekly':
4651 + $should_reset = ($current_time - $timestamp) >= 604800; // 7 days
4652 + break;
4653 + case 'monthly':
4654 + $should_reset = ($current_time - $timestamp) >= 2592000; // 30 days
4655 + break;
4656 + }
4657 +
4658 + //error_log('MXChat Rate Limit: Current time: ' . $current_time . ', Last timestamp: ' . $timestamp);
4659 + //error_log('MXChat Rate Limit: Time elapsed: ' . ($current_time - $timestamp) . ' seconds');
4660 + //error_log('MXChat Rate Limit: Should reset: ' . ($should_reset ? 'Yes' : 'No'));
4661 +
4662 + // Reset the counter if the timeframe has passed
4663 + if ($should_reset) {
4664 + $limit_data = ['count' => 0, 'timestamp' => $current_time];
4665 + update_option($option_name, $limit_data);
4666 + //error_log('MXChat Rate Limit: Reset counter to 0');
4667 + }
4668 +
4669 + // Check if user has exceeded their limit
4670 + if ($limit_data['count'] >= intval($limit)) {
4671 + // Get the custom message for this role
4672 + $message = !empty($all_options['rate_limits'][$role]['message'])
4673 + ? $all_options['rate_limits'][$role]['message']
4674 + : __('Rate limit exceeded. Please try again later.', 'mxchat');
4675 +
4676 + // Add timeframe information to the message if placeholders exist
4677 + $timeframe_label = '';
4678 + switch ($timeframe) {
4679 + case 'hourly':
4680 + $timeframe_label = __('hour', 'mxchat');
4681 + break;
4682 + case 'daily':
4683 + $timeframe_label = __('day', 'mxchat');
4684 + break;
4685 + case 'weekly':
4686 + $timeframe_label = __('week', 'mxchat');
4687 + break;
4688 + case 'monthly':
4689 + $timeframe_label = __('month', 'mxchat');
4690 + break;
4691 + }
4692 +
4693 + // Replace placeholders in the message
4694 + $message = str_replace(
4695 + ['{limit}', '{count}', '{remaining}', '{timeframe}'],
4696 + [intval($limit), $limit_data['count'], max(0, intval($limit) - $limit_data['count']), $timeframe_label],
4697 + $message
4698 + );
4699 +
4700 + //error_log('MXChat Rate Limit: Limit exceeded. Message: ' . $message);
4701 +
4702 + // Return error with the custom message
4703 + return [
4704 + 'error' => true,
4705 + 'message' => $message
4706 + ];
4707 + }
4708 +
4709 + // Increment the counter
4710 + $limit_data['count']++;
4711 + update_option($option_name, $limit_data);
4712 + //error_log('MXChat Rate Limit: Incremented counter to ' . $limit_data['count']);
4713 +
4714 + return true;
4715 +}
4716 +
4717 +// Helper function to get client IP address
4718 +private function get_client_ip() {
4719 + // Check for shared internet/ISP IP
4720 + if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
4721 + return sanitize_text_field($_SERVER['HTTP_CLIENT_IP']);
4722 + }
4723 +
4724 + // Check for IPs passing through proxies
4725 + if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
4726 + // Use the first value in the comma-separated list
4727 + $forwarded_for = explode(',', sanitize_text_field($_SERVER['HTTP_X_FORWARDED_FOR']));
4728 + return trim($forwarded_for[0]);
4729 + }
4730 +
4731 + if (!empty($_SERVER['REMOTE_ADDR'])) {
4732 + return sanitize_text_field($_SERVER['REMOTE_ADDR']);
4733 + }
4734 +
4735 + // Fallback
4736 + return 'unknown';
4098 4737 }
4099 4738
4100 4739 }
4101 4740 ?>