';
$this->fallbackResponse = [
'text' => "",
'html' => $html_output,
];
// Save response in chat history
$this->mxchat_save_chat_message($session_id, 'bot', $html_output);
} else {
/*
if (defined('WP_DEBUG') && WP_DEBUG) {
error_log("Brave Image API response did not contain expected data structure or was empty: " . print_r($body, true));
}
*/
$this->fallbackResponse = [
'text' => __("I'm sorry, I couldn't retrieve any images based on your request.", 'mxchat'),
'html' => "",
];
}
}
public function mxchat_interpret_search_query($user_query) {
$system_prompt = "Interpret the user's request to provide only the essential keywords or phrases for image searching. Remove conversational language, politeness, or extra context. Return a concise search query that doesn't lose any of the original meaning.";
// Retrieve OpenAI API key using 'api_key' as the option key
$api_key = isset($this->options['api_key']) ? sanitize_text_field($this->options['api_key']) : sanitize_text_field(get_option('mxchat_options')['api_key']);
/*
// Log the API key check, without exposing the key
if (defined('WP_DEBUG') && WP_DEBUG) {
error_log("Retrieved OpenAI API Key: " . ($api_key ? "Present" : "Missing"));
}
*/
if (empty($api_key)) {
//error_log("OpenAI API key is missing.");
return sanitize_text_field($user_query); // Default to the original query if API key is missing
}
$url = 'https://api.openai.com/v1/chat/completions';
$args = [
'headers' => [
'Authorization' => 'Bearer ' . $api_key,
'Content-Type' => 'application/json',
],
'body' => wp_json_encode([
'model' => 'gpt-3.5-turbo',
'messages' => [
['role' => 'system', 'content' => $system_prompt],
['role' => 'user', 'content' => sanitize_text_field($user_query)],
],
'temperature' => 0.2,
'max_tokens' => 20,
]),
'method' => 'POST',
];
$response = wp_remote_post($url, $args);
if (is_wp_error($response)) {
//error_log("OpenAI request failed: " . $response->get_error_message());
return sanitize_text_field($user_query); // Fallback to the original query if there's an error
}
$body = json_decode(wp_remote_retrieve_body($response), true);
// Check for a valid response and sanitize output
if (isset($body['choices'][0]['message']['content'])) {
$interpreted_query = sanitize_text_field(trim($body['choices'][0]['message']['content']));
/*
// Log the interpreted query for debugging
if (defined('WP_DEBUG') && WP_DEBUG) {
error_log("Interpreted search query: " . $interpreted_query);
}
*/
return $interpreted_query;
} else {
//error_log("Unexpected API response format: " . print_r($body, true));
return sanitize_text_field($user_query);
}
}
//very good
public function mxchat_handle_add_to_cart_intent($message, $user_id, $session_id) {
// Check if WooCommerce is active before proceeding
if (!class_exists('WooCommerce')) {
// error_log("WooCommerce is not active. Add-to-cart intent cannot be processed.");
return false;
}
// Sanitize user ID for transient key
$sanitized_user_id = sanitize_key($user_id);
// Retrieve the last discussed product ID from the transient
$last_product_id = get_transient('mxchat_last_discussed_product_' . $sanitized_user_id);
if ($last_product_id) {
// Attempt to add the product to the WooCommerce cart
$added = WC()->cart->add_to_cart($last_product_id);
$product = wc_get_product($last_product_id);
if ($added && $product) {
// Escape product name for safety
$product_name = esc_html($product->get_name());
// Success response if the product is added to the cart
$response = "The product '{$product_name}' has been added to your cart. To proceed to checkout, please type 'checkout'.";
set_transient('mxchat_checkout_prompt_' . $sanitized_user_id, true, 5 * MINUTE_IN_SECONDS);
// Save the response in chat history and send a JSON response
$this->mxchat_save_chat_message($session_id, 'bot', $response);
wp_send_json(['message' => $response]);
wp_die();
} else {
// Failure response if the product could not be added
$response = "Sorry, I couldn't add the product to your cart. Please try again.";
$this->mxchat_save_chat_message($session_id, 'bot', $response);
wp_send_json(['message' => $response]);
wp_die();
}
} else {
// Prompt the user to specify the product name if no product was found
$response = "I couldn't find the product to add. Please mention the product name again.";
$this->mxchat_save_chat_message($session_id, 'bot', $response);
wp_send_json(['message' => $response]);
wp_die();
}
}
public function mxchat_handle_checkout_intent($message, $user_id, $session_id) {
// Ensure WooCommerce is available and perform checkout actions
if (class_exists('WooCommerce')) {
// Sanitize user ID for transient key
$sanitized_user_id = sanitize_key($user_id);
$checkout_prompt = get_transient('mxchat_checkout_prompt_' . $sanitized_user_id);
// Check if there is an active checkout prompt and if the cart has items
if ($checkout_prompt && WC()->cart->get_cart_contents_count() > 0) {
// Get and escape the checkout URL
$checkout_url = wc_get_checkout_url();
$checkout_url = esc_url_raw($checkout_url);
$response = "Great! Redirecting you to the checkout page...";
// Clear the checkout prompt transient to avoid duplicate responses
delete_transient('mxchat_checkout_prompt_' . $sanitized_user_id);
// Save and send the response with the checkout URL for redirection
$this->mxchat_save_chat_message($session_id, 'bot', $response);
wp_send_json(['message' => $response, 'redirect_url' => $checkout_url]);
wp_die();
} else {
// If no active checkout prompt or empty cart, prompt user to add items first
$response = "It seems like there is no active checkout prompt or no items in your cart. Please add a product to the cart first.";
$this->mxchat_save_chat_message($session_id, 'bot', $response);
wp_send_json(['message' => $response]);
wp_die();
}
} else {
// WooCommerce is not available, send an error message
$response = "WooCommerce is not active on this site, so checkout is not possible.";
$this->mxchat_save_chat_message($session_id, 'bot', $response);
wp_send_json(['message' => $response]);
wp_die();
}
}
//very good
private function add_email_to_loops($email) {
// Sanitize the email
$email = sanitize_email($email);
// Retrieve and sanitize options
$api_key = isset($this->options['loops_api_key']) ? sanitize_text_field($this->options['loops_api_key']) : '';
$mailing_list_id = isset($this->options['loops_mailing_list']) ? sanitize_text_field($this->options['loops_mailing_list']) : '';
// Check for missing API key or mailing list ID
if (empty($api_key) || empty($mailing_list_id)) {
//error_log('Loops API key or mailing list ID is missing.');
return;
}
$data = array(
'email' => $email,
'subscribed' => true,
'source' => 'MxChat AI Chatbot',
'mailingLists' => array($mailing_list_id => true),
);
$url = 'https://app.loops.so/api/v1/contacts/create';
$args = array(
'body' => wp_json_encode($data),
'headers' => array(
'Authorization' => 'Bearer ' . $api_key,
'Content-Type' => 'application/json',
),
'method' => 'POST',
'timeout' => 45,
);
$response = wp_remote_post($url, $args);
// Handle errors in the API request
if (is_wp_error($response)) {
//error_log('Error adding email to Loops: ' . $response->get_error_message());
return;
}
// Check for non-200 HTTP responses
$response_code = wp_remote_retrieve_response_code($response);
if ($response_code != 200) {
$response_body = wp_remote_retrieve_body($response);
//error_log('Loops API responded with code ' . $response_code . ': ' . $response_body);
}
}
public function mxchat_handle_pdf_discussion($message, $user_id, $session_id) {
// Get the maximum number of pages allowed from admin settings
$max_pages = isset($this->options['pdf_max_pages']) ? intval($this->options['pdf_max_pages']) : 69; // Default to 69
// Retrieve options for dynamic texts
$trigger_text = $this->options['pdf_intent_trigger_text'] ?? "Please provide the URL to the PDF you'd like to discuss.";
$success_text = $this->options['pdf_intent_success_text'] ?? "I've processed the PDF. What questions do you have about it?";
$error_text = $this->options['pdf_intent_error_text'] ?? "Sorry, I couldn't process the PDF. Please ensure it's a valid file.";
// Check if we're waiting for a URL
$waiting_for_url = get_transient('mxchat_waiting_for_pdf_url_' . $session_id);
// Check if we already have a PDF URL stored for this session
$pdf_url = get_transient('mxchat_pdf_url_' . $session_id);
// Always process a new URL if detected in the current message
if (preg_match('/https?:\/\/[^\s"]+/i', $message, $matches)) {
$new_pdf_url = $matches[0];
// Validate HTTPS
if (!wp_http_validate_url($new_pdf_url) || parse_url($new_pdf_url, PHP_URL_SCHEME) !== 'https') {
//error_log("Invalid PDF URL: $new_pdf_url");
$this->fallbackResponse['text'] = $trigger_text;
return;
}
// Reset previous transients if a new URL is provided
if ($pdf_url !== $new_pdf_url) {
//error_log("New PDF URL detected. Resetting previous transients for session $session_id.");
delete_transient('mxchat_pdf_url_' . $session_id);
delete_transient('mxchat_pdf_embeddings_' . $session_id);
delete_transient('mxchat_include_pdf_in_context_' . $session_id);
// Store the new PDF URL
set_transient('mxchat_pdf_url_' . $session_id, $new_pdf_url, HOUR_IN_SECONDS);
// Fetch and process the new PDF
$embeddings = $this->fetch_and_split_pdf_pages($new_pdf_url, $max_pages); // Ensure both arguments are passed
if ($embeddings === 'too_many_pages') {
//error_log("PDF exceeds maximum allowed pages ($max_pages) for URL: $new_pdf_url");
$error_text = sprintf(
$this->options['pdf_intent_error_text'] ?? "The provided PDF exceeds the maximum allowed limit of %d pages. Please provide a smaller document.",
$max_pages
);
$this->fallbackResponse['text'] = $error_text;
delete_transient('mxchat_pdf_url_' . $session_id);
} elseif ($embeddings) {
//error_log("PDF processed successfully for URL: $new_pdf_url");
set_transient('mxchat_pdf_embeddings_' . $session_id, $embeddings, HOUR_IN_SECONDS);
set_transient('mxchat_include_pdf_in_context_' . $session_id, true, HOUR_IN_SECONDS);
$this->fallbackResponse['text'] = $success_text;
} else {
//error_log("Failed to process PDF for URL: $new_pdf_url");
$this->fallbackResponse['text'] = $error_text;
delete_transient('mxchat_pdf_url_' . $session_id);
}
return;
}
}
if (!$pdf_url) {
//error_log("No PDF URL provided or stored for session $session_id.");
set_transient('mxchat_waiting_for_pdf_url_' . $session_id, true, HOUR_IN_SECONDS);
$this->fallbackResponse['text'] = $trigger_text;
return;
}
// Retrieve stored embeddings or fetch if missing
$embeddings = get_transient('mxchat_pdf_embeddings_' . $session_id);
if (!$embeddings) {
//error_log("No embeddings found for PDF URL: $pdf_url. Attempting to process again.");
$embeddings = $this->fetch_and_split_pdf_pages($pdf_url, $max_pages); // Ensure both arguments are passed
if ($embeddings === 'too_many_pages') {
//error_log("PDF exceeds maximum allowed pages ($max_pages) for URL: $pdf_url");
$this->fallbackResponse['text'] = "The provided PDF exceeds the maximum allowed limit of {$max_pages} pages. Please provide a smaller document.";
delete_transient('mxchat_pdf_url_' . $session_id);
} elseif ($embeddings) {
//error_log("PDF processed successfully for URL: $pdf_url");
set_transient('mxchat_pdf_embeddings_' . $session_id, $embeddings, HOUR_IN_SECONDS);
set_transient('mxchat_include_pdf_in_context_' . $session_id, true, HOUR_IN_SECONDS);
$this->fallbackResponse['text'] = $success_text;
} else {
//error_log("Failed to process PDF for URL: $pdf_url");
$this->fallbackResponse['text'] = $error_text;
delete_transient('mxchat_pdf_url_' . $session_id);
}
} else {
//error_log("Using stored embeddings for session $session_id.");
set_transient('mxchat_include_pdf_in_context_' . $session_id, true, HOUR_IN_SECONDS);
$this->fallbackResponse['text'] = ''; // Proceed without additional message
}
}
private function fetch_and_split_pdf_pages($pdf_source, $max_pages) {
$upload_dir = wp_upload_dir();
$temp_file = null;
try {
// Handle URL vs local file
if (filter_var($pdf_source, FILTER_VALIDATE_URL)) {
// Validate and download the file from URL
$temp_file = wp_tempnam($pdf_source); // Safe temporary file name
$response = wp_remote_get($pdf_source, ['timeout' => 60]);
if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) {
//error_log("Failed to download PDF. Error: " . print_r($response, true));
return false;
}
file_put_contents($temp_file, wp_remote_retrieve_body($response));
// Validate that the downloaded file is a PDF
$mime_type = mime_content_type($temp_file);
if ($mime_type !== 'application/pdf') {
//error_log("Invalid MIME type detected for PDF: $mime_type");
unlink($temp_file);
return false;
}
} else {
// For local files, use the provided path directly
$temp_file = $pdf_source;
}
// Parse and process the PDF
$parser = new \Smalot\PdfParser\Parser();
$pdf = $parser->parseFile($temp_file);
$pages = $pdf->getPages();
if (count($pages) > $max_pages) {
//error_log("PDF exceeds the maximum allowed pages: " . count($pages));
if (filter_var($pdf_source, FILTER_VALIDATE_URL)) {
unlink($temp_file);
}
return 'too_many_pages';
}
$embeddings = [];
foreach ($pages as $page_number => $page) {
$text = $page->getText();
// Ensure text is non-empty before generating embeddings
if (empty(trim($text))) {
//error_log("Skipping empty page: " . ($page_number + 1));
continue;
}
$embedding = $this->mxchat_generate_embedding(
"Page " . ($page_number + 1) . ": " . $text,
$this->options['api_key']
);
if ($embedding) {
$embeddings[] = [
'page_number' => $page_number + 1,
'embedding' => $embedding,
'text' => $text,
];
} else {
//error_log("Failed to generate embedding for page " . ($page_number + 1));
}
}
// Clean up downloaded file if it was from URL
if (filter_var($pdf_source, FILTER_VALIDATE_URL) && $temp_file) {
unlink($temp_file);
}
return $embeddings;
} catch (\Exception $e) {
// error_log("Error parsing or processing PDF: " . $e->getMessage());
// Cleanup in case of exception
if (filter_var($pdf_source, FILTER_VALIDATE_URL) && $temp_file && file_exists($temp_file)) {
unlink($temp_file);
}
return false;
}
}
private function find_relevant_pdf_pages($query_embedding, $embeddings) {
//error_log("find_relevant_pdf_pages called.");
$most_relevant = null;
$highest_similarity = -INF;
foreach ($embeddings as $page_data) {
$similarity = $this->mxchat_calculate_cosine_similarity($query_embedding, $page_data['embedding']);
if ($similarity > $highest_similarity) {
$highest_similarity = $similarity;
$most_relevant = $page_data['page_number'];
}
}
if (!is_null($most_relevant)) {
$page_numbers = range(max(1, $most_relevant - 1), min(count($embeddings), $most_relevant + 1));
return array_filter($embeddings, function ($page) use ($page_numbers) {
return in_array($page['page_number'], $page_numbers);
});
}
return [];
}
// Add this to your class
public function handle_pdf_upload() {
// Log the start of the function for debugging
//error_log('PDF upload handler started');
// Verify the nonce for security
check_ajax_referer('mxchat_chat_nonce', 'nonce');
// Check required parameters
if (!isset($_FILES['pdf_file']) || !isset($_POST['session_id'])) {
// error_log('Missing required parameters: ' . print_r($_FILES, true) . ', ' . print_r($_POST, true));
wp_send_json_error('Missing required parameters.');
return;
}
// Sanitize inputs
$file = $_FILES['pdf_file'];
$session_id = sanitize_text_field($_POST['session_id']);
//error_log('Session ID: ' . $session_id);
// Validate the file type
$file_type = wp_check_filetype($file['name'], ['pdf' => 'application/pdf']);
if ($file_type['type'] !== 'application/pdf') {
wp_send_json_error('Invalid file type. Only PDF files are allowed.');
return;
}
// Set up the upload directory
$upload_dir = wp_upload_dir();
$pdf_filename = 'mxchat_' . $session_id . '_' . time() . '.pdf';
$pdf_path = $upload_dir['path'] . '/' . $pdf_filename;
// Move the uploaded file to the uploads directory
if (!move_uploaded_file($file['tmp_name'], $pdf_path)) {
//error_log('Failed to move uploaded file to: ' . $pdf_path);
wp_send_json_error('Failed to upload file.');
return;
}
// Log the file upload success
//error_log('File uploaded successfully to: ' . $pdf_path);
// Clear any existing PDF data for this session
$this->clear_pdf_transients($session_id);
// Process the PDF
$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);
// Handle cases where the PDF exceeds the maximum allowed pages
if ($embeddings === 'too_many_pages') {
unlink($pdf_path); // Delete the file as it's not usable
$error_message = sprintf(
$this->options['pdf_intent_error_text'] ??
"The provided PDF exceeds the maximum allowed limit of %d pages. Please provide a smaller document.",
$max_pages
);
//error_log('PDF exceeds maximum allowed pages. Error: ' . $error_message);
wp_send_json_error($error_message);
return;
}
// Handle cases where the PDF text could not be extracted
if ($embeddings === false || empty($embeddings)) {
unlink($pdf_path);
$error_message = $this->options['pdf_intent_error_text'] ??
'The uploaded PDF appears to be empty or contains unsupported content. Please upload a valid PDF with text content.';
//error_log('Failed to extract text or generate embeddings. Error: ' . $error_message);
wp_send_json_error($error_message);
return;
}
// Handle successful embedding creation
if (!empty($embeddings)) {
// Store the PDF data in transients
set_transient('mxchat_pdf_url_' . $session_id, $pdf_path, 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);
// Use the success message from options
$success_message = $this->options['pdf_intent_success_text'] ??
"I've processed the PDF. What questions do you have about it?";
wp_send_json_success([
'message' => $success_message,
]);
return;
}
// If embeddings couldn't be generated, delete the uploaded file
unlink($pdf_path);
$error_message = $this->options['pdf_intent_error_text'] ??
'Sorry, I couldn\'t process the PDF. Please ensure it\'s a valid file.';
//error_log('Unexpected failure during embedding generation. Error: ' . $error_message);
wp_send_json_error($error_message);
return;
}
/**
* Calls the AI API with the provided prompt.
*
* @param string $prompt The prompt to send to the AI.
* @return array An array containing the AI's response text.
*/
private function mxchat_call_ai_api( $prompt ) {
//error_log( 'Calling AI API with the provided prompt.' );
$api_key = $this->options['api_key'];
if ( empty( $api_key ) ) {
//error_log( 'API key is not set.' );
return [ 'text' => 'API key is not set.' ];
}
$url = 'https://api.openai.com/v1/chat/completions';
$messages = [
[
'role' => 'system',
'content' => 'You are a helpful assistant that provides concise and personalized product recommendations. Someone has asked you for some recommendations please respond very concisely appropriate for an AI chatbot.',
],
[
'role' => 'user',
'content' => $prompt,
],
];
$args = [
'headers' => [
'Authorization' => 'Bearer ' . $api_key,
'Content-Type' => 'application/json',
],
'body' => wp_json_encode(
[
'model' => 'gpt-4o',
'messages' => $messages,
'temperature' => 0.7,
]
),
'timeout' => 10,
'method' => 'POST',
];
$response = wp_remote_post( $url, $args );
if ( is_wp_error( $response ) ) {
//error_log( 'Error communicating with AI API: ' . $response->get_error_message() );
return [ 'text' => 'Error communicating with AI API.' ];
}
$body = wp_remote_retrieve_body( $response );
//error_log( 'API response body: ' . $body );
$decoded_body = json_decode( $body, true );
if ( isset( $decoded_body['choices'][0]['message']['content'] ) ) {
return [ 'text' => $decoded_body['choices'][0]['message']['content'] ];
} else {
//error_log( 'Unexpected API response format: ' . wp_json_encode( $decoded_body ) );
return [ 'text' => 'No response received from AI.' ];
}
}
/**
* Fetches the AI response for the given prompt.
*
* @param string $prompt The prompt to send to the AI.
* @return string|null The AI's response text or null if not available.
*/
private function mxchat_fetch_ai_response( $prompt ) {
$response = $this->mxchat_call_ai_api( $prompt );
return isset( $response['text'] ) ? $response['text'] : null;
}
/**
* Generates the AI prompt for product recommendations.
*
* @param array $recommendations An array of product recommendations.
* @return string The generated AI prompt.
*/
private function mxchat_generate_ai_recommendation_prompt( $recommendations ) {
//error_log( 'Generating AI recommendation prompt.' );
$recommendation_list = '';
foreach ( $recommendations as $index => $rec ) {
$number = $index + 1;
$name = $rec['name'];
$price = $rec['price'];
$url = $rec['url'];
$image = $rec['image'];
$recommendation_list .= "{$number}. Product: {$name} (Price: \${$price})\n";
$recommendation_list .= " [Link]({$url})\n";
$recommendation_list .= " \n\n";
}
$prompt = "Based on the following list of products, generate a unique, friendly, and personalized response to a user who asked for a recommendation ";
$prompt .= "Then, for each product, provide a brief justification of why it's relevant to the user. ";
$prompt .= "Please number your responses to match the product numbers.\n\n";
$prompt .= "Products:\n\n{$recommendation_list}";
$prompt .= "Please ensure that the number of each product matches the order in which the products are listed.";
//error_log( 'Generated AI prompt: ' . $prompt );
return $prompt;
}
/**
* Handles product recommendations by generating and formatting the AI response.
*
* @param string $message The user's message.
* @param int $user_id The user's ID.
* @param int $session_id The session ID.
*/
public function mxchat_handle_product_recommendations( $message, $user_id, $session_id ) {
try {
//error_log( "Starting product recommendations for user: $user_id, session: $session_id." );
$recommendation_data = $this->mxchat_generate_recommendations( $user_id );
//error_log( 'Generated recommendation data: ' . wp_json_encode( $recommendation_data ) );
if ( empty( $recommendation_data['recommendations'] ) ) {
//error_log( "No recommendations found for user: $user_id." );
$this->fallbackResponse = [
'text' => __( "I couldn't find any product recommendations for you right now. Please try again later!", 'mxchat' ),
];
return;
}
// Remove duplicates and limit to top 4 recommendations
$unique_recommendations = [];
foreach ( $recommendation_data['recommendations'] as $rec ) {
$unique_recommendations[ $rec['url'] ] = $rec;
}
//error_log( 'Unique recommendations: ' . wp_json_encode( $unique_recommendations ) );
$unique_recommendations = array_slice( $unique_recommendations, 0, 4 );
//error_log( 'Top 4 recommendations: ' . wp_json_encode( $unique_recommendations ) );
$recommendations_summary = [];
foreach ( $unique_recommendations as $rec ) {
$recommendations_summary[] = [
'name' => $rec['name'],
'price' => strip_tags( $rec['price'] ),
'url' => $rec['url'],
'image' => $rec['image'],
];
}
// Generate AI prompt and fetch response
$ai_prompt = $this->mxchat_generate_ai_recommendation_prompt( $recommendations_summary );
$ai_response = $this->mxchat_fetch_ai_response( $ai_prompt );
//error_log( 'AI response received: ' . wp_json_encode( $ai_response ) );
if ( empty( $ai_response ) ) {
$this->fallbackResponse = [
'text' => __( 'An unexpected error occurred while generating recommendations. Please try again later.', 'mxchat' ),
];
return;
}
// Split the AI's response into lines
$ai_lines = preg_split( '/\r\n|\r|\n/', $ai_response );
// Initialize variables
$formatted_response = '';
$justifications = [];
$current_number = 0;
$in_introduction = true;
$introduction = '';
// Parse the AI response to separate the introduction and the justifications
foreach ( $ai_lines as $line ) {
if ( preg_match( '/^\s*(\d+)\.\s*(.*)$/', $line, $matches ) ) {
// This line is a numbered justification
$current_number = intval( $matches[1] ) - 1;
$justifications[ $current_number ] = $matches[2];
$in_introduction = false;
} elseif ( $in_introduction ) {
// This line is part of the introduction
$introduction .= $line . ' ';
} else {
// This line is a continuation of the current justification
if ( isset( $justifications[ $current_number ] ) ) {
$justifications[ $current_number ] .= ' ' . $line;
}
}
}
// Build the formatted response
if ( ! empty( $introduction ) ) {
$formatted_response .= esc_html( trim( $introduction ) ) . "