PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 1.4.2
MxChat – AI Chatbot & Content Generation for WordPress v1.4.2
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.4.2, at includes/class-mxchat-integrator.php

1,558 lines 60.2 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 // Generate the user embedding
353 $user_embedding = $this->mxchat_generate_embedding( $message, $this->options['api_key'] );
354 if ( ! is_array( $user_embedding ) ) {
355 error_log( 'mxchat: Error generating embedding for the message.' );
356 wp_send_json_error( 'Error generating embedding for the message.' );
357 return false;
358 }
359
360 // Fetch all intents from the database
361 $table_name = $wpdb->prefix . 'mxchat_intents';
362 $intents = $wpdb->get_results( "SELECT * FROM $table_name" );
363
364 if ( empty( $intents ) ) {
365 error_log( 'mxchat: No intents found in the database.' );
366 return false; // No intents to process
367 }
368
369 $highest_similarity = -INF;
370 $matched_intent = null;
371
372 // Calculate similarity for each intent, using each intent's individual threshold if set
373 foreach ( $intents as $intent ) {
374 $intent_embedding_serialized = $intent->embedding_vector;
375
376 // Securely unserialize the embedding vector
377 $intent_embedding = $intent_embedding_serialized
378 ? unserialize( $intent_embedding_serialized, [ 'allowed_classes' => false ] )
379 : null;
380
381 if ( ! is_array( $intent_embedding ) ) {
382 error_log( "mxchat: Invalid embedding for intent '{$intent->intent_label}'. Skipping." );
383 continue; // Skip if the embedding is invalid
384 }
385
386 // Calculate cosine similarity between user message embedding and intent embedding
387 $similarity = $this->mxchat_calculate_cosine_similarity( $user_embedding, $intent_embedding );
388 error_log( "mxchat: Similarity for intent '{$intent->intent_label}': {$similarity}" );
389
390 // Determine the similarity threshold for this intent
391 $intent_threshold = isset( $intent->similarity_threshold ) ? $intent->similarity_threshold : 0.85; // Default to 0.85 if not set
392 error_log( "mxchat: Using threshold {$intent_threshold} for intent '{$intent->intent_label}'" );
393
394 // Only consider this intent as a potential match if it meets or exceeds the intent's threshold
395 if ( $similarity >= $intent_threshold && $similarity > $highest_similarity ) {
396 $highest_similarity = $similarity;
397 $matched_intent = $intent;
398 }
399 }
400
401 // Log the highest similarity and matched intent if found
402 if ( $matched_intent ) {
403 error_log( "mxchat: Highest similarity is {$highest_similarity} for intent '{$matched_intent->intent_label}'" );
404 } else {
405 error_log( 'mxchat: No intent matched.' );
406 }
407
408 // If a match is found above the threshold, call the associated callback
409 if ( $matched_intent ) {
410 error_log( "mxchat: Intent '{$matched_intent->intent_label}' matches the threshold. Invoking callback '{$matched_intent->callback_function}'" );
411
412 // Ensure the callback function exists before calling it
413 if ( method_exists( $this, $matched_intent->callback_function ) ) {
414 // Call the intent handler function
415 call_user_func( [ $this, $matched_intent->callback_function ], $message, $user_id, $session_id );
416 return true; // Indicate that an intent was matched and handled
417 } else {
418 error_log( "mxchat: Callback function '{$matched_intent->callback_function}' not found." );
419 }
420 } else {
421 error_log( 'mxchat: No intent matched above its respective threshold.' );
422 }
423
424 return false; // No intent was matched above the threshold
425 }
426
427
428
429 //verified good
430 public function mxchat_handle_order_history($message, $user_id, $session_id) {
431 // Check if WooCommerce is active
432 if (!class_exists('WooCommerce')) {
433 $this->fallbackResponse['text'] = "Order functionality is currently unavailable.";
434 return; // No need to proceed further
435 }
436
437 // Determine if the user is asking for the "last order" or "all orders"
438 $queryType = (stripos($message, 'last order') !== false || stripos($message, 'recent order') !== false) ? 'last' : 'all';
439
440 // Fetch the relevant order details
441 $orderDetails = MxChat_WooCommerce::mxchat_fetch_user_orders_details($queryType);
442
443 // Set the fallback response to order details, which will be used if needed
444 $this->fallbackResponse['text'] = $orderDetails ?: "No previous orders found.";
445 }
446
447
448 //verified good
449 public function mxchat_handle_product_inquiry( $message, $user_id, $session_id ) {
450 // Attempt to retrieve the product ID from the message
451 $product_id = MxChat_WooCommerce::mxchat_extract_product_id_from_message( $message );
452
453 // Check if there's a previously discussed product for follow-up queries
454 if ( ! $product_id ) {
455 $product_id = get_transient( 'mxchat_last_discussed_product_' . $user_id );
456 }
457
458 if ( $product_id && class_exists( 'WooCommerce' ) ) {
459 $product = wc_get_product( $product_id );
460 if ( $product ) {
461 // Escape and prepare product data
462 $product_name = esc_html( $product->get_name() );
463 $product_price = $product->get_price_html(); // Assuming this is safe HTML
464 $product_image_url = esc_url( wp_get_attachment_url( $product->get_image_id() ) );
465 $product_url = esc_url( get_permalink( $product_id ) );
466 $product_id_attr = esc_attr( $product_id );
467
468 // Generate the product card HTML using HEREDOC syntax
469 $product_card_html = <<<HTML
470 <div class="mxchat-product-card">
471 <a href="{$product_url}" target="_blank">
472 <img src="{$product_image_url}" alt="{$product_name}" class="mxchat-product-image" />
473 <h3 class="mxchat-product-name">{$product_name}</h3>
474 </a>
475 <div class="mxchat-product-price">{$product_price}</div>
476 <button class="mxchat-add-to-cart-button" data-product-id="{$product_id_attr}">Add to Cart</button>
477 </div>
478 HTML;
479
480 // Save the last discussed product
481 set_transient( 'mxchat_last_discussed_product_' . $user_id, $product_id, HOUR_IN_SECONDS );
482
483 // Save the product card HTML to be appended later
484 $this->productCardHtml = $product_card_html;
485
486 // Do not send a response here; allow execution to continue
487 return;
488 }
489 }
490
491 // If no product found, set a fallback response
492 $this->fallbackResponse = __( "I couldn't find details for that product. Could you specify the product name?", 'mxchat' );
493 return;
494 }
495
496 //verified good
497 public function mxchat_handle_email_capture($message, $user_id, $session_id) {
498 // Log the message safely
499 //error_log("Triggered email capture intent for message: " . sanitize_text_field($message));
500
501 // Initiate email capture flow
502 $response = esc_html($this->options['triggered_phrase_response'] ?? "Would you like to join our mailing list? Please provide your email below.");
503
504 set_transient('mxchat_email_capture_' . $user_id, true, 5 * MINUTE_IN_SECONDS);
505 $this->mxchat_save_chat_message($session_id, 'bot', $response);
506
507 // Respond to the user
508 wp_send_json(['message' => $response]);
509 wp_die();
510 }
511
512 //very good
513 public function mxchat_generate_image($message, $user_id, $session_id) {
514 // Prepare a prompt for DALL-E
515 $prompt = "Create an image of " . sanitize_text_field($message);
516
517 // Use the existing OpenAI API key
518 $openai_api_key = sanitize_text_field($this->options['api_key']);
519
520 // Call DALL-E to generate an image
521 $image_response = $this->mxchat_generate_dalle_image($prompt, $openai_api_key);
522
523 // Check if the response contains an image URL
524 if (isset($image_response['imageUrl'])) {
525 $image_url = esc_url_raw($image_response['imageUrl']);
526
527 // Construct the HTML with a CSS class instead of inline styles
528 $response_html = '<img src="' . esc_url($image_url) . '" alt="Generated Image" class="mxchat-generated-image" />';
529
530 $response_text = "Here is the image I generated:";
531 } else {
532 $response_text = "I'm sorry, but I couldn't generate an image based on your request.";
533 $response_html = '';
534 //error_log("DALL-E image generation error: " . esc_html($image_response['error'] ?? 'Unknown error.'));
535 }
536
537 // Save both text and HTML responses
538 $this->mxchat_save_chat_message($session_id, 'bot', $response_text . "\n" . $response_html);
539
540 // Prepare the response data
541 $response_data = [
542 'message' => $response_text,
543 'html' => $response_html,
544 'image_url' => $image_url ?? '',
545 ];
546
547 // Send the JSON response
548 header('Content-Type: application/json; charset=' . get_option('blog_charset'));
549 echo json_encode($response_data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
550 wp_die();
551 }
552 private function mxchat_generate_dalle_image($prompt, $api_key, $model = 'dall-e-3', $timeout = 60) {
553 $api_url = 'https://api.openai.com/v1/images/generations';
554 $body = json_encode([
555 'prompt' => sanitize_text_field($prompt),
556 'n' => 1,
557 'size' => '1024x1024',
558 'model' => sanitize_text_field($model),
559 ]);
560
561 $args = [
562 'body' => $body,
563 'headers' => [
564 'Content-Type' => 'application/json',
565 'Authorization' => 'Bearer ' . sanitize_text_field($api_key),
566 ],
567 'method' => 'POST',
568 'timeout' => absint($timeout),
569 ];
570
571 $response = wp_remote_post($api_url, $args);
572
573 if (is_wp_error($response)) {
574 //error_log("DALL-E request failed: " . $response->get_error_message());
575 return ['error' => "Error generating image: " . $response->get_error_message()];
576 }
577
578 $response_body = json_decode(wp_remote_retrieve_body($response), true);
579
580 if (isset($response_body['data'][0]['url'])) {
581 return ['imageUrl' => esc_url_raw($response_body['data'][0]['url'])];
582 } else {
583 //error_log("DALL-E response error: " . wp_remote_retrieve_body($response));
584 return ['error' => "Failed to generate image."];
585 }
586 }
587
588 //very good
589 public function mxchat_handle_search_request($message, $user_id, $session_id) {
590 // Sanitize user input
591 $search_query = preg_replace('/^search the web for\s+/i', '', $message);
592 $search_query = preg_replace('/^show me the latest news about\s+/i', '', $message);
593 $search_query = preg_replace('/^what\'?s the news in\s+/i', '', $message);
594 $search_query = preg_replace('/^news about\s+/i', '', $message);
595 $search_query = trim(sanitize_text_field($search_query));
596
597 if (empty($search_query)) {
598 $this->fallbackResponse = [
599 'text' => __("Please provide a valid search query.", 'mxchat'),
600 ];
601 return;
602 }
603
604 // Check if the query is likely related to news
605 $is_news_search = preg_match("/\bnews\b/i", $message);
606
607 // Retrieve settings
608 $options = get_option('mxchat_options');
609
610 $api_key = isset($options['brave_api_key']) ? sanitize_text_field($options['brave_api_key']) : '';
611 $news_count = isset($options['brave_news_count']) ? intval($options['brave_news_count']) : 3;
612 $country = isset($options['brave_country']) ? sanitize_text_field($options['brave_country']) : 'us';
613 $language = isset($options['brave_language']) ? sanitize_text_field($options['brave_language']) : 'en';
614
615 if (empty($api_key)) {
616
617 $this->fallbackResponse = [
618 'text' => __("API key is not configured. Please set it in the Brave Search Settings.", 'mxchat'),
619 ];
620 return;
621 }
622
623 // Set API endpoint and parameters based on search type
624 $api_url = $is_news_search
625 ? 'https://api.search.brave.com/res/v1/news/search'
626 : 'https://api.search.brave.com/res/v1/web/search';
627
628 $query_args = [
629 'q' => urlencode($search_query),
630 ];
631
632 if ($is_news_search) {
633 $query_args['count'] = $news_count;
634 $query_args['country'] = $country;
635 $query_args['search_lang'] = $language;
636 }
637
638 $api_url = add_query_arg($query_args, $api_url);
639
640 // Implement caching
641 $transient_key = 'mxchat_search_' . md5($api_url);
642 $body = get_transient($transient_key);
643
644 if (false === $body) {
645 $args = [
646 'headers' => [
647 'Accept' => 'application/json',
648 'Accept-Encoding' => 'gzip',
649 'Authorization' => 'Bearer ' . $api_key,
650 'x-subscription-token' => $api_key,
651 ],
652 'timeout' => 10,
653 ];
654
655 $response = wp_remote_get($api_url, $args);
656
657 if (is_wp_error($response)) {
658
659 $this->fallbackResponse = [
660 'text' => __("I'm sorry, I couldn't retrieve any information based on your request.", 'mxchat'),
661 ];
662 return;
663 }
664
665 $body = json_decode(wp_remote_retrieve_body($response), true);
666 set_transient($transient_key, $body, HOUR_IN_SECONDS);
667 }
668
669 // Process the API response and build a more informative summary
670 $text_summary = "";
671
672 if ($is_news_search && isset($body['results']) && is_array($body['results']) && count($body['results']) > 0) {
673 $text_summary = "Here are some recent news articles:\n\n";
674
675 foreach ($body['results'] as $news) {
676 $title = isset($news['title']) ? esc_html($news['title']) : __('No Title', 'mxchat');
677 $url = isset($news['url']) ? esc_url($news['url']) : '#';
678 $description = isset($news['description']) ? esc_html(strip_tags($news['description'])) : __('No description available.', 'mxchat');
679 $age = isset($news['age']) ? esc_html($news['age']) : '';
680 $hostname = isset($news['meta_url']['hostname']) ? esc_html($news['meta_url']['hostname']) : '';
681 $thumbnail = isset($news['thumbnail']['src']) ? esc_url($news['thumbnail']['src']) : '';
682
683 // Build text-only news item summary
684 $text_summary .= "Title: {$title}\nURL: {$url}\nDescription: {$description}\nPublished: {$age}\n";
685 if (!empty($hostname)) {
686 $text_summary .= "Source: {$hostname}\n";
687 }
688 if (!empty($thumbnail)) {
689 $text_summary .= "Thumbnail: ![Image]({$thumbnail})\n";
690 }
691 if (!empty($news['extra_snippets'])) {
692 $extra_snippet_text = implode(" ", $news['extra_snippets']);
693 $text_summary .= "Additional Info: {$extra_snippet_text}\n";
694 }
695 $text_summary .= "\n"; // Separate entries
696 }
697 } elseif (!$is_news_search && isset($body['web']['results']) && is_array($body['web']['results']) && count($body['web']['results']) > 0) {
698 $text_summary = "Here are some relevant articles based on your query:\n\n";
699
700 foreach ($body['web']['results'] as $result) {
701 $title = isset($result['title']) ? esc_html($result['title']) : __('No Title', 'mxchat');
702 $url = isset($result['url']) ? esc_url($result['url']) : '#';
703 $description = isset($result['description']) ? esc_html(strip_tags($result['description'])) : __('No description available.', 'mxchat');
704 $hostname = isset($result['meta_url']['hostname']) ? esc_html($result['meta_url']['hostname']) : '';
705 $thumbnail = isset($result['thumbnail']['src']) ? esc_url($result['thumbnail']['src']) : '';
706
707 // Build text-only web item summary
708 $text_summary .= "Title: {$title}\nURL: {$url}\nDescription: {$description}\n";
709 if (!empty($hostname)) {
710 $text_summary .= "Source: {$hostname}\n";
711 }
712 if (!empty($thumbnail)) {
713 $text_summary .= "Thumbnail: ![Image]({$thumbnail})\n";
714 }
715 if (!empty($result['extra_snippets'])) {
716 $extra_snippet_text = implode(" ", $result['extra_snippets']);
717 $text_summary .= "Additional Info: {$extra_snippet_text}\n";
718 }
719 $text_summary .= "\n"; // Separate entries
720 }
721 } else {
722
723 $this->fallbackResponse = [
724 'text' => __("I'm sorry, I couldn't retrieve any information based on your request.", 'mxchat'),
725 ];
726 return;
727 }
728
729 $this->fallbackResponse = [
730 'text' => $text_summary,
731 ];
732
733
734 }
735
736 //very good
737 public function mxchat_handle_image_search_request($message, $user_id, $session_id) {
738
739 // Step 1: Interpret the search query for better results
740 $refined_search_query = $this->mxchat_interpret_search_query($message);
741
742
743 // If no query was interpreted, return a fallback message
744 if (empty($refined_search_query)) {
745 $this->fallbackResponse = [
746 'text' => __("I'm sorry, I couldn't interpret your search query. Please specify what you'd like to see images of.", 'mxchat'),
747 'html' => "",
748 ];
749 return;
750 }
751
752 // Brave API URL
753 $api_url = 'https://api.search.brave.com/res/v1/images/search';
754
755 // Retrieve Brave API settings
756 $options = get_option('mxchat_options');
757 $api_key = isset($options['brave_api_key']) ? sanitize_text_field($options['brave_api_key']) : '';
758
759 if (empty($api_key)) {
760 /*
761 if (defined('WP_DEBUG') && WP_DEBUG) {
762 error_log("Brave API key is missing.");
763 }
764 */
765
766 $this->fallbackResponse = [
767 'text' => __("API key is not configured. Please set it in the Brave Search Settings.", 'mxchat'),
768 'html' => "",
769 ];
770 return;
771 }
772
773 $image_count = isset($options['brave_image_count']) ? intval($options['brave_image_count']) : 4;
774 $safe_search = isset($options['brave_safe_search']) ? sanitize_text_field($options['brave_safe_search']) : 'strict';
775
776 // Append query parameters based on settings
777 $api_url = add_query_arg([
778 'q' => rawurlencode($refined_search_query),
779 'count' => $image_count,
780 'safesearch' => $safe_search,
781 ], $api_url);
782
783 /*
784 // Log the final API URL for the search
785 if (defined('WP_DEBUG') && WP_DEBUG) {
786 error_log("Final API URL for Brave Image Search: " . esc_url_raw($api_url));
787 }
788 */
789
790
791 // Implement caching
792 $transient_key = 'mxchat_image_search_' . md5($refined_search_query);
793 $body = get_transient($transient_key);
794
795 if (false === $body) {
796 $args = [
797 'headers' => [
798 'Accept' => 'application/json',
799 'Accept-Encoding' => 'gzip',
800 'X-Subscription-Token' => $api_key,
801 ],
802 'timeout' => 10,
803 ];
804
805 $response = wp_remote_get($api_url, $args);
806
807 if (is_wp_error($response)) {
808 /*
809 if (defined('WP_DEBUG') && WP_DEBUG) {
810 error_log("Brave Image API request failed: " . $response->get_error_message());
811 }
812 */
813
814 $this->fallbackResponse = [
815 'text' => __("I'm sorry, I couldn't retrieve any images based on your request.", 'mxchat'),
816 'html' => "",
817 ];
818 return;
819 }
820
821 $body = json_decode(wp_remote_retrieve_body($response), true);
822 set_transient($transient_key, $body, HOUR_IN_SECONDS);
823 }
824
825 // Process the API response
826 if (isset($body['results']) && is_array($body['results']) && count($body['results']) > 0) {
827 $html_output = '<div class="mxchat-image-gallery">';
828
829 foreach ($body['results'] as $image) {
830 $image_url = isset($image['url']) ? esc_url($image['url']) : '';
831 $thumbnail_url = isset($image['thumbnail']['src']) ? esc_url($image['thumbnail']['src']) : '';
832 $title = isset($image['title']) ? esc_html($image['title']) : __('Image', 'mxchat');
833
834 if ($image_url && $thumbnail_url) {
835 $html_output .= '<div class="mxchat-image-item">';
836 $html_output .= '<strong class="mxchat-image-title">' . $title . '</strong>';
837 $html_output .= '<a href="' . $image_url . '" target="_blank" rel="noopener noreferrer" class="mxchat-image-link">';
838 $html_output .= '<img src="' . $thumbnail_url . '" alt="' . $title . '" class="mxchat-image-thumbnail">';
839 $html_output .= '</a></div>';
840 }
841 }
842
843 $html_output .= '</div>';
844
845 $this->fallbackResponse = [
846 'text' => "",
847 'html' => $html_output,
848 ];
849
850 // Save response in chat history
851 $this->mxchat_save_chat_message($session_id, 'bot', $html_output);
852
853 } else {
854 /*
855 if (defined('WP_DEBUG') && WP_DEBUG) {
856 error_log("Brave Image API response did not contain expected data structure or was empty: " . print_r($body, true));
857 }
858 */
859
860 $this->fallbackResponse = [
861 'text' => __("I'm sorry, I couldn't retrieve any images based on your request.", 'mxchat'),
862 'html' => "",
863 ];
864 }
865 }
866 public function mxchat_interpret_search_query($user_query) {
867 $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.";
868
869 // Retrieve OpenAI API key using 'api_key' as the option key
870 $api_key = isset($this->options['api_key']) ? sanitize_text_field($this->options['api_key']) : sanitize_text_field(get_option('mxchat_options')['api_key']);
871
872 /*
873 // Log the API key check, without exposing the key
874 if (defined('WP_DEBUG') && WP_DEBUG) {
875 error_log("Retrieved OpenAI API Key: " . ($api_key ? "Present" : "Missing"));
876 }
877 */
878
879
880 if (empty($api_key)) {
881 //error_log("OpenAI API key is missing.");
882 return sanitize_text_field($user_query); // Default to the original query if API key is missing
883 }
884
885 $url = 'https://api.openai.com/v1/chat/completions';
886 $args = [
887 'headers' => [
888 'Authorization' => 'Bearer ' . $api_key,
889 'Content-Type' => 'application/json',
890 ],
891 'body' => wp_json_encode([
892 'model' => 'gpt-3.5-turbo',
893 'messages' => [
894 ['role' => 'system', 'content' => $system_prompt],
895 ['role' => 'user', 'content' => sanitize_text_field($user_query)],
896 ],
897 'temperature' => 0.2,
898 'max_tokens' => 20,
899 ]),
900 'method' => 'POST',
901 ];
902
903 $response = wp_remote_post($url, $args);
904
905 if (is_wp_error($response)) {
906 //error_log("OpenAI request failed: " . $response->get_error_message());
907 return sanitize_text_field($user_query); // Fallback to the original query if there's an error
908 }
909
910 $body = json_decode(wp_remote_retrieve_body($response), true);
911
912 // Check for a valid response and sanitize output
913 if (isset($body['choices'][0]['message']['content'])) {
914 $interpreted_query = sanitize_text_field(trim($body['choices'][0]['message']['content']));
915
916 /*
917 // Log the interpreted query for debugging
918 if (defined('WP_DEBUG') && WP_DEBUG) {
919 error_log("Interpreted search query: " . $interpreted_query);
920 }
921 */
922
923 return $interpreted_query;
924 } else {
925 //error_log("Unexpected API response format: " . print_r($body, true));
926 return sanitize_text_field($user_query);
927 }
928 }
929
930 //very good
931 public function mxchat_handle_add_to_cart_intent($message, $user_id, $session_id) {
932 // Check if WooCommerce is active before proceeding
933 if (!class_exists('WooCommerce')) {
934 // error_log("WooCommerce is not active. Add-to-cart intent cannot be processed.");
935 return false;
936 }
937
938 // Sanitize user ID for transient key
939 $sanitized_user_id = sanitize_key($user_id);
940
941 // Retrieve the last discussed product ID from the transient
942 $last_product_id = get_transient('mxchat_last_discussed_product_' . $sanitized_user_id);
943 if ($last_product_id) {
944 // Attempt to add the product to the WooCommerce cart
945 $added = WC()->cart->add_to_cart($last_product_id);
946 $product = wc_get_product($last_product_id);
947
948 if ($added && $product) {
949 // Escape product name for safety
950 $product_name = esc_html($product->get_name());
951
952 // Success response if the product is added to the cart
953 $response = "The product '{$product_name}' has been added to your cart. To proceed to checkout, please type 'checkout'.";
954 set_transient('mxchat_checkout_prompt_' . $sanitized_user_id, true, 5 * MINUTE_IN_SECONDS);
955
956 // Save the response in chat history and send a JSON response
957 $this->mxchat_save_chat_message($session_id, 'bot', $response);
958 wp_send_json(['message' => $response]);
959 wp_die();
960 } else {
961 // Failure response if the product could not be added
962 $response = "Sorry, I couldn't add the product to your cart. Please try again.";
963 $this->mxchat_save_chat_message($session_id, 'bot', $response);
964 wp_send_json(['message' => $response]);
965 wp_die();
966 }
967 } else {
968 // Prompt the user to specify the product name if no product was found
969 $response = "I couldn't find the product to add. Please mention the product name again.";
970 $this->mxchat_save_chat_message($session_id, 'bot', $response);
971 wp_send_json(['message' => $response]);
972 wp_die();
973 }
974 }
975 public function mxchat_handle_checkout_intent($message, $user_id, $session_id) {
976 // Ensure WooCommerce is available and perform checkout actions
977 if (class_exists('WooCommerce')) {
978 // Sanitize user ID for transient key
979 $sanitized_user_id = sanitize_key($user_id);
980
981 $checkout_prompt = get_transient('mxchat_checkout_prompt_' . $sanitized_user_id);
982
983 // Check if there is an active checkout prompt and if the cart has items
984 if ($checkout_prompt && WC()->cart->get_cart_contents_count() > 0) {
985 // Get and escape the checkout URL
986 $checkout_url = wc_get_checkout_url();
987 $checkout_url = esc_url_raw($checkout_url);
988
989 $response = "Great! Redirecting you to the checkout page...";
990
991 // Clear the checkout prompt transient to avoid duplicate responses
992 delete_transient('mxchat_checkout_prompt_' . $sanitized_user_id);
993
994 // Save and send the response with the checkout URL for redirection
995 $this->mxchat_save_chat_message($session_id, 'bot', $response);
996 wp_send_json(['message' => $response, 'redirect_url' => $checkout_url]);
997 wp_die();
998 } else {
999 // If no active checkout prompt or empty cart, prompt user to add items first
1000 $response = "It seems like there is no active checkout prompt or no items in your cart. Please add a product to the cart first.";
1001 $this->mxchat_save_chat_message($session_id, 'bot', $response);
1002 wp_send_json(['message' => $response]);
1003 wp_die();
1004 }
1005 } else {
1006 // WooCommerce is not available, send an error message
1007 $response = "WooCommerce is not active on this site, so checkout is not possible.";
1008 $this->mxchat_save_chat_message($session_id, 'bot', $response);
1009 wp_send_json(['message' => $response]);
1010 wp_die();
1011 }
1012 }
1013
1014 //very good
1015 public static function mxchat_extract_product_id_from_message($message) {
1016 // Sanitize the message input
1017 $message = sanitize_text_field($message);
1018
1019 // Convert Markdown links to plain URLs
1020 $message = preg_replace('/\[(.*?)\]\((.*?)\)/', '$2', $message);
1021
1022 // Match all URLs in the message
1023 preg_match_all('/https?:\/\/[^\s]+/', $message, $matches);
1024
1025 if (!empty($matches[0])) {
1026 foreach ($matches[0] as $url) {
1027 // Sanitize the URL
1028 $url = esc_url_raw($url);
1029
1030 // Try to get the post ID from the URL
1031 $post_id = url_to_postid($url);
1032
1033 if ($post_id) {
1034 // Check if the post type is 'product'
1035 if (get_post_type($post_id) === 'product') {
1036 return $post_id;
1037 }
1038 }
1039 }
1040 }
1041 return false;
1042 }
1043
1044
1045 //very good
1046 private function add_email_to_loops($email) {
1047 // Sanitize the email
1048 $email = sanitize_email($email);
1049
1050 // Retrieve and sanitize options
1051 $api_key = isset($this->options['loops_api_key']) ? sanitize_text_field($this->options['loops_api_key']) : '';
1052 $mailing_list_id = isset($this->options['loops_mailing_list']) ? sanitize_text_field($this->options['loops_mailing_list']) : '';
1053
1054 // Check for missing API key or mailing list ID
1055 if (empty($api_key) || empty($mailing_list_id)) {
1056 //error_log('Loops API key or mailing list ID is missing.');
1057 return;
1058 }
1059
1060 $data = array(
1061 'email' => $email,
1062 'subscribed' => true,
1063 'source' => 'MxChat AI Chatbot',
1064 'mailingLists' => array($mailing_list_id => true),
1065 );
1066
1067 $url = 'https://app.loops.so/api/v1/contacts/create';
1068 $args = array(
1069 'body' => wp_json_encode($data),
1070 'headers' => array(
1071 'Authorization' => 'Bearer ' . $api_key,
1072 'Content-Type' => 'application/json',
1073 ),
1074 'method' => 'POST',
1075 'timeout' => 45,
1076 );
1077
1078 $response = wp_remote_post($url, $args);
1079
1080 // Handle errors in the API request
1081 if (is_wp_error($response)) {
1082 //error_log('Error adding email to Loops: ' . $response->get_error_message());
1083 return;
1084 }
1085
1086 // Check for non-200 HTTP responses
1087 $response_code = wp_remote_retrieve_response_code($response);
1088 if ($response_code != 200) {
1089 $response_body = wp_remote_retrieve_body($response);
1090 //error_log('Loops API responded with code ' . $response_code . ': ' . $response_body);
1091 }
1092 }
1093
1094 private function mxchat_get_user_identifier() {
1095 return MxChat_User::mxchat_get_user_identifier();
1096 }
1097
1098 private function mxchat_generate_embedding($text, $api_key) {
1099 $endpoint = 'https://api.openai.com/v1/embeddings';
1100
1101 $body = wp_json_encode([
1102 'input' => $text,
1103 'model' => 'text-embedding-ada-002'
1104 ]);
1105
1106 $args = [
1107 'body' => $body,
1108 'headers' => [
1109 'Content-Type' => 'application/json',
1110 'Authorization' => 'Bearer ' . $api_key,
1111 ],
1112 'timeout' => 60,
1113 'redirection' => 5,
1114 'blocking' => true,
1115 'httpversion' => '1.0',
1116 'sslverify' => true,
1117 ];
1118
1119 $response = wp_remote_post($endpoint, $args);
1120
1121 if (is_wp_error($response)) {
1122 return null;
1123 }
1124
1125 $response_body = json_decode(wp_remote_retrieve_body($response), true);
1126
1127 if (isset($response_body['data'][0]['embedding']) && is_array($response_body['data'][0]['embedding'])) {
1128 return $response_body['data'][0]['embedding'];
1129 } else {
1130 return null;
1131 }
1132 }
1133
1134 private function mxchat_find_relevant_content($user_embedding) {
1135 global $wpdb;
1136 $system_prompt_table = $wpdb->prefix . 'mxchat_system_prompt_content';
1137
1138 // Define a cache key for embeddings
1139 $cache_key = 'mxchat_system_prompt_embeddings';
1140
1141 // Attempt to get the embeddings from the cache
1142 $embeddings = wp_cache_get($cache_key, 'mxchat_system_prompts');
1143
1144 if ($embeddings === false) {
1145 // Cache miss, query the database and cache the results
1146 $query = "SELECT id, embedding_vector FROM {$system_prompt_table}";
1147 $embeddings = $wpdb->get_results($query);
1148
1149 if ($embeddings === null || empty($embeddings)) {
1150 return null; // Return null to handle no embeddings gracefully
1151 }
1152
1153 // Cache the results if successful
1154 wp_cache_set($cache_key, $embeddings, 'mxchat_system_prompts', 3600); // Cache for 1 hour
1155 }
1156
1157 $most_relevant_id = null;
1158 $highest_similarity = -INF;
1159
1160 foreach ($embeddings as $embedding) {
1161 $database_embedding = $embedding->embedding_vector
1162 ? unserialize($embedding->embedding_vector, ['allowed_classes' => false])
1163 : null;
1164
1165 if (is_array($database_embedding) && is_array($user_embedding)) {
1166 $similarity = $this->mxchat_calculate_cosine_similarity($user_embedding, $database_embedding);
1167
1168 if ($similarity > $highest_similarity) {
1169 $highest_similarity = $similarity;
1170 $most_relevant_id = $embedding->id;
1171 }
1172 }
1173 }
1174
1175 if ($most_relevant_id !== null) {
1176 // Fetch content with product links
1177 return $this->fetch_content_with_product_links($most_relevant_id);
1178 }
1179
1180 return null; // Return null if no relevant content is found
1181 }
1182
1183 private function fetch_content_with_product_links($most_relevant_id) {
1184 global $wpdb;
1185 $system_prompt_table = $wpdb->prefix . 'mxchat_system_prompt_content';
1186
1187 // Fetch the article content and associated product URL
1188 $query = $wpdb->prepare("SELECT article_content, source_url FROM {$system_prompt_table} WHERE id = %d", $most_relevant_id);
1189 $result = $wpdb->get_row($query);
1190
1191 if ($result) {
1192 // Append the product link to the content if available
1193 $content = $result->article_content;
1194 if (!empty($result->source_url)) {
1195 $content .= "\n\nFor more details, check out this product: " . esc_url($result->source_url);
1196 }
1197 return $content;
1198 }
1199
1200 return null;
1201 }
1202
1203 private function mxchat_generate_response($relevant_content, $api_key, $xai_api_key, $claude_api_key, $conversation_history) {
1204 if (!$relevant_content) {
1205 return "I'm sorry, I couldn't find relevant information on that topic.";
1206 }
1207
1208 // Check the selected model
1209 $selected_model = isset($this->options['model']) ? $this->options['model'] : 'gpt-3.5-turbo';
1210
1211 // Call the appropriate function based on the selected model
1212 if (strpos($selected_model, 'claude') !== false) {
1213 return $this->mxchat_generate_response_claude($selected_model, $claude_api_key, $conversation_history, $relevant_content);
1214 } elseif ($selected_model === 'grok-beta') {
1215 return $this->mxchat_generate_response_xai($selected_model, $xai_api_key, $conversation_history, $relevant_content);
1216 } else {
1217 return $this->mxchat_generate_response_openai($selected_model, $api_key, $conversation_history, $relevant_content);
1218 }
1219 }
1220
1221 private function mxchat_generate_response_openai($selected_model, $api_key, $conversation_history, $relevant_content) {
1222 // Get system prompt instructions from options
1223 $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : '';
1224
1225 // Add system prompt to relevant content
1226 $content_with_instructions = $system_prompt_instructions . " " . $relevant_content;
1227
1228 // Prepend system instructions to the conversation history
1229 array_unshift($conversation_history, [
1230 'role' => 'system',
1231 'content' => "Here are your instructions: " . $content_with_instructions
1232 ]);
1233
1234 // Ensure consistency: Replace 'bot' role with 'assistant' in conversation history
1235 foreach ($conversation_history as &$message) {
1236 if ($message['role'] === 'bot') {
1237 $message['role'] = 'assistant';
1238 }
1239 }
1240
1241 // Build the request body
1242 $body = json_encode([
1243 'model' => $selected_model,
1244 'messages' => $conversation_history,
1245 'temperature' => 0.8,
1246 'stream' => false
1247 ]);
1248
1249 // Set up the API request
1250 $args = [
1251 'body' => $body,
1252 'headers' => [
1253 'Content-Type' => 'application/json',
1254 'Authorization' => 'Bearer ' . $api_key,
1255 ],
1256 'timeout' => 60,
1257 'redirection' => 5,
1258 'blocking' => true,
1259 'httpversion' => '1.0',
1260 'sslverify' => true,
1261 ];
1262
1263 // Make the API request
1264 $response = wp_remote_post('https://api.openai.com/v1/chat/completions', $args);
1265
1266 // Process the response
1267 if (is_wp_error($response)) {
1268 return "Sorry, there was an error processing your request.";
1269 }
1270
1271 $response_body = json_decode(wp_remote_retrieve_body($response), true);
1272
1273 if (isset($response_body['choices'][0]['message']['content'])) {
1274 return trim($response_body['choices'][0]['message']['content']);
1275 } else {
1276 return "Sorry, I couldn't process that request.";
1277 }
1278 }
1279
1280 private function mxchat_generate_response_xai($selected_model, $xai_api_key, $conversation_history, $relevant_content) {
1281 // Get system prompt instructions from options
1282 $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : '';
1283
1284 // Add system prompt to relevant content
1285 $content_with_instructions = $system_prompt_instructions . " " . $relevant_content;
1286
1287 // Prepend system instructions to the conversation history
1288 array_unshift($conversation_history, [
1289 'role' => 'system',
1290 'content' => "Here are your instructions: " . $content_with_instructions
1291 ]);
1292
1293 // Ensure consistency: Replace 'bot' role with 'assistant' in conversation history
1294 foreach ($conversation_history as &$message) {
1295 if ($message['role'] === 'bot') {
1296 $message['role'] = 'assistant';
1297 }
1298 }
1299
1300 // Build the request body
1301 $body = json_encode([
1302 'model' => $selected_model,
1303 'messages' => $conversation_history,
1304 'temperature' => 0.8,
1305 'stream' => false
1306 ]);
1307
1308 // Set up the API request
1309 $args = [
1310 'body' => $body,
1311 'headers' => [
1312 'Content-Type' => 'application/json',
1313 'Authorization' => 'Bearer ' . $xai_api_key,
1314 ],
1315 'timeout' => 60,
1316 'redirection' => 5,
1317 'blocking' => true,
1318 'httpversion' => '1.0',
1319 'sslverify' => true,
1320 ];
1321
1322 // Make the API request
1323 $response = wp_remote_post('https://api.x.ai/v1/chat/completions', $args);
1324
1325 // Process the response
1326 if (is_wp_error($response)) {
1327 return "Sorry, there was an error processing your request.";
1328 }
1329
1330 $response_body = json_decode(wp_remote_retrieve_body($response), true);
1331
1332 if (isset($response_body['choices'][0]['message']['content'])) {
1333 return trim($response_body['choices'][0]['message']['content']);
1334 } else {
1335 return "Sorry, I couldn't process that request.";
1336 }
1337 }
1338
1339 private function mxchat_generate_response_claude($selected_model, $claude_api_key, $conversation_history, $relevant_content) {
1340 // Get system prompt instructions from options for Claude's top-level system parameter
1341 $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : '';
1342
1343 // Ensure consistency: Replace 'bot' role with 'assistant' in conversation history
1344 foreach ($conversation_history as &$message) {
1345 if ($message['role'] === 'bot') {
1346 $message['role'] = 'assistant';
1347 }
1348 }
1349
1350 // Add relevant content as the latest user message in conversation history
1351 $conversation_history[] = [
1352 'role' => 'user',
1353 'content' => $relevant_content
1354 ];
1355
1356 // Build the request body with Claude's expected structure, using system instructions as a top-level parameter
1357 $body = json_encode([
1358 'model' => $selected_model,
1359 'max_tokens' => 1000,
1360 'temperature' => 0.8,
1361 'system' => $system_prompt_instructions, // Set the system prompt at the top level as required
1362 'messages' => $conversation_history
1363 ]);
1364
1365 // Set up the API request with the necessary headers
1366 $args = [
1367 'body' => $body,
1368 'headers' => [
1369 'Content-Type' => 'application/json',
1370 'x-api-key' => $claude_api_key,
1371 'anthropic-version' => '2023-06-01',
1372 ],
1373 'timeout' => 60,
1374 'redirection' => 5,
1375 'blocking' => true,
1376 'httpversion' => '1.0',
1377 'sslverify' => true,
1378 ];
1379
1380 // Make the API request
1381 $response = wp_remote_post('https://api.anthropic.com/v1/messages', $args);
1382
1383 /*
1384 // Check for errors and log the response for debugging
1385 if (is_wp_error($response)) {
1386 error_log("Claude API request error: " . print_r($response->get_error_message(), true));
1387 return "Sorry, there was an error processing your request.";
1388 }
1389 */
1390
1391
1392 // Decode the response and parse according to the expected Claude response structure
1393 $response_body = json_decode(wp_remote_retrieve_body($response), true);
1394 //error_log("Claude API response: " . print_r($response_body, true));
1395
1396 // Check if the response has the expected 'content' array with 'text' blocks
1397 if (isset($response_body['content'][0]['text'])) {
1398 return trim($response_body['content'][0]['text']);
1399 } else {
1400 return "Sorry, I couldn't process that request.";
1401 }
1402 }
1403
1404 public function mxchat_dismiss_pre_chat_message() {
1405 // Get and sanitize the user identifier
1406 $user_id = $this->mxchat_get_user_identifier();
1407 $user_id = sanitize_key($user_id);
1408
1409 // Set a transient to track that the user has dismissed the pre-chat message
1410 $transient_key = 'mxchat_pre_chat_message_dismissed_' . $user_id;
1411 set_transient($transient_key, true, DAY_IN_SECONDS);
1412
1413 wp_send_json_success();
1414 }
1415
1416 public function mxchat_check_pre_chat_message_status() {
1417 // Get and sanitize the user identifier
1418 $user_id = $this->mxchat_get_user_identifier();
1419 $user_id = sanitize_key($user_id);
1420
1421 // Check if the transient exists (i.e., if the message was dismissed)
1422 $transient_key = 'mxchat_pre_chat_message_dismissed_' . $user_id;
1423 $dismissed = get_transient($transient_key);
1424
1425 // Log the result to see if it's being set correctly
1426 //error_log("Check pre-chat message dismissed for $user_id: " . ($dismissed ? 'Yes' : 'No'));
1427
1428 if ($dismissed) {
1429 wp_send_json_success(['dismissed' => true]);
1430 } else {
1431 wp_send_json_success(['dismissed' => false]);
1432 }
1433
1434 wp_die();
1435 }
1436
1437 private function mxchat_calculate_cosine_similarity($vectorA, $vectorB) {
1438 if (!is_array($vectorA) || !is_array($vectorB) || empty($vectorA) || empty($vectorB)) {
1439 return 0;
1440 }
1441
1442 $dotProduct = array_sum(array_map(function ($a, $b) {
1443 return $a * $b;
1444 }, $vectorA, $vectorB));
1445 $normA = sqrt(array_sum(array_map(function ($a) {
1446 return $a * $a;
1447 }, $vectorA)));
1448 $normB = sqrt(array_sum(array_map(function ($b) {
1449 return $b * $b;
1450 }, $vectorB)));
1451
1452 if ($normA == 0 || $normB == 0) {
1453 return 0;
1454 }
1455
1456 return $dotProduct / ($normA * $normB);
1457 }
1458
1459 public function mxchat_enqueue_scripts_styles() {
1460 // Define version numbers for the styles and scripts
1461 $chat_style_version = '1.4.2'; // Replace with your actual version
1462 $chat_script_version = '1.4.2'; // Replace with your actual version
1463
1464 // Correct path to the script file
1465 wp_enqueue_script(
1466 'mxchat-chat-js', // Handle for the script
1467 plugin_dir_url(__FILE__) . '../js/chat-script.js', // Correct path using __FILE__
1468 array('jquery'), // Dependencies
1469 $chat_script_version, // Version for cache busting
1470 true // Load script in footer
1471 );
1472
1473 // Enqueue the CSS file similarly
1474 wp_enqueue_style(
1475 'mxchat-chat-css', // Handle for the style
1476 plugin_dir_url(__FILE__) . '../css/chat-style.css', // Correct path using __FILE__
1477 array(), // No dependencies
1478 $chat_style_version // Version for cache busting
1479 );
1480
1481 // Fetch options from the database
1482 $this->options = get_option('mxchat_options');
1483
1484 // Prepare settings to pass to JavaScript
1485 $style_settings = array(
1486 'ajax_url' => admin_url('admin-ajax.php'),
1487 'nonce' => wp_create_nonce('mxchat_chat_nonce'), // Nonce for security
1488 'rate_limit_message' => $this->options['rate_limit_message'] ?? 'Rate limit exceeded. Please try again later.',
1489 'appendWidgetToBody' => $this->options['append_to_body'] ?? 'off'
1490 );
1491
1492 // Localize the script with necessary data
1493 wp_localize_script('mxchat-chat-js', 'mxchatChat', $style_settings);
1494 }
1495
1496 public function mxchat_reset_rate_limits() {
1497 global $wpdb;
1498
1499 // Define a cache key pattern for rate limits
1500 $cache_key_pattern = 'mxchat_chat_limit_%';
1501
1502 // Retrieve all option names matching the pattern
1503 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery
1504 $option_names = $wpdb->get_col("SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE 'mxchat_chat_limit_%'");
1505
1506 // db call ok; no-cache ok
1507 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- db call ok
1508 $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE 'mxchat_chat_limit_%'");
1509
1510 // Clear the relevant cache entries
1511 foreach ($option_names as $option_name) {
1512 wp_cache_delete($option_name, 'options');
1513 }
1514
1515 // Optionally, clear a general cache if you have one
1516 wp_cache_delete('mxchat_all_chat_limits', 'options');
1517 }
1518
1519 private function mxchat_fetch_woocommerce_products() {
1520 // Ensure WooCommerce is active
1521 if (!class_exists('WooCommerce')) {
1522 return [];
1523 }
1524
1525 $args = array(
1526 'post_type' => 'product',
1527 'post_status' => 'publish',
1528 'posts_per_page' => -1,
1529 );
1530
1531 $products = get_posts($args);
1532 $product_data = [];
1533
1534 foreach ($products as $product) {
1535 $product_id = $product->ID;
1536 $product_obj = wc_get_product($product_id);
1537
1538 $product_data[] = array(
1539 'id' => $product_id,
1540 'name' => $product_obj->get_name(),
1541 'description' => $product_obj->get_description(),
1542 'short_description' => $product_obj->get_short_description(),
1543 'url' => get_permalink($product_id),
1544 'price' => $product_obj->get_regular_price(),
1545 'sale_price' => $product_obj->get_sale_price(),
1546 'stock_status' => $product_obj->get_stock_status(),
1547 'sku' => $product_obj->get_sku(),
1548 'in_stock' => $product_obj->is_in_stock(),
1549 'total_sales' => $product_obj->get_total_sales(),
1550 );
1551 }
1552
1553 return $product_data;
1554 }
1555
1556 }
1557 ?>
1558