';
$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 .= " \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)) . "