PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 1.3
MxChat – AI Chatbot & Content Generation for WordPress v1.3
3.2.21 3.2.20 3.2.19 3.2.18 3.2.17 3.2.16 3.2.15 3.2.14 3.2.12 3.2.13 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 3.2.6 3.2.5 3.2.4 3.2.3 3.2.2 3.2.1 2.0.3 2.0.4 2.0.5 2.0.6 All 152 releases
mxchat-basic / includes / class-mxchat-integrator.php

class-mxchat-integrator.php in MxChat – AI Chatbot & Content Generation for WordPress 1.3, at includes/class-mxchat-integrator.php

1,547 lines 59.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if (!defined('ABSPATH')) {
3 exit;
4 }
5
6 class MxChat_Integrator {
7 private $options;
8 private $chat_count;
9
10 public function __construct() {
11 $this->options = get_option('mxchat_options');
12 $this->chat_count = get_option('mxchat_chat_count', 0);
13
14 // Add WooCommerce hooks
15 add_action('wp_insert_post', array($this, 'mxchat_handle_product_change'), 10, 3);
16
17 // Ensure embeddings are removed when a product is moved to trash or permanently deleted
18 add_action('wp_trash_post', array($this, 'mxchat_handle_product_delete'));
19 add_action('before_delete_post', array($this, 'mxchat_handle_product_delete'));
20
21 add_action('wp_enqueue_scripts', array($this, 'mxchat_enqueue_scripts_styles'));
22 add_action('wp_ajax_mxchat_handle_chat_request', array($this, 'mxchat_handle_chat_request'));
23 add_action('wp_ajax_nopriv_mxchat_handle_chat_request', array($this, 'mxchat_handle_chat_request'));
24
25 add_action('wp_ajax_mxchat_dismiss_pre_chat_message', array($this, 'mxchat_dismiss_pre_chat_message'));
26 add_action('wp_ajax_nopriv_mxchat_dismiss_pre_chat_message', array($this, 'mxchat_dismiss_pre_chat_message'));
27 // Add the AJAX actions for checking if the pre-chat message was dismissed
28 add_action('wp_ajax_mxchat_check_pre_chat_message_status', array($this, 'mxchat_check_pre_chat_message_status'));
29 add_action('wp_ajax_nopriv_mxchat_check_pre_chat_message_status', array($this, 'mxchat_check_pre_chat_message_status'));
30
31 add_action('wp_ajax_mxchat_fetch_conversation_history', [$this, 'mxchat_fetch_conversation_history']);
32 add_action('wp_ajax_nopriv_mxchat_fetch_conversation_history', [$this, 'mxchat_fetch_conversation_history']);
33
34
35 add_action('wp_ajax_mxchat_add_to_cart', [$this, 'mxchat_add_to_cart']);
36 add_action('wp_ajax_nopriv_mxchat_add_to_cart', [$this, 'mxchat_add_to_cart']);
37
38 if (!wp_next_scheduled('mxchat_reset_rate_limits')) {
39 wp_schedule_event(time(), 'daily', 'mxchat_reset_rate_limits');
40 }
41
42 add_action('mxchat_reset_rate_limits', array($this, 'mxchat_reset_rate_limits'));
43 }
44
45 public function mxchat_handle_product_change($post_id, $post, $update) {
46 // Ensure this is a product post type
47 if ($post->post_type !== 'product') {
48 return;
49 }
50
51 // Only generate embeddings if the product is published
52 if ($post->post_status === 'publish') {
53 // Delay the embedding slightly to ensure all product data is available
54 add_action('shutdown', function() use ($post_id) {
55 $product = wc_get_product($post_id);
56 if ($product && $product->get_price() !== '') {
57 $this->mxchat_store_product_embedding($product);
58 } else {
59 // Optionally, log or handle the case where product data is incomplete
60 // error_log("Product {$post_id} does not have complete data. Embedding not generated.");
61 }
62 });
63 }
64 }
65
66 public function mxchat_handle_product_delete($post_id) {
67 if (get_post_type($post_id) !== 'product') {
68 return;
69 }
70
71 global $wpdb;
72 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
73
74 // Delete the embedding associated with this product
75 $wpdb->delete($table_name, array('source_url' => get_permalink($post_id)), array('%s'));
76 }
77
78 private function mxchat_store_product_embedding($product) {
79 if (isset($this->options['enable_woocommerce_integration']) && $this->options['enable_woocommerce_integration'] === '1') {
80
81 $source_url = get_permalink($product->get_id());
82 $regular_price = $product->get_regular_price();
83 $sale_price = $product->get_sale_price();
84 $price = $sale_price ?: $regular_price;
85
86 $description = $product->get_description() . "\n\n" .
87 "Short Description: " . $product->get_short_description() . "\n" .
88 "Price: " . $regular_price . "\n" .
89 "Sale Price: " . ($sale_price ?: 'N/A') . "\n" .
90 "SKU: " . $product->get_sku();
91
92 global $wpdb;
93 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
94
95 // Delete any existing embedding for this product
96 $wpdb->delete($table_name, array('source_url' => $source_url), array('%s'));
97
98 // Submit the new content and embedding to the database
99 MxChat_Utils::submit_content_to_db($description, $source_url, $this->options['api_key']);
100 }
101 }
102
103
104
105
106
107 private function mxchat_increment_chat_count() {
108 $chat_count = get_option('mxchat_chat_count', 0);
109 $chat_count++;
110 update_option('mxchat_chat_count', $chat_count);
111 }
112
113 function mxchat_fetch_conversation_history() {
114 if (empty($_POST['session_id'])) {
115 wp_send_json_error(['message' => 'Session ID missing.']);
116 wp_die();
117 }
118
119 $session_id = sanitize_text_field($_POST['session_id']);
120 $history = get_option("mxchat_history_{$session_id}", []); // Retrieve stored history
121
122 if (empty($history)) {
123 wp_send_json_error(['message' => 'No history found.']);
124 wp_die();
125 }
126
127 wp_send_json_success(['conversation' => $history]);
128 wp_die();
129 }
130 private function mxchat_fetch_conversation_history_for_ajax($session_id) {
131 $history = get_option("mxchat_history_{$session_id}", []); // Retrieve stored history based on session ID
132 $formatted_history = [];
133
134 // Format the history to align with the expected structure for OpenAI
135 foreach ($history as $entry) {
136 $formatted_history[] = [
137 'role' => $entry['role'], // Ensure this matches 'user' or 'assistant'
138 'content' => $entry['content']
139 ];
140 }
141
142 return $formatted_history;
143 }
144 private function mxchat_fetch_conversation_history_for_ai($session_id) {
145 $history = get_option("mxchat_history_{$session_id}", []);
146
147 $formatted_history = [];
148
149 foreach ($history as $entry) {
150 // Skip messages containing HTML
151 if ($entry['content'] !== strip_tags($entry['content'])) {
152 continue;
153 }
154
155 $formatted_history[] = [
156 'role' => $entry['role'],
157 'content' => $entry['content']
158 ];
159 }
160
161 return $formatted_history;
162 }
163
164
165 private function mxchat_save_chat_message($session_id, $role, $message) {
166 global $wpdb;
167 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
168
169 $user_id = is_user_logged_in() ? get_current_user_id() : 0;
170 $user_identifier = MxChat_User::mxchat_get_user_identifier();
171 $user_email = MxChat_User::mxchat_get_user_email();
172
173 $history = get_option("mxchat_history_{$session_id}", []);
174 $history[] = ['role' => $role, 'content' => $message];
175 update_option("mxchat_history_{$session_id}", $history);
176
177 $wpdb->insert($table_name, [
178 'user_id' => $user_id,
179 'user_identifier' => $user_identifier,
180 'user_email' => $user_email,
181 'session_id' => $session_id,
182 'role' => $role,
183 'message' => $message,
184 'timestamp' => current_time('mysql', 1)
185 ]);
186 }
187
188
189 public function mxchat_handle_chat_request() {
190 global $wpdb;
191
192 // Get and sanitize the user identifier
193 $user_id = $this->mxchat_get_user_identifier();
194 $user_id = sanitize_key($user_id);
195
196 // Setup rate limiting
197 $rate_limit_transient_key = 'mxchat_chat_limit_' . $user_id;
198 $chat_count = get_transient($rate_limit_transient_key) ?: 0;
199
200 // Retrieve the session ID from the client's POST data
201 $session_id = isset($_POST['session_id']) ? sanitize_text_field($_POST['session_id']) : '';
202
203 if (empty($session_id)) {
204 wp_send_json_error('Session ID is missing.');
205 wp_die();
206 }
207
208 // Check rate limit
209 $rate_limit_option = $this->options['rate_limit'] ?? 'unlimited';
210 if ($rate_limit_option !== 'unlimited' && $chat_count >= intval($rate_limit_option)) {
211 wp_send_json_error(['message' => $this->options['rate_limit_message'] ?? 'Rate limit exceeded. Please try again later.']);
212 wp_die();
213 }
214
215 // Increment chat count only if limit is not 'unlimited'
216 if ($rate_limit_option !== 'unlimited') {
217 set_transient($rate_limit_transient_key, $chat_count + 1, DAY_IN_SECONDS);
218 }
219
220 // Validate and sanitize the incoming message
221 if (empty($_POST['message'])) {
222 wp_send_json_error('No message received');
223 wp_die();
224 }
225
226 $message = sanitize_text_field($_POST['message']);
227 $this->mxchat_save_chat_message($session_id, 'user', $message);
228
229 // Track email capture and WooCommerce flows
230 $email_capture_prompt = get_transient('mxchat_email_capture_' . $user_id);
231 $interaction_count = get_transient('mxchat_email_interaction_count_' . $user_id) ?: 0;
232 $woocommerce_prompt = get_transient('mxchat_woocommerce_prompt_' . $user_id);
233
234 // Handle email capture flow
235 if ($email_capture_prompt) {
236 if (preg_match('/[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}/i', $message, $matches)) {
237 $email = $matches[0];
238 $this->add_email_to_loops($email);
239 $response = $this->options['email_capture_response'] ?? 'Thank you for providing your email! You\'ve been added to our list.';
240
241 delete_transient('mxchat_email_capture_' . $user_id);
242 delete_transient('mxchat_email_interaction_count_' . $user_id);
243 delete_transient('mxchat_woocommerce_prompt_' . $user_id);
244
245 $this->mxchat_save_chat_message($session_id, 'bot', $response);
246 wp_send_json(['message' => $response]);
247 wp_die();
248 } else {
249 if ($interaction_count >= 3) {
250 delete_transient('mxchat_email_capture_' . $user_id);
251 delete_transient('mxchat_email_interaction_count_' . $user_id);
252 } else {
253 set_transient('mxchat_email_interaction_count_' . $user_id, ++$interaction_count, 5 * MINUTE_IN_SECONDS);
254 }
255 }
256 }
257
258
259 // Initialize variables for storing intent and fallback response data
260 $this->fallbackResponse = [
261 'text' => '',
262 'html' => '',
263 'images' => [],
264 ];
265 $this->productCardHtml = ''; // Initialize product card HTML
266 $intent_info = '';
267
268 // Step 1: Detect intent and handle intent-based responses if applicable
269 $intent_matched = $this->mxchat_check_intent_and_invoke_callback($message, $user_id, $session_id);
270
271 /*
272 // Log the intent match and fallback response
273 if (defined('WP_DEBUG') && WP_DEBUG) {
274 error_log("Intent matched: " . ($intent_matched ? 'Yes' : 'No'));
275 error_log("Fallback response after intent handling: " . print_r($this->fallbackResponse['text'], true));
276 }
277 */
278
279
280 // Store any intent-based info from fallbackResponse for use in context
281 if (!empty($this->fallbackResponse['text'])) {
282 $intent_info = $this->fallbackResponse['text'];
283 }
284
285 // Step 2: Check for relevant knowledge database content based on user message embedding
286 $user_message_embedding = $this->mxchat_generate_embedding($message, $this->options['api_key']);
287 if (!is_array($user_message_embedding)) {
288 wp_send_json_error('Error processing your message.');
289 wp_die();
290 }
291
292 // Fetch relevant content from the knowledge base using the embedding
293 $relevant_content = $this->mxchat_find_relevant_content($user_message_embedding);
294
295 // Step 3: Prepare context for AI by combining user query, intent-based info, and knowledge base content
296 $context_content = "User asked: '{$message}'\n\n";
297
298 // Append intent-based information if any
299 if (!empty($intent_info)) {
300 $context_content .= "Relevant intent-based information:\n" . $intent_info . "\n\n";
301 }
302
303 // Append relevant knowledge base content if any
304 if (!empty($relevant_content)) {
305 $context_content .= "Relevant content from knowledge database:\n" . $relevant_content . "\n\n";
306 }
307
308 // Step 4: Generate AI response based on context and conversation history
309 $conversation_history = $this->mxchat_fetch_conversation_history_for_ai($session_id);
310 $this->mxchat_increment_chat_count();
311
312 // Call AI to generate a response with the full context
313 $response = $this->mxchat_generate_response(
314 $context_content,
315 $this->options['api_key'],
316 $this->options['xai_api_key'],
317 $this->options['claude_api_key'],
318 $conversation_history
319 );
320
321
322 // Step 5: Save the response in chat history and prepare to send it back to the user
323 $this->mxchat_save_chat_message($session_id, 'bot', $response);
324
325 // Step 4: Then save any intent content
326 if (!empty($this->productCardHtml)) {
327 $this->mxchat_save_chat_message($session_id, 'bot', $this->productCardHtml);
328 }
329
330 if (!empty($this->fallbackResponse['html'])) {
331 $this->mxchat_save_chat_message($session_id, 'bot', $this->fallbackResponse['html']);
332 }
333
334 $response_data = [
335 'text' => $response,
336 'html' => !empty($this->productCardHtml) ? $this->productCardHtml : (isset($this->fallbackResponse['html']) ? $this->fallbackResponse['html'] : ''),
337 'session_id' => $session_id
338 ];
339
340
341 //error_log("Response Data Sent to Frontend: " . print_r($response_data, true));
342
343 // Send response to the user
344 wp_send_json($response_data);
345 wp_die();
346 }
347
348 // New function to check intents and invoke the callback function
349 private function mxchat_check_intent_and_invoke_callback($message, $user_id, $session_id) {
350 global $wpdb;
351
352 // Log that we are starting the intent check
353 //error_log("Starting intent check for message: " . $message);
354
355 // Embed the user message
356 $user_embedding = $this->mxchat_generate_embedding($message, $this->options['api_key']);
357 if (!is_array($user_embedding)) {
358 // error_log('Error generating embedding for the message.');
359 wp_send_json_error('Error generating embedding for the message.');
360 return false;
361 }
362
363 // Fetch all intents from the database
364 $table_name = $wpdb->prefix . 'mxchat_intents';
365 $intents = $wpdb->get_results("SELECT * FROM $table_name");
366
367 // Log if no intents are found
368 if (empty($intents)) {
369 //error_log("No intents found in the database.");
370 }
371
372 $highest_similarity = -INF;
373 $matched_intent = null;
374
375 // Calculate similarity for each intent
376 foreach ($intents as $intent) {
377 // Use unserialize with allowed_classes parameter for enhanced security
378 $intent_embedding = $intent->embedding_vector
379 ? unserialize($intent->embedding_vector, ['allowed_classes' => false])
380 : null;
381
382 if (!is_array($intent_embedding)) {
383 //error_log("Invalid embedding for intent: " . $intent->intent_label);
384 continue; // Skip if the embedding is invalid
385 }
386
387 $similarity = $this->mxchat_calculate_cosine_similarity($user_embedding, $intent_embedding);
388 //error_log("Similarity for intent '{$intent->intent_label}': " . $similarity);
389
390 if ($similarity > $highest_similarity) {
391 $highest_similarity = $similarity;
392 $matched_intent = $intent;
393 }
394 }
395
396 // Define the similarity threshold for matching
397 $similarity_threshold = 0.80;
398
399 // If a match is found above the threshold, call the associated callback
400 if ($highest_similarity >= $similarity_threshold && $matched_intent) {
401 //error_log("Matched intent: " . $matched_intent->intent_label . " with similarity: " . $highest_similarity);
402
403 // Ensure the callback function exists before calling it
404 if (method_exists($this, $matched_intent->callback_function)) {
405 // Call the intent handler function
406 call_user_func([$this, $matched_intent->callback_function], $message, $user_id, $session_id);
407 return true; // Indicate that an intent was matched and handled
408 } else {
409 //error_log("Callback function not found: " . $matched_intent->callback_function);
410 }
411 } else {
412 //error_log("No intent matched above the threshold.");
413 }
414
415 return false; // No intent was matched above the threshold
416 }
417
418 //verified good
419 public function mxchat_handle_order_history($message, $user_id, $session_id) {
420 // Check if WooCommerce is active
421 if (!class_exists('WooCommerce')) {
422 $this->fallbackResponse['text'] = "Order functionality is currently unavailable.";
423 return; // No need to proceed further
424 }
425
426 // Determine if the user is asking for the "last order" or "all orders"
427 $queryType = (stripos($message, 'last order') !== false || stripos($message, 'recent order') !== false) ? 'last' : 'all';
428
429 // Fetch the relevant order details
430 $orderDetails = MxChat_WooCommerce::mxchat_fetch_user_orders_details($queryType);
431
432 // Set the fallback response to order details, which will be used if needed
433 $this->fallbackResponse['text'] = $orderDetails ?: "No previous orders found.";
434 }
435
436
437 //verified good
438 public function mxchat_handle_product_inquiry( $message, $user_id, $session_id ) {
439 // Attempt to retrieve the product ID from the message
440 $product_id = MxChat_WooCommerce::mxchat_extract_product_id_from_message( $message );
441
442 // Check if there's a previously discussed product for follow-up queries
443 if ( ! $product_id ) {
444 $product_id = get_transient( 'mxchat_last_discussed_product_' . $user_id );
445 }
446
447 if ( $product_id && class_exists( 'WooCommerce' ) ) {
448 $product = wc_get_product( $product_id );
449 if ( $product ) {
450 // Escape and prepare product data
451 $product_name = esc_html( $product->get_name() );
452 $product_price = $product->get_price_html(); // Assuming this is safe HTML
453 $product_image_url = esc_url( wp_get_attachment_url( $product->get_image_id() ) );
454 $product_url = esc_url( get_permalink( $product_id ) );
455 $product_id_attr = esc_attr( $product_id );
456
457 // Generate the product card HTML using HEREDOC syntax
458 $product_card_html = <<<HTML
459 <div class="mxchat-product-card">
460 <a href="{$product_url}" target="_blank">
461 <img src="{$product_image_url}" alt="{$product_name}" class="mxchat-product-image" />
462 <h3 class="mxchat-product-name">{$product_name}</h3>
463 </a>
464 <div class="mxchat-product-price">{$product_price}</div>
465 <button class="mxchat-add-to-cart-button" data-product-id="{$product_id_attr}">Add to Cart</button>
466 </div>
467 HTML;
468
469 // Save the last discussed product
470 set_transient( 'mxchat_last_discussed_product_' . $user_id, $product_id, HOUR_IN_SECONDS );
471
472 // Save the product card HTML to be appended later
473 $this->productCardHtml = $product_card_html;
474
475 // Do not send a response here; allow execution to continue
476 return;
477 }
478 }
479
480 // If no product found, set a fallback response
481 $this->fallbackResponse = __( "I couldn't find details for that product. Could you specify the product name?", 'mxchat' );
482 return;
483 }
484
485 //verified good
486 public function mxchat_handle_email_capture($message, $user_id, $session_id) {
487 // Log the message safely
488 //error_log("Triggered email capture intent for message: " . sanitize_text_field($message));
489
490 // Initiate email capture flow
491 $response = esc_html($this->options['triggered_phrase_response'] ?? "Would you like to join our mailing list? Please provide your email below.");
492
493 set_transient('mxchat_email_capture_' . $user_id, true, 5 * MINUTE_IN_SECONDS);
494 $this->mxchat_save_chat_message($session_id, 'bot', $response);
495
496 // Respond to the user
497 wp_send_json(['message' => $response]);
498 wp_die();
499 }
500
501 //very good
502 public function mxchat_generate_image($message, $user_id, $session_id) {
503 // Prepare a prompt for DALL-E
504 $prompt = "Create an image of " . sanitize_text_field($message);
505
506 // Use the existing OpenAI API key
507 $openai_api_key = sanitize_text_field($this->options['api_key']);
508
509 // Call DALL-E to generate an image
510 $image_response = $this->mxchat_generate_dalle_image($prompt, $openai_api_key);
511
512 // Check if the response contains an image URL
513 if (isset($image_response['imageUrl'])) {
514 $image_url = esc_url_raw($image_response['imageUrl']);
515
516 // Construct the HTML with a CSS class instead of inline styles
517 $response_html = '<img src="' . esc_url($image_url) . '" alt="Generated Image" class="mxchat-generated-image" />';
518
519 $response_text = "Here is the image I generated:";
520 } else {
521 $response_text = "I'm sorry, but I couldn't generate an image based on your request.";
522 $response_html = '';
523 //error_log("DALL-E image generation error: " . esc_html($image_response['error'] ?? 'Unknown error.'));
524 }
525
526 // Save both text and HTML responses
527 $this->mxchat_save_chat_message($session_id, 'bot', $response_text . "\n" . $response_html);
528
529 // Prepare the response data
530 $response_data = [
531 'message' => $response_text,
532 'html' => $response_html,
533 'image_url' => $image_url ?? '',
534 ];
535
536 // Send the JSON response
537 header('Content-Type: application/json; charset=' . get_option('blog_charset'));
538 echo json_encode($response_data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
539 wp_die();
540 }
541 private function mxchat_generate_dalle_image($prompt, $api_key, $model = 'dall-e-3', $timeout = 60) {
542 $api_url = 'https://api.openai.com/v1/images/generations';
543 $body = json_encode([
544 'prompt' => sanitize_text_field($prompt),
545 'n' => 1,
546 'size' => '1024x1024',
547 'model' => sanitize_text_field($model),
548 ]);
549
550 $args = [
551 'body' => $body,
552 'headers' => [
553 'Content-Type' => 'application/json',
554 'Authorization' => 'Bearer ' . sanitize_text_field($api_key),
555 ],
556 'method' => 'POST',
557 'timeout' => absint($timeout),
558 ];
559
560 $response = wp_remote_post($api_url, $args);
561
562 if (is_wp_error($response)) {
563 //error_log("DALL-E request failed: " . $response->get_error_message());
564 return ['error' => "Error generating image: " . $response->get_error_message()];
565 }
566
567 $response_body = json_decode(wp_remote_retrieve_body($response), true);
568
569 if (isset($response_body['data'][0]['url'])) {
570 return ['imageUrl' => esc_url_raw($response_body['data'][0]['url'])];
571 } else {
572 //error_log("DALL-E response error: " . wp_remote_retrieve_body($response));
573 return ['error' => "Failed to generate image."];
574 }
575 }
576
577 //very good
578 public function mxchat_handle_search_request($message, $user_id, $session_id) {
579 // Sanitize user input
580 $search_query = preg_replace('/^search the web for\s+/i', '', $message);
581 $search_query = preg_replace('/^show me the latest news about\s+/i', '', $message);
582 $search_query = preg_replace('/^what\'?s the news in\s+/i', '', $message);
583 $search_query = preg_replace('/^news about\s+/i', '', $message);
584 $search_query = trim(sanitize_text_field($search_query));
585
586 if (empty($search_query)) {
587 $this->fallbackResponse = [
588 'text' => __("Please provide a valid search query.", 'mxchat'),
589 ];
590 return;
591 }
592
593 // Check if the query is likely related to news
594 $is_news_search = preg_match("/\bnews\b/i", $message);
595
596 // Retrieve settings
597 $options = get_option('mxchat_options');
598
599 $api_key = isset($options['brave_api_key']) ? sanitize_text_field($options['brave_api_key']) : '';
600 $news_count = isset($options['brave_news_count']) ? intval($options['brave_news_count']) : 3;
601 $country = isset($options['brave_country']) ? sanitize_text_field($options['brave_country']) : 'us';
602 $language = isset($options['brave_language']) ? sanitize_text_field($options['brave_language']) : 'en';
603
604 if (empty($api_key)) {
605
606 $this->fallbackResponse = [
607 'text' => __("API key is not configured. Please set it in the Brave Search Settings.", 'mxchat'),
608 ];
609 return;
610 }
611
612 // Set API endpoint and parameters based on search type
613 $api_url = $is_news_search
614 ? 'https://api.search.brave.com/res/v1/news/search'
615 : 'https://api.search.brave.com/res/v1/web/search';
616
617 $query_args = [
618 'q' => urlencode($search_query),
619 ];
620
621 if ($is_news_search) {
622 $query_args['count'] = $news_count;
623 $query_args['country'] = $country;
624 $query_args['search_lang'] = $language;
625 }
626
627 $api_url = add_query_arg($query_args, $api_url);
628
629 // Implement caching
630 $transient_key = 'mxchat_search_' . md5($api_url);
631 $body = get_transient($transient_key);
632
633 if (false === $body) {
634 $args = [
635 'headers' => [
636 'Accept' => 'application/json',
637 'Accept-Encoding' => 'gzip',
638 'Authorization' => 'Bearer ' . $api_key,
639 'x-subscription-token' => $api_key,
640 ],
641 'timeout' => 10,
642 ];
643
644 $response = wp_remote_get($api_url, $args);
645
646 if (is_wp_error($response)) {
647
648 $this->fallbackResponse = [
649 'text' => __("I'm sorry, I couldn't retrieve any information based on your request.", 'mxchat'),
650 ];
651 return;
652 }
653
654 $body = json_decode(wp_remote_retrieve_body($response), true);
655 set_transient($transient_key, $body, HOUR_IN_SECONDS);
656 }
657
658 // Process the API response and build a more informative summary
659 $text_summary = "";
660
661 if ($is_news_search && isset($body['results']) && is_array($body['results']) && count($body['results']) > 0) {
662 $text_summary = "Here are some recent news articles:\n\n";
663
664 foreach ($body['results'] as $news) {
665 $title = isset($news['title']) ? esc_html($news['title']) : __('No Title', 'mxchat');
666 $url = isset($news['url']) ? esc_url($news['url']) : '#';
667 $description = isset($news['description']) ? esc_html(strip_tags($news['description'])) : __('No description available.', 'mxchat');
668 $age = isset($news['age']) ? esc_html($news['age']) : '';
669 $hostname = isset($news['meta_url']['hostname']) ? esc_html($news['meta_url']['hostname']) : '';
670 $thumbnail = isset($news['thumbnail']['src']) ? esc_url($news['thumbnail']['src']) : '';
671
672 // Build text-only news item summary
673 $text_summary .= "Title: {$title}\nURL: {$url}\nDescription: {$description}\nPublished: {$age}\n";
674 if (!empty($hostname)) {
675 $text_summary .= "Source: {$hostname}\n";
676 }
677 if (!empty($thumbnail)) {
678 $text_summary .= "Thumbnail: ![Image]({$thumbnail})\n";
679 }
680 if (!empty($news['extra_snippets'])) {
681 $extra_snippet_text = implode(" ", $news['extra_snippets']);
682 $text_summary .= "Additional Info: {$extra_snippet_text}\n";
683 }
684 $text_summary .= "\n"; // Separate entries
685 }
686 } elseif (!$is_news_search && isset($body['web']['results']) && is_array($body['web']['results']) && count($body['web']['results']) > 0) {
687 $text_summary = "Here are some relevant articles based on your query:\n\n";
688
689 foreach ($body['web']['results'] as $result) {
690 $title = isset($result['title']) ? esc_html($result['title']) : __('No Title', 'mxchat');
691 $url = isset($result['url']) ? esc_url($result['url']) : '#';
692 $description = isset($result['description']) ? esc_html(strip_tags($result['description'])) : __('No description available.', 'mxchat');
693 $hostname = isset($result['meta_url']['hostname']) ? esc_html($result['meta_url']['hostname']) : '';
694 $thumbnail = isset($result['thumbnail']['src']) ? esc_url($result['thumbnail']['src']) : '';
695
696 // Build text-only web item summary
697 $text_summary .= "Title: {$title}\nURL: {$url}\nDescription: {$description}\n";
698 if (!empty($hostname)) {
699 $text_summary .= "Source: {$hostname}\n";
700 }
701 if (!empty($thumbnail)) {
702 $text_summary .= "Thumbnail: ![Image]({$thumbnail})\n";
703 }
704 if (!empty($result['extra_snippets'])) {
705 $extra_snippet_text = implode(" ", $result['extra_snippets']);
706 $text_summary .= "Additional Info: {$extra_snippet_text}\n";
707 }
708 $text_summary .= "\n"; // Separate entries
709 }
710 } else {
711
712 $this->fallbackResponse = [
713 'text' => __("I'm sorry, I couldn't retrieve any information based on your request.", 'mxchat'),
714 ];
715 return;
716 }
717
718 $this->fallbackResponse = [
719 'text' => $text_summary,
720 ];
721
722
723 }
724
725 //very good
726 public function mxchat_handle_image_search_request($message, $user_id, $session_id) {
727
728 // Step 1: Interpret the search query for better results
729 $refined_search_query = $this->mxchat_interpret_search_query($message);
730
731
732 // If no query was interpreted, return a fallback message
733 if (empty($refined_search_query)) {
734 $this->fallbackResponse = [
735 'text' => __("I'm sorry, I couldn't interpret your search query. Please specify what you'd like to see images of.", 'mxchat'),
736 'html' => "",
737 ];
738 return;
739 }
740
741 // Brave API URL
742 $api_url = 'https://api.search.brave.com/res/v1/images/search';
743
744 // Retrieve Brave API settings
745 $options = get_option('mxchat_options');
746 $api_key = isset($options['brave_api_key']) ? sanitize_text_field($options['brave_api_key']) : '';
747
748 if (empty($api_key)) {
749 /*
750 if (defined('WP_DEBUG') && WP_DEBUG) {
751 error_log("Brave API key is missing.");
752 }
753 */
754
755 $this->fallbackResponse = [
756 'text' => __("API key is not configured. Please set it in the Brave Search Settings.", 'mxchat'),
757 'html' => "",
758 ];
759 return;
760 }
761
762 $image_count = isset($options['brave_image_count']) ? intval($options['brave_image_count']) : 4;
763 $safe_search = isset($options['brave_safe_search']) ? sanitize_text_field($options['brave_safe_search']) : 'strict';
764
765 // Append query parameters based on settings
766 $api_url = add_query_arg([
767 'q' => rawurlencode($refined_search_query),
768 'count' => $image_count,
769 'safesearch' => $safe_search,
770 ], $api_url);
771
772 /*
773 // Log the final API URL for the search
774 if (defined('WP_DEBUG') && WP_DEBUG) {
775 error_log("Final API URL for Brave Image Search: " . esc_url_raw($api_url));
776 }
777 */
778
779
780 // Implement caching
781 $transient_key = 'mxchat_image_search_' . md5($refined_search_query);
782 $body = get_transient($transient_key);
783
784 if (false === $body) {
785 $args = [
786 'headers' => [
787 'Accept' => 'application/json',
788 'Accept-Encoding' => 'gzip',
789 'X-Subscription-Token' => $api_key,
790 ],
791 'timeout' => 10,
792 ];
793
794 $response = wp_remote_get($api_url, $args);
795
796 if (is_wp_error($response)) {
797 /*
798 if (defined('WP_DEBUG') && WP_DEBUG) {
799 error_log("Brave Image API request failed: " . $response->get_error_message());
800 }
801 */
802
803 $this->fallbackResponse = [
804 'text' => __("I'm sorry, I couldn't retrieve any images based on your request.", 'mxchat'),
805 'html' => "",
806 ];
807 return;
808 }
809
810 $body = json_decode(wp_remote_retrieve_body($response), true);
811 set_transient($transient_key, $body, HOUR_IN_SECONDS);
812 }
813
814 // Process the API response
815 if (isset($body['results']) && is_array($body['results']) && count($body['results']) > 0) {
816 $html_output = '<div class="mxchat-image-gallery">';
817
818 foreach ($body['results'] as $image) {
819 $image_url = isset($image['url']) ? esc_url($image['url']) : '';
820 $thumbnail_url = isset($image['thumbnail']['src']) ? esc_url($image['thumbnail']['src']) : '';
821 $title = isset($image['title']) ? esc_html($image['title']) : __('Image', 'mxchat');
822
823 if ($image_url && $thumbnail_url) {
824 $html_output .= '<div class="mxchat-image-item">';
825 $html_output .= '<strong class="mxchat-image-title">' . $title . '</strong>';
826 $html_output .= '<a href="' . $image_url . '" target="_blank" rel="noopener noreferrer" class="mxchat-image-link">';
827 $html_output .= '<img src="' . $thumbnail_url . '" alt="' . $title . '" class="mxchat-image-thumbnail">';
828 $html_output .= '</a></div>';
829 }
830 }
831
832 $html_output .= '</div>';
833
834 $this->fallbackResponse = [
835 'text' => "",
836 'html' => $html_output,
837 ];
838
839 // Save response in chat history
840 $this->mxchat_save_chat_message($session_id, 'bot', $html_output);
841
842 } else {
843 /*
844 if (defined('WP_DEBUG') && WP_DEBUG) {
845 error_log("Brave Image API response did not contain expected data structure or was empty: " . print_r($body, true));
846 }
847 */
848
849 $this->fallbackResponse = [
850 'text' => __("I'm sorry, I couldn't retrieve any images based on your request.", 'mxchat'),
851 'html' => "",
852 ];
853 }
854 }
855 public function mxchat_interpret_search_query($user_query) {
856 $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.";
857
858 // Retrieve OpenAI API key using 'api_key' as the option key
859 $api_key = isset($this->options['api_key']) ? sanitize_text_field($this->options['api_key']) : sanitize_text_field(get_option('mxchat_options')['api_key']);
860
861 /*
862 // Log the API key check, without exposing the key
863 if (defined('WP_DEBUG') && WP_DEBUG) {
864 error_log("Retrieved OpenAI API Key: " . ($api_key ? "Present" : "Missing"));
865 }
866 */
867
868
869 if (empty($api_key)) {
870 //error_log("OpenAI API key is missing.");
871 return sanitize_text_field($user_query); // Default to the original query if API key is missing
872 }
873
874 $url = 'https://api.openai.com/v1/chat/completions';
875 $args = [
876 'headers' => [
877 'Authorization' => 'Bearer ' . $api_key,
878 'Content-Type' => 'application/json',
879 ],
880 'body' => wp_json_encode([
881 'model' => 'gpt-3.5-turbo',
882 'messages' => [
883 ['role' => 'system', 'content' => $system_prompt],
884 ['role' => 'user', 'content' => sanitize_text_field($user_query)],
885 ],
886 'temperature' => 0.2,
887 'max_tokens' => 20,
888 ]),
889 'method' => 'POST',
890 ];
891
892 $response = wp_remote_post($url, $args);
893
894 if (is_wp_error($response)) {
895 //error_log("OpenAI request failed: " . $response->get_error_message());
896 return sanitize_text_field($user_query); // Fallback to the original query if there's an error
897 }
898
899 $body = json_decode(wp_remote_retrieve_body($response), true);
900
901 // Check for a valid response and sanitize output
902 if (isset($body['choices'][0]['message']['content'])) {
903 $interpreted_query = sanitize_text_field(trim($body['choices'][0]['message']['content']));
904
905 /*
906 // Log the interpreted query for debugging
907 if (defined('WP_DEBUG') && WP_DEBUG) {
908 error_log("Interpreted search query: " . $interpreted_query);
909 }
910 */
911
912 return $interpreted_query;
913 } else {
914 //error_log("Unexpected API response format: " . print_r($body, true));
915 return sanitize_text_field($user_query);
916 }
917 }
918
919 //very good
920 public function mxchat_handle_add_to_cart_intent($message, $user_id, $session_id) {
921 // Check if WooCommerce is active before proceeding
922 if (!class_exists('WooCommerce')) {
923 // error_log("WooCommerce is not active. Add-to-cart intent cannot be processed.");
924 return false;
925 }
926
927 // Sanitize user ID for transient key
928 $sanitized_user_id = sanitize_key($user_id);
929
930 // Retrieve the last discussed product ID from the transient
931 $last_product_id = get_transient('mxchat_last_discussed_product_' . $sanitized_user_id);
932 if ($last_product_id) {
933 // Attempt to add the product to the WooCommerce cart
934 $added = WC()->cart->add_to_cart($last_product_id);
935 $product = wc_get_product($last_product_id);
936
937 if ($added && $product) {
938 // Escape product name for safety
939 $product_name = esc_html($product->get_name());
940
941 // Success response if the product is added to the cart
942 $response = "The product '{$product_name}' has been added to your cart. To proceed to checkout, please type 'checkout'.";
943 set_transient('mxchat_checkout_prompt_' . $sanitized_user_id, true, 5 * MINUTE_IN_SECONDS);
944
945 // Save the response in chat history and send a JSON response
946 $this->mxchat_save_chat_message($session_id, 'bot', $response);
947 wp_send_json(['message' => $response]);
948 wp_die();
949 } else {
950 // Failure response if the product could not be added
951 $response = "Sorry, I couldn't add the product to your cart. Please try again.";
952 $this->mxchat_save_chat_message($session_id, 'bot', $response);
953 wp_send_json(['message' => $response]);
954 wp_die();
955 }
956 } else {
957 // Prompt the user to specify the product name if no product was found
958 $response = "I couldn't find the product to add. Please mention the product name again.";
959 $this->mxchat_save_chat_message($session_id, 'bot', $response);
960 wp_send_json(['message' => $response]);
961 wp_die();
962 }
963 }
964 public function mxchat_handle_checkout_intent($message, $user_id, $session_id) {
965 // Ensure WooCommerce is available and perform checkout actions
966 if (class_exists('WooCommerce')) {
967 // Sanitize user ID for transient key
968 $sanitized_user_id = sanitize_key($user_id);
969
970 $checkout_prompt = get_transient('mxchat_checkout_prompt_' . $sanitized_user_id);
971
972 // Check if there is an active checkout prompt and if the cart has items
973 if ($checkout_prompt && WC()->cart->get_cart_contents_count() > 0) {
974 // Get and escape the checkout URL
975 $checkout_url = wc_get_checkout_url();
976 $checkout_url = esc_url_raw($checkout_url);
977
978 $response = "Great! Redirecting you to the checkout page...";
979
980 // Clear the checkout prompt transient to avoid duplicate responses
981 delete_transient('mxchat_checkout_prompt_' . $sanitized_user_id);
982
983 // Save and send the response with the checkout URL for redirection
984 $this->mxchat_save_chat_message($session_id, 'bot', $response);
985 wp_send_json(['message' => $response, 'redirect_url' => $checkout_url]);
986 wp_die();
987 } else {
988 // If no active checkout prompt or empty cart, prompt user to add items first
989 $response = "It seems like there is no active checkout prompt or no items in your cart. Please add a product to the cart first.";
990 $this->mxchat_save_chat_message($session_id, 'bot', $response);
991 wp_send_json(['message' => $response]);
992 wp_die();
993 }
994 } else {
995 // WooCommerce is not available, send an error message
996 $response = "WooCommerce is not active on this site, so checkout is not possible.";
997 $this->mxchat_save_chat_message($session_id, 'bot', $response);
998 wp_send_json(['message' => $response]);
999 wp_die();
1000 }
1001 }
1002
1003 //very good
1004 public static function mxchat_extract_product_id_from_message($message) {
1005 // Sanitize the message input
1006 $message = sanitize_text_field($message);
1007
1008 // Convert Markdown links to plain URLs
1009 $message = preg_replace('/\[(.*?)\]\((.*?)\)/', '$2', $message);
1010
1011 // Match all URLs in the message
1012 preg_match_all('/https?:\/\/[^\s]+/', $message, $matches);
1013
1014 if (!empty($matches[0])) {
1015 foreach ($matches[0] as $url) {
1016 // Sanitize the URL
1017 $url = esc_url_raw($url);
1018
1019 // Try to get the post ID from the URL
1020 $post_id = url_to_postid($url);
1021
1022 if ($post_id) {
1023 // Check if the post type is 'product'
1024 if (get_post_type($post_id) === 'product') {
1025 return $post_id;
1026 }
1027 }
1028 }
1029 }
1030 return false;
1031 }
1032
1033
1034 //very good
1035 private function add_email_to_loops($email) {
1036 // Sanitize the email
1037 $email = sanitize_email($email);
1038
1039 // Retrieve and sanitize options
1040 $api_key = isset($this->options['loops_api_key']) ? sanitize_text_field($this->options['loops_api_key']) : '';
1041 $mailing_list_id = isset($this->options['loops_mailing_list']) ? sanitize_text_field($this->options['loops_mailing_list']) : '';
1042
1043 // Check for missing API key or mailing list ID
1044 if (empty($api_key) || empty($mailing_list_id)) {
1045 //error_log('Loops API key or mailing list ID is missing.');
1046 return;
1047 }
1048
1049 $data = array(
1050 'email' => $email,
1051 'subscribed' => true,
1052 'source' => 'MxChat AI Chatbot',
1053 'mailingLists' => array($mailing_list_id => true),
1054 );
1055
1056 $url = 'https://app.loops.so/api/v1/contacts/create';
1057 $args = array(
1058 'body' => wp_json_encode($data),
1059 'headers' => array(
1060 'Authorization' => 'Bearer ' . $api_key,
1061 'Content-Type' => 'application/json',
1062 ),
1063 'method' => 'POST',
1064 'timeout' => 45,
1065 );
1066
1067 $response = wp_remote_post($url, $args);
1068
1069 // Handle errors in the API request
1070 if (is_wp_error($response)) {
1071 //error_log('Error adding email to Loops: ' . $response->get_error_message());
1072 return;
1073 }
1074
1075 // Check for non-200 HTTP responses
1076 $response_code = wp_remote_retrieve_response_code($response);
1077 if ($response_code != 200) {
1078 $response_body = wp_remote_retrieve_body($response);
1079 //error_log('Loops API responded with code ' . $response_code . ': ' . $response_body);
1080 }
1081 }
1082
1083 private function mxchat_get_user_identifier() {
1084 return MxChat_User::mxchat_get_user_identifier();
1085 }
1086
1087 private function mxchat_generate_embedding($text, $api_key) {
1088 $endpoint = 'https://api.openai.com/v1/embeddings';
1089
1090 $body = wp_json_encode([
1091 'input' => $text,
1092 'model' => 'text-embedding-ada-002'
1093 ]);
1094
1095 $args = [
1096 'body' => $body,
1097 'headers' => [
1098 'Content-Type' => 'application/json',
1099 'Authorization' => 'Bearer ' . $api_key,
1100 ],
1101 'timeout' => 60,
1102 'redirection' => 5,
1103 'blocking' => true,
1104 'httpversion' => '1.0',
1105 'sslverify' => true,
1106 ];
1107
1108 $response = wp_remote_post($endpoint, $args);
1109
1110 if (is_wp_error($response)) {
1111 return null;
1112 }
1113
1114 $response_body = json_decode(wp_remote_retrieve_body($response), true);
1115
1116 if (isset($response_body['data'][0]['embedding']) && is_array($response_body['data'][0]['embedding'])) {
1117 return $response_body['data'][0]['embedding'];
1118 } else {
1119 return null;
1120 }
1121 }
1122
1123 private function mxchat_find_relevant_content($user_embedding) {
1124 global $wpdb;
1125 $system_prompt_table = $wpdb->prefix . 'mxchat_system_prompt_content';
1126
1127 // Define a cache key for embeddings
1128 $cache_key = 'mxchat_system_prompt_embeddings';
1129
1130 // Attempt to get the embeddings from the cache
1131 $embeddings = wp_cache_get($cache_key, 'mxchat_system_prompts');
1132
1133 if ($embeddings === false) {
1134 // Cache miss, query the database and cache the results
1135 $query = "SELECT id, embedding_vector FROM {$system_prompt_table}";
1136 $embeddings = $wpdb->get_results($query);
1137
1138 if ($embeddings === null || empty($embeddings)) {
1139 return null; // Return null to handle no embeddings gracefully
1140 }
1141
1142 // Cache the results if successful
1143 wp_cache_set($cache_key, $embeddings, 'mxchat_system_prompts', 3600); // Cache for 1 hour
1144 }
1145
1146 $most_relevant_id = null;
1147 $highest_similarity = -INF;
1148
1149 foreach ($embeddings as $embedding) {
1150 $database_embedding = $embedding->embedding_vector
1151 ? unserialize($embedding->embedding_vector, ['allowed_classes' => false])
1152 : null;
1153
1154 if (is_array($database_embedding) && is_array($user_embedding)) {
1155 $similarity = $this->mxchat_calculate_cosine_similarity($user_embedding, $database_embedding);
1156
1157 if ($similarity > $highest_similarity) {
1158 $highest_similarity = $similarity;
1159 $most_relevant_id = $embedding->id;
1160 }
1161 }
1162 }
1163
1164 if ($most_relevant_id !== null) {
1165 // Fetch content with product links
1166 return $this->fetch_content_with_product_links($most_relevant_id);
1167 }
1168
1169 return null; // Return null if no relevant content is found
1170 }
1171
1172 private function fetch_content_with_product_links($most_relevant_id) {
1173 global $wpdb;
1174 $system_prompt_table = $wpdb->prefix . 'mxchat_system_prompt_content';
1175
1176 // Fetch the article content and associated product URL
1177 $query = $wpdb->prepare("SELECT article_content, source_url FROM {$system_prompt_table} WHERE id = %d", $most_relevant_id);
1178 $result = $wpdb->get_row($query);
1179
1180 if ($result) {
1181 // Append the product link to the content if available
1182 $content = $result->article_content;
1183 if (!empty($result->source_url)) {
1184 $content .= "\n\nFor more details, check out this product: " . esc_url($result->source_url);
1185 }
1186 return $content;
1187 }
1188
1189 return null;
1190 }
1191
1192 private function mxchat_generate_response($relevant_content, $api_key, $xai_api_key, $claude_api_key, $conversation_history) {
1193 if (!$relevant_content) {
1194 return "I'm sorry, I couldn't find relevant information on that topic.";
1195 }
1196
1197 // Check the selected model
1198 $selected_model = isset($this->options['model']) ? $this->options['model'] : 'gpt-3.5-turbo';
1199
1200 // Call the appropriate function based on the selected model
1201 if (strpos($selected_model, 'claude') !== false) {
1202 return $this->mxchat_generate_response_claude($selected_model, $claude_api_key, $conversation_history, $relevant_content);
1203 } elseif ($selected_model === 'grok-beta') {
1204 return $this->mxchat_generate_response_xai($selected_model, $xai_api_key, $conversation_history, $relevant_content);
1205 } else {
1206 return $this->mxchat_generate_response_openai($selected_model, $api_key, $conversation_history, $relevant_content);
1207 }
1208 }
1209
1210 private function mxchat_generate_response_openai($selected_model, $api_key, $conversation_history, $relevant_content) {
1211 // Get system prompt instructions from options
1212 $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : '';
1213
1214 // Add system prompt to relevant content
1215 $content_with_instructions = $system_prompt_instructions . " " . $relevant_content;
1216
1217 // Prepend system instructions to the conversation history
1218 array_unshift($conversation_history, [
1219 'role' => 'system',
1220 'content' => "Here are your instructions: " . $content_with_instructions
1221 ]);
1222
1223 // Ensure consistency: Replace 'bot' role with 'assistant' in conversation history
1224 foreach ($conversation_history as &$message) {
1225 if ($message['role'] === 'bot') {
1226 $message['role'] = 'assistant';
1227 }
1228 }
1229
1230 // Build the request body
1231 $body = json_encode([
1232 'model' => $selected_model,
1233 'messages' => $conversation_history,
1234 'temperature' => 0.8,
1235 'stream' => false
1236 ]);
1237
1238 // Set up the API request
1239 $args = [
1240 'body' => $body,
1241 'headers' => [
1242 'Content-Type' => 'application/json',
1243 'Authorization' => 'Bearer ' . $api_key,
1244 ],
1245 'timeout' => 60,
1246 'redirection' => 5,
1247 'blocking' => true,
1248 'httpversion' => '1.0',
1249 'sslverify' => true,
1250 ];
1251
1252 // Make the API request
1253 $response = wp_remote_post('https://api.openai.com/v1/chat/completions', $args);
1254
1255 // Process the response
1256 if (is_wp_error($response)) {
1257 return "Sorry, there was an error processing your request.";
1258 }
1259
1260 $response_body = json_decode(wp_remote_retrieve_body($response), true);
1261
1262 if (isset($response_body['choices'][0]['message']['content'])) {
1263 return trim($response_body['choices'][0]['message']['content']);
1264 } else {
1265 return "Sorry, I couldn't process that request.";
1266 }
1267 }
1268
1269 private function mxchat_generate_response_xai($selected_model, $xai_api_key, $conversation_history, $relevant_content) {
1270 // Get system prompt instructions from options
1271 $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : '';
1272
1273 // Add system prompt to relevant content
1274 $content_with_instructions = $system_prompt_instructions . " " . $relevant_content;
1275
1276 // Prepend system instructions to the conversation history
1277 array_unshift($conversation_history, [
1278 'role' => 'system',
1279 'content' => "Here are your instructions: " . $content_with_instructions
1280 ]);
1281
1282 // Ensure consistency: Replace 'bot' role with 'assistant' in conversation history
1283 foreach ($conversation_history as &$message) {
1284 if ($message['role'] === 'bot') {
1285 $message['role'] = 'assistant';
1286 }
1287 }
1288
1289 // Build the request body
1290 $body = json_encode([
1291 'model' => $selected_model,
1292 'messages' => $conversation_history,
1293 'temperature' => 0.8,
1294 'stream' => false
1295 ]);
1296
1297 // Set up the API request
1298 $args = [
1299 'body' => $body,
1300 'headers' => [
1301 'Content-Type' => 'application/json',
1302 'Authorization' => 'Bearer ' . $xai_api_key,
1303 ],
1304 'timeout' => 60,
1305 'redirection' => 5,
1306 'blocking' => true,
1307 'httpversion' => '1.0',
1308 'sslverify' => true,
1309 ];
1310
1311 // Make the API request
1312 $response = wp_remote_post('https://api.x.ai/v1/chat/completions', $args);
1313
1314 // Process the response
1315 if (is_wp_error($response)) {
1316 return "Sorry, there was an error processing your request.";
1317 }
1318
1319 $response_body = json_decode(wp_remote_retrieve_body($response), true);
1320
1321 if (isset($response_body['choices'][0]['message']['content'])) {
1322 return trim($response_body['choices'][0]['message']['content']);
1323 } else {
1324 return "Sorry, I couldn't process that request.";
1325 }
1326 }
1327
1328 private function mxchat_generate_response_claude($selected_model, $claude_api_key, $conversation_history, $relevant_content) {
1329 // Get system prompt instructions from options for Claude's top-level system parameter
1330 $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : '';
1331
1332 // Ensure consistency: Replace 'bot' role with 'assistant' in conversation history
1333 foreach ($conversation_history as &$message) {
1334 if ($message['role'] === 'bot') {
1335 $message['role'] = 'assistant';
1336 }
1337 }
1338
1339 // Add relevant content as the latest user message in conversation history
1340 $conversation_history[] = [
1341 'role' => 'user',
1342 'content' => $relevant_content
1343 ];
1344
1345 // Build the request body with Claude's expected structure, using system instructions as a top-level parameter
1346 $body = json_encode([
1347 'model' => $selected_model,
1348 'max_tokens' => 1000,
1349 'temperature' => 0.8,
1350 'system' => $system_prompt_instructions, // Set the system prompt at the top level as required
1351 'messages' => $conversation_history
1352 ]);
1353
1354 // Set up the API request with the necessary headers
1355 $args = [
1356 'body' => $body,
1357 'headers' => [
1358 'Content-Type' => 'application/json',
1359 'x-api-key' => $claude_api_key,
1360 'anthropic-version' => '2023-06-01',
1361 ],
1362 'timeout' => 60,
1363 'redirection' => 5,
1364 'blocking' => true,
1365 'httpversion' => '1.0',
1366 'sslverify' => true,
1367 ];
1368
1369 // Make the API request
1370 $response = wp_remote_post('https://api.anthropic.com/v1/messages', $args);
1371
1372 /*
1373 // Check for errors and log the response for debugging
1374 if (is_wp_error($response)) {
1375 error_log("Claude API request error: " . print_r($response->get_error_message(), true));
1376 return "Sorry, there was an error processing your request.";
1377 }
1378 */
1379
1380
1381 // Decode the response and parse according to the expected Claude response structure
1382 $response_body = json_decode(wp_remote_retrieve_body($response), true);
1383 //error_log("Claude API response: " . print_r($response_body, true));
1384
1385 // Check if the response has the expected 'content' array with 'text' blocks
1386 if (isset($response_body['content'][0]['text'])) {
1387 return trim($response_body['content'][0]['text']);
1388 } else {
1389 return "Sorry, I couldn't process that request.";
1390 }
1391 }
1392
1393 public function mxchat_dismiss_pre_chat_message() {
1394 // Get and sanitize the user identifier
1395 $user_id = $this->mxchat_get_user_identifier();
1396 $user_id = sanitize_key($user_id);
1397
1398 // Set a transient to track that the user has dismissed the pre-chat message
1399 $transient_key = 'mxchat_pre_chat_message_dismissed_' . $user_id;
1400 set_transient($transient_key, true, DAY_IN_SECONDS);
1401
1402 wp_send_json_success();
1403 }
1404
1405 public function mxchat_check_pre_chat_message_status() {
1406 // Get and sanitize the user identifier
1407 $user_id = $this->mxchat_get_user_identifier();
1408 $user_id = sanitize_key($user_id);
1409
1410 // Check if the transient exists (i.e., if the message was dismissed)
1411 $transient_key = 'mxchat_pre_chat_message_dismissed_' . $user_id;
1412 $dismissed = get_transient($transient_key);
1413
1414 // Log the result to see if it's being set correctly
1415 //error_log("Check pre-chat message dismissed for $user_id: " . ($dismissed ? 'Yes' : 'No'));
1416
1417 if ($dismissed) {
1418 wp_send_json_success(['dismissed' => true]);
1419 } else {
1420 wp_send_json_success(['dismissed' => false]);
1421 }
1422
1423 wp_die();
1424 }
1425
1426 private function mxchat_calculate_cosine_similarity($vectorA, $vectorB) {
1427 if (!is_array($vectorA) || !is_array($vectorB) || empty($vectorA) || empty($vectorB)) {
1428 return 0;
1429 }
1430
1431 $dotProduct = array_sum(array_map(function ($a, $b) {
1432 return $a * $b;
1433 }, $vectorA, $vectorB));
1434 $normA = sqrt(array_sum(array_map(function ($a) {
1435 return $a * $a;
1436 }, $vectorA)));
1437 $normB = sqrt(array_sum(array_map(function ($b) {
1438 return $b * $b;
1439 }, $vectorB)));
1440
1441 if ($normA == 0 || $normB == 0) {
1442 return 0;
1443 }
1444
1445 return $dotProduct / ($normA * $normB);
1446 }
1447
1448 public function mxchat_enqueue_scripts_styles() {
1449 // Define version numbers for the styles and scripts
1450 $chat_style_version = '1.3'; // Replace with your actual version
1451 $chat_script_version = '1.3'; // Replace with your actual version
1452
1453 // Correct path to the script file
1454 wp_enqueue_script(
1455 'mxchat-chat-js', // Handle for the script
1456 plugin_dir_url(__FILE__) . '../js/chat-script.js', // Correct path using __FILE__
1457 array('jquery'), // Dependencies
1458 $chat_script_version, // Version for cache busting
1459 true // Load script in footer
1460 );
1461
1462 // Enqueue the CSS file similarly
1463 wp_enqueue_style(
1464 'mxchat-chat-css', // Handle for the style
1465 plugin_dir_url(__FILE__) . '../css/chat-style.css', // Correct path using __FILE__
1466 array(), // No dependencies
1467 $chat_style_version // Version for cache busting
1468 );
1469
1470 // Fetch options from the database
1471 $this->options = get_option('mxchat_options');
1472
1473 // Prepare settings to pass to JavaScript
1474 $style_settings = array(
1475 'ajax_url' => admin_url('admin-ajax.php'),
1476 'nonce' => wp_create_nonce('mxchat_chat_nonce'), // Nonce for security
1477 'rate_limit_message' => $this->options['rate_limit_message'] ?? 'Rate limit exceeded. Please try again later.',
1478 'appendWidgetToBody' => $this->options['append_to_body'] ?? 'off'
1479 );
1480
1481 // Localize the script with necessary data
1482 wp_localize_script('mxchat-chat-js', 'mxchatChat', $style_settings);
1483 }
1484
1485 public function mxchat_reset_rate_limits() {
1486 global $wpdb;
1487
1488 // Define a cache key pattern for rate limits
1489 $cache_key_pattern = 'mxchat_chat_limit_%';
1490
1491 // Retrieve all option names matching the pattern
1492 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery
1493 $option_names = $wpdb->get_col("SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE 'mxchat_chat_limit_%'");
1494
1495 // db call ok; no-cache ok
1496 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- db call ok
1497 $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE 'mxchat_chat_limit_%'");
1498
1499 // Clear the relevant cache entries
1500 foreach ($option_names as $option_name) {
1501 wp_cache_delete($option_name, 'options');
1502 }
1503
1504 // Optionally, clear a general cache if you have one
1505 wp_cache_delete('mxchat_all_chat_limits', 'options');
1506 }
1507
1508 private function mxchat_fetch_woocommerce_products() {
1509 // Ensure WooCommerce is active
1510 if (!class_exists('WooCommerce')) {
1511 return [];
1512 }
1513
1514 $args = array(
1515 'post_type' => 'product',
1516 'post_status' => 'publish',
1517 'posts_per_page' => -1,
1518 );
1519
1520 $products = get_posts($args);
1521 $product_data = [];
1522
1523 foreach ($products as $product) {
1524 $product_id = $product->ID;
1525 $product_obj = wc_get_product($product_id);
1526
1527 $product_data[] = array(
1528 'id' => $product_id,
1529 'name' => $product_obj->get_name(),
1530 'description' => $product_obj->get_description(),
1531 'short_description' => $product_obj->get_short_description(),
1532 'url' => get_permalink($product_id),
1533 'price' => $product_obj->get_regular_price(),
1534 'sale_price' => $product_obj->get_sale_price(),
1535 'stock_status' => $product_obj->get_stock_status(),
1536 'sku' => $product_obj->get_sku(),
1537 'in_stock' => $product_obj->is_in_stock(),
1538 'total_sales' => $product_obj->get_total_sales(),
1539 );
1540 }
1541
1542 return $product_data;
1543 }
1544
1545 }
1546 ?>
1547