PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 2.0.1
MxChat – AI Chatbot & Content Generation for WordPress v2.0.1
3.2.22 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 All 153 releases
← All changes | includes/class-mxchat-integrator.php +894 -423 2.0.62.0.1 View file →
@@ -471,45 +471,45 @@
471 471
472 472
473 473 // First, add this helper function to get the highest rate limit for a user's roles
474 474 private function get_user_role_rate_limit($user_id) {
475 - //error_log(esc_html__("Checking rate limit for user ID: ", 'mxchat') . $user_id);
475 + error_log(esc_html__("Checking rate limit for user ID: ", 'mxchat') . $user_id);
476 476
477 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'));
478 + error_log(esc_html__("No user ID provided, using logged-out limit: ", 'mxchat') . ($this->options['rate_limit_logged_out'] ?? '10'));
479 479 return $this->options['rate_limit_logged_out'] ?? '10';
480 480 }
481 481
482 482 $user = get_userdata($user_id);
483 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'));
484 + error_log(esc_html__("No user data or roles found, using logged-out limit: ", 'mxchat') . ($this->options['rate_limit_logged_out'] ?? '10'));
485 485 return $this->options['rate_limit_logged_out'] ?? '10';
486 486 }
487 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));
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 490
491 491 $max_limit = 0;
492 492 foreach ($user->roles as $role) {
493 - //error_log(esc_html__("Checking limit for role: ", 'mxchat') . $role);
493 + error_log(esc_html__("Checking limit for role: ", 'mxchat') . $role);
494 494 if (isset($this->options['role_rate_limits'][$role])) {
495 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);
496 + error_log(esc_html__("Found limit for role ", 'mxchat') . $role . esc_html__(": ", 'mxchat') . $role_limit);
497 497
498 498 if ($role_limit === 'unlimited') {
499 - //error_log(esc_html__("Returning unlimited for role: ", 'mxchat') . $role);
499 + error_log(esc_html__("Returning unlimited for role: ", 'mxchat') . $role);
500 500 return 'unlimited';
501 501 }
502 502
503 503 $max_limit = max($max_limit, (int)$role_limit);
504 - //error_log(esc_html__("Current max limit: ", 'mxchat') . $max_limit);
504 + error_log(esc_html__("Current max limit: ", 'mxchat') . $max_limit);
505 505 } else {
506 - //error_log(esc_html__("No limit found for role: ", 'mxchat') . $role);
506 + error_log(esc_html__("No limit found for role: ", 'mxchat') . $role);
507 507 }
508 508 }
509 509
510 510 $final_limit = $max_limit > 0 ? (string)$max_limit : '100';
511 - //error_log(esc_html__("Final rate limit: ", 'mxchat') . $final_limit);
511 + error_log(esc_html__("Final rate limit: ", 'mxchat') . $final_limit);
512 512 return $final_limit;
513 513 }
514 514
515 515
@@ -554,46 +554,8 @@
554 554
555 555 public function mxchat_handle_chat_request() {
556 556 global $wpdb;
557 557
558 -
559 - // Check if MX Chat Moderation is active
560 - if (class_exists('MX_Chat_Moderation')) {
561 - // Get user email and IP
562 - $user_email = '';
563 - $user_ip = $_SERVER['REMOTE_ADDR'];
564 -
565 - // If user is logged in, get their email
566 - if (is_user_logged_in()) {
567 - $current_user = wp_get_current_user();
568 - $user_email = $current_user->user_email;
569 - }
570 -
571 - // Create ban handler instance
572 - $ban_handler = new MX_Chat_Ban_Handler();
573 -
574 - // Check if user is banned by IP
575 - if ($ban_handler->check_ban($user_ip, 'ip')) {
576 - wp_send_json([
577 - 'success' => false,
578 - 'message' => esc_html__('Access denied. Your IP address has been banned.', 'mxchat'),
579 - 'status' => 'banned'
580 - ]);
581 - wp_die();
582 - }
583 -
584 - // If user is logged in, also check email
585 - if (!empty($user_email) && $ban_handler->check_ban($user_email, 'email')) {
586 - wp_send_json([
587 - 'success' => false,
588 - 'message' => esc_html__('Access denied. Your email address has been banned.', 'mxchat'),
589 - 'status' => 'banned'
590 - ]);
591 - wp_die();
592 - }
593 - }
594 -
595 -
596 558 // Reset fallback response at the start of each request
597 559 $this->fallbackResponse = ['text' => '', 'html' => '', 'images' => []];
598 560 $this->productCardHtml = '';
599 561
@@ -610,9 +572,9 @@
610 572 $user_id = sanitize_key($user_id);
611 573
612 574 // Determine if user is logged in
613 575 $is_logged_in = is_user_logged_in();
614 - //error_log("User logged in status: " . ($is_logged_in ? 'true' : 'false'));
576 + error_log("User logged in status: " . ($is_logged_in ? 'true' : 'false'));
615 577
616 578 // Get rate limit based on user status
617 579 // Get rate limit based on user status
618 580 $rate_limit = $is_logged_in
@@ -618,9 +580,9 @@
618 580 $rate_limit = $is_logged_in
619 581 ? $this->get_user_role_rate_limit($user_id)
620 582 : ($this->options['rate_limit_logged_out'] ?? '10');
621 583
622 - //error_log("Selected rate limit: " . $rate_limit);
584 + error_log("Selected rate limit: " . $rate_limit);
623 585
624 586 // Rest of your code remains the same
625 587 // If rate limit is 'unlimited', skip rate limiting checks
626 588 if ($rate_limit !== 'unlimited') {
@@ -960,138 +922,236 @@
960 922 wp_send_json($response_data);
961 923 wp_die();
962 924 }
963 925
926 +
927 +// Helper function to clear PDF and Word document related transients
928 +private function clear_pdf_transients($session_id) {
929 + // PDF transients
930 + delete_transient('mxchat_pdf_url_' . $session_id);
931 + delete_transient('mxchat_pdf_embeddings_' . $session_id);
932 + delete_transient('mxchat_include_pdf_in_context_' . $session_id);
933 + delete_transient('mxchat_waiting_for_pdf_url_' . $session_id);
934 +
935 + // Word document transients
936 + delete_transient('mxchat_word_url_' . $session_id);
937 + delete_transient('mxchat_word_filename_' . $session_id);
938 + delete_transient('mxchat_word_embeddings_' . $session_id);
939 + delete_transient('mxchat_include_word_in_context_' . $session_id);
940 + delete_transient('mxchat_waiting_for_word_' . $session_id);
941 +}
942 +
964 943 // New function to check intents and invoke the callback function
965 944 private function mxchat_check_intent_and_invoke_callback($message, $user_id, $session_id) {
966 945 global $wpdb;
946 +
947 + // Check chat mode
967 948 $chat_mode = get_option("mxchat_mode_{$session_id}", 'ai');
968 -
969 - //error_log('🔍 MXCHAT DEBUG: Intent Check Started ==================');
970 - //error_log("🔍 MXCHAT DEBUG: Message: '$message'");
971 - //error_log("🔍 MXCHAT DEBUG: Chat Mode: $chat_mode");
972 -
949 + //error_log(esc_html__("Checking intents for mode: ", 'mxchat') . $chat_mode);
950 +
973 951 // Generate the user embedding
974 - //error_log('🔄 MXCHAT DEBUG: Generating user embedding');
975 952 $user_embedding = $this->mxchat_generate_embedding($message, $this->options['api_key']);
976 953 if (!is_array($user_embedding)) {
977 - //error_log('❌ MXCHAT DEBUG: Failed to generate user embedding');
954 + //error_log(esc_html__("Failed to generate user embedding", 'mxchat'));
978 955 return false;
979 956 }
980 - //error_log('✅ MXCHAT DEBUG: User embedding generated successfully');
981 -
957 +
982 958 // Fetch intents from the database
983 959 $table_name = $wpdb->prefix . 'mxchat_intents';
960 +
984 961 if ($chat_mode === 'agent') {
985 - //error_log('🔍 MXCHAT DEBUG: Agent mode - fetching only chatbot switch intent');
962 + // Only fetch the switch intent when in agent mode
986 963 $query = $wpdb->prepare(
987 964 "SELECT * FROM $table_name WHERE callback_function = %s",
988 965 'mxchat_handle_switch_to_chatbot_intent'
989 966 );
967 + //error_log(esc_html__("Searching for switch intent with query: ", 'mxchat') . $query);
990 968 $intents = $wpdb->get_results($query);
969 + //error_log(esc_html__("Found ", 'mxchat') . count($intents) . esc_html__(" switch intents", 'mxchat'));
991 970 } else {
992 - //error_log('🔍 MXCHAT DEBUG: AI mode - fetching all intents');
993 971 $intents = $wpdb->get_results("SELECT * FROM $table_name");
994 972 }
995 -
996 - //error_log('🔍 MXCHAT DEBUG: Found ' . count($intents) . ' intents to check');
997 -
973 +
998 974 if (empty($intents)) {
999 - //error_log('❌ MXCHAT DEBUG: No intents found in database');
975 + //error_log(esc_html__("No intents found in database", 'mxchat'));
1000 976 return false;
1001 977 }
1002 -
978 +
1003 979 $highest_similarity = -INF;
1004 980 $matched_intent = null;
1005 -
1006 - //error_log('📊 MXCHAT DEBUG: Intent Similarity Scores ==================');
981 +
1007 982 foreach ($intents as $intent) {
1008 - //error_log("🔄 MXCHAT DEBUG: Checking intent: '{$intent->intent_label}' (callback: {$intent->callback_function})");
1009 -
983 + //error_log(esc_html__("Checking intent: ", 'mxchat') . $intent->intent_label);
1010 984 $intent_embedding_serialized = $intent->embedding_vector;
1011 985 $intent_embedding = $intent_embedding_serialized
1012 986 ? unserialize($intent_embedding_serialized, ['allowed_classes' => false])
1013 987 : null;
1014 -
988 +
1015 989 if (!is_array($intent_embedding)) {
1016 - //error_log("❌ MXCHAT DEBUG: Invalid embedding for intent: {$intent->intent_label}");
990 + //error_log(esc_html__("Invalid embedding for intent: ", 'mxchat') . $intent->intent_label);
1017 991 continue;
1018 992 }
1019 -
993 +
1020 994 $similarity = $this->mxchat_calculate_cosine_similarity($user_embedding, $intent_embedding);
1021 995 $intent_threshold = isset($intent->similarity_threshold) ? $intent->similarity_threshold : 0.85;
1022 -
1023 -
996 + //error_log(esc_html__("Similarity for ", 'mxchat') . $intent->intent_label . esc_html__(": ", 'mxchat') . $similarity . esc_html__(" (threshold: ", 'mxchat') . $intent_threshold . esc_html__(")", 'mxchat'));
997 +
1024 998 if ($similarity >= $intent_threshold && $similarity > $highest_similarity) {
1025 999 $highest_similarity = $similarity;
1026 1000 $matched_intent = $intent;
1027 - //error_log("✅ MXCHAT DEBUG: New best match: '{$intent->intent_label}' with similarity {$similarity}");
1001 + //error_log(esc_html__("New best match: ", 'mxchat') . $intent->intent_label . esc_html__(" with similarity ", 'mxchat') . $similarity);
1028 1002 }
1029 1003 }
1030 - //error_log('📊 MXCHAT DEBUG: End Intent Scores ==================');
1031 -
1032 - if ($matched_intent) {
1033 - //error_log("🎯 MXCHAT DEBUG: Final Intent Match: '{$matched_intent->intent_label}'");
1034 - //error_log("🔄 MXCHAT DEBUG: Invoking callback: {$matched_intent->callback_function}");
1035 -
1036 - // If the callback is a method on this instance (core callback), call it directly
1037 - if (method_exists($this, $matched_intent->callback_function)) {
1038 - //error_log('🔍 MXCHAT DEBUG: Using direct method call for core callback');
1039 - $callback_result = call_user_func(
1040 - [$this, $matched_intent->callback_function],
1041 - $message,
1042 - $user_id,
1043 - $session_id,
1044 - $matched_intent,
1045 - $user_context // Add user context
1046 - );
1047 - } else {
1048 - //error_log('🔍 MXCHAT DEBUG: Using apply_filters for add-on callback');
1049 - // Otherwise, use apply_filters for add-on callbacks
1050 - $callback_result = apply_filters(
1051 - $matched_intent->callback_function,
1052 - false, // default return value
1053 - $message,
1054 - $user_id,
1055 - $session_id,
1056 - $matched_intent
1057 - );
1058 - }
1059 -
1060 - //error_log('🔍 MXCHAT DEBUG: Callback result type: ' . gettype($callback_result));
1061 - if ($callback_result !== false) {
1062 - //error_log('✅ MXCHAT DEBUG: Intent handled successfully');
1063 - $this->fallbackResponse = $callback_result;
1064 - return true;
1065 - }
1066 - //error_log('❌ MXCHAT DEBUG: Callback returned false');
1067 - } else {
1068 - //error_log('❌ MXCHAT DEBUG: No matching intent found');
1004 +
1005 + if ($matched_intent && method_exists($this, $matched_intent->callback_function)) {
1006 + //error_log(esc_html__("Calling callback function: ", 'mxchat') . $matched_intent->callback_function);
1007 + call_user_func([$this, $matched_intent->callback_function], $message, $user_id, $session_id);
1008 + return true;
1069 1009 }
1070 -
1071 - //error_log('🔍 MXCHAT DEBUG: Intent Check Completed ==================');
1010 +
1011 + //error_log(esc_html__("No matching intent found", 'mxchat'));
1072 1012 return false;
1073 1013 }
1074 1014
1015 +//verified good
1016 +public function mxchat_handle_order_history($message, $user_id, $session_id) {
1017 + if (!class_exists('WooCommerce')) {
1018 + $this->fallbackResponse['text'] = esc_html__("I can't access order information right now. The order system seems to be unavailable.", 'mxchat');
1019 + return true;
1020 + }
1075 1021
1076 -// Helper function to clear PDF and Word document related transients
1077 -private function clear_pdf_transients($session_id) {
1078 - // PDF transients
1079 - delete_transient('mxchat_pdf_url_' . $session_id);
1080 - delete_transient('mxchat_pdf_embeddings_' . $session_id);
1081 - delete_transient('mxchat_include_pdf_in_context_' . $session_id);
1082 - delete_transient('mxchat_waiting_for_pdf_url_' . $session_id);
1022 + $orderDetails = MxChat_WooCommerce::mxchat_fetch_user_orders_details('all');
1083 1023
1084 - // Word document transients
1085 - delete_transient('mxchat_word_url_' . $session_id);
1086 - delete_transient('mxchat_word_filename_' . $session_id);
1087 - delete_transient('mxchat_word_embeddings_' . $session_id);
1088 - delete_transient('mxchat_include_word_in_context_' . $session_id);
1089 - delete_transient('mxchat_waiting_for_word_' . $session_id);
1024 + if (empty($orderDetails)) {
1025 + $this->fallbackResponse['text'] = esc_html__("I don't see any orders associated with your account. Are you logged in?", 'mxchat');
1026 + return true;
1027 + }
1028 +
1029 + // Generate AI prompt with context
1030 + $prompt = __("User asked about their orders:", 'mxchat') . " '{$message}'\n\n";
1031 + $prompt .= __("Order information:", 'mxchat') . "\n";
1032 + foreach ($orderDetails as $order) {
1033 + $items_list = array_map(function($item) {
1034 + return "{$item['name']} ({$item['quantity']})";
1035 + }, $order['items']);
1036 +
1037 + $prompt .= __("Order #", 'mxchat') . "{$order['order_id']}: {$order['formatted_total']} " . __("on", 'mxchat') . " {$order['date']}\n";
1038 + $prompt .= __("Items:", 'mxchat') . " " . implode(', ', $items_list) . "\n";
1039 + }
1040 +
1041 + $prompt .= __("\nProvide a natural, conversational response focusing on the specific information the user asked about. ", 'mxchat');
1042 + $prompt .= __("If they ask about a specific order or detail, provide just that information. ", 'mxchat');
1043 + $prompt .= __("If they ask about license keys or sensitive information, inform them to check their email or contact support.", 'mxchat');
1044 +
1045 + // Get AI response
1046 + $ai_response = $this->mxchat_call_ai_api($prompt);
1047 + $this->fallbackResponse['text'] = $ai_response['text'];
1048 +
1049 + return true;
1090 1050 }
1091 1051
1092 1052
1053 +public function mxchat_handle_product_inquiry($message, $user_id, $session_id) {
1054 + //error_log("PRODUCT INQUIRY START - Message: $message, User ID: $user_id");
1093 1055
1056 + // Sanitize user ID
1057 + $sanitized_user_id = sanitize_key($user_id);
1058 + //error_log("Sanitized User ID: $sanitized_user_id");
1059 +
1060 + // Find product
1061 + $product_id = $this->find_product_in_message($message);
1062 + //error_log("Initial product ID from message: " . ($product_id ?? 'null'));
1063 +
1064 + // Check transient if no product found
1065 + if (!$product_id) {
1066 + $product_id = get_transient('mxchat_last_discussed_product_' . $sanitized_user_id);
1067 + //error_log("Product ID from transient: " . ($product_id ?? 'null'));
1068 + }
1069 +
1070 + // Handle product inquiries
1071 + if ($product_id && class_exists('WooCommerce')) {
1072 + $product = wc_get_product($product_id);
1073 + if ($product) {
1074 + //error_log("Found product: " . $product->get_name() . " (ID: $product_id)");
1075 +
1076 + // Prepare product details
1077 + $product_name = esc_html($product->get_name());
1078 + $product_price = $product->get_price_html();
1079 + $product_image_url = esc_url(wp_get_attachment_url($product->get_image_id()));
1080 + $product_url = esc_url(get_permalink($product_id));
1081 + $product_id_attr = esc_attr($product_id);
1082 +
1083 + // Get product description
1084 + $product_description = $product->get_description() ?: $product->get_short_description();
1085 +
1086 + // Log embeddings process
1087 + //error_log("Generating embedding for message: $message");
1088 + $user_message_embedding = $this->mxchat_generate_embedding($message, $this->options['api_key']);
1089 + //error_log("Finding relevant content for product inquiry");
1090 + $relevant_content = $this->mxchat_find_relevant_content($user_message_embedding);
1091 +
1092 + // Build AI prompt
1093 + $ai_prompt = esc_html__("You are a knowledgeable product assistant. ", 'mxchat');
1094 + $ai_prompt .= esc_html__("Respond to this user query: '{$message}'\n\n", 'mxchat');
1095 +
1096 + if (!empty($relevant_content)) {
1097 + $ai_prompt .= esc_html__("Relevant information from our knowledge base:\n{$relevant_content}\n\n", 'mxchat');
1098 + }
1099 +
1100 + $ai_prompt .= esc_html__("Product details:\n", 'mxchat');
1101 + $ai_prompt .= esc_html__("Name: {$product_name}\n", 'mxchat');
1102 + $ai_prompt .= esc_html__("Price: ", 'mxchat') . strip_tags($product_price) . "\n";
1103 + if ($product_description) {
1104 + $ai_prompt .= esc_html__("Description: {$product_description}\n", 'mxchat');
1105 + }
1106 +
1107 + $ai_prompt .= esc_html__("\nInstructions:\n", 'mxchat');
1108 + $ai_prompt .= esc_html__("1. Address the user's specific question or concern about the product\n", 'mxchat');
1109 + $ai_prompt .= esc_html__("2. Incorporate relevant information from our knowledge base if provided\n", 'mxchat');
1110 + $ai_prompt .= esc_html__("3. Highlight key product features that relate to their query\n", 'mxchat');
1111 + $ai_prompt .= esc_html__("4. Include a natural suggestion to check out the product\n", 'mxchat');
1112 + $ai_prompt .= esc_html__("5. Keep the response conversational and helpful\n", 'mxchat');
1113 +
1114 + //error_log("Sending AI prompt: " . $ai_prompt);
1115 +
1116 + // Build and log AI response
1117 + $ai_response = $this->mxchat_call_ai_api($ai_prompt);
1118 + //error_log("AI Response received for product inquiry: " . print_r($ai_response, true));
1119 +
1120 + // Generate and log product card
1121 + $product_card_html = <<<HTML
1122 +<div class="mxchat-product-card">
1123 + <a href="{$product_url}" target="_blank">
1124 + <img src="{$product_image_url}" alt="{$product_name}" class="mxchat-product-image" />
1125 + <h3 class="mxchat-product-name">{$product_name}</h3>
1126 + </a>
1127 + <div class="mxchat-product-price">{$product_price}</div>
1128 + <button class="mxchat-add-to-cart-button" data-product-id="{$product_id_attr}">Add to Cart</button>
1129 +</div>
1130 +HTML;
1131 +
1132 + // Save response and set transient
1133 + $this->productCardHtml = $product_card_html;
1134 + $this->fallbackResponse = [
1135 + 'text' => $ai_response['text'],
1136 + 'html' => $this->productCardHtml,
1137 + ];
1138 +
1139 + //error_log("Setting transient for product: $product_id with key: mxchat_last_discussed_product_$sanitized_user_id");
1140 + set_transient('mxchat_last_discussed_product_' . $sanitized_user_id, $product_id, HOUR_IN_SECONDS);
1141 +
1142 + // Verify transient was set
1143 + $verify_transient = get_transient('mxchat_last_discussed_product_' . $sanitized_user_id);
1144 + //error_log("Verification - Retrieved transient value: " . ($verify_transient ?? 'null'));
1145 +
1146 + return true;
1147 + }
1148 + }
1149 +
1150 + //error_log("PRODUCT INQUIRY END - No product found or WooCommerce not available");
1151 + return false;
1152 +}
1153 +
1094 1154 //verified good
1095 1155 public function mxchat_handle_email_capture($message, $user_id, $session_id) {
1096 1156 // Log the message safely
1097 1157 //error_log("Triggered email capture intent for message: " . sanitize_text_field($message));
@@ -1578,10 +1638,100 @@
1578 1638 return sanitize_text_field($user_query);
1579 1639 }
1580 1640 }
1581 1641
1642 +public function mxchat_handle_add_to_cart_intent($message, $user_id, $session_id) {
1643 + //error_log("ADD TO CART START - Message: $message, User ID: $user_id");
1582 1644
1645 + if (!class_exists('WooCommerce')) {
1646 + //error_log("WooCommerce not available");
1647 + return $this->generate_intent_response([
1648 + 'intent' => 'add_to_cart',
1649 + 'status' => 'error',
1650 + 'reason' => esc_html__('woocommerce_not_available', 'mxchat')
1651 + ], $session_id);
1652 + }
1583 1653
1654 + $sanitized_user_id = sanitize_key($user_id);
1655 + // error_log("Sanitized User ID: $sanitized_user_id");
1656 + $product_id = null;
1657 +
1658 + // If button click, use transient
1659 + if ($message === '!addtocart') {
1660 + //error_log("Button click detected - using transient");
1661 + $product_id = get_transient('mxchat_last_discussed_product_' . $sanitized_user_id);
1662 + //error_log("Product ID from transient: " . ($product_id ?? 'null'));
1663 + } else {
1664 + // For text commands, search message first
1665 + //error_log("Text command detected - searching message first");
1666 + $product_id = $this->find_product_in_message($message);
1667 + //error_log("Product ID from message search: " . ($product_id ?? 'null'));
1668 +
1669 + // Only use transient as fallback if no product found in message
1670 + if (!$product_id) {
1671 + //error_log("No product found in message, checking transient as fallback");
1672 + $product_id = get_transient('mxchat_last_discussed_product_' . $sanitized_user_id);
1673 + //error_log("Product ID from transient fallback: " . ($product_id ?? 'null'));
1674 + }
1675 + }
1676 +
1677 + // Handle no product found
1678 + if (!$product_id) {
1679 + //error_log("No product ID found through any method");
1680 + return $this->generate_intent_response([
1681 + 'intent' => 'add_to_cart',
1682 + 'status' => 'error',
1683 + 'reason' => esc_html__('no_product_context', 'mxchat'),
1684 + 'action_needed' => esc_html__('request_product_name', 'mxchat'),
1685 + 'searched_message' => $message
1686 + ], $session_id);
1687 + }
1688 +
1689 + // Get and verify product
1690 + $product = wc_get_product($product_id);
1691 + if (!$product) {
1692 + //error_log("Product not found with ID: $product_id");
1693 + return $this->generate_intent_response([
1694 + 'intent' => 'add_to_cart',
1695 + 'status' => 'error',
1696 + 'reason' => esc_html__('product_not_found', 'mxchat'),
1697 + 'product_id' => $product_id
1698 + ], $session_id);
1699 + }
1700 +
1701 + //error_log("Found product: " . $product->get_name() . " (ID: $product_id)");
1702 +
1703 + // Add to cart
1704 + $added = WC()->cart->add_to_cart($product_id);
1705 + if ($added) {
1706 + //error_log("Successfully added to cart: " . $product->get_name());
1707 + return $this->generate_intent_response([
1708 + 'intent' => 'add_to_cart',
1709 + 'status' => 'success',
1710 + 'product' => [
1711 + 'name' => $product->get_name(),
1712 + 'id' => $product_id
1713 + ],
1714 + 'cart_url' => wc_get_cart_url(),
1715 + 'available_actions' => [
1716 + esc_html__('view_cart', 'mxchat'),
1717 + esc_html__('checkout', 'mxchat'),
1718 + esc_html__('continue_shopping', 'mxchat')
1719 + ]
1720 + ], $session_id);
1721 + } else {
1722 + //error_log("Failed to add to cart: " . $product->get_name());
1723 + return $this->generate_intent_response([
1724 + 'intent' => 'add_to_cart',
1725 + 'status' => 'error',
1726 + 'reason' => esc_html__('add_to_cart_failed', 'mxchat'),
1727 + 'product' => [
1728 + 'name' => $product->get_name(),
1729 + 'id' => $product_id
1730 + ]
1731 + ], $session_id);
1732 + }
1733 +}
1584 1734 private function find_product_in_message($message) {
1585 1735 global $wpdb;
1586 1736
1587 1737 // Get embedding for the search query
@@ -1707,8 +1857,44 @@
1707 1857 return $context_string;
1708 1858 }
1709 1859
1710 1860
1861 +public function mxchat_handle_checkout_intent($message, $user_id, $session_id) {
1862 + if (!class_exists('WooCommerce')) {
1863 + $this->fallbackResponse['text'] = esc_html__("I apologize, but the checkout feature isn't available at the moment.", 'mxchat');
1864 + return true;
1865 + }
1866 +
1867 + // Check if cart has items
1868 + if (WC()->cart->is_empty()) {
1869 + $this->fallbackResponse['text'] = esc_html__("Your cart is empty at the moment. Would you like to see our products?", 'mxchat');
1870 + return true;
1871 + }
1872 +
1873 + // Get cart summary
1874 + $cart_count = WC()->cart->get_cart_contents_count();
1875 + $cart_total = WC()->cart->get_total();
1876 +
1877 + // Get and validate checkout URL
1878 + $checkout_url = wc_get_checkout_url();
1879 + if (!$checkout_url) {
1880 + $this->fallbackResponse['text'] = esc_html__("I'm having trouble accessing the checkout page. Please try again in a moment.", 'mxchat');
1881 + return true;
1882 + }
1883 +
1884 + wp_send_json([
1885 + 'text' => sprintf(
1886 + esc_html__("You have %d item%s in your cart totaling %s. I'll redirect you to checkout now.", 'mxchat'),
1887 + $cart_count,
1888 + $cart_count > 1 ? esc_html__('s', 'mxchat') : '',
1889 + strip_tags($cart_total)
1890 + ),
1891 + 'redirect_url' => esc_url_raw($checkout_url)
1892 + ]);
1893 + wp_die();
1894 +}
1895 +
1896 +
1711 1897 //very good
1712 1898 private function add_email_to_loops($email) {
1713 1899 // Sanitize the email
1714 1900 $email = sanitize_email($email);
@@ -2001,10 +2187,408 @@
2001 2187 wp_die();
2002 2188 }
2003 2189
2004 2190
2191 +/**
2192 + * Calls the AI API with the provided prompt.
2193 + *
2194 + * @param string $prompt The prompt to send to the AI.
2195 + * @return array An array containing the AI's response text.
2196 + */
2197 +private function mxchat_call_ai_api( $prompt ) {
2198 + //error_log( esc_html__( 'Calling AI API with the provided prompt.', 'mxchat' ) );
2005 2199
2200 + $api_key = $this->options['api_key'];
2201 + if ( empty( $api_key ) ) {
2202 + //error_log( esc_html__( 'API key is not set.', 'mxchat' ) );
2203 + return [ 'text' => esc_html__( 'API key is not set.', 'mxchat' ) ];
2204 + }
2006 2205
2206 + $url = 'https://api.openai.com/v1/chat/completions';
2207 + $messages = [
2208 + [
2209 + 'role' => 'system',
2210 + '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' ),
2211 + ],
2212 + [
2213 + 'role' => 'user',
2214 + 'content' => $prompt,
2215 + ],
2216 + ];
2217 +
2218 + $args = [
2219 + 'headers' => [
2220 + 'Authorization' => 'Bearer ' . $api_key,
2221 + 'Content-Type' => 'application/json',
2222 + ],
2223 + 'body' => wp_json_encode(
2224 + [
2225 + 'model' => 'gpt-4o',
2226 + 'messages' => $messages,
2227 + 'temperature' => 0.7,
2228 + ]
2229 + ),
2230 + 'timeout' => 10,
2231 + 'method' => 'POST',
2232 + ];
2233 +
2234 + $response = wp_remote_post( $url, $args );
2235 +
2236 + if ( is_wp_error( $response ) ) {
2237 + //error_log( esc_html__( 'Error communicating with AI API: ', 'mxchat' ) . $response->get_error_message() );
2238 + return [ 'text' => esc_html__( 'Error communicating with AI API.', 'mxchat' ) ];
2239 + }
2240 +
2241 + $body = wp_remote_retrieve_body( $response );
2242 + //error_log( esc_html__( 'API response body: ', 'mxchat' ) . $body );
2243 +
2244 + $decoded_body = json_decode( $body, true );
2245 + if ( isset( $decoded_body['choices'][0]['message']['content'] ) ) {
2246 + return [ 'text' => $decoded_body['choices'][0]['message']['content'] ];
2247 + } else {
2248 + //error_log( esc_html__( 'Unexpected API response format: ', 'mxchat' ) . wp_json_encode( $decoded_body ) );
2249 + return [ 'text' => esc_html__( 'No response received from AI.', 'mxchat' ) ];
2250 + }
2251 +}
2252 +
2253 +/**
2254 + * Fetches the AI response for the given prompt.
2255 + *
2256 + * @param string $prompt The prompt to send to the AI.
2257 + * @return string|null The AI's response text or null if not available.
2258 + */
2259 +private function mxchat_fetch_ai_response( $prompt ) {
2260 + $response = $this->mxchat_call_ai_api( $prompt );
2261 + return isset( $response['text'] ) ? $response['text'] : null;
2262 +}
2263 +/**
2264 + * Generates the AI prompt for product recommendations.
2265 + *
2266 + * @param array $recommendations An array of product recommendations.
2267 + * @return string The generated AI prompt.
2268 + */
2269 +private function mxchat_generate_ai_recommendation_prompt($recommendations) {
2270 + $recommendation_list = '';
2271 + foreach ($recommendations as $index => $rec) {
2272 + $number = $index + 1;
2273 + $name = $rec['name'];
2274 + $price = $rec['price'];
2275 + $url = $rec['url'];
2276 + $image = $rec['image'];
2277 + $recommendation_list .= "{$number}. " . esc_html__('Product:', 'mxchat') . " {$name} (" . esc_html__('Price:', 'mxchat') . " \\${$price})\n";
2278 + $recommendation_list .= " [Link]({$url})\n";
2279 + $recommendation_list .= " ![Image]({$image})\n\n";
2280 + }
2281 +
2282 + $prompt = esc_html__('Based on the following list of products, generate a unique, friendly, and personalized response to a user. ', 'mxchat');
2283 + $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');
2284 + $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');
2285 + $prompt .= esc_html__('Please number your responses to match the product numbers.', 'mxchat') . "\n\n";
2286 + $prompt .= esc_html__('Products:', 'mxchat') . "\n\n{$recommendation_list}";
2287 + $prompt .= esc_html__('Please ensure that the number of each product matches the order in which the products are listed.', 'mxchat');
2288 +
2289 + return $prompt;
2290 +}
2291 +private function mxchat_generate_recommendations($user_id, $message) {
2292 + // 1. Gather user context
2293 + $user_context = $this->mxchat_get_user_context($user_id);
2294 +
2295 + // Get cart item IDs for filtering
2296 + $cart_item_ids = [];
2297 + if (!empty($user_context['cart_items'])) {
2298 + $cart_item_ids = array_map(function($item) {
2299 + return $item['product_id'];
2300 + }, $user_context['cart_items']);
2301 + }
2302 +
2303 + // 2. Get AI recommendation based on context
2304 + $ai_suggestion = $this->mxchat_get_ai_shopping_suggestion($message, $user_context);
2305 + //error_log("AI Shopping Suggestion: " . $ai_suggestion);
2306 +
2307 + // 3. Generate embedding for the AI suggestion
2308 + $suggestion_embedding = $this->mxchat_generate_embedding($ai_suggestion, $this->options['api_key']);
2309 + if (!$suggestion_embedding) {
2310 + //error_log("Could not generate embedding for AI suggestion");
2311 + return ['recommendations' => [], 'sources' => []];
2312 + }
2313 +
2314 + // 4. Use existing relevant content function to find matches
2315 + $relevant_content = $this->mxchat_find_relevant_content($suggestion_embedding);
2316 +
2317 + // 5. Extract product URLs from the relevant content
2318 + preg_match_all('/https?:\/\/[^\s<>"\']+?\/product\/[^\s<>"\']+/', $relevant_content, $matches);
2319 +
2320 + $found_products = [];
2321 + if (!empty($matches[0])) {
2322 + foreach ($matches[0] as $url) {
2323 + // Clean the URL
2324 + $url = rtrim($url, '/."\']');
2325 +
2326 + // Get the product slug
2327 + $path = parse_url($url, PHP_URL_PATH);
2328 + $slug = basename(rtrim($path, '/'));
2329 +
2330 + // Find product by slug
2331 + $args = array(
2332 + 'post_type' => 'product',
2333 + 'post_status' => 'publish',
2334 + 'name' => $slug,
2335 + 'posts_per_page' => 1
2336 + );
2337 +
2338 + $products = get_posts($args);
2339 +
2340 + if (!empty($products)) {
2341 + $product_id = $products[0]->ID;
2342 +
2343 + // Skip if product is in cart
2344 + if (in_array($product_id, $cart_item_ids)) {
2345 + //error_log("Skipping product ID $product_id - already in cart");
2346 + continue;
2347 + }
2348 +
2349 + $product = wc_get_product($product_id);
2350 +
2351 + if ($product && $product->is_purchasable() && !in_array($product_id, array_map(function($p) {
2352 + return $p->get_id();
2353 + }, $found_products))) {
2354 + $found_products[] = $product;
2355 + }
2356 + }
2357 + }
2358 + }
2359 +
2360 + // If no products found by URLs, look for product names in the content
2361 + if (empty($found_products)) {
2362 + $products = wc_get_products([
2363 + 'status' => 'publish',
2364 + 'limit' => -1,
2365 + 'return' => 'objects'
2366 + ]);
2367 +
2368 + foreach ($products as $product) {
2369 + // Skip if product is in cart
2370 + if (in_array($product->get_id(), $cart_item_ids)) {
2371 + continue;
2372 + }
2373 +
2374 + $name = $product->get_name();
2375 + if (stripos($relevant_content, $name) !== false) {
2376 + if ($product->is_purchasable() && !in_array($product->get_id(), array_map(function($p) {
2377 + return $p->get_id();
2378 + }, $found_products))) {
2379 + $found_products[] = $product;
2380 + }
2381 + }
2382 + }
2383 + }
2384 +
2385 + // Keep searching until we have 4 products or run out of options
2386 + $formatted_recommendations = [];
2387 + foreach ($found_products as $product) {
2388 + if (count($formatted_recommendations) >= 4) break;
2389 +
2390 + $formatted_recommendations[] = [
2391 + 'name' => $product->get_name(),
2392 + 'price' => $product->get_price(),
2393 + 'url' => get_permalink($product->get_id()),
2394 + 'image' => wp_get_attachment_url($product->get_image_id())
2395 + ];
2396 + }
2397 +
2398 + return [
2399 + 'recommendations' => $formatted_recommendations,
2400 + 'sources' => [__('AI-powered personalized recommendations', 'mxchat')]
2401 + ];
2402 +}
2403 +private function mxchat_get_ai_shopping_suggestion($message, $user_context) {
2404 + $prompt = esc_html__("As a shopping assistant, analyze this user's context and generate a specific product search suggestion. ", 'mxchat');
2405 + $prompt .= esc_html__("User's Question: \"{$message}\"\n\n", 'mxchat');
2406 +
2407 + if (!empty($user_context['order_history'])) {
2408 + $prompt .= esc_html__("Their recent orders include:\n", 'mxchat');
2409 + foreach ($user_context['order_history'] as $order) {
2410 + $prompt .= esc_html__("- {$order['name']} ({$order['date']})\n", 'mxchat');
2411 + }
2412 + }
2413 +
2414 + if (!empty($user_context['cart_items'])) {
2415 + $prompt .= esc_html__("\nThey currently have in their cart:\n", 'mxchat');
2416 + foreach ($user_context['cart_items'] as $item) {
2417 + $prompt .= esc_html__("- {$item['name']}\n", 'mxchat');
2418 + }
2419 + }
2420 +
2421 + $prompt .= esc_html__("\nBased on their question and history, suggest a specific search query that would help find the most relevant products. ", 'mxchat');
2422 + $prompt .= esc_html__("Respond with ONLY the search query, nothing else.", 'mxchat');
2423 +
2424 + $response = $this->mxchat_call_ai_api($prompt);
2425 + return isset($response['text']) ? trim($response['text']) : $message;
2426 +}
2427 +public function mxchat_handle_product_recommendations($message, $user_id, $session_id) {
2428 + try {
2429 + //error_log("Starting product recommendations for user: $user_id, session: $session_id");
2430 +
2431 + // Pass the message to get context-aware recommendations
2432 + $recommendation_data = $this->mxchat_generate_recommendations($user_id, $message);
2433 + //error_log('Generated recommendation data: ' . wp_json_encode($recommendation_data));
2434 +
2435 + if (empty($recommendation_data['recommendations'])) {
2436 + //error_log("No recommendations found for user: $user_id");
2437 + $this->fallbackResponse = [
2438 + 'text' => __("I couldn't find any product recommendations for you right now. Please try again later!", 'mxchat'),
2439 + ];
2440 + return;
2441 + }
2442 +
2443 + // Remove duplicates and limit to top 4 recommendations
2444 + $unique_recommendations = [];
2445 + foreach ($recommendation_data['recommendations'] as $rec) {
2446 + $unique_recommendations[$rec['url']] = $rec;
2447 + }
2448 +
2449 + $unique_recommendations = array_slice($unique_recommendations, 0, 4);
2450 + //error_log('Top 4 recommendations: ' . wp_json_encode($unique_recommendations));
2451 +
2452 + $recommendations_summary = [];
2453 + foreach ($unique_recommendations as $rec) {
2454 + $recommendations_summary[] = [
2455 + 'name' => $rec['name'],
2456 + 'price' => strip_tags($rec['price']),
2457 + 'url' => $rec['url'],
2458 + 'image' => $rec['image'],
2459 + ];
2460 + }
2461 +
2462 + // Generate AI prompt and fetch response
2463 + $ai_prompt = $this->mxchat_generate_ai_recommendation_prompt($recommendations_summary);
2464 + $ai_response = $this->mxchat_fetch_ai_response($ai_prompt);
2465 +
2466 + if (empty($ai_response)) {
2467 + $this->fallbackResponse = [
2468 + 'text' => __('An unexpected error occurred while generating recommendations. Please try again later.', 'mxchat'),
2469 + ];
2470 + return;
2471 + }
2472 +
2473 + // Split the AI's response into lines
2474 + $ai_lines = preg_split('/\r\n|\r|\n/', $ai_response);
2475 +
2476 + // Initialize variables
2477 + $formatted_response = '';
2478 + $justifications = [];
2479 + $current_number = 0;
2480 + $in_introduction = true;
2481 + $introduction = '';
2482 +
2483 + // Parse the AI response to separate the introduction and the justifications
2484 + foreach ($ai_lines as $line) {
2485 + if (preg_match('/^\s*(\d+)\.\s*(.*)$/', $line, $matches)) {
2486 + // This line is a numbered justification
2487 + $current_number = intval($matches[1]) - 1;
2488 + $justifications[$current_number] = $matches[2];
2489 + $in_introduction = false;
2490 + } elseif ($in_introduction) {
2491 + // This line is part of the introduction
2492 + $introduction .= $line . ' ';
2493 + } else {
2494 + // This line is a continuation of the current justification
2495 + if (isset($justifications[$current_number])) {
2496 + $justifications[$current_number] .= ' ' . $line;
2497 + }
2498 + }
2499 + }
2500 +
2501 + // Build the formatted response
2502 + if (!empty($introduction)) {
2503 + $formatted_response .= esc_html(trim($introduction)) . "<br><br>";
2504 + }
2505 +
2506 + foreach ($recommendations_summary as $index => $rec) {
2507 + $name = esc_html($rec['name']);
2508 + $price = number_format((float)$rec['price'], 2, '.', '');
2509 + $url = esc_url($rec['url']);
2510 + $image = esc_url($rec['image']);
2511 +
2512 + $formatted_response .= ($index + 1) . ". <strong>" . $name . "</strong> - <strong>$" . $price . "</strong><br>";
2513 + $formatted_response .= "<img src=\"{$image}\" alt=\"{$name}\" style=\"max-width: 200px; height: auto; display: block; margin: 10px 0;\" /><br>";
2514 +
2515 + if (isset($justifications[$index])) {
2516 + $formatted_response .= "<em>" . esc_html(trim($justifications[$index])) . "</em><br><br>";
2517 + }
2518 + }
2519 +
2520 + $this->fallbackResponse = [
2521 + 'text' => $formatted_response,
2522 + ];
2523 + //error_log('Final formatted response set.');
2524 + } catch (Exception $e) {
2525 + //error_log('Error in mxchat_handle_product_recommendations: ' . $e->getMessage());
2526 + $this->fallbackResponse = [
2527 + 'text' => __('An unexpected error occurred while generating recommendations. Please try again later.', 'mxchat'),
2528 + ];
2529 + }
2530 +}
2531 +private function mxchat_get_user_context($user_id) {
2532 + $context = [
2533 + 'order_history' => [],
2534 + 'cart_items' => [],
2535 + 'recently_viewed' => [],
2536 + ];
2537 +
2538 + // Get order history
2539 + if (is_user_logged_in() && $user_id) {
2540 + $orders = wc_get_orders([
2541 + 'customer_id' => $user_id,
2542 + 'limit' => 5, // Last 5 orders
2543 + 'orderby' => 'date',
2544 + 'order' => 'DESC',
2545 + ]);
2546 +
2547 + foreach ($orders as $order) {
2548 + foreach ($order->get_items() as $item) {
2549 + $context['order_history'][] = [
2550 + 'name' => $item->get_name(),
2551 + 'product_id' => $item->get_product_id(),
2552 + 'date' => $order->get_date_created()->format('Y-m-d')
2553 + ];
2554 + }
2555 + }
2556 + }
2557 +
2558 + // Get cart items
2559 + if (WC()->cart && WC()->cart->get_cart_contents_count() > 0) {
2560 + foreach (WC()->cart->get_cart() as $cart_item) {
2561 + $product = wc_get_product($cart_item['product_id']);
2562 + if ($product) {
2563 + $context['cart_items'][] = [
2564 + 'name' => $product->get_name(),
2565 + 'product_id' => $product->get_id()
2566 + ];
2567 + }
2568 + }
2569 + }
2570 +
2571 + // Get recently viewed items from session
2572 + $viewed_products = WC()->session->get('recently_viewed_products', []);
2573 + foreach ($viewed_products as $product_id) {
2574 + $product = wc_get_product($product_id);
2575 + if ($product) {
2576 + $context['recently_viewed'][] = [
2577 + 'name' => $product->get_name(),
2578 + 'product_id' => $product->get_id()
2579 + ];
2580 + }
2581 + }
2582 +
2583 + return $context;
2584 +}
2585 +
2586 +
2587 +
2588 +
2589 +
2590 +
2007 2591 function mxchat_fetch_new_messages() {
2008 2592 $session_id = sanitize_text_field($_POST['session_id']);
2009 2593 $last_seen_id = sanitize_text_field($_POST['last_seen_id']);
2010 2594 $persistence_enabled = $_POST['persistence_enabled'] === 'true';
@@ -2348,9 +2932,9 @@
2348 2932 $command_text = $request->get_param('text');
2349 2933 // error_log('Command text: ' . $command_text);
2350 2934
2351 2935 if (empty($command_text)) {
2352 - //error_log(esc_html__('Agent response error: No command text received', 'mxchat'));
2936 + error_log(esc_html__('Agent response error: No command text received', 'mxchat'));
2353 2937 return new WP_REST_Response([
2354 2938 'error' => esc_html__('Command text is required. Format: /reply session_id message', 'mxchat')
2355 2939 ], 400);
2356 2940 }
@@ -2429,68 +3013,46 @@
2429 3013 return MxChat_User::mxchat_get_user_identifier();
2430 3014 }
2431 3015
2432 3016 private function mxchat_generate_embedding($text, $api_key) {
2433 - // Get options and selected model
2434 - $options = get_option('mxchat_options');
2435 - $selected_model = $options['embedding_model'] ?? 'text-embedding-ada-002';
2436 -
2437 - // Determine endpoint and API key based on model
2438 - if (strpos($selected_model, 'voyage') === 0) {
2439 - $endpoint = 'https://api.voyageai.com/v1/embeddings';
2440 - $api_key = $options['voyage_api_key'] ?? '';
2441 - } else {
2442 3017 $endpoint = 'https://api.openai.com/v1/embeddings';
2443 - // Use the passed API key for OpenAI
3018 +
3019 + $body = wp_json_encode([
3020 + 'input' => $text,
3021 + 'model' => 'text-embedding-ada-002'
3022 + ]);
3023 +
3024 + $args = [
3025 + 'body' => $body,
3026 + 'headers' => [
3027 + 'Content-Type' => 'application/json',
3028 + 'Authorization' => 'Bearer ' . $api_key,
3029 + ],
3030 + 'timeout' => 60,
3031 + 'redirection' => 5,
3032 + 'blocking' => true,
3033 + 'httpversion' => '1.0',
3034 + 'sslverify' => true,
3035 + ];
3036 +
3037 + $response = wp_remote_post($endpoint, $args);
3038 +
3039 + if (is_wp_error($response)) {
3040 + return null;
3041 + }
3042 +
3043 + $response_body = json_decode(wp_remote_retrieve_body($response), true);
3044 +
3045 + if (isset($response_body['data'][0]['embedding']) && is_array($response_body['data'][0]['embedding'])) {
3046 + return $response_body['data'][0]['embedding'];
3047 + } else {
3048 + return null;
3049 + }
2444 3050 }
2445 -
2446 - // Prepare request body with conditional output_dimension
2447 - $request_body = [
2448 - 'input' => $text,
2449 - 'model' => $selected_model
2450 - ];
2451 -
2452 - // Add output_dimension for voyage-3-large
2453 - if ($selected_model === 'voyage-3-large') {
2454 - $request_body['output_dimension'] = 2048;
2455 - }
2456 -
2457 - // Prepare request arguments
2458 - $args = [
2459 - 'body' => wp_json_encode($request_body),
2460 - 'headers' => [
2461 - 'Content-Type' => 'application/json',
2462 - 'Authorization' => 'Bearer ' . $api_key,
2463 - ],
2464 - 'timeout' => 60,
2465 - 'redirection' => 5,
2466 - 'blocking' => true,
2467 - 'httpversion' => '1.0',
2468 - 'sslverify' => true,
2469 - ];
2470 -
2471 - // Make the request
2472 - $response = wp_remote_post($endpoint, $args);
2473 -
2474 - // Rest of your existing code...
2475 - if (is_wp_error($response)) {
2476 - //error_log('Embedding Generation Error: ' . $response->get_error_message());
2477 - return null;
2478 - }
2479 -
2480 - $response_body = json_decode(wp_remote_retrieve_body($response), true);
2481 -
2482 - if (isset($response_body['data'][0]['embedding']) && is_array($response_body['data'][0]['embedding'])) {
2483 - return $response_body['data'][0]['embedding'];
2484 - } else {
2485 - //error_log('Invalid embedding response: ' . wp_json_encode($response_body));
2486 - return null;
2487 - }
2488 -}
2489 3051
2490 3052
2491 3053 private function mxchat_find_relevant_content($user_embedding) {
2492 - //error_log('MXChat Vector Search: Starting content search...');
3054 + error_log('MXChat Vector Search: Starting content search...');
2493 3055
2494 3056 // Retrieve the add-on settings from the database.
2495 3057 $addon_options = get_option('mxchat_pinecone_addon_options', array());
2496 3058
@@ -2497,15 +3059,15 @@
2497 3059 // Determine whether Pinecone is enabled.
2498 3060 // We expect the sanitized setting to be a string '1' if enabled, otherwise '0'.
2499 3061 $use_pinecone = (isset($addon_options['mxchat_use_pinecone']) && $addon_options['mxchat_use_pinecone'] === '1') ? 1 : 0;
2500 3062
2501 - //error_log('Pinecone enabled flag: ' . $use_pinecone);
3063 + error_log('Pinecone enabled flag: ' . $use_pinecone);
2502 3064
2503 3065 if ($use_pinecone === 1) {
2504 - //error_log('MXChat Vector Search: Using Pinecone database');
3066 + error_log('MXChat Vector Search: Using Pinecone database');
2505 3067 return $this->find_relevant_content_pinecone($user_embedding);
2506 3068 } else {
2507 - //error_log('MXChat Vector Search: Using WordPress database');
3069 + error_log('MXChat Vector Search: Using WordPress database');
2508 3070 return $this->find_relevant_content_wordpress($user_embedding);
2509 3071 }
2510 3072 }
2511 3073
@@ -2625,9 +3187,9 @@
2625 3187 $api_key = $options['mxchat_pinecone_api_key'] ?? '';
2626 3188 $host = $options['mxchat_pinecone_host'] ?? '';
2627 3189
2628 3190 if (empty($host) || empty($api_key)) {
2629 - //error_log('Pinecone credentials not properly configured');
3191 + error_log('Pinecone credentials not properly configured');
2630 3192 return '';
2631 3193 }
2632 3194
2633 3195 // Get similarity threshold from WordPress settings
@@ -2653,15 +3215,15 @@
2653 3215 'timeout' => 30
2654 3216 ));
2655 3217
2656 3218 if (is_wp_error($response)) {
2657 - //error_log('Pinecone query error: ' . $response->get_error_message());
3219 + error_log('Pinecone query error: ' . $response->get_error_message());
2658 3220 return '';
2659 3221 }
2660 3222
2661 3223 $response_code = wp_remote_retrieve_response_code($response);
2662 3224 if ($response_code !== 200) {
2663 - //error_log('Pinecone API error: ' . wp_remote_retrieve_body($response));
3225 + error_log('Pinecone API error: ' . wp_remote_retrieve_body($response));
2664 3226 return '';
2665 3227 }
2666 3228
2667 3229 $results = json_decode(wp_remote_retrieve_body($response), true);
@@ -2690,182 +3252,116 @@
2690 3252 }
2691 3253
2692 3254
2693 3255 private function mxchat_find_relevant_products($user_embedding) {
2694 - //error_log('MXChat Vector Search: Starting product search...');
3256 + global $wpdb;
3257 + $system_prompt_table = $wpdb->prefix . 'mxchat_system_prompt_content';
3258 + $cache_key = 'mxchat_system_prompt_embeddings';
3259 + $batch_size = 500;
2695 3260
2696 - // Retrieve the add-on settings from the database
2697 - $addon_options = get_option('mxchat_pinecone_addon_options', array());
3261 + // Retrieve embeddings from cache or database
3262 + $embeddings = wp_cache_get($cache_key, 'mxchat_system_prompts');
3263 + if ($embeddings === false) {
3264 + $embeddings = [];
3265 + $offset = 0;
2698 3266
2699 - // Determine whether Pinecone is enabled
2700 - $use_pinecone = (isset($addon_options['mxchat_use_pinecone']) && $addon_options['mxchat_use_pinecone'] === '1') ? 1 : 0;
3267 + // Load in batches and build cache
3268 + do {
3269 + $query = $wpdb->prepare(
3270 + "SELECT id, embedding_vector
3271 + FROM {$system_prompt_table}
3272 + LIMIT %d OFFSET %d",
3273 + $batch_size,
3274 + $offset
3275 + );
2701 3276
2702 - //error_log('Pinecone enabled flag: ' . $use_pinecone);
3277 + $batch = $wpdb->get_results($query);
3278 + if (empty($batch)) {
3279 + break;
3280 + }
2703 3281
2704 - if ($use_pinecone === 1) {
2705 - //error_log('MXChat Vector Search: Using Pinecone database for products');
2706 - return $this->find_relevant_products_pinecone($user_embedding);
2707 - } else {
2708 - //error_log('MXChat Vector Search: Using WordPress database for products');
2709 - return $this->find_relevant_products_wordpress($user_embedding);
2710 - }
2711 -}
3282 + $embeddings = array_merge($embeddings, $batch);
3283 + $offset += $batch_size;
2712 3284
2713 -private function find_relevant_products_wordpress($user_embedding) {
2714 - global $wpdb;
2715 - $system_prompt_table = $wpdb->prefix . 'mxchat_system_prompt_content';
2716 - $cache_key = 'mxchat_system_prompt_embeddings';
2717 - $batch_size = 500;
3285 + // Free memory
3286 + unset($batch);
2718 3287
2719 - // Original WordPress database search logic
2720 - // [Previous implementation remains the same]
2721 - $embeddings = wp_cache_get($cache_key, 'mxchat_system_prompts');
2722 - if ($embeddings === false) {
2723 - $embeddings = [];
2724 - $offset = 0;
3288 + } while (true);
2725 3289
2726 - do {
2727 - $query = $wpdb->prepare(
2728 - "SELECT id, embedding_vector
2729 - FROM {$system_prompt_table}
2730 - LIMIT %d OFFSET %d",
2731 - $batch_size,
2732 - $offset
2733 - );
3290 + if (empty($embeddings)) {
3291 + return ''; // Return an empty string if no embeddings found
3292 + }
3293 + wp_cache_set($cache_key, $embeddings, 'mxchat_system_prompts', 3600);
3294 + }
2734 3295
2735 - $batch = $wpdb->get_results($query);
2736 - if (empty($batch)) {
2737 - break;
2738 - }
3296 + // Initialize array to store relevant results with similarity scores
3297 + $relevant_results = [];
3298 + // Iterate through embeddings to calculate similarity
3299 + foreach ($embeddings as $embedding) {
3300 + $database_embedding = $embedding->embedding_vector
3301 + ? unserialize($embedding->embedding_vector, ['allowed_classes' => false])
3302 + : null;
3303 + if (is_array($database_embedding) && is_array($user_embedding)) {
3304 + $similarity = $this->mxchat_calculate_cosine_similarity($user_embedding, $database_embedding);
3305 + $relevant_results[] = [
3306 + 'id' => $embedding->id,
3307 + 'similarity' => $similarity
3308 + ];
3309 + }
3310 + // Free memory
3311 + unset($database_embedding);
3312 + }
2739 3313
2740 - $embeddings = array_merge($embeddings, $batch);
2741 - $offset += $batch_size;
3314 + // Use a fixed similarity threshold of 0.85
3315 + $similarity_threshold = 0.85;
2742 3316
2743 - unset($batch);
3317 + // Filter and sort relevant results by similarity
3318 + $relevant_results = array_filter($relevant_results, function ($result) use ($similarity_threshold) {
3319 + return $result['similarity'] >= $similarity_threshold;
3320 + });
3321 + usort($relevant_results, function ($a, $b) {
3322 + return $b['similarity'] <=> $a['similarity'];
3323 + });
2744 3324
2745 - } while (true);
3325 + // Limit to the top 5 results
3326 + $top_results = array_slice($relevant_results, 0, 5);
2746 3327
2747 - if (empty($embeddings)) {
2748 - return '';
2749 - }
2750 - wp_cache_set($cache_key, $embeddings, 'mxchat_system_prompts', 3600);
2751 - }
3328 + // Initialize the final content
3329 + $content = '';
2752 3330
2753 - $relevant_results = [];
2754 - foreach ($embeddings as $embedding) {
2755 - $database_embedding = $embedding->embedding_vector
2756 - ? unserialize($embedding->embedding_vector, ['allowed_classes' => false])
2757 - : null;
2758 - if (is_array($database_embedding) && is_array($user_embedding)) {
2759 - $similarity = $this->mxchat_calculate_cosine_similarity($user_embedding, $database_embedding);
2760 - $relevant_results[] = [
2761 - 'id' => $embedding->id,
2762 - 'similarity' => $similarity
2763 - ];
2764 - }
2765 - unset($database_embedding);
2766 - }
3331 + // Fetch and combine content for the top results
3332 + foreach ($top_results as $result) {
3333 + $chunk_content = $this->fetch_content_with_product_links($result['id']);
3334 + // Check if the content is PDF-related and add surrounding pages
3335 + if (strpos($chunk_content, '{"document_type":"pdf"') !== false) {
3336 + $surrounding_content = $wpdb->get_results($wpdb->prepare(
3337 + "SELECT article_content FROM {$system_prompt_table}
3338 + WHERE id IN (
3339 + (SELECT id FROM {$system_prompt_table} WHERE id < %d ORDER BY id DESC LIMIT 1),
3340 + (SELECT id FROM {$system_prompt_table} WHERE id > %d ORDER BY id ASC LIMIT 1)
3341 + )",
3342 + $result['id'],
3343 + $result['id']
3344 + ));
3345 + // Add previous content if it exists
3346 + if (!empty($surrounding_content[0])) {
3347 + $content .= $surrounding_content[0]->article_content . "\n\n";
3348 + }
3349 + // Add the main chunk content
3350 + $content .= $chunk_content . "\n\n";
3351 + // Add next content if it exists
3352 + if (!empty($surrounding_content[1])) {
3353 + $content .= $surrounding_content[1]->article_content . "\n\n";
3354 + }
3355 + } else {
3356 + // For non-PDF content, add directly
3357 + $content .= $chunk_content . "\n\n";
3358 + }
3359 + }
2767 3360
2768 - // Use fixed threshold for products
2769 - $similarity_threshold = 0.85;
2770 -
2771 - $relevant_results = array_filter($relevant_results, function ($result) use ($similarity_threshold) {
2772 - return $result['similarity'] >= $similarity_threshold;
2773 - });
2774 - usort($relevant_results, function ($a, $b) {
2775 - return $b['similarity'] <=> $a['similarity'];
2776 - });
2777 -
2778 - $top_results = array_slice($relevant_results, 0, 5);
2779 - $content = '';
2780 -
2781 - foreach ($top_results as $result) {
2782 - $chunk_content = $this->fetch_content_with_product_links($result['id']);
2783 - $content .= $chunk_content . "\n\n";
2784 - }
2785 -
2786 - return trim($content);
3361 + return trim($content);
2787 3362 }
2788 3363
2789 -// Modified search function with correct filter syntax
2790 -private function find_relevant_products_pinecone($user_embedding) {
2791 - //error_log('Starting Pinecone product search...');
2792 -
2793 - $options = get_option('mxchat_pinecone_addon_options', array());
2794 - $api_key = $options['mxchat_pinecone_api_key'] ?? '';
2795 - $host = $options['mxchat_pinecone_host'] ?? '';
2796 -
2797 - if (empty($host) || empty($api_key)) {
2798 - //error_log('Pinecone credentials not properly configured for product search');
2799 - return '';
2800 - }
2801 -
2802 - $similarity_threshold = 0.85;
2803 - $api_endpoint = "https://{$host}/query";
2804 -
2805 - $request_body = array(
2806 - 'vector' => $user_embedding,
2807 - 'topK' => 5,
2808 - 'includeMetadata' => true,
2809 - 'includeValues' => true,
2810 - 'filter' => array(
2811 - 'type' => 'product'
2812 - )
2813 - );
2814 -
2815 - //error_log('Sending request to Pinecone with body: ' . wp_json_encode($request_body));
2816 -
2817 - $response = wp_remote_post($api_endpoint, array(
2818 - 'headers' => array(
2819 - 'Api-Key' => $api_key,
2820 - 'accept' => 'application/json',
2821 - 'content-type' => 'application/json'
2822 - ),
2823 - 'body' => wp_json_encode($request_body),
2824 - 'timeout' => 30
2825 - ));
2826 -
2827 - if (is_wp_error($response)) {
2828 - //error_log('Pinecone product query error: ' . $response->get_error_message());
2829 - return '';
2830 - }
2831 -
2832 - $response_code = wp_remote_retrieve_response_code($response);
2833 - //error_log('Pinecone response code: ' . $response_code);
2834 -
2835 - if ($response_code !== 200) {
2836 - //error_log('Pinecone API error during product search: ' . wp_remote_retrieve_body($response));
2837 - return '';
2838 - }
2839 -
2840 - $results = json_decode(wp_remote_retrieve_body($response), true);
2841 - //error_log('Pinecone raw response: ' . wp_remote_retrieve_body($response));
2842 -
2843 - if (empty($results['matches'])) {
2844 - //error_log('No matches found in Pinecone response');
2845 - return '';
2846 - }
2847 -
2848 - $content = '';
2849 - foreach ($results['matches'] as $match) {
2850 - if ($match['score'] < $similarity_threshold) {
2851 - //error_log("Match below threshold: " . $match['score']);
2852 - continue;
2853 - }
2854 -
2855 - if (!empty($match['metadata']['text'])) {
2856 - $content .= $match['metadata']['text'];
2857 - if (!empty($match['metadata']['source_url'])) {
2858 - $content .= "\n\nFor more details, check out this product: " . esc_url($match['metadata']['source_url']);
2859 - }
2860 - $content .= "\n\n";
2861 - }
2862 - }
2863 -
2864 - return trim($content);
2865 -}
2866 -
2867 -
2868 3364 private function fetch_content_with_product_links($most_relevant_id) {
2869 3365 global $wpdb;
2870 3366 $system_prompt_table = $wpdb->prefix . 'mxchat_system_prompt_content';
2871 3367
@@ -2962,9 +3458,9 @@
2962 3458 $relevant_content
2963 3459 );
2964 3460 }
2965 3461 } catch (Exception $e) {
2966 - //error_log('MXChat Error: ' . $e->getMessage());
3462 + error_log('MXChat Error: ' . $e->getMessage());
2967 3463 return sprintf(
2968 3464 esc_html__('An error occurred: %s', 'mxchat'),
2969 3465 esc_html($e->getMessage())
2970 3466 );
@@ -3032,9 +3528,9 @@
3032 3528
3033 3529 $response = wp_remote_post('https://api.deepseek.com/v1/chat/completions', $args);
3034 3530
3035 3531 if (is_wp_error($response)) {
3036 - //error_log('DeepSeek API Error: ' . $response->get_error_message());
3532 + error_log('DeepSeek API Error: ' . $response->get_error_message());
3037 3533 return "Sorry, there was an error processing your request.";
3038 3534 }
3039 3535
3040 3536 $response_body = wp_remote_retrieve_body($response);
@@ -3042,12 +3538,13 @@
3042 3538
3043 3539 if (isset($decoded_response['choices'][0]['message']['content'])) {
3044 3540 return trim($decoded_response['choices'][0]['message']['content']);
3045 3541 } else {
3046 - //error_log('DeepSeek API Response Format Error: ' . print_r($decoded_response, true));
3542 + error_log('DeepSeek API Response Format Error: ' . print_r($decoded_response, true));
3047 3543 return "Sorry, I couldn't process that request.";
3048 3544 }
3049 3545 }
3546 +
3050 3547 private function mxchat_generate_response_openai($selected_model, $api_key, $conversation_history, $relevant_content) {
3051 3548 // Ensure conversation_history is an array
3052 3549 if (!is_array($conversation_history)) {
3053 3550 $conversation_history = array();
@@ -3107,9 +3604,9 @@
3107 3604
3108 3605 $response = wp_remote_post('https://api.openai.com/v1/chat/completions', $args);
3109 3606
3110 3607 if (is_wp_error($response)) {
3111 - //error_log('OpenAI API Error: ' . $response->get_error_message());
3608 + error_log('OpenAI API Error: ' . $response->get_error_message());
3112 3609 return "Sorry, there was an error processing your request.";
3113 3610 }
3114 3611
3115 3612 $response_body = wp_remote_retrieve_body($response);
@@ -3117,9 +3614,9 @@
3117 3614
3118 3615 if (isset($decoded_response['choices'][0]['message']['content'])) {
3119 3616 return trim($decoded_response['choices'][0]['message']['content']);
3120 3617 } else {
3121 - //error_log('OpenAI API Response Format Error: ' . print_r($decoded_response, true));
3618 + error_log('OpenAI API Response Format Error: ' . print_r($decoded_response, true));
3122 3619 return "Sorry, I couldn't process that request.";
3123 3620 }
3124 3621 }
3125 3622 private function mxchat_generate_response_xai($selected_model, $xai_api_key, $conversation_history, $relevant_content) {
@@ -3191,111 +3688,85 @@
3191 3688 } else {
3192 3689 return "Sorry, I couldn't process that request.";
3193 3690 }
3194 3691 }
3692 +
3195 3693 private function mxchat_generate_response_claude($selected_model, $claude_api_key, $conversation_history, $relevant_content) {
3196 - // Get system prompt instructions from options
3694 + // Get system prompt instructions from options for Claude's top-level system parameter
3197 3695 $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : '';
3198 3696
3199 - // Clean and validate conversation history
3697 + // Ensure consistency: Replace 'bot' and 'agent' roles with supported values
3200 3698 foreach ($conversation_history as &$message) {
3201 - // Convert bot and agent roles to assistant
3202 - if ($message['role'] === 'bot' || $message['role'] === 'agent') {
3699 + if ($message['role'] === 'bot') {
3203 3700 $message['role'] = 'assistant';
3701 + } elseif ($message['role'] === 'agent') {
3702 + // Tag the message as coming from a live agent
3703 + $message['role'] = 'assistant';
3704 + if (!isset($message['metadata'])) {
3705 + $message['metadata'] = ['source' => 'live_agent'];
3706 + }
3204 3707 }
3205 -
3206 - // Remove unsupported roles - Claude only supports 'assistant' and 'user'
3207 - if (!in_array($message['role'], ['assistant', 'user'])) {
3208 - $message['role'] = 'user';
3209 - }
3210 3708
3211 - // Ensure content field exists
3212 - if (!isset($message['content']) || empty($message['content'])) {
3213 - $message['content'] = '';
3709 + // Ensure all roles are valid
3710 + if (!in_array($message['role'], ['system', 'assistant', 'user', 'function', 'tool'])) {
3711 + $message['role'] = 'user'; // Default to 'user'
3214 3712 }
3215 -
3216 - // Remove any unsupported fields
3217 - $message = array_intersect_key($message, array_flip(['role', 'content']));
3218 3713 }
3219 3714
3220 - // Add relevant content as the latest user message
3715 + // Add relevant content as the latest user message in conversation history
3221 3716 $conversation_history[] = [
3222 3717 'role' => 'user',
3223 3718 'content' => $relevant_content
3224 3719 ];
3225 3720
3226 - // Build request body
3721 + // Build the request body with Claude's expected structure, using system instructions as a top-level parameter
3227 3722 $body = json_encode([
3228 3723 'model' => $selected_model,
3229 3724 'max_tokens' => 1000,
3230 3725 'temperature' => 0.8,
3231 - 'messages' => $conversation_history,
3232 - 'system' => $system_prompt_instructions
3726 + 'system' => $system_prompt_instructions, // Set the system prompt at the top level as required
3727 + 'messages' => $conversation_history
3233 3728 ]);
3234 3729
3235 - // Set up API request
3730 + // Set up the API request with the necessary headers
3236 3731 $args = [
3237 - 'body' => $body,
3238 - 'headers' => [
3239 - 'Content-Type' => 'application/json',
3240 - 'x-api-key' => $claude_api_key,
3241 - 'anthropic-version' => '2023-06-01'
3242 - ],
3243 - 'timeout' => 60,
3732 + 'body' => $body,
3733 + 'headers' => [
3734 + 'Content-Type' => 'application/json',
3735 + 'x-api-key' => $claude_api_key,
3736 + 'anthropic-version' => '2023-06-01',
3737 + ],
3738 + 'timeout' => 60,
3244 3739 'redirection' => 5,
3245 - 'blocking' => true,
3740 + 'blocking' => true,
3246 3741 'httpversion' => '1.0',
3247 - 'sslverify' => true,
3742 + 'sslverify' => true,
3248 3743 ];
3249 3744
3250 - // Make API request
3745 + // Make the API request
3251 3746 $response = wp_remote_post('https://api.anthropic.com/v1/messages', $args);
3252 3747
3253 - // Check for WordPress errors
3748 +/*
3749 + // Check for errors and log the response for debugging
3254 3750 if (is_wp_error($response)) {
3255 - //error_log("Claude API request error: " . $response->get_error_message());
3256 - return "Sorry, there was an error connecting to the API.";
3751 + error_log("Claude API request error: " . print_r($response->get_error_message(), true));
3752 + return "Sorry, there was an error processing your request.";
3257 3753 }
3754 +*/
3258 3755
3259 - // Check HTTP response code
3260 - $http_code = wp_remote_retrieve_response_code($response);
3261 - if ($http_code !== 200) {
3262 - $error_body = wp_remote_retrieve_body($response);
3263 - //error_log("Claude API HTTP error: " . $http_code . " - " . $error_body);
3264 -
3265 - // Try to extract error message from response
3266 - $error_data = json_decode($error_body, true);
3267 - $error_message = isset($error_data['error']['message']) ?
3268 - $error_data['error']['message'] :
3269 - "HTTP error " . $http_code;
3270 -
3271 - return "Sorry, the API returned an error: " . $error_message;
3272 - }
3273 3756
3274 - // Parse response
3757 + // Decode the response and parse according to the expected Claude response structure
3275 3758 $response_body = json_decode(wp_remote_retrieve_body($response), true);
3276 -
3277 - // Check for JSON decode errors
3278 - if (json_last_error() !== JSON_ERROR_NONE) {
3279 - //error_log("Claude API JSON decode error: " . json_last_error_msg());
3280 - return "Sorry, there was an error processing the API response.";
3281 - }
3759 + //error_log("Claude API response: " . print_r($response_body, true));
3282 3760
3283 - // Extract and validate response content
3284 - if (isset($response_body['content']) &&
3285 - is_array($response_body['content']) &&
3286 - !empty($response_body['content']) &&
3287 - isset($response_body['content'][0]['text'])) {
3761 + // Check if the response has the expected 'content' array with 'text' blocks
3762 + if (isset($response_body['content'][0]['text'])) {
3288 3763 return trim($response_body['content'][0]['text']);
3764 + } else {
3765 + return "Sorry, I couldn't process that request.";
3289 3766 }
3290 -
3291 - // Log unexpected response format
3292 - //error_log("Claude API unexpected response format: " . print_r($response_body, true));
3293 - return "Sorry, I received an unexpected response format from the API.";
3294 3767 }
3295 3768
3296 -
3297 -
3298 3769 public function mxchat_dismiss_pre_chat_message() {
3299 3770 // Get and sanitize the user identifier
3300 3771 $user_id = $this->mxchat_get_user_identifier();
3301 3772 $user_id = sanitize_key($user_id);
@@ -3351,10 +3822,10 @@
3351 3822 }
3352 3823
3353 3824 public function mxchat_enqueue_scripts_styles() {
3354 3825 // Define version numbers for the styles and scripts
3355 - $chat_style_version = '2.0.6'; // Replace with your actual version
3356 - $chat_script_version = '2.0.6'; // Replace with your actual version
3826 + $chat_style_version = '2.0.1'; // Replace with your actual version
3827 + $chat_script_version = '2.0.1'; // Replace with your actual version
3357 3828
3358 3829 // Enqueue the script
3359 3830 wp_enqueue_script(
3360 3831 'mxchat-chat-js',