options = get_option('mxchat_options'); $this->chat_count = get_option('mxchat_chat_count', 0); $this->word_handler = new MXChat_Word_Handler($this->options); // Add WooCommerce hooks add_action('wp_insert_post', array($this, 'mxchat_handle_product_change'), 10, 3); // Ensure embeddings are removed when a product is moved to trash or permanently deleted add_action('wp_trash_post', array($this, 'mxchat_handle_product_delete')); add_action('before_delete_post', array($this, 'mxchat_handle_product_delete')); add_action('wp_enqueue_scripts', array($this, 'mxchat_enqueue_scripts_styles')); add_action('wp_ajax_mxchat_handle_chat_request', array($this, 'mxchat_handle_chat_request')); add_action('wp_ajax_nopriv_mxchat_handle_chat_request', array($this, 'mxchat_handle_chat_request')); add_action('wp_ajax_mxchat_dismiss_pre_chat_message', array($this, 'mxchat_dismiss_pre_chat_message')); add_action('wp_ajax_nopriv_mxchat_dismiss_pre_chat_message', array($this, 'mxchat_dismiss_pre_chat_message')); // Add the AJAX actions for checking if the pre-chat message was dismissed add_action('wp_ajax_mxchat_check_pre_chat_message_status', array($this, 'mxchat_check_pre_chat_message_status')); add_action('wp_ajax_nopriv_mxchat_check_pre_chat_message_status', array($this, 'mxchat_check_pre_chat_message_status')); add_action('wp_ajax_mxchat_fetch_conversation_history', [$this, 'mxchat_fetch_conversation_history']); add_action('wp_ajax_nopriv_mxchat_fetch_conversation_history', [$this, 'mxchat_fetch_conversation_history']); add_action('wp_ajax_mxchat_add_to_cart', [$this, 'mxchat_add_to_cart']); add_action('wp_ajax_nopriv_mxchat_add_to_cart', [$this, 'mxchat_add_to_cart']); if (!wp_next_scheduled('mxchat_reset_rate_limits')) { wp_schedule_event(time(), 'daily', 'mxchat_reset_rate_limits'); } // Add REST API routes registration add_action('rest_api_init', array($this, 'register_routes')); add_action('wp_ajax_mxchat_fetch_new_messages', array($this, 'mxchat_fetch_new_messages')); add_action('wp_ajax_nopriv_mxchat_fetch_new_messages', array($this, 'mxchat_fetch_new_messages')); add_action('mxchat_reset_rate_limits', array($this, 'mxchat_reset_rate_limits')); add_action('wp_ajax_mxchat_upload_pdf', [$this, 'handle_pdf_upload']); add_action('wp_ajax_nopriv_mxchat_upload_pdf', [$this, 'handle_pdf_upload']); add_action('wp_ajax_mxchat_remove_pdf', [$this, 'handle_pdf_remove']); add_action('wp_ajax_nopriv_mxchat_remove_pdf', [$this, 'handle_pdf_remove']); // Add these with your other add_action hooks add_action('wp_ajax_mxchat_upload_word', array($this, 'mxchat_handle_word_upload')); add_action('wp_ajax_nopriv_mxchat_upload_word', array($this, 'mxchat_handle_word_upload')); add_action('wp_ajax_mxchat_remove_word', array($this, 'mxchat_handle_word_remove')); add_action('wp_ajax_nopriv_mxchat_remove_word', array($this, 'mxchat_handle_word_remove')); add_action('wp_ajax_mxchat_check_word_status', array($this, 'mxchat_check_word_status')); add_action('wp_ajax_nopriv_mxchat_check_word_status', array($this, 'mxchat_check_word_status')); add_action('wp_ajax_nopriv_mxchat_handle_save_email_and_response', [$this, 'mxchat_handle_save_email_and_response']); add_action('wp_ajax_mxchat_handle_save_email_and_response', [$this, 'mxchat_handle_save_email_and_response']); add_action('wp_ajax_nopriv_mxchat_check_email_provided', [$this, 'mxchat_check_email_provided']); add_action('wp_ajax_mxchat_check_email_provided', [$this, 'mxchat_check_email_provided']); } public function mxchat_handle_product_change($post_id, $post, $update) { // Ensure this is a product post type if ($post->post_type !== 'product') { return; } // Only generate embeddings if the product is published if ($post->post_status === 'publish') { // Delay the embedding slightly to ensure all product data is available add_action('shutdown', function() use ($post_id) { $product = wc_get_product($post_id); if ($product && $product->get_price() !== '') { $this->mxchat_store_product_embedding($product); } else { // Optionally, log or handle the case where product data is incomplete // error_log("Product {$post_id} does not have complete data. Embedding not generated."); } }); } } public function mxchat_handle_product_delete($post_id) { if (get_post_type($post_id) !== 'product') { return; } global $wpdb; $table_name = $wpdb->prefix . 'mxchat_system_prompt_content'; // Delete the embedding associated with this product $wpdb->delete($table_name, array('source_url' => get_permalink($post_id)), array('%s')); } private function mxchat_store_product_embedding($product) { if (isset($this->options['enable_woocommerce_integration']) && ($this->options['enable_woocommerce_integration'] === '1' || $this->options['enable_woocommerce_integration'] === 'on')) { $source_url = get_permalink($product->get_id()); $regular_price = $product->get_regular_price(); $sale_price = $product->get_sale_price(); $price = $sale_price ?: $regular_price; $description = $product->get_description() . "\n\n" . "Short Description: " . $product->get_short_description() . "\n" . "Price: " . $regular_price . "\n" . "Sale Price: " . ($sale_price ?: 'N/A') . "\n" . "SKU: " . $product->get_sku(); global $wpdb; $table_name = $wpdb->prefix . 'mxchat_system_prompt_content'; // Delete any existing embedding for this product $wpdb->delete($table_name, array('source_url' => $source_url), array('%s')); // Submit the new content and embedding to the database MxChat_Utils::submit_content_to_db($description, $source_url, $this->options['api_key']); } } private function mxchat_increment_chat_count() { $chat_count = get_option('mxchat_chat_count', 0); $chat_count++; update_option('mxchat_chat_count', $chat_count); } function mxchat_fetch_conversation_history() { if (empty($_POST['session_id'])) { wp_send_json_error(['message' => 'Session ID missing.']); wp_die(); } $session_id = sanitize_text_field($_POST['session_id']); $history = get_option("mxchat_history_{$session_id}", []); // Retrieve stored history $chat_mode = get_option("mxchat_mode_{$session_id}", 'ai'); // Get current chat mode if (empty($history)) { // Even if history is empty, return the chat mode wp_send_json_success([ 'conversation' => [], 'chat_mode' => $chat_mode ]); wp_die(); } wp_send_json_success([ 'conversation' => $history, 'chat_mode' => $chat_mode ]); wp_die(); } private function mxchat_fetch_conversation_history_for_ajax($session_id) { $history = get_option("mxchat_history_{$session_id}", []); // Retrieve stored history based on session ID $formatted_history = []; // Format the history to align with the expected structure for OpenAI foreach ($history as $entry) { $formatted_history[] = [ 'role' => $entry['role'], // Ensure this matches 'user' or 'assistant' 'content' => $entry['content'] ]; } return $formatted_history; } private function mxchat_fetch_conversation_history_for_ai($session_id) { $history = get_option("mxchat_history_{$session_id}", []); $formatted_history = []; $max_tokens = 120000; // Adjust based on your model's context window $reserved_tokens = 4000; // Reserve tokens for current query and system/context prompt $current_token_count = 0; foreach ($history as $entry) { // Skip messages containing HTML if ($entry['content'] !== strip_tags($entry['content'])) { continue; } // Calculate tokens for this entry $tokens_in_entry = str_word_count($entry['content']) * 1.5; // Rough estimate: ~1.5 tokens per word $current_token_count += $tokens_in_entry; // If adding this entry exceeds the limit, stop if ($current_token_count + $reserved_tokens > $max_tokens) { break; } $formatted_history[] = [ 'role' => $entry['role'], 'content' => $entry['content'] ]; } return $formatted_history; } public function register_routes() { //error_log('Registering MxChat REST routes'); register_rest_route('mxchat/v1', '/stream', [ 'methods' => 'GET', 'callback' => [$this, 'mxchat_stream_events'], 'permission_callback' => [$this, 'verify_chat_session'], ]); register_rest_route('mxchat/v1', '/agent-response', [ 'methods' => 'POST', 'callback' => [$this, 'mxchat_handle_agent_response'], 'permission_callback' => [$this, 'verify_slack_request'], ]); register_rest_route('mxchat/v1', '/slack-interaction', [ 'methods' => 'POST', 'callback' => [$this, 'handle_slack_interaction'], 'permission_callback' => [$this, 'verify_slack_request'], ]); //error_log('MxChat REST routes registered'); } /** * Verify valid chat session */ public function verify_chat_session($request) { $session_id = $request->get_param('session_id'); if (empty($session_id)) { //error_log('Empty session ID in chat request'); return false; } $chat_mode = get_option("mxchat_mode_{$session_id}", 'ai'); return $chat_mode === 'agent'; } /** * Verify request is coming from Slack. * * @param WP_REST_Request $request * @return bool True if valid, false otherwise. */ public function verify_slack_request($request) { // Get the Slack signing secret from your plugin options $valid_key = $this->options['live_agent_secret_key'] ?? ''; if (empty($valid_key)) { //error_log('Slack signing secret not configured'); return false; } $timestamp = $request->get_header('X-Slack-Request-Timestamp'); $slack_signature = $request->get_header('X-Slack-Signature'); // Verify timestamp to prevent replay attacks if (abs(time() - intval($timestamp)) > 300) { //error_log('Slack request timestamp too old'); return false; } // Get raw request body $request_body = file_get_contents('php://input'); // Create the signature base string $sig_basestring = "v0:{$timestamp}:{$request_body}"; // Calculate expected signature $my_signature = 'v0=' . hash_hmac('sha256', $sig_basestring, $valid_key); // Compare signatures return hash_equals($my_signature, $slack_signature); } public function mxchat_stream_events(WP_REST_Request $request) { header('Content-Type: text/event-stream'); header('Cache-Control: no-cache'); header('Connection: keep-alive'); $session_id = sanitize_text_field($request->get_param('session_id')); $last_seen_id = sanitize_text_field($request->get_param('last_seen_id')) ?: ''; if (empty($session_id)) { echo "event: error\ndata: Missing session_id\n\n"; flush(); exit; } $history = get_option("mxchat_history_{$session_id}", []); // Filter only new messages $new_messages = array_filter($history, function ($message) use ($last_seen_id) { return !empty($message['id']) && $message['id'] > $last_seen_id; }); // Send new messages if available if (!empty($new_messages)) { echo "event: newMessages\ndata: " . json_encode(array_values($new_messages)) . "\n\n"; } else { // Keep the connection alive echo "event: keepAlive\ndata: {}\n\n"; } flush(); exit; } private function mxchat_save_chat_message($session_id, $role, $message) { global $wpdb; $table_name = $wpdb->prefix . 'mxchat_chat_transcripts'; //error_log("[DEBUG] mxchat_save_chat_message -> START for session_id: {$session_id}, role: {$role}"); // 1) Extract agent name if present $agent_name = ''; if (preg_match('/^Agent: (.*?) - /', $message, $matches)) { $agent_name = $matches[1]; $message = str_replace("Agent: $agent_name - ", '', $message); $session_meta_key = "mxchat_agent_name_{$session_id}"; if (empty(get_option($session_meta_key))) { update_option($session_meta_key, $agent_name); //error_log("[DEBUG] mxchat_save_chat_message -> Stored agent_name in option: {$session_meta_key} => {$agent_name}"); } } // 2) Generate unique message_id $message_id = uniqid(); //error_log("[DEBUG] mxchat_save_chat_message -> Generated message_id: {$message_id}"); // 3) Determine user_id $user_id = is_user_logged_in() ? get_current_user_id() : 0; // 4) Determine user_identifier $user_identifier = $agent_name ? $agent_name : MxChat_User::mxchat_get_user_identifier(); // 5) Determine displayed_name $user_email = MxChat_User::mxchat_get_user_email(); $displayed_name = $agent_name ? $agent_name : ($user_email ?: $user_identifier); // 6) Check for a saved email in wp_options $email_option_key = "mxchat_email_{$session_id}"; $saved_email = get_option($email_option_key); //error_log("[DEBUG] mxchat_save_chat_message -> Checking wp_options for email_option_key: {$email_option_key}, found: {$saved_email}"); // If found, update DB user_email if ($saved_email) { $update_res = $wpdb->update( $table_name, ['user_email' => $saved_email], ['session_id' => $session_id], ['%s'], ['%s'] ); //error_log("[DEBUG] mxchat_save_chat_message -> Attempted DB user_email update for session_id {$session_id}. update_res: {$update_res}"); } // 7) Save to session history in wp_options $history_key = "mxchat_history_{$session_id}"; $history = get_option($history_key, []); $history[] = [ 'id' => $message_id, 'role' => $role, 'content' => $message, 'timestamp' => round(microtime(true) * 1000), 'agent_name' => $displayed_name, ]; update_option($history_key, $history); //error_log("[DEBUG] mxchat_save_chat_message -> Updated session history in option: {$history_key}"); // 8) Save the message to DB (INSERT) $insert_data = [ 'user_id' => $user_id, 'user_identifier'=> $user_identifier, 'user_email' => $saved_email ?: $user_email, 'session_id' => $session_id, 'role' => $role, 'message' => $message, 'timestamp' => current_time('mysql', 1), ]; $wpdb->insert($table_name, $insert_data); //error_log("[DEBUG] mxchat_save_chat_message -> Inserted message into DB. row_id: {$wpdb->insert_id}, data: " . print_r($insert_data, true)); //error_log("[DEBUG] mxchat_save_chat_message -> END for session_id: {$session_id}"); return $message_id; } public function mxchat_handle_save_email_and_response() { //error_log('[DEBUG] ---------- mxchat_handle_save_email_and_response START ----------'); // Validate nonce if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'mxchat_chat_nonce')) { error_log('[ERROR] Invalid nonce in mxchat_handle_save_email_and_response'); wp_send_json_error(['message' => 'Invalid nonce.']); wp_die(); } $session_id = isset($_POST['session_id']) ? sanitize_text_field($_POST['session_id']) : ''; $email = isset($_POST['email']) ? sanitize_email($_POST['email']) : ''; //error_log("[DEBUG] handle_save_email_and_response -> session_id: {$session_id}, email: {$email}"); if (empty($session_id) || empty($email)) { //error_log("[ERROR] Missing session_id or email: session_id={$session_id}, email={$email}"); wp_send_json_error(['message' => 'Session ID or email is missing.']); wp_die(); } // 1) Always store in wp_options $option_key = "mxchat_email_{$session_id}"; update_option($option_key, $email); //error_log("[DEBUG] handle_save_email_and_response -> updated option: {$option_key} => {$email}"); // 2) (Optional) Also store in DB if a row already exists global $wpdb; $table_name = $wpdb->prefix . 'mxchat_chat_transcripts'; // Make sure we have a valid placeholder in prepare $sql = $wpdb->prepare("SELECT COUNT(*) FROM {$table_name} WHERE session_id = %s", $session_id); $session_count = $wpdb->get_var($sql); //error_log("[DEBUG] handle_save_email_and_response -> session_count for {$session_id}: {$session_count} (SQL: {$sql})"); if ($session_count) { // Update user_email if row(s) exist $update_sql = $wpdb->prepare( "UPDATE {$table_name} SET user_email = %s WHERE session_id = %s", $email, $session_id ); $wpdb->query($update_sql); //error_log("[DEBUG] handle_save_email_and_response -> DB updated: {$update_sql}"); } else { //error_log("[INFO] handle_save_email_and_response -> No DB entry for {$session_id}, so email is only in wp_options."); } // Provide success response $bot_message = 'Thanks for providing your email! You can continue chatting now.'; //error_log("[DEBUG] handle_save_email_and_response -> success, returning bot_message: {$bot_message}"); wp_send_json_success(['message' => $bot_message]); wp_die(); } public function mxchat_check_email_provided() { //error_log('[DEBUG] ---------- mxchat_check_email_provided START ----------'); if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'mxchat_chat_nonce')) { //error_log('[ERROR] Invalid nonce in mxchat_check_email_provided'); wp_send_json_error(['message' => 'Invalid nonce']); } $session_id = isset($_POST['session_id']) ? sanitize_text_field($_POST['session_id']) : ''; if (empty($session_id)) { //error_log('[ERROR] No session ID provided in mxchat_check_email_provided'); wp_send_json_error(['message' => 'No session ID provided']); } // Check if the user is logged in if (is_user_logged_in()) { $current_user = wp_get_current_user(); //error_log("[DEBUG] User is logged in as {$current_user->user_email}"); wp_send_json_success(['logged_in' => true, 'email' => $current_user->user_email]); } $option_key = "mxchat_email_{$session_id}"; $stored_email = get_option($option_key, ''); //error_log("[DEBUG] mxchat_check_email_provided -> Checking option: {$option_key}, found: {$stored_email}"); if (!empty($stored_email)) { //error_log("[DEBUG] mxchat_check_email_provided -> Email found, returning success"); wp_send_json_success(['email' => $stored_email]); } else { //error_log("[DEBUG] mxchat_check_email_provided -> No email found, returning error"); wp_send_json_error(['message' => 'No email found']); } } // First, add this helper function to get the highest rate limit for a user's roles private function get_user_role_rate_limit($user_id) { error_log("Checking rate limit for user ID: " . $user_id); if (!$user_id) { error_log("No user ID provided, using logged-out limit: " . ($this->options['rate_limit_logged_out'] ?? '10')); return $this->options['rate_limit_logged_out'] ?? '10'; } $user = get_userdata($user_id); if (!$user || !$user->roles) { error_log("No user data or roles found, using logged-out limit: " . ($this->options['rate_limit_logged_out'] ?? '10')); return $this->options['rate_limit_logged_out'] ?? '10'; } error_log("User roles: " . print_r($user->roles, true)); error_log("Available role rate limits: " . print_r($this->options['role_rate_limits'] ?? [], true)); $max_limit = 0; foreach ($user->roles as $role) { error_log("Checking limit for role: " . $role); if (isset($this->options['role_rate_limits'][$role])) { $role_limit = $this->options['role_rate_limits'][$role]; error_log("Found limit for role {$role}: {$role_limit}"); if ($role_limit === 'unlimited') { error_log("Returning unlimited for role: " . $role); return 'unlimited'; } $max_limit = max($max_limit, (int)$role_limit); error_log("Current max limit: " . $max_limit); } else { error_log("No limit found for role: " . $role); } } $final_limit = $max_limit > 0 ? (string)$max_limit : '100'; error_log("Final rate limit: " . $final_limit); return $final_limit; } public function mxchat_handle_chat_request() { global $wpdb; // Reset fallback response at the start of each request $this->fallbackResponse = ['text' => '', 'html' => '', 'images' => []]; $this->productCardHtml = ''; // Get the actual WordPress user ID if logged in $is_logged_in = is_user_logged_in(); if ($is_logged_in) { $user_id = get_current_user_id(); // This will get the actual WordPress user ID } else { // For logged-out users, use your existing identifier method $user_id = $this->mxchat_get_user_identifier(); } // Get and sanitize the user identifier $user_id = sanitize_key($user_id); // Determine if user is logged in $is_logged_in = is_user_logged_in(); error_log("User logged in status: " . ($is_logged_in ? 'true' : 'false')); // Get rate limit based on user status // Get rate limit based on user status $rate_limit = $is_logged_in ? $this->get_user_role_rate_limit($user_id) : ($this->options['rate_limit_logged_out'] ?? '10'); error_log("Selected rate limit: " . $rate_limit); // Rest of your code remains the same // If rate limit is 'unlimited', skip rate limiting checks if ($rate_limit !== 'unlimited') { // Convert rate limit to integer $rate_limit = intval($rate_limit); // Setup rate limiting $rate_limit_transient_key = 'mxchat_chat_limit_' . $user_id; $chat_count = get_transient($rate_limit_transient_key); if ($chat_count === false) { // Initialize new counter if none exists $chat_count = 0; } // Check if user has exceeded their rate limit if ($chat_count >= $rate_limit) { // Get custom rate limit message or use default $rate_limit_message = isset($this->options['rate_limit_message']) ? $this->options['rate_limit_message'] : 'Rate limit exceeded. Please try again later.'; // Replace placeholder if it exists in the message $rate_limit_message = str_replace( array('{limit}', '{count}', '{remaining}'), array($rate_limit, $chat_count, max(0, $rate_limit - $chat_count)), $rate_limit_message ); wp_send_json([ 'success' => false, 'message' => $rate_limit_message, 'status' => 'rate_limit_exceeded', 'limit' => $rate_limit, 'count' => $chat_count ]); wp_die(); } // Increment the counter $chat_count++; // Store the updated count with 24-hour expiration set_transient($rate_limit_transient_key, $chat_count, DAY_IN_SECONDS); } // Rest of your existing code... $session_id = isset($_POST['session_id']) ? sanitize_text_field($_POST['session_id']) : ''; //error_log("Session ID: $session_id"); if (empty($session_id)) { //error_log("Error: Session ID is missing."); wp_send_json_error('Session ID is missing.'); wp_die(); } // Validate and sanitize the incoming message if (empty($_POST['message'])) { //error_log("Error: No message received."); wp_send_json_error('No message received.'); wp_die(); } $message = wp_strip_all_tags($_POST['message'], false); $message = trim($message); // Save the user's message $this->mxchat_save_chat_message($session_id, 'user', $message); // Check if the message is an email address if (is_email($message)) { // Add the email to Loops $this->add_email_to_loops($message); // Send success response $response_message = $this->options['email_capture_response'] ?? 'Thank you! Your coupon is on the way!'; wp_send_json([ 'success' => true, 'status' => 'email_captured', 'message' => $response_message ]); wp_die(); } $intent_info = ''; // Check chat mode $chat_mode = get_option("mxchat_mode_{$session_id}", 'ai'); //error_log("Chat Mode: $chat_mode"); // Handle agent mode if ($chat_mode === 'agent') { // First, check for switch intent before doing anything else $intent_matched = $this->mxchat_check_intent_and_invoke_callback($message, $user_id, $session_id); // If we matched an intent and it's the switch intent, handle it if ($intent_matched && !empty($this->fallbackResponse['text'])) { //error_log("Switch to chatbot intent detected"); // Update chat mode first update_option("mxchat_mode_{$session_id}", 'ai'); // Clear any existing PDF context to start fresh $this->clear_pdf_transients($session_id); // Prepare clean switch response $response_data = [ 'text' => $this->fallbackResponse['text'], 'html' => '', 'session_id' => $session_id, 'chat_mode' => 'ai' ]; // Save the mode switch message $this->mxchat_save_chat_message($session_id, 'system', 'Switched to AI chat mode'); $this->mxchat_save_chat_message($session_id, 'bot', $this->fallbackResponse['text']); // Send response and exit wp_send_json($response_data); wp_die(); } elseif (!$intent_matched) { // No intent matched, handle live agent message try { $this->mxchat_send_user_message_to_agent($message, $user_id, $session_id); //error_log("Message sent to agent."); wp_send_json_success([ 'status' => 'waiting_for_agent', 'message' => 'Message sent to live agent.' ]); } catch (\Exception $e) { //error_log("Error sending message to agent: " . $e->getMessage()); wp_send_json_error('Failed to send message to agent'); } wp_die(); } } // Step 1: Check for new PDF URL in the message if (preg_match('/https?:\/\/[^\s"]+/i', $message, $matches)) { $new_pdf_url = $matches[0]; // Check if this is likely a PDF-related request $pdf_keywords = ['pdf', 'document', 'read', 'analyze']; $is_pdf_request = false; foreach ($pdf_keywords as $keyword) { if (stripos($message, $keyword) !== false) { $is_pdf_request = true; break; } } // If it looks like a PDF request or we're waiting for a PDF URL if ($is_pdf_request || get_transient('mxchat_waiting_for_pdf_url_' . $session_id)) { // Validate HTTPS if (wp_http_validate_url($new_pdf_url) && parse_url($new_pdf_url, PHP_URL_SCHEME) === 'https') { // Extract filename from URL $pdf_filename = basename(parse_url($new_pdf_url, PHP_URL_PATH)); // Clear previous PDF transients $this->clear_pdf_transients($session_id); // Process new PDF $max_pages = $this->options['pdf_max_pages'] ?? 69; $embeddings = $this->fetch_and_split_pdf_pages($new_pdf_url, $max_pages); if ($embeddings === 'too_many_pages') { $error_text = sprintf( $this->options['pdf_intent_error_text'] ?? "The provided PDF exceeds the maximum allowed limit of %d pages. Please provide a smaller document.", $max_pages ); $this->fallbackResponse['text'] = $error_text; } elseif ($embeddings) { // Store new PDF information // Create a more meaningful filename from URL $pdf_filename = basename(parse_url($new_pdf_url, PHP_URL_PATH)); // If the filename is generic (like results_download.php), create a more descriptive one if (in_array($pdf_filename, ['results_download.php', 'download.php', 'view.php', 'pdf.php']) || strpos($pdf_filename, '.php') !== false) { // Create a timestamp-based name $pdf_filename = 'Document_' . date('Y-m-d_H-i') . '.pdf'; } set_transient('mxchat_pdf_url_' . $session_id, $new_pdf_url, HOUR_IN_SECONDS); set_transient('mxchat_pdf_filename_' . $session_id, $pdf_filename, HOUR_IN_SECONDS); set_transient('mxchat_pdf_embeddings_' . $session_id, $embeddings, HOUR_IN_SECONDS); set_transient('mxchat_include_pdf_in_context_' . $session_id, true, HOUR_IN_SECONDS); $success_text = $this->options['pdf_intent_success_text'] ?? "I've processed the new PDF '{$pdf_filename}'. What questions do you have about it?"; // Return success with filename for UI update wp_send_json([ 'success' => true, 'message' => $success_text, 'data' => [ 'filename' => $pdf_filename ] ]); wp_die(); } else { $error_text = $this->options['pdf_intent_error_text'] ?? "Sorry, I couldn't process the PDF. Please ensure it's a valid file."; $this->fallbackResponse['text'] = $error_text; } wp_send_json([ 'success' => false, 'message' => $this->fallbackResponse['text'] ]); wp_die(); } } } // Step 2: Detect intent and handle intent-based responses $intent_matched = $this->mxchat_check_intent_and_invoke_callback($message, $user_id, $session_id); //error_log("Intent Matched: " . ($intent_matched ? "Yes" : "No")); // Step 3: If intent is matched and handled, respond immediately if ($intent_matched && (!empty($this->fallbackResponse['text']) || !empty($this->fallbackResponse['html']))) { //error_log("Intent response triggered."); $response_data = [ 'text' => $this->fallbackResponse['text'], 'html' => $this->fallbackResponse['html'], 'session_id' => $session_id ]; $this->mxchat_save_chat_message($session_id, 'bot', $this->fallbackResponse['text'] . $this->fallbackResponse['html']); wp_send_json($response_data); wp_die(); } // If no intent matched or product not found, proceed with AI response //error_log("No matching intent or fallback. Generating AI response."); // Step 4: Generate AI response $conversation_history = $this->mxchat_fetch_conversation_history_for_ai($session_id); $this->mxchat_increment_chat_count(); // Generate embedding for the user's query $user_message_embedding = $this->mxchat_generate_embedding($message, $this->options['api_key']); if (!is_array($user_message_embedding)) { //error_log("Failed to generate message embedding for session $session_id"); wp_send_json_error('Error processing your message.'); wp_die(); } // Build context with both knowledge base and PDF content if available $context_content = "User asked: '{$message}'\n\n"; // Get relevant content from knowledge base $relevant_content = $this->mxchat_find_relevant_content($user_message_embedding); if (!empty($relevant_content)) { $context_content .= "Relevant content from knowledge database:\n" . $relevant_content . "\n\n"; } // Check for and include PDF content $pdf_url = get_transient('mxchat_pdf_url_' . $session_id); $pdf_embeddings = get_transient('mxchat_pdf_embeddings_' . $session_id); $pdf_filename = get_transient('mxchat_pdf_filename_' . $session_id); if ($pdf_url && $pdf_embeddings && get_transient('mxchat_include_pdf_in_context_' . $session_id)) { $relevant_pdf_pages = $this->find_relevant_pdf_pages($user_message_embedding, $pdf_embeddings); if (!empty($relevant_pdf_pages)) { $context_content .= "Relevant content from PDF document '{$pdf_filename}':\n"; foreach ($relevant_pdf_pages as $page_data) { $context_content .= "Page {$page_data['page_number']} of '{$pdf_filename}': {$page_data['text']}\n"; } $context_content .= "\n"; } } // Check for and include Word content $word_url = get_transient('mxchat_word_url_' . $session_id); $word_embeddings = get_transient('mxchat_word_embeddings_' . $session_id); $word_filename = get_transient('mxchat_word_filename_' . $session_id); if ($word_url && $word_embeddings && get_transient('mxchat_include_word_in_context_' . $session_id)) { $relevant_word_chunks = $this->word_handler->mxchat_find_relevant_word_chunks($user_message_embedding, $word_embeddings); if (!empty($relevant_word_chunks)) { $context_content .= "Relevant content from Word document '{$word_filename}':\n"; foreach ($relevant_word_chunks as $chunk_data) { $context_content .= "Section {$chunk_data['chunk_number']} of '{$word_filename}': {$chunk_data['text']}\n"; } $context_content .= "\n"; } } // Generate the response using the full context $response = $this->mxchat_generate_response( $context_content, $this->options['api_key'], $this->options['xai_api_key'], $this->options['claude_api_key'], $this->options['deepseek_api_key'], $conversation_history ); $this->mxchat_save_chat_message($session_id, 'bot', $response); // Step 5: Save additional content if available if (!empty($this->productCardHtml)) { $this->mxchat_save_chat_message($session_id, 'bot', $this->productCardHtml); } if (!empty($this->fallbackResponse['html'])) { $this->mxchat_save_chat_message($session_id, 'bot', $this->fallbackResponse['html']); } // Step 6: Return the response $response_data = [ 'text' => $response, 'html' => !empty($this->productCardHtml) ? $this->productCardHtml : ($this->fallbackResponse['html'] ?? ''), 'session_id' => $session_id ]; wp_send_json($response_data); wp_die(); } // Helper function to clear PDF and Word document related transients private function clear_pdf_transients($session_id) { // PDF transients delete_transient('mxchat_pdf_url_' . $session_id); delete_transient('mxchat_pdf_embeddings_' . $session_id); delete_transient('mxchat_include_pdf_in_context_' . $session_id); delete_transient('mxchat_waiting_for_pdf_url_' . $session_id); // Word document transients delete_transient('mxchat_word_url_' . $session_id); delete_transient('mxchat_word_filename_' . $session_id); delete_transient('mxchat_word_embeddings_' . $session_id); delete_transient('mxchat_include_word_in_context_' . $session_id); delete_transient('mxchat_waiting_for_word_' . $session_id); } // New function to check intents and invoke the callback function private function mxchat_check_intent_and_invoke_callback($message, $user_id, $session_id) { global $wpdb; // Check chat mode $chat_mode = get_option("mxchat_mode_{$session_id}", 'ai'); //error_log("Checking intents for mode: " . $chat_mode); // Generate the user embedding $user_embedding = $this->mxchat_generate_embedding($message, $this->options['api_key']); if (!is_array($user_embedding)) { //error_log("Failed to generate user embedding"); return false; } // Fetch intents from the database $table_name = $wpdb->prefix . 'mxchat_intents'; if ($chat_mode === 'agent') { // Only fetch the switch intent when in agent mode $query = $wpdb->prepare( "SELECT * FROM $table_name WHERE callback_function = %s", 'mxchat_handle_switch_to_chatbot_intent' ); //error_log("Searching for switch intent with query: " . $query); $intents = $wpdb->get_results($query); //error_log("Found " . count($intents) . " switch intents"); } else { $intents = $wpdb->get_results("SELECT * FROM $table_name"); } if (empty($intents)) { //error_log("No intents found in database"); return false; } $highest_similarity = -INF; $matched_intent = null; foreach ($intents as $intent) { //error_log("Checking intent: " . $intent->intent_label); $intent_embedding_serialized = $intent->embedding_vector; $intent_embedding = $intent_embedding_serialized ? unserialize($intent_embedding_serialized, ['allowed_classes' => false]) : null; if (!is_array($intent_embedding)) { //error_log("Invalid embedding for intent: " . $intent->intent_label); continue; } $similarity = $this->mxchat_calculate_cosine_similarity($user_embedding, $intent_embedding); $intent_threshold = isset($intent->similarity_threshold) ? $intent->similarity_threshold : 0.85; //error_log("Similarity for " . $intent->intent_label . ": " . $similarity . " (threshold: " . $intent_threshold . ")"); if ($similarity >= $intent_threshold && $similarity > $highest_similarity) { $highest_similarity = $similarity; $matched_intent = $intent; //error_log("New best match: " . $intent->intent_label . " with similarity " . $similarity); } } if ($matched_intent && method_exists($this, $matched_intent->callback_function)) { //error_log("Calling callback function: " . $matched_intent->callback_function); call_user_func([$this, $matched_intent->callback_function], $message, $user_id, $session_id); return true; } //error_log("No matching intent found"); return false; } //verified good public function mxchat_handle_order_history($message, $user_id, $session_id) { if (!class_exists('WooCommerce')) { $this->fallbackResponse['text'] = "I can't access order information right now. The order system seems to be unavailable."; return true; } $orderDetails = MxChat_WooCommerce::mxchat_fetch_user_orders_details('all'); if (empty($orderDetails)) { $this->fallbackResponse['text'] = "I don't see any orders associated with your account. Are you logged in?"; return true; } // Generate AI prompt with context $prompt = "User asked about their orders: '{$message}'\n\n"; $prompt .= "Order information:\n"; foreach ($orderDetails as $order) { $items_list = array_map(function($item) { return "{$item['name']} ({$item['quantity']})"; }, $order['items']); $prompt .= "Order #{$order['order_id']}: {$order['formatted_total']} on {$order['date']}\n"; $prompt .= "Items: " . implode(', ', $items_list) . "\n"; } $prompt .= "\nProvide a natural, conversational response focusing on the specific information the user asked about. "; $prompt .= "If they ask about a specific order or detail, provide just that information. "; $prompt .= "If they ask about license keys or sensitive information, inform them to check their email or contact support."; // Get AI response $ai_response = $this->mxchat_call_ai_api($prompt); $this->fallbackResponse['text'] = $ai_response['text']; return true; } public function mxchat_handle_product_inquiry($message, $user_id, $session_id) { //error_log("PRODUCT INQUIRY START - Message: $message, User ID: $user_id"); // Sanitize user ID $sanitized_user_id = sanitize_key($user_id); //error_log("Sanitized User ID: $sanitized_user_id"); // Find product $product_id = $this->find_product_in_message($message); //error_log("Initial product ID from message: " . ($product_id ?? 'null')); // Check transient if no product found if (!$product_id) { $product_id = get_transient('mxchat_last_discussed_product_' . $sanitized_user_id); //error_log("Product ID from transient: " . ($product_id ?? 'null')); } // Handle product inquiries if ($product_id && class_exists('WooCommerce')) { $product = wc_get_product($product_id); if ($product) { //error_log("Found product: " . $product->get_name() . " (ID: $product_id)"); // Prepare product details $product_name = esc_html($product->get_name()); $product_price = $product->get_price_html(); $product_image_url = esc_url(wp_get_attachment_url($product->get_image_id())); $product_url = esc_url(get_permalink($product_id)); $product_id_attr = esc_attr($product_id); // Get product description $product_description = $product->get_description() ?: $product->get_short_description(); // Log embeddings process //error_log("Generating embedding for message: $message"); $user_message_embedding = $this->mxchat_generate_embedding($message, $this->options['api_key']); //error_log("Finding relevant content for product inquiry"); $relevant_content = $this->mxchat_find_relevant_content($user_message_embedding); // Build AI prompt $ai_prompt = "You are a knowledgeable product assistant. "; $ai_prompt .= "Respond to this user query: '{$message}'\n\n"; if (!empty($relevant_content)) { $ai_prompt .= "Relevant information from our knowledge base:\n{$relevant_content}\n\n"; } $ai_prompt .= "Product details:\n"; $ai_prompt .= "Name: {$product_name}\n"; $ai_prompt .= "Price: " . strip_tags($product_price) . "\n"; if ($product_description) { $ai_prompt .= "Description: {$product_description}\n"; } $ai_prompt .= "\nInstructions:\n"; $ai_prompt .= "1. Address the user's specific question or concern about the product\n"; $ai_prompt .= "2. Incorporate relevant information from our knowledge base if provided\n"; $ai_prompt .= "3. Highlight key product features that relate to their query\n"; $ai_prompt .= "4. Include a natural suggestion to check out the product\n"; $ai_prompt .= "5. Keep the response conversational and helpful\n"; //error_log("Sending AI prompt: " . $ai_prompt); // Build and log AI response $ai_response = $this->mxchat_call_ai_api($ai_prompt); //error_log("AI Response received for product inquiry: " . print_r($ai_response, true)); // Generate and log product card $product_card_html = << {$product_name}

