options = get_option('mxchat_options'); $this->prompts_options = get_option('mxchat_prompts_options', array()); $this->chat_count = get_option('mxchat_chat_count', 0); $this->word_handler = new MXChat_Word_Handler($this->options); 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']); } 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' => esc_html__('Session ID missing.', 'mxchat')]); 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 = []; // Adjusted for code-heavy conversations $max_tokens = 120000; // Context window size $reserved_tokens = 5000; // Space for system prompts + current query $current_token_count = 0; // Allowed HTML tags for content sanitization $allowed_tags = [ 'pre' => ['class' => true], 'code' => ['class' => true], 'span' => ['class' => true], 'div' => ['class' => true], 'strong' => [], 'em' => [] ]; foreach (array_reverse($history) as $entry) { // Preserve code blocks while sanitizing other HTML $clean_content = wp_kses($entry['content'], $allowed_tags); // Detect code blocks in content $has_code = false; // Replace the HTML check with: // Allow messages that contain code blocks or are plain text if (strpos($clean_content, '
$max_tokens) {
// Try to fit partial content if it's the first entry
if (empty($formatted_history)) {
$clean_content = mb_substr($clean_content, 0, ($max_tokens - $reserved_tokens) * 4);
$token_estimate = ceil(mb_strlen($clean_content, 'UTF-8') / 4);
} else {
break;
}
}
// Add to formatted history
$formatted_history[] = [
'role' => $entry['role'],
'content' => $clean_content
];
$current_token_count += $token_estimate;
}
// Reverse back to maintain chronological order
$formatted_history = array_reverse($formatted_history);
// Add system message about code context
array_unshift($formatted_history, [
'role' => 'system',
'content' => 'Preserved code blocks are marked with [CODE BLOCK PRESERVED]. '
. 'Maintain formatting and syntax highlighting when referencing code.'
]);
return $formatted_history;
}
public function register_routes() {
//error_log(esc_html__('Registering MxChat REST routes', 'mxchat'));
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(esc_html__('MxChat REST routes registered', 'mxchat'));
}
/**
* Verify valid chat session
*/
public function verify_chat_session($request) {
$session_id = $request->get_param('session_id');
if (empty($session_id)) {
//error_log(esc_html__('Empty session ID in chat request', 'mxchat'));
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(esc_html__('Slack signing secret not configured', 'mxchat'));
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(esc_html__('Slack request timestamp too old', 'mxchat'));
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 esc_html__("event: error\ndata: ", 'mxchat') . esc_html__('Missing session_id', 'mxchat') . "\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 esc_html__("event: newMessages\ndata: ", 'mxchat') . json_encode(array_values($new_messages)) . "\n\n";
} else {
// Keep the connection alive
echo esc_html__("event: keepAlive\ndata: ", 'mxchat') . "{}\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(esc_html__('[ERROR] Invalid nonce in mxchat_handle_save_email_and_response', 'mxchat'));
wp_send_json_error(['message' => esc_html__('Invalid nonce.', 'mxchat')]);
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' => esc_html__('Session ID or email is missing.', 'mxchat')]);
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.', 'mxchat');
//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' => esc_html__('Invalid nonce', 'mxchat')]);
}
$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' => esc_html__('No session ID provided', 'mxchat')]);
}
// 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' => esc_html__('No email found', 'mxchat')]);
}
}
// 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(esc_html__("Checking rate limit for user ID: ", 'mxchat') . $user_id);
if (!$user_id) {
//error_log(esc_html__("No user ID provided, using logged-out limit: ", 'mxchat') . ($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(esc_html__("No user data or roles found, using logged-out limit: ", 'mxchat') . ($this->options['rate_limit_logged_out'] ?? '10'));
return $this->options['rate_limit_logged_out'] ?? '10';
}
//error_log(esc_html__("User roles: ", 'mxchat') . print_r($user->roles, true));
//error_log(esc_html__("Available role rate limits: ", 'mxchat') . print_r($this->options['role_rate_limits'] ?? [], true));
$max_limit = 0;
foreach ($user->roles as $role) {
//error_log(esc_html__("Checking limit for role: ", 'mxchat') . $role);
if (isset($this->options['role_rate_limits'][$role])) {
$role_limit = $this->options['role_rate_limits'][$role];
//error_log(esc_html__("Found limit for role ", 'mxchat') . $role . esc_html__(": ", 'mxchat') . $role_limit);
if ($role_limit === 'unlimited') {
//error_log(esc_html__("Returning unlimited for role: ", 'mxchat') . $role);
return 'unlimited';
}
$max_limit = max($max_limit, (int)$role_limit);
//error_log(esc_html__("Current max limit: ", 'mxchat') . $max_limit);
} else {
//error_log(esc_html__("No limit found for role: ", 'mxchat') . $role);
}
}
$final_limit = $max_limit > 0 ? (string)$max_limit : '100';
//error_log(esc_html__("Final rate limit: ", 'mxchat') . $final_limit);
return $final_limit;
}
// Add this to your plugin's main PHP file
public function mxchat_check_new_messages() {
if (!isset($_POST['session_id']) || !isset($_POST['last_seen_id'])) {
wp_send_json_error(['message' => 'Missing required parameters']);
wp_die();
}
$session_id = sanitize_text_field($_POST['session_id']);
$last_seen_id = sanitize_text_field($_POST['last_seen_id']);
// Get chat history
$history = get_option("mxchat_history_{$session_id}", []);
if (empty($history)) {
wp_send_json_success([
'hasNewMessages' => false,
'new_messages' => []
]);
wp_die();
}
// Filter new messages
$new_messages = array_filter($history, function($message) use ($last_seen_id) {
return isset($message['id']) && $message['id'] > $last_seen_id;
});
// Sort by ID to ensure proper order
usort($new_messages, function($a, $b) {
return $a['id'] <=> $b['id'];
});
wp_send_json_success([
'hasNewMessages' => !empty($new_messages),
'new_messages' => array_values($new_messages),
'latestMessageId' => end($new_messages)['id'] ?? $last_seen_id
]);
wp_die();
}
public function mxchat_handle_chat_request() {
global $wpdb;
// Check if MX Chat Moderation is active
if (class_exists('MX_Chat_Moderation')) {
// Get user email and IP
$user_email = '';
$user_ip = $_SERVER['REMOTE_ADDR'];
// If user is logged in, get their email
if (is_user_logged_in()) {
$current_user = wp_get_current_user();
$user_email = $current_user->user_email;
}
// Create ban handler instance
$ban_handler = new MX_Chat_Ban_Handler();
// Check if user is banned by IP
if ($ban_handler->check_ban($user_ip, 'ip')) {
wp_send_json([
'success' => false,
'message' => esc_html__('Access denied. Your IP address has been banned.', 'mxchat'),
'status' => 'banned'
]);
wp_die();
}
// If user is logged in, also check email
if (!empty($user_email) && $ban_handler->check_ban($user_email, 'email')) {
wp_send_json([
'success' => false,
'message' => esc_html__('Access denied. Your email address has been banned.', 'mxchat'),
'status' => 'banned'
]);
wp_die();
}
}
// 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']
: esc_html__('Rate limit exceeded. Please try again later.', 'mxchat');
// 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(esc_html__('Session ID is missing.', 'mxchat'));
wp_die();
}
// Validate and sanitize the incoming message
if (empty($_POST['message'])) {
//error_log("Error: No message received.");
wp_send_json_error(esc_html__('No message received.', 'mxchat'));
wp_die();
}
// Modify the message sanitization to preserve PHP tags in code blocks
$allowed_tags = [
'pre' => [],
'code' => ['class' => true],
'span' => ['class' => true],
'div' => ['class' => true],
];
// First preserve code blocks
$message = preg_replace_callback('/.*?<\/code><\/pre>/s', function($matches) {
return htmlspecialchars_decode($matches[0]);
}, $_POST['message']);
// Then apply sanitization
$message = wp_kses($message, $allowed_tags);
// Decode code blocks
$message = preg_replace_callback('/(<pre><code.*?>.*?<\/code><\/pre>)/s', function($matches) {
return htmlspecialchars_decode($matches[1]);
}, $message);
$message = trim($message);
// Preserve code blocks from markdown conversion
$message = preg_replace('/```(\w+)?\s*([\s\S]+?)```/s', '$2
', $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'] ??
esc_html__('Thank you! Your coupon is on the way!', 'mxchat');
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', esc_html__('Switched to AI chat mode', 'mxchat'));
$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' => esc_html__('Message sent to live agent.', 'mxchat')
]);
} catch (\Exception $e) {
//error_log("Error sending message to agent: " . $e->getMessage());
wp_send_json_error(esc_html__('Failed to send message to agent', 'mxchat'));
}
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'] ??
esc_html__("The provided PDF exceeds the maximum allowed limit of %d pages. Please provide a smaller document.", 'mxchat'),
$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'] ??
esc_html__("I've processed the new PDF '{$pdf_filename}'. What questions do you have about it?", 'mxchat');
// 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'] ??
esc_html__("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat');
$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(esc_html__('Error processing your message.', 'mxchat'));
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();
}
// 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;
$chat_mode = get_option("mxchat_mode_{$session_id}", 'ai');
//error_log('🔍 MXCHAT DEBUG: Intent Check Started ==================');
//error_log("🔍 MXCHAT DEBUG: Message: '$message'");
//error_log("🔍 MXCHAT DEBUG: Chat Mode: $chat_mode");
// Generate the user embedding
//error_log('🔄 MXCHAT DEBUG: Generating user embedding');
$user_embedding = $this->mxchat_generate_embedding($message, $this->options['api_key']);
if (!is_array($user_embedding)) {
//error_log('❌ MXCHAT DEBUG: Failed to generate user embedding');
return false;
}
//error_log('✅ MXCHAT DEBUG: User embedding generated successfully');
// Fetch intents from the database
$table_name = $wpdb->prefix . 'mxchat_intents';
if ($chat_mode === 'agent') {
//error_log('🔍 MXCHAT DEBUG: Agent mode - fetching only chatbot switch intent');
$query = $wpdb->prepare(
"SELECT * FROM $table_name WHERE callback_function = %s",
'mxchat_handle_switch_to_chatbot_intent'
);
$intents = $wpdb->get_results($query);
} else {
//error_log('🔍 MXCHAT DEBUG: AI mode - fetching all intents');
$intents = $wpdb->get_results("SELECT * FROM $table_name");
}
//error_log('🔍 MXCHAT DEBUG: Found ' . count($intents) . ' intents to check');
if (empty($intents)) {
//error_log('❌ MXCHAT DEBUG: No intents found in database');
return false;
}
$highest_similarity = -INF;
$matched_intent = null;
//error_log('📊 MXCHAT DEBUG: Intent Similarity Scores ==================');
foreach ($intents as $intent) {
//error_log("🔄 MXCHAT DEBUG: Checking intent: '{$intent->intent_label}' (callback: {$intent->callback_function})");
$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("❌ MXCHAT DEBUG: 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;
if ($similarity >= $intent_threshold && $similarity > $highest_similarity) {
$highest_similarity = $similarity;
$matched_intent = $intent;
//error_log("✅ MXCHAT DEBUG: New best match: '{$intent->intent_label}' with similarity {$similarity}");
}
}
//error_log('📊 MXCHAT DEBUG: End Intent Scores ==================');
if ($matched_intent) {
//error_log("🎯 MXCHAT DEBUG: Final Intent Match: '{$matched_intent->intent_label}'");
//error_log("🔄 MXCHAT DEBUG: Invoking callback: {$matched_intent->callback_function}");
// If the callback is a method on this instance (core callback), call it directly
if (method_exists($this, $matched_intent->callback_function)) {
//error_log('🔍 MXCHAT DEBUG: Using direct method call for core callback');
$callback_result = call_user_func(
[$this, $matched_intent->callback_function],
$message,
$user_id,
$session_id,
$matched_intent,
$user_context // Add user context
);
} else {
//error_log('🔍 MXCHAT DEBUG: Using apply_filters for add-on callback');
// Otherwise, use apply_filters for add-on callbacks
$callback_result = apply_filters(
$matched_intent->callback_function,
false, // default return value
$message,
$user_id,
$session_id,
$matched_intent
);
}
//error_log('🔍 MXCHAT DEBUG: Callback result type: ' . gettype($callback_result));
if ($callback_result !== false) {
//error_log('✅ MXCHAT DEBUG: Intent handled successfully');
$this->fallbackResponse = $callback_result;
return true;
}
//error_log('❌ MXCHAT DEBUG: Callback returned false');
} else {
//error_log('❌ MXCHAT DEBUG: No matching intent found');
}
//error_log('🔍 MXCHAT DEBUG: Intent Check Completed ==================');
return false;
}
// 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);
}
//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'] ?? esc_html__("Would you like to join our mailing list? Please provide your email below.", 'mxchat'));
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 = esc_html__('Create an image of ', 'mxchat') . 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 = '
';
$response_text = esc_html__('Here is the image I generated:', 'mxchat');
} else {
$response_text = esc_html__("I'm sorry, but I couldn't generate an image based on your request.", 'mxchat');
$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' => esc_html__('Error generating image: ', 'mxchat') . $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' => esc_html__('Failed to generate image.', 'mxchat')];
}
}
/**
* 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();
?>
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 = '';
foreach ($body['results'] as $image) {
$image_url = isset($image['url']) ? esc_url($image['url']) : '';
$thumbnail_url = isset($image['thumbnail']['src']) ? esc_url($image['thumbnail']['src']) : '';
$title = isset($image['title']) ? esc_html($image['title']) : esc_html__('Image', 'mxchat');
if ($image_url && $thumbnail_url) {
$html_output .= '';
$html_output .= '' . $title . '';
$html_output .= '';
$html_output .= '
';
$html_output .= '';
}
}
$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 = esc_html__("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.", 'mxchat');
// 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);
}
}
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'] = esc_html__("I couldn't find any relevant products based on your query. Could you please be more specific about the product you're looking for?", 'mxchat');
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'] = esc_html__("I couldn't find any relevant products based on your query. Try to be more specific", 'mxchat');
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 = esc_html__("INTENT CONTEXT:\n", 'mxchat');
switch ($context['intent']) {
case 'add_to_cart':
if ($context['status'] === 'success') {
$context_string .= esc_html__("Action: Successfully added product to cart\n", 'mxchat');
$context_string .= sprintf(esc_html__("Product: %s\n", 'mxchat'), $context['product']['name']);
$context_string .= esc_html__("Available actions: ", 'mxchat') . implode(', ', $context['available_actions']) . "\n";
$context_string .= sprintf(esc_html__("Cart URL: %s\n", 'mxchat'), $context['cart_url']);
$context_string .= esc_html__("\nPlease inform the user of the successful addition and their available options.", 'mxchat');
} else {
$context_string .= esc_html__("Action: Failed to add product to cart\n", 'mxchat');
$context_string .= sprintf(esc_html__("Reason: %s\n", 'mxchat'), $context['reason']);
switch ($context['reason']) {
case 'woocommerce_not_available':
$context_string .= esc_html__("\nPlease inform the user that shopping features are not available.", 'mxchat');
break;
case 'no_product_context':
$context_string .= esc_html__("\nPlease ask the user to specify which product they want to add.", 'mxchat');
break;
case 'product_not_found':
$context_string .= esc_html__("\nPlease inform the user that the product couldn't be found and ask them to try again.", 'mxchat');
break;
case 'add_to_cart_failed':
$context_string .= esc_html__("\nPlease apologize to the user and suggest they try again or ask for assistance.", 'mxchat');
break;
}
}
break;
}
return $context_string;
}
//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(esc_html__('Loops API key or mailing list ID is missing.', 'mxchat'));
return;
}
$data = array(
'email' => $email,
'subscribed' => true,
'source' => __('MxChat AI Chatbot', 'mxchat'),
'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(esc_html__('Error adding email to Loops: ', 'mxchat') . $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(esc_html__('Loops API responded with code ', 'mxchat') . $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.", 'mxchat');
$success_text = $this->options['pdf_intent_success_text'] ?? __("I've processed the PDF. What questions do you have about it?", 'mxchat');
$error_text = $this->options['pdf_intent_error_text'] ?? __("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat');
// 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(esc_html__("Failed to download PDF. Error: ", 'mxchat') . 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(esc_html__("Invalid MIME type detected for PDF: ", 'mxchat') . $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(esc_html__("PDF exceeds the maximum allowed pages: ", 'mxchat') . count($pages));
if (filter_var($pdf_source, FILTER_VALIDATE_URL)) {
unlink($temp_file);
}
return esc_html__('too_many_pages', 'mxchat');
}
$embeddings = [];
foreach ($pages as $page_number => $page) {
$text = $page->getText();
// Ensure text is non-empty before generating embeddings
if (empty(trim($text))) {
//error_log(esc_html__("Skipping empty page: ", 'mxchat') . ($page_number + 1));
continue;
}
$embedding = $this->mxchat_generate_embedding(
esc_html__("Page ", 'mxchat') . ($page_number + 1) . ": " . $text,
$this->options['api_key']
);
if ($embedding) {
$embeddings[] = [
'page_number' => $page_number + 1,
'embedding' => $embedding,
'text' => $text,
];
} else {
//error_log(esc_html__("Failed to generate embedding for page ", 'mxchat') . ($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(esc_html__("Error parsing or processing PDF: ", 'mxchat') . $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(esc_html__("find_relevant_pdf_pages called.", 'mxchat'));
$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(esc_html__('Missing required parameters.', 'mxchat'));
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(esc_html__('Invalid file type. Only PDF files are allowed.', 'mxchat'));
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(esc_html__('Failed to upload file.', 'mxchat'));
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'] ??
esc_html__("The provided PDF exceeds the maximum allowed limit of %d pages. Please provide a smaller document.", 'mxchat'),
$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'] ??
esc_html__('The uploaded PDF appears to be empty or contains unsupported content.', 'mxchat');
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'] ??
esc_html__("I've processed the PDF. What questions do you have about it?", 'mxchat');
wp_send_json_success([
'message' => $success_message,
'filename' => $original_filename
]);
return;
}
unlink($pdf_path);
$error_message = $this->options['pdf_intent_error_text'] ??
esc_html__('Sorry, I couldn\'t process the PDF. Please ensure it\'s a valid file.', 'mxchat');
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(esc_html__('Session ID missing.', 'mxchat'));
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' => esc_html__('PDF removed successfully.', 'mxchat')
]);
wp_die();
}
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(esc_html__('Fetch new messages error: Session ID missing.', 'mxchat'));
wp_send_json_error(['message' => esc_html__('Session ID missing.', 'mxchat')]);
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(esc_html__("New agent messages fetched for session $session_id. Last seen ID: $last_seen_id", 'mxchat'));
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') {
$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'] ?? '';
if (empty($slack_webhook_url)) {
return false;
}
// Get recent chat history (last 5 messages)
$history = get_option("mxchat_history_{$session_id}", []);
$recent_history = array_slice($history, -5); // Get last 5 messages
// Format conversation history
$conversation_context = "";
if (!empty($recent_history)) {
$conversation_context = "*Recent Conversation:*\n";
foreach ($recent_history as $hist_message) {
$role_display = $hist_message['role'] === 'user' ? 'User' : 'AI';
$conversation_context .= ">{$role_display}: {$hist_message['content']}\n";
}
$conversation_context .= "\n";
}
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' => sprintf('*User ID:*\n`%s`', $user_id)
],
[
'type' => 'mrkdwn',
'text' => sprintf('*Session ID:*\n`%s`', $session_id)
]
]
]
]
];
// Add conversation history if exists
if (!empty($conversation_context)) {
$webhook_data['blocks'][] = [
'type' => 'section',
'text' => [
'type' => 'mrkdwn',
'text' => $conversation_context
]
];
}
// Add the current message
$webhook_data['blocks'][] = [
'type' => 'section',
'text' => [
'type' => 'mrkdwn',
'text' => sprintf('*Current Message:*\n%s', $message)
]
];
// Add the reply button
$webhook_data['blocks'][] = [
'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)) {
return false;
}
$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(esc_html__('Slack Webhook URL is not configured.', 'mxchat'));
return false;
}
$webhook_data = [
'blocks' => [
[
'type' => 'header',
'text' => [
'type' => 'plain_text',
'text' => esc_html__('📩 New Chat Message', 'mxchat'),
'emoji' => true
]
],
[
'type' => 'section',
'fields' => [
[
'type' => 'mrkdwn',
'text' => sprintf(esc_html__('*User ID:*\n`%s`', 'mxchat'), $user_id)
],
[
'type' => 'mrkdwn',
'text' => sprintf(esc_html__('*Session ID:*\n`%s`', 'mxchat'), $session_id)
]
]
],
[
'type' => 'section',
'text' => [
'type' => 'mrkdwn',
'text' => sprintf(esc_html__('*Message:*\n%s', 'mxchat'), $message)
]
],
[
'type' => 'actions',
'elements' => [
[
'type' => 'button',
'text' => [
'type' => 'plain_text',
'text' => esc_html__('✍️ Reply', 'mxchat'),
'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(esc_html__('Error sending message to Slack: ', 'mxchat') . $response->get_error_message());
return false;
}
//error_log(esc_html__('Message sent to Slack successfully.', 'mxchat'));
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' => esc_html__('Bot token not configured', 'mxchat')], 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', 'mxchat')
],
'submit' => [
'type' => 'plain_text',
'text' => __('Send', 'mxchat')
],
'close' => [
'type' => 'plain_text',
'text' => __('Cancel', 'mxchat')
],
'blocks' => [
[
'type' => 'input',
'block_id' => 'reply_block',
'label' => [
'type' => 'plain_text',
'text' => sprintf(__('Reply to session: %s', 'mxchat'), $session_id)
],
'element' => [
'type' => 'plain_text_input',
'action_id' => 'message',
'multiline' => true,
'placeholder' => [
'type' => 'plain_text',
'text' => __('Type your message here...', 'mxchat')
]
]
]
],
'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
// 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 (keep the message_id but don't include in response)
$this->mxchat_save_chat_message($session_id, 'agent', $message);
// Keep the original response format for Slack
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(esc_html__('Agent response error: No command text received', 'mxchat'));
return new WP_REST_Response([
'error' => esc_html__('Command text is required. Format: /reply session_id message', 'mxchat')
], 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' => esc_html__('Invalid format. Use: /reply session_id message', 'mxchat')
], 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' => esc_html__('Failed to save message', 'mxchat')
], 500);
}
// Return success response in Slack's expected format
return new WP_REST_Response([
'response_type' => 'in_channel',
'text' => esc_html__("Message sent successfully to session $session_id", 'mxchat')
], 200);
}
public function mxchat_handle_switch_to_chatbot_intent($message, $user_id, $session_id) {
//error_log(esc_html__("Switching back to chatbot mode via intent.", 'mxchat'));
// 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'] = esc_html__('You are now chatting with the AI chatbot.', 'mxchat');
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) {
//error_log('MXChat Vector Search: Starting content search...');
// Retrieve the add-on settings from the database.
$addon_options = get_option('mxchat_pinecone_addon_options', array());
// Determine whether Pinecone is enabled.
// We expect the sanitized setting to be a string '1' if enabled, otherwise '0'.
$use_pinecone = (isset($addon_options['mxchat_use_pinecone']) && $addon_options['mxchat_use_pinecone'] === '1') ? 1 : 0;
//error_log('Pinecone enabled flag: ' . $use_pinecone);
if ($use_pinecone === 1) {
//error_log('MXChat Vector Search: Using Pinecone database');
return $this->find_relevant_content_pinecone($user_embedding);
} else {
//error_log('MXChat Vector Search: Using WordPress database');
return $this->find_relevant_content_wordpress($user_embedding);
}
}
private function find_relevant_content_wordpress($user_embedding) {
global $wpdb;
$system_prompt_table = $wpdb->prefix . 'mxchat_system_prompt_content';
$cache_key = 'mxchat_system_prompt_embeddings';
$batch_size = 500;
// Retrieve embeddings from cache or database
$embeddings = wp_cache_get($cache_key, 'mxchat_system_prompts');
if ($embeddings === false) {
$embeddings = [];
$offset = 0;
// Load in batches and build cache
do {
$query = $wpdb->prepare(
"SELECT id, embedding_vector
FROM {$system_prompt_table}
LIMIT %d OFFSET %d",
$batch_size,
$offset
);
$batch = $wpdb->get_results($query);
if (empty($batch)) {
break;
}
$embeddings = array_merge($embeddings, $batch);
$offset += $batch_size;
// Free memory
unset($batch);
} while (true);
if (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
];
}
// Free memory
unset($database_embedding);
}
// 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);
}
/**
* Find relevant content in Pinecone vector database
*/
private function find_relevant_content_pinecone($user_embedding) {
$options = get_option('mxchat_pinecone_addon_options', array());
$api_key = $options['mxchat_pinecone_api_key'] ?? '';
$host = $options['mxchat_pinecone_host'] ?? '';
if (empty($host) || empty($api_key)) {
//error_log('Pinecone credentials not properly configured');
return '';
}
// Get similarity threshold from WordPress settings
$similarity_threshold = ((int) get_option('mxchat_similarity_threshold', 80)) / 100;
// Prepare the query request for Pinecone
$api_endpoint = "https://{$host}/query";
$request_body = array(
'vector' => $user_embedding,
'topK' => 5,
'includeMetadata' => true,
'includeValues' => true
);
$response = wp_remote_post($api_endpoint, array(
'headers' => array(
'Api-Key' => $api_key,
'accept' => 'application/json',
'content-type' => 'application/json'
),
'body' => wp_json_encode($request_body),
'timeout' => 30
));
if (is_wp_error($response)) {
//error_log('Pinecone query error: ' . $response->get_error_message());
return '';
}
$response_code = wp_remote_retrieve_response_code($response);
if ($response_code !== 200) {
//error_log('Pinecone API error: ' . wp_remote_retrieve_body($response));
return '';
}
$results = json_decode(wp_remote_retrieve_body($response), true);
if (empty($results['matches'])) {
return '';
}
// Initialize the final content
$content = '';
// Process each match
foreach ($results['matches'] as $match) {
// Skip if similarity is below threshold
if ($match['score'] < $similarity_threshold) {
continue;
}
if (!empty($match['metadata']['text']) && !empty($match['metadata']['source_url'])) {
// Add content with citation
$content .= $match['metadata']['text'] . "\n";
$content .= "Source: " . $match['metadata']['source_url'] . "\n\n";
}
}
return trim($content);
}
private function mxchat_find_relevant_products($user_embedding) {
//error_log('MXChat Vector Search: Starting product search...');
// Retrieve the add-on settings from the database
$addon_options = get_option('mxchat_pinecone_addon_options', array());
// Determine whether Pinecone is enabled
$use_pinecone = (isset($addon_options['mxchat_use_pinecone']) && $addon_options['mxchat_use_pinecone'] === '1') ? 1 : 0;
//error_log('Pinecone enabled flag: ' . $use_pinecone);
if ($use_pinecone === 1) {
//error_log('MXChat Vector Search: Using Pinecone database for products');
return $this->find_relevant_products_pinecone($user_embedding);
} else {
//error_log('MXChat Vector Search: Using WordPress database for products');
return $this->find_relevant_products_wordpress($user_embedding);
}
}
private function find_relevant_products_wordpress($user_embedding) {
global $wpdb;
$system_prompt_table = $wpdb->prefix . 'mxchat_system_prompt_content';
$cache_key = 'mxchat_system_prompt_embeddings';
$batch_size = 500;
// Original WordPress database search logic
// [Previous implementation remains the same]
$embeddings = wp_cache_get($cache_key, 'mxchat_system_prompts');
if ($embeddings === false) {
$embeddings = [];
$offset = 0;
do {
$query = $wpdb->prepare(
"SELECT id, embedding_vector
FROM {$system_prompt_table}
LIMIT %d OFFSET %d",
$batch_size,
$offset
);
$batch = $wpdb->get_results($query);
if (empty($batch)) {
break;
}
$embeddings = array_merge($embeddings, $batch);
$offset += $batch_size;
unset($batch);
} while (true);
if (empty($embeddings)) {
return '';
}
wp_cache_set($cache_key, $embeddings, 'mxchat_system_prompts', 3600);
}
$relevant_results = [];
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
];
}
unset($database_embedding);
}
// Use fixed threshold for products
$similarity_threshold = 0.85;
$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'];
});
$top_results = array_slice($relevant_results, 0, 5);
$content = '';
foreach ($top_results as $result) {
$chunk_content = $this->fetch_content_with_product_links($result['id']);
$content .= $chunk_content . "\n\n";
}
return trim($content);
}
// Modified search function with correct filter syntax
private function find_relevant_products_pinecone($user_embedding) {
//error_log('Starting Pinecone product search...');
$options = get_option('mxchat_pinecone_addon_options', array());
$api_key = $options['mxchat_pinecone_api_key'] ?? '';
$host = $options['mxchat_pinecone_host'] ?? '';
if (empty($host) || empty($api_key)) {
//error_log('Pinecone credentials not properly configured for product search');
return '';
}
$similarity_threshold = 0.85;
$api_endpoint = "https://{$host}/query";
$request_body = array(
'vector' => $user_embedding,
'topK' => 5,
'includeMetadata' => true,
'includeValues' => true,
'filter' => array(
'type' => 'product'
)
);
//error_log('Sending request to Pinecone with body: ' . wp_json_encode($request_body));
$response = wp_remote_post($api_endpoint, array(
'headers' => array(
'Api-Key' => $api_key,
'accept' => 'application/json',
'content-type' => 'application/json'
),
'body' => wp_json_encode($request_body),
'timeout' => 30
));
if (is_wp_error($response)) {
//error_log('Pinecone product query error: ' . $response->get_error_message());
return '';
}
$response_code = wp_remote_retrieve_response_code($response);
//error_log('Pinecone response code: ' . $response_code);
if ($response_code !== 200) {
//error_log('Pinecone API error during product search: ' . wp_remote_retrieve_body($response));
return '';
}
$results = json_decode(wp_remote_retrieve_body($response), true);
//error_log('Pinecone raw response: ' . wp_remote_retrieve_body($response));
if (empty($results['matches'])) {
//error_log('No matches found in Pinecone response');
return '';
}
$content = '';
foreach ($results['matches'] as $match) {
if ($match['score'] < $similarity_threshold) {
//error_log("Match below threshold: " . $match['score']);
continue;
}
if (!empty($match['metadata']['text'])) {
$content .= $match['metadata']['text'];
if (!empty($match['metadata']['source_url'])) {
$content .= "\n\nFor more details, check out this product: " . esc_url($match['metadata']['source_url']);
}
$content .= "\n\n";
}
}
return trim($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, $deepseek_api_key, $conversation_history) {
try {
if (!$relevant_content) {
return esc_html__("I'm sorry, I couldn't find relevant information on that topic.", 'mxchat');
}
// Ensure conversation_history is an array
if (!is_array($conversation_history)) {
$conversation_history = array();
}
// Get selected model with default fallback
$selected_model = isset($this->options['model']) ? $this->options['model'] : 'gpt-4o';
// Extract model prefix to determine the provider
$model_parts = explode('-', $selected_model);
$provider = strtolower($model_parts[0]);
// Handle model selection based on provider prefix
switch ($provider) {
case 'claude':
if (empty($claude_api_key)) {
throw new Exception(esc_html__('Claude API key is not configured', 'mxchat'));
}
return $this->mxchat_generate_response_claude(
$selected_model,
$claude_api_key,
$conversation_history,
$relevant_content
);
case 'grok':
if (empty($xai_api_key)) {
throw new Exception(esc_html__('X.AI API key is not configured', 'mxchat'));
}
return $this->mxchat_generate_response_xai(
$selected_model,
$xai_api_key,
$conversation_history,
$relevant_content
);
case 'deepseek':
if (empty($deepseek_api_key)) {
throw new Exception(esc_html__('DeepSeek API key is not configured', 'mxchat'));
}
return $this->mxchat_generate_response_deepseek(
$selected_model,
$deepseek_api_key,
$conversation_history,
$relevant_content
);
case 'gpt':
if (empty($api_key)) {
throw new Exception(esc_html__('OpenAI API key is not configured', 'mxchat'));
}
return $this->mxchat_generate_response_openai(
$selected_model,
$api_key,
$conversation_history,
$relevant_content
);
default:
// Default to OpenAI for custom models or unrecognized prefixes
if (empty($api_key)) {
throw new Exception(esc_html__('OpenAI API key is not configured', 'mxchat'));
}
return $this->mxchat_generate_response_openai(
$selected_model,
$api_key,
$conversation_history,
$relevant_content
);
}
} catch (Exception $e) {
//error_log('MXChat Error: ' . $e->getMessage());
return sprintf(
esc_html__('An error occurred: %s', 'mxchat'),
esc_html($e->getMessage())
);
}
}
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
$system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : '';
// Clean and validate conversation history
foreach ($conversation_history as &$message) {
// Convert bot and agent roles to assistant
if ($message['role'] === 'bot' || $message['role'] === 'agent') {
$message['role'] = 'assistant';
}
// Remove unsupported roles - Claude only supports 'assistant' and 'user'
if (!in_array($message['role'], ['assistant', 'user'])) {
$message['role'] = 'user';
}
// Ensure content field exists
if (!isset($message['content']) || empty($message['content'])) {
$message['content'] = '';
}
// Remove any unsupported fields
$message = array_intersect_key($message, array_flip(['role', 'content']));
}
// Add relevant content as the latest user message
$conversation_history[] = [
'role' => 'user',
'content' => $relevant_content
];
// Build request body
$body = json_encode([
'model' => $selected_model,
'max_tokens' => 1000,
'temperature' => 0.8,
'messages' => $conversation_history,
'system' => $system_prompt_instructions
]);
// Set up API request
$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 API request
$response = wp_remote_post('https://api.anthropic.com/v1/messages', $args);
// Check for WordPress errors
if (is_wp_error($response)) {
//error_log("Claude API request error: " . $response->get_error_message());
return "Sorry, there was an error connecting to the API.";
}
// Check HTTP response code
$http_code = wp_remote_retrieve_response_code($response);
if ($http_code !== 200) {
$error_body = wp_remote_retrieve_body($response);
//error_log("Claude API HTTP error: " . $http_code . " - " . $error_body);
// Try to extract error message from response
$error_data = json_decode($error_body, true);
$error_message = isset($error_data['error']['message']) ?
$error_data['error']['message'] :
"HTTP error " . $http_code;
return "Sorry, the API returned an error: " . $error_message;
}
// Parse response
$response_body = json_decode(wp_remote_retrieve_body($response), true);
// Check for JSON decode errors
if (json_last_error() !== JSON_ERROR_NONE) {
//error_log("Claude API JSON decode error: " . json_last_error_msg());
return "Sorry, there was an error processing the API response.";
}
// Extract and validate response content
if (isset($response_body['content']) &&
is_array($response_body['content']) &&
!empty($response_body['content']) &&
isset($response_body['content'][0]['text'])) {
return trim($response_body['content'][0]['text']);
}
// Log unexpected response format
//error_log("Claude API unexpected response format: " . print_r($response_body, true));
return "Sorry, I received an unexpected response format from the API.";
}
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 = '2.0.5'; // Replace with your actual version
$chat_script_version = '2.0.5'; // 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');
$prompts_options = get_option('mxchat_prompts_options', array());
// 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',
'use_pinecone' => $prompts_options['mxchat_use_pinecone'] ?? '0',
'pinecone_enabled' => isset($prompts_options['mxchat_use_pinecone']) && $prompts_options['mxchat_use_pinecone'] === '1'
);
// 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;
}
}
?>