PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 1.5.3
MxChat – AI Chatbot & Content Generation for WordPress v1.5.3
3.2.21 3.2.20 3.2.19 3.2.18 3.2.17 3.2.16 3.2.15 3.2.14 3.2.12 3.2.13 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 3.2.6 3.2.5 3.2.4 3.2.3 3.2.2 3.2.1 2.0.3 2.0.4 2.0.5 2.0.6 All 152 releases
← All changes | includes/class-mxchat-integrator.php +1149 -1676 2.0.41.5.3 View file →
@@ -4,21 +4,23 @@
4 4 }
5 5
6 6 class MxChat_Integrator {
7 7 private $options;
8 - private $prompts_options;
9 8 private $chat_count;
10 - private $fallbackResponse;
9 + private $fallbackResponse;
11 10 private $productCardHtml;
12 - private $word_handler;
13 11
14 12 public function __construct() {
15 13 $this->options = get_option('mxchat_options');
16 - $this->prompts_options = get_option('mxchat_prompts_options', array());
14 + $this->chat_count = get_option('mxchat_chat_count', 0);
17 15
18 - $this->chat_count = get_option('mxchat_chat_count', 0);
19 - $this->word_handler = new MXChat_Word_Handler($this->options);
16 + // Add WooCommerce hooks
17 + add_action('wp_insert_post', array($this, 'mxchat_handle_product_change'), 10, 3);
20 18
19 + // Ensure embeddings are removed when a product is moved to trash or permanently deleted
20 + add_action('wp_trash_post', array($this, 'mxchat_handle_product_delete'));
21 + add_action('before_delete_post', array($this, 'mxchat_handle_product_delete'));
22 +
21 23 add_action('wp_enqueue_scripts', array($this, 'mxchat_enqueue_scripts_styles'));
22 24 add_action('wp_ajax_mxchat_handle_chat_request', array($this, 'mxchat_handle_chat_request'));
23 25 add_action('wp_ajax_nopriv_mxchat_handle_chat_request', array($this, 'mxchat_handle_chat_request'));
24 26
@@ -43,8 +45,9 @@
43 45
44 46 add_action('wp_ajax_mxchat_fetch_new_messages', array($this, 'mxchat_fetch_new_messages'));
45 47 add_action('wp_ajax_nopriv_mxchat_fetch_new_messages', array($this, 'mxchat_fetch_new_messages'));
46 48
49 +
47 50 add_action('mxchat_reset_rate_limits', array($this, 'mxchat_reset_rate_limits'));
48 51
49 52 add_action('wp_ajax_mxchat_upload_pdf', [$this, 'handle_pdf_upload']);
50 53 add_action('wp_ajax_nopriv_mxchat_upload_pdf', [$this, 'handle_pdf_upload']);
@@ -49,24 +52,72 @@
49 52 add_action('wp_ajax_mxchat_upload_pdf', [$this, 'handle_pdf_upload']);
50 53 add_action('wp_ajax_nopriv_mxchat_upload_pdf', [$this, 'handle_pdf_upload']);
51 54 add_action('wp_ajax_mxchat_remove_pdf', [$this, 'handle_pdf_remove']);
52 55 add_action('wp_ajax_nopriv_mxchat_remove_pdf', [$this, 'handle_pdf_remove']);
56 +}
53 57
54 - // Add these with your other add_action hooks
55 - add_action('wp_ajax_mxchat_upload_word', array($this, 'mxchat_handle_word_upload'));
56 - add_action('wp_ajax_nopriv_mxchat_upload_word', array($this, 'mxchat_handle_word_upload'));
57 - add_action('wp_ajax_mxchat_remove_word', array($this, 'mxchat_handle_word_remove'));
58 - add_action('wp_ajax_nopriv_mxchat_remove_word', array($this, 'mxchat_handle_word_remove'));
59 - add_action('wp_ajax_mxchat_check_word_status', array($this, 'mxchat_check_word_status'));
60 - add_action('wp_ajax_nopriv_mxchat_check_word_status', array($this, 'mxchat_check_word_status'));
58 +public function mxchat_handle_product_change($post_id, $post, $update) {
59 + // Ensure this is a product post type
60 + if ($post->post_type !== 'product') {
61 + return;
62 + }
61 63
62 - add_action('wp_ajax_nopriv_mxchat_handle_save_email_and_response', [$this, 'mxchat_handle_save_email_and_response']);
63 - add_action('wp_ajax_mxchat_handle_save_email_and_response', [$this, 'mxchat_handle_save_email_and_response']);
64 - add_action('wp_ajax_nopriv_mxchat_check_email_provided', [$this, 'mxchat_check_email_provided']);
65 - add_action('wp_ajax_mxchat_check_email_provided', [$this, 'mxchat_check_email_provided']);
64 + // Only generate embeddings if the product is published
65 + if ($post->post_status === 'publish') {
66 + // Delay the embedding slightly to ensure all product data is available
67 + add_action('shutdown', function() use ($post_id) {
68 + $product = wc_get_product($post_id);
69 + if ($product && $product->get_price() !== '') {
70 + $this->mxchat_store_product_embedding($product);
71 + } else {
72 + // Optionally, log or handle the case where product data is incomplete
73 + // error_log("Product {$post_id} does not have complete data. Embedding not generated.");
74 + }
75 + });
76 + }
66 77 }
67 78
79 +public function mxchat_handle_product_delete($post_id) {
80 + if (get_post_type($post_id) !== 'product') {
81 + return;
82 + }
68 83
84 + global $wpdb;
85 + $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
86 +
87 + // Delete the embedding associated with this product
88 + $wpdb->delete($table_name, array('source_url' => get_permalink($post_id)), array('%s'));
89 +}
90 +
91 +private function mxchat_store_product_embedding($product) {
92 + if (isset($this->options['enable_woocommerce_integration']) && $this->options['enable_woocommerce_integration'] === '1') {
93 +
94 + $source_url = get_permalink($product->get_id());
95 + $regular_price = $product->get_regular_price();
96 + $sale_price = $product->get_sale_price();
97 + $price = $sale_price ?: $regular_price;
98 +
99 + $description = $product->get_description() . "\n\n" .
100 + "Short Description: " . $product->get_short_description() . "\n" .
101 + "Price: " . $regular_price . "\n" .
102 + "Sale Price: " . ($sale_price ?: 'N/A') . "\n" .
103 + "SKU: " . $product->get_sku();
104 +
105 + global $wpdb;
106 + $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
107 +
108 + // Delete any existing embedding for this product
109 + $wpdb->delete($table_name, array('source_url' => $source_url), array('%s'));
110 +
111 + // Submit the new content and embedding to the database
112 + MxChat_Utils::submit_content_to_db($description, $source_url, $this->options['api_key']);
113 + }
114 +}
115 +
116 +
117 +
118 +
119 +
69 120 private function mxchat_increment_chat_count() {
70 121 $chat_count = get_option('mxchat_chat_count', 0);
71 122 $chat_count++;
72 123 update_option('mxchat_chat_count', $chat_count);
@@ -73,9 +124,9 @@
73 124 }
74 125
75 126 function mxchat_fetch_conversation_history() {
76 127 if (empty($_POST['session_id'])) {
77 - wp_send_json_error(['message' => esc_html__('Session ID missing.', 'mxchat')]);
128 + wp_send_json_error(['message' => 'Session ID missing.']);
78 129 wp_die();
79 130 }
80 131
81 132 $session_id = sanitize_text_field($_POST['session_id']);
@@ -110,86 +161,32 @@
110 161 }
111 162
112 163 return $formatted_history;
113 164 }
114 -
115 -
116 165 private function mxchat_fetch_conversation_history_for_ai($session_id) {
117 166 $history = get_option("mxchat_history_{$session_id}", []);
167 +
118 168 $formatted_history = [];
119 169
120 - // Adjusted for code-heavy conversations
121 - $max_tokens = 120000; // Context window size
122 - $reserved_tokens = 5000; // Space for system prompts + current query
123 - $current_token_count = 0;
124 -
125 - // Allowed HTML tags for content sanitization
126 - $allowed_tags = [
127 - 'pre' => ['class' => true],
128 - 'code' => ['class' => true],
129 - 'span' => ['class' => true],
130 - 'div' => ['class' => true],
131 - 'strong' => [],
132 - 'em' => []
133 - ];
134 -
135 - foreach (array_reverse($history) as $entry) {
136 - // Preserve code blocks while sanitizing other HTML
137 - $clean_content = wp_kses($entry['content'], $allowed_tags);
138 -
139 - // Detect code blocks in content
140 - $has_code = false;
141 -// Replace the HTML check with:
142 -// Allow messages that contain code blocks or are plain text
143 -if (strpos($clean_content, '<pre') === false &&
144 - strpos($clean_content, '<code') === false &&
145 - $clean_content !== strip_tags($entry['content'])) {
146 - continue;
147 -}
148 -
149 - // Skip entries that lost significant content during sanitization
150 - if (!$has_code && $clean_content !== strip_tags($entry['content'])) {
170 + foreach ($history as $entry) {
171 + // Skip messages containing HTML
172 + if ($entry['content'] !== strip_tags($entry['content'])) {
151 173 continue;
152 174 }
153 175
154 - // More accurate token estimation (1 token ≈ 4 characters)
155 - $token_estimate = ceil(mb_strlen($clean_content, 'UTF-8') / 4);
156 -
157 - // Check token budget with the new estimate
158 - if (($current_token_count + $token_estimate + $reserved_tokens) > $max_tokens) {
159 - // Try to fit partial content if it's the first entry
160 - if (empty($formatted_history)) {
161 - $clean_content = mb_substr($clean_content, 0, ($max_tokens - $reserved_tokens) * 4);
162 - $token_estimate = ceil(mb_strlen($clean_content, 'UTF-8') / 4);
163 - } else {
164 - break;
165 - }
166 - }
167 -
168 - // Add to formatted history
169 176 $formatted_history[] = [
170 177 'role' => $entry['role'],
171 - 'content' => $clean_content
178 + 'content' => $entry['content']
172 179 ];
173 -
174 - $current_token_count += $token_estimate;
175 180 }
176 181
177 - // Reverse back to maintain chronological order
178 - $formatted_history = array_reverse($formatted_history);
182 + return $formatted_history;
183 +}
179 184
180 - // Add system message about code context
181 - array_unshift($formatted_history, [
182 - 'role' => 'system',
183 - 'content' => 'Preserved code blocks are marked with [CODE BLOCK PRESERVED]. '
184 - . 'Maintain formatting and syntax highlighting when referencing code.'
185 - ]);
186 185
187 - return $formatted_history;
188 -}
189 186
190 187 public function register_routes() {
191 - //error_log(esc_html__('Registering MxChat REST routes', 'mxchat'));
188 + //error_log('Registering MxChat REST routes');
192 189
193 190 register_rest_route('mxchat/v1', '/stream', [
194 191 'methods' => 'GET',
195 192 'callback' => [$this, 'mxchat_stream_events'],
@@ -207,9 +204,9 @@
207 204 'callback' => [$this, 'handle_slack_interaction'],
208 205 'permission_callback' => [$this, 'verify_slack_request'],
209 206 ]);
210 207
211 - //error_log(esc_html__('MxChat REST routes registered', 'mxchat'));
208 + //error_log('MxChat REST routes registered');
212 209 }
213 210
214 211 /**
215 212 * Verify valid chat session
@@ -216,9 +213,9 @@
216 213 */
217 214 public function verify_chat_session($request) {
218 215 $session_id = $request->get_param('session_id');
219 216 if (empty($session_id)) {
220 - //error_log(esc_html__('Empty session ID in chat request', 'mxchat'));
217 + //error_log('Empty session ID in chat request');
221 218 return false;
222 219 }
223 220
224 221 $chat_mode = get_option("mxchat_mode_{$session_id}", 'ai');
@@ -235,9 +232,9 @@
235 232 // Get the Slack signing secret from your plugin options
236 233 $valid_key = $this->options['live_agent_secret_key'] ?? '';
237 234
238 235 if (empty($valid_key)) {
239 - //error_log(esc_html__('Slack signing secret not configured', 'mxchat'));
236 + //error_log('Slack signing secret not configured');
240 237 return false;
241 238 }
242 239
243 240 $timestamp = $request->get_header('X-Slack-Request-Timestamp');
@@ -244,9 +241,9 @@
244 241 $slack_signature = $request->get_header('X-Slack-Signature');
245 242
246 243 // Verify timestamp to prevent replay attacks
247 244 if (abs(time() - intval($timestamp)) > 300) {
248 - //error_log(esc_html__('Slack request timestamp too old', 'mxchat'));
245 + //error_log('Slack request timestamp too old');
249 246 return false;
250 247 }
251 248
252 249 // Get raw request body
@@ -269,9 +266,9 @@
269 266 $session_id = sanitize_text_field($request->get_param('session_id'));
270 267 $last_seen_id = sanitize_text_field($request->get_param('last_seen_id')) ?: '';
271 268
272 269 if (empty($session_id)) {
273 - echo esc_html__("event: error\ndata: ", 'mxchat') . esc_html__('Missing session_id', 'mxchat') . "\n\n";
270 + echo "event: error\ndata: Missing session_id\n\n";
274 271 flush();
275 272 exit;
276 273 }
277 274
@@ -283,12 +280,12 @@
283 280 });
284 281
285 282 // Send new messages if available
286 283 if (!empty($new_messages)) {
287 - echo esc_html__("event: newMessages\ndata: ", 'mxchat') . json_encode(array_values($new_messages)) . "\n\n";
284 + echo "event: newMessages\ndata: " . json_encode(array_values($new_messages)) . "\n\n";
288 285 } else {
289 286 // Keep the connection alive
290 - echo esc_html__("event: keepAlive\ndata: ", 'mxchat') . "{}\n\n";
287 + echo "event: keepAlive\ndata: {}\n\n";
291 288 }
292 289 flush();
293 290 exit;
294 291 }
@@ -294,380 +291,68 @@
294 291 }
295 292
296 293
297 294
298 -
299 295 private function mxchat_save_chat_message($session_id, $role, $message) {
300 296 global $wpdb;
301 297
298 + // Define the table name for chat transcripts
302 299 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
303 - //error_log("[DEBUG] mxchat_save_chat_message -> START for session_id: {$session_id}, role: {$role}");
304 300
305 - // 1) Extract agent name if present
306 - $agent_name = '';
307 - if (preg_match('/^Agent: (.*?) - /', $message, $matches)) {
308 - $agent_name = $matches[1];
309 - $message = str_replace("Agent: $agent_name - ", '', $message);
310 -
311 - $session_meta_key = "mxchat_agent_name_{$session_id}";
312 - if (empty(get_option($session_meta_key))) {
313 - update_option($session_meta_key, $agent_name);
314 - //error_log("[DEBUG] mxchat_save_chat_message -> Stored agent_name in option: {$session_meta_key} => {$agent_name}");
315 - }
316 - }
317 -
318 - // 2) Generate unique message_id
301 + // Generate a unique message ID
319 302 $message_id = uniqid();
320 - //error_log("[DEBUG] mxchat_save_chat_message -> Generated message_id: {$message_id}");
321 303
322 - // 3) Determine user_id
304 + // Get user details
323 305 $user_id = is_user_logged_in() ? get_current_user_id() : 0;
324 -
325 - // 4) Determine user_identifier
326 - $user_identifier = $agent_name
327 - ? $agent_name
328 - : MxChat_User::mxchat_get_user_identifier();
329 -
330 - // 5) Determine displayed_name
306 + $user_identifier = MxChat_User::mxchat_get_user_identifier();
331 307 $user_email = MxChat_User::mxchat_get_user_email();
332 - $displayed_name = $agent_name ? $agent_name : ($user_email ?: $user_identifier);
333 308
334 - // 6) Check for a saved email in wp_options
335 - $email_option_key = "mxchat_email_{$session_id}";
336 - $saved_email = get_option($email_option_key);
337 - //error_log("[DEBUG] mxchat_save_chat_message -> Checking wp_options for email_option_key: {$email_option_key}, found: {$saved_email}");
338 -
339 - // If found, update DB user_email
340 - if ($saved_email) {
341 - $update_res = $wpdb->update(
342 - $table_name,
343 - ['user_email' => $saved_email],
344 - ['session_id' => $session_id],
345 - ['%s'],
346 - ['%s']
347 - );
348 - //error_log("[DEBUG] mxchat_save_chat_message -> Attempted DB user_email update for session_id {$session_id}. update_res: {$update_res}");
349 - }
350 -
351 - // 7) Save to session history in wp_options
352 - $history_key = "mxchat_history_{$session_id}";
353 - $history = get_option($history_key, []);
309 + // Save the message to the session history
310 + $history = get_option("mxchat_history_{$session_id}", []);
354 311 $history[] = [
355 312 'id' => $message_id,
356 313 'role' => $role,
357 314 'content' => $message,
358 315 'timestamp' => round(microtime(true) * 1000),
359 - 'agent_name' => $displayed_name,
360 316 ];
361 - update_option($history_key, $history);
362 - //error_log("[DEBUG] mxchat_save_chat_message -> Updated session history in option: {$history_key}");
317 + update_option("mxchat_history_{$session_id}", $history);
363 318
364 - // 8) Save the message to DB (INSERT)
365 - $insert_data = [
366 - 'user_id' => $user_id,
367 - 'user_identifier'=> $user_identifier,
368 - 'user_email' => $saved_email ?: $user_email,
369 - 'session_id' => $session_id,
370 - 'role' => $role,
371 - 'message' => $message,
372 - 'timestamp' => current_time('mysql', 1),
373 - ];
374 - $wpdb->insert($table_name, $insert_data);
375 - //error_log("[DEBUG] mxchat_save_chat_message -> Inserted message into DB. row_id: {$wpdb->insert_id}, data: " . print_r($insert_data, true));
319 + // Save the message to the database
320 + $wpdb->insert($table_name, [
321 + 'user_id' => $user_id,
322 + 'user_identifier' => $user_identifier,
323 + 'user_email' => $user_email,
324 + 'session_id' => $session_id,
325 + 'role' => $role,
326 + 'message' => $message,
327 + 'timestamp' => current_time('mysql', 1),
328 + ]);
376 329
377 - //error_log("[DEBUG] mxchat_save_chat_message -> END for session_id: {$session_id}");
330 + // Log the saved message for debugging
331 + //error_log("Message saved to database. Session ID: $session_id, Role: $role, Message ID: $message_id, Content: $message");
332 +
378 333 return $message_id;
379 334 }
380 335
381 -public function mxchat_handle_save_email_and_response() {
382 - //error_log('[DEBUG] ---------- mxchat_handle_save_email_and_response START ----------');
383 -
384 - // Validate nonce
385 - if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'mxchat_chat_nonce')) {
386 - error_log(esc_html__('[ERROR] Invalid nonce in mxchat_handle_save_email_and_response', 'mxchat'));
387 - wp_send_json_error(['message' => esc_html__('Invalid nonce.', 'mxchat')]);
388 - wp_die();
389 - }
390 -
391 - $session_id = isset($_POST['session_id']) ? sanitize_text_field($_POST['session_id']) : '';
392 - $email = isset($_POST['email']) ? sanitize_email($_POST['email']) : '';
393 -
394 - //error_log("[DEBUG] handle_save_email_and_response -> session_id: {$session_id}, email: {$email}");
395 -
396 - if (empty($session_id) || empty($email)) {
397 - //error_log("[ERROR] Missing session_id or email: session_id={$session_id}, email={$email}");
398 - wp_send_json_error(['message' => esc_html__('Session ID or email is missing.', 'mxchat')]);
399 - wp_die();
400 - }
401 -
402 - // 1) Always store in wp_options
403 - $option_key = "mxchat_email_{$session_id}";
404 - update_option($option_key, $email);
405 - //error_log("[DEBUG] handle_save_email_and_response -> updated option: {$option_key} => {$email}");
406 -
407 - // 2) (Optional) Also store in DB if a row already exists
408 - global $wpdb;
409 - $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
410 -
411 - // Make sure we have a valid placeholder in prepare
412 - $sql = $wpdb->prepare("SELECT COUNT(*) FROM {$table_name} WHERE session_id = %s", $session_id);
413 - $session_count = $wpdb->get_var($sql);
414 -
415 - //error_log("[DEBUG] handle_save_email_and_response -> session_count for {$session_id}: {$session_count} (SQL: {$sql})");
416 -
417 - if ($session_count) {
418 - // Update user_email if row(s) exist
419 - $update_sql = $wpdb->prepare(
420 - "UPDATE {$table_name} SET user_email = %s WHERE session_id = %s",
421 - $email,
422 - $session_id
423 - );
424 - $wpdb->query($update_sql);
425 - //error_log("[DEBUG] handle_save_email_and_response -> DB updated: {$update_sql}");
426 - } else {
427 - //error_log("[INFO] handle_save_email_and_response -> No DB entry for {$session_id}, so email is only in wp_options.");
428 - }
429 -
430 - // Provide success response
431 - $bot_message = __('Thanks for providing your email! You can continue chatting now.', 'mxchat');
432 - //error_log("[DEBUG] handle_save_email_and_response -> success, returning bot_message: {$bot_message}");
433 - wp_send_json_success(['message' => $bot_message]);
434 - wp_die();
435 -}
436 -
437 -public function mxchat_check_email_provided() {
438 - //error_log('[DEBUG] ---------- mxchat_check_email_provided START ----------');
439 -
440 - if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'mxchat_chat_nonce')) {
441 - //error_log('[ERROR] Invalid nonce in mxchat_check_email_provided');
442 - wp_send_json_error(['message' => esc_html__('Invalid nonce', 'mxchat')]);
443 - }
444 -
445 - $session_id = isset($_POST['session_id']) ? sanitize_text_field($_POST['session_id']) : '';
446 - if (empty($session_id)) {
447 - //error_log('[ERROR] No session ID provided in mxchat_check_email_provided');
448 - wp_send_json_error(['message' => esc_html__('No session ID provided', 'mxchat')]);
449 - }
450 -
451 - // Check if the user is logged in
452 - if (is_user_logged_in()) {
453 - $current_user = wp_get_current_user();
454 - //error_log("[DEBUG] User is logged in as {$current_user->user_email}");
455 - wp_send_json_success(['logged_in' => true, 'email' => $current_user->user_email]);
456 - }
457 -
458 - $option_key = "mxchat_email_{$session_id}";
459 - $stored_email = get_option($option_key, '');
460 -
461 - //error_log("[DEBUG] mxchat_check_email_provided -> Checking option: {$option_key}, found: {$stored_email}");
462 -
463 - if (!empty($stored_email)) {
464 - //error_log("[DEBUG] mxchat_check_email_provided -> Email found, returning success");
465 - wp_send_json_success(['email' => $stored_email]);
466 - } else {
467 - //error_log("[DEBUG] mxchat_check_email_provided -> No email found, returning error");
468 - wp_send_json_error(['message' => esc_html__('No email found', 'mxchat')]);
469 - }
470 -}
471 -
472 -
473 -// First, add this helper function to get the highest rate limit for a user's roles
474 -private function get_user_role_rate_limit($user_id) {
475 - //error_log(esc_html__("Checking rate limit for user ID: ", 'mxchat') . $user_id);
476 -
477 - if (!$user_id) {
478 - //error_log(esc_html__("No user ID provided, using logged-out limit: ", 'mxchat') . ($this->options['rate_limit_logged_out'] ?? '10'));
479 - return $this->options['rate_limit_logged_out'] ?? '10';
480 - }
481 -
482 - $user = get_userdata($user_id);
483 - if (!$user || !$user->roles) {
484 - //error_log(esc_html__("No user data or roles found, using logged-out limit: ", 'mxchat') . ($this->options['rate_limit_logged_out'] ?? '10'));
485 - return $this->options['rate_limit_logged_out'] ?? '10';
486 - }
487 -
488 - //error_log(esc_html__("User roles: ", 'mxchat') . print_r($user->roles, true));
489 - //error_log(esc_html__("Available role rate limits: ", 'mxchat') . print_r($this->options['role_rate_limits'] ?? [], true));
490 -
491 - $max_limit = 0;
492 - foreach ($user->roles as $role) {
493 - //error_log(esc_html__("Checking limit for role: ", 'mxchat') . $role);
494 - if (isset($this->options['role_rate_limits'][$role])) {
495 - $role_limit = $this->options['role_rate_limits'][$role];
496 - //error_log(esc_html__("Found limit for role ", 'mxchat') . $role . esc_html__(": ", 'mxchat') . $role_limit);
497 -
498 - if ($role_limit === 'unlimited') {
499 - //error_log(esc_html__("Returning unlimited for role: ", 'mxchat') . $role);
500 - return 'unlimited';
501 - }
502 -
503 - $max_limit = max($max_limit, (int)$role_limit);
504 - //error_log(esc_html__("Current max limit: ", 'mxchat') . $max_limit);
505 - } else {
506 - //error_log(esc_html__("No limit found for role: ", 'mxchat') . $role);
507 - }
508 - }
509 -
510 - $final_limit = $max_limit > 0 ? (string)$max_limit : '100';
511 - //error_log(esc_html__("Final rate limit: ", 'mxchat') . $final_limit);
512 - return $final_limit;
513 -}
514 -
515 -
516 -// Add this to your plugin's main PHP file
517 -public function mxchat_check_new_messages() {
518 - if (!isset($_POST['session_id']) || !isset($_POST['last_seen_id'])) {
519 - wp_send_json_error(['message' => 'Missing required parameters']);
520 - wp_die();
521 - }
522 -
523 - $session_id = sanitize_text_field($_POST['session_id']);
524 - $last_seen_id = sanitize_text_field($_POST['last_seen_id']);
525 -
526 - // Get chat history
527 - $history = get_option("mxchat_history_{$session_id}", []);
528 -
529 - if (empty($history)) {
530 - wp_send_json_success([
531 - 'hasNewMessages' => false,
532 - 'new_messages' => []
533 - ]);
534 - wp_die();
535 - }
536 -
537 - // Filter new messages
538 - $new_messages = array_filter($history, function($message) use ($last_seen_id) {
539 - return isset($message['id']) && $message['id'] > $last_seen_id;
540 - });
541 -
542 - // Sort by ID to ensure proper order
543 - usort($new_messages, function($a, $b) {
544 - return $a['id'] <=> $b['id'];
545 - });
546 -
547 - wp_send_json_success([
548 - 'hasNewMessages' => !empty($new_messages),
549 - 'new_messages' => array_values($new_messages),
550 - 'latestMessageId' => end($new_messages)['id'] ?? $last_seen_id
551 - ]);
552 - wp_die();
553 -}
554 -
555 336 public function mxchat_handle_chat_request() {
556 337 global $wpdb;
557 338
558 -
559 - // Check if MX Chat Moderation is active
560 - if (class_exists('MX_Chat_Moderation')) {
561 - // Get user email and IP
562 - $user_email = '';
563 - $user_ip = $_SERVER['REMOTE_ADDR'];
564 -
565 - // If user is logged in, get their email
566 - if (is_user_logged_in()) {
567 - $current_user = wp_get_current_user();
568 - $user_email = $current_user->user_email;
569 - }
570 -
571 - // Create ban handler instance
572 - $ban_handler = new MX_Chat_Ban_Handler();
573 -
574 - // Check if user is banned by IP
575 - if ($ban_handler->check_ban($user_ip, 'ip')) {
576 - wp_send_json([
577 - 'success' => false,
578 - 'message' => esc_html__('Access denied. Your IP address has been banned.', 'mxchat'),
579 - 'status' => 'banned'
580 - ]);
581 - wp_die();
582 - }
583 -
584 - // If user is logged in, also check email
585 - if (!empty($user_email) && $ban_handler->check_ban($user_email, 'email')) {
586 - wp_send_json([
587 - 'success' => false,
588 - 'message' => esc_html__('Access denied. Your email address has been banned.', 'mxchat'),
589 - 'status' => 'banned'
590 - ]);
591 - wp_die();
592 - }
593 - }
594 -
595 -
596 - // Reset fallback response at the start of each request
597 - $this->fallbackResponse = ['text' => '', 'html' => '', 'images' => []];
598 - $this->productCardHtml = '';
599 -
600 - // Get the actual WordPress user ID if logged in
601 - $is_logged_in = is_user_logged_in();
602 - if ($is_logged_in) {
603 - $user_id = get_current_user_id(); // This will get the actual WordPress user ID
604 - } else {
605 - // For logged-out users, use your existing identifier method
606 - $user_id = $this->mxchat_get_user_identifier();
607 - }
608 -
609 339 // Get and sanitize the user identifier
340 + $user_id = $this->mxchat_get_user_identifier();
610 341 $user_id = sanitize_key($user_id);
342 + //error_log("User ID: $user_id");
611 343
612 - // Determine if user is logged in
613 - $is_logged_in = is_user_logged_in();
614 - //error_log("User logged in status: " . ($is_logged_in ? 'true' : 'false'));
344 + // Setup rate limiting
345 + $rate_limit_transient_key = 'mxchat_chat_limit_' . $user_id;
346 + $chat_count = get_transient($rate_limit_transient_key) ?: 0;
615 347
616 - // Get rate limit based on user status
617 - // Get rate limit based on user status
618 - $rate_limit = $is_logged_in
619 - ? $this->get_user_role_rate_limit($user_id)
620 - : ($this->options['rate_limit_logged_out'] ?? '10');
621 -
622 - //error_log("Selected rate limit: " . $rate_limit);
623 -
624 - // Rest of your code remains the same
625 - // If rate limit is 'unlimited', skip rate limiting checks
626 - if ($rate_limit !== 'unlimited') {
627 - // Convert rate limit to integer
628 - $rate_limit = intval($rate_limit);
629 - // Setup rate limiting
630 - $rate_limit_transient_key = 'mxchat_chat_limit_' . $user_id;
631 - $chat_count = get_transient($rate_limit_transient_key);
632 - if ($chat_count === false) {
633 - // Initialize new counter if none exists
634 - $chat_count = 0;
635 - }
636 - // Check if user has exceeded their rate limit
637 - if ($chat_count >= $rate_limit) {
638 - // Get custom rate limit message or use default
639 - $rate_limit_message = isset($this->options['rate_limit_message'])
640 - ? $this->options['rate_limit_message']
641 - : esc_html__('Rate limit exceeded. Please try again later.', 'mxchat');
642 - // Replace placeholder if it exists in the message
643 - $rate_limit_message = str_replace(
644 - array('{limit}', '{count}', '{remaining}'),
645 - array($rate_limit, $chat_count, max(0, $rate_limit - $chat_count)),
646 - $rate_limit_message
647 - );
648 - wp_send_json([
649 - 'success' => false,
650 - 'message' => $rate_limit_message,
651 - 'status' => 'rate_limit_exceeded',
652 - 'limit' => $rate_limit,
653 - 'count' => $chat_count
654 - ]);
655 - wp_die();
656 - }
657 - // Increment the counter
658 - $chat_count++;
659 - // Store the updated count with 24-hour expiration
660 - set_transient($rate_limit_transient_key, $chat_count, DAY_IN_SECONDS);
661 - }
662 -
663 - // Rest of your existing code...
348 + // Retrieve the session ID from the client's POST data
664 349 $session_id = isset($_POST['session_id']) ? sanitize_text_field($_POST['session_id']) : '';
665 350 //error_log("Session ID: $session_id");
666 351
667 352 if (empty($session_id)) {
668 353 //error_log("Error: Session ID is missing.");
669 - wp_send_json_error(esc_html__('Session ID is missing.', 'mxchat'));
354 + wp_send_json_error('Session ID is missing.');
670 355 wp_die();
671 356 }
672 357
673 358 // Validate and sanitize the incoming message
@@ -672,39 +357,16 @@
672 357
673 358 // Validate and sanitize the incoming message
674 359 if (empty($_POST['message'])) {
675 360 //error_log("Error: No message received.");
676 - wp_send_json_error(esc_html__('No message received.', 'mxchat'));
361 + wp_send_json_error('No message received.');
677 362 wp_die();
678 363 }
679 364
680 365
681 -// Modify the message sanitization to preserve PHP tags in code blocks
682 -$allowed_tags = [
683 - 'pre' => [],
684 - 'code' => ['class' => true],
685 - 'span' => ['class' => true],
686 - 'div' => ['class' => true],
687 -];
366 + $message = wp_strip_all_tags($_POST['message'], false);
367 + $message = trim($message);
688 368
689 -// First preserve code blocks
690 -$message = preg_replace_callback('/<pre><code.*?>.*?<\/code><\/pre>/s', function($matches) {
691 - return htmlspecialchars_decode($matches[0]);
692 -}, $_POST['message']);
693 -
694 -// Then apply sanitization
695 -$message = wp_kses($message, $allowed_tags);
696 -
697 -// Decode code blocks
698 -$message = preg_replace_callback('/(&lt;pre&gt;&lt;code.*?&gt;.*?&lt;\/code&gt;&lt;\/pre&gt;)/s', function($matches) {
699 - return htmlspecialchars_decode($matches[1]);
700 -}, $message);
701 -
702 -$message = trim($message);
703 -
704 -// Preserve code blocks from markdown conversion
705 -$message = preg_replace('/```(\w+)?\s*([\s\S]+?)```/s', '<pre><code class="$1">$2</code></pre>', $message);
706 -
707 369 // Save the user's message
708 370 $this->mxchat_save_chat_message($session_id, 'user', $message);
709 371
710 372 // Check if the message is an email address
@@ -713,9 +375,9 @@
713 375 $this->add_email_to_loops($message);
714 376
715 377 // Send success response
716 378 $response_message = $this->options['email_capture_response'] ??
717 - esc_html__('Thank you! Your coupon is on the way!', 'mxchat');
379 + 'Thank you! Your coupon is on the way!';
718 380
719 381 wp_send_json([
720 382 'success' => true,
721 383 'status' => 'email_captured',
@@ -723,8 +385,11 @@
723 385 ]);
724 386 wp_die();
725 387 }
726 388
389 + // Initialize response variables
390 + $this->fallbackResponse = ['text' => '', 'html' => '', 'images' => []];
391 + $this->productCardHtml = '';
727 392 $intent_info = '';
728 393
729 394 // Check chat mode
730 395 $chat_mode = get_option("mxchat_mode_{$session_id}", 'ai');
@@ -753,9 +418,9 @@
753 418 'chat_mode' => 'ai'
754 419 ];
755 420
756 421 // Save the mode switch message
757 - $this->mxchat_save_chat_message($session_id, 'system', esc_html__('Switched to AI chat mode', 'mxchat'));
422 + $this->mxchat_save_chat_message($session_id, 'system', 'Switched to AI chat mode');
758 423 $this->mxchat_save_chat_message($session_id, 'bot', $this->fallbackResponse['text']);
759 424
760 425 // Send response and exit
761 426 wp_send_json($response_data);
@@ -767,97 +432,58 @@
767 432 //error_log("Message sent to agent.");
768 433
769 434 wp_send_json_success([
770 435 'status' => 'waiting_for_agent',
771 - 'message' => esc_html__('Message sent to live agent.', 'mxchat')
436 + 'message' => 'Message sent to live agent.'
772 437 ]);
773 438 } catch (\Exception $e) {
774 439 //error_log("Error sending message to agent: " . $e->getMessage());
775 - wp_send_json_error(esc_html__('Failed to send message to agent', 'mxchat'));
440 + wp_send_json_error('Failed to send message to agent');
776 441 }
777 442 wp_die();
778 443 }
779 444 }
780 445
781 - // Step 1: Check for new PDF URL in the message
782 - if (preg_match('/https?:\/\/[^\s"]+/i', $message, $matches)) {
783 - $new_pdf_url = $matches[0];
446 + // Step 1: Check for new PDF URL in the message
447 + if (preg_match('/https?:\/\/[^\s"]+/i', $message, $matches)) {
448 + $new_pdf_url = $matches[0];
784 449
785 - // Check if this is likely a PDF-related request
786 - $pdf_keywords = ['pdf', 'document', 'read', 'analyze'];
787 - $is_pdf_request = false;
450 + // Validate HTTPS
451 + if (wp_http_validate_url($new_pdf_url) && parse_url($new_pdf_url, PHP_URL_SCHEME) === 'https') {
452 + $existing_pdf_url = get_transient('mxchat_pdf_url_' . $session_id);
788 453
789 - foreach ($pdf_keywords as $keyword) {
790 - if (stripos($message, $keyword) !== false) {
791 - $is_pdf_request = true;
792 - break;
793 - }
794 - }
454 + if ($existing_pdf_url !== $new_pdf_url) {
455 + // Clear all PDF-related transients
456 + $this->clear_pdf_transients($session_id);
795 457
796 - // If it looks like a PDF request or we're waiting for a PDF URL
797 - if ($is_pdf_request || get_transient('mxchat_waiting_for_pdf_url_' . $session_id)) {
798 - // Validate HTTPS
799 - if (wp_http_validate_url($new_pdf_url) && parse_url($new_pdf_url, PHP_URL_SCHEME) === 'https') {
800 - // Extract filename from URL
801 - $pdf_filename = basename(parse_url($new_pdf_url, PHP_URL_PATH));
458 + // Process new PDF
459 + $max_pages = $this->options['pdf_max_pages'] ?? 69;
460 + $embeddings = $this->fetch_and_split_pdf_pages($new_pdf_url, $max_pages);
802 461
803 - // Clear previous PDF transients
804 - $this->clear_pdf_transients($session_id);
462 + if ($embeddings === 'too_many_pages') {
463 + $error_text = sprintf(
464 + $this->options['pdf_intent_error_text'] ?? "The provided PDF exceeds the maximum allowed limit of %d pages. Please provide a smaller document.",
465 + $max_pages
466 + );
467 + $this->fallbackResponse['text'] = $error_text;
468 + } elseif ($embeddings) {
469 + // Store new PDF information
470 + set_transient('mxchat_pdf_url_' . $session_id, $new_pdf_url, HOUR_IN_SECONDS);
471 + set_transient('mxchat_pdf_embeddings_' . $session_id, $embeddings, HOUR_IN_SECONDS);
472 + set_transient('mxchat_include_pdf_in_context_' . $session_id, true, HOUR_IN_SECONDS);
805 473
806 - // Process new PDF
807 - $max_pages = $this->options['pdf_max_pages'] ?? 69;
808 - $embeddings = $this->fetch_and_split_pdf_pages($new_pdf_url, $max_pages);
474 + $success_text = $this->options['pdf_intent_success_text'] ?? "I've processed the new PDF. What questions do you have about it?";
475 + $this->fallbackResponse['text'] = $success_text;
476 + } else {
477 + $error_text = $this->options['pdf_intent_error_text'] ?? "Sorry, I couldn't process the new PDF. Please ensure it's a valid file.";
478 + $this->fallbackResponse['text'] = $error_text;
479 + }
809 480
810 - if ($embeddings === 'too_many_pages') {
811 - $error_text = sprintf(
812 - $this->options['pdf_intent_error_text'] ??
813 - esc_html__("The provided PDF exceeds the maximum allowed limit of %d pages. Please provide a smaller document.", 'mxchat'),
814 - $max_pages
815 - );
816 - $this->fallbackResponse['text'] = $error_text;
817 - } elseif ($embeddings) {
818 - // Store new PDF information
819 - // Create a more meaningful filename from URL
820 - $pdf_filename = basename(parse_url($new_pdf_url, PHP_URL_PATH));
821 -
822 - // If the filename is generic (like results_download.php), create a more descriptive one
823 - if (in_array($pdf_filename, ['results_download.php', 'download.php', 'view.php', 'pdf.php']) ||
824 - strpos($pdf_filename, '.php') !== false) {
825 - // Create a timestamp-based name
826 - $pdf_filename = 'Document_' . date('Y-m-d_H-i') . '.pdf';
827 - }
828 -
829 - set_transient('mxchat_pdf_url_' . $session_id, $new_pdf_url, HOUR_IN_SECONDS);
830 - set_transient('mxchat_pdf_filename_' . $session_id, $pdf_filename, HOUR_IN_SECONDS);
831 - set_transient('mxchat_pdf_embeddings_' . $session_id, $embeddings, HOUR_IN_SECONDS);
832 - set_transient('mxchat_include_pdf_in_context_' . $session_id, true, HOUR_IN_SECONDS);
833 -
834 - $success_text = $this->options['pdf_intent_success_text'] ??
835 - esc_html__("I've processed the new PDF '{$pdf_filename}'. What questions do you have about it?", 'mxchat');
836 -
837 - // Return success with filename for UI update
838 - wp_send_json([
839 - 'success' => true,
840 - 'message' => $success_text,
841 - 'data' => [
842 - 'filename' => $pdf_filename
843 - ]
844 - ]);
845 - wp_die();
846 - } else {
847 - $error_text = $this->options['pdf_intent_error_text'] ??
848 - esc_html__("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat');
849 - $this->fallbackResponse['text'] = $error_text;
850 - }
851 -
852 - wp_send_json([
853 - 'success' => false,
854 - 'message' => $this->fallbackResponse['text']
855 - ]);
856 - wp_die();
857 - }
481 + wp_send_json(['message' => $this->fallbackResponse['text']]);
482 + wp_die();
858 483 }
859 484 }
485 + }
860 486
861 487 // Step 2: Detect intent and handle intent-based responses
862 488 $intent_matched = $this->mxchat_check_intent_and_invoke_callback($message, $user_id, $session_id);
863 489 //error_log("Intent Matched: " . ($intent_matched ? "Yes" : "No"));
@@ -885,9 +511,9 @@
885 511 // Generate embedding for the user's query
886 512 $user_message_embedding = $this->mxchat_generate_embedding($message, $this->options['api_key']);
887 513 if (!is_array($user_message_embedding)) {
888 514 //error_log("Failed to generate message embedding for session $session_id");
889 - wp_send_json_error(esc_html__('Error processing your message.', 'mxchat'));
515 + wp_send_json_error('Error processing your message.');
890 516 wp_die();
891 517 }
892 518
893 519 // Build context with both knowledge base and PDF content if available
@@ -898,38 +524,22 @@
898 524 if (!empty($relevant_content)) {
899 525 $context_content .= "Relevant content from knowledge database:\n" . $relevant_content . "\n\n";
900 526 }
901 527
902 -
903 - // Check for and include PDF content
904 - $pdf_url = get_transient('mxchat_pdf_url_' . $session_id);
905 - $pdf_embeddings = get_transient('mxchat_pdf_embeddings_' . $session_id);
906 - $pdf_filename = get_transient('mxchat_pdf_filename_' . $session_id);
907 - if ($pdf_url && $pdf_embeddings && get_transient('mxchat_include_pdf_in_context_' . $session_id)) {
908 - $relevant_pdf_pages = $this->find_relevant_pdf_pages($user_message_embedding, $pdf_embeddings);
909 - if (!empty($relevant_pdf_pages)) {
910 - $context_content .= "Relevant content from PDF document '{$pdf_filename}':\n";
911 - foreach ($relevant_pdf_pages as $page_data) {
912 - $context_content .= "Page {$page_data['page_number']} of '{$pdf_filename}': {$page_data['text']}\n";
913 - }
914 - $context_content .= "\n";
528 + // Check for and include PDF content if available
529 + $pdf_url = get_transient('mxchat_pdf_url_' . $session_id);
530 + $pdf_embeddings = get_transient('mxchat_pdf_embeddings_' . $session_id);
531 + if ($pdf_url && $pdf_embeddings && get_transient('mxchat_include_pdf_in_context_' . $session_id)) {
532 + $relevant_pdf_pages = $this->find_relevant_pdf_pages($user_message_embedding, $pdf_embeddings);
533 + if (!empty($relevant_pdf_pages)) {
534 + $context_content .= "Relevant content from PDF:\n";
535 + foreach ($relevant_pdf_pages as $page_data) {
536 + $context_content .= "Page {$page_data['page_number']}: {$page_data['text']}\n";
915 537 }
538 + $context_content .= "\n";
916 539 }
540 + }
917 541
918 - // Check for and include Word content
919 - $word_url = get_transient('mxchat_word_url_' . $session_id);
920 - $word_embeddings = get_transient('mxchat_word_embeddings_' . $session_id);
921 - $word_filename = get_transient('mxchat_word_filename_' . $session_id);
922 - if ($word_url && $word_embeddings && get_transient('mxchat_include_word_in_context_' . $session_id)) {
923 - $relevant_word_chunks = $this->word_handler->mxchat_find_relevant_word_chunks($user_message_embedding, $word_embeddings);
924 - if (!empty($relevant_word_chunks)) {
925 - $context_content .= "Relevant content from Word document '{$word_filename}':\n";
926 - foreach ($relevant_word_chunks as $chunk_data) {
927 - $context_content .= "Section {$chunk_data['chunk_number']} of '{$word_filename}': {$chunk_data['text']}\n";
928 - }
929 - $context_content .= "\n";
930 - }
931 - }
932 542 // Generate the response using the full context
933 543 $response = $this->mxchat_generate_response(
934 544 $context_content,
935 545 $this->options['api_key'],
@@ -934,9 +544,8 @@
934 544 $context_content,
935 545 $this->options['api_key'],
936 546 $this->options['xai_api_key'],
937 547 $this->options['claude_api_key'],
938 - $this->options['deepseek_api_key'],
939 548 $conversation_history
940 549 );
941 550
942 551 $this->mxchat_save_chat_message($session_id, 'bot', $response);
@@ -960,138 +569,212 @@
960 569 wp_send_json($response_data);
961 570 wp_die();
962 571 }
963 572
573 +
574 +// Helper function to clear PDF-related transients
575 +private function clear_pdf_transients($session_id) {
576 + delete_transient('mxchat_pdf_url_' . $session_id);
577 + delete_transient('mxchat_pdf_embeddings_' . $session_id);
578 + delete_transient('mxchat_include_pdf_in_context_' . $session_id);
579 + delete_transient('mxchat_waiting_for_pdf_url_' . $session_id);
580 +}
581 +
964 582 // New function to check intents and invoke the callback function
965 583 private function mxchat_check_intent_and_invoke_callback($message, $user_id, $session_id) {
966 584 global $wpdb;
585 +
586 + // Check chat mode
967 587 $chat_mode = get_option("mxchat_mode_{$session_id}", 'ai');
968 -
969 - //error_log('🔍 MXCHAT DEBUG: Intent Check Started ==================');
970 - //error_log("🔍 MXCHAT DEBUG: Message: '$message'");
971 - //error_log("🔍 MXCHAT DEBUG: Chat Mode: $chat_mode");
972 -
588 + //error_log("Checking intents for mode: " . $chat_mode);
589 +
973 590 // Generate the user embedding
974 - //error_log('🔄 MXCHAT DEBUG: Generating user embedding');
975 591 $user_embedding = $this->mxchat_generate_embedding($message, $this->options['api_key']);
976 592 if (!is_array($user_embedding)) {
977 - //error_log('❌ MXCHAT DEBUG: Failed to generate user embedding');
593 + //error_log("Failed to generate user embedding");
978 594 return false;
979 595 }
980 - //error_log('✅ MXCHAT DEBUG: User embedding generated successfully');
981 -
596 +
982 597 // Fetch intents from the database
983 598 $table_name = $wpdb->prefix . 'mxchat_intents';
599 +
984 600 if ($chat_mode === 'agent') {
985 - //error_log('🔍 MXCHAT DEBUG: Agent mode - fetching only chatbot switch intent');
601 + // Only fetch the switch intent when in agent mode
986 602 $query = $wpdb->prepare(
987 603 "SELECT * FROM $table_name WHERE callback_function = %s",
988 604 'mxchat_handle_switch_to_chatbot_intent'
989 605 );
606 + //error_log("Searching for switch intent with query: " . $query);
990 607 $intents = $wpdb->get_results($query);
608 + //error_log("Found " . count($intents) . " switch intents");
991 609 } else {
992 - //error_log('🔍 MXCHAT DEBUG: AI mode - fetching all intents');
993 610 $intents = $wpdb->get_results("SELECT * FROM $table_name");
994 611 }
995 -
996 - //error_log('🔍 MXCHAT DEBUG: Found ' . count($intents) . ' intents to check');
997 -
612 +
998 613 if (empty($intents)) {
999 - //error_log('❌ MXCHAT DEBUG: No intents found in database');
614 + //error_log("No intents found in database");
1000 615 return false;
1001 616 }
1002 -
617 +
1003 618 $highest_similarity = -INF;
1004 619 $matched_intent = null;
1005 -
1006 - //error_log('📊 MXCHAT DEBUG: Intent Similarity Scores ==================');
620 +
1007 621 foreach ($intents as $intent) {
1008 - //error_log("🔄 MXCHAT DEBUG: Checking intent: '{$intent->intent_label}' (callback: {$intent->callback_function})");
1009 -
622 + //error_log("Checking intent: " . $intent->intent_label);
1010 623 $intent_embedding_serialized = $intent->embedding_vector;
1011 624 $intent_embedding = $intent_embedding_serialized
1012 625 ? unserialize($intent_embedding_serialized, ['allowed_classes' => false])
1013 626 : null;
1014 -
627 +
1015 628 if (!is_array($intent_embedding)) {
1016 - //error_log("❌ MXCHAT DEBUG: Invalid embedding for intent: {$intent->intent_label}");
629 + //error_log("Invalid embedding for intent: " . $intent->intent_label);
1017 630 continue;
1018 631 }
1019 -
632 +
1020 633 $similarity = $this->mxchat_calculate_cosine_similarity($user_embedding, $intent_embedding);
1021 634 $intent_threshold = isset($intent->similarity_threshold) ? $intent->similarity_threshold : 0.85;
1022 -
1023 -
635 + //error_log("Similarity for " . $intent->intent_label . ": " . $similarity . " (threshold: " . $intent_threshold . ")");
636 +
1024 637 if ($similarity >= $intent_threshold && $similarity > $highest_similarity) {
1025 638 $highest_similarity = $similarity;
1026 639 $matched_intent = $intent;
1027 - //error_log("✅ MXCHAT DEBUG: New best match: '{$intent->intent_label}' with similarity {$similarity}");
640 + //error_log("New best match: " . $intent->intent_label . " with similarity " . $similarity);
1028 641 }
1029 642 }
1030 - //error_log('📊 MXCHAT DEBUG: End Intent Scores ==================');
1031 -
1032 - if ($matched_intent) {
1033 - //error_log("🎯 MXCHAT DEBUG: Final Intent Match: '{$matched_intent->intent_label}'");
1034 - //error_log("🔄 MXCHAT DEBUG: Invoking callback: {$matched_intent->callback_function}");
1035 -
1036 - // If the callback is a method on this instance (core callback), call it directly
1037 - if (method_exists($this, $matched_intent->callback_function)) {
1038 - //error_log('🔍 MXCHAT DEBUG: Using direct method call for core callback');
1039 - $callback_result = call_user_func(
1040 - [$this, $matched_intent->callback_function],
1041 - $message,
1042 - $user_id,
1043 - $session_id,
1044 - $matched_intent,
1045 - $user_context // Add user context
1046 - );
1047 - } else {
1048 - //error_log('🔍 MXCHAT DEBUG: Using apply_filters for add-on callback');
1049 - // Otherwise, use apply_filters for add-on callbacks
1050 - $callback_result = apply_filters(
1051 - $matched_intent->callback_function,
1052 - false, // default return value
1053 - $message,
1054 - $user_id,
1055 - $session_id,
1056 - $matched_intent
1057 - );
1058 - }
1059 -
1060 - //error_log('🔍 MXCHAT DEBUG: Callback result type: ' . gettype($callback_result));
1061 - if ($callback_result !== false) {
1062 - //error_log('✅ MXCHAT DEBUG: Intent handled successfully');
1063 - $this->fallbackResponse = $callback_result;
1064 - return true;
1065 - }
1066 - //error_log('❌ MXCHAT DEBUG: Callback returned false');
1067 - } else {
1068 - //error_log('❌ MXCHAT DEBUG: No matching intent found');
643 +
644 + if ($matched_intent && method_exists($this, $matched_intent->callback_function)) {
645 + //error_log("Calling callback function: " . $matched_intent->callback_function);
646 + call_user_func([$this, $matched_intent->callback_function], $message, $user_id, $session_id);
647 + return true;
1069 648 }
1070 -
1071 - //error_log('🔍 MXCHAT DEBUG: Intent Check Completed ==================');
649 +
650 + //error_log("No matching intent found");
1072 651 return false;
1073 652 }
1074 653
654 +//verified good
655 +public function mxchat_handle_order_history($message, $user_id, $session_id) {
656 + if (!class_exists('WooCommerce')) {
657 + $this->fallbackResponse['text'] = "I can't access order information right now. The order system seems to be unavailable.";
658 + return true;
659 + }
1075 660
1076 -// Helper function to clear PDF and Word document related transients
1077 -private function clear_pdf_transients($session_id) {
1078 - // PDF transients
1079 - delete_transient('mxchat_pdf_url_' . $session_id);
1080 - delete_transient('mxchat_pdf_embeddings_' . $session_id);
1081 - delete_transient('mxchat_include_pdf_in_context_' . $session_id);
1082 - delete_transient('mxchat_waiting_for_pdf_url_' . $session_id);
661 + $orderDetails = MxChat_WooCommerce::mxchat_fetch_user_orders_details('all');
1083 662
1084 - // Word document transients
1085 - delete_transient('mxchat_word_url_' . $session_id);
1086 - delete_transient('mxchat_word_filename_' . $session_id);
1087 - delete_transient('mxchat_word_embeddings_' . $session_id);
1088 - delete_transient('mxchat_include_word_in_context_' . $session_id);
1089 - delete_transient('mxchat_waiting_for_word_' . $session_id);
663 + if (empty($orderDetails)) {
664 + $this->fallbackResponse['text'] = "I don't see any orders associated with your account. Are you logged in?";
665 + return true;
666 + }
667 +
668 + // Generate AI prompt with context
669 + $prompt = "User asked about their orders: '{$message}'\n\n";
670 + $prompt .= "Order information:\n";
671 + foreach ($orderDetails as $order) {
672 + $items_list = array_map(function($item) {
673 + return "{$item['name']} ({$item['quantity']})";
674 + }, $order['items']);
675 +
676 + $prompt .= "Order #{$order['order_id']}: {$order['formatted_total']} on {$order['date']}\n";
677 + $prompt .= "Items: " . implode(', ', $items_list) . "\n";
678 + }
679 +
680 + $prompt .= "\nProvide a natural, conversational response focusing on the specific information the user asked about. ";
681 + $prompt .= "If they ask about a specific order or detail, provide just that information. ";
682 + $prompt .= "If they ask about license keys or sensitive information, inform them to check their email or contact support.";
683 +
684 + // Get AI response
685 + $ai_response = $this->mxchat_call_ai_api($prompt);
686 + $this->fallbackResponse['text'] = $ai_response['text'];
687 +
688 + return true;
1090 689 }
1091 690
1092 691
692 +//verified good
693 +public function mxchat_handle_product_inquiry($message, $user_id, $session_id) {
694 + // Attempt to extract product ID from the message
695 + $product_id = MxChat_WooCommerce::mxchat_extract_product_id_from_message($message);
1093 696
697 + // Use last discussed product if no product ID is found
698 + if (!$product_id) {
699 + $product_id = get_transient('mxchat_last_discussed_product_' . $user_id);
700 + }
701 +
702 + // Handle specific product inquiries
703 + if ($product_id && class_exists('WooCommerce')) {
704 + $product = wc_get_product($product_id);
705 + if ($product) {
706 + // Prepare product details
707 + $product_name = esc_html($product->get_name());
708 + $product_price = $product->get_price_html();
709 + $product_image_url = esc_url(wp_get_attachment_url($product->get_image_id()));
710 + $product_url = esc_url(get_permalink($product_id));
711 + $product_id_attr = esc_attr($product_id);
712 +
713 + // Get product description
714 + $product_description = $product->get_description() ?: $product->get_short_description();
715 +
716 + // Get relevant content based on user query
717 + $user_message_embedding = $this->mxchat_generate_embedding($message, $this->options['api_key']);
718 + $relevant_content = $this->mxchat_find_relevant_content($user_message_embedding);
719 +
720 + // Build AI prompt with context and product details
721 + $ai_prompt = "You are a knowledgeable product assistant. ";
722 + $ai_prompt .= "Respond to this user query: '{$message}'\n\n";
723 +
724 + // Add relevant content if available
725 + if (!empty($relevant_content)) {
726 + $ai_prompt .= "Relevant information from our knowledge base:\n{$relevant_content}\n\n";
727 + }
728 +
729 + // Add product details
730 + $ai_prompt .= "Product details:\n";
731 + $ai_prompt .= "Name: {$product_name}\n";
732 + $ai_prompt .= "Price: " . strip_tags($product_price) . "\n";
733 + if ($product_description) {
734 + $ai_prompt .= "Description: {$product_description}\n";
735 + }
736 +
737 + // Add instructions for response format
738 + $ai_prompt .= "\nInstructions:\n";
739 + $ai_prompt .= "1. Address the user's specific question or concern about the product\n";
740 + $ai_prompt .= "2. Incorporate relevant information from our knowledge base if provided\n";
741 + $ai_prompt .= "3. Highlight key product features that relate to their query\n";
742 + $ai_prompt .= "4. Include a natural suggestion to check out the product\n";
743 + $ai_prompt .= "5. Keep the response conversational and helpful\n";
744 +
745 + // Get AI response
746 + $ai_response = $this->mxchat_call_ai_api($ai_prompt);
747 +
748 + // Generate product card HTML
749 + $product_card_html = <<<HTML
750 +<div class="mxchat-product-card">
751 + <a href="{$product_url}" target="_blank">
752 + <img src="{$product_image_url}" alt="{$product_name}" class="mxchat-product-image" />
753 + <h3 class="mxchat-product-name">{$product_name}</h3>
754 + </a>
755 + <div class="mxchat-product-price">{$product_price}</div>
756 + <button class="mxchat-add-to-cart-button" data-product-id="{$product_id_attr}">Add to Cart</button>
757 +</div>
758 +HTML;
759 +
760 + // Save the response and product card
761 + $this->productCardHtml = $product_card_html;
762 + $this->fallbackResponse = [
763 + 'text' => $ai_response['text'],
764 + 'html' => $this->productCardHtml,
765 + ];
766 +
767 + // Save the last discussed product
768 + set_transient('mxchat_last_discussed_product_' . $user_id, $product_id, HOUR_IN_SECONDS);
769 + return true;
770 + }
771 + }
772 +
773 + // If no product found, skip intent handling
774 + return false;
775 +}
776 +
1094 777 //verified good
1095 778 public function mxchat_handle_email_capture($message, $user_id, $session_id) {
1096 779 // Log the message safely
1097 780 //error_log("Triggered email capture intent for message: " . sanitize_text_field($message));
@@ -1096,9 +779,9 @@
1096 779 // Log the message safely
1097 780 //error_log("Triggered email capture intent for message: " . sanitize_text_field($message));
1098 781
1099 782 // Initiate email capture flow
1100 - $response = esc_html($this->options['triggered_phrase_response'] ?? esc_html__("Would you like to join our mailing list? Please provide your email below.", 'mxchat'));
783 + $response = esc_html($this->options['triggered_phrase_response'] ?? "Would you like to join our mailing list? Please provide your email below.");
1101 784
1102 785 set_transient('mxchat_email_capture_' . $user_id, true, 5 * MINUTE_IN_SECONDS);
1103 786 $this->mxchat_save_chat_message($session_id, 'bot', $response);
1104 787
@@ -1109,9 +792,9 @@
1109 792
1110 793 //very good
1111 794 public function mxchat_generate_image($message, $user_id, $session_id) {
1112 795 // Prepare a prompt for DALL-E
1113 - $prompt = esc_html__('Create an image of ', 'mxchat') . sanitize_text_field($message);
796 + $prompt = "Create an image of " . sanitize_text_field($message);
1114 797
1115 798 // Use the existing OpenAI API key
1116 799 $openai_api_key = sanitize_text_field($this->options['api_key']);
1117 800
@@ -1122,13 +805,13 @@
1122 805 if (isset($image_response['imageUrl'])) {
1123 806 $image_url = esc_url_raw($image_response['imageUrl']);
1124 807
1125 808 // Construct the HTML with a CSS class instead of inline styles
1126 - $response_html = '<img src="' . esc_url($image_url) . '" alt="' . esc_attr__('Generated Image', 'mxchat') . '" class="mxchat-generated-image" />';
809 + $response_html = '<img src="' . esc_url($image_url) . '" alt="Generated Image" class="mxchat-generated-image" />';
1127 810
1128 - $response_text = esc_html__('Here is the image I generated:', 'mxchat');
811 + $response_text = "Here is the image I generated:";
1129 812 } else {
1130 - $response_text = esc_html__("I'm sorry, but I couldn't generate an image based on your request.", 'mxchat');
813 + $response_text = "I'm sorry, but I couldn't generate an image based on your request.";
1131 814 $response_html = '';
1132 815 //error_log("DALL-E image generation error: " . esc_html($image_response['error'] ?? 'Unknown error.'));
1133 816 }
1134 817
@@ -1169,9 +852,9 @@
1169 852 $response = wp_remote_post($api_url, $args);
1170 853
1171 854 if (is_wp_error($response)) {
1172 855 //error_log("DALL-E request failed: " . $response->get_error_message());
1173 - return ['error' => esc_html__('Error generating image: ', 'mxchat') . $response->get_error_message()];
856 + return ['error' => "Error generating image: " . $response->get_error_message()];
1174 857 }
1175 858
1176 859 $response_body = json_decode(wp_remote_retrieve_body($response), true);
1177 860
@@ -1178,215 +861,160 @@
1178 861 if (isset($response_body['data'][0]['url'])) {
1179 862 return ['imageUrl' => esc_url_raw($response_body['data'][0]['url'])];
1180 863 } else {
1181 864 //error_log("DALL-E response error: " . wp_remote_retrieve_body($response));
1182 - return ['error' => esc_html__('Failed to generate image.', 'mxchat')];
865 + return ['error' => "Failed to generate image."];
1183 866 }
1184 867 }
1185 868
1186 -/**
1187 - * Handle web search requests.
1188 - *
1189 - * Sends the refined search query to the Brave Search API and displays neatly formatted,
1190 - * styled search results. Results are cached for performance.
1191 - *
1192 - * @since 1.0.0
1193 - * @param string $message The user's search query.
1194 - * @param string $user_id The user identifier.
1195 - * @param string $session_id The current session ID.
1196 - * @return void
1197 - */
1198 -public function mxchat_handle_search_request( $message, $user_id, $session_id ) {
1199 - // Step 1: Interpret and refine the search query
1200 - $refined_search_query = $this->mxchat_interpret_search_query( $message );
869 +//very good
870 +public function mxchat_handle_search_request($message, $user_id, $session_id) {
871 + // Sanitize user input
872 + $search_query = preg_replace('/^search the web for\s+/i', '', $message);
873 + $search_query = preg_replace('/^show me the latest news about\s+/i', '', $message);
874 + $search_query = preg_replace('/^what\'?s the news in\s+/i', '', $message);
875 + $search_query = preg_replace('/^news about\s+/i', '', $message);
876 + $search_query = trim(sanitize_text_field($search_query));
1201 877
1202 - if ( empty( $refined_search_query ) ) {
1203 - $this->fallbackResponse = array(
1204 - 'text' => esc_html__( 'I apologize, but could you please rephrase your search request?', 'mxchat' ),
1205 - );
878 + if (empty($search_query)) {
879 + $this->fallbackResponse = [
880 + 'text' => __("Please provide a valid search query.", 'mxchat'),
881 + ];
1206 882 return;
1207 883 }
1208 884
1209 - // Retrieve and validate API settings
1210 - $options = get_option( 'mxchat_options' );
1211 - $api_key = isset( $options['brave_api_key'] ) ? sanitize_text_field( $options['brave_api_key'] ) : '';
1212 - $results_count = isset( $options['brave_results_count'] ) ? absint( $options['brave_results_count'] ) : 5;
885 + // Check if the query is likely related to news
886 + $is_news_search = preg_match("/\bnews\b/i", $message);
1213 887
1214 - if ( empty( $api_key ) ) {
1215 - $this->fallbackResponse = array(
1216 - 'text' => esc_html__( 'Search functionality is temporarily unavailable. Please try again later.', 'mxchat' ),
1217 - );
888 + // Retrieve settings
889 + $options = get_option('mxchat_options');
890 +
891 + $api_key = isset($options['brave_api_key']) ? sanitize_text_field($options['brave_api_key']) : '';
892 + $news_count = isset($options['brave_news_count']) ? intval($options['brave_news_count']) : 3;
893 + $country = isset($options['brave_country']) ? sanitize_text_field($options['brave_country']) : 'us';
894 + $language = isset($options['brave_language']) ? sanitize_text_field($options['brave_language']) : 'en';
895 +
896 + if (empty($api_key)) {
897 +
898 + $this->fallbackResponse = [
899 + 'text' => __("API key is not configured. Please set it in the Brave Search Settings.", 'mxchat'),
900 + ];
1218 901 return;
1219 902 }
1220 903
1221 - // Build the API request URL
1222 - $api_url = add_query_arg(
1223 - array(
1224 - 'q' => rawurlencode( $refined_search_query ),
1225 - 'count' => $results_count,
1226 - 'text_decorations' => 'true',
1227 - 'rich_data' => 'true',
1228 - ),
1229 - 'https://api.search.brave.com/res/v1/web/search'
1230 - );
904 + // Set API endpoint and parameters based on search type
905 + $api_url = $is_news_search
906 + ? 'https://api.search.brave.com/res/v1/news/search'
907 + : 'https://api.search.brave.com/res/v1/web/search';
1231 908
1232 - // Attempt to retrieve cached results first
1233 - $transient_key = 'mxchat_search_' . md5( $refined_search_query );
1234 - $results = get_transient( $transient_key );
909 + $query_args = [
910 + 'q' => urlencode($search_query),
911 + ];
1235 912
1236 - if ( false === $results ) {
1237 - // Fetch new results from the Brave Search API
1238 - $response = wp_remote_get(
1239 - $api_url,
1240 - array(
1241 - 'headers' => array(
1242 - 'Accept' => 'application/json',
1243 - 'Accept-Encoding' => 'gzip',
1244 - 'X-Subscription-Token'=> $api_key,
1245 - ),
1246 - 'timeout' => 10,
1247 - )
1248 - );
913 + if ($is_news_search) {
914 + $query_args['count'] = $news_count;
915 + $query_args['country'] = $country;
916 + $query_args['search_lang'] = $language;
917 + }
1249 918
1250 - if ( is_wp_error( $response ) ) {
1251 - $this->fallbackResponse = array(
1252 - 'text' => esc_html__( 'I encountered an error while searching. Please try again.', 'mxchat' ),
1253 - );
1254 - return;
1255 - }
919 + $api_url = add_query_arg($query_args, $api_url);
1256 920
1257 - $results = json_decode( wp_remote_retrieve_body( $response ), true );
921 + // Implement caching
922 + $transient_key = 'mxchat_search_' . md5($api_url);
923 + $body = get_transient($transient_key);
1258 924
1259 - if ( json_last_error() !== JSON_ERROR_NONE ) {
1260 - $this->fallbackResponse = array(
1261 - 'text' => esc_html__( 'I received an invalid response from the search service.', 'mxchat' ),
1262 - );
925 + if (false === $body) {
926 + $args = [
927 + 'headers' => [
928 + 'Accept' => 'application/json',
929 + 'Accept-Encoding' => 'gzip',
930 + 'Authorization' => 'Bearer ' . $api_key,
931 + 'x-subscription-token' => $api_key,
932 + ],
933 + 'timeout' => 10,
934 + ];
935 +
936 + $response = wp_remote_get($api_url, $args);
937 +
938 + if (is_wp_error($response)) {
939 +
940 + $this->fallbackResponse = [
941 + 'text' => __("I'm sorry, I couldn't retrieve any information based on your request.", 'mxchat'),
942 + ];
1263 943 return;
1264 944 }
1265 945
1266 - // Cache results for one hour
1267 - set_transient( $transient_key, $results, HOUR_IN_SECONDS );
946 + $body = json_decode(wp_remote_retrieve_body($response), true);
947 + set_transient($transient_key, $body, HOUR_IN_SECONDS);
1268 948 }
1269 949
1270 - // Process and display results
1271 - if ( ! empty( $results['web']['results'] ) && is_array( $results['web']['results'] ) ) {
1272 - $html = $this->generate_search_results_html( $results['web']['results'], $refined_search_query );
950 + // Process the API response and build a more informative summary
951 + $text_summary = "";
1273 952
1274 - // Only return HTML (no large text summary)
1275 - $this->fallbackResponse = array(
1276 - 'html' => $html,
1277 - );
953 + if ($is_news_search && isset($body['results']) && is_array($body['results']) && count($body['results']) > 0) {
954 + $text_summary = "Here are some recent news articles:\n\n";
1278 955
1279 - // Save to chat history
1280 - $this->mxchat_save_chat_message( $session_id, 'bot', $html );
1281 - } else {
1282 - $this->fallbackResponse = array(
1283 - 'text' => sprintf(
1284 - esc_html__( 'I couldn\'t find any relevant results for "%s". Would you like to try different search terms?', 'mxchat' ),
1285 - esc_html( $refined_search_query )
1286 - ),
1287 - );
1288 - }
1289 -}
956 + foreach ($body['results'] as $news) {
957 + $title = isset($news['title']) ? esc_html($news['title']) : __('No Title', 'mxchat');
958 + $url = isset($news['url']) ? esc_url($news['url']) : '#';
959 + $description = isset($news['description']) ? esc_html(strip_tags($news['description'])) : __('No description available.', 'mxchat');
960 + $age = isset($news['age']) ? esc_html($news['age']) : '';
961 + $hostname = isset($news['meta_url']['hostname']) ? esc_html($news['meta_url']['hostname']) : '';
962 + $thumbnail = isset($news['thumbnail']['src']) ? esc_url($news['thumbnail']['src']) : '';
1290 963
964 + // Build text-only news item summary
965 + $text_summary .= "Title: {$title}\nURL: {$url}\nDescription: {$description}\nPublished: {$age}\n";
966 + if (!empty($hostname)) {
967 + $text_summary .= "Source: {$hostname}\n";
968 + }
969 + if (!empty($thumbnail)) {
970 + $text_summary .= "Thumbnail: ![Image]({$thumbnail})\n";
971 + }
972 + if (!empty($news['extra_snippets'])) {
973 + $extra_snippet_text = implode(" ", $news['extra_snippets']);
974 + $text_summary .= "Additional Info: {$extra_snippet_text}\n";
975 + }
976 + $text_summary .= "\n"; // Separate entries
977 + }
978 + } elseif (!$is_news_search && isset($body['web']['results']) && is_array($body['web']['results']) && count($body['web']['results']) > 0) {
979 + $text_summary = "Here are some relevant articles based on your query:\n\n";
1291 980
1292 -/**
1293 - * Format search results into a natural text summary.
1294 - *
1295 - * @since 1.0.0
1296 - * @param array $results The search results from the API.
1297 - * @param string $query The original search query.
1298 - * @return string The text summary of the top results.
1299 - */
1300 -private function format_search_results( $results, $query ) {
1301 - $summary = sprintf(
1302 - esc_html__( 'Here are the most relevant results for "%s":', 'mxchat' ),
1303 - esc_html( $query )
1304 - ) . "\n\n";
981 + foreach ($body['web']['results'] as $result) {
982 + $title = isset($result['title']) ? esc_html($result['title']) : __('No Title', 'mxchat');
983 + $url = isset($result['url']) ? esc_url($result['url']) : '#';
984 + $description = isset($result['description']) ? esc_html(strip_tags($result['description'])) : __('No description available.', 'mxchat');
985 + $hostname = isset($result['meta_url']['hostname']) ? esc_html($result['meta_url']['hostname']) : '';
986 + $thumbnail = isset($result['thumbnail']['src']) ? esc_url($result['thumbnail']['src']) : '';
1305 987
1306 - $max_results = min( count( $results ), 3 );
1307 - for ( $i = 0; $i < $max_results; $i++ ) {
1308 - $result = $results[ $i ];
1309 - $title = isset( $result['title'] ) ? wp_strip_all_tags( $result['title'] ) : '';
1310 - $description = isset( $result['description'] ) ? wp_strip_all_tags( $result['description'] ) : '';
988 + // Build text-only web item summary
989 + $text_summary .= "Title: {$title}\nURL: {$url}\nDescription: {$description}\n";
990 + if (!empty($hostname)) {
991 + $text_summary .= "Source: {$hostname}\n";
992 + }
993 + if (!empty($thumbnail)) {
994 + $text_summary .= "Thumbnail: ![Image]({$thumbnail})\n";
995 + }
996 + if (!empty($result['extra_snippets'])) {
997 + $extra_snippet_text = implode(" ", $result['extra_snippets']);
998 + $text_summary .= "Additional Info: {$extra_snippet_text}\n";
999 + }
1000 + $text_summary .= "\n"; // Separate entries
1001 + }
1002 + } else {
1311 1003
1312 - // Append title and description to the summary
1313 - $summary .= sprintf(
1314 - "%s\n%s\n\n",
1315 - esc_html( $title ),
1316 - esc_html( $description )
1317 - );
1004 + $this->fallbackResponse = [
1005 + 'text' => __("I'm sorry, I couldn't retrieve any information based on your request.", 'mxchat'),
1006 + ];
1007 + return;
1318 1008 }
1319 1009
1320 - return $summary;
1321 -}
1010 + $this->fallbackResponse = [
1011 + 'text' => $text_summary,
1012 + ];
1322 1013
1323 -/**
1324 - * Generate HTML markup for search results.
1325 - *
1326 - * @since 1.0.0
1327 - * @param array $results The search results from the API.
1328 - * @param string $query The user-refined query.
1329 - * @return string The HTML markup for displaying the results.
1330 - */
1331 -private function generate_search_results_html( $results, $query ) {
1332 - ob_start();
1333 - ?>
1334 - <div class="mxchat-search-results">
1335 - <?php foreach ( $results as $result ) :
1336 - $title = isset( $result['title'] ) ? wp_strip_all_tags( $result['title'] ) : '';
1337 - $url = isset( $result['url'] ) ? esc_url( $result['url'] ) : '#';
1338 - $description = isset( $result['description'] ) ? wp_strip_all_tags( $result['description'] ) : '';
1339 - $favicon = isset( $result['meta_url']['favicon'] ) ? esc_url( $result['meta_url']['favicon'] ) : '';
1340 - $thumbnail = isset( $result['thumbnail']['src'] ) ? esc_url( $result['thumbnail']['src'] ) : '';
1341 - $domain = parse_url( $url, PHP_URL_HOST );
1342 - ?>
1343 - <div class="mxchat-search-item">
1344 - <div class="mxchat-search-header">
1345 - <?php if ( $favicon ) : ?>
1346 - <img
1347 - src="<?php echo esc_url( $favicon ); ?>"
1348 - class="mxchat-site-icon"
1349 - alt="<?php echo esc_attr__( 'Site icon', 'mxchat' ); ?>"
1350 - width="16"
1351 - height="16"
1352 - />
1353 - <?php endif; ?>
1354 - <div class="mxchat-site-url"><?php echo esc_html( $domain ); ?></div>
1355 - </div>
1356 1014
1357 - <div class="mxchat-search-content">
1358 - <h3 class="mxchat-search-title">
1359 - <a href="<?php echo esc_url( $url ); ?>"
1360 - target="_blank"
1361 - rel="noopener noreferrer"
1362 - >
1363 - <?php echo esc_html( $title ); ?>
1364 - </a>
1365 - </h3>
1366 -
1367 - <?php if ( $thumbnail ) : ?>
1368 - <div class="mxchat-search-thumbnail">
1369 - <img
1370 - src="<?php echo esc_url( $thumbnail ); ?>"
1371 - alt="<?php echo esc_attr__( 'Thumbnail image', 'mxchat' ); ?>"
1372 - loading="lazy"
1373 - />
1374 - </div>
1375 - <?php endif; ?>
1376 -
1377 - <div class="mxchat-search-description">
1378 - <?php echo esc_html( $description ); ?>
1379 - </div>
1380 - </div>
1381 - </div>
1382 - <?php endforeach; ?>
1383 - </div>
1384 - <?php
1385 - return ob_get_clean();
1386 1015 }
1387 1016
1388 -
1389 1017 //very good
1390 1018 public function mxchat_handle_image_search_request($message, $user_id, $session_id) {
1391 1019
1392 1020 // Step 1: Interpret the search query for better results
@@ -1481,9 +1109,9 @@
1481 1109
1482 1110 foreach ($body['results'] as $image) {
1483 1111 $image_url = isset($image['url']) ? esc_url($image['url']) : '';
1484 1112 $thumbnail_url = isset($image['thumbnail']['src']) ? esc_url($image['thumbnail']['src']) : '';
1485 - $title = isset($image['title']) ? esc_html($image['title']) : esc_html__('Image', 'mxchat');
1113 + $title = isset($image['title']) ? esc_html($image['title']) : __('Image', 'mxchat');
1486 1114
1487 1115 if ($image_url && $thumbnail_url) {
1488 1116 $html_output .= '<div class="mxchat-image-item">';
1489 1117 $html_output .= '<strong class="mxchat-image-title">' . $title . '</strong>';
@@ -1516,20 +1144,21 @@
1516 1144 ];
1517 1145 }
1518 1146 }
1519 1147 public function mxchat_interpret_search_query($user_query) {
1520 - $system_prompt = esc_html__("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.", 'mxchat');
1148 + $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.";
1521 1149
1522 1150 // Retrieve OpenAI API key using 'api_key' as the option key
1523 1151 $api_key = isset($this->options['api_key']) ? sanitize_text_field($this->options['api_key']) : sanitize_text_field(get_option('mxchat_options')['api_key']);
1524 1152
1525 - /*
1153 +/*
1526 1154 // Log the API key check, without exposing the key
1527 1155 if (defined('WP_DEBUG') && WP_DEBUG) {
1528 1156 error_log("Retrieved OpenAI API Key: " . ($api_key ? "Present" : "Missing"));
1529 1157 }
1530 - */
1158 +*/
1531 1159
1160 +
1532 1161 if (empty($api_key)) {
1533 1162 //error_log("OpenAI API key is missing.");
1534 1163 return sanitize_text_field($user_query); // Default to the original query if API key is missing
1535 1164 }
@@ -1564,14 +1193,14 @@
1564 1193 // Check for a valid response and sanitize output
1565 1194 if (isset($body['choices'][0]['message']['content'])) {
1566 1195 $interpreted_query = sanitize_text_field(trim($body['choices'][0]['message']['content']));
1567 1196
1568 - /*
1197 +/*
1569 1198 // Log the interpreted query for debugging
1570 1199 if (defined('WP_DEBUG') && WP_DEBUG) {
1571 1200 error_log("Interpreted search query: " . $interpreted_query);
1572 1201 }
1573 - */
1202 +*/
1574 1203
1575 1204 return $interpreted_query;
1576 1205 } else {
1577 1206 //error_log("Unexpected API response format: " . print_r($body, true));
@@ -1578,137 +1207,74 @@
1578 1207 return sanitize_text_field($user_query);
1579 1208 }
1580 1209 }
1581 1210
1211 +public function mxchat_handle_add_to_cart_intent($message, $user_id, $session_id) {
1212 + if (!class_exists('WooCommerce')) {
1213 + $this->fallbackResponse['text'] = "I apologize, but the shopping cart feature isn't available at the moment.";
1214 + return true;
1215 + }
1582 1216
1217 + $sanitized_user_id = sanitize_key($user_id);
1218 + $last_product_id = get_transient('mxchat_last_discussed_product_' . $sanitized_user_id);
1583 1219
1584 -private function find_product_in_message($message) {
1585 - global $wpdb;
1220 + if (!$last_product_id) {
1221 + $this->fallbackResponse['text'] = "I couldn't find which product you'd like to add. Could you mention the product name again?";
1222 + return true;
1223 + }
1586 1224
1587 - // Get embedding for the search query
1588 - $query_embedding = $this->mxchat_generate_embedding($message, $this->options['api_key']);
1589 - if (!is_array($query_embedding)) {
1590 - return null;
1225 + $product = wc_get_product($last_product_id);
1226 + if (!$product) {
1227 + $this->fallbackResponse['text'] = "I'm sorry, but I couldn't find that product. Could you try again?";
1228 + return true;
1591 1229 }
1592 1230
1593 - // Get relevant content as string
1594 - $relevant_content = $this->mxchat_find_relevant_products($query_embedding);
1595 - if (empty($relevant_content)) {
1596 - // Return null to indicate no results and set fallback response
1597 - $this->fallbackResponse['text'] = esc_html__("I couldn't find any relevant products based on your query. Could you please be more specific about the product you're looking for?", 'mxchat');
1598 - return null;
1231 + $added = WC()->cart->add_to_cart($last_product_id);
1232 + if ($added) {
1233 + $product_name = esc_html($product->get_name());
1234 + $cart_url = wc_get_cart_url();
1235 + $this->fallbackResponse['text'] = "Great! I've added '{$product_name}' to your cart. You can view your cart anytime by asking me to show it, or proceed to checkout when you're ready.";
1236 + } else {
1237 + $this->fallbackResponse['text'] = "I couldn't add the product to your cart. Please try again or let me know if you need help.";
1599 1238 }
1600 1239
1601 - // Extract product URLs from the content
1602 - preg_match_all('/https?:\/\/[^\s<>"\']+?\/product\/[^\s<>"\']+/', $relevant_content, $matches);
1240 + return true;
1241 +}
1603 1242
1604 - if (!empty($matches[0])) {
1605 - // Try each URL found
1606 - foreach ($matches[0] as $url) {
1607 - // Clean the URL
1608 - $url = rtrim($url, '/."\']');
1609 -
1610 - // Get the product slug
1611 - $path = parse_url($url, PHP_URL_PATH);
1612 - $slug = basename(rtrim($path, '/'));
1613 -
1614 - // Find product by slug
1615 - $args = array(
1616 - 'post_type' => 'product',
1617 - 'post_status' => 'publish',
1618 - 'name' => $slug,
1619 - 'posts_per_page' => 1
1620 - );
1621 -
1622 - $products = get_posts($args);
1623 -
1624 - if (!empty($products)) {
1625 - $product_id = $products[0]->ID;
1626 - $product = wc_get_product($product_id);
1627 -
1628 - if ($product && $product->is_purchasable()) {
1629 - return $product_id;
1630 - }
1631 - }
1632 - }
1243 +public function mxchat_handle_checkout_intent($message, $user_id, $session_id) {
1244 + if (!class_exists('WooCommerce')) {
1245 + $this->fallbackResponse['text'] = "I apologize, but the checkout feature isn't available at the moment.";
1246 + return true;
1633 1247 }
1634 1248
1635 - // Fallback: Look for product names in the content
1636 - $products = wc_get_products([
1637 - 'status' => 'publish',
1638 - 'limit' => -1,
1639 - 'return' => 'all'
1640 - ]);
1641 -
1642 - foreach ($products as $product) {
1643 - $name = $product->get_name();
1644 - if (stripos($relevant_content, $name) !== false) {
1645 - if ($product->is_purchasable()) {
1646 - return $product->get_id();
1647 - }
1648 - }
1249 + // Check if cart has items
1250 + if (WC()->cart->is_empty()) {
1251 + $this->fallbackResponse['text'] = "Your cart is empty at the moment. Would you like to see our products?";
1252 + return true;
1649 1253 }
1650 1254
1651 - // If no product is found after all checks, set the fallback response
1652 - $this->fallbackResponse['text'] = esc_html__("I couldn't find any relevant products based on your query. Try to be more specific", 'mxchat');
1653 - return null;
1654 -}
1255 + // Get cart summary
1256 + $cart_count = WC()->cart->get_cart_contents_count();
1257 + $cart_total = WC()->cart->get_total();
1655 1258
1656 -// New method to handle intent responses
1657 -private function generate_intent_response($context_content, $session_id) {
1658 - // Convert the context array to a structured string for the AI
1659 - $context_string = $this->format_intent_context($context_content);
1660 -
1661 - // Generate AI response using the context
1662 - $response = $this->mxchat_generate_response(
1663 - $context_string,
1664 - $this->options['api_key'],
1665 - $this->options['xai_api_key'],
1666 - $this->options['claude_api_key'],
1667 - $this->options['deepseek_api_key'],
1668 - $this->mxchat_fetch_conversation_history_for_ai($session_id)
1669 - );
1670 -
1671 - $this->fallbackResponse['text'] = $response;
1672 - return true;
1673 -}
1674 -// Helper method to format intent context
1675 -private function format_intent_context($context) {
1676 - $context_string = esc_html__("INTENT CONTEXT:\n", 'mxchat');
1677 -
1678 - switch ($context['intent']) {
1679 - case 'add_to_cart':
1680 - if ($context['status'] === 'success') {
1681 - $context_string .= esc_html__("Action: Successfully added product to cart\n", 'mxchat');
1682 - $context_string .= sprintf(esc_html__("Product: %s\n", 'mxchat'), $context['product']['name']);
1683 - $context_string .= esc_html__("Available actions: ", 'mxchat') . implode(', ', $context['available_actions']) . "\n";
1684 - $context_string .= sprintf(esc_html__("Cart URL: %s\n", 'mxchat'), $context['cart_url']);
1685 - $context_string .= esc_html__("\nPlease inform the user of the successful addition and their available options.", 'mxchat');
1686 - } else {
1687 - $context_string .= esc_html__("Action: Failed to add product to cart\n", 'mxchat');
1688 - $context_string .= sprintf(esc_html__("Reason: %s\n", 'mxchat'), $context['reason']);
1689 - switch ($context['reason']) {
1690 - case 'woocommerce_not_available':
1691 - $context_string .= esc_html__("\nPlease inform the user that shopping features are not available.", 'mxchat');
1692 - break;
1693 - case 'no_product_context':
1694 - $context_string .= esc_html__("\nPlease ask the user to specify which product they want to add.", 'mxchat');
1695 - break;
1696 - case 'product_not_found':
1697 - $context_string .= esc_html__("\nPlease inform the user that the product couldn't be found and ask them to try again.", 'mxchat');
1698 - break;
1699 - case 'add_to_cart_failed':
1700 - $context_string .= esc_html__("\nPlease apologize to the user and suggest they try again or ask for assistance.", 'mxchat');
1701 - break;
1702 - }
1703 - }
1704 - break;
1259 + // Get and validate checkout URL
1260 + $checkout_url = wc_get_checkout_url();
1261 + if (!$checkout_url) {
1262 + $this->fallbackResponse['text'] = "I'm having trouble accessing the checkout page. Please try again in a moment.";
1263 + return true;
1705 1264 }
1706 1265
1707 - return $context_string;
1266 + wp_send_json([
1267 + 'text' => sprintf(
1268 + "You have %d item%s in your cart totaling %s. I'll redirect you to checkout now.",
1269 + $cart_count,
1270 + $cart_count > 1 ? 's' : '',
1271 + strip_tags($cart_total)
1272 + ),
1273 + 'redirect_url' => esc_url_raw($checkout_url)
1274 + ]);
1275 + wp_die();
1708 1276 }
1709 -
1710 -
1711 1277 //very good
1712 1278 private function add_email_to_loops($email) {
1713 1279 // Sanitize the email
1714 1280 $email = sanitize_email($email);
@@ -1718,9 +1284,9 @@
1718 1284 $mailing_list_id = isset($this->options['loops_mailing_list']) ? sanitize_text_field($this->options['loops_mailing_list']) : '';
1719 1285
1720 1286 // Check for missing API key or mailing list ID
1721 1287 if (empty($api_key) || empty($mailing_list_id)) {
1722 - //error_log(esc_html__('Loops API key or mailing list ID is missing.', 'mxchat'));
1288 + //error_log('Loops API key or mailing list ID is missing.');
1723 1289 return;
1724 1290 }
1725 1291
1726 1292 $data = array(
@@ -1725,9 +1291,9 @@
1725 1291
1726 1292 $data = array(
1727 1293 'email' => $email,
1728 1294 'subscribed' => true,
1729 - 'source' => __('MxChat AI Chatbot', 'mxchat'),
1295 + 'source' => 'MxChat AI Chatbot',
1730 1296 'mailingLists' => array($mailing_list_id => true),
1731 1297 );
1732 1298
1733 1299 $url = 'https://app.loops.so/api/v1/contacts/create';
@@ -1744,9 +1310,9 @@
1744 1310 $response = wp_remote_post($url, $args);
1745 1311
1746 1312 // Handle errors in the API request
1747 1313 if (is_wp_error($response)) {
1748 - //error_log(esc_html__('Error adding email to Loops: ', 'mxchat') . $response->get_error_message());
1314 + //error_log('Error adding email to Loops: ' . $response->get_error_message());
1749 1315 return;
1750 1316 }
1751 1317
1752 1318 // Check for non-200 HTTP responses
@@ -1752,47 +1318,110 @@
1752 1318 // Check for non-200 HTTP responses
1753 1319 $response_code = wp_remote_retrieve_response_code($response);
1754 1320 if ($response_code != 200) {
1755 1321 $response_body = wp_remote_retrieve_body($response);
1756 - //error_log(esc_html__('Loops API responded with code ', 'mxchat') . $response_code . ': ' . $response_body);
1322 + //error_log('Loops API responded with code ' . $response_code . ': ' . $response_body);
1757 1323 }
1758 1324 }
1759 1325
1760 1326 public function mxchat_handle_pdf_discussion($message, $user_id, $session_id) {
1761 1327 // Get the maximum number of pages allowed from admin settings
1762 - $max_pages = isset($this->options['pdf_max_pages']) ? intval($this->options['pdf_max_pages']) : 69;
1328 + $max_pages = isset($this->options['pdf_max_pages']) ? intval($this->options['pdf_max_pages']) : 69; // Default to 69
1763 1329
1330 +
1764 1331 // Retrieve options for dynamic texts
1765 - $trigger_text = $this->options['pdf_intent_trigger_text'] ?? __("Please provide the URL to the PDF you'd like to discuss.", 'mxchat');
1766 - $success_text = $this->options['pdf_intent_success_text'] ?? __("I've processed the PDF. What questions do you have about it?", 'mxchat');
1767 - $error_text = $this->options['pdf_intent_error_text'] ?? __("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat');
1332 + $trigger_text = $this->options['pdf_intent_trigger_text'] ?? "Please provide the URL to the PDF you'd like to discuss.";
1333 + $success_text = $this->options['pdf_intent_success_text'] ?? "I've processed the PDF. What questions do you have about it?";
1334 + $error_text = $this->options['pdf_intent_error_text'] ?? "Sorry, I couldn't process the PDF. Please ensure it's a valid file.";
1768 1335
1769 - // Check for explicit request for new PDF
1770 - $new_pdf_requested = stripos($message, 'new') !== false ||
1771 - stripos($message, 'another') !== false ||
1772 - stripos($message, 'different') !== false;
1336 + // Check if we're waiting for a URL
1337 + $waiting_for_url = get_transient('mxchat_waiting_for_pdf_url_' . $session_id);
1773 1338
1774 - // If user mentions adding/reading a PDF, set waiting flag
1775 - if (stripos($message, 'pdf') !== false ||
1776 - stripos($message, 'document') !== false ||
1777 - stripos($message, 'read') !== false) {
1339 + // Check if we already have a PDF URL stored for this session
1340 + $pdf_url = get_transient('mxchat_pdf_url_' . $session_id);
1341 +
1342 + // Always process a new URL if detected in the current message
1343 + if (preg_match('/https?:\/\/[^\s"]+/i', $message, $matches)) {
1344 + $new_pdf_url = $matches[0];
1345 +
1346 + // Validate HTTPS
1347 + if (!wp_http_validate_url($new_pdf_url) || parse_url($new_pdf_url, PHP_URL_SCHEME) !== 'https') {
1348 + //error_log("Invalid PDF URL: $new_pdf_url");
1349 + $this->fallbackResponse['text'] = $trigger_text;
1350 + return;
1351 + }
1352 +
1353 + // Reset previous transients if a new URL is provided
1354 + if ($pdf_url !== $new_pdf_url) {
1355 + //error_log("New PDF URL detected. Resetting previous transients for session $session_id.");
1356 + delete_transient('mxchat_pdf_url_' . $session_id);
1357 + delete_transient('mxchat_pdf_embeddings_' . $session_id);
1358 + delete_transient('mxchat_include_pdf_in_context_' . $session_id);
1359 +
1360 + // Store the new PDF URL
1361 + set_transient('mxchat_pdf_url_' . $session_id, $new_pdf_url, HOUR_IN_SECONDS);
1362 +
1363 + // Fetch and process the new PDF
1364 + $embeddings = $this->fetch_and_split_pdf_pages($new_pdf_url, $max_pages); // Ensure both arguments are passed
1365 +
1366 + if ($embeddings === 'too_many_pages') {
1367 + //error_log("PDF exceeds maximum allowed pages ($max_pages) for URL: $new_pdf_url");
1368 + $error_text = sprintf(
1369 + $this->options['pdf_intent_error_text'] ?? "The provided PDF exceeds the maximum allowed limit of %d pages. Please provide a smaller document.",
1370 + $max_pages
1371 + );
1372 + $this->fallbackResponse['text'] = $error_text;
1373 + delete_transient('mxchat_pdf_url_' . $session_id);
1374 + } elseif ($embeddings) {
1375 + //error_log("PDF processed successfully for URL: $new_pdf_url");
1376 + set_transient('mxchat_pdf_embeddings_' . $session_id, $embeddings, HOUR_IN_SECONDS);
1377 + set_transient('mxchat_include_pdf_in_context_' . $session_id, true, HOUR_IN_SECONDS);
1378 +
1379 + $this->fallbackResponse['text'] = $success_text;
1380 + } else {
1381 + //error_log("Failed to process PDF for URL: $new_pdf_url");
1382 + $this->fallbackResponse['text'] = $error_text;
1383 + delete_transient('mxchat_pdf_url_' . $session_id);
1384 + }
1385 +
1386 + return;
1387 + }
1388 + }
1389 +
1390 + if (!$pdf_url) {
1391 + //error_log("No PDF URL provided or stored for session $session_id.");
1778 1392 set_transient('mxchat_waiting_for_pdf_url_' . $session_id, true, HOUR_IN_SECONDS);
1779 1393 $this->fallbackResponse['text'] = $trigger_text;
1780 1394 return;
1781 1395 }
1782 1396
1783 - // If we're waiting for a URL or user requested new PDF
1784 - if ($new_pdf_requested || get_transient('mxchat_waiting_for_pdf_url_' . $session_id)) {
1785 - if (preg_match('/https?:\/\/[^\s"]+/i', $message, $matches)) {
1786 - // Process URL... (rest of your existing URL processing code)
1397 + // Retrieve stored embeddings or fetch if missing
1398 + $embeddings = get_transient('mxchat_pdf_embeddings_' . $session_id);
1399 +
1400 + if (!$embeddings) {
1401 + //error_log("No embeddings found for PDF URL: $pdf_url. Attempting to process again.");
1402 + $embeddings = $this->fetch_and_split_pdf_pages($pdf_url, $max_pages); // Ensure both arguments are passed
1403 +
1404 + if ($embeddings === 'too_many_pages') {
1405 + //error_log("PDF exceeds maximum allowed pages ($max_pages) for URL: $pdf_url");
1406 + $this->fallbackResponse['text'] = "The provided PDF exceeds the maximum allowed limit of {$max_pages} pages. Please provide a smaller document.";
1407 + delete_transient('mxchat_pdf_url_' . $session_id);
1408 + } elseif ($embeddings) {
1409 + //error_log("PDF processed successfully for URL: $pdf_url");
1410 + set_transient('mxchat_pdf_embeddings_' . $session_id, $embeddings, HOUR_IN_SECONDS);
1411 + set_transient('mxchat_include_pdf_in_context_' . $session_id, true, HOUR_IN_SECONDS);
1412 +
1413 + $this->fallbackResponse['text'] = $success_text;
1787 1414 } else {
1788 - $this->fallbackResponse['text'] = $trigger_text;
1415 + //error_log("Failed to process PDF for URL: $pdf_url");
1416 + $this->fallbackResponse['text'] = $error_text;
1417 + delete_transient('mxchat_pdf_url_' . $session_id);
1789 1418 }
1790 - return;
1419 + } else {
1420 + //error_log("Using stored embeddings for session $session_id.");
1421 + set_transient('mxchat_include_pdf_in_context_' . $session_id, true, HOUR_IN_SECONDS);
1422 + $this->fallbackResponse['text'] = ''; // Proceed without additional message
1791 1423 }
1792 -
1793 - // Default to proceeding with conversation if no specific PDF action is needed
1794 - $this->fallbackResponse['text'] = '';
1795 1424 }
1796 1425 private function fetch_and_split_pdf_pages($pdf_source, $max_pages) {
1797 1426 $upload_dir = wp_upload_dir();
1798 1427 $temp_file = null;
@@ -1804,9 +1433,9 @@
1804 1433 $temp_file = wp_tempnam($pdf_source); // Safe temporary file name
1805 1434 $response = wp_remote_get($pdf_source, ['timeout' => 60]);
1806 1435
1807 1436 if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) {
1808 - //error_log(esc_html__("Failed to download PDF. Error: ", 'mxchat') . print_r($response, true));
1437 + //error_log("Failed to download PDF. Error: " . print_r($response, true));
1809 1438 return false;
1810 1439 }
1811 1440
1812 1441 file_put_contents($temp_file, wp_remote_retrieve_body($response));
@@ -1813,9 +1442,9 @@
1813 1442
1814 1443 // Validate that the downloaded file is a PDF
1815 1444 $mime_type = mime_content_type($temp_file);
1816 1445 if ($mime_type !== 'application/pdf') {
1817 - //error_log(esc_html__("Invalid MIME type detected for PDF: ", 'mxchat') . $mime_type);
1446 + //error_log("Invalid MIME type detected for PDF: $mime_type");
1818 1447 unlink($temp_file);
1819 1448 return false;
1820 1449 }
1821 1450 } else {
@@ -1828,13 +1457,13 @@
1828 1457 $pdf = $parser->parseFile($temp_file);
1829 1458 $pages = $pdf->getPages();
1830 1459
1831 1460 if (count($pages) > $max_pages) {
1832 - //error_log(esc_html__("PDF exceeds the maximum allowed pages: ", 'mxchat') . count($pages));
1461 + //error_log("PDF exceeds the maximum allowed pages: " . count($pages));
1833 1462 if (filter_var($pdf_source, FILTER_VALIDATE_URL)) {
1834 1463 unlink($temp_file);
1835 1464 }
1836 - return esc_html__('too_many_pages', 'mxchat');
1465 + return 'too_many_pages';
1837 1466 }
1838 1467
1839 1468 $embeddings = [];
1840 1469 foreach ($pages as $page_number => $page) {
@@ -1841,14 +1470,14 @@
1841 1470 $text = $page->getText();
1842 1471
1843 1472 // Ensure text is non-empty before generating embeddings
1844 1473 if (empty(trim($text))) {
1845 - //error_log(esc_html__("Skipping empty page: ", 'mxchat') . ($page_number + 1));
1474 + //error_log("Skipping empty page: " . ($page_number + 1));
1846 1475 continue;
1847 1476 }
1848 1477
1849 1478 $embedding = $this->mxchat_generate_embedding(
1850 - esc_html__("Page ", 'mxchat') . ($page_number + 1) . ": " . $text,
1479 + "Page " . ($page_number + 1) . ": " . $text,
1851 1480 $this->options['api_key']
1852 1481 );
1853 1482
1854 1483 if ($embedding) {
@@ -1857,9 +1486,9 @@
1857 1486 'embedding' => $embedding,
1858 1487 'text' => $text,
1859 1488 ];
1860 1489 } else {
1861 - //error_log(esc_html__("Failed to generate embedding for page ", 'mxchat') . ($page_number + 1));
1490 + //error_log("Failed to generate embedding for page " . ($page_number + 1));
1862 1491 }
1863 1492 }
1864 1493
1865 1494 // Clean up downloaded file if it was from URL
@@ -1869,9 +1498,9 @@
1869 1498
1870 1499 return $embeddings;
1871 1500
1872 1501 } catch (\Exception $e) {
1873 - // error_log(esc_html__("Error parsing or processing PDF: ", 'mxchat') . $e->getMessage());
1502 + // error_log("Error parsing or processing PDF: " . $e->getMessage());
1874 1503
1875 1504 // Cleanup in case of exception
1876 1505 if (filter_var($pdf_source, FILTER_VALIDATE_URL) && $temp_file && file_exists($temp_file)) {
1877 1506 unlink($temp_file);
@@ -1879,10 +1508,11 @@
1879 1508
1880 1509 return false;
1881 1510 }
1882 1511 }
1512 +
1883 1513 private function find_relevant_pdf_pages($query_embedding, $embeddings) {
1884 - //error_log(esc_html__("find_relevant_pdf_pages called.", 'mxchat'));
1514 + //error_log("find_relevant_pdf_pages called.");
1885 1515
1886 1516 $most_relevant = null;
1887 1517 $highest_similarity = -INF;
1888 1518
@@ -1908,9 +1538,9 @@
1908 1538 public function handle_pdf_upload() {
1909 1539 check_ajax_referer('mxchat_chat_nonce', 'nonce');
1910 1540
1911 1541 if (!isset($_FILES['pdf_file']) || !isset($_POST['session_id'])) {
1912 - wp_send_json_error(esc_html__('Missing required parameters.', 'mxchat'));
1542 + wp_send_json_error('Missing required parameters.');
1913 1543 return;
1914 1544 }
1915 1545
1916 1546 $file = $_FILES['pdf_file'];
@@ -1918,9 +1548,9 @@
1918 1548 $original_filename = sanitize_text_field($file['name']);
1919 1549
1920 1550 $file_type = wp_check_filetype($file['name'], ['pdf' => 'application/pdf']);
1921 1551 if ($file_type['type'] !== 'application/pdf') {
1922 - wp_send_json_error(esc_html__('Invalid file type. Only PDF files are allowed.', 'mxchat'));
1552 + wp_send_json_error('Invalid file type. Only PDF files are allowed.');
1923 1553 return;
1924 1554 }
1925 1555
1926 1556 $upload_dir = wp_upload_dir();
@@ -1927,9 +1557,9 @@
1927 1557 $pdf_filename = 'mxchat_' . $session_id . '_' . time() . '.pdf';
1928 1558 $pdf_path = $upload_dir['path'] . '/' . $pdf_filename;
1929 1559
1930 1560 if (!move_uploaded_file($file['tmp_name'], $pdf_path)) {
1931 - wp_send_json_error(esc_html__('Failed to upload file.', 'mxchat'));
1561 + wp_send_json_error('Failed to upload file.');
1932 1562 return;
1933 1563 }
1934 1564
1935 1565 $this->clear_pdf_transients($session_id);
@@ -1940,9 +1570,9 @@
1940 1570 if ($embeddings === 'too_many_pages') {
1941 1571 unlink($pdf_path);
1942 1572 $error_message = sprintf(
1943 1573 $this->options['pdf_intent_error_text'] ??
1944 - esc_html__("The provided PDF exceeds the maximum allowed limit of %d pages. Please provide a smaller document.", 'mxchat'),
1574 + "The provided PDF exceeds the maximum allowed limit of %d pages. Please provide a smaller document.",
1945 1575 $max_pages
1946 1576 );
1947 1577 wp_send_json_error($error_message);
1948 1578 return;
@@ -1950,9 +1580,9 @@
1950 1580
1951 1581 if ($embeddings === false || empty($embeddings)) {
1952 1582 unlink($pdf_path);
1953 1583 $error_message = $this->options['pdf_intent_error_text'] ??
1954 - esc_html__('The uploaded PDF appears to be empty or contains unsupported content.', 'mxchat');
1584 + 'The uploaded PDF appears to be empty or contains unsupported content.';
1955 1585 wp_send_json_error($error_message);
1956 1586 return;
1957 1587 }
1958 1588
@@ -1962,9 +1592,9 @@
1962 1592 set_transient('mxchat_pdf_embeddings_' . $session_id, $embeddings, HOUR_IN_SECONDS);
1963 1593 set_transient('mxchat_include_pdf_in_context_' . $session_id, true, HOUR_IN_SECONDS);
1964 1594
1965 1595 $success_message = $this->options['pdf_intent_success_text'] ??
1966 - esc_html__("I've processed the PDF. What questions do you have about it?", 'mxchat');
1596 + "I've processed the PDF. What questions do you have about it?";
1967 1597
1968 1598 wp_send_json_success([
1969 1599 'message' => $success_message,
1970 1600 'filename' => $original_filename
@@ -1973,9 +1603,9 @@
1973 1603 }
1974 1604
1975 1605 unlink($pdf_path);
1976 1606 $error_message = $this->options['pdf_intent_error_text'] ??
1977 - esc_html__('Sorry, I couldn\'t process the PDF. Please ensure it\'s a valid file.', 'mxchat');
1607 + 'Sorry, I couldn\'t process the PDF. Please ensure it\'s a valid file.';
1978 1608 wp_send_json_error($error_message);
1979 1609 return;
1980 1610 }
1981 1611 public function handle_pdf_remove() {
@@ -1981,9 +1611,9 @@
1981 1611 public function handle_pdf_remove() {
1982 1612 check_ajax_referer('mxchat_chat_nonce', 'nonce');
1983 1613
1984 1614 if (empty($_POST['session_id'])) {
1985 - wp_send_json_error(esc_html__('Session ID missing.', 'mxchat'));
1615 + wp_send_json_error('Session ID missing.');
1986 1616 wp_die();
1987 1617 }
1988 1618
1989 1619 $session_id = sanitize_text_field($_POST['session_id']);
@@ -1995,16 +1625,342 @@
1995 1625
1996 1626 $this->clear_pdf_transients($session_id);
1997 1627
1998 1628 wp_send_json_success([
1999 - 'message' => esc_html__('PDF removed successfully.', 'mxchat')
1629 + 'message' => 'PDF removed successfully.'
2000 1630 ]);
2001 1631 wp_die();
2002 1632 }
2003 1633
2004 1634
1635 +/**
1636 + * Calls the AI API with the provided prompt.
1637 + *
1638 + * @param string $prompt The prompt to send to the AI.
1639 + * @return array An array containing the AI's response text.
1640 + */
1641 +private function mxchat_call_ai_api( $prompt ) {
1642 + //error_log( 'Calling AI API with the provided prompt.' );
2005 1643
1644 + $api_key = $this->options['api_key'];
1645 + if ( empty( $api_key ) ) {
1646 + //error_log( 'API key is not set.' );
1647 + return [ 'text' => 'API key is not set.' ];
1648 + }
2006 1649
1650 + $url = 'https://api.openai.com/v1/chat/completions';
1651 + $messages = [
1652 + [
1653 + 'role' => 'system',
1654 + 'content' => 'You are a helpful assistant that provides concise and personalized product recommendations. Someone has asked you for some recommendations please respond very concisely appropriate for an AI chatbot.',
1655 + ],
1656 + [
1657 + 'role' => 'user',
1658 + 'content' => $prompt,
1659 + ],
1660 + ];
1661 +
1662 + $args = [
1663 + 'headers' => [
1664 + 'Authorization' => 'Bearer ' . $api_key,
1665 + 'Content-Type' => 'application/json',
1666 + ],
1667 + 'body' => wp_json_encode(
1668 + [
1669 + 'model' => 'gpt-4o',
1670 + 'messages' => $messages,
1671 + 'temperature' => 0.7,
1672 + ]
1673 + ),
1674 + 'timeout' => 10,
1675 + 'method' => 'POST',
1676 + ];
1677 +
1678 + $response = wp_remote_post( $url, $args );
1679 +
1680 + if ( is_wp_error( $response ) ) {
1681 + //error_log( 'Error communicating with AI API: ' . $response->get_error_message() );
1682 + return [ 'text' => 'Error communicating with AI API.' ];
1683 + }
1684 +
1685 + $body = wp_remote_retrieve_body( $response );
1686 + //error_log( 'API response body: ' . $body );
1687 +
1688 + $decoded_body = json_decode( $body, true );
1689 + if ( isset( $decoded_body['choices'][0]['message']['content'] ) ) {
1690 + return [ 'text' => $decoded_body['choices'][0]['message']['content'] ];
1691 + } else {
1692 + //error_log( 'Unexpected API response format: ' . wp_json_encode( $decoded_body ) );
1693 + return [ 'text' => 'No response received from AI.' ];
1694 + }
1695 +}
1696 +
1697 +/**
1698 + * Fetches the AI response for the given prompt.
1699 + *
1700 + * @param string $prompt The prompt to send to the AI.
1701 + * @return string|null The AI's response text or null if not available.
1702 + */
1703 +private function mxchat_fetch_ai_response( $prompt ) {
1704 + $response = $this->mxchat_call_ai_api( $prompt );
1705 + return isset( $response['text'] ) ? $response['text'] : null;
1706 +}
1707 +
1708 +/**
1709 + * Generates the AI prompt for product recommendations.
1710 + *
1711 + * @param array $recommendations An array of product recommendations.
1712 + * @return string The generated AI prompt.
1713 + */
1714 +private function mxchat_generate_ai_recommendation_prompt( $recommendations ) {
1715 + //error_log( 'Generating AI recommendation prompt.' );
1716 +
1717 + $recommendation_list = '';
1718 + foreach ( $recommendations as $index => $rec ) {
1719 + $number = $index + 1;
1720 + $name = $rec['name'];
1721 + $price = $rec['price'];
1722 + $url = $rec['url'];
1723 + $image = $rec['image'];
1724 + $recommendation_list .= "{$number}. Product: {$name} (Price: \${$price})\n";
1725 + $recommendation_list .= " [Link]({$url})\n";
1726 + $recommendation_list .= " ![Image]({$image})\n\n";
1727 + }
1728 +
1729 + $prompt = "Based on the following list of products, generate a unique, friendly, and personalized response to a user who asked for a recommendation ";
1730 + $prompt .= "Then, for each product, provide a brief justification of why it's relevant to the user. ";
1731 + $prompt .= "Please number your responses to match the product numbers.\n\n";
1732 + $prompt .= "Products:\n\n{$recommendation_list}";
1733 + $prompt .= "Please ensure that the number of each product matches the order in which the products are listed.";
1734 +
1735 + //error_log( 'Generated AI prompt: ' . $prompt );
1736 +
1737 + return $prompt;
1738 +}
1739 +
1740 +/**
1741 + * Handles product recommendations by generating and formatting the AI response.
1742 + *
1743 + * @param string $message The user's message.
1744 + * @param int $user_id The user's ID.
1745 + * @param int $session_id The session ID.
1746 + */
1747 +public function mxchat_handle_product_recommendations( $message, $user_id, $session_id ) {
1748 + try {
1749 + //error_log( "Starting product recommendations for user: $user_id, session: $session_id." );
1750 +
1751 + $recommendation_data = $this->mxchat_generate_recommendations( $user_id );
1752 + //error_log( 'Generated recommendation data: ' . wp_json_encode( $recommendation_data ) );
1753 +
1754 + if ( empty( $recommendation_data['recommendations'] ) ) {
1755 + //error_log( "No recommendations found for user: $user_id." );
1756 + $this->fallbackResponse = [
1757 + 'text' => __( "I couldn't find any product recommendations for you right now. Please try again later!", 'mxchat' ),
1758 + ];
1759 + return;
1760 + }
1761 +
1762 + // Remove duplicates and limit to top 4 recommendations
1763 + $unique_recommendations = [];
1764 + foreach ( $recommendation_data['recommendations'] as $rec ) {
1765 + $unique_recommendations[ $rec['url'] ] = $rec;
1766 + }
1767 + //error_log( 'Unique recommendations: ' . wp_json_encode( $unique_recommendations ) );
1768 +
1769 + $unique_recommendations = array_slice( $unique_recommendations, 0, 4 );
1770 + //error_log( 'Top 4 recommendations: ' . wp_json_encode( $unique_recommendations ) );
1771 +
1772 + $recommendations_summary = [];
1773 + foreach ( $unique_recommendations as $rec ) {
1774 + $recommendations_summary[] = [
1775 + 'name' => $rec['name'],
1776 + 'price' => strip_tags( $rec['price'] ),
1777 + 'url' => $rec['url'],
1778 + 'image' => $rec['image'],
1779 + ];
1780 + }
1781 +
1782 + // Generate AI prompt and fetch response
1783 + $ai_prompt = $this->mxchat_generate_ai_recommendation_prompt( $recommendations_summary );
1784 + $ai_response = $this->mxchat_fetch_ai_response( $ai_prompt );
1785 + //error_log( 'AI response received: ' . wp_json_encode( $ai_response ) );
1786 +
1787 + if ( empty( $ai_response ) ) {
1788 + $this->fallbackResponse = [
1789 + 'text' => __( 'An unexpected error occurred while generating recommendations. Please try again later.', 'mxchat' ),
1790 + ];
1791 + return;
1792 + }
1793 +
1794 + // Split the AI's response into lines
1795 + $ai_lines = preg_split( '/\r\n|\r|\n/', $ai_response );
1796 +
1797 + // Initialize variables
1798 + $formatted_response = '';
1799 + $justifications = [];
1800 + $current_number = 0;
1801 + $in_introduction = true;
1802 + $introduction = '';
1803 +
1804 + // Parse the AI response to separate the introduction and the justifications
1805 + foreach ( $ai_lines as $line ) {
1806 + if ( preg_match( '/^\s*(\d+)\.\s*(.*)$/', $line, $matches ) ) {
1807 + // This line is a numbered justification
1808 + $current_number = intval( $matches[1] ) - 1;
1809 + $justifications[ $current_number ] = $matches[2];
1810 + $in_introduction = false;
1811 + } elseif ( $in_introduction ) {
1812 + // This line is part of the introduction
1813 + $introduction .= $line . ' ';
1814 + } else {
1815 + // This line is a continuation of the current justification
1816 + if ( isset( $justifications[ $current_number ] ) ) {
1817 + $justifications[ $current_number ] .= ' ' . $line;
1818 + }
1819 + }
1820 + }
1821 +
1822 + // Build the formatted response
1823 + if ( ! empty( $introduction ) ) {
1824 + $formatted_response .= esc_html( trim( $introduction ) ) . "<br><br>";
1825 + }
1826 +
1827 + foreach ( $recommendations_summary as $index => $rec ) {
1828 + $name = esc_html( $rec['name'] );
1829 + $price = esc_html( $rec['price'] );
1830 + $url = esc_url( $rec['url'] );
1831 + $image = esc_url( $rec['image'] );
1832 +
1833 + $formatted_response .= ( $index + 1 ) . ". <strong>{$name}</strong> - <strong>\${$price}</strong><br>";
1834 + $formatted_response .= "<a href=\"{$url}\" target=\"_self\">Check it out here!</a><br>";
1835 + $formatted_response .= "<img src=\"{$image}\" alt=\"{$name}\" style=\"max-width: 200px; height: auto; display: block; margin: 10px 0;\" /><br>";
1836 +
1837 + if ( isset( $justifications[ $index ] ) ) {
1838 + $formatted_response .= "<em>" . esc_html( trim( $justifications[ $index ] ) ) . "</em><br><br>";
1839 + }
1840 + }
1841 +
1842 + $this->fallbackResponse = [
1843 + 'text' => $formatted_response,
1844 + ];
1845 + //error_log( 'Final formatted response set.' );
1846 + } catch ( Exception $e ) {
1847 + //error_log( 'Error in mxchat_handle_product_recommendations: ' . $e->getMessage() );
1848 + $this->fallbackResponse = [
1849 + 'text' => __( 'An unexpected error occurred while generating recommendations. Please try again later.', 'mxchat' ),
1850 + ];
1851 + }
1852 +}
1853 +
1854 +
1855 +
1856 +
1857 +
1858 +
1859 +// Inside MxChat_Integrator class
1860 +private function mxchat_generate_recommendations($user_id) {
1861 + $recommendations = [];
1862 + $recommendation_sources = [];
1863 + $added_product_ids = []; // To track unique products
1864 +
1865 + // 1. Recommendations based on order history
1866 + if (is_user_logged_in() && $user_id) {
1867 + $order_recommendations = $this->mxchat_get_recommendations_from_order_history($user_id);
1868 + foreach ($order_recommendations as $product) {
1869 + if (!in_array($product->get_id(), $added_product_ids)) {
1870 + $recommendations[] = $product;
1871 + $added_product_ids[] = $product->get_id();
1872 + $recommendation_sources[] = "Order history";
1873 + }
1874 + }
1875 + }
1876 +
1877 + // 2. Recommendations based on cart contents
1878 + if (WC()->cart && WC()->cart->get_cart_contents_count() > 0) {
1879 + $cart_recommendations = $this->mxchat_get_recommendations_from_cart();
1880 + foreach ($cart_recommendations as $product) {
1881 + if (!in_array($product->get_id(), $added_product_ids)) {
1882 + $recommendations[] = $product;
1883 + $added_product_ids[] = $product->get_id();
1884 + $recommendation_sources[] = "Cart contents";
1885 + }
1886 + }
1887 + }
1888 +
1889 + // 3. General recommendations (bestsellers or sale items)
1890 + $general_recommendations = $this->mxchat_get_general_recommendations();
1891 + foreach ($general_recommendations as $product) {
1892 + if (!in_array($product->get_id(), $added_product_ids)) {
1893 + $recommendations[] = $product;
1894 + $added_product_ids[] = $product->get_id();
1895 + $recommendation_sources[] = "General recommendations";
1896 + }
1897 + }
1898 +
1899 + // Format for output
1900 + $formatted_recommendations = [];
1901 + foreach ($recommendations as $product) {
1902 + $formatted_recommendations[] = [
1903 + 'name' => $product->get_name(),
1904 + 'price' => $product->get_price_html(),
1905 + 'url' => get_permalink($product->get_id()),
1906 + 'image' => wp_get_attachment_url($product->get_image_id()),
1907 + ];
1908 + }
1909 +
1910 + return [
1911 + 'recommendations' => $formatted_recommendations,
1912 + 'sources' => array_unique($recommendation_sources),
1913 + ];
1914 +}
1915 +private function mxchat_get_recommendations_from_order_history($user_id) {
1916 + $args = [
1917 + 'customer_id' => $user_id,
1918 + 'limit' => -1,
1919 + ];
1920 + $orders = wc_get_orders($args);
1921 +
1922 + $purchased_products = [];
1923 + foreach ($orders as $order) {
1924 + foreach ($order->get_items() as $item) {
1925 + $purchased_products[] = $item->get_product_id();
1926 + }
1927 + }
1928 +
1929 + $related_product_ids = wc_get_related_products($purchased_products, 5); // Get up to 5 related products
1930 + return wc_get_products(['include' => $related_product_ids]);
1931 +}
1932 +private function mxchat_get_recommendations_from_cart() {
1933 + $cart = WC()->cart->get_cart();
1934 + $cart_product_ids = array_map(function ($cart_item) {
1935 + return $cart_item['product_id'];
1936 + }, $cart);
1937 +
1938 + $related_product_ids = wc_get_related_products($cart_product_ids, 5); // Get up to 5 related products
1939 + return wc_get_products(['include' => $related_product_ids]);
1940 +}
1941 +private function mxchat_get_general_recommendations() {
1942 + $args = [
1943 + 'status' => 'publish',
1944 + 'limit' => 5,
1945 + 'orderby' => 'popularity',
1946 + 'meta_query' => [
1947 + 'relation' => 'OR',
1948 + [
1949 + 'key' => '_sale_price',
1950 + 'compare' => '>',
1951 + 'value' => 0,
1952 + ]
1953 + ],
1954 + ];
1955 +
1956 + return wc_get_products($args);
1957 +}
1958 +
1959 +
1960 +
1961 +
1962 +
2007 1963 function mxchat_fetch_new_messages() {
2008 1964 $session_id = sanitize_text_field($_POST['session_id']);
2009 1965 $last_seen_id = sanitize_text_field($_POST['last_seen_id']);
2010 1966 $persistence_enabled = $_POST['persistence_enabled'] === 'true';
@@ -2010,10 +1966,10 @@
2010 1966 $persistence_enabled = $_POST['persistence_enabled'] === 'true';
2011 1967 $initial_timestamp = isset($_POST['initial_timestamp']) ? intval($_POST['initial_timestamp']) : 0;
2012 1968
2013 1969 if (empty($session_id)) {
2014 - //error_log(esc_html__('Fetch new messages error: Session ID missing.', 'mxchat'));
2015 - wp_send_json_error(['message' => esc_html__('Session ID missing.', 'mxchat')]);
1970 + //error_log('Fetch new messages error: Session ID missing.');
1971 + wp_send_json_error(['message' => 'Session ID missing.']);
2016 1972 wp_die();
2017 1973 }
2018 1974
2019 1975 $history = get_option("mxchat_history_{$session_id}", []);
@@ -2031,9 +1987,9 @@
2031 1987 $message['role'] === 'agent' &&
2032 1988 $message['timestamp'] > $initial_timestamp;
2033 1989 });
2034 1990
2035 - //error_log(esc_html__("New agent messages fetched for session $session_id. Last seen ID: $last_seen_id", 'mxchat'));
1991 + //error_log("New agent messages fetched for session $session_id. Last seen ID: $last_seen_id");
2036 1992
2037 1993 wp_send_json_success([
2038 1994 'new_messages' => array_values($new_messages)
2039 1995 ]);
@@ -2042,11 +1998,13 @@
2042 1998
2043 1999
2044 2000 public function mxchat_live_agent_handover($message, $user_id, $session_id) {
2045 2001 // First check if live agents are available
2046 - $live_agent_available = $this->options['live_agent_status'] ?? 'off';
2047 - if ($live_agent_available !== 'on') {
2002 + $live_agent_available = $this->options['live_agent_status'] ?? 'offline';
2003 +
2004 + if ($live_agent_available !== 'online') {
2048 2005 $away_message = $this->options['live_agent_away_message'] ?? 'Sorry, live agents are currently unavailable. I can continue helping you as an AI assistant.';
2006 +
2049 2007 $this->fallbackResponse = [
2050 2008 'text' => $away_message,
2051 2009 'html' => '',
2052 2010 'images' => [],
@@ -2051,8 +2009,11 @@
2051 2009 'html' => '',
2052 2010 'images' => [],
2053 2011 'chat_mode' => 'ai'
2054 2012 ];
2013 +
2014 + //error_log('Live agent handover attempted but agents are offline');
2015 +
2055 2016 wp_send_json([
2056 2017 'text' => $away_message,
2057 2018 'html' => '',
2058 2019 'chat_mode' => 'ai',
@@ -2061,27 +2022,15 @@
2061 2022 wp_die();
2062 2023 }
2063 2024
2064 2025 $slack_webhook_url = $this->options['live_agent_webhook_url'] ?? '';
2026 + //error_log('Slack Webhook URL retrieved: ' . $slack_webhook_url);
2027 +
2065 2028 if (empty($slack_webhook_url)) {
2029 + //error_log('Slack Webhook URL is not configured.');
2066 2030 return false;
2067 2031 }
2068 2032
2069 - // Get recent chat history (last 5 messages)
2070 - $history = get_option("mxchat_history_{$session_id}", []);
2071 - $recent_history = array_slice($history, -5); // Get last 5 messages
2072 -
2073 - // Format conversation history
2074 - $conversation_context = "";
2075 - if (!empty($recent_history)) {
2076 - $conversation_context = "*Recent Conversation:*\n";
2077 - foreach ($recent_history as $hist_message) {
2078 - $role_display = $hist_message['role'] === 'user' ? 'User' : 'AI';
2079 - $conversation_context .= ">{$role_display}: {$hist_message['content']}\n";
2080 - }
2081 - $conversation_context .= "\n";
2082 - }
2083 -
2084 2033 update_option("mxchat_mode_{$session_id}", 'agent');
2085 2034
2086 2035 $webhook_data = [
2087 2036 'blocks' => [
@@ -2097,53 +2046,38 @@
2097 2046 'type' => 'section',
2098 2047 'fields' => [
2099 2048 [
2100 2049 'type' => 'mrkdwn',
2101 - 'text' => sprintf('*User ID:*\n`%s`', $user_id)
2050 + 'text' => "*User ID:*\n`$user_id`"
2102 2051 ],
2103 2052 [
2104 2053 'type' => 'mrkdwn',
2105 - 'text' => sprintf('*Session ID:*\n`%s`', $session_id)
2054 + 'text' => "*Session ID:*\n`$session_id`"
2106 2055 ]
2107 2056 ]
2108 - ]
2109 - ]
2110 - ];
2111 -
2112 - // Add conversation history if exists
2113 - if (!empty($conversation_context)) {
2114 - $webhook_data['blocks'][] = [
2115 - 'type' => 'section',
2116 - 'text' => [
2117 - 'type' => 'mrkdwn',
2118 - 'text' => $conversation_context
2119 - ]
2120 - ];
2121 - }
2122 -
2123 - // Add the current message
2124 - $webhook_data['blocks'][] = [
2125 - 'type' => 'section',
2126 - 'text' => [
2127 - 'type' => 'mrkdwn',
2128 - 'text' => sprintf('*Current Message:*\n%s', $message)
2129 - ]
2130 - ];
2131 -
2132 - // Add the reply button
2133 - $webhook_data['blocks'][] = [
2134 - 'type' => 'actions',
2135 - 'elements' => [
2057 + ],
2136 2058 [
2137 - 'type' => 'button',
2059 + 'type' => 'section',
2138 2060 'text' => [
2139 - 'type' => 'plain_text',
2140 - 'text' => '✍️ Reply',
2141 - 'emoji' => true
2142 - ],
2143 - 'value' => $session_id,
2144 - 'action_id' => 'reply_to_user',
2145 - 'style' => 'primary'
2061 + 'type' => 'mrkdwn',
2062 + 'text' => "*Initial Message:*\n$message"
2063 + ]
2064 + ],
2065 + [
2066 + 'type' => 'actions',
2067 + 'elements' => [
2068 + [
2069 + 'type' => 'button',
2070 + 'text' => [
2071 + 'type' => 'plain_text',
2072 + 'text' => '✍️ Reply',
2073 + 'emoji' => true
2074 + ],
2075 + 'value' => $session_id,
2076 + 'action_id' => 'reply_to_user',
2077 + 'style' => 'primary'
2078 + ]
2079 + ]
2146 2080 ]
2147 2081 ]
2148 2082 ];
2149 2083
@@ -2154,12 +2088,16 @@
2154 2088 ],
2155 2089 ]);
2156 2090
2157 2091 if (is_wp_error($response)) {
2092 + //error_log('Error sending live agent handover: ' . $response->get_error_message());
2158 2093 return false;
2159 2094 }
2160 2095
2096 + //error_log('Live agent handover triggered successfully.');
2097 +
2161 2098 $success_message = $this->options['live_agent_notification_message'] ?? 'Live agent has been notified.';
2099 +
2162 2100 $this->mxchat_save_chat_message($session_id, 'bot', $success_message);
2163 2101
2164 2102 $this->fallbackResponse = [
2165 2103 'text' => $success_message,
@@ -2181,9 +2119,9 @@
2181 2119 public function mxchat_send_user_message_to_agent($message, $user_id, $session_id) {
2182 2120 $slack_webhook_url = $this->options['live_agent_webhook_url'] ?? '';
2183 2121
2184 2122 if (empty($slack_webhook_url)) {
2185 - //error_log(esc_html__('Slack Webhook URL is not configured.', 'mxchat'));
2123 + //error_log('Slack Webhook URL is not configured.');
2186 2124 return false;
2187 2125 }
2188 2126
2189 2127 $webhook_data = [
@@ -2191,9 +2129,9 @@
2191 2129 [
2192 2130 'type' => 'header',
2193 2131 'text' => [
2194 2132 'type' => 'plain_text',
2195 - 'text' => esc_html__('📩 New Chat Message', 'mxchat'),
2133 + 'text' => '📩 New Chat Message',
2196 2134 'emoji' => true
2197 2135 ]
2198 2136 ],
2199 2137 [
@@ -2200,13 +2138,13 @@
2200 2138 'type' => 'section',
2201 2139 'fields' => [
2202 2140 [
2203 2141 'type' => 'mrkdwn',
2204 - 'text' => sprintf(esc_html__('*User ID:*\n`%s`', 'mxchat'), $user_id)
2142 + 'text' => "*User ID:*\n`$user_id`"
2205 2143 ],
2206 2144 [
2207 2145 'type' => 'mrkdwn',
2208 - 'text' => sprintf(esc_html__('*Session ID:*\n`%s`', 'mxchat'), $session_id)
2146 + 'text' => "*Session ID:*\n`$session_id`"
2209 2147 ]
2210 2148 ]
2211 2149 ],
2212 2150 [
@@ -2212,9 +2150,9 @@
2212 2150 [
2213 2151 'type' => 'section',
2214 2152 'text' => [
2215 2153 'type' => 'mrkdwn',
2216 - 'text' => sprintf(esc_html__('*Message:*\n%s', 'mxchat'), $message)
2154 + 'text' => "*Message:*\n$message"
2217 2155 ]
2218 2156 ],
2219 2157 [
2220 2158 'type' => 'actions',
@@ -2222,9 +2160,9 @@
2222 2160 [
2223 2161 'type' => 'button',
2224 2162 'text' => [
2225 2163 'type' => 'plain_text',
2226 - 'text' => esc_html__('✍️ Reply', 'mxchat'),
2164 + 'text' => '✍️ Reply',
2227 2165 'emoji' => true
2228 2166 ],
2229 2167 'value' => $session_id,
2230 2168 'action_id' => 'reply_to_user',
@@ -2242,13 +2180,13 @@
2242 2180 ],
2243 2181 ]);
2244 2182
2245 2183 if (is_wp_error($response)) {
2246 - //error_log(esc_html__('Error sending message to Slack: ', 'mxchat') . $response->get_error_message());
2184 + //error_log('Error sending message to Slack: ' . $response->get_error_message());
2247 2185 return false;
2248 2186 }
2249 2187
2250 - //error_log(esc_html__('Message sent to Slack successfully.', 'mxchat'));
2188 + //error_log('Message sent to Slack successfully.');
2251 2189 return true;
2252 2190 }
2253 2191 public function handle_slack_interaction(WP_REST_Request $request) {
2254 2192 //error_log('Received Slack interaction');
@@ -2265,9 +2203,9 @@
2265 2203 $slack_token = $this->options['live_agent_bot_token'] ?? '';
2266 2204
2267 2205 if (empty($slack_token)) {
2268 2206 //error_log('Slack Bot Token not configured');
2269 - return new WP_REST_Response(['error' => esc_html__('Bot token not configured', 'mxchat')], 400);
2207 + return new WP_REST_Response(['error' => 'Bot token not configured'], 400);
2270 2208 }
2271 2209 $response = wp_remote_post('https://slack.com/api/views.open', [
2272 2210 'headers' => [
2273 2211 'Content-Type' => 'application/json',
@@ -2279,17 +2217,17 @@
2279 2217 'type' => 'modal',
2280 2218 'callback_id' => 'reply_modal',
2281 2219 'title' => [
2282 2220 'type' => 'plain_text',
2283 - 'text' => __('Reply to User', 'mxchat')
2221 + 'text' => 'Reply to User'
2284 2222 ],
2285 2223 'submit' => [
2286 2224 'type' => 'plain_text',
2287 - 'text' => __('Send', 'mxchat')
2225 + 'text' => 'Send'
2288 2226 ],
2289 2227 'close' => [
2290 2228 'type' => 'plain_text',
2291 - 'text' => __('Cancel', 'mxchat')
2229 + 'text' => 'Cancel'
2292 2230 ],
2293 2231 'blocks' => [
2294 2232 [
2295 2233 'type' => 'input',
@@ -2295,9 +2233,9 @@
2295 2233 'type' => 'input',
2296 2234 'block_id' => 'reply_block',
2297 2235 'label' => [
2298 2236 'type' => 'plain_text',
2299 - 'text' => sprintf(__('Reply to session: %s', 'mxchat'), $session_id)
2237 + 'text' => "Reply to session: $session_id"
2300 2238 ],
2301 2239 'element' => [
2302 2240 'type' => 'plain_text_input',
2303 2241 'action_id' => 'message',
@@ -2303,9 +2241,9 @@
2303 2241 'action_id' => 'message',
2304 2242 'multiline' => true,
2305 2243 'placeholder' => [
2306 2244 'type' => 'plain_text',
2307 - 'text' => __('Type your message here...', 'mxchat')
2245 + 'text' => 'Type your message here...'
2308 2246 ]
2309 2247 ]
2310 2248 ]
2311 2249 ],
@@ -2320,21 +2258,19 @@
2320 2258 return new WP_REST_Response(['ok' => true]);
2321 2259 }
2322 2260
2323 2261 // Handle modal submission
2324 -// Handle modal submission
2325 -if ($payload['type'] === 'view_submission') {
2326 - $session_id = $payload['view']['private_metadata'];
2327 - $message = $payload['view']['state']['values']['reply_block']['message']['value'];
2262 + if ($payload['type'] === 'view_submission') {
2263 + $session_id = $payload['view']['private_metadata'];
2264 + $message = $payload['view']['state']['values']['reply_block']['message']['value'];
2328 2265
2329 - // Save the message (keep the message_id but don't include in response)
2330 - $this->mxchat_save_chat_message($session_id, 'agent', $message);
2266 + // Save the message
2267 + $this->mxchat_save_chat_message($session_id, 'agent', $message);
2331 2268
2332 - // Keep the original response format for Slack
2333 - return new WP_REST_Response([
2334 - 'response_action' => 'clear'
2335 - ]);
2336 -}
2269 + return new WP_REST_Response([
2270 + 'response_action' => 'clear'
2271 + ]);
2272 + }
2337 2273
2338 2274 // Default acknowledgment
2339 2275 return new WP_REST_Response(['ok' => true]);
2340 2276 }
@@ -2348,11 +2284,11 @@
2348 2284 $command_text = $request->get_param('text');
2349 2285 // error_log('Command text: ' . $command_text);
2350 2286
2351 2287 if (empty($command_text)) {
2352 - //error_log(esc_html__('Agent response error: No command text received', 'mxchat'));
2288 + error_log('Agent response error: No command text received');
2353 2289 return new WP_REST_Response([
2354 - 'error' => esc_html__('Command text is required. Format: /reply session_id message', 'mxchat')
2290 + 'error' => 'Command text is required. Format: /reply session_id message'
2355 2291 ], 400);
2356 2292 }
2357 2293
2358 2294 // Split the command text into session_id and message
@@ -2359,9 +2295,9 @@
2359 2295 $parts = explode(' ', $command_text, 2);
2360 2296 if (count($parts) !== 2) {
2361 2297 //error_log('Agent response error: Invalid command format');
2362 2298 return new WP_REST_Response([
2363 - 'error' => esc_html__('Invalid format. Use: /reply session_id message', 'mxchat')
2299 + 'error' => 'Invalid format. Use: /reply session_id message'
2364 2300 ], 400);
2365 2301 }
2366 2302
2367 2303 $session_id = sanitize_text_field($parts[0]);
@@ -2374,9 +2310,9 @@
2374 2310
2375 2311 if (!$message_id) {
2376 2312 // error_log('Failed to save agent message');
2377 2313 return new WP_REST_Response([
2378 - 'error' => esc_html__('Failed to save message', 'mxchat')
2314 + 'error' => 'Failed to save message'
2379 2315 ], 500);
2380 2316 }
2381 2317
2382 2318 // Return success response in Slack's expected format
@@ -2381,15 +2317,15 @@
2381 2317
2382 2318 // Return success response in Slack's expected format
2383 2319 return new WP_REST_Response([
2384 2320 'response_type' => 'in_channel',
2385 - 'text' => esc_html__("Message sent successfully to session $session_id", 'mxchat')
2321 + 'text' => "Message sent successfully to session $session_id"
2386 2322 ], 200);
2387 2323 }
2388 2324
2389 2325
2390 2326 public function mxchat_handle_switch_to_chatbot_intent($message, $user_id, $session_id) {
2391 - //error_log(esc_html__("Switching back to chatbot mode via intent.", 'mxchat'));
2327 + //error_log("Switching back to chatbot mode via intent.");
2392 2328
2393 2329 // Just update mode to AI
2394 2330 update_option("mxchat_mode_{$session_id}", 'ai');
2395 2331
@@ -2397,35 +2333,14 @@
2397 2333 $this->fallbackResponse = ['text' => '', 'html' => '', 'images' => []];
2398 2334 $this->productCardHtml = '';
2399 2335
2400 2336 // Set the response message
2401 - $this->fallbackResponse['text'] = esc_html__('You are now chatting with the AI chatbot.', 'mxchat');
2337 + $this->fallbackResponse['text'] = 'You are now chatting with the AI chatbot.';
2402 2338
2403 2339 return true; // Intent was handled
2404 2340 }
2405 2341
2406 2342
2407 -
2408 -
2409 -// For the word upload handler
2410 -public function mxchat_handle_word_upload() {
2411 - // Delegate to word handler
2412 - $this->word_handler->mxchat_handle_word_upload();
2413 -}
2414 -
2415 -// For the word removal handler
2416 -public function mxchat_handle_word_remove() {
2417 - // Delegate to word handler
2418 - $this->word_handler->mxchat_handle_word_remove();
2419 -}
2420 -
2421 -// For the word status check
2422 -public function mxchat_check_word_status() {
2423 - // Delegate to word handler
2424 - $this->word_handler->mxchat_check_word_status();
2425 -}
2426 -
2427 -
2428 2343 private function mxchat_get_user_identifier() {
2429 2344 return MxChat_User::mxchat_get_user_identifier();
2430 2345 }
2431 2346
@@ -2464,74 +2379,32 @@
2464 2379 return null;
2465 2380 }
2466 2381 }
2467 2382
2468 -
2469 2383 private function mxchat_find_relevant_content($user_embedding) {
2470 - //error_log('MXChat Vector Search: Starting content search...');
2471 -
2472 - // Retrieve the add-on settings from the database.
2473 - $addon_options = get_option('mxchat_pinecone_addon_options', array());
2474 -
2475 - // Determine whether Pinecone is enabled.
2476 - // We expect the sanitized setting to be a string '1' if enabled, otherwise '0'.
2477 - $use_pinecone = (isset($addon_options['mxchat_use_pinecone']) && $addon_options['mxchat_use_pinecone'] === '1') ? 1 : 0;
2478 -
2479 - //error_log('Pinecone enabled flag: ' . $use_pinecone);
2480 -
2481 - if ($use_pinecone === 1) {
2482 - //error_log('MXChat Vector Search: Using Pinecone database');
2483 - return $this->find_relevant_content_pinecone($user_embedding);
2484 - } else {
2485 - //error_log('MXChat Vector Search: Using WordPress database');
2486 - return $this->find_relevant_content_wordpress($user_embedding);
2487 - }
2488 -}
2489 -
2490 -private function find_relevant_content_wordpress($user_embedding) {
2491 2384 global $wpdb;
2492 2385 $system_prompt_table = $wpdb->prefix . 'mxchat_system_prompt_content';
2493 2386 $cache_key = 'mxchat_system_prompt_embeddings';
2494 - $batch_size = 500;
2495 2387
2496 - // Retrieve embeddings from cache or database
2388 + // Attempt to get the embeddings from the cache
2497 2389 $embeddings = wp_cache_get($cache_key, 'mxchat_system_prompts');
2498 2390 if ($embeddings === false) {
2499 - $embeddings = [];
2500 - $offset = 0;
2501 -
2502 - // Load in batches and build cache
2503 - do {
2504 - $query = $wpdb->prepare(
2505 - "SELECT id, embedding_vector
2506 - FROM {$system_prompt_table}
2507 - LIMIT %d OFFSET %d",
2508 - $batch_size,
2509 - $offset
2510 - );
2511 -
2512 - $batch = $wpdb->get_results($query);
2513 - if (empty($batch)) {
2514 - break;
2515 - }
2516 -
2517 - $embeddings = array_merge($embeddings, $batch);
2518 - $offset += $batch_size;
2519 -
2520 - // Free memory
2521 - unset($batch);
2522 -
2523 - } while (true);
2524 -
2525 - if (empty($embeddings)) {
2526 - return ''; // Return an empty string if no embeddings found
2391 + // Cache miss, query the database and cache the results
2392 + $query = "SELECT id, embedding_vector FROM {$system_prompt_table}";
2393 + $embeddings = $wpdb->get_results($query);
2394 + if ($embeddings === null || empty($embeddings)) {
2395 + return ''; // Return an empty string for compatibility
2527 2396 }
2528 2397 wp_cache_set($cache_key, $embeddings, 'mxchat_system_prompts', 3600);
2529 2398 }
2530 2399
2531 - // Initialize array to store relevant results with similarity scores
2532 - $relevant_results = [];
2533 - // Iterate through embeddings to calculate similarity
2400 + // Initialize variables to track the two most relevant results
2401 + $most_relevant_id = null;
2402 + $second_most_relevant_id = null;
2403 + $highest_similarity = -INF;
2404 + $second_highest_similarity = -INF;
2405 +
2406 + // Iterate through all embeddings
2534 2407 foreach ($embeddings as $embedding) {
2535 2408 $database_embedding = $embedding->embedding_vector
2536 2409 ? unserialize($embedding->embedding_vector, ['allowed_classes' => false])
2537 2410 : null;
@@ -2536,39 +2409,39 @@
2536 2409 ? unserialize($embedding->embedding_vector, ['allowed_classes' => false])
2537 2410 : null;
2538 2411 if (is_array($database_embedding) && is_array($user_embedding)) {
2539 2412 $similarity = $this->mxchat_calculate_cosine_similarity($user_embedding, $database_embedding);
2540 - $relevant_results[] = [
2541 - 'id' => $embedding->id,
2542 - 'similarity' => $similarity
2543 - ];
2413 + if ($similarity > $highest_similarity) {
2414 + // Shift the current highest to second highest
2415 + $second_highest_similarity = $highest_similarity;
2416 + $second_most_relevant_id = $most_relevant_id;
2417 +
2418 + // Update the new highest
2419 + $highest_similarity = $similarity;
2420 + $most_relevant_id = $embedding->id;
2421 + } elseif ($similarity > $second_highest_similarity) {
2422 + // Update the second highest if applicable
2423 + $second_highest_similarity = $similarity;
2424 + $second_most_relevant_id = $embedding->id;
2425 + }
2544 2426 }
2545 - // Free memory
2546 - unset($database_embedding);
2547 2427 }
2548 2428
2549 - // Retrieve the similarity threshold
2550 - $similarity_threshold = ((int) get_option('mxchat_similarity_threshold', 80)) / 100;
2429 + // Retrieve the similarity threshold and convert it to decimal
2430 + $similarity_threshold = ((int) get_option('mxchat_similarity_threshold', 80)) / 100; // Convert to decimal
2551 2431
2552 - // Filter and sort relevant results by similarity
2553 - $relevant_results = array_filter($relevant_results, function ($result) use ($similarity_threshold) {
2554 - return $result['similarity'] >= $similarity_threshold;
2555 - });
2556 - usort($relevant_results, function ($a, $b) {
2557 - return $b['similarity'] <=> $a['similarity'];
2558 - });
2432 + if ($highest_similarity >= $similarity_threshold) {
2433 + // Fetch content for the most relevant match
2434 + $content = $this->fetch_content_with_product_links($most_relevant_id);
2559 2435
2560 - // Limit to the top 5 results
2561 - $top_results = array_slice($relevant_results, 0, 5);
2436 + // Fetch content for the second most relevant match (if applicable)
2437 + $second_content = '';
2438 + if ($second_highest_similarity >= $similarity_threshold && $second_most_relevant_id !== null) {
2439 + $second_content = $this->fetch_content_with_product_links($second_most_relevant_id);
2440 + }
2562 2441
2563 - // Initialize the final content
2564 - $content = '';
2565 -
2566 - // Fetch and combine content for the top results
2567 - foreach ($top_results as $result) {
2568 - $chunk_content = $this->fetch_content_with_product_links($result['id']);
2569 - // Check if the content is PDF-related and add surrounding pages
2570 - if (strpos($chunk_content, '{"document_type":"pdf"') !== false) {
2442 + // If the most relevant content is PDF-related, handle surrounding content
2443 + if (strpos($content, '{"document_type":"pdf"') !== false) {
2571 2444 $surrounding_content = $wpdb->get_results($wpdb->prepare(
2572 2445 "SELECT article_content FROM {$system_prompt_table}
2573 2446 WHERE id IN (
2574 2447 (SELECT id FROM {$system_prompt_table} WHERE id < %d ORDER BY id DESC LIMIT 1),
@@ -2573,277 +2446,42 @@
2573 2446 WHERE id IN (
2574 2447 (SELECT id FROM {$system_prompt_table} WHERE id < %d ORDER BY id DESC LIMIT 1),
2575 2448 (SELECT id FROM {$system_prompt_table} WHERE id > %d ORDER BY id ASC LIMIT 1)
2576 2449 )",
2577 - $result['id'],
2578 - $result['id']
2450 + $most_relevant_id,
2451 + $most_relevant_id
2579 2452 ));
2580 - // Add previous content if it exists
2453 +
2454 + $combined_content = '';
2455 +
2456 + // Add previous content if exists
2581 2457 if (!empty($surrounding_content[0])) {
2582 - $content .= $surrounding_content[0]->article_content . "\n\n";
2458 + $combined_content .= $surrounding_content[0]->article_content . "\n\n";
2583 2459 }
2584 - // Add the main chunk content
2585 - $content .= $chunk_content . "\n\n";
2586 - // Add next content if it exists
2587 - if (!empty($surrounding_content[1])) {
2588 - $content .= $surrounding_content[1]->article_content . "\n\n";
2589 - }
2590 - } else {
2591 - // For non-PDF content, add directly
2592 - $content .= $chunk_content . "\n\n";
2593 - }
2594 - }
2595 2460
2596 - return trim($content);
2597 -}
2598 -/**
2599 - * Find relevant content in Pinecone vector database
2600 - */
2601 -private function find_relevant_content_pinecone($user_embedding) {
2602 - $options = get_option('mxchat_pinecone_addon_options', array());
2603 - $api_key = $options['mxchat_pinecone_api_key'] ?? '';
2604 - $host = $options['mxchat_pinecone_host'] ?? '';
2461 + // Add main content
2462 + $combined_content .= $content;
2605 2463
2606 - if (empty($host) || empty($api_key)) {
2607 - //error_log('Pinecone credentials not properly configured');
2608 - return '';
2609 - }
2610 -
2611 - // Get similarity threshold from WordPress settings
2612 - $similarity_threshold = ((int) get_option('mxchat_similarity_threshold', 80)) / 100;
2613 -
2614 - // Prepare the query request for Pinecone
2615 - $api_endpoint = "https://{$host}/query";
2616 -
2617 - $request_body = array(
2618 - 'vector' => $user_embedding,
2619 - 'topK' => 5,
2620 - 'includeMetadata' => true,
2621 - 'includeValues' => true
2622 - );
2623 -
2624 - $response = wp_remote_post($api_endpoint, array(
2625 - 'headers' => array(
2626 - 'Api-Key' => $api_key,
2627 - 'accept' => 'application/json',
2628 - 'content-type' => 'application/json'
2629 - ),
2630 - 'body' => wp_json_encode($request_body),
2631 - 'timeout' => 30
2632 - ));
2633 -
2634 - if (is_wp_error($response)) {
2635 - //error_log('Pinecone query error: ' . $response->get_error_message());
2636 - return '';
2637 - }
2638 -
2639 - $response_code = wp_remote_retrieve_response_code($response);
2640 - if ($response_code !== 200) {
2641 - //error_log('Pinecone API error: ' . wp_remote_retrieve_body($response));
2642 - return '';
2643 - }
2644 -
2645 - $results = json_decode(wp_remote_retrieve_body($response), true);
2646 - if (empty($results['matches'])) {
2647 - return '';
2648 - }
2649 -
2650 - // Initialize the final content
2651 - $content = '';
2652 -
2653 - // Process each match
2654 - foreach ($results['matches'] as $match) {
2655 - // Skip if similarity is below threshold
2656 - if ($match['score'] < $similarity_threshold) {
2657 - continue;
2658 - }
2659 -
2660 - if (!empty($match['metadata']['text']) && !empty($match['metadata']['source_url'])) {
2661 - // Add content with citation
2662 - $content .= $match['metadata']['text'] . "\n";
2663 - $content .= "Source: " . $match['metadata']['source_url'] . "\n\n";
2664 - }
2665 - }
2666 -
2667 - return trim($content);
2668 -}
2669 -
2670 -
2671 -private function mxchat_find_relevant_products($user_embedding) {
2672 - //error_log('MXChat Vector Search: Starting product search...');
2673 -
2674 - // Retrieve the add-on settings from the database
2675 - $addon_options = get_option('mxchat_pinecone_addon_options', array());
2676 -
2677 - // Determine whether Pinecone is enabled
2678 - $use_pinecone = (isset($addon_options['mxchat_use_pinecone']) && $addon_options['mxchat_use_pinecone'] === '1') ? 1 : 0;
2679 -
2680 - //error_log('Pinecone enabled flag: ' . $use_pinecone);
2681 -
2682 - if ($use_pinecone === 1) {
2683 - //error_log('MXChat Vector Search: Using Pinecone database for products');
2684 - return $this->find_relevant_products_pinecone($user_embedding);
2685 - } else {
2686 - //error_log('MXChat Vector Search: Using WordPress database for products');
2687 - return $this->find_relevant_products_wordpress($user_embedding);
2688 - }
2689 -}
2690 -
2691 -private function find_relevant_products_wordpress($user_embedding) {
2692 - global $wpdb;
2693 - $system_prompt_table = $wpdb->prefix . 'mxchat_system_prompt_content';
2694 - $cache_key = 'mxchat_system_prompt_embeddings';
2695 - $batch_size = 500;
2696 -
2697 - // Original WordPress database search logic
2698 - // [Previous implementation remains the same]
2699 - $embeddings = wp_cache_get($cache_key, 'mxchat_system_prompts');
2700 - if ($embeddings === false) {
2701 - $embeddings = [];
2702 - $offset = 0;
2703 -
2704 - do {
2705 - $query = $wpdb->prepare(
2706 - "SELECT id, embedding_vector
2707 - FROM {$system_prompt_table}
2708 - LIMIT %d OFFSET %d",
2709 - $batch_size,
2710 - $offset
2711 - );
2712 -
2713 - $batch = $wpdb->get_results($query);
2714 - if (empty($batch)) {
2715 - break;
2464 + // Add next content if exists
2465 + if (!empty($surrounding_content[1])) {
2466 + $combined_content .= "\n\n" . $surrounding_content[1]->article_content;
2716 2467 }
2717 2468
2718 - $embeddings = array_merge($embeddings, $batch);
2719 - $offset += $batch_size;
2720 -
2721 - unset($batch);
2722 -
2723 - } while (true);
2724 -
2725 - if (empty($embeddings)) {
2726 - return '';
2469 + return $combined_content;
2727 2470 }
2728 - wp_cache_set($cache_key, $embeddings, 'mxchat_system_prompts', 3600);
2729 - }
2730 2471
2731 - $relevant_results = [];
2732 - foreach ($embeddings as $embedding) {
2733 - $database_embedding = $embedding->embedding_vector
2734 - ? unserialize($embedding->embedding_vector, ['allowed_classes' => false])
2735 - : null;
2736 - if (is_array($database_embedding) && is_array($user_embedding)) {
2737 - $similarity = $this->mxchat_calculate_cosine_similarity($user_embedding, $database_embedding);
2738 - $relevant_results[] = [
2739 - 'id' => $embedding->id,
2740 - 'similarity' => $similarity
2741 - ];
2472 + // Combine most relevant and second most relevant content if available
2473 + if (!empty($second_content)) {
2474 + return $content . "\n\n---\n\n" . $second_content; // Separate the two contents with a delimiter
2742 2475 }
2743 - unset($database_embedding);
2744 - }
2745 2476
2746 - // Use fixed threshold for products
2747 - $similarity_threshold = 0.85;
2748 -
2749 - $relevant_results = array_filter($relevant_results, function ($result) use ($similarity_threshold) {
2750 - return $result['similarity'] >= $similarity_threshold;
2751 - });
2752 - usort($relevant_results, function ($a, $b) {
2753 - return $b['similarity'] <=> $a['similarity'];
2754 - });
2755 -
2756 - $top_results = array_slice($relevant_results, 0, 5);
2757 - $content = '';
2758 -
2759 - foreach ($top_results as $result) {
2760 - $chunk_content = $this->fetch_content_with_product_links($result['id']);
2761 - $content .= $chunk_content . "\n\n";
2477 + return $content; // Return only the most relevant content if no second content is found
2762 2478 }
2763 2479
2764 - return trim($content);
2480 + return ''; // Return an empty string for compatibility
2765 2481 }
2766 2482
2767 -// Modified search function with correct filter syntax
2768 -private function find_relevant_products_pinecone($user_embedding) {
2769 - //error_log('Starting Pinecone product search...');
2770 2483
2771 - $options = get_option('mxchat_pinecone_addon_options', array());
2772 - $api_key = $options['mxchat_pinecone_api_key'] ?? '';
2773 - $host = $options['mxchat_pinecone_host'] ?? '';
2774 -
2775 - if (empty($host) || empty($api_key)) {
2776 - //error_log('Pinecone credentials not properly configured for product search');
2777 - return '';
2778 - }
2779 -
2780 - $similarity_threshold = 0.85;
2781 - $api_endpoint = "https://{$host}/query";
2782 -
2783 - $request_body = array(
2784 - 'vector' => $user_embedding,
2785 - 'topK' => 5,
2786 - 'includeMetadata' => true,
2787 - 'includeValues' => true,
2788 - 'filter' => array(
2789 - 'type' => 'product'
2790 - )
2791 - );
2792 -
2793 - //error_log('Sending request to Pinecone with body: ' . wp_json_encode($request_body));
2794 -
2795 - $response = wp_remote_post($api_endpoint, array(
2796 - 'headers' => array(
2797 - 'Api-Key' => $api_key,
2798 - 'accept' => 'application/json',
2799 - 'content-type' => 'application/json'
2800 - ),
2801 - 'body' => wp_json_encode($request_body),
2802 - 'timeout' => 30
2803 - ));
2804 -
2805 - if (is_wp_error($response)) {
2806 - //error_log('Pinecone product query error: ' . $response->get_error_message());
2807 - return '';
2808 - }
2809 -
2810 - $response_code = wp_remote_retrieve_response_code($response);
2811 - //error_log('Pinecone response code: ' . $response_code);
2812 -
2813 - if ($response_code !== 200) {
2814 - //error_log('Pinecone API error during product search: ' . wp_remote_retrieve_body($response));
2815 - return '';
2816 - }
2817 -
2818 - $results = json_decode(wp_remote_retrieve_body($response), true);
2819 - //error_log('Pinecone raw response: ' . wp_remote_retrieve_body($response));
2820 -
2821 - if (empty($results['matches'])) {
2822 - //error_log('No matches found in Pinecone response');
2823 - return '';
2824 - }
2825 -
2826 - $content = '';
2827 - foreach ($results['matches'] as $match) {
2828 - if ($match['score'] < $similarity_threshold) {
2829 - //error_log("Match below threshold: " . $match['score']);
2830 - continue;
2831 - }
2832 -
2833 - if (!empty($match['metadata']['text'])) {
2834 - $content .= $match['metadata']['text'];
2835 - if (!empty($match['metadata']['source_url'])) {
2836 - $content .= "\n\nFor more details, check out this product: " . esc_url($match['metadata']['source_url']);
2837 - }
2838 - $content .= "\n\n";
2839 - }
2840 - }
2841 -
2842 - return trim($content);
2843 -}
2844 -
2845 -
2846 2484 private function fetch_content_with_product_links($most_relevant_id) {
2847 2485 global $wpdb;
2848 2486 $system_prompt_table = $wpdb->prefix . 'mxchat_system_prompt_content';
2849 2487
@@ -2862,216 +2500,69 @@
2862 2500
2863 2501 return null;
2864 2502 }
2865 2503
2866 -// Function definition
2867 -private function mxchat_generate_response($relevant_content, $api_key, $xai_api_key, $claude_api_key, $deepseek_api_key, $conversation_history) {
2868 - try {
2869 - if (!$relevant_content) {
2870 - return esc_html__("I'm sorry, I couldn't find relevant information on that topic.", 'mxchat');
2871 - }
2872 -
2873 - // Ensure conversation_history is an array
2874 - if (!is_array($conversation_history)) {
2875 - $conversation_history = array();
2876 - }
2877 -
2878 - // Get selected model with default fallback
2879 - $selected_model = isset($this->options['model']) ? $this->options['model'] : 'gpt-4o';
2880 -
2881 - // Extract model prefix to determine the provider
2882 - $model_parts = explode('-', $selected_model);
2883 - $provider = strtolower($model_parts[0]);
2884 -
2885 - // Handle model selection based on provider prefix
2886 - switch ($provider) {
2887 - case 'claude':
2888 - if (empty($claude_api_key)) {
2889 - throw new Exception(esc_html__('Claude API key is not configured', 'mxchat'));
2890 - }
2891 - return $this->mxchat_generate_response_claude(
2892 - $selected_model,
2893 - $claude_api_key,
2894 - $conversation_history,
2895 - $relevant_content
2896 - );
2897 -
2898 - case 'grok':
2899 - if (empty($xai_api_key)) {
2900 - throw new Exception(esc_html__('X.AI API key is not configured', 'mxchat'));
2901 - }
2902 - return $this->mxchat_generate_response_xai(
2903 - $selected_model,
2904 - $xai_api_key,
2905 - $conversation_history,
2906 - $relevant_content
2907 - );
2908 -
2909 - case 'deepseek':
2910 - if (empty($deepseek_api_key)) {
2911 - throw new Exception(esc_html__('DeepSeek API key is not configured', 'mxchat'));
2912 - }
2913 - return $this->mxchat_generate_response_deepseek(
2914 - $selected_model,
2915 - $deepseek_api_key,
2916 - $conversation_history,
2917 - $relevant_content
2918 - );
2919 -
2920 - case 'gpt':
2921 - if (empty($api_key)) {
2922 - throw new Exception(esc_html__('OpenAI API key is not configured', 'mxchat'));
2923 - }
2924 - return $this->mxchat_generate_response_openai(
2925 - $selected_model,
2926 - $api_key,
2927 - $conversation_history,
2928 - $relevant_content
2929 - );
2930 -
2931 - default:
2932 - // Default to OpenAI for custom models or unrecognized prefixes
2933 - if (empty($api_key)) {
2934 - throw new Exception(esc_html__('OpenAI API key is not configured', 'mxchat'));
2935 - }
2936 - return $this->mxchat_generate_response_openai(
2937 - $selected_model,
2938 - $api_key,
2939 - $conversation_history,
2940 - $relevant_content
2941 - );
2942 - }
2943 - } catch (Exception $e) {
2944 - //error_log('MXChat Error: ' . $e->getMessage());
2945 - return sprintf(
2946 - esc_html__('An error occurred: %s', 'mxchat'),
2947 - esc_html($e->getMessage())
2948 - );
2504 +private function mxchat_generate_response($relevant_content, $api_key, $xai_api_key, $claude_api_key, $conversation_history) {
2505 + if (!$relevant_content) {
2506 + return "I'm sorry, I couldn't find relevant information on that topic.";
2949 2507 }
2950 -}
2951 2508
2509 + // Check the selected model
2510 + $selected_model = isset($this->options['model']) ? $this->options['model'] : 'gpt-3.5-turbo';
2952 2511
2953 -private function mxchat_generate_response_deepseek($selected_model, $deepseek_api_key, $conversation_history, $relevant_content) {
2954 - // Ensure conversation_history is an array
2955 - if (!is_array($conversation_history)) {
2956 - $conversation_history = array();
2957 - }
2958 -
2959 - // Get system prompt instructions from options
2960 - $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : '';
2961 -
2962 - // Create a new array for the formatted conversation
2963 - $formatted_conversation = array();
2964 -
2965 - // Add system message first
2966 - $formatted_conversation[] = array(
2967 - 'role' => 'system',
2968 - 'content' => $system_prompt_instructions . " " . $relevant_content
2969 - );
2970 -
2971 - // Add the rest of the conversation history
2972 - foreach ($conversation_history as $message) {
2973 - if (is_array($message) && isset($message['role']) && isset($message['content'])) {
2974 - $role = $message['role'];
2975 -
2976 - // Convert roles to supported format
2977 - if ($role === 'bot' || $role === 'agent') {
2978 - $role = 'assistant';
2979 - }
2980 - if (!in_array($role, ['system', 'assistant', 'user', 'function', 'tool'])) {
2981 - $role = 'user';
2982 - }
2983 -
2984 - $formatted_conversation[] = array(
2985 - 'role' => $role,
2986 - 'content' => $message['content']
2987 - );
2988 - }
2989 - }
2990 -
2991 - $body = json_encode([
2992 - 'model' => $selected_model,
2993 - 'messages' => $formatted_conversation,
2994 - 'temperature' => 0.8,
2995 - 'stream' => false
2996 - ]);
2997 -
2998 - $args = [
2999 - 'body' => $body,
3000 - 'headers' => [
3001 - 'Content-Type' => 'application/json',
3002 - 'Authorization' => 'Bearer ' . $deepseek_api_key,
3003 - ],
3004 - 'timeout' => 60,
3005 - 'redirection' => 5,
3006 - 'blocking' => true,
3007 - 'httpversion' => '1.0',
3008 - 'sslverify' => true,
3009 - ];
3010 -
3011 - $response = wp_remote_post('https://api.deepseek.com/v1/chat/completions', $args);
3012 -
3013 - if (is_wp_error($response)) {
3014 - //error_log('DeepSeek API Error: ' . $response->get_error_message());
3015 - return "Sorry, there was an error processing your request.";
3016 - }
3017 -
3018 - $response_body = wp_remote_retrieve_body($response);
3019 - $decoded_response = json_decode($response_body, true);
3020 -
3021 - if (isset($decoded_response['choices'][0]['message']['content'])) {
3022 - return trim($decoded_response['choices'][0]['message']['content']);
2512 + // Call the appropriate function based on the selected model
2513 + if (strpos($selected_model, 'claude') !== false) {
2514 + return $this->mxchat_generate_response_claude($selected_model, $claude_api_key, $conversation_history, $relevant_content);
2515 + } elseif ($selected_model === 'grok-beta') {
2516 + return $this->mxchat_generate_response_xai($selected_model, $xai_api_key, $conversation_history, $relevant_content);
3023 2517 } else {
3024 - //error_log('DeepSeek API Response Format Error: ' . print_r($decoded_response, true));
3025 - return "Sorry, I couldn't process that request.";
2518 + return $this->mxchat_generate_response_openai($selected_model, $api_key, $conversation_history, $relevant_content);
3026 2519 }
3027 2520 }
3028 2521
3029 2522 private function mxchat_generate_response_openai($selected_model, $api_key, $conversation_history, $relevant_content) {
3030 - // Ensure conversation_history is an array
3031 - if (!is_array($conversation_history)) {
3032 - $conversation_history = array();
3033 - }
3034 -
3035 2523 // Get system prompt instructions from options
3036 2524 $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : '';
3037 2525
3038 - // Create a new array for the formatted conversation
3039 - $formatted_conversation = array();
2526 + // Add system prompt to relevant content
2527 + $content_with_instructions = $system_prompt_instructions . " " . $relevant_content;
3040 2528
3041 - // Add system message first
3042 - $formatted_conversation[] = array(
2529 + // Prepend system instructions to the conversation history
2530 + array_unshift($conversation_history, [
3043 2531 'role' => 'system',
3044 - 'content' => $system_prompt_instructions . " " . $relevant_content
3045 - );
2532 + 'content' => "Here are your instructions: " . $content_with_instructions
2533 + ]);
3046 2534
3047 - // Add the rest of the conversation history
3048 - foreach ($conversation_history as $message) {
3049 - if (is_array($message) && isset($message['role']) && isset($message['content'])) {
3050 - $role = $message['role'];
3051 -
3052 - // Convert roles to supported format
3053 - if ($role === 'bot' || $role === 'agent') {
3054 - $role = 'assistant';
2535 + // Ensure consistency: Replace 'bot' and 'agent' roles with supported values
2536 + foreach ($conversation_history as &$message) {
2537 + if ($message['role'] === 'bot') {
2538 + $message['role'] = 'assistant';
2539 + } elseif ($message['role'] === 'agent') {
2540 + // Tag the message as coming from a live agent
2541 + $message['role'] = 'assistant';
2542 + if (!isset($message['metadata'])) {
2543 + $message['metadata'] = ['source' => 'live_agent'];
3055 2544 }
3056 - if (!in_array($role, ['system', 'assistant', 'user', 'function', 'tool'])) {
3057 - $role = 'user';
3058 - }
2545 + }
3059 2546
3060 - $formatted_conversation[] = array(
3061 - 'role' => $role,
3062 - 'content' => $message['content']
3063 - );
2547 + // Ensure all roles are valid
2548 + if (!in_array($message['role'], ['system', 'assistant', 'user', 'function', 'tool'])) {
2549 + $message['role'] = 'user'; // Default to 'user'
3064 2550 }
3065 2551 }
3066 2552
2553 +
2554 + // Build the request body
3067 2555 $body = json_encode([
3068 2556 'model' => $selected_model,
3069 - 'messages' => $formatted_conversation,
2557 + 'messages' => $conversation_history,
3070 2558 'temperature' => 0.8,
3071 2559 'stream' => false
3072 2560 ]);
3073 2561
2562 + //error_log("OpenAI API Request Body: " . $body);
2563 +
2564 + // Set up the API request
3074 2565 $args = [
3075 2566 'body' => $body,
3076 2567 'headers' => [
3077 2568 'Content-Type' => 'application/json',
@@ -3083,25 +2574,35 @@
3083 2574 'httpversion' => '1.0',
3084 2575 'sslverify' => true,
3085 2576 ];
3086 2577
2578 + // Make the API request
3087 2579 $response = wp_remote_post('https://api.openai.com/v1/chat/completions', $args);
3088 2580
2581 + // Log the response or error
3089 2582 if (is_wp_error($response)) {
3090 - //error_log('OpenAI API Error: ' . $response->get_error_message());
2583 + //error_log("OpenAI API Error: " . $response->get_error_message());
3091 2584 return "Sorry, there was an error processing your request.";
3092 2585 }
3093 2586
2587 + // Log raw response for debugging
2588 + //error_log("OpenAI API Raw Response: " . print_r($response, true));
2589 +
3094 2590 $response_body = wp_remote_retrieve_body($response);
3095 2591 $decoded_response = json_decode($response_body, true);
3096 2592
2593 + // Log the decoded response body
2594 + //error_log("OpenAI API Decoded Response: " . print_r($decoded_response, true));
2595 +
3097 2596 if (isset($decoded_response['choices'][0]['message']['content'])) {
3098 2597 return trim($decoded_response['choices'][0]['message']['content']);
3099 2598 } else {
3100 - //error_log('OpenAI API Response Format Error: ' . print_r($decoded_response, true));
2599 + // Log an error if the expected response format is missing
2600 + //error_log("OpenAI API Response Format Error: Expected 'choices[0][message][content]' not found.");
3101 2601 return "Sorry, I couldn't process that request.";
3102 2602 }
3103 2603 }
2604 +
3104 2605 private function mxchat_generate_response_xai($selected_model, $xai_api_key, $conversation_history, $relevant_content) {
3105 2606 // Get system prompt instructions from options
3106 2607 $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : '';
3107 2608
@@ -3172,107 +2673,83 @@
3172 2673 }
3173 2674 }
3174 2675
3175 2676 private function mxchat_generate_response_claude($selected_model, $claude_api_key, $conversation_history, $relevant_content) {
3176 - // Get system prompt instructions from options
2677 + // Get system prompt instructions from options for Claude's top-level system parameter
3177 2678 $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : '';
3178 2679
3179 - // Clean and validate conversation history
2680 + // Ensure consistency: Replace 'bot' and 'agent' roles with supported values
3180 2681 foreach ($conversation_history as &$message) {
3181 - // Convert bot and agent roles to assistant
3182 - if ($message['role'] === 'bot' || $message['role'] === 'agent') {
2682 + if ($message['role'] === 'bot') {
3183 2683 $message['role'] = 'assistant';
2684 + } elseif ($message['role'] === 'agent') {
2685 + // Tag the message as coming from a live agent
2686 + $message['role'] = 'assistant';
2687 + if (!isset($message['metadata'])) {
2688 + $message['metadata'] = ['source' => 'live_agent'];
2689 + }
3184 2690 }
3185 -
3186 - // Remove unsupported roles - Claude only supports 'assistant' and 'user'
3187 - if (!in_array($message['role'], ['assistant', 'user'])) {
3188 - $message['role'] = 'user';
3189 - }
3190 2691
3191 - // Ensure content field exists
3192 - if (!isset($message['content']) || empty($message['content'])) {
3193 - $message['content'] = '';
2692 + // Ensure all roles are valid
2693 + if (!in_array($message['role'], ['system', 'assistant', 'user', 'function', 'tool'])) {
2694 + $message['role'] = 'user'; // Default to 'user'
3194 2695 }
3195 -
3196 - // Remove any unsupported fields
3197 - $message = array_intersect_key($message, array_flip(['role', 'content']));
3198 2696 }
3199 2697
3200 - // Add relevant content as the latest user message
2698 + // Add relevant content as the latest user message in conversation history
3201 2699 $conversation_history[] = [
3202 2700 'role' => 'user',
3203 2701 'content' => $relevant_content
3204 2702 ];
3205 2703
3206 - // Build request body
2704 + // Build the request body with Claude's expected structure, using system instructions as a top-level parameter
3207 2705 $body = json_encode([
3208 2706 'model' => $selected_model,
3209 2707 'max_tokens' => 1000,
3210 2708 'temperature' => 0.8,
3211 - 'messages' => $conversation_history,
3212 - 'system' => $system_prompt_instructions
2709 + 'system' => $system_prompt_instructions, // Set the system prompt at the top level as required
2710 + 'messages' => $conversation_history
3213 2711 ]);
3214 2712
3215 - // Set up API request
2713 + // Set up the API request with the necessary headers
3216 2714 $args = [
3217 - 'body' => $body,
3218 - 'headers' => [
3219 - 'Content-Type' => 'application/json',
3220 - 'x-api-key' => $claude_api_key,
3221 - 'anthropic-version' => '2023-06-01'
3222 - ],
3223 - 'timeout' => 60,
2715 + 'body' => $body,
2716 + 'headers' => [
2717 + 'Content-Type' => 'application/json',
2718 + 'x-api-key' => $claude_api_key,
2719 + 'anthropic-version' => '2023-06-01',
2720 + ],
2721 + 'timeout' => 60,
3224 2722 'redirection' => 5,
3225 - 'blocking' => true,
2723 + 'blocking' => true,
3226 2724 'httpversion' => '1.0',
3227 - 'sslverify' => true,
2725 + 'sslverify' => true,
3228 2726 ];
3229 2727
3230 - // Make API request
2728 + // Make the API request
3231 2729 $response = wp_remote_post('https://api.anthropic.com/v1/messages', $args);
3232 2730
3233 - // Check for WordPress errors
2731 +/*
2732 + // Check for errors and log the response for debugging
3234 2733 if (is_wp_error($response)) {
3235 - //error_log("Claude API request error: " . $response->get_error_message());
3236 - return "Sorry, there was an error connecting to the API.";
2734 + error_log("Claude API request error: " . print_r($response->get_error_message(), true));
2735 + return "Sorry, there was an error processing your request.";
3237 2736 }
2737 +*/
3238 2738
3239 - // Check HTTP response code
3240 - $http_code = wp_remote_retrieve_response_code($response);
3241 - if ($http_code !== 200) {
3242 - $error_body = wp_remote_retrieve_body($response);
3243 - //error_log("Claude API HTTP error: " . $http_code . " - " . $error_body);
3244 -
3245 - // Try to extract error message from response
3246 - $error_data = json_decode($error_body, true);
3247 - $error_message = isset($error_data['error']['message']) ?
3248 - $error_data['error']['message'] :
3249 - "HTTP error " . $http_code;
3250 -
3251 - return "Sorry, the API returned an error: " . $error_message;
3252 - }
3253 2739
3254 - // Parse response
2740 + // Decode the response and parse according to the expected Claude response structure
3255 2741 $response_body = json_decode(wp_remote_retrieve_body($response), true);
3256 -
3257 - // Check for JSON decode errors
3258 - if (json_last_error() !== JSON_ERROR_NONE) {
3259 - //error_log("Claude API JSON decode error: " . json_last_error_msg());
3260 - return "Sorry, there was an error processing the API response.";
3261 - }
2742 + //error_log("Claude API response: " . print_r($response_body, true));
3262 2743
3263 - // Extract and validate response content
3264 - if (isset($response_body['content']) &&
3265 - is_array($response_body['content']) &&
3266 - !empty($response_body['content']) &&
3267 - isset($response_body['content'][0]['text'])) {
2744 + // Check if the response has the expected 'content' array with 'text' blocks
2745 + if (isset($response_body['content'][0]['text'])) {
3268 2746 return trim($response_body['content'][0]['text']);
2747 + } else {
2748 + return "Sorry, I couldn't process that request.";
3269 2749 }
2750 +}
3270 2751
3271 - // Log unexpected response format
3272 - //error_log("Claude API unexpected response format: " . print_r($response_body, true));
3273 - return "Sorry, I received an unexpected response format from the API.";
3274 -}
3275 2752 public function mxchat_dismiss_pre_chat_message() {
3276 2753 // Get and sanitize the user identifier
3277 2754 $user_id = $this->mxchat_get_user_identifier();
3278 2755 $user_id = sanitize_key($user_id);
@@ -3328,10 +2805,10 @@
3328 2805 }
3329 2806
3330 2807 public function mxchat_enqueue_scripts_styles() {
3331 2808 // Define version numbers for the styles and scripts
3332 - $chat_style_version = '2.0.4'; // Replace with your actual version
3333 - $chat_script_version = '2.0.4'; // Replace with your actual version
2809 + $chat_style_version = '1.5.3'; // Replace with your actual version
2810 + $chat_script_version = '1.5.3'; // Replace with your actual version
3334 2811
3335 2812 // Enqueue the script
3336 2813 wp_enqueue_script(
3337 2814 'mxchat-chat-js',
@@ -3350,9 +2827,8 @@
3350 2827 );
3351 2828
3352 2829 // Fetch options from the database
3353 2830 $this->options = get_option('mxchat_options');
3354 - $prompts_options = get_option('mxchat_prompts_options', array());
3355 2831
3356 2832 // Prepare settings for JavaScript
3357 2833 $style_settings = array(
3358 2834 'ajax_url' => admin_url('admin-ajax.php'),
@@ -3379,11 +2855,8 @@
3379 2855 'chat_toolbar_toggle' => $this->options['chat_toolbar_toggle'] ?? 'off',
3380 2856 'mode_indicator_bg_color' => $this->options['mode_indicator_bg_color'] ?? '#767676',
3381 2857 'mode_indicator_font_color' => $this->options['mode_indicator_font_color'] ?? '#ffffff',
3382 2858 'toolbar_icon_color' => $this->options['toolbar_icon_color'] ?? '#212121',
3383 -
3384 - 'use_pinecone' => $prompts_options['mxchat_use_pinecone'] ?? '0',
3385 - 'pinecone_enabled' => isset($prompts_options['mxchat_use_pinecone']) && $prompts_options['mxchat_use_pinecone'] === '1'
3386 2859 );
3387 2860
3388 2861 // Pass the settings to the script
3389 2862 wp_localize_script('mxchat-chat-js', 'mxchatChat', $style_settings);