{$product_name}

{$product_price}
HTML; // Save response and set transient $this->productCardHtml = $product_card_html; $this->fallbackResponse = [ 'text' => $ai_response['text'], 'html' => $this->productCardHtml, ]; //error_log("Setting transient for product: $product_id with key: mxchat_last_discussed_product_$sanitized_user_id"); set_transient('mxchat_last_discussed_product_' . $sanitized_user_id, $product_id, HOUR_IN_SECONDS); // Verify transient was set $verify_transient = get_transient('mxchat_last_discussed_product_' . $sanitized_user_id); //error_log("Verification - Retrieved transient value: " . ($verify_transient ?? 'null')); return true; } } //error_log("PRODUCT INQUIRY END - No product found or WooCommerce not available"); return false; } //verified good public function mxchat_handle_email_capture($message, $user_id, $session_id) { // Log the message safely //error_log("Triggered email capture intent for message: " . sanitize_text_field($message)); // Initiate email capture flow $response = esc_html($this->options['triggered_phrase_response'] ?? "Would you like to join our mailing list? Please provide your email below."); set_transient('mxchat_email_capture_' . $user_id, true, 5 * MINUTE_IN_SECONDS); $this->mxchat_save_chat_message($session_id, 'bot', $response); // Respond to the user wp_send_json(['message' => $response]); wp_die(); } //very good public function mxchat_generate_image($message, $user_id, $session_id) { // Prepare a prompt for DALL-E $prompt = "Create an image of " . sanitize_text_field($message); // Use the existing OpenAI API key $openai_api_key = sanitize_text_field($this->options['api_key']); // Call DALL-E to generate an image $image_response = $this->mxchat_generate_dalle_image($prompt, $openai_api_key); // Check if the response contains an image URL if (isset($image_response['imageUrl'])) { $image_url = esc_url_raw($image_response['imageUrl']); // Construct the HTML with a CSS class instead of inline styles $response_html = 'Generated Image'; $response_text = "Here is the image I generated:"; } else { $response_text = "I'm sorry, but I couldn't generate an image based on your request."; $response_html = ''; //error_log("DALL-E image generation error: " . esc_html($image_response['error'] ?? 'Unknown error.')); } // Save both text and HTML responses $this->mxchat_save_chat_message($session_id, 'bot', $response_text . "\n" . $response_html); // Prepare the response data $response_data = [ 'message' => $response_text, 'html' => $response_html, 'image_url' => $image_url ?? '', ]; // Send the JSON response header('Content-Type: application/json; charset=' . get_option('blog_charset')); echo json_encode($response_data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); wp_die(); } private function mxchat_generate_dalle_image($prompt, $api_key, $model = 'dall-e-3', $timeout = 60) { $api_url = 'https://api.openai.com/v1/images/generations'; $body = json_encode([ 'prompt' => sanitize_text_field($prompt), 'n' => 1, 'size' => '1024x1024', 'model' => sanitize_text_field($model), ]); $args = [ 'body' => $body, 'headers' => [ 'Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . sanitize_text_field($api_key), ], 'method' => 'POST', 'timeout' => absint($timeout), ]; $response = wp_remote_post($api_url, $args); if (is_wp_error($response)) { //error_log("DALL-E request failed: " . $response->get_error_message()); return ['error' => "Error generating image: " . $response->get_error_message()]; } $response_body = json_decode(wp_remote_retrieve_body($response), true); if (isset($response_body['data'][0]['url'])) { return ['imageUrl' => esc_url_raw($response_body['data'][0]['url'])]; } else { //error_log("DALL-E response error: " . wp_remote_retrieve_body($response)); return ['error' => "Failed to generate image."]; } } /** * Handle web search requests. * * Sends the refined search query to the Brave Search API and displays neatly formatted, * styled search results. Results are cached for performance. * * @since 1.0.0 * @param string $message The user's search query. * @param string $user_id The user identifier. * @param string $session_id The current session ID. * @return void */ public function mxchat_handle_search_request( $message, $user_id, $session_id ) { // Step 1: Interpret and refine the search query $refined_search_query = $this->mxchat_interpret_search_query( $message ); if ( empty( $refined_search_query ) ) { $this->fallbackResponse = array( 'text' => esc_html__( 'I apologize, but could you please rephrase your search request?', 'mxchat' ), ); return; } // Retrieve and validate API settings $options = get_option( 'mxchat_options' ); $api_key = isset( $options['brave_api_key'] ) ? sanitize_text_field( $options['brave_api_key'] ) : ''; $results_count = isset( $options['brave_results_count'] ) ? absint( $options['brave_results_count'] ) : 5; if ( empty( $api_key ) ) { $this->fallbackResponse = array( 'text' => esc_html__( 'Search functionality is temporarily unavailable. Please try again later.', 'mxchat' ), ); return; } // Build the API request URL $api_url = add_query_arg( array( 'q' => rawurlencode( $refined_search_query ), 'count' => $results_count, 'text_decorations' => 'true', 'rich_data' => 'true', ), 'https://api.search.brave.com/res/v1/web/search' ); // Attempt to retrieve cached results first $transient_key = 'mxchat_search_' . md5( $refined_search_query ); $results = get_transient( $transient_key ); if ( false === $results ) { // Fetch new results from the Brave Search API $response = wp_remote_get( $api_url, array( 'headers' => array( 'Accept' => 'application/json', 'Accept-Encoding' => 'gzip', 'X-Subscription-Token'=> $api_key, ), 'timeout' => 10, ) ); if ( is_wp_error( $response ) ) { $this->fallbackResponse = array( 'text' => esc_html__( 'I encountered an error while searching. Please try again.', 'mxchat' ), ); return; } $results = json_decode( wp_remote_retrieve_body( $response ), true ); if ( json_last_error() !== JSON_ERROR_NONE ) { $this->fallbackResponse = array( 'text' => esc_html__( 'I received an invalid response from the search service.', 'mxchat' ), ); return; } // Cache results for one hour set_transient( $transient_key, $results, HOUR_IN_SECONDS ); } // Process and display results if ( ! empty( $results['web']['results'] ) && is_array( $results['web']['results'] ) ) { $html = $this->generate_search_results_html( $results['web']['results'], $refined_search_query ); // Only return HTML (no large text summary) $this->fallbackResponse = array( 'html' => $html, ); // Save to chat history $this->mxchat_save_chat_message( $session_id, 'bot', $html ); } else { $this->fallbackResponse = array( 'text' => sprintf( esc_html__( 'I couldn\'t find any relevant results for "%s". Would you like to try different search terms?', 'mxchat' ), esc_html( $refined_search_query ) ), ); } } /** * Format search results into a natural text summary. * * @since 1.0.0 * @param array $results The search results from the API. * @param string $query The original search query. * @return string The text summary of the top results. */ private function format_search_results( $results, $query ) { $summary = sprintf( esc_html__( 'Here are the most relevant results for "%s":', 'mxchat' ), esc_html( $query ) ) . "\n\n"; $max_results = min( count( $results ), 3 ); for ( $i = 0; $i < $max_results; $i++ ) { $result = $results[ $i ]; $title = isset( $result['title'] ) ? wp_strip_all_tags( $result['title'] ) : ''; $description = isset( $result['description'] ) ? wp_strip_all_tags( $result['description'] ) : ''; // Append title and description to the summary $summary .= sprintf( "%s\n%s\n\n", esc_html( $title ), esc_html( $description ) ); } return $summary; } /** * Generate HTML markup for search results. * * @since 1.0.0 * @param array $results The search results from the API. * @param string $query The user-refined query. * @return string The HTML markup for displaying the results. */ private function generate_search_results_html( $results, $query ) { ob_start(); ?>
<?php echo esc_attr__( 'Site icon', 'mxchat' ); ?>
mxchat_interpret_search_query($message); // If no query was interpreted, return a fallback message if (empty($refined_search_query)) { $this->fallbackResponse = [ 'text' => __("I'm sorry, I couldn't interpret your search query. Please specify what you'd like to see images of.", 'mxchat'), 'html' => "", ]; return; } // Brave API URL $api_url = 'https://api.search.brave.com/res/v1/images/search'; // Retrieve Brave API settings $options = get_option('mxchat_options'); $api_key = isset($options['brave_api_key']) ? sanitize_text_field($options['brave_api_key']) : ''; if (empty($api_key)) { /* if (defined('WP_DEBUG') && WP_DEBUG) { error_log("Brave API key is missing."); } */ $this->fallbackResponse = [ 'text' => __("API key is not configured. Please set it in the Brave Search Settings.", 'mxchat'), 'html' => "", ]; return; } $image_count = isset($options['brave_image_count']) ? intval($options['brave_image_count']) : 4; $safe_search = isset($options['brave_safe_search']) ? sanitize_text_field($options['brave_safe_search']) : 'strict'; // Append query parameters based on settings $api_url = add_query_arg([ 'q' => rawurlencode($refined_search_query), 'count' => $image_count, 'safesearch' => $safe_search, ], $api_url); /* // Log the final API URL for the search if (defined('WP_DEBUG') && WP_DEBUG) { error_log("Final API URL for Brave Image Search: " . esc_url_raw($api_url)); } */ // Implement caching $transient_key = 'mxchat_image_search_' . md5($refined_search_query); $body = get_transient($transient_key); if (false === $body) { $args = [ 'headers' => [ 'Accept' => 'application/json', 'Accept-Encoding' => 'gzip', 'X-Subscription-Token' => $api_key, ], 'timeout' => 10, ]; $response = wp_remote_get($api_url, $args); if (is_wp_error($response)) { /* if (defined('WP_DEBUG') && WP_DEBUG) { error_log("Brave Image API request failed: " . $response->get_error_message()); } */ $this->fallbackResponse = [ 'text' => __("I'm sorry, I couldn't retrieve any images based on your request.", 'mxchat'), 'html' => "", ]; return; } $body = json_decode(wp_remote_retrieve_body($response), true); set_transient($transient_key, $body, HOUR_IN_SECONDS); } // Process the API response if (isset($body['results']) && is_array($body['results']) && count($body['results']) > 0) { $html_output = ''; $this->fallbackResponse = [ 'text' => "", 'html' => $html_output, ]; // Save response in chat history $this->mxchat_save_chat_message($session_id, 'bot', $html_output); } else { /* if (defined('WP_DEBUG') && WP_DEBUG) { error_log("Brave Image API response did not contain expected data structure or was empty: " . print_r($body, true)); } */ $this->fallbackResponse = [ 'text' => __("I'm sorry, I couldn't retrieve any images based on your request.", 'mxchat'), 'html' => "", ]; } } public function mxchat_interpret_search_query($user_query) { $system_prompt = "Interpret the user's request to provide only the essential keywords or phrases for image searching. Remove conversational language, politeness, or extra context. Return a concise search query that doesn't lose any of the original meaning."; // Retrieve OpenAI API key using 'api_key' as the option key $api_key = isset($this->options['api_key']) ? sanitize_text_field($this->options['api_key']) : sanitize_text_field(get_option('mxchat_options')['api_key']); /* // Log the API key check, without exposing the key if (defined('WP_DEBUG') && WP_DEBUG) { error_log("Retrieved OpenAI API Key: " . ($api_key ? "Present" : "Missing")); } */ if (empty($api_key)) { //error_log("OpenAI API key is missing."); return sanitize_text_field($user_query); // Default to the original query if API key is missing } $url = 'https://api.openai.com/v1/chat/completions'; $args = [ 'headers' => [ 'Authorization' => 'Bearer ' . $api_key, 'Content-Type' => 'application/json', ], 'body' => wp_json_encode([ 'model' => 'gpt-3.5-turbo', 'messages' => [ ['role' => 'system', 'content' => $system_prompt], ['role' => 'user', 'content' => sanitize_text_field($user_query)], ], 'temperature' => 0.2, 'max_tokens' => 20, ]), 'method' => 'POST', ]; $response = wp_remote_post($url, $args); if (is_wp_error($response)) { //error_log("OpenAI request failed: " . $response->get_error_message()); return sanitize_text_field($user_query); // Fallback to the original query if there's an error } $body = json_decode(wp_remote_retrieve_body($response), true); // Check for a valid response and sanitize output if (isset($body['choices'][0]['message']['content'])) { $interpreted_query = sanitize_text_field(trim($body['choices'][0]['message']['content'])); /* // Log the interpreted query for debugging if (defined('WP_DEBUG') && WP_DEBUG) { error_log("Interpreted search query: " . $interpreted_query); } */ return $interpreted_query; } else { //error_log("Unexpected API response format: " . print_r($body, true)); return sanitize_text_field($user_query); } } public function mxchat_handle_add_to_cart_intent($message, $user_id, $session_id) { //error_log("ADD TO CART START - Message: $message, User ID: $user_id"); if (!class_exists('WooCommerce')) { //error_log("WooCommerce not available"); return $this->generate_intent_response([ 'intent' => 'add_to_cart', 'status' => 'error', 'reason' => 'woocommerce_not_available' ], $session_id); } $sanitized_user_id = sanitize_key($user_id); // error_log("Sanitized User ID: $sanitized_user_id"); $product_id = null; // If button click, use transient if ($message === '!addtocart') { //error_log("Button click detected - using transient"); $product_id = get_transient('mxchat_last_discussed_product_' . $sanitized_user_id); //error_log("Product ID from transient: " . ($product_id ?? 'null')); } else { // For text commands, search message first //error_log("Text command detected - searching message first"); $product_id = $this->find_product_in_message($message); //error_log("Product ID from message search: " . ($product_id ?? 'null')); // Only use transient as fallback if no product found in message if (!$product_id) { //error_log("No product found in message, checking transient as fallback"); $product_id = get_transient('mxchat_last_discussed_product_' . $sanitized_user_id); //error_log("Product ID from transient fallback: " . ($product_id ?? 'null')); } } // Handle no product found if (!$product_id) { //error_log("No product ID found through any method"); return $this->generate_intent_response([ 'intent' => 'add_to_cart', 'status' => 'error', 'reason' => 'no_product_context', 'action_needed' => 'request_product_name', 'searched_message' => $message ], $session_id); } // Get and verify product $product = wc_get_product($product_id); if (!$product) { //error_log("Product not found with ID: $product_id"); return $this->generate_intent_response([ 'intent' => 'add_to_cart', 'status' => 'error', 'reason' => 'product_not_found', 'product_id' => $product_id ], $session_id); } //error_log("Found product: " . $product->get_name() . " (ID: $product_id)"); // Add to cart $added = WC()->cart->add_to_cart($product_id); if ($added) { //error_log("Successfully added to cart: " . $product->get_name()); return $this->generate_intent_response([ 'intent' => 'add_to_cart', 'status' => 'success', 'product' => [ 'name' => $product->get_name(), 'id' => $product_id ], 'cart_url' => wc_get_cart_url(), 'available_actions' => [ 'view_cart', 'checkout', 'continue_shopping' ] ], $session_id); } else { //error_log("Failed to add to cart: " . $product->get_name()); return $this->generate_intent_response([ 'intent' => 'add_to_cart', 'status' => 'error', 'reason' => 'add_to_cart_failed', 'product' => [ 'name' => $product->get_name(), 'id' => $product_id ] ], $session_id); } } private function find_product_in_message($message) { global $wpdb; // Get embedding for the search query $query_embedding = $this->mxchat_generate_embedding($message, $this->options['api_key']); if (!is_array($query_embedding)) { return null; } // Get relevant content as string $relevant_content = $this->mxchat_find_relevant_products($query_embedding); if (empty($relevant_content)) { // Return null to indicate no results and set fallback response $this->fallbackResponse['text'] = "I couldn't find any relevant products based on your query. Could you please be more specific about the product you're looking for?"; return null; } // Extract product URLs from the content preg_match_all('/https?:\/\/[^\s<>"\']+?\/product\/[^\s<>"\']+/', $relevant_content, $matches); if (!empty($matches[0])) { // Try each URL found foreach ($matches[0] as $url) { // Clean the URL $url = rtrim($url, '/."\']'); // Get the product slug $path = parse_url($url, PHP_URL_PATH); $slug = basename(rtrim($path, '/')); // Find product by slug $args = array( 'post_type' => 'product', 'post_status' => 'publish', 'name' => $slug, 'posts_per_page' => 1 ); $products = get_posts($args); if (!empty($products)) { $product_id = $products[0]->ID; $product = wc_get_product($product_id); if ($product && $product->is_purchasable()) { return $product_id; } } } } // Fallback: Look for product names in the content $products = wc_get_products([ 'status' => 'publish', 'limit' => -1, 'return' => 'all' ]); foreach ($products as $product) { $name = $product->get_name(); if (stripos($relevant_content, $name) !== false) { if ($product->is_purchasable()) { return $product->get_id(); } } } // If no product is found after all checks, set the fallback response $this->fallbackResponse['text'] = "I couldn't find any relevant products based on your query. Try to be more specific"; return null; } // New method to handle intent responses private function generate_intent_response($context_content, $session_id) { // Convert the context array to a structured string for the AI $context_string = $this->format_intent_context($context_content); // Generate AI response using the context $response = $this->mxchat_generate_response( $context_string, $this->options['api_key'], $this->options['xai_api_key'], $this->options['claude_api_key'], $this->options['deepseek_api_key'], $this->mxchat_fetch_conversation_history_for_ai($session_id) ); $this->fallbackResponse['text'] = $response; return true; } // Helper method to format intent context private function format_intent_context($context) { $context_string = "INTENT CONTEXT:\n"; switch ($context['intent']) { case 'add_to_cart': if ($context['status'] === 'success') { $context_string .= "Action: Successfully added product to cart\n"; $context_string .= "Product: {$context['product']['name']}\n"; $context_string .= "Available actions: " . implode(', ', $context['available_actions']) . "\n"; $context_string .= "Cart URL: {$context['cart_url']}\n"; $context_string .= "\nPlease inform the user of the successful addition and their available options."; } else { $context_string .= "Action: Failed to add product to cart\n"; $context_string .= "Reason: {$context['reason']}\n"; switch ($context['reason']) { case 'woocommerce_not_available': $context_string .= "\nPlease inform the user that shopping features are not available."; break; case 'no_product_context': $context_string .= "\nPlease ask the user to specify which product they want to add."; break; case 'product_not_found': $context_string .= "\nPlease inform the user that the product couldn't be found and ask them to try again."; break; case 'add_to_cart_failed': $context_string .= "\nPlease apologize to the user and suggest they try again or ask for assistance."; break; } } break; } return $context_string; } public function mxchat_handle_checkout_intent($message, $user_id, $session_id) { if (!class_exists('WooCommerce')) { $this->fallbackResponse['text'] = "I apologize, but the checkout feature isn't available at the moment."; return true; } // Check if cart has items if (WC()->cart->is_empty()) { $this->fallbackResponse['text'] = "Your cart is empty at the moment. Would you like to see our products?"; return true; } // Get cart summary $cart_count = WC()->cart->get_cart_contents_count(); $cart_total = WC()->cart->get_total(); // Get and validate checkout URL $checkout_url = wc_get_checkout_url(); if (!$checkout_url) { $this->fallbackResponse['text'] = "I'm having trouble accessing the checkout page. Please try again in a moment."; return true; } wp_send_json([ 'text' => sprintf( "You have %d item%s in your cart totaling %s. I'll redirect you to checkout now.", $cart_count, $cart_count > 1 ? 's' : '', strip_tags($cart_total) ), 'redirect_url' => esc_url_raw($checkout_url) ]); wp_die(); } //very good private function add_email_to_loops($email) { // Sanitize the email $email = sanitize_email($email); // Retrieve and sanitize options $api_key = isset($this->options['loops_api_key']) ? sanitize_text_field($this->options['loops_api_key']) : ''; $mailing_list_id = isset($this->options['loops_mailing_list']) ? sanitize_text_field($this->options['loops_mailing_list']) : ''; // Check for missing API key or mailing list ID if (empty($api_key) || empty($mailing_list_id)) { //error_log('Loops API key or mailing list ID is missing.'); return; } $data = array( 'email' => $email, 'subscribed' => true, 'source' => 'MxChat AI Chatbot', 'mailingLists' => array($mailing_list_id => true), ); $url = 'https://app.loops.so/api/v1/contacts/create'; $args = array( 'body' => wp_json_encode($data), 'headers' => array( 'Authorization' => 'Bearer ' . $api_key, 'Content-Type' => 'application/json', ), 'method' => 'POST', 'timeout' => 45, ); $response = wp_remote_post($url, $args); // Handle errors in the API request if (is_wp_error($response)) { //error_log('Error adding email to Loops: ' . $response->get_error_message()); return; } // Check for non-200 HTTP responses $response_code = wp_remote_retrieve_response_code($response); if ($response_code != 200) { $response_body = wp_remote_retrieve_body($response); //error_log('Loops API responded with code ' . $response_code . ': ' . $response_body); } } public function mxchat_handle_pdf_discussion($message, $user_id, $session_id) { // Get the maximum number of pages allowed from admin settings $max_pages = isset($this->options['pdf_max_pages']) ? intval($this->options['pdf_max_pages']) : 69; // Retrieve options for dynamic texts $trigger_text = $this->options['pdf_intent_trigger_text'] ?? "Please provide the URL to the PDF you'd like to discuss."; $success_text = $this->options['pdf_intent_success_text'] ?? "I've processed the PDF. What questions do you have about it?"; $error_text = $this->options['pdf_intent_error_text'] ?? "Sorry, I couldn't process the PDF. Please ensure it's a valid file."; // Check for explicit request for new PDF $new_pdf_requested = stripos($message, 'new') !== false || stripos($message, 'another') !== false || stripos($message, 'different') !== false; // If user mentions adding/reading a PDF, set waiting flag if (stripos($message, 'pdf') !== false || stripos($message, 'document') !== false || stripos($message, 'read') !== false) { set_transient('mxchat_waiting_for_pdf_url_' . $session_id, true, HOUR_IN_SECONDS); $this->fallbackResponse['text'] = $trigger_text; return; } // If we're waiting for a URL or user requested new PDF if ($new_pdf_requested || get_transient('mxchat_waiting_for_pdf_url_' . $session_id)) { if (preg_match('/https?:\/\/[^\s"]+/i', $message, $matches)) { // Process URL... (rest of your existing URL processing code) } else { $this->fallbackResponse['text'] = $trigger_text; } return; } // Default to proceeding with conversation if no specific PDF action is needed $this->fallbackResponse['text'] = ''; } private function fetch_and_split_pdf_pages($pdf_source, $max_pages) { $upload_dir = wp_upload_dir(); $temp_file = null; try { // Handle URL vs local file if (filter_var($pdf_source, FILTER_VALIDATE_URL)) { // Validate and download the file from URL $temp_file = wp_tempnam($pdf_source); // Safe temporary file name $response = wp_remote_get($pdf_source, ['timeout' => 60]); if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) { //error_log("Failed to download PDF. Error: " . print_r($response, true)); return false; } file_put_contents($temp_file, wp_remote_retrieve_body($response)); // Validate that the downloaded file is a PDF $mime_type = mime_content_type($temp_file); if ($mime_type !== 'application/pdf') { //error_log("Invalid MIME type detected for PDF: $mime_type"); unlink($temp_file); return false; } } else { // For local files, use the provided path directly $temp_file = $pdf_source; } // Parse and process the PDF $parser = new \Smalot\PdfParser\Parser(); $pdf = $parser->parseFile($temp_file); $pages = $pdf->getPages(); if (count($pages) > $max_pages) { //error_log("PDF exceeds the maximum allowed pages: " . count($pages)); if (filter_var($pdf_source, FILTER_VALIDATE_URL)) { unlink($temp_file); } return 'too_many_pages'; } $embeddings = []; foreach ($pages as $page_number => $page) { $text = $page->getText(); // Ensure text is non-empty before generating embeddings if (empty(trim($text))) { //error_log("Skipping empty page: " . ($page_number + 1)); continue; } $embedding = $this->mxchat_generate_embedding( "Page " . ($page_number + 1) . ": " . $text, $this->options['api_key'] ); if ($embedding) { $embeddings[] = [ 'page_number' => $page_number + 1, 'embedding' => $embedding, 'text' => $text, ]; } else { //error_log("Failed to generate embedding for page " . ($page_number + 1)); } } // Clean up downloaded file if it was from URL if (filter_var($pdf_source, FILTER_VALIDATE_URL) && $temp_file) { unlink($temp_file); } return $embeddings; } catch (\Exception $e) { // error_log("Error parsing or processing PDF: " . $e->getMessage()); // Cleanup in case of exception if (filter_var($pdf_source, FILTER_VALIDATE_URL) && $temp_file && file_exists($temp_file)) { unlink($temp_file); } return false; } } private function find_relevant_pdf_pages($query_embedding, $embeddings) { //error_log("find_relevant_pdf_pages called."); $most_relevant = null; $highest_similarity = -INF; foreach ($embeddings as $page_data) { $similarity = $this->mxchat_calculate_cosine_similarity($query_embedding, $page_data['embedding']); if ($similarity > $highest_similarity) { $highest_similarity = $similarity; $most_relevant = $page_data['page_number']; } } if (!is_null($most_relevant)) { $page_numbers = range(max(1, $most_relevant - 1), min(count($embeddings), $most_relevant + 1)); return array_filter($embeddings, function ($page) use ($page_numbers) { return in_array($page['page_number'], $page_numbers); }); } return []; } // Add this to your class public function handle_pdf_upload() { check_ajax_referer('mxchat_chat_nonce', 'nonce'); if (!isset($_FILES['pdf_file']) || !isset($_POST['session_id'])) { wp_send_json_error('Missing required parameters.'); return; } $file = $_FILES['pdf_file']; $session_id = sanitize_text_field($_POST['session_id']); $original_filename = sanitize_text_field($file['name']); $file_type = wp_check_filetype($file['name'], ['pdf' => 'application/pdf']); if ($file_type['type'] !== 'application/pdf') { wp_send_json_error('Invalid file type. Only PDF files are allowed.'); return; } $upload_dir = wp_upload_dir(); $pdf_filename = 'mxchat_' . $session_id . '_' . time() . '.pdf'; $pdf_path = $upload_dir['path'] . '/' . $pdf_filename; if (!move_uploaded_file($file['tmp_name'], $pdf_path)) { wp_send_json_error('Failed to upload file.'); return; } $this->clear_pdf_transients($session_id); $max_pages = isset($this->options['pdf_max_pages']) ? intval($this->options['pdf_max_pages']) : 69; $embeddings = $this->fetch_and_split_pdf_pages($pdf_path, $max_pages); if ($embeddings === 'too_many_pages') { unlink($pdf_path); $error_message = sprintf( $this->options['pdf_intent_error_text'] ?? "The provided PDF exceeds the maximum allowed limit of %d pages. Please provide a smaller document.", $max_pages ); wp_send_json_error($error_message); return; } if ($embeddings === false || empty($embeddings)) { unlink($pdf_path); $error_message = $this->options['pdf_intent_error_text'] ?? 'The uploaded PDF appears to be empty or contains unsupported content.'; wp_send_json_error($error_message); return; } if (!empty($embeddings)) { set_transient('mxchat_pdf_url_' . $session_id, $pdf_path, HOUR_IN_SECONDS); set_transient('mxchat_pdf_filename_' . $session_id, $original_filename, HOUR_IN_SECONDS); set_transient('mxchat_pdf_embeddings_' . $session_id, $embeddings, HOUR_IN_SECONDS); set_transient('mxchat_include_pdf_in_context_' . $session_id, true, HOUR_IN_SECONDS); $success_message = $this->options['pdf_intent_success_text'] ?? "I've processed the PDF. What questions do you have about it?"; wp_send_json_success([ 'message' => $success_message, 'filename' => $original_filename ]); return; } unlink($pdf_path); $error_message = $this->options['pdf_intent_error_text'] ?? 'Sorry, I couldn\'t process the PDF. Please ensure it\'s a valid file.'; wp_send_json_error($error_message); return; } public function handle_pdf_remove() { check_ajax_referer('mxchat_chat_nonce', 'nonce'); if (empty($_POST['session_id'])) { wp_send_json_error('Session ID missing.'); wp_die(); } $session_id = sanitize_text_field($_POST['session_id']); $pdf_path = get_transient('mxchat_pdf_url_' . $session_id); if ($pdf_path && file_exists($pdf_path)) { unlink($pdf_path); } $this->clear_pdf_transients($session_id); wp_send_json_success([ 'message' => 'PDF removed successfully.' ]); wp_die(); } /** * Calls the AI API with the provided prompt. * * @param string $prompt The prompt to send to the AI. * @return array An array containing the AI's response text. */ private function mxchat_call_ai_api( $prompt ) { //error_log( 'Calling AI API with the provided prompt.' ); $api_key = $this->options['api_key']; if ( empty( $api_key ) ) { //error_log( 'API key is not set.' ); return [ 'text' => 'API key is not set.' ]; } $url = 'https://api.openai.com/v1/chat/completions'; $messages = [ [ 'role' => 'system', '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.', ], [ 'role' => 'user', 'content' => $prompt, ], ]; $args = [ 'headers' => [ 'Authorization' => 'Bearer ' . $api_key, 'Content-Type' => 'application/json', ], 'body' => wp_json_encode( [ 'model' => 'gpt-4o', 'messages' => $messages, 'temperature' => 0.7, ] ), 'timeout' => 10, 'method' => 'POST', ]; $response = wp_remote_post( $url, $args ); if ( is_wp_error( $response ) ) { //error_log( 'Error communicating with AI API: ' . $response->get_error_message() ); return [ 'text' => 'Error communicating with AI API.' ]; } $body = wp_remote_retrieve_body( $response ); //error_log( 'API response body: ' . $body ); $decoded_body = json_decode( $body, true ); if ( isset( $decoded_body['choices'][0]['message']['content'] ) ) { return [ 'text' => $decoded_body['choices'][0]['message']['content'] ]; } else { //error_log( 'Unexpected API response format: ' . wp_json_encode( $decoded_body ) ); return [ 'text' => 'No response received from AI.' ]; } } /** * Fetches the AI response for the given prompt. * * @param string $prompt The prompt to send to the AI. * @return string|null The AI's response text or null if not available. */ private function mxchat_fetch_ai_response( $prompt ) { $response = $this->mxchat_call_ai_api( $prompt ); return isset( $response['text'] ) ? $response['text'] : null; } /** * Generates the AI prompt for product recommendations. * * @param array $recommendations An array of product recommendations. * @return string The generated AI prompt. */ private function mxchat_generate_ai_recommendation_prompt($recommendations) { $recommendation_list = ''; foreach ($recommendations as $index => $rec) { $number = $index + 1; $name = $rec['name']; $price = $rec['price']; $url = $rec['url']; $image = $rec['image']; $recommendation_list .= "{$number}. Product: {$name} (Price: \\${$price})\n"; $recommendation_list .= " [Link]({$url})\n"; $recommendation_list .= " ![Image]({$image})\n\n"; } $prompt = "Based on the following list of products, generate a unique, friendly, and personalized response to a user. "; $prompt .= "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. "; $prompt .= "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. "; $prompt .= "Please number your responses to match the product numbers.\n\n"; $prompt .= "Products:\n\n{$recommendation_list}"; $prompt .= "Please ensure that the number of each product matches the order in which the products are listed."; return $prompt; } private function mxchat_generate_recommendations($user_id, $message) { // 1. Gather user context $user_context = $this->mxchat_get_user_context($user_id); // Get cart item IDs for filtering $cart_item_ids = []; if (!empty($user_context['cart_items'])) { $cart_item_ids = array_map(function($item) { return $item['product_id']; }, $user_context['cart_items']); } // 2. Get AI recommendation based on context $ai_suggestion = $this->mxchat_get_ai_shopping_suggestion($message, $user_context); //error_log("AI Shopping Suggestion: " . $ai_suggestion); // 3. Generate embedding for the AI suggestion $suggestion_embedding = $this->mxchat_generate_embedding($ai_suggestion, $this->options['api_key']); if (!$suggestion_embedding) { //error_log("Could not generate embedding for AI suggestion"); return ['recommendations' => [], 'sources' => []]; } // 4. Use existing relevant content function to find matches $relevant_content = $this->mxchat_find_relevant_content($suggestion_embedding); // 5. Extract product URLs from the relevant content preg_match_all('/https?:\/\/[^\s<>"\']+?\/product\/[^\s<>"\']+/', $relevant_content, $matches); $found_products = []; if (!empty($matches[0])) { foreach ($matches[0] as $url) { // Clean the URL $url = rtrim($url, '/."\']'); // Get the product slug $path = parse_url($url, PHP_URL_PATH); $slug = basename(rtrim($path, '/')); // Find product by slug $args = array( 'post_type' => 'product', 'post_status' => 'publish', 'name' => $slug, 'posts_per_page' => 1 ); $products = get_posts($args); if (!empty($products)) { $product_id = $products[0]->ID; // Skip if product is in cart if (in_array($product_id, $cart_item_ids)) { //error_log("Skipping product ID $product_id - already in cart"); continue; } $product = wc_get_product($product_id); if ($product && $product->is_purchasable() && !in_array($product_id, array_map(function($p) { return $p->get_id(); }, $found_products))) { $found_products[] = $product; } } } } // If no products found by URLs, look for product names in the content if (empty($found_products)) { $products = wc_get_products([ 'status' => 'publish', 'limit' => -1, 'return' => 'objects' ]); foreach ($products as $product) { // Skip if product is in cart if (in_array($product->get_id(), $cart_item_ids)) { continue; } $name = $product->get_name(); if (stripos($relevant_content, $name) !== false) { if ($product->is_purchasable() && !in_array($product->get_id(), array_map(function($p) { return $p->get_id(); }, $found_products))) { $found_products[] = $product; } } } } // Keep searching until we have 4 products or run out of options $formatted_recommendations = []; foreach ($found_products as $product) { if (count($formatted_recommendations) >= 4) break; $formatted_recommendations[] = [ 'name' => $product->get_name(), 'price' => $product->get_price(), 'url' => get_permalink($product->get_id()), 'image' => wp_get_attachment_url($product->get_image_id()) ]; } return [ 'recommendations' => $formatted_recommendations, 'sources' => ['AI-powered personalized recommendations'] ]; } private function mxchat_get_ai_shopping_suggestion($message, $user_context) { $prompt = "As a shopping assistant, analyze this user's context and generate a specific product search suggestion. "; $prompt .= "User's Question: \"{$message}\"\n\n"; if (!empty($user_context['order_history'])) { $prompt .= "Their recent orders include:\n"; foreach ($user_context['order_history'] as $order) { $prompt .= "- {$order['name']} ({$order['date']})\n"; } } if (!empty($user_context['cart_items'])) { $prompt .= "\nThey currently have in their cart:\n"; foreach ($user_context['cart_items'] as $item) { $prompt .= "- {$item['name']}\n"; } } $prompt .= "\nBased on their question and history, suggest a specific search query that would help find the most relevant products. "; $prompt .= "Respond with ONLY the search query, nothing else."; $response = $this->mxchat_call_ai_api($prompt); return isset($response['text']) ? trim($response['text']) : $message; } public function mxchat_handle_product_recommendations($message, $user_id, $session_id) { try { //error_log("Starting product recommendations for user: $user_id, session: $session_id"); // Pass the message to get context-aware recommendations $recommendation_data = $this->mxchat_generate_recommendations($user_id, $message); //error_log('Generated recommendation data: ' . wp_json_encode($recommendation_data)); if (empty($recommendation_data['recommendations'])) { //error_log("No recommendations found for user: $user_id"); $this->fallbackResponse = [ 'text' => __("I couldn't find any product recommendations for you right now. Please try again later!", 'mxchat'), ]; return; } // Remove duplicates and limit to top 4 recommendations $unique_recommendations = []; foreach ($recommendation_data['recommendations'] as $rec) { $unique_recommendations[$rec['url']] = $rec; } $unique_recommendations = array_slice($unique_recommendations, 0, 4); //error_log('Top 4 recommendations: ' . wp_json_encode($unique_recommendations)); $recommendations_summary = []; foreach ($unique_recommendations as $rec) { $recommendations_summary[] = [ 'name' => $rec['name'], 'price' => strip_tags($rec['price']), 'url' => $rec['url'], 'image' => $rec['image'], ]; } // Generate AI prompt and fetch response $ai_prompt = $this->mxchat_generate_ai_recommendation_prompt($recommendations_summary); $ai_response = $this->mxchat_fetch_ai_response($ai_prompt); if (empty($ai_response)) { $this->fallbackResponse = [ 'text' => __('An unexpected error occurred while generating recommendations. Please try again later.', 'mxchat'), ]; return; } // Split the AI's response into lines $ai_lines = preg_split('/\r\n|\r|\n/', $ai_response); // Initialize variables $formatted_response = ''; $justifications = []; $current_number = 0; $in_introduction = true; $introduction = ''; // Parse the AI response to separate the introduction and the justifications foreach ($ai_lines as $line) { if (preg_match('/^\s*(\d+)\.\s*(.*)$/', $line, $matches)) { // This line is a numbered justification $current_number = intval($matches[1]) - 1; $justifications[$current_number] = $matches[2]; $in_introduction = false; } elseif ($in_introduction) { // This line is part of the introduction $introduction .= $line . ' '; } else { // This line is a continuation of the current justification if (isset($justifications[$current_number])) { $justifications[$current_number] .= ' ' . $line; } } } // Build the formatted response if (!empty($introduction)) { $formatted_response .= esc_html(trim($introduction)) . "

