| @@ -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 .= " \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', |