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

840 lines 30.8 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
36 if (!wp_next_scheduled('mxchat_reset_rate_limits')) {
37 wp_schedule_event(time(), 'daily', 'mxchat_reset_rate_limits');
38 }
39
40 add_action('mxchat_reset_rate_limits', array($this, 'mxchat_reset_rate_limits'));
41 }
42
43 public function mxchat_handle_product_change($post_id, $post, $update) {
44 // Ensure this is a product post type
45 if ($post->post_type !== 'product') {
46 return;
47 }
48
49 // Only generate embeddings if the product is published
50 if ($post->post_status === 'publish') {
51 // Delay the embedding slightly to ensure all product data is available
52 add_action('shutdown', function() use ($post_id) {
53 $product = wc_get_product($post_id);
54 if ($product && $product->get_price() !== '') {
55 $this->mxchat_store_product_embedding($product);
56 } else {
57 // Optionally, log or handle the case where product data is incomplete
58 // error_log("Product {$post_id} does not have complete data. Embedding not generated.");
59 }
60 });
61 }
62 }
63
64 public function mxchat_handle_product_delete($post_id) {
65 if (get_post_type($post_id) !== 'product') {
66 return;
67 }
68
69 global $wpdb;
70 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
71
72 // Delete the embedding associated with this product
73 $wpdb->delete($table_name, array('source_url' => get_permalink($post_id)), array('%s'));
74 }
75
76 private function mxchat_store_product_embedding($product) {
77 if (isset($this->options['enable_woocommerce_integration']) && $this->options['enable_woocommerce_integration'] === '1') {
78
79 $source_url = get_permalink($product->get_id());
80 $regular_price = $product->get_regular_price();
81 $sale_price = $product->get_sale_price();
82 $price = $sale_price ?: $regular_price;
83
84 $description = $product->get_description() . "\n\n" .
85 "Short Description: " . $product->get_short_description() . "\n" .
86 "Price: " . $regular_price . "\n" .
87 "Sale Price: " . ($sale_price ?: 'N/A') . "\n" .
88 "SKU: " . $product->get_sku();
89
90 global $wpdb;
91 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
92
93 // Delete any existing embedding for this product
94 $wpdb->delete($table_name, array('source_url' => $source_url), array('%s'));
95
96 // Submit the new content and embedding to the database
97 MxChat_Utils::submit_content_to_db($description, $source_url, $this->options['api_key']);
98 }
99 }
100
101
102
103
104
105 private function mxchat_increment_chat_count() {
106 $chat_count = get_option('mxchat_chat_count', 0);
107 $chat_count++;
108 update_option('mxchat_chat_count', $chat_count);
109 }
110
111 function mxchat_fetch_conversation_history() {
112 if (empty($_POST['session_id'])) {
113 wp_send_json_error(['message' => 'Session ID missing.']);
114 wp_die();
115 }
116
117 $session_id = sanitize_text_field($_POST['session_id']);
118 $history = get_option("mxchat_history_{$session_id}", []); // Retrieve stored history
119
120 if (empty($history)) {
121 wp_send_json_error(['message' => 'No history found.']);
122 wp_die();
123 }
124
125 wp_send_json_success(['conversation' => $history]);
126 wp_die();
127 }
128 private function mxchat_fetch_conversation_history_for_ajax($session_id) {
129 $history = get_option("mxchat_history_{$session_id}", []); // Retrieve stored history based on session ID
130 $formatted_history = [];
131
132 // Format the history to align with the expected structure for OpenAI
133 foreach ($history as $entry) {
134 $formatted_history[] = [
135 'role' => $entry['role'], // Ensure this matches 'user' or 'assistant'
136 'content' => $entry['content']
137 ];
138 }
139
140 return $formatted_history;
141 }
142
143
144 private function mxchat_save_chat_message($session_id, $role, $message) {
145 global $wpdb;
146 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
147
148 $user_id = is_user_logged_in() ? get_current_user_id() : 0;
149 $user_identifier = MxChat_User::mxchat_get_user_identifier();
150 $user_email = MxChat_User::mxchat_get_user_email();
151
152 $history = get_option("mxchat_history_{$session_id}", []);
153 $history[] = ['role' => $role, 'content' => $message];
154 update_option("mxchat_history_{$session_id}", $history);
155
156 $wpdb->insert($table_name, [
157 'user_id' => $user_id,
158 'user_identifier' => $user_identifier,
159 'user_email' => $user_email,
160 'session_id' => $session_id,
161 'role' => $role,
162 'message' => $message,
163 'timestamp' => current_time('mysql', 1)
164 ]);
165 }
166
167
168 public function mxchat_handle_chat_request() {
169 global $wpdb;
170
171 // Get and sanitize the user identifier
172 $user_id = $this->mxchat_get_user_identifier();
173 $user_id = sanitize_key($user_id);
174
175 // Setup rate limiting
176 $rate_limit_transient_key = 'mxchat_chat_limit_' . $user_id;
177 $chat_count = get_transient($rate_limit_transient_key) ?: 0;
178
179 // Retrieve the session ID from the client's POST data
180 $session_id = isset($_POST['session_id']) ? sanitize_text_field($_POST['session_id']) : '';
181
182 if (empty($session_id)) {
183 // Handle the case where the session ID is missing
184 wp_send_json_error('Session ID is missing.');
185 wp_die();
186 }
187
188 // No need to set transients or server-side cookies for the session ID
189
190 // Check rate limit
191 $rate_limit_option = $this->options['rate_limit'] ?? 'unlimited';
192 if ($rate_limit_option !== 'unlimited' && $chat_count >= intval($rate_limit_option)) {
193 wp_send_json_error(['message' => $this->options['rate_limit_message'] ?? 'Rate limit exceeded. Please try again later.']);
194 wp_die();
195 }
196 set_transient($rate_limit_transient_key, $chat_count + 1, DAY_IN_SECONDS);
197
198 // Validate and sanitize the incoming message
199 if (empty($_POST['message'])) {
200 wp_send_json_error('No message received');
201 wp_die();
202 }
203
204 $message = sanitize_text_field($_POST['message']);
205 $this->mxchat_save_chat_message($session_id, 'user', $message);
206
207 // Track email capture and WooCommerce flows with individual transients
208 $email_capture_prompt = get_transient('mxchat_email_capture_' . $user_id);
209 $interaction_count = get_transient('mxchat_email_interaction_count_' . $user_id) ?: 0;
210 $woocommerce_prompt = get_transient('mxchat_woocommerce_prompt_' . $user_id);
211
212 // Handle email capture flow
213 if ($email_capture_prompt) {
214 if (preg_match('/[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}/i', $message, $matches)) {
215 $email = $matches[0];
216 $this->add_email_to_loops($email);
217 $response = $this->options['email_capture_response'] ?? 'Thank you for providing your email! You\'ve been added to our list.';
218
219 delete_transient('mxchat_email_capture_' . $user_id);
220 delete_transient('mxchat_email_interaction_count_' . $user_id);
221 delete_transient('mxchat_woocommerce_prompt_' . $user_id);
222
223 $this->mxchat_save_chat_message($session_id, 'bot', $response);
224 wp_send_json(['message' => $response]);
225 wp_die();
226 } else {
227 if ($interaction_count >= 3) {
228 delete_transient('mxchat_email_capture_' . $user_id);
229 delete_transient('mxchat_email_interaction_count_' . $user_id);
230 } else {
231 set_transient('mxchat_email_interaction_count_' . $user_id, ++$interaction_count, 5 * MINUTE_IN_SECONDS);
232 }
233 }
234 }
235
236 // Handle WooCommerce add-to-cart flow
237 if (class_exists('WooCommerce') && stripos($message, 'add to cart') !== false) {
238 $last_product_id = get_transient('mxchat_last_discussed_product_' . $user_id);
239 if ($last_product_id) {
240 $added = WC()->cart->add_to_cart($last_product_id);
241 $product = wc_get_product($last_product_id);
242
243 if ($added) {
244 $response = "The product '{$product->get_name()}' has been added to your cart. To proceed to checkout, please type 'checkout'.";
245 set_transient('mxchat_checkout_prompt_' . $user_id, true, 5 * MINUTE_IN_SECONDS);
246 $this->mxchat_save_chat_message($session_id, 'bot', $response);
247 wp_send_json(['message' => $response]);
248 wp_die();
249 } else {
250 $response = "Sorry, I couldn't add the product to your cart. Please try again.";
251 $this->mxchat_save_chat_message($session_id, 'bot', $response);
252 wp_send_json(['message' => $response]);
253 wp_die();
254 }
255 } else {
256 $response = "I couldn't find the product to add. Please mention the product name again.";
257 $this->mxchat_save_chat_message($session_id, 'bot', $response);
258 wp_send_json(['message' => $response]);
259 wp_die();
260 }
261 }
262
263 // Handle checkout response
264 if (class_exists('WooCommerce') && stripos($message, 'checkout') !== false) {
265 $checkout_prompt = get_transient('mxchat_checkout_prompt_' . $user_id);
266 if ($checkout_prompt && WC()->cart->get_cart_contents_count() > 0) {
267 $checkout_url = wc_get_checkout_url();
268 $response = "Great! Redirecting you to the checkout page...";
269 delete_transient('mxchat_checkout_prompt_' . $user_id);
270
271 $this->mxchat_save_chat_message($session_id, 'bot', $response);
272 wp_send_json(['message' => $response, 'redirect_url' => $checkout_url]);
273 wp_die();
274 } else {
275 $response = "It seems like there is no active checkout prompt or no items in your cart. Please add a product to the cart first.";
276 $this->mxchat_save_chat_message($session_id, 'bot', $response);
277 wp_send_json(['message' => $response]);
278 wp_die();
279 }
280 }
281
282 // Handle order-related queries
283 if (class_exists('WooCommerce') && MxChat_WooCommerce::mxchat_is_order_related_query($message)) {
284 $response = MxChat_WooCommerce::mxchat_fetch_user_orders_details();
285 $this->mxchat_save_chat_message($session_id, 'bot', $response);
286 wp_send_json(['message' => $response]);
287 wp_die();
288 }
289
290 // Check for trigger keywords to initiate email capture
291 $trigger_keywords = explode(',', $this->options['trigger_keywords'] ?? '');
292 if (!empty($trigger_keywords) && $trigger_keywords[0] !== '') {
293 foreach ($trigger_keywords as $keyword) {
294 if (stripos($message, trim($keyword)) !== false) {
295 $response = $this->options['triggered_phrase_response'] ?? "Would you like to join our mailing list? Please provide your email below.";
296 set_transient('mxchat_email_capture_' . $user_id, true, 5 * MINUTE_IN_SECONDS);
297 $this->mxchat_save_chat_message($session_id, 'bot', $response);
298 wp_send_json(['message' => $response]);
299 wp_die();
300 }
301 }
302 }
303
304 // Store product discussion in transient
305 $last_discussed_product_id = MxChat_WooCommerce::mxchat_extract_product_id_from_message($message);
306 if ($last_discussed_product_id) {
307 set_transient('mxchat_last_discussed_product_' . $user_id, $last_discussed_product_id, 3600);
308 }
309
310 // Standard chat processing
311 $user_message_embedding = $this->mxchat_generate_embedding($message, $this->options['api_key']);
312 if (!is_array($user_message_embedding)) {
313 wp_send_json_error('Error processing your message.');
314 wp_die();
315 }
316
317 $relevant_content = $this->mxchat_find_relevant_content($user_message_embedding);
318 $conversation_history = $this->mxchat_fetch_conversation_history_for_ajax($session_id);
319 $this->mxchat_increment_chat_count();
320 $response = $this->mxchat_generate_response(
321 $relevant_content,
322 $this->options['api_key'], // OpenAI API Key
323 $this->options['xai_api_key'], // X.AI API Key
324 $this->options['claude_api_key'], // Claude API Key
325 $conversation_history
326 );
327
328 $this->mxchat_save_chat_message($session_id, 'bot', $response);
329 wp_send_json(['message' => $response, 'session_id' => $session_id]);
330 wp_die();
331 }
332
333
334 // Function to add the captured email to Loops
335 private function add_email_to_loops($email) {
336 $api_key = $this->options['loops_api_key'];
337 $mailing_list_id = $this->options['loops_mailing_list'];
338
339 $data = array(
340 'email' => $email,
341 'subscribed' => true,
342 'source' => 'MxChat AI Chatbot',
343 'mailingLists' => array($mailing_list_id => true),
344 );
345
346 $url = "https://app.loops.so/api/v1/contacts/create";
347 $args = array(
348 'body' => json_encode($data),
349 'headers' => array(
350 'Authorization' => 'Bearer ' . $api_key,
351 'Content-Type' => 'application/json',
352 ),
353 'method' => 'POST',
354 'timeout' => 45,
355 );
356
357 wp_remote_post($url, $args);
358 }
359
360
361 private function mxchat_get_user_identifier() {
362 return MxChat_User::mxchat_get_user_identifier();
363 }
364
365
366
367 private function mxchat_generate_embedding($text, $api_key) {
368 $endpoint = 'https://api.openai.com/v1/embeddings';
369
370 $body = wp_json_encode([
371 'input' => $text,
372 'model' => 'text-embedding-ada-002'
373 ]);
374
375 $args = [
376 'body' => $body,
377 'headers' => [
378 'Content-Type' => 'application/json',
379 'Authorization' => 'Bearer ' . $api_key,
380 ],
381 'timeout' => 60,
382 'redirection' => 5,
383 'blocking' => true,
384 'httpversion' => '1.0',
385 'sslverify' => true,
386 ];
387
388 $response = wp_remote_post($endpoint, $args);
389
390 if (is_wp_error($response)) {
391 return null;
392 }
393
394 $response_body = json_decode(wp_remote_retrieve_body($response), true);
395
396 if (isset($response_body['data'][0]['embedding']) && is_array($response_body['data'][0]['embedding'])) {
397 return $response_body['data'][0]['embedding'];
398 } else {
399 return null;
400 }
401 }
402
403 private function mxchat_find_relevant_content($user_embedding) {
404 global $wpdb;
405 $system_prompt_table = $wpdb->prefix . 'mxchat_system_prompt_content';
406
407 // Define a cache key for embeddings
408 $cache_key = 'mxchat_system_prompt_embeddings';
409
410 // Attempt to get the embeddings from the cache
411 $embeddings = wp_cache_get($cache_key, 'mxchat_system_prompts');
412
413 if ($embeddings === false) {
414 // Cache miss, query the database and cache the results
415 $query = "SELECT id, embedding_vector FROM {$system_prompt_table}";
416 $embeddings = $wpdb->get_results($query);
417
418 if ($embeddings === null || empty($embeddings)) {
419 //error_log("No embeddings found in the database.");
420 return null; // Return null to handle no embeddings gracefully
421 }
422
423 // Cache the results if successful
424 wp_cache_set($cache_key, $embeddings, 'mxchat_system_prompts', 3600); // Cache for 1 hour
425 }
426
427 $most_relevant_id = null;
428 $highest_similarity = -INF;
429
430 foreach ($embeddings as $embedding) {
431 $database_embedding = maybe_unserialize($embedding->embedding_vector);
432
433 // Debugging: Log the embeddings
434 // if (!is_array($database_embedding)) {
435 // error_log("Invalid database embedding format for ID {$embedding->id}: " . print_r($database_embedding, true));
436 // continue;
437 // }
438
439 if (is_array($user_embedding)) {
440 $similarity = $this->mxchat_calculate_cosine_similarity($user_embedding, $database_embedding);
441
442 // Debugging: Log the similarity score
443 // error_log("Calculated similarity for ID {$embedding->id}: {$similarity}");
444
445 if ($similarity > $highest_similarity) {
446 $highest_similarity = $similarity;
447 $most_relevant_id = $embedding->id;
448 }
449 } else {
450 // error_log("User embedding is not an array. Embedding data: " . print_r($user_embedding, true));
451 }
452 }
453
454 if ($most_relevant_id !== null) {
455 // Fetch content with product links
456 return $this->fetch_content_with_product_links($most_relevant_id);
457 }
458
459 //error_log("No relevant content found. Most relevant ID was null.");
460 return null; // Return null if no relevant content is found
461 }
462
463
464 private function fetch_content_with_product_links($most_relevant_id) {
465 global $wpdb;
466 $system_prompt_table = $wpdb->prefix . 'mxchat_system_prompt_content';
467
468 // Fetch the article content and associated product URL
469 $query = $wpdb->prepare("SELECT article_content, source_url FROM {$system_prompt_table} WHERE id = %d", $most_relevant_id);
470 $result = $wpdb->get_row($query);
471
472 if ($result) {
473 // Append the product link to the content if available
474 $content = $result->article_content;
475 if (!empty($result->source_url)) {
476 $content .= "\n\nFor more details, check out this product: " . esc_url($result->source_url);
477 }
478 return $content;
479 }
480
481 return null;
482 }
483
484 private function mxchat_generate_response($relevant_content, $api_key, $xai_api_key, $claude_api_key, $conversation_history) {
485 if (!$relevant_content) {
486 return "I'm sorry, I couldn't find relevant information on that topic.";
487 }
488
489 // Check the selected model
490 $selected_model = isset($this->options['model']) ? $this->options['model'] : 'gpt-3.5-turbo';
491
492 // Call the appropriate function based on the selected model
493 if (strpos($selected_model, 'claude') !== false) {
494 return $this->mxchat_generate_response_claude($selected_model, $claude_api_key, $conversation_history, $relevant_content);
495 } elseif ($selected_model === 'grok-beta') {
496 return $this->mxchat_generate_response_xai($selected_model, $xai_api_key, $conversation_history, $relevant_content);
497 } else {
498 return $this->mxchat_generate_response_openai($selected_model, $api_key, $conversation_history, $relevant_content);
499 }
500 }
501
502 private function mxchat_generate_response_openai($selected_model, $api_key, $conversation_history, $relevant_content) {
503 // Get system prompt instructions from options
504 $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : '';
505
506 // Add system prompt to relevant content
507 $content_with_instructions = $system_prompt_instructions . " " . $relevant_content;
508
509 // Prepend system instructions to the conversation history
510 array_unshift($conversation_history, [
511 'role' => 'system',
512 'content' => "Here are your instructions: " . $content_with_instructions
513 ]);
514
515 // Ensure consistency: Replace 'bot' role with 'assistant' in conversation history
516 foreach ($conversation_history as &$message) {
517 if ($message['role'] === 'bot') {
518 $message['role'] = 'assistant';
519 }
520 }
521
522 // Build the request body
523 $body = json_encode([
524 'model' => $selected_model,
525 'messages' => $conversation_history,
526 'temperature' => 0.8,
527 'stream' => false
528 ]);
529
530 // Set up the API request
531 $args = [
532 'body' => $body,
533 'headers' => [
534 'Content-Type' => 'application/json',
535 'Authorization' => 'Bearer ' . $api_key,
536 ],
537 'timeout' => 60,
538 'redirection' => 5,
539 'blocking' => true,
540 'httpversion' => '1.0',
541 'sslverify' => true,
542 ];
543
544 // Make the API request
545 $response = wp_remote_post('https://api.openai.com/v1/chat/completions', $args);
546
547 // Process the response
548 if (is_wp_error($response)) {
549 return "Sorry, there was an error processing your request.";
550 }
551
552 $response_body = json_decode(wp_remote_retrieve_body($response), true);
553
554 if (isset($response_body['choices'][0]['message']['content'])) {
555 return trim($response_body['choices'][0]['message']['content']);
556 } else {
557 return "Sorry, I couldn't process that request.";
558 }
559 }
560
561 private function mxchat_generate_response_xai($selected_model, $xai_api_key, $conversation_history, $relevant_content) {
562 // Get system prompt instructions from options
563 $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : '';
564
565 // Add system prompt to relevant content
566 $content_with_instructions = $system_prompt_instructions . " " . $relevant_content;
567
568 // Prepend system instructions to the conversation history
569 array_unshift($conversation_history, [
570 'role' => 'system',
571 'content' => "Here are your instructions: " . $content_with_instructions
572 ]);
573
574 // Ensure consistency: Replace 'bot' role with 'assistant' in conversation history
575 foreach ($conversation_history as &$message) {
576 if ($message['role'] === 'bot') {
577 $message['role'] = 'assistant';
578 }
579 }
580
581 // Build the request body
582 $body = json_encode([
583 'model' => $selected_model,
584 'messages' => $conversation_history,
585 'temperature' => 0.8,
586 'stream' => false
587 ]);
588
589 // Set up the API request
590 $args = [
591 'body' => $body,
592 'headers' => [
593 'Content-Type' => 'application/json',
594 'Authorization' => 'Bearer ' . $xai_api_key,
595 ],
596 'timeout' => 60,
597 'redirection' => 5,
598 'blocking' => true,
599 'httpversion' => '1.0',
600 'sslverify' => true,
601 ];
602
603 // Make the API request
604 $response = wp_remote_post('https://api.x.ai/v1/chat/completions', $args);
605
606 // Process the response
607 if (is_wp_error($response)) {
608 return "Sorry, there was an error processing your request.";
609 }
610
611 $response_body = json_decode(wp_remote_retrieve_body($response), true);
612
613 if (isset($response_body['choices'][0]['message']['content'])) {
614 return trim($response_body['choices'][0]['message']['content']);
615 } else {
616 return "Sorry, I couldn't process that request.";
617 }
618 }
619
620 private function mxchat_generate_response_claude($selected_model, $claude_api_key, $conversation_history, $relevant_content) {
621 // The system prompt should be passed at the beginning of the conversation
622 $system_prompt = "You are an AI Chatbot assistant for MxChat.AI. Your role is to answer questions about the MxChat WordPress plugin and its features. Provide concise and helpful responses.";
623
624 // If conversation history is empty, add the system prompt
625 if (empty($conversation_history)) {
626 $conversation_history[] = [
627 'role' => 'system',
628 'content' => $system_prompt
629 ];
630 }
631
632 // Add the new user message to the conversation
633 $conversation_history[] = [
634 'role' => 'user',
635 'content' => $relevant_content
636 ];
637
638 // Build the request body
639 $body = json_encode([
640 'model' => $selected_model,
641 'max_tokens' => 1000,
642 'temperature' => 0.8,
643 'messages' => $conversation_history
644 ]);
645
646 // Set up the API request
647 $args = [
648 'body' => $body,
649 'headers' => [
650 'Content-Type' => 'application/json',
651 'x-api-key' => $claude_api_key,
652 'anthropic-version' => '2023-06-01',
653 ],
654 'timeout' => 60,
655 'redirection' => 5,
656 'blocking' => true,
657 'httpversion' => '1.0',
658 'sslverify' => true,
659 ];
660
661 // Make the API request
662 $response = wp_remote_post('https://api.anthropic.com/v1/messages', $args);
663
664 // Process the response
665 if (is_wp_error($response)) {
666 return "Sorry, there was an error processing your request.";
667 }
668
669 $response_body = json_decode(wp_remote_retrieve_body($response), true);
670
671 if (isset($response_body['content'][0]['text'])) {
672 return trim($response_body['content'][0]['text']);
673 } else {
674 return "Sorry, I couldn't process that request.";
675 }
676 }
677
678
679 public function mxchat_dismiss_pre_chat_message() {
680 // Get and sanitize the user identifier
681 $user_id = $this->mxchat_get_user_identifier();
682 $user_id = sanitize_key($user_id);
683
684 // Set a transient to track that the user has dismissed the pre-chat message
685 $transient_key = 'mxchat_pre_chat_message_dismissed_' . $user_id;
686 set_transient($transient_key, true, DAY_IN_SECONDS);
687
688 wp_send_json_success();
689 }
690
691 public function mxchat_check_pre_chat_message_status() {
692 // Get and sanitize the user identifier
693 $user_id = $this->mxchat_get_user_identifier();
694 $user_id = sanitize_key($user_id);
695
696 // Check if the transient exists (i.e., if the message was dismissed)
697 $transient_key = 'mxchat_pre_chat_message_dismissed_' . $user_id;
698 $dismissed = get_transient($transient_key);
699
700 // Log the result to see if it's being set correctly
701 //error_log("Check pre-chat message dismissed for $user_id: " . ($dismissed ? 'Yes' : 'No'));
702
703 if ($dismissed) {
704 wp_send_json_success(['dismissed' => true]);
705 } else {
706 wp_send_json_success(['dismissed' => false]);
707 }
708
709 wp_die();
710 }
711
712
713
714
715
716 private function mxchat_calculate_cosine_similarity($vectorA, $vectorB) {
717 if (!is_array($vectorA) || !is_array($vectorB) || empty($vectorA) || empty($vectorB)) {
718 return 0;
719 }
720
721 $dotProduct = array_sum(array_map(function ($a, $b) {
722 return $a * $b;
723 }, $vectorA, $vectorB));
724 $normA = sqrt(array_sum(array_map(function ($a) {
725 return $a * $a;
726 }, $vectorA)));
727 $normB = sqrt(array_sum(array_map(function ($b) {
728 return $b * $b;
729 }, $vectorB)));
730
731 if ($normA == 0 || $normB == 0) {
732 return 0;
733 }
734
735 return $dotProduct / ($normA * $normB);
736 }
737
738 public function mxchat_enqueue_scripts_styles() {
739 // Define version numbers for the styles and scripts
740 $chat_style_version = '1.1.91'; // Replace with your actual version
741 $chat_script_version = '1.1.91'; // Replace with your actual version
742
743 // Correct path to the script file
744 wp_enqueue_script(
745 'mxchat-chat-js', // Handle for the script
746 plugin_dir_url(__FILE__) . '../js/chat-script.js', // Correct path using __FILE__
747 array('jquery'), // Dependencies
748 $chat_script_version, // Version for cache busting
749 true // Load script in footer
750 );
751
752 // Enqueue the CSS file similarly
753 wp_enqueue_style(
754 'mxchat-chat-css', // Handle for the style
755 plugin_dir_url(__FILE__) . '../css/chat-style.css', // Correct path using __FILE__
756 array(), // No dependencies
757 $chat_style_version // Version for cache busting
758 );
759
760 // Fetch options from the database
761 $this->options = get_option('mxchat_options');
762
763 // Prepare settings to pass to JavaScript
764 $style_settings = array(
765 'ajax_url' => admin_url('admin-ajax.php'),
766 'nonce' => wp_create_nonce('mxchat_chat_nonce'), // Nonce for security
767 'rate_limit_message' => $this->options['rate_limit_message'] ?? 'Rate limit exceeded. Please try again later.',
768 'appendWidgetToBody' => $this->options['append_to_body'] ?? 'off'
769 );
770
771 // Localize the script with necessary data
772 wp_localize_script('mxchat-chat-js', 'mxchatChat', $style_settings);
773 }
774
775
776
777 public function mxchat_reset_rate_limits() {
778 global $wpdb;
779
780 // Define a cache key pattern for rate limits
781 $cache_key_pattern = 'mxchat_chat_limit_%';
782
783 // Retrieve all option names matching the pattern
784 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery
785 $option_names = $wpdb->get_col("SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE 'mxchat_chat_limit_%'");
786
787 // db call ok; no-cache ok
788 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- db call ok
789 $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE 'mxchat_chat_limit_%'");
790
791 // Clear the relevant cache entries
792 foreach ($option_names as $option_name) {
793 wp_cache_delete($option_name, 'options');
794 }
795
796 // Optionally, clear a general cache if you have one
797 wp_cache_delete('mxchat_all_chat_limits', 'options');
798 }
799
800
801 private function mxchat_fetch_woocommerce_products() {
802 // Ensure WooCommerce is active
803 if (!class_exists('WooCommerce')) {
804 return [];
805 }
806
807 $args = array(
808 'post_type' => 'product',
809 'post_status' => 'publish',
810 'posts_per_page' => -1,
811 );
812
813 $products = get_posts($args);
814 $product_data = [];
815
816 foreach ($products as $product) {
817 $product_id = $product->ID;
818 $product_obj = wc_get_product($product_id);
819
820 $product_data[] = array(
821 'id' => $product_id,
822 'name' => $product_obj->get_name(),
823 'description' => $product_obj->get_description(),
824 'short_description' => $product_obj->get_short_description(),
825 'url' => get_permalink($product_id),
826 'price' => $product_obj->get_regular_price(),
827 'sale_price' => $product_obj->get_sale_price(),
828 'stock_status' => $product_obj->get_stock_status(),
829 'sku' => $product_obj->get_sku(),
830 'in_stock' => $product_obj->is_in_stock(),
831 'total_sales' => $product_obj->get_total_sales(),
832 );
833 }
834
835 return $product_data;
836 }
837
838 }
839 ?>
840