"; } foreach ($recommendations_summary as $index => $rec) { $name = esc_html($rec['name']); $price = number_format((float)$rec['price'], 2, '.', ''); $url = esc_url($rec['url']); $image = esc_url($rec['image']); $formatted_response .= ($index + 1) . ". " . $name . " - $" . $price . "
"; $formatted_response .= "\"{$name}\"
"; if (isset($justifications[$index])) { $formatted_response .= "" . esc_html(trim($justifications[$index])) . "

"; } } $this->fallbackResponse = [ 'text' => $formatted_response, ]; //error_log('Final formatted response set.'); } catch (Exception $e) { //error_log('Error in mxchat_handle_product_recommendations: ' . $e->getMessage()); $this->fallbackResponse = [ 'text' => __('An unexpected error occurred while generating recommendations. Please try again later.', 'mxchat'), ]; } } private function mxchat_get_user_context($user_id) { $context = [ 'order_history' => [], 'cart_items' => [], 'recently_viewed' => [], ]; // Get order history if (is_user_logged_in() && $user_id) { $orders = wc_get_orders([ 'customer_id' => $user_id, 'limit' => 5, // Last 5 orders 'orderby' => 'date', 'order' => 'DESC', ]); foreach ($orders as $order) { foreach ($order->get_items() as $item) { $context['order_history'][] = [ 'name' => $item->get_name(), 'product_id' => $item->get_product_id(), 'date' => $order->get_date_created()->format('Y-m-d') ]; } } } // Get cart items if (WC()->cart && WC()->cart->get_cart_contents_count() > 0) { foreach (WC()->cart->get_cart() as $cart_item) { $product = wc_get_product($cart_item['product_id']); if ($product) { $context['cart_items'][] = [ 'name' => $product->get_name(), 'product_id' => $product->get_id() ]; } } } // Get recently viewed items from session $viewed_products = WC()->session->get('recently_viewed_products', []); foreach ($viewed_products as $product_id) { $product = wc_get_product($product_id); if ($product) { $context['recently_viewed'][] = [ 'name' => $product->get_name(), 'product_id' => $product->get_id() ]; } } return $context; } function mxchat_fetch_new_messages() { $session_id = sanitize_text_field($_POST['session_id']); $last_seen_id = sanitize_text_field($_POST['last_seen_id']); $persistence_enabled = $_POST['persistence_enabled'] === 'true'; $initial_timestamp = isset($_POST['initial_timestamp']) ? intval($_POST['initial_timestamp']) : 0; if (empty($session_id)) { //error_log('Fetch new messages error: Session ID missing.'); wp_send_json_error(['message' => 'Session ID missing.']); wp_die(); } $history = get_option("mxchat_history_{$session_id}", []); $new_messages = array_filter($history, function ($message) use ($last_seen_id, $persistence_enabled, $initial_timestamp) { // If persistence is enabled, show all new messages if ($persistence_enabled) { return !empty($message['id']) && strcmp($message['id'], $last_seen_id) > 0 && $message['role'] === 'agent'; } // If persistence is disabled, only show messages after initial timestamp return !empty($message['id']) && $message['role'] === 'agent' && $message['timestamp'] > $initial_timestamp; }); //error_log("New agent messages fetched for session $session_id. Last seen ID: $last_seen_id"); wp_send_json_success([ 'new_messages' => array_values($new_messages) ]); wp_die(); } public function mxchat_live_agent_handover($message, $user_id, $session_id) { // First check if live agents are available $live_agent_available = $this->options['live_agent_status'] ?? 'off'; if ($live_agent_available !== 'on') { // Changed from 'online' to 'on' $away_message = $this->options['live_agent_away_message'] ?? 'Sorry, live agents are currently unavailable. I can continue helping you as an AI assistant.'; $this->fallbackResponse = [ 'text' => $away_message, 'html' => '', 'images' => [], 'chat_mode' => 'ai' ]; wp_send_json([ 'text' => $away_message, 'html' => '', 'chat_mode' => 'ai', 'session_id' => $session_id ]); wp_die(); } $slack_webhook_url = $this->options['live_agent_webhook_url'] ?? ''; //error_log('Slack Webhook URL retrieved: ' . $slack_webhook_url); if (empty($slack_webhook_url)) { //error_log('Slack Webhook URL is not configured.'); return false; } update_option("mxchat_mode_{$session_id}", 'agent'); $webhook_data = [ 'blocks' => [ [ 'type' => 'header', 'text' => [ 'type' => 'plain_text', 'text' => '🔔 New Live Agent Request', 'emoji' => true ] ], [ 'type' => 'section', 'fields' => [ [ 'type' => 'mrkdwn', 'text' => "*User ID:*\n`$user_id`" ], [ 'type' => 'mrkdwn', 'text' => "*Session ID:*\n`$session_id`" ] ] ], [ 'type' => 'section', 'text' => [ 'type' => 'mrkdwn', 'text' => "*Initial Message:*\n$message" ] ], [ 'type' => 'actions', 'elements' => [ [ 'type' => 'button', 'text' => [ 'type' => 'plain_text', 'text' => '✍️ Reply', 'emoji' => true ], 'value' => $session_id, 'action_id' => 'reply_to_user', 'style' => 'primary' ] ] ] ] ]; $response = wp_remote_post($slack_webhook_url, [ 'body' => json_encode($webhook_data), 'headers' => [ 'Content-Type' => 'application/json', ], ]); if (is_wp_error($response)) { //error_log('Error sending live agent handover: ' . $response->get_error_message()); return false; } //error_log('Live agent handover triggered successfully.'); $success_message = $this->options['live_agent_notification_message'] ?? 'Live agent has been notified.'; $this->mxchat_save_chat_message($session_id, 'bot', $success_message); $this->fallbackResponse = [ 'text' => $success_message, 'html' => '', 'images' => [], 'chat_mode' => 'agent' ]; wp_send_json([ 'success' => true, 'text' => $success_message, 'html' => '', 'chat_mode' => 'agent', 'session_id' => $session_id, 'fallbackResponse' => $this->fallbackResponse ]); wp_die(); } public function mxchat_send_user_message_to_agent($message, $user_id, $session_id) { $slack_webhook_url = $this->options['live_agent_webhook_url'] ?? ''; if (empty($slack_webhook_url)) { //error_log('Slack Webhook URL is not configured.'); return false; } $webhook_data = [ 'blocks' => [ [ 'type' => 'header', 'text' => [ 'type' => 'plain_text', 'text' => '📩 New Chat Message', 'emoji' => true ] ], [ 'type' => 'section', 'fields' => [ [ 'type' => 'mrkdwn', 'text' => "*User ID:*\n`$user_id`" ], [ 'type' => 'mrkdwn', 'text' => "*Session ID:*\n`$session_id`" ] ] ], [ 'type' => 'section', 'text' => [ 'type' => 'mrkdwn', 'text' => "*Message:*\n$message" ] ], [ 'type' => 'actions', 'elements' => [ [ 'type' => 'button', 'text' => [ 'type' => 'plain_text', 'text' => '✍️ Reply', 'emoji' => true ], 'value' => $session_id, 'action_id' => 'reply_to_user', 'style' => 'primary' ] ] ] ] ]; $response = wp_remote_post($slack_webhook_url, [ 'body' => json_encode($webhook_data), 'headers' => [ 'Content-Type' => 'application/json', ], ]); if (is_wp_error($response)) { //error_log('Error sending message to Slack: ' . $response->get_error_message()); return false; } //error_log('Message sent to Slack successfully.'); return true; } public function handle_slack_interaction(WP_REST_Request $request) { //error_log('Received Slack interaction'); $payload = json_decode($request->get_param('payload'), true); //error_log('Payload: ' . print_r($payload, true)); // Handle button click if ($payload['type'] === 'block_actions' && $payload['actions'][0]['action_id'] === 'reply_to_user') { $session_id = $payload['actions'][0]['value']; $trigger_id = $payload['trigger_id']; // Get Bot Token from settings $slack_token = $this->options['live_agent_bot_token'] ?? ''; if (empty($slack_token)) { //error_log('Slack Bot Token not configured'); return new WP_REST_Response(['error' => 'Bot token not configured'], 400); } $response = wp_remote_post('https://slack.com/api/views.open', [ 'headers' => [ 'Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $slack_token ], 'body' => json_encode([ 'trigger_id' => $trigger_id, 'view' => [ 'type' => 'modal', 'callback_id' => 'reply_modal', 'title' => [ 'type' => 'plain_text', 'text' => 'Reply to User' ], 'submit' => [ 'type' => 'plain_text', 'text' => 'Send' ], 'close' => [ 'type' => 'plain_text', 'text' => 'Cancel' ], 'blocks' => [ [ 'type' => 'input', 'block_id' => 'reply_block', 'label' => [ 'type' => 'plain_text', 'text' => "Reply to session: $session_id" ], 'element' => [ 'type' => 'plain_text_input', 'action_id' => 'message', 'multiline' => true, 'placeholder' => [ 'type' => 'plain_text', 'text' => 'Type your message here...' ] ] ] ], 'private_metadata' => $session_id ] ]) ]); //error_log('Views.open response: ' . print_r($response, true)); // Return immediate acknowledgment return new WP_REST_Response(['ok' => true]); } // Handle modal submission if ($payload['type'] === 'view_submission') { $session_id = $payload['view']['private_metadata']; $message = $payload['view']['state']['values']['reply_block']['message']['value']; // Save the message $this->mxchat_save_chat_message($session_id, 'agent', $message); return new WP_REST_Response([ 'response_action' => 'clear' ]); } // Default acknowledgment return new WP_REST_Response(['ok' => true]); } public function mxchat_handle_agent_response(WP_REST_Request $request) { //error_log('Received agent response request'); //error_log('Request data: ' . print_r($request->get_params(), true)); // error_log('Raw body: ' . file_get_contents('php://input')); // Get the data from Slack's slash command format $command_text = $request->get_param('text'); // error_log('Command text: ' . $command_text); if (empty($command_text)) { error_log('Agent response error: No command text received'); return new WP_REST_Response([ 'error' => 'Command text is required. Format: /reply session_id message' ], 400); } // Split the command text into session_id and message $parts = explode(' ', $command_text, 2); if (count($parts) !== 2) { //error_log('Agent response error: Invalid command format'); return new WP_REST_Response([ 'error' => 'Invalid format. Use: /reply session_id message' ], 400); } $session_id = sanitize_text_field($parts[0]); $message = sanitize_text_field($parts[1]); //error_log("Processing agent response - Session ID: $session_id, Message: $message"); // Save the message $message_id = $this->mxchat_save_chat_message($session_id, 'agent', $message); if (!$message_id) { // error_log('Failed to save agent message'); return new WP_REST_Response([ 'error' => 'Failed to save message' ], 500); } // Return success response in Slack's expected format return new WP_REST_Response([ 'response_type' => 'in_channel', 'text' => "Message sent successfully to session $session_id" ], 200); } public function mxchat_handle_switch_to_chatbot_intent($message, $user_id, $session_id) { //error_log("Switching back to chatbot mode via intent."); // Just update mode to AI update_option("mxchat_mode_{$session_id}", 'ai'); // Initialize states $this->fallbackResponse = ['text' => '', 'html' => '', 'images' => []]; $this->productCardHtml = ''; // Set the response message $this->fallbackResponse['text'] = 'You are now chatting with the AI chatbot.'; return true; // Intent was handled } // For the word upload handler public function mxchat_handle_word_upload() { // Delegate to word handler $this->word_handler->mxchat_handle_word_upload(); } // For the word removal handler public function mxchat_handle_word_remove() { // Delegate to word handler $this->word_handler->mxchat_handle_word_remove(); } // For the word status check public function mxchat_check_word_status() { // Delegate to word handler $this->word_handler->mxchat_check_word_status(); } private function mxchat_get_user_identifier() { return MxChat_User::mxchat_get_user_identifier(); } private function mxchat_generate_embedding($text, $api_key) { $endpoint = 'https://api.openai.com/v1/embeddings'; $body = wp_json_encode([ 'input' => $text, 'model' => 'text-embedding-ada-002' ]); $args = [ 'body' => $body, 'headers' => [ 'Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $api_key, ], 'timeout' => 60, 'redirection' => 5, 'blocking' => true, 'httpversion' => '1.0', 'sslverify' => true, ]; $response = wp_remote_post($endpoint, $args); if (is_wp_error($response)) { return null; } $response_body = json_decode(wp_remote_retrieve_body($response), true); if (isset($response_body['data'][0]['embedding']) && is_array($response_body['data'][0]['embedding'])) { return $response_body['data'][0]['embedding']; } else { return null; } } private function mxchat_find_relevant_content($user_embedding) { global $wpdb; $system_prompt_table = $wpdb->prefix . 'mxchat_system_prompt_content'; $cache_key = 'mxchat_system_prompt_embeddings'; // Retrieve embeddings from cache or database $embeddings = wp_cache_get($cache_key, 'mxchat_system_prompts'); if ($embeddings === false) { $query = "SELECT id, embedding_vector FROM {$system_prompt_table}"; $embeddings = $wpdb->get_results($query); if ($embeddings === null || empty($embeddings)) { return ''; // Return an empty string if no embeddings found } wp_cache_set($cache_key, $embeddings, 'mxchat_system_prompts', 3600); } // Initialize array to store relevant results with similarity scores $relevant_results = []; // Iterate through embeddings to calculate similarity foreach ($embeddings as $embedding) { $database_embedding = $embedding->embedding_vector ? unserialize($embedding->embedding_vector, ['allowed_classes' => false]) : null; if (is_array($database_embedding) && is_array($user_embedding)) { $similarity = $this->mxchat_calculate_cosine_similarity($user_embedding, $database_embedding); $relevant_results[] = [ 'id' => $embedding->id, 'similarity' => $similarity ]; } } // Retrieve the similarity threshold $similarity_threshold = ((int) get_option('mxchat_similarity_threshold', 80)) / 100; // Filter and sort relevant results by similarity $relevant_results = array_filter($relevant_results, function ($result) use ($similarity_threshold) { return $result['similarity'] >= $similarity_threshold; }); usort($relevant_results, function ($a, $b) { return $b['similarity'] <=> $a['similarity']; }); // Limit to the top 5 results $top_results = array_slice($relevant_results, 0, 5); // Initialize the final content $content = ''; // Fetch and combine content for the top results foreach ($top_results as $result) { $chunk_content = $this->fetch_content_with_product_links($result['id']); // Check if the content is PDF-related and add surrounding pages if (strpos($chunk_content, '{"document_type":"pdf"') !== false) { $surrounding_content = $wpdb->get_results($wpdb->prepare( "SELECT article_content FROM {$system_prompt_table} WHERE id IN ( (SELECT id FROM {$system_prompt_table} WHERE id < %d ORDER BY id DESC LIMIT 1), (SELECT id FROM {$system_prompt_table} WHERE id > %d ORDER BY id ASC LIMIT 1) )", $result['id'], $result['id'] )); // Add previous content if it exists if (!empty($surrounding_content[0])) { $content .= $surrounding_content[0]->article_content . "\n\n"; } // Add the main chunk content $content .= $chunk_content . "\n\n"; // Add next content if it exists if (!empty($surrounding_content[1])) { $content .= $surrounding_content[1]->article_content . "\n\n"; } } else { // For non-PDF content, add directly $content .= $chunk_content . "\n\n"; } } return trim($content); // Return the combined content } private function mxchat_find_relevant_products($user_embedding) { global $wpdb; $system_prompt_table = $wpdb->prefix . 'mxchat_system_prompt_content'; $cache_key = 'mxchat_system_prompt_embeddings'; // Retrieve embeddings from cache or database $embeddings = wp_cache_get($cache_key, 'mxchat_system_prompts'); if ($embeddings === false) { $query = "SELECT id, embedding_vector FROM {$system_prompt_table}"; $embeddings = $wpdb->get_results($query); if ($embeddings === null || empty($embeddings)) { return ''; // Return an empty string if no embeddings found } wp_cache_set($cache_key, $embeddings, 'mxchat_system_prompts', 3600); } // Initialize array to store relevant results with similarity scores $relevant_results = []; // Iterate through embeddings to calculate similarity foreach ($embeddings as $embedding) { $database_embedding = $embedding->embedding_vector ? unserialize($embedding->embedding_vector, ['allowed_classes' => false]) : null; if (is_array($database_embedding) && is_array($user_embedding)) { $similarity = $this->mxchat_calculate_cosine_similarity($user_embedding, $database_embedding); $relevant_results[] = [ 'id' => $embedding->id, 'similarity' => $similarity ]; } } // Use a fixed similarity threshold of 0.85 $similarity_threshold = 0.85; // Filter and sort relevant results by similarity $relevant_results = array_filter($relevant_results, function ($result) use ($similarity_threshold) { return $result['similarity'] >= $similarity_threshold; }); usort($relevant_results, function ($a, $b) { return $b['similarity'] <=> $a['similarity']; }); // Limit to the top 5 results $top_results = array_slice($relevant_results, 0, 5); // Initialize the final content $content = ''; // Fetch and combine content for the top results foreach ($top_results as $result) { $chunk_content = $this->fetch_content_with_product_links($result['id']); // Check if the content is PDF-related and add surrounding pages if (strpos($chunk_content, '{"document_type":"pdf"') !== false) { $surrounding_content = $wpdb->get_results($wpdb->prepare( "SELECT article_content FROM {$system_prompt_table} WHERE id IN ( (SELECT id FROM {$system_prompt_table} WHERE id < %d ORDER BY id DESC LIMIT 1), (SELECT id FROM {$system_prompt_table} WHERE id > %d ORDER BY id ASC LIMIT 1) )", $result['id'], $result['id'] )); // Add previous content if it exists if (!empty($surrounding_content[0])) { $content .= $surrounding_content[0]->article_content . "\n\n"; } // Add the main chunk content $content .= $chunk_content . "\n\n"; // Add next content if it exists if (!empty($surrounding_content[1])) { $content .= $surrounding_content[1]->article_content . "\n\n"; } } else { // For non-PDF content, add directly $content .= $chunk_content . "\n\n"; } } return trim($content); // Return the combined content } private function fetch_content_with_product_links($most_relevant_id) { global $wpdb; $system_prompt_table = $wpdb->prefix . 'mxchat_system_prompt_content'; // Fetch the article content and associated product URL $query = $wpdb->prepare("SELECT article_content, source_url FROM {$system_prompt_table} WHERE id = %d", $most_relevant_id); $result = $wpdb->get_row($query); if ($result) { // Append the product link to the content if available $content = $result->article_content; if (!empty($result->source_url)) { $content .= "\n\nFor more details, check out this product: " . esc_url($result->source_url); } return $content; } return null; } // Function definition private function mxchat_generate_response($relevant_content, $api_key, $xai_api_key, $claude_api_key, $conversation_history) { if (!$relevant_content) { return "I'm sorry, I couldn't find relevant information on that topic."; } // Ensure conversation_history is an array if (!is_array($conversation_history)) { $conversation_history = array(); } // Check the selected model $selected_model = isset($this->options['model']) ? $this->options['model'] : 'gpt-4o'; // Call the appropriate function based on the selected model if (strpos($selected_model, 'claude') !== false) { return $this->mxchat_generate_response_claude($selected_model, $claude_api_key, $conversation_history, $relevant_content); } elseif ($selected_model === 'grok-beta') { return $this->mxchat_generate_response_xai($selected_model, $xai_api_key, $conversation_history, $relevant_content); } elseif ($selected_model === 'deepseek-chat') { return $this->mxchat_generate_response_deepseek($selected_model, $this->options['deepseek_api_key'], $conversation_history, $relevant_content); } else { return $this->mxchat_generate_response_openai($selected_model, $api_key, $conversation_history, $relevant_content); } } private function mxchat_generate_response_deepseek($selected_model, $deepseek_api_key, $conversation_history, $relevant_content) { // Ensure conversation_history is an array if (!is_array($conversation_history)) { $conversation_history = array(); } // Get system prompt instructions from options $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : ''; // Create a new array for the formatted conversation $formatted_conversation = array(); // Add system message first $formatted_conversation[] = array( 'role' => 'system', 'content' => $system_prompt_instructions . " " . $relevant_content ); // Add the rest of the conversation history foreach ($conversation_history as $message) { if (is_array($message) && isset($message['role']) && isset($message['content'])) { $role = $message['role']; // Convert roles to supported format if ($role === 'bot' || $role === 'agent') { $role = 'assistant'; } if (!in_array($role, ['system', 'assistant', 'user', 'function', 'tool'])) { $role = 'user'; } $formatted_conversation[] = array( 'role' => $role, 'content' => $message['content'] ); } } $body = json_encode([ 'model' => $selected_model, 'messages' => $formatted_conversation, 'temperature' => 0.8, 'stream' => false ]); $args = [ 'body' => $body, 'headers' => [ 'Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $deepseek_api_key, ], 'timeout' => 60, 'redirection' => 5, 'blocking' => true, 'httpversion' => '1.0', 'sslverify' => true, ]; $response = wp_remote_post('https://api.deepseek.com/v1/chat/completions', $args); if (is_wp_error($response)) { error_log('DeepSeek API Error: ' . $response->get_error_message()); return "Sorry, there was an error processing your request."; } $response_body = wp_remote_retrieve_body($response); $decoded_response = json_decode($response_body, true); if (isset($decoded_response['choices'][0]['message']['content'])) { return trim($decoded_response['choices'][0]['message']['content']); } else { error_log('DeepSeek API Response Format Error: ' . print_r($decoded_response, true)); return "Sorry, I couldn't process that request."; } } private function mxchat_generate_response_openai($selected_model, $api_key, $conversation_history, $relevant_content) { // Ensure conversation_history is an array if (!is_array($conversation_history)) { $conversation_history = array(); } // Get system prompt instructions from options $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : ''; // Create a new array for the formatted conversation $formatted_conversation = array(); // Add system message first $formatted_conversation[] = array( 'role' => 'system', 'content' => $system_prompt_instructions . " " . $relevant_content ); // Add the rest of the conversation history foreach ($conversation_history as $message) { if (is_array($message) && isset($message['role']) && isset($message['content'])) { $role = $message['role']; // Convert roles to supported format if ($role === 'bot' || $role === 'agent') { $role = 'assistant'; } if (!in_array($role, ['system', 'assistant', 'user', 'function', 'tool'])) { $role = 'user'; } $formatted_conversation[] = array( 'role' => $role, 'content' => $message['content'] ); } } $body = json_encode([ 'model' => $selected_model, 'messages' => $formatted_conversation, 'temperature' => 0.8, 'stream' => false ]); $args = [ 'body' => $body, 'headers' => [ 'Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $api_key, ], 'timeout' => 60, 'redirection' => 5, 'blocking' => true, 'httpversion' => '1.0', 'sslverify' => true, ]; $response = wp_remote_post('https://api.openai.com/v1/chat/completions', $args); if (is_wp_error($response)) { error_log('OpenAI API Error: ' . $response->get_error_message()); return "Sorry, there was an error processing your request."; } $response_body = wp_remote_retrieve_body($response); $decoded_response = json_decode($response_body, true); if (isset($decoded_response['choices'][0]['message']['content'])) { return trim($decoded_response['choices'][0]['message']['content']); } else { error_log('OpenAI API Response Format Error: ' . print_r($decoded_response, true)); return "Sorry, I couldn't process that request."; } } private function mxchat_generate_response_xai($selected_model, $xai_api_key, $conversation_history, $relevant_content) { // Get system prompt instructions from options $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : ''; // Add system prompt to relevant content $content_with_instructions = $system_prompt_instructions . " " . $relevant_content; // Prepend system instructions to the conversation history array_unshift($conversation_history, [ 'role' => 'system', 'content' => "Here are your instructions: " . $content_with_instructions ]); // Ensure consistency: Replace 'bot' and 'agent' roles with supported values foreach ($conversation_history as &$message) { if ($message['role'] === 'bot') { $message['role'] = 'assistant'; } elseif ($message['role'] === 'agent') { // Tag the message as coming from a live agent $message['role'] = 'assistant'; if (!isset($message['metadata'])) { $message['metadata'] = ['source' => 'live_agent']; } } // Ensure all roles are valid if (!in_array($message['role'], ['system', 'assistant', 'user', 'function', 'tool'])) { $message['role'] = 'user'; // Default to 'user' } } // Build the request body $body = json_encode([ 'model' => $selected_model, 'messages' => $conversation_history, 'temperature' => 0.8, 'stream' => false ]); // Set up the API request $args = [ 'body' => $body, 'headers' => [ 'Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $xai_api_key, ], 'timeout' => 60, 'redirection' => 5, 'blocking' => true, 'httpversion' => '1.0', 'sslverify' => true, ]; // Make the API request $response = wp_remote_post('https://api.x.ai/v1/chat/completions', $args); // Process the response if (is_wp_error($response)) { return "Sorry, there was an error processing your request."; } $response_body = json_decode(wp_remote_retrieve_body($response), true); if (isset($response_body['choices'][0]['message']['content'])) { return trim($response_body['choices'][0]['message']['content']); } else { return "Sorry, I couldn't process that request."; } } private function mxchat_generate_response_claude($selected_model, $claude_api_key, $conversation_history, $relevant_content) { // Get system prompt instructions from options for Claude's top-level system parameter $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : ''; // Ensure consistency: Replace 'bot' and 'agent' roles with supported values foreach ($conversation_history as &$message) { if ($message['role'] === 'bot') { $message['role'] = 'assistant'; } elseif ($message['role'] === 'agent') { // Tag the message as coming from a live agent $message['role'] = 'assistant'; if (!isset($message['metadata'])) { $message['metadata'] = ['source' => 'live_agent']; } } // Ensure all roles are valid if (!in_array($message['role'], ['system', 'assistant', 'user', 'function', 'tool'])) { $message['role'] = 'user'; // Default to 'user' } } // Add relevant content as the latest user message in conversation history $conversation_history[] = [ 'role' => 'user', 'content' => $relevant_content ]; // Build the request body with Claude's expected structure, using system instructions as a top-level parameter $body = json_encode([ 'model' => $selected_model, 'max_tokens' => 1000, 'temperature' => 0.8, 'system' => $system_prompt_instructions, // Set the system prompt at the top level as required 'messages' => $conversation_history ]); // Set up the API request with the necessary headers $args = [ 'body' => $body, 'headers' => [ 'Content-Type' => 'application/json', 'x-api-key' => $claude_api_key, 'anthropic-version' => '2023-06-01', ], 'timeout' => 60, 'redirection' => 5, 'blocking' => true, 'httpversion' => '1.0', 'sslverify' => true, ]; // Make the API request $response = wp_remote_post('https://api.anthropic.com/v1/messages', $args); /* // Check for errors and log the response for debugging if (is_wp_error($response)) { error_log("Claude API request error: " . print_r($response->get_error_message(), true)); return "Sorry, there was an error processing your request."; } */ // Decode the response and parse according to the expected Claude response structure $response_body = json_decode(wp_remote_retrieve_body($response), true); //error_log("Claude API response: " . print_r($response_body, true)); // Check if the response has the expected 'content' array with 'text' blocks if (isset($response_body['content'][0]['text'])) { return trim($response_body['content'][0]['text']); } else { return "Sorry, I couldn't process that request."; } } public function mxchat_dismiss_pre_chat_message() { // Get and sanitize the user identifier $user_id = $this->mxchat_get_user_identifier(); $user_id = sanitize_key($user_id); // Set a transient to track that the user has dismissed the pre-chat message $transient_key = 'mxchat_pre_chat_message_dismissed_' . $user_id; set_transient($transient_key, true, DAY_IN_SECONDS); wp_send_json_success(); } public function mxchat_check_pre_chat_message_status() { // Get and sanitize the user identifier $user_id = $this->mxchat_get_user_identifier(); $user_id = sanitize_key($user_id); // Check if the transient exists (i.e., if the message was dismissed) $transient_key = 'mxchat_pre_chat_message_dismissed_' . $user_id; $dismissed = get_transient($transient_key); // Log the result to see if it's being set correctly //error_log("Check pre-chat message dismissed for $user_id: " . ($dismissed ? 'Yes' : 'No')); if ($dismissed) { wp_send_json_success(['dismissed' => true]); } else { wp_send_json_success(['dismissed' => false]); } wp_die(); } private function mxchat_calculate_cosine_similarity($vectorA, $vectorB) { if (!is_array($vectorA) || !is_array($vectorB) || empty($vectorA) || empty($vectorB)) { return 0; } $dotProduct = array_sum(array_map(function ($a, $b) { return $a * $b; }, $vectorA, $vectorB)); $normA = sqrt(array_sum(array_map(function ($a) { return $a * $a; }, $vectorA))); $normB = sqrt(array_sum(array_map(function ($b) { return $b * $b; }, $vectorB))); if ($normA == 0 || $normB == 0) { return 0; } return $dotProduct / ($normA * $normB); } public function mxchat_enqueue_scripts_styles() { // Define version numbers for the styles and scripts $chat_style_version = '1.6.3'; // Replace with your actual version $chat_script_version = '1.6.3'; // Replace with your actual version // Enqueue the script wp_enqueue_script( 'mxchat-chat-js', plugin_dir_url(__FILE__) . '../js/chat-script.js', array('jquery'), $chat_script_version, true ); // Enqueue the CSS wp_enqueue_style( 'mxchat-chat-css', plugin_dir_url(__FILE__) . '../css/chat-style.css', array(), $chat_style_version ); // Fetch options from the database $this->options = get_option('mxchat_options'); // Prepare settings for JavaScript $style_settings = array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('mxchat_chat_nonce'), 'link_target_toggle' => $this->options['link_target_toggle'] ?? 'off', 'rate_limit_message' => $this->options['rate_limit_message'] ?? 'Rate limit exceeded. Please try again later.', 'complianz_toggle' => isset($this->options['complianz_toggle']) && $this->options['complianz_toggle'] === 'on', 'user_message_bg_color' => $this->options['user_message_bg_color'] ?? '#fff', 'user_message_font_color' => $this->options['user_message_font_color'] ?? '#212121', 'bot_message_bg_color' => $this->options['bot_message_bg_color'] ?? '#212121', 'bot_message_font_color' => $this->options['bot_message_font_color'] ?? '#fff', 'top_bar_bg_color' => $this->options['top_bar_bg_color'] ?? '#212121', 'send_button_font_color' => $this->options['send_button_font_color'] ?? '#212121', 'close_button_color' => $this->options['close_button_color'] ?? '#fff', 'chatbot_background_color' => $this->options['chatbot_background_color'] ?? '#212121', 'chatbot_bg_color' => $this->options['chatbot_bg_color'] ?? '#fff', 'icon_color' => $this->options['icon_color'] ?? '#fff', 'chat_input_font_color' => $this->options['chat_input_font_color'] ?? '#212121', 'chat_persistence_toggle' => $this->options['chat_persistence_toggle'] ?? 'off', 'appendWidgetToBody' => $this->options['append_to_body'] ?? 'off', // Example for consistency 'live_agent_message_bg_color' => $this->options['live_agent_message_bg_color'] ?? '#ffffff', 'live_agent_message_font_color' => $this->options['live_agent_message_font_color'] ?? '#333333', 'chat_toolbar_toggle' => $this->options['chat_toolbar_toggle'] ?? 'off', 'mode_indicator_bg_color' => $this->options['mode_indicator_bg_color'] ?? '#767676', 'mode_indicator_font_color' => $this->options['mode_indicator_font_color'] ?? '#ffffff', 'toolbar_icon_color' => $this->options['toolbar_icon_color'] ?? '#212121', ); // Pass the settings to the script wp_localize_script('mxchat-chat-js', 'mxchatChat', $style_settings); } public function mxchat_reset_rate_limits() { global $wpdb; // Define a cache key pattern for rate limits $cache_key_pattern = 'mxchat_chat_limit_%'; // Retrieve all option names matching the pattern // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery $option_names = $wpdb->get_col("SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE 'mxchat_chat_limit_%'"); // db call ok; no-cache ok // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- db call ok $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE 'mxchat_chat_limit_%'"); // Clear the relevant cache entries foreach ($option_names as $option_name) { wp_cache_delete($option_name, 'options'); } // Optionally, clear a general cache if you have one wp_cache_delete('mxchat_all_chat_limits', 'options'); } private function mxchat_fetch_woocommerce_products() { // Ensure WooCommerce is active if (!class_exists('WooCommerce')) { return []; } $args = array( 'post_type' => 'product', 'post_status' => 'publish', 'posts_per_page' => -1, ); $products = get_posts($args); $product_data = []; foreach ($products as $product) { $product_id = $product->ID; $product_obj = wc_get_product($product_id); $product_data[] = array( 'id' => $product_id, 'name' => $product_obj->get_name(), 'description' => $product_obj->get_description(), 'short_description' => $product_obj->get_short_description(), 'url' => get_permalink($product_id), 'price' => $product_obj->get_regular_price(), 'sale_price' => $product_obj->get_sale_price(), 'stock_status' => $product_obj->get_stock_status(), 'sku' => $product_obj->get_sku(), 'in_stock' => $product_obj->is_in_stock(), 'total_sales' => $product_obj->get_total_sales(), ); } return $product_data; } } ?>