options = get_option('mxchat_options'); $this->chat_count = get_option('mxchat_chat_count', 0); 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')); if (!wp_next_scheduled('mxchat_reset_rate_limits')) { wp_schedule_event(time(), 'daily', 'mxchat_reset_rate_limits'); } add_action('mxchat_reset_rate_limits', array($this, 'mxchat_reset_rate_limits')); } private function mxchat_increment_chat_count() { $chat_count = get_option('mxchat_chat_count', 0); $chat_count++; update_option('mxchat_chat_count', $chat_count); } public function mxchat_fetch_conversation_history_for_ajax($session_id) { global $wpdb; $table_name = $wpdb->prefix . 'mxchat_chat_transcripts'; // Prepare and execute the query safely $chat_transcripts = $wpdb->get_results( $wpdb->prepare("SELECT * FROM $table_name WHERE session_id = %s ORDER BY timestamp ASC", sanitize_text_field($session_id)) ); // Check if results are empty if (empty($chat_transcripts)) { return []; } // Build the conversation history $conversation_history = []; foreach ($chat_transcripts as $transcript) { $conversation_history[] = [ 'role' => $transcript->role, 'content' => $transcript->message ]; } return $conversation_history; } private function mxchat_save_chat_message($session_id, $role, $message) { global $wpdb; $table_name = $wpdb->prefix . 'mxchat_chat_transcripts'; $wpdb->insert($table_name, [ 'user_id' => 0, 'session_id' => $session_id, 'role' => $role, 'message' => $message, 'timestamp' => current_time('mysql', 1) ]); } public function mxchat_handle_chat_request() { global $wpdb; // Get and sanitize the user identifier $user_id = $this->mxchat_get_user_identifier(); $user_id = sanitize_key($user_id); // Manage rate limiting $rate_limit_transient_key = 'mxchat_chat_limit_' . $user_id; $chat_count = get_transient($rate_limit_transient_key); $session_transient_key = 'mxchat_chat_session_' . $user_id; $session_id = get_transient($session_transient_key); if ($chat_count === false) { $chat_count = 0; } if ($session_id === false) { $session_id = uniqid('mxchat_chat_', true); set_transient($session_transient_key, $session_id, DAY_IN_SECONDS); // Store session ID for a day } $rate_limit_option = isset($this->options['rate_limit']) ? $this->options['rate_limit'] : 'unlimited'; // Check if rate limit is not 'unlimited' if ($rate_limit_option !== 'unlimited') { $rate_limit = intval($rate_limit_option); if ($chat_count >= $rate_limit) { wp_send_json_error('Rate limit exceeded. Please try again later.'); wp_die(); } set_transient($rate_limit_transient_key, $chat_count + 1, DAY_IN_SECONDS); } // Validate and sanitize the incoming message if (!isset($_POST['message'])) { wp_send_json_error('No message received'); wp_die(); } $message = sanitize_text_field($_POST['message']); if (empty($message)) { wp_send_json_error('Message is empty or invalid.'); wp_die(); } // Save the user message to the database $this->mxchat_save_chat_message($session_id, 'user', $message); // Generate and validate the embedding $user_message_embedding = $this->mxchat_generate_embedding($message, $this->options['api_key']); if (!is_array($user_message_embedding)) { wp_send_json_error('Error processing your message.'); wp_die(); } // Find relevant content based on embedding $relevant_content = $this->mxchat_find_relevant_content($user_message_embedding); // Fetch conversation history from the database $conversation_history = $this->mxchat_fetch_conversation_history_for_ajax($session_id); // Increment the chat count $this->mxchat_increment_chat_count(); // Generate a response from the AI model $response = $this->mxchat_generate_response($relevant_content, $this->options['api_key'], $conversation_history); // Save the bot response to the database $this->mxchat_save_chat_message($session_id, 'bot', $response); // Send the response back to the client wp_send_json(['message' => $response]); wp_die(); } private function mxchat_get_user_identifier() { return sanitize_text_field($_SERVER['REMOTE_ADDR']); } 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'; // Define a cache key for embeddings $cache_key = 'mxchat_system_prompt_embeddings'; // Attempt to get the embeddings from the cache $embeddings = wp_cache_get($cache_key, 'mxchat_system_prompts'); if ($embeddings === false) { // Cache miss, query the database and cache the results $query = "SELECT id, embedding_vector FROM {$system_prompt_table}"; $embeddings = $wpdb->get_results($query); if ($embeddings === null || empty($embeddings)) { error_log("No embeddings found in the database."); return null; // Return null to handle no embeddings gracefully } // Cache the results if successful wp_cache_set($cache_key, $embeddings, 'mxchat_system_prompts', 3600); // Cache for 1 hour } $most_relevant_id = null; $highest_similarity = -INF; foreach ($embeddings as $embedding) { $database_embedding = maybe_unserialize($embedding->embedding_vector); // Debugging: Log the embeddings if (!is_array($database_embedding)) { error_log("Invalid database embedding format for ID {$embedding->id}: " . print_r($database_embedding, true)); continue; } if (is_array($user_embedding)) { $similarity = $this->mxchat_calculate_cosine_similarity($user_embedding, $database_embedding); // Debugging: Log the similarity score error_log("Calculated similarity for ID {$embedding->id}: {$similarity}"); if ($similarity > $highest_similarity) { $highest_similarity = $similarity; $most_relevant_id = $embedding->id; } } else { error_log("User embedding is not an array. Embedding data: " . print_r($user_embedding, true)); } } if ($most_relevant_id !== null) { // Define a cache key for the relevant content $content_cache_key = 'mxchat_article_content_' . $most_relevant_id; // Attempt to get the relevant content from the cache $relevant_content = wp_cache_get($content_cache_key, 'mxchat_system_prompts'); if ($relevant_content === false) { // Cache miss, query the database and cache the result $query = $wpdb->prepare("SELECT article_content FROM {$system_prompt_table} WHERE id = %d", $most_relevant_id); $relevant_content = $wpdb->get_var($query); if ($relevant_content === null) { error_log("No relevant content found for ID {$most_relevant_id}."); return null; // Return null if no content is found } wp_cache_set($content_cache_key, $relevant_content, 'mxchat_system_prompts', 3600); // Cache for 1 hour } return $relevant_content; } error_log("No relevant content found. Most relevant ID was null."); return null; // Return null if no relevant content is found } private function mxchat_generate_response($relevant_content, $api_key, $conversation_history) { if (!$relevant_content) { return "I'm sorry, I couldn't find relevant information on that topic."; } $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : ''; $content_with_instructions = $system_prompt_instructions . " " . $relevant_content; array_unshift($conversation_history, [ 'role' => 'system', 'content' => "Here are your instructions: " . $content_with_instructions ]); foreach ($conversation_history as &$message) { if ($message['role'] === 'bot') { $message['role'] = 'assistant'; } } $api_url = 'https://api.openai.com/v1/chat/completions'; $body = json_encode([ 'model' => isset($this->options['model']) ? $this->options['model'] : 'gpt-3.5', 'messages' => $conversation_history, ]); $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($api_url, $args); 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'])) { if (isset($response_body['usage'])) { $prompt_tokens = $response_body['usage']['prompt_tokens']; $total_tokens = $response_body['usage']['total_tokens']; } return trim($response_body['choices'][0]['message']['content']); } 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(); } 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.0.5'; // Replace with your actual version $chat_script_version = '1.0.5'; // Replace with your actual version // Correct path to the script file wp_enqueue_script( 'mxchat-chat-js', // Handle for the script plugin_dir_url(__FILE__) . '../js/chat-script.js', // Correct path using __FILE__ array('jquery'), // Dependencies $chat_script_version, // Version for cache busting true // Load script in footer ); // Enqueue the CSS file similarly wp_enqueue_style( 'mxchat-chat-css', // Handle for the style plugin_dir_url(__FILE__) . '../css/chat-style.css', // Correct path using __FILE__ array(), // No dependencies $chat_style_version // Version for cache busting ); // Fetch options from the database $this->options = get_option('mxchat_options'); // Prepare settings to pass to JavaScript $style_settings = array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('mxchat_chat_nonce'), // Nonce for security 'rate_limit_message' => 'Rate limit exceeded. Please try again later.', 'appendWidgetToBody' => $this->options['append_to_body'] ?? 'off' ); // Localize the script with necessary data 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'); } } ?>