options = get_option('mxchat_options');
$this->chat_count = get_option('mxchat_chat_count', 0);
// 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_action('mxchat_reset_rate_limits', array($this, 'mxchat_reset_rate_limits'));
}
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') {
$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
if (empty($history)) {
wp_send_json_error(['message' => 'No history found.']);
wp_die();
}
wp_send_json_success(['conversation' => $history]);
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 = [];
foreach ($history as $entry) {
// Skip messages containing HTML
if ($entry['content'] !== strip_tags($entry['content'])) {
continue;
}
$formatted_history[] = [
'role' => $entry['role'],
'content' => $entry['content']
];
}
return $formatted_history;
}
private function mxchat_save_chat_message($session_id, $role, $message) {
global $wpdb;
$table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
$user_id = is_user_logged_in() ? get_current_user_id() : 0;
$user_identifier = MxChat_User::mxchat_get_user_identifier();
$user_email = MxChat_User::mxchat_get_user_email();
$history = get_option("mxchat_history_{$session_id}", []);
$history[] = ['role' => $role, 'content' => $message];
update_option("mxchat_history_{$session_id}", $history);
$wpdb->insert($table_name, [
'user_id' => $user_id,
'user_identifier' => $user_identifier,
'user_email' => $user_email,
'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);
// Setup rate limiting
$rate_limit_transient_key = 'mxchat_chat_limit_' . $user_id;
$chat_count = get_transient($rate_limit_transient_key) ?: 0;
// Retrieve the session ID from the client's POST data
$session_id = isset($_POST['session_id']) ? sanitize_text_field($_POST['session_id']) : '';
if (empty($session_id)) {
wp_send_json_error('Session ID is missing.');
wp_die();
}
// Check rate limit
$rate_limit_option = $this->options['rate_limit'] ?? 'unlimited';
if ($rate_limit_option !== 'unlimited' && $chat_count >= intval($rate_limit_option)) {
wp_send_json_error(['message' => $this->options['rate_limit_message'] ?? 'Rate limit exceeded. Please try again later.']);
wp_die();
}
// Increment chat count only if limit is not 'unlimited'
if ($rate_limit_option !== 'unlimited') {
set_transient($rate_limit_transient_key, $chat_count + 1, DAY_IN_SECONDS);
}
// Validate and sanitize the incoming message
if (empty($_POST['message'])) {
wp_send_json_error('No message received');
wp_die();
}
$message = sanitize_text_field($_POST['message']);
$this->mxchat_save_chat_message($session_id, 'user', $message);
// Track email capture and WooCommerce flows
$email_capture_prompt = get_transient('mxchat_email_capture_' . $user_id);
$interaction_count = get_transient('mxchat_email_interaction_count_' . $user_id) ?: 0;
$woocommerce_prompt = get_transient('mxchat_woocommerce_prompt_' . $user_id);
// Handle email capture flow
if ($email_capture_prompt) {
if (preg_match('/[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}/i', $message, $matches)) {
$email = $matches[0];
$this->add_email_to_loops($email);
$response = $this->options['email_capture_response'] ?? 'Thank you for providing your email! You\'ve been added to our list.';
delete_transient('mxchat_email_capture_' . $user_id);
delete_transient('mxchat_email_interaction_count_' . $user_id);
delete_transient('mxchat_woocommerce_prompt_' . $user_id);
$this->mxchat_save_chat_message($session_id, 'bot', $response);
wp_send_json(['message' => $response]);
wp_die();
} else {
if ($interaction_count >= 3) {
delete_transient('mxchat_email_capture_' . $user_id);
delete_transient('mxchat_email_interaction_count_' . $user_id);
} else {
set_transient('mxchat_email_interaction_count_' . $user_id, ++$interaction_count, 5 * MINUTE_IN_SECONDS);
}
}
}
// Initialize variables for storing intent and fallback response data
$this->fallbackResponse = [
'text' => '',
'html' => '',
'images' => [],
];
$this->productCardHtml = ''; // Initialize product card HTML
$intent_info = '';
// Step 1: Detect intent and handle intent-based responses if applicable
$intent_matched = $this->mxchat_check_intent_and_invoke_callback($message, $user_id, $session_id);
/*
// Log the intent match and fallback response
if (defined('WP_DEBUG') && WP_DEBUG) {
error_log("Intent matched: " . ($intent_matched ? 'Yes' : 'No'));
error_log("Fallback response after intent handling: " . print_r($this->fallbackResponse['text'], true));
}
*/
// Store any intent-based info from fallbackResponse for use in context
if (!empty($this->fallbackResponse['text'])) {
$intent_info = $this->fallbackResponse['text'];
}
// Step 2: Check for relevant knowledge database content based on user message 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();
}
// Fetch relevant content from the knowledge base using the embedding
$relevant_content = $this->mxchat_find_relevant_content($user_message_embedding);
// Step 3: Prepare context for AI by combining user query, intent-based info, and knowledge base content
$context_content = "User asked: '{$message}'\n\n";
// Append intent-based information if any
if (!empty($intent_info)) {
$context_content .= "Relevant intent-based information:\n" . $intent_info . "\n\n";
}
// Append relevant knowledge base content if any
if (!empty($relevant_content)) {
$context_content .= "Relevant content from knowledge database:\n" . $relevant_content . "\n\n";
}
// Step 4: Generate AI response based on context and conversation history
$conversation_history = $this->mxchat_fetch_conversation_history_for_ai($session_id);
$this->mxchat_increment_chat_count();
// Call AI to generate a response with the full context
$response = $this->mxchat_generate_response(
$context_content,
$this->options['api_key'],
$this->options['xai_api_key'],
$this->options['claude_api_key'],
$conversation_history
);
// Step 5: Save the response in chat history and prepare to send it back to the user
$this->mxchat_save_chat_message($session_id, 'bot', $response);
// Step 4: Then save any intent content
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']);
}
$response_data = [
'text' => $response,
'html' => !empty($this->productCardHtml) ? $this->productCardHtml : (isset($this->fallbackResponse['html']) ? $this->fallbackResponse['html'] : ''),
'session_id' => $session_id
];
//error_log("Response Data Sent to Frontend: " . print_r($response_data, true));
// Send response to the user
wp_send_json($response_data);
wp_die();
}
// 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;
// Log that we are starting the intent check
//error_log("Starting intent check for message: " . $message);
// Embed the user message
$user_embedding = $this->mxchat_generate_embedding($message, $this->options['api_key']);
if (!is_array($user_embedding)) {
// error_log('Error generating embedding for the message.');
wp_send_json_error('Error generating embedding for the message.');
return false;
}
// Fetch all intents from the database
$table_name = $wpdb->prefix . 'mxchat_intents';
$intents = $wpdb->get_results("SELECT * FROM $table_name");
// Log if no intents are found
if (empty($intents)) {
//error_log("No intents found in the database.");
}
$highest_similarity = -INF;
$matched_intent = null;
// Calculate similarity for each intent
foreach ($intents as $intent) {
// Use unserialize with allowed_classes parameter for enhanced security
$intent_embedding = $intent->embedding_vector
? unserialize($intent->embedding_vector, ['allowed_classes' => false])
: null;
if (!is_array($intent_embedding)) {
//error_log("Invalid embedding for intent: " . $intent->intent_label);
continue; // Skip if the embedding is invalid
}
$similarity = $this->mxchat_calculate_cosine_similarity($user_embedding, $intent_embedding);
//error_log("Similarity for intent '{$intent->intent_label}': " . $similarity);
if ($similarity > $highest_similarity) {
$highest_similarity = $similarity;
$matched_intent = $intent;
}
}
// Define the similarity threshold for matching
$similarity_threshold = 0.80;
// If a match is found above the threshold, call the associated callback
if ($highest_similarity >= $similarity_threshold && $matched_intent) {
//error_log("Matched intent: " . $matched_intent->intent_label . " with similarity: " . $highest_similarity);
// Ensure the callback function exists before calling it
if (method_exists($this, $matched_intent->callback_function)) {
// Call the intent handler function
call_user_func([$this, $matched_intent->callback_function], $message, $user_id, $session_id);
return true; // Indicate that an intent was matched and handled
} else {
//error_log("Callback function not found: " . $matched_intent->callback_function);
}
} else {
//error_log("No intent matched above the threshold.");
}
return false; // No intent was matched above the threshold
}
//verified good
public function mxchat_handle_order_history($message, $user_id, $session_id) {
// Check if WooCommerce is active
if (!class_exists('WooCommerce')) {
$this->fallbackResponse['text'] = "Order functionality is currently unavailable.";
return; // No need to proceed further
}
// Determine if the user is asking for the "last order" or "all orders"
$queryType = (stripos($message, 'last order') !== false || stripos($message, 'recent order') !== false) ? 'last' : 'all';
// Fetch the relevant order details
$orderDetails = MxChat_WooCommerce::mxchat_fetch_user_orders_details($queryType);
// Set the fallback response to order details, which will be used if needed
$this->fallbackResponse['text'] = $orderDetails ?: "No previous orders found.";
}
//verified good
public function mxchat_handle_product_inquiry( $message, $user_id, $session_id ) {
// Attempt to retrieve the product ID from the message
$product_id = MxChat_WooCommerce::mxchat_extract_product_id_from_message( $message );
// Check if there's a previously discussed product for follow-up queries
if ( ! $product_id ) {
$product_id = get_transient( 'mxchat_last_discussed_product_' . $user_id );
}
if ( $product_id && class_exists( 'WooCommerce' ) ) {
$product = wc_get_product( $product_id );
if ( $product ) {
// Escape and prepare product data
$product_name = esc_html( $product->get_name() );
$product_price = $product->get_price_html(); // Assuming this is safe 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 );
// Generate the product card HTML using HEREDOC syntax
$product_card_html = <<
';
$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);
}
}
//very good
public function mxchat_handle_add_to_cart_intent($message, $user_id, $session_id) {
// Check if WooCommerce is active before proceeding
if (!class_exists('WooCommerce')) {
// error_log("WooCommerce is not active. Add-to-cart intent cannot be processed.");
return false;
}
// Sanitize user ID for transient key
$sanitized_user_id = sanitize_key($user_id);
// Retrieve the last discussed product ID from the transient
$last_product_id = get_transient('mxchat_last_discussed_product_' . $sanitized_user_id);
if ($last_product_id) {
// Attempt to add the product to the WooCommerce cart
$added = WC()->cart->add_to_cart($last_product_id);
$product = wc_get_product($last_product_id);
if ($added && $product) {
// Escape product name for safety
$product_name = esc_html($product->get_name());
// Success response if the product is added to the cart
$response = "The product '{$product_name}' has been added to your cart. To proceed to checkout, please type 'checkout'.";
set_transient('mxchat_checkout_prompt_' . $sanitized_user_id, true, 5 * MINUTE_IN_SECONDS);
// Save the response in chat history and send a JSON response
$this->mxchat_save_chat_message($session_id, 'bot', $response);
wp_send_json(['message' => $response]);
wp_die();
} else {
// Failure response if the product could not be added
$response = "Sorry, I couldn't add the product to your cart. Please try again.";
$this->mxchat_save_chat_message($session_id, 'bot', $response);
wp_send_json(['message' => $response]);
wp_die();
}
} else {
// Prompt the user to specify the product name if no product was found
$response = "I couldn't find the product to add. Please mention the product name again.";
$this->mxchat_save_chat_message($session_id, 'bot', $response);
wp_send_json(['message' => $response]);
wp_die();
}
}
public function mxchat_handle_checkout_intent($message, $user_id, $session_id) {
// Ensure WooCommerce is available and perform checkout actions
if (class_exists('WooCommerce')) {
// Sanitize user ID for transient key
$sanitized_user_id = sanitize_key($user_id);
$checkout_prompt = get_transient('mxchat_checkout_prompt_' . $sanitized_user_id);
// Check if there is an active checkout prompt and if the cart has items
if ($checkout_prompt && WC()->cart->get_cart_contents_count() > 0) {
// Get and escape the checkout URL
$checkout_url = wc_get_checkout_url();
$checkout_url = esc_url_raw($checkout_url);
$response = "Great! Redirecting you to the checkout page...";
// Clear the checkout prompt transient to avoid duplicate responses
delete_transient('mxchat_checkout_prompt_' . $sanitized_user_id);
// Save and send the response with the checkout URL for redirection
$this->mxchat_save_chat_message($session_id, 'bot', $response);
wp_send_json(['message' => $response, 'redirect_url' => $checkout_url]);
wp_die();
} else {
// If no active checkout prompt or empty cart, prompt user to add items first
$response = "It seems like there is no active checkout prompt or no items in your cart. Please add a product to the cart first.";
$this->mxchat_save_chat_message($session_id, 'bot', $response);
wp_send_json(['message' => $response]);
wp_die();
}
} else {
// WooCommerce is not available, send an error message
$response = "WooCommerce is not active on this site, so checkout is not possible.";
$this->mxchat_save_chat_message($session_id, 'bot', $response);
wp_send_json(['message' => $response]);
wp_die();
}
}
//very good
public static function mxchat_extract_product_id_from_message($message) {
// Sanitize the message input
$message = sanitize_text_field($message);
// Convert Markdown links to plain URLs
$message = preg_replace('/\[(.*?)\]\((.*?)\)/', '$2', $message);
// Match all URLs in the message
preg_match_all('/https?:\/\/[^\s]+/', $message, $matches);
if (!empty($matches[0])) {
foreach ($matches[0] as $url) {
// Sanitize the URL
$url = esc_url_raw($url);
// Try to get the post ID from the URL
$post_id = url_to_postid($url);
if ($post_id) {
// Check if the post type is 'product'
if (get_post_type($post_id) === 'product') {
return $post_id;
}
}
}
}
return false;
}
//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);
}
}
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';
// 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)) {
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 = $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);
if ($similarity > $highest_similarity) {
$highest_similarity = $similarity;
$most_relevant_id = $embedding->id;
}
}
}
if ($most_relevant_id !== null) {
// Fetch content with product links
return $this->fetch_content_with_product_links($most_relevant_id);
}
return null; // Return null if no relevant content is found
}
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;
}
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.";
}
// Check the selected model
$selected_model = isset($this->options['model']) ? $this->options['model'] : 'gpt-3.5-turbo';
// 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);
} else {
return $this->mxchat_generate_response_openai($selected_model, $api_key, $conversation_history, $relevant_content);
}
}
private function mxchat_generate_response_openai($selected_model, $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' role with 'assistant' in conversation history
foreach ($conversation_history as &$message) {
if ($message['role'] === 'bot') {
$message['role'] = 'assistant';
}
}
// 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 ' . $api_key,
],
'timeout' => 60,
'redirection' => 5,
'blocking' => true,
'httpversion' => '1.0',
'sslverify' => true,
];
// Make the API request
$response = wp_remote_post('https://api.openai.com/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_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' role with 'assistant' in conversation history
foreach ($conversation_history as &$message) {
if ($message['role'] === 'bot') {
$message['role'] = 'assistant';
}
}
// 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' role with 'assistant' in conversation history
foreach ($conversation_history as &$message) {
if ($message['role'] === 'bot') {
$message['role'] = 'assistant';
}
}
// 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.3'; // Replace with your actual version
$chat_script_version = '1.3'; // 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' => $this->options['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');
}
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;
}
}
?>