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

434 lines 15.0 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_action('wp_enqueue_scripts', array($this, 'mxchat_enqueue_scripts_styles'));
15 add_action('wp_ajax_mxchat_handle_chat_request', array($this, 'mxchat_handle_chat_request'));
16 add_action('wp_ajax_nopriv_mxchat_handle_chat_request', array($this, 'mxchat_handle_chat_request'));
17 add_action('wp_ajax_mxchat_dismiss_pre_chat_message', array($this, 'mxchat_dismiss_pre_chat_message'));
18 add_action('wp_ajax_nopriv_mxchat_dismiss_pre_chat_message', array($this, 'mxchat_dismiss_pre_chat_message'));
19
20
21 if (!wp_next_scheduled('mxchat_reset_rate_limits')) {
22 wp_schedule_event(time(), 'daily', 'mxchat_reset_rate_limits');
23 }
24
25 add_action('mxchat_reset_rate_limits', array($this, 'mxchat_reset_rate_limits'));
26 }
27
28 private function mxchat_increment_chat_count() {
29 $chat_count = get_option('mxchat_chat_count', 0);
30 $chat_count++;
31 update_option('mxchat_chat_count', $chat_count);
32 }
33
34 public function mxchat_fetch_conversation_history_for_ajax($session_id) {
35 global $wpdb;
36 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
37
38 // Prepare and execute the query safely
39 $chat_transcripts = $wpdb->get_results(
40 $wpdb->prepare("SELECT * FROM $table_name WHERE session_id = %s ORDER BY timestamp ASC", sanitize_text_field($session_id))
41 );
42
43 // Check if results are empty
44 if (empty($chat_transcripts)) {
45 return [];
46 }
47
48 // Build the conversation history
49 $conversation_history = [];
50 foreach ($chat_transcripts as $transcript) {
51 $conversation_history[] = [
52 'role' => $transcript->role,
53 'content' => $transcript->message
54 ];
55 }
56
57 return $conversation_history;
58 }
59
60
61 private function mxchat_save_chat_message($session_id, $role, $message) {
62 global $wpdb;
63 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
64
65 $wpdb->insert($table_name, [
66 'user_id' => 0,
67 'session_id' => $session_id,
68 'role' => $role,
69 'message' => $message,
70 'timestamp' => current_time('mysql', 1)
71 ]);
72 }
73
74
75
76 public function mxchat_handle_chat_request() {
77 global $wpdb;
78
79 // Get and sanitize the user identifier
80 $user_id = $this->mxchat_get_user_identifier();
81 $user_id = sanitize_key($user_id);
82
83 // Manage rate limiting
84 $rate_limit_transient_key = 'mxchat_chat_limit_' . $user_id;
85 $chat_count = get_transient($rate_limit_transient_key);
86 $session_transient_key = 'mxchat_chat_session_' . $user_id;
87 $session_id = get_transient($session_transient_key);
88
89 if ($chat_count === false) {
90 $chat_count = 0;
91 }
92
93 if ($session_id === false) {
94 $session_id = uniqid('mxchat_chat_', true);
95 set_transient($session_transient_key, $session_id, DAY_IN_SECONDS); // Store session ID for a day
96 }
97
98 $rate_limit_option = isset($this->options['rate_limit']) ? $this->options['rate_limit'] : 'unlimited';
99
100 // Check if rate limit is not 'unlimited'
101 if ($rate_limit_option !== 'unlimited') {
102 $rate_limit = intval($rate_limit_option);
103
104 if ($chat_count >= $rate_limit) {
105 wp_send_json_error('Rate limit exceeded. Please try again later.');
106 wp_die();
107 }
108 set_transient($rate_limit_transient_key, $chat_count + 1, DAY_IN_SECONDS);
109 }
110
111 // Validate and sanitize the incoming message
112 if (!isset($_POST['message'])) {
113 wp_send_json_error('No message received');
114 wp_die();
115 }
116
117 $message = sanitize_text_field($_POST['message']);
118 if (empty($message)) {
119 wp_send_json_error('Message is empty or invalid.');
120 wp_die();
121 }
122
123 // Save the user message to the database
124 $this->mxchat_save_chat_message($session_id, 'user', $message);
125
126 // Generate and validate the embedding
127 $user_message_embedding = $this->mxchat_generate_embedding($message, $this->options['api_key']);
128 if (!is_array($user_message_embedding)) {
129 wp_send_json_error('Error processing your message.');
130 wp_die();
131 }
132
133 // Find relevant content based on embedding
134 $relevant_content = $this->mxchat_find_relevant_content($user_message_embedding);
135
136 // Fetch conversation history from the database
137 $conversation_history = $this->mxchat_fetch_conversation_history_for_ajax($session_id);
138
139 // Increment the chat count
140 $this->mxchat_increment_chat_count();
141
142 // Generate a response from the AI model
143 $response = $this->mxchat_generate_response($relevant_content, $this->options['api_key'], $conversation_history);
144
145 // Save the bot response to the database
146 $this->mxchat_save_chat_message($session_id, 'bot', $response);
147
148 // Send the response back to the client
149 wp_send_json(['message' => $response]);
150
151 wp_die();
152 }
153
154
155 private function mxchat_get_user_identifier() {
156 return sanitize_text_field($_SERVER['REMOTE_ADDR']);
157 }
158
159
160
161 private function mxchat_generate_embedding($text, $api_key) {
162 $endpoint = 'https://api.openai.com/v1/embeddings';
163
164 $body = wp_json_encode([
165 'input' => $text,
166 'model' => 'text-embedding-ada-002'
167 ]);
168
169 $args = [
170 'body' => $body,
171 'headers' => [
172 'Content-Type' => 'application/json',
173 'Authorization' => 'Bearer ' . $api_key,
174 ],
175 'timeout' => 60,
176 'redirection' => 5,
177 'blocking' => true,
178 'httpversion' => '1.0',
179 'sslverify' => true,
180 ];
181
182 $response = wp_remote_post($endpoint, $args);
183
184 if (is_wp_error($response)) {
185 return null;
186 }
187
188 $response_body = json_decode(wp_remote_retrieve_body($response), true);
189
190 if (isset($response_body['data'][0]['embedding']) && is_array($response_body['data'][0]['embedding'])) {
191 return $response_body['data'][0]['embedding'];
192 } else {
193 return null;
194 }
195 }
196
197 private function mxchat_find_relevant_content($user_embedding) {
198 global $wpdb;
199 $system_prompt_table = $wpdb->prefix . 'mxchat_system_prompt_content';
200
201 // Define a cache key for embeddings
202 $cache_key = 'mxchat_system_prompt_embeddings';
203
204 // Attempt to get the embeddings from the cache
205 $embeddings = wp_cache_get($cache_key, 'mxchat_system_prompts');
206
207 if ($embeddings === false) {
208 // Cache miss, query the database and cache the results
209 $query = "SELECT id, embedding_vector FROM {$system_prompt_table}";
210 $embeddings = $wpdb->get_results($query);
211
212 if ($embeddings === null || empty($embeddings)) {
213 error_log("No embeddings found in the database.");
214 return null; // Return null to handle no embeddings gracefully
215 }
216
217 // Cache the results if successful
218 wp_cache_set($cache_key, $embeddings, 'mxchat_system_prompts', 3600); // Cache for 1 hour
219 }
220
221 $most_relevant_id = null;
222 $highest_similarity = -INF;
223
224 foreach ($embeddings as $embedding) {
225 $database_embedding = maybe_unserialize($embedding->embedding_vector);
226
227 // Debugging: Log the embeddings
228 if (!is_array($database_embedding)) {
229 error_log("Invalid database embedding format for ID {$embedding->id}: " . print_r($database_embedding, true));
230 continue;
231 }
232
233 if (is_array($user_embedding)) {
234 $similarity = $this->mxchat_calculate_cosine_similarity($user_embedding, $database_embedding);
235
236 // Debugging: Log the similarity score
237 error_log("Calculated similarity for ID {$embedding->id}: {$similarity}");
238
239 if ($similarity > $highest_similarity) {
240 $highest_similarity = $similarity;
241 $most_relevant_id = $embedding->id;
242 }
243 } else {
244 error_log("User embedding is not an array. Embedding data: " . print_r($user_embedding, true));
245 }
246 }
247
248 if ($most_relevant_id !== null) {
249 // Define a cache key for the relevant content
250 $content_cache_key = 'mxchat_article_content_' . $most_relevant_id;
251
252 // Attempt to get the relevant content from the cache
253 $relevant_content = wp_cache_get($content_cache_key, 'mxchat_system_prompts');
254
255 if ($relevant_content === false) {
256 // Cache miss, query the database and cache the result
257 $query = $wpdb->prepare("SELECT article_content FROM {$system_prompt_table} WHERE id = %d", $most_relevant_id);
258 $relevant_content = $wpdb->get_var($query);
259
260 if ($relevant_content === null) {
261 error_log("No relevant content found for ID {$most_relevant_id}.");
262 return null; // Return null if no content is found
263 }
264
265 wp_cache_set($content_cache_key, $relevant_content, 'mxchat_system_prompts', 3600); // Cache for 1 hour
266 }
267
268 return $relevant_content;
269 }
270
271 error_log("No relevant content found. Most relevant ID was null.");
272 return null; // Return null if no relevant content is found
273 }
274
275 private function mxchat_generate_response($relevant_content, $api_key, $conversation_history) {
276 if (!$relevant_content) {
277 return "I'm sorry, I couldn't find relevant information on that topic.";
278 }
279
280 $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : '';
281
282 $content_with_instructions = $system_prompt_instructions . " " . $relevant_content;
283
284 array_unshift($conversation_history, [
285 'role' => 'system',
286 'content' => "Here are your instructions: " . $content_with_instructions
287 ]);
288
289 foreach ($conversation_history as &$message) {
290 if ($message['role'] === 'bot') {
291 $message['role'] = 'assistant';
292 }
293 }
294
295 $api_url = 'https://api.openai.com/v1/chat/completions';
296
297 $body = json_encode([
298 'model' => isset($this->options['model']) ? $this->options['model'] : 'gpt-3.5',
299 'messages' => $conversation_history,
300 ]);
301
302 $args = [
303 'body' => $body,
304 'headers' => [
305 'Content-Type' => 'application/json',
306 'Authorization' => 'Bearer ' . $api_key,
307 ],
308 'timeout' => 60,
309 'redirection' => 5,
310 'blocking' => true,
311 'httpversion' => '1.0',
312 'sslverify' => true,
313 ];
314
315 $response = wp_remote_post($api_url, $args);
316
317 if (is_wp_error($response)) {
318 return "Sorry, there was an error processing your request.";
319 }
320
321 $response_body = json_decode(wp_remote_retrieve_body($response), true);
322
323 if (isset($response_body['choices'][0]['message']['content'])) {
324 if (isset($response_body['usage'])) {
325 $prompt_tokens = $response_body['usage']['prompt_tokens'];
326 $total_tokens = $response_body['usage']['total_tokens'];
327 }
328 return trim($response_body['choices'][0]['message']['content']);
329 } else {
330 return "Sorry, I couldn't process that request.";
331 }
332 }
333
334
335 public function mxchat_dismiss_pre_chat_message() {
336 // Get and sanitize the user identifier
337 $user_id = $this->mxchat_get_user_identifier();
338 $user_id = sanitize_key($user_id);
339
340 // Set a transient to track that the user has dismissed the pre-chat message
341 $transient_key = 'mxchat_pre_chat_message_dismissed_' . $user_id;
342 set_transient($transient_key, true, DAY_IN_SECONDS);
343
344 wp_send_json_success();
345 }
346
347
348
349 private function mxchat_calculate_cosine_similarity($vectorA, $vectorB) {
350 if (!is_array($vectorA) || !is_array($vectorB) || empty($vectorA) || empty($vectorB)) {
351 return 0;
352 }
353
354 $dotProduct = array_sum(array_map(function ($a, $b) {
355 return $a * $b;
356 }, $vectorA, $vectorB));
357 $normA = sqrt(array_sum(array_map(function ($a) {
358 return $a * $a;
359 }, $vectorA)));
360 $normB = sqrt(array_sum(array_map(function ($b) {
361 return $b * $b;
362 }, $vectorB)));
363
364 if ($normA == 0 || $normB == 0) {
365 return 0;
366 }
367
368 return $dotProduct / ($normA * $normB);
369 }
370
371 public function mxchat_enqueue_scripts_styles() {
372 // Define version numbers for the styles and scripts
373 $chat_style_version = '1.0.5'; // Replace with your actual version
374 $chat_script_version = '1.0.5'; // Replace with your actual version
375
376 // Correct path to the script file
377 wp_enqueue_script(
378 'mxchat-chat-js', // Handle for the script
379 plugin_dir_url(__FILE__) . '../js/chat-script.js', // Correct path using __FILE__
380 array('jquery'), // Dependencies
381 $chat_script_version, // Version for cache busting
382 true // Load script in footer
383 );
384
385 // Enqueue the CSS file similarly
386 wp_enqueue_style(
387 'mxchat-chat-css', // Handle for the style
388 plugin_dir_url(__FILE__) . '../css/chat-style.css', // Correct path using __FILE__
389 array(), // No dependencies
390 $chat_style_version // Version for cache busting
391 );
392
393 // Fetch options from the database
394 $this->options = get_option('mxchat_options');
395
396 // Prepare settings to pass to JavaScript
397 $style_settings = array(
398 'ajax_url' => admin_url('admin-ajax.php'),
399 'nonce' => wp_create_nonce('mxchat_chat_nonce'), // Nonce for security
400 'rate_limit_message' => 'Rate limit exceeded. Please try again later.',
401 'appendWidgetToBody' => $this->options['append_to_body'] ?? 'off'
402 );
403
404 // Localize the script with necessary data
405 wp_localize_script('mxchat-chat-js', 'mxchatChat', $style_settings);
406 }
407
408
409
410 public function mxchat_reset_rate_limits() {
411 global $wpdb;
412
413 // Define a cache key pattern for rate limits
414 $cache_key_pattern = 'mxchat_chat_limit_%';
415
416 // Retrieve all option names matching the pattern
417 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery
418 $option_names = $wpdb->get_col("SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE 'mxchat_chat_limit_%'");
419
420 // db call ok; no-cache ok
421 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- db call ok
422 $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE 'mxchat_chat_limit_%'");
423
424 // Clear the relevant cache entries
425 foreach ($option_names as $option_name) {
426 wp_cache_delete($option_name, 'options');
427 }
428
429 // Optionally, clear a general cache if you have one
430 wp_cache_delete('mxchat_all_chat_limits', 'options');
431 }
432 }
433 ?>
434