PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 1.2
MxChat – AI Chatbot & Content Generation for WordPress v1.2
3.2.21 3.2.20 3.2.19 3.2.18 3.2.17 3.2.16 3.2.15 3.2.14 3.2.12 3.2.13 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 3.2.6 3.2.5 3.2.4 3.2.3 3.2.2 3.2.1 2.0.3 2.0.4 2.0.5 2.0.6 All 152 releases
← All changes | includes/class-mxchat-integrator.php +338 -2953 2.0.51.2 View file →
@@ -4,21 +4,21 @@
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;
11 - private $productCardHtml;
12 - private $word_handler;
13 9
14 10 public function __construct() {
15 11 $this->options = get_option('mxchat_options');
16 - $this->prompts_options = get_option('mxchat_prompts_options', array());
12 + $this->chat_count = get_option('mxchat_chat_count', 0);
17 13
18 - $this->chat_count = get_option('mxchat_chat_count', 0);
19 - $this->word_handler = new MXChat_Word_Handler($this->options);
14 + // Add WooCommerce hooks
15 + add_action('wp_insert_post', array($this, 'mxchat_handle_product_change'), 10, 3);
20 16
17 + // Ensure embeddings are removed when a product is moved to trash or permanently deleted
18 + add_action('wp_trash_post', array($this, 'mxchat_handle_product_delete'));
19 + add_action('before_delete_post', array($this, 'mxchat_handle_product_delete'));
20 +
21 21 add_action('wp_enqueue_scripts', array($this, 'mxchat_enqueue_scripts_styles'));
22 22 add_action('wp_ajax_mxchat_handle_chat_request', array($this, 'mxchat_handle_chat_request'));
23 23 add_action('wp_ajax_nopriv_mxchat_handle_chat_request', array($this, 'mxchat_handle_chat_request'));
24 24
@@ -30,43 +30,79 @@
30 30
31 31 add_action('wp_ajax_mxchat_fetch_conversation_history', [$this, 'mxchat_fetch_conversation_history']);
32 32 add_action('wp_ajax_nopriv_mxchat_fetch_conversation_history', [$this, 'mxchat_fetch_conversation_history']);
33 33
34 - add_action('wp_ajax_mxchat_add_to_cart', [$this, 'mxchat_add_to_cart']);
35 - add_action('wp_ajax_nopriv_mxchat_add_to_cart', [$this, 'mxchat_add_to_cart']);
36 34
35 +
37 36 if (!wp_next_scheduled('mxchat_reset_rate_limits')) {
38 37 wp_schedule_event(time(), 'daily', 'mxchat_reset_rate_limits');
39 38 }
40 39
41 - // Add REST API routes registration
42 - add_action('rest_api_init', array($this, 'register_routes'));
40 + add_action('mxchat_reset_rate_limits', array($this, 'mxchat_reset_rate_limits'));
41 +}
43 42
44 - add_action('wp_ajax_mxchat_fetch_new_messages', array($this, 'mxchat_fetch_new_messages'));
45 - add_action('wp_ajax_nopriv_mxchat_fetch_new_messages', array($this, 'mxchat_fetch_new_messages'));
43 +public function mxchat_handle_product_change($post_id, $post, $update) {
44 + // Ensure this is a product post type
45 + if ($post->post_type !== 'product') {
46 + return;
47 + }
46 48
47 - add_action('mxchat_reset_rate_limits', array($this, 'mxchat_reset_rate_limits'));
49 + // Only generate embeddings if the product is published
50 + if ($post->post_status === 'publish') {
51 + // Delay the embedding slightly to ensure all product data is available
52 + add_action('shutdown', function() use ($post_id) {
53 + $product = wc_get_product($post_id);
54 + if ($product && $product->get_price() !== '') {
55 + $this->mxchat_store_product_embedding($product);
56 + } else {
57 + // Optionally, log or handle the case where product data is incomplete
58 + // error_log("Product {$post_id} does not have complete data. Embedding not generated.");
59 + }
60 + });
61 + }
62 +}
48 63
49 - add_action('wp_ajax_mxchat_upload_pdf', [$this, 'handle_pdf_upload']);
50 - add_action('wp_ajax_nopriv_mxchat_upload_pdf', [$this, 'handle_pdf_upload']);
51 - add_action('wp_ajax_mxchat_remove_pdf', [$this, 'handle_pdf_remove']);
52 - add_action('wp_ajax_nopriv_mxchat_remove_pdf', [$this, 'handle_pdf_remove']);
64 +public function mxchat_handle_product_delete($post_id) {
65 + if (get_post_type($post_id) !== 'product') {
66 + return;
67 + }
53 68
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'));
69 + global $wpdb;
70 + $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
61 71
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']);
72 + // Delete the embedding associated with this product
73 + $wpdb->delete($table_name, array('source_url' => get_permalink($post_id)), array('%s'));
66 74 }
67 75
76 +private function mxchat_store_product_embedding($product) {
77 + if (isset($this->options['enable_woocommerce_integration']) && $this->options['enable_woocommerce_integration'] === '1') {
68 78
79 + $source_url = get_permalink($product->get_id());
80 + $regular_price = $product->get_regular_price();
81 + $sale_price = $product->get_sale_price();
82 + $price = $sale_price ?: $regular_price;
83 +
84 + $description = $product->get_description() . "\n\n" .
85 + "Short Description: " . $product->get_short_description() . "\n" .
86 + "Price: " . $regular_price . "\n" .
87 + "Sale Price: " . ($sale_price ?: 'N/A') . "\n" .
88 + "SKU: " . $product->get_sku();
89 +
90 + global $wpdb;
91 + $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
92 +
93 + // Delete any existing embedding for this product
94 + $wpdb->delete($table_name, array('source_url' => $source_url), array('%s'));
95 +
96 + // Submit the new content and embedding to the database
97 + MxChat_Utils::submit_content_to_db($description, $source_url, $this->options['api_key']);
98 + }
99 +}
100 +
101 +
102 +
103 +
104 +
69 105 private function mxchat_increment_chat_count() {
70 106 $chat_count = get_option('mxchat_chat_count', 0);
71 107 $chat_count++;
72 108 update_option('mxchat_chat_count', $chat_count);
@@ -73,29 +109,21 @@
73 109 }
74 110
75 111 function mxchat_fetch_conversation_history() {
76 112 if (empty($_POST['session_id'])) {
77 - wp_send_json_error(['message' => esc_html__('Session ID missing.', 'mxchat')]);
113 + wp_send_json_error(['message' => 'Session ID missing.']);
78 114 wp_die();
79 115 }
80 116
81 117 $session_id = sanitize_text_field($_POST['session_id']);
82 118 $history = get_option("mxchat_history_{$session_id}", []); // Retrieve stored history
83 - $chat_mode = get_option("mxchat_mode_{$session_id}", 'ai'); // Get current chat mode
84 119
85 120 if (empty($history)) {
86 - // Even if history is empty, return the chat mode
87 - wp_send_json_success([
88 - 'conversation' => [],
89 - 'chat_mode' => $chat_mode
90 - ]);
121 + wp_send_json_error(['message' => 'No history found.']);
91 122 wp_die();
92 123 }
93 124
94 - wp_send_json_success([
95 - 'conversation' => $history,
96 - 'chat_mode' => $chat_mode
97 - ]);
125 + wp_send_json_success(['conversation' => $history]);
98 126 wp_die();
99 127 }
100 128 private function mxchat_fetch_conversation_history_for_ajax($session_id) {
101 129 $history = get_option("mxchat_history_{$session_id}", []); // Retrieve stored history based on session ID
@@ -112,2325 +140,232 @@
112 140 return $formatted_history;
113 141 }
114 142
115 143
116 -private function mxchat_fetch_conversation_history_for_ai($session_id) {
117 - $history = get_option("mxchat_history_{$session_id}", []);
118 - $formatted_history = [];
119 -
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'])) {
151 - continue;
152 - }
153 -
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 - $formatted_history[] = [
170 - 'role' => $entry['role'],
171 - 'content' => $clean_content
172 - ];
173 -
174 - $current_token_count += $token_estimate;
175 - }
176 -
177 - // Reverse back to maintain chronological order
178 - $formatted_history = array_reverse($formatted_history);
179 -
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 -
187 - return $formatted_history;
188 -}
189 -
190 -public function register_routes() {
191 - //error_log(esc_html__('Registering MxChat REST routes', 'mxchat'));
192 -
193 - register_rest_route('mxchat/v1', '/stream', [
194 - 'methods' => 'GET',
195 - 'callback' => [$this, 'mxchat_stream_events'],
196 - 'permission_callback' => [$this, 'verify_chat_session'],
197 - ]);
198 -
199 - register_rest_route('mxchat/v1', '/agent-response', [
200 - 'methods' => 'POST',
201 - 'callback' => [$this, 'mxchat_handle_agent_response'],
202 - 'permission_callback' => [$this, 'verify_slack_request'],
203 - ]);
204 -
205 - register_rest_route('mxchat/v1', '/slack-interaction', [
206 - 'methods' => 'POST',
207 - 'callback' => [$this, 'handle_slack_interaction'],
208 - 'permission_callback' => [$this, 'verify_slack_request'],
209 - ]);
210 -
211 - //error_log(esc_html__('MxChat REST routes registered', 'mxchat'));
212 -}
213 -
214 -/**
215 - * Verify valid chat session
216 - */
217 -public function verify_chat_session($request) {
218 - $session_id = $request->get_param('session_id');
219 - if (empty($session_id)) {
220 - //error_log(esc_html__('Empty session ID in chat request', 'mxchat'));
221 - return false;
222 - }
223 -
224 - $chat_mode = get_option("mxchat_mode_{$session_id}", 'ai');
225 - return $chat_mode === 'agent';
226 -}
227 -
228 -/**
229 - * Verify request is coming from Slack.
230 - *
231 - * @param WP_REST_Request $request
232 - * @return bool True if valid, false otherwise.
233 - */
234 -public function verify_slack_request($request) {
235 - // Get the Slack signing secret from your plugin options
236 - $valid_key = $this->options['live_agent_secret_key'] ?? '';
237 -
238 - if (empty($valid_key)) {
239 - //error_log(esc_html__('Slack signing secret not configured', 'mxchat'));
240 - return false;
241 - }
242 -
243 - $timestamp = $request->get_header('X-Slack-Request-Timestamp');
244 - $slack_signature = $request->get_header('X-Slack-Signature');
245 -
246 - // Verify timestamp to prevent replay attacks
247 - if (abs(time() - intval($timestamp)) > 300) {
248 - //error_log(esc_html__('Slack request timestamp too old', 'mxchat'));
249 - return false;
250 - }
251 -
252 - // Get raw request body
253 - $request_body = file_get_contents('php://input');
254 -
255 - // Create the signature base string
256 - $sig_basestring = "v0:{$timestamp}:{$request_body}";
257 -
258 - // Calculate expected signature
259 - $my_signature = 'v0=' . hash_hmac('sha256', $sig_basestring, $valid_key);
260 -
261 - // Compare signatures
262 - return hash_equals($my_signature, $slack_signature);
263 -}
264 -public function mxchat_stream_events(WP_REST_Request $request) {
265 - header('Content-Type: text/event-stream');
266 - header('Cache-Control: no-cache');
267 - header('Connection: keep-alive');
268 -
269 - $session_id = sanitize_text_field($request->get_param('session_id'));
270 - $last_seen_id = sanitize_text_field($request->get_param('last_seen_id')) ?: '';
271 -
272 - if (empty($session_id)) {
273 - echo esc_html__("event: error\ndata: ", 'mxchat') . esc_html__('Missing session_id', 'mxchat') . "\n\n";
274 - flush();
275 - exit;
276 - }
277 -
278 - $history = get_option("mxchat_history_{$session_id}", []);
279 -
280 - // Filter only new messages
281 - $new_messages = array_filter($history, function ($message) use ($last_seen_id) {
282 - return !empty($message['id']) && $message['id'] > $last_seen_id;
283 - });
284 -
285 - // Send new messages if available
286 - if (!empty($new_messages)) {
287 - echo esc_html__("event: newMessages\ndata: ", 'mxchat') . json_encode(array_values($new_messages)) . "\n\n";
288 - } else {
289 - // Keep the connection alive
290 - echo esc_html__("event: keepAlive\ndata: ", 'mxchat') . "{}\n\n";
291 - }
292 - flush();
293 - exit;
294 -}
295 -
296 -
297 -
298 -
299 144 private function mxchat_save_chat_message($session_id, $role, $message) {
300 145 global $wpdb;
301 -
302 146 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
303 - //error_log("[DEBUG] mxchat_save_chat_message -> START for session_id: {$session_id}, role: {$role}");
304 147
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
319 - $message_id = uniqid();
320 - //error_log("[DEBUG] mxchat_save_chat_message -> Generated message_id: {$message_id}");
321 -
322 - // 3) Determine user_id
323 148 $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
149 + $user_identifier = MxChat_User::mxchat_get_user_identifier();
331 150 $user_email = MxChat_User::mxchat_get_user_email();
332 - $displayed_name = $agent_name ? $agent_name : ($user_email ?: $user_identifier);
333 151
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}");
152 + $history = get_option("mxchat_history_{$session_id}", []);
153 + $history[] = ['role' => $role, 'content' => $message];
154 + update_option("mxchat_history_{$session_id}", $history);
338 155
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, []);
354 - $history[] = [
355 - 'id' => $message_id,
156 + $wpdb->insert($table_name, [
157 + 'user_id' => $user_id,
158 + 'user_identifier' => $user_identifier,
159 + 'user_email' => $user_email,
160 + 'session_id' => $session_id,
356 161 'role' => $role,
357 - 'content' => $message,
358 - 'timestamp' => round(microtime(true) * 1000),
359 - 'agent_name' => $displayed_name,
360 - ];
361 - update_option($history_key, $history);
362 - //error_log("[DEBUG] mxchat_save_chat_message -> Updated session history in option: {$history_key}");
363 -
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));
376 -
377 - //error_log("[DEBUG] mxchat_save_chat_message -> END for session_id: {$session_id}");
378 - return $message_id;
162 + 'message' => $message,
163 + 'timestamp' => current_time('mysql', 1)
164 + ]);
379 165 }
380 166
381 -public function mxchat_handle_save_email_and_response() {
382 - //error_log('[DEBUG] ---------- mxchat_handle_save_email_and_response START ----------');
383 167
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
168 +public function mxchat_handle_chat_request() {
408 169 global $wpdb;
409 - $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
410 170
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);
171 + // Get and sanitize the user identifier
172 + $user_id = $this->mxchat_get_user_identifier();
173 + $user_id = sanitize_key($user_id);
414 174
415 - //error_log("[DEBUG] handle_save_email_and_response -> session_count for {$session_id}: {$session_count} (SQL: {$sql})");
175 + // Setup rate limiting
176 + $rate_limit_transient_key = 'mxchat_chat_limit_' . $user_id;
177 + $chat_count = get_transient($rate_limit_transient_key) ?: 0;
416 178
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 - }
179 + // Retrieve the session ID from the client's POST data
180 + $session_id = isset($_POST['session_id']) ? sanitize_text_field($_POST['session_id']) : '';
429 181
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 182 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')]);
183 + // Handle the case where the session ID is missing
184 + wp_send_json_error('Session ID is missing.');
185 + wp_die();
449 186 }
450 187
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 - }
188 + // No need to set transients or server-side cookies for the session ID
457 189
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')]);
190 + // Check rate limit
191 + $rate_limit_option = $this->options['rate_limit'] ?? 'unlimited';
192 + if ($rate_limit_option !== 'unlimited' && $chat_count >= intval($rate_limit_option)) {
193 + wp_send_json_error(['message' => $this->options['rate_limit_message'] ?? 'Rate limit exceeded. Please try again later.']);
194 + wp_die();
469 195 }
470 -}
196 + set_transient($rate_limit_transient_key, $chat_count + 1, DAY_IN_SECONDS);
471 197
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';
198 + // Validate and sanitize the incoming message
199 + if (empty($_POST['message'])) {
200 + wp_send_json_error('No message received');
201 + wp_die();
480 202 }
481 203
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 - }
204 + $message = sanitize_text_field($_POST['message']);
205 + $this->mxchat_save_chat_message($session_id, 'user', $message);
487 206
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));
207 + // Track email capture and WooCommerce flows with individual transients
208 + $email_capture_prompt = get_transient('mxchat_email_capture_' . $user_id);
209 + $interaction_count = get_transient('mxchat_email_interaction_count_' . $user_id) ?: 0;
210 + $woocommerce_prompt = get_transient('mxchat_woocommerce_prompt_' . $user_id);
490 211
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);
212 + // Handle email capture flow
213 + if ($email_capture_prompt) {
214 + if (preg_match('/[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}/i', $message, $matches)) {
215 + $email = $matches[0];
216 + $this->add_email_to_loops($email);
217 + $response = $this->options['email_capture_response'] ?? 'Thank you for providing your email! You\'ve been added to our list.';
497 218
498 - if ($role_limit === 'unlimited') {
499 - //error_log(esc_html__("Returning unlimited for role: ", 'mxchat') . $role);
500 - return 'unlimited';
501 - }
219 + delete_transient('mxchat_email_capture_' . $user_id);
220 + delete_transient('mxchat_email_interaction_count_' . $user_id);
221 + delete_transient('mxchat_woocommerce_prompt_' . $user_id);
502 222
503 - $max_limit = max($max_limit, (int)$role_limit);
504 - //error_log(esc_html__("Current max limit: ", 'mxchat') . $max_limit);
223 + $this->mxchat_save_chat_message($session_id, 'bot', $response);
224 + wp_send_json(['message' => $response]);
225 + wp_die();
505 226 } else {
506 - //error_log(esc_html__("No limit found for role: ", 'mxchat') . $role);
227 + if ($interaction_count >= 3) {
228 + delete_transient('mxchat_email_capture_' . $user_id);
229 + delete_transient('mxchat_email_interaction_count_' . $user_id);
230 + } else {
231 + set_transient('mxchat_email_interaction_count_' . $user_id, ++$interaction_count, 5 * MINUTE_IN_SECONDS);
232 + }
507 233 }
508 234 }
509 235
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 -}
236 + // Handle WooCommerce add-to-cart flow
237 + if (class_exists('WooCommerce') && stripos($message, 'add to cart') !== false) {
238 + $last_product_id = get_transient('mxchat_last_discussed_product_' . $user_id);
239 + if ($last_product_id) {
240 + $added = WC()->cart->add_to_cart($last_product_id);
241 + $product = wc_get_product($last_product_id);
514 242
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 -public function mxchat_handle_chat_request() {
556 - global $wpdb;
557 -
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 - ]);
243 + if ($added) {
244 + $response = "The product '{$product->get_name()}' has been added to your cart. To proceed to checkout, please type 'checkout'.";
245 + set_transient('mxchat_checkout_prompt_' . $user_id, true, 5 * MINUTE_IN_SECONDS);
246 + $this->mxchat_save_chat_message($session_id, 'bot', $response);
247 + wp_send_json(['message' => $response]);
248 + wp_die();
249 + } else {
250 + $response = "Sorry, I couldn't add the product to your cart. Please try again.";
251 + $this->mxchat_save_chat_message($session_id, 'bot', $response);
252 + wp_send_json(['message' => $response]);
253 + wp_die();
254 + }
255 + } else {
256 + $response = "I couldn't find the product to add. Please mention the product name again.";
257 + $this->mxchat_save_chat_message($session_id, 'bot', $response);
258 + wp_send_json(['message' => $response]);
581 259 wp_die();
582 260 }
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 261 }
594 262
263 + // Handle checkout response
264 + if (class_exists('WooCommerce') && stripos($message, 'checkout') !== false) {
265 + $checkout_prompt = get_transient('mxchat_checkout_prompt_' . $user_id);
266 + if ($checkout_prompt && WC()->cart->get_cart_contents_count() > 0) {
267 + $checkout_url = wc_get_checkout_url();
268 + $response = "Great! Redirecting you to the checkout page...";
269 + delete_transient('mxchat_checkout_prompt_' . $user_id);
595 270
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 - // Get and sanitize the user identifier
610 - $user_id = sanitize_key($user_id);
611 -
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'));
615 -
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 - ]);
271 + $this->mxchat_save_chat_message($session_id, 'bot', $response);
272 + wp_send_json(['message' => $response, 'redirect_url' => $checkout_url]);
655 273 wp_die();
274 + } else {
275 + $response = "It seems like there is no active checkout prompt or no items in your cart. Please add a product to the cart first.";
276 + $this->mxchat_save_chat_message($session_id, 'bot', $response);
277 + wp_send_json(['message' => $response]);
278 + wp_die();
656 279 }
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 280 }
662 281
663 - // Rest of your existing code...
664 - $session_id = isset($_POST['session_id']) ? sanitize_text_field($_POST['session_id']) : '';
665 - //error_log("Session ID: $session_id");
666 -
667 - if (empty($session_id)) {
668 - //error_log("Error: Session ID is missing.");
669 - wp_send_json_error(esc_html__('Session ID is missing.', 'mxchat'));
282 + // Handle order-related queries
283 + if (class_exists('WooCommerce') && MxChat_WooCommerce::mxchat_is_order_related_query($message)) {
284 + $response = MxChat_WooCommerce::mxchat_fetch_user_orders_details();
285 + $this->mxchat_save_chat_message($session_id, 'bot', $response);
286 + wp_send_json(['message' => $response]);
670 287 wp_die();
671 288 }
672 289
673 - // Validate and sanitize the incoming message
674 - if (empty($_POST['message'])) {
675 - //error_log("Error: No message received.");
676 - wp_send_json_error(esc_html__('No message received.', 'mxchat'));
677 - wp_die();
678 - }
679 -
680 -
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 -];
688 -
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 - // Save the user's message
708 - $this->mxchat_save_chat_message($session_id, 'user', $message);
709 -
710 - // Check if the message is an email address
711 - if (is_email($message)) {
712 - // Add the email to Loops
713 - $this->add_email_to_loops($message);
714 -
715 - // Send success response
716 - $response_message = $this->options['email_capture_response'] ??
717 - esc_html__('Thank you! Your coupon is on the way!', 'mxchat');
718 -
719 - wp_send_json([
720 - 'success' => true,
721 - 'status' => 'email_captured',
722 - 'message' => $response_message
723 - ]);
724 - wp_die();
725 - }
726 -
727 - $intent_info = '';
728 -
729 - // Check chat mode
730 - $chat_mode = get_option("mxchat_mode_{$session_id}", 'ai');
731 - //error_log("Chat Mode: $chat_mode");
732 -
733 - // Handle agent mode
734 - if ($chat_mode === 'agent') {
735 - // First, check for switch intent before doing anything else
736 - $intent_matched = $this->mxchat_check_intent_and_invoke_callback($message, $user_id, $session_id);
737 -
738 - // If we matched an intent and it's the switch intent, handle it
739 - if ($intent_matched && !empty($this->fallbackResponse['text'])) {
740 - //error_log("Switch to chatbot intent detected");
741 -
742 - // Update chat mode first
743 - update_option("mxchat_mode_{$session_id}", 'ai');
744 -
745 - // Clear any existing PDF context to start fresh
746 - $this->clear_pdf_transients($session_id);
747 -
748 - // Prepare clean switch response
749 - $response_data = [
750 - 'text' => $this->fallbackResponse['text'],
751 - 'html' => '',
752 - 'session_id' => $session_id,
753 - 'chat_mode' => 'ai'
754 - ];
755 -
756 - // Save the mode switch message
757 - $this->mxchat_save_chat_message($session_id, 'system', esc_html__('Switched to AI chat mode', 'mxchat'));
758 - $this->mxchat_save_chat_message($session_id, 'bot', $this->fallbackResponse['text']);
759 -
760 - // Send response and exit
761 - wp_send_json($response_data);
762 - wp_die();
763 - } elseif (!$intent_matched) {
764 - // No intent matched, handle live agent message
765 - try {
766 - $this->mxchat_send_user_message_to_agent($message, $user_id, $session_id);
767 - //error_log("Message sent to agent.");
768 -
769 - wp_send_json_success([
770 - 'status' => 'waiting_for_agent',
771 - 'message' => esc_html__('Message sent to live agent.', 'mxchat')
772 - ]);
773 - } catch (\Exception $e) {
774 - //error_log("Error sending message to agent: " . $e->getMessage());
775 - wp_send_json_error(esc_html__('Failed to send message to agent', 'mxchat'));
290 + // Check for trigger keywords to initiate email capture
291 + $trigger_keywords = explode(',', $this->options['trigger_keywords'] ?? '');
292 + if (!empty($trigger_keywords) && $trigger_keywords[0] !== '') {
293 + foreach ($trigger_keywords as $keyword) {
294 + if (stripos($message, trim($keyword)) !== false) {
295 + $response = $this->options['triggered_phrase_response'] ?? "Would you like to join our mailing list? Please provide your email below.";
296 + set_transient('mxchat_email_capture_' . $user_id, true, 5 * MINUTE_IN_SECONDS);
297 + $this->mxchat_save_chat_message($session_id, 'bot', $response);
298 + wp_send_json(['message' => $response]);
299 + wp_die();
776 300 }
777 - wp_die();
778 301 }
779 302 }
780 303
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];
784 -
785 - // Check if this is likely a PDF-related request
786 - $pdf_keywords = ['pdf', 'document', 'read', 'analyze'];
787 - $is_pdf_request = false;
788 -
789 - foreach ($pdf_keywords as $keyword) {
790 - if (stripos($message, $keyword) !== false) {
791 - $is_pdf_request = true;
792 - break;
793 - }
794 - }
795 -
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));
802 -
803 - // Clear previous PDF transients
804 - $this->clear_pdf_transients($session_id);
805 -
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);
809 -
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 - }
858 - }
859 - }
860 -
861 - // Step 2: Detect intent and handle intent-based responses
862 - $intent_matched = $this->mxchat_check_intent_and_invoke_callback($message, $user_id, $session_id);
863 - //error_log("Intent Matched: " . ($intent_matched ? "Yes" : "No"));
864 -
865 - // Step 3: If intent is matched and handled, respond immediately
866 - if ($intent_matched && (!empty($this->fallbackResponse['text']) || !empty($this->fallbackResponse['html']))) {
867 - //error_log("Intent response triggered.");
868 - $response_data = [
869 - 'text' => $this->fallbackResponse['text'],
870 - 'html' => $this->fallbackResponse['html'],
871 - 'session_id' => $session_id
872 - ];
873 - $this->mxchat_save_chat_message($session_id, 'bot', $this->fallbackResponse['text'] . $this->fallbackResponse['html']);
874 - wp_send_json($response_data);
875 - wp_die();
304 + // Store product discussion in transient
305 + $last_discussed_product_id = MxChat_WooCommerce::mxchat_extract_product_id_from_message($message);
306 + if ($last_discussed_product_id) {
307 + set_transient('mxchat_last_discussed_product_' . $user_id, $last_discussed_product_id, 3600);
876 308 }
877 309
878 - // If no intent matched or product not found, proceed with AI response
879 - //error_log("No matching intent or fallback. Generating AI response.");
880 -
881 - // Step 4: Generate AI response
882 - $conversation_history = $this->mxchat_fetch_conversation_history_for_ai($session_id);
883 - $this->mxchat_increment_chat_count();
884 -
885 - // Generate embedding for the user's query
310 + // Standard chat processing
886 311 $user_message_embedding = $this->mxchat_generate_embedding($message, $this->options['api_key']);
887 312 if (!is_array($user_message_embedding)) {
888 - //error_log("Failed to generate message embedding for session $session_id");
889 - wp_send_json_error(esc_html__('Error processing your message.', 'mxchat'));
313 + wp_send_json_error('Error processing your message.');
890 314 wp_die();
891 315 }
892 316
893 - // Build context with both knowledge base and PDF content if available
894 - $context_content = "User asked: '{$message}'\n\n";
895 -
896 - // Get relevant content from knowledge base
897 317 $relevant_content = $this->mxchat_find_relevant_content($user_message_embedding);
898 - if (!empty($relevant_content)) {
899 - $context_content .= "Relevant content from knowledge database:\n" . $relevant_content . "\n\n";
900 - }
901 -
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";
915 - }
916 - }
917 -
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 - // Generate the response using the full context
318 + $conversation_history = $this->mxchat_fetch_conversation_history_for_ajax($session_id);
319 + $this->mxchat_increment_chat_count();
933 320 $response = $this->mxchat_generate_response(
934 - $context_content,
935 - $this->options['api_key'],
936 - $this->options['xai_api_key'],
937 - $this->options['claude_api_key'],
938 - $this->options['deepseek_api_key'],
321 + $relevant_content,
322 + $this->options['api_key'], // OpenAI API Key
323 + $this->options['xai_api_key'], // X.AI API Key
324 + $this->options['claude_api_key'], // Claude API Key
939 325 $conversation_history
940 326 );
941 327
942 328 $this->mxchat_save_chat_message($session_id, 'bot', $response);
943 -
944 - // Step 5: Save additional content if available
945 - if (!empty($this->productCardHtml)) {
946 - $this->mxchat_save_chat_message($session_id, 'bot', $this->productCardHtml);
947 - }
948 -
949 - if (!empty($this->fallbackResponse['html'])) {
950 - $this->mxchat_save_chat_message($session_id, 'bot', $this->fallbackResponse['html']);
951 - }
952 -
953 - // Step 6: Return the response
954 - $response_data = [
955 - 'text' => $response,
956 - 'html' => !empty($this->productCardHtml) ? $this->productCardHtml : ($this->fallbackResponse['html'] ?? ''),
957 - 'session_id' => $session_id
958 - ];
959 -
960 - wp_send_json($response_data);
329 + wp_send_json(['message' => $response, 'session_id' => $session_id]);
961 330 wp_die();
962 331 }
963 332
964 -// New function to check intents and invoke the callback function
965 -private function mxchat_check_intent_and_invoke_callback($message, $user_id, $session_id) {
966 - global $wpdb;
967 - $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 -
973 - // Generate the user embedding
974 - //error_log('🔄 MXCHAT DEBUG: Generating user embedding');
975 - $user_embedding = $this->mxchat_generate_embedding($message, $this->options['api_key']);
976 - if (!is_array($user_embedding)) {
977 - //error_log('❌ MXCHAT DEBUG: Failed to generate user embedding');
978 - return false;
979 - }
980 - //error_log('✅ MXCHAT DEBUG: User embedding generated successfully');
981 -
982 - // Fetch intents from the database
983 - $table_name = $wpdb->prefix . 'mxchat_intents';
984 - if ($chat_mode === 'agent') {
985 - //error_log('🔍 MXCHAT DEBUG: Agent mode - fetching only chatbot switch intent');
986 - $query = $wpdb->prepare(
987 - "SELECT * FROM $table_name WHERE callback_function = %s",
988 - 'mxchat_handle_switch_to_chatbot_intent'
989 - );
990 - $intents = $wpdb->get_results($query);
991 - } else {
992 - //error_log('🔍 MXCHAT DEBUG: AI mode - fetching all intents');
993 - $intents = $wpdb->get_results("SELECT * FROM $table_name");
994 - }
995 -
996 - //error_log('🔍 MXCHAT DEBUG: Found ' . count($intents) . ' intents to check');
997 -
998 - if (empty($intents)) {
999 - //error_log('❌ MXCHAT DEBUG: No intents found in database');
1000 - return false;
1001 - }
1002 -
1003 - $highest_similarity = -INF;
1004 - $matched_intent = null;
1005 -
1006 - //error_log('📊 MXCHAT DEBUG: Intent Similarity Scores ==================');
1007 - foreach ($intents as $intent) {
1008 - //error_log("🔄 MXCHAT DEBUG: Checking intent: '{$intent->intent_label}' (callback: {$intent->callback_function})");
1009 -
1010 - $intent_embedding_serialized = $intent->embedding_vector;
1011 - $intent_embedding = $intent_embedding_serialized
1012 - ? unserialize($intent_embedding_serialized, ['allowed_classes' => false])
1013 - : null;
1014 -
1015 - if (!is_array($intent_embedding)) {
1016 - //error_log("❌ MXCHAT DEBUG: Invalid embedding for intent: {$intent->intent_label}");
1017 - continue;
1018 - }
1019 -
1020 - $similarity = $this->mxchat_calculate_cosine_similarity($user_embedding, $intent_embedding);
1021 - $intent_threshold = isset($intent->similarity_threshold) ? $intent->similarity_threshold : 0.85;
1022 -
1023 -
1024 - if ($similarity >= $intent_threshold && $similarity > $highest_similarity) {
1025 - $highest_similarity = $similarity;
1026 - $matched_intent = $intent;
1027 - //error_log("✅ MXCHAT DEBUG: New best match: '{$intent->intent_label}' with similarity {$similarity}");
1028 - }
1029 - }
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');
1069 - }
1070 -
1071 - //error_log('🔍 MXCHAT DEBUG: Intent Check Completed ==================');
1072 - return false;
1073 -}
1074 333
1075 -
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);
1083 -
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);
1090 -}
1091 -
1092 -
1093 -
1094 -//verified good
1095 -public function mxchat_handle_email_capture($message, $user_id, $session_id) {
1096 - // Log the message safely
1097 - //error_log("Triggered email capture intent for message: " . sanitize_text_field($message));
1098 -
1099 - // 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'));
1101 -
1102 - set_transient('mxchat_email_capture_' . $user_id, true, 5 * MINUTE_IN_SECONDS);
1103 - $this->mxchat_save_chat_message($session_id, 'bot', $response);
1104 -
1105 - // Respond to the user
1106 - wp_send_json(['message' => $response]);
1107 - wp_die();
1108 -}
1109 -
1110 -//very good
1111 -public function mxchat_generate_image($message, $user_id, $session_id) {
1112 - // Prepare a prompt for DALL-E
1113 - $prompt = esc_html__('Create an image of ', 'mxchat') . sanitize_text_field($message);
1114 -
1115 - // Use the existing OpenAI API key
1116 - $openai_api_key = sanitize_text_field($this->options['api_key']);
1117 -
1118 - // Call DALL-E to generate an image
1119 - $image_response = $this->mxchat_generate_dalle_image($prompt, $openai_api_key);
1120 -
1121 - // Check if the response contains an image URL
1122 - if (isset($image_response['imageUrl'])) {
1123 - $image_url = esc_url_raw($image_response['imageUrl']);
1124 -
1125 - // 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" />';
1127 -
1128 - $response_text = esc_html__('Here is the image I generated:', 'mxchat');
1129 - } else {
1130 - $response_text = esc_html__("I'm sorry, but I couldn't generate an image based on your request.", 'mxchat');
1131 - $response_html = '';
1132 - //error_log("DALL-E image generation error: " . esc_html($image_response['error'] ?? 'Unknown error.'));
1133 - }
1134 -
1135 - // Save both text and HTML responses
1136 - $this->mxchat_save_chat_message($session_id, 'bot', $response_text . "\n" . $response_html);
1137 -
1138 - // Prepare the response data
1139 - $response_data = [
1140 - 'message' => $response_text,
1141 - 'html' => $response_html,
1142 - 'image_url' => $image_url ?? '',
1143 - ];
1144 -
1145 - // Send the JSON response
1146 - header('Content-Type: application/json; charset=' . get_option('blog_charset'));
1147 - echo json_encode($response_data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
1148 - wp_die();
1149 -}
1150 -private function mxchat_generate_dalle_image($prompt, $api_key, $model = 'dall-e-3', $timeout = 60) {
1151 - $api_url = 'https://api.openai.com/v1/images/generations';
1152 - $body = json_encode([
1153 - 'prompt' => sanitize_text_field($prompt),
1154 - 'n' => 1,
1155 - 'size' => '1024x1024',
1156 - 'model' => sanitize_text_field($model),
1157 - ]);
1158 -
1159 - $args = [
1160 - 'body' => $body,
1161 - 'headers' => [
1162 - 'Content-Type' => 'application/json',
1163 - 'Authorization' => 'Bearer ' . sanitize_text_field($api_key),
1164 - ],
1165 - 'method' => 'POST',
1166 - 'timeout' => absint($timeout),
1167 - ];
1168 -
1169 - $response = wp_remote_post($api_url, $args);
1170 -
1171 - if (is_wp_error($response)) {
1172 - //error_log("DALL-E request failed: " . $response->get_error_message());
1173 - return ['error' => esc_html__('Error generating image: ', 'mxchat') . $response->get_error_message()];
1174 - }
1175 -
1176 - $response_body = json_decode(wp_remote_retrieve_body($response), true);
1177 -
1178 - if (isset($response_body['data'][0]['url'])) {
1179 - return ['imageUrl' => esc_url_raw($response_body['data'][0]['url'])];
1180 - } else {
1181 - //error_log("DALL-E response error: " . wp_remote_retrieve_body($response));
1182 - return ['error' => esc_html__('Failed to generate image.', 'mxchat')];
1183 - }
1184 -}
1185 -
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 );
1201 -
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 - );
1206 - return;
1207 - }
1208 -
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;
1213 -
1214 - if ( empty( $api_key ) ) {
1215 - $this->fallbackResponse = array(
1216 - 'text' => esc_html__( 'Search functionality is temporarily unavailable. Please try again later.', 'mxchat' ),
1217 - );
1218 - return;
1219 - }
1220 -
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 - );
1231 -
1232 - // Attempt to retrieve cached results first
1233 - $transient_key = 'mxchat_search_' . md5( $refined_search_query );
1234 - $results = get_transient( $transient_key );
1235 -
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 - );
1249 -
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 - }
1256 -
1257 - $results = json_decode( wp_remote_retrieve_body( $response ), true );
1258 -
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 - );
1263 - return;
1264 - }
1265 -
1266 - // Cache results for one hour
1267 - set_transient( $transient_key, $results, HOUR_IN_SECONDS );
1268 - }
1269 -
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 );
1273 -
1274 - // Only return HTML (no large text summary)
1275 - $this->fallbackResponse = array(
1276 - 'html' => $html,
1277 - );
1278 -
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 -}
1290 -
1291 -
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";
1305 -
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'] ) : '';
1311 -
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 - );
1318 - }
1319 -
1320 - return $summary;
1321 -}
1322 -
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 -
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 -}
1387 -
1388 -
1389 -//very good
1390 -public function mxchat_handle_image_search_request($message, $user_id, $session_id) {
1391 -
1392 - // Step 1: Interpret the search query for better results
1393 - $refined_search_query = $this->mxchat_interpret_search_query($message);
1394 -
1395 -
1396 - // If no query was interpreted, return a fallback message
1397 - if (empty($refined_search_query)) {
1398 - $this->fallbackResponse = [
1399 - 'text' => __("I'm sorry, I couldn't interpret your search query. Please specify what you'd like to see images of.", 'mxchat'),
1400 - 'html' => "",
1401 - ];
1402 - return;
1403 - }
1404 -
1405 - // Brave API URL
1406 - $api_url = 'https://api.search.brave.com/res/v1/images/search';
1407 -
1408 - // Retrieve Brave API settings
1409 - $options = get_option('mxchat_options');
1410 - $api_key = isset($options['brave_api_key']) ? sanitize_text_field($options['brave_api_key']) : '';
1411 -
1412 - if (empty($api_key)) {
1413 -/*
1414 - if (defined('WP_DEBUG') && WP_DEBUG) {
1415 - error_log("Brave API key is missing.");
1416 - }
1417 -*/
1418 -
1419 - $this->fallbackResponse = [
1420 - 'text' => __("API key is not configured. Please set it in the Brave Search Settings.", 'mxchat'),
1421 - 'html' => "",
1422 - ];
1423 - return;
1424 - }
1425 -
1426 - $image_count = isset($options['brave_image_count']) ? intval($options['brave_image_count']) : 4;
1427 - $safe_search = isset($options['brave_safe_search']) ? sanitize_text_field($options['brave_safe_search']) : 'strict';
1428 -
1429 - // Append query parameters based on settings
1430 - $api_url = add_query_arg([
1431 - 'q' => rawurlencode($refined_search_query),
1432 - 'count' => $image_count,
1433 - 'safesearch' => $safe_search,
1434 - ], $api_url);
1435 -
1436 -/*
1437 - // Log the final API URL for the search
1438 - if (defined('WP_DEBUG') && WP_DEBUG) {
1439 - error_log("Final API URL for Brave Image Search: " . esc_url_raw($api_url));
1440 - }
1441 -*/
1442 -
1443 -
1444 - // Implement caching
1445 - $transient_key = 'mxchat_image_search_' . md5($refined_search_query);
1446 - $body = get_transient($transient_key);
1447 -
1448 - if (false === $body) {
1449 - $args = [
1450 - 'headers' => [
1451 - 'Accept' => 'application/json',
1452 - 'Accept-Encoding' => 'gzip',
1453 - 'X-Subscription-Token' => $api_key,
1454 - ],
1455 - 'timeout' => 10,
1456 - ];
1457 -
1458 - $response = wp_remote_get($api_url, $args);
1459 -
1460 - if (is_wp_error($response)) {
1461 -/*
1462 - if (defined('WP_DEBUG') && WP_DEBUG) {
1463 - error_log("Brave Image API request failed: " . $response->get_error_message());
1464 - }
1465 -*/
1466 -
1467 - $this->fallbackResponse = [
1468 - 'text' => __("I'm sorry, I couldn't retrieve any images based on your request.", 'mxchat'),
1469 - 'html' => "",
1470 - ];
1471 - return;
1472 - }
1473 -
1474 - $body = json_decode(wp_remote_retrieve_body($response), true);
1475 - set_transient($transient_key, $body, HOUR_IN_SECONDS);
1476 - }
1477 -
1478 - // Process the API response
1479 - if (isset($body['results']) && is_array($body['results']) && count($body['results']) > 0) {
1480 - $html_output = '<div class="mxchat-image-gallery">';
1481 -
1482 - foreach ($body['results'] as $image) {
1483 - $image_url = isset($image['url']) ? esc_url($image['url']) : '';
1484 - $thumbnail_url = isset($image['thumbnail']['src']) ? esc_url($image['thumbnail']['src']) : '';
1485 - $title = isset($image['title']) ? esc_html($image['title']) : esc_html__('Image', 'mxchat');
1486 -
1487 - if ($image_url && $thumbnail_url) {
1488 - $html_output .= '<div class="mxchat-image-item">';
1489 - $html_output .= '<strong class="mxchat-image-title">' . $title . '</strong>';
1490 - $html_output .= '<a href="' . $image_url . '" target="_blank" rel="noopener noreferrer" class="mxchat-image-link">';
1491 - $html_output .= '<img src="' . $thumbnail_url . '" alt="' . $title . '" class="mxchat-image-thumbnail">';
1492 - $html_output .= '</a></div>';
1493 - }
1494 - }
1495 -
1496 - $html_output .= '</div>';
1497 -
1498 - $this->fallbackResponse = [
1499 - 'text' => "",
1500 - 'html' => $html_output,
1501 - ];
1502 -
1503 - // Save response in chat history
1504 - $this->mxchat_save_chat_message($session_id, 'bot', $html_output);
1505 -
1506 - } else {
1507 -/*
1508 - if (defined('WP_DEBUG') && WP_DEBUG) {
1509 - error_log("Brave Image API response did not contain expected data structure or was empty: " . print_r($body, true));
1510 - }
1511 -*/
1512 -
1513 - $this->fallbackResponse = [
1514 - 'text' => __("I'm sorry, I couldn't retrieve any images based on your request.", 'mxchat'),
1515 - 'html' => "",
1516 - ];
1517 - }
1518 -}
1519 -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');
1521 -
1522 - // Retrieve OpenAI API key using 'api_key' as the option key
1523 - $api_key = isset($this->options['api_key']) ? sanitize_text_field($this->options['api_key']) : sanitize_text_field(get_option('mxchat_options')['api_key']);
1524 -
1525 - /*
1526 - // Log the API key check, without exposing the key
1527 - if (defined('WP_DEBUG') && WP_DEBUG) {
1528 - error_log("Retrieved OpenAI API Key: " . ($api_key ? "Present" : "Missing"));
1529 - }
1530 - */
1531 -
1532 - if (empty($api_key)) {
1533 - //error_log("OpenAI API key is missing.");
1534 - return sanitize_text_field($user_query); // Default to the original query if API key is missing
1535 - }
1536 -
1537 - $url = 'https://api.openai.com/v1/chat/completions';
1538 - $args = [
1539 - 'headers' => [
1540 - 'Authorization' => 'Bearer ' . $api_key,
1541 - 'Content-Type' => 'application/json',
1542 - ],
1543 - 'body' => wp_json_encode([
1544 - 'model' => 'gpt-3.5-turbo',
1545 - 'messages' => [
1546 - ['role' => 'system', 'content' => $system_prompt],
1547 - ['role' => 'user', 'content' => sanitize_text_field($user_query)],
1548 - ],
1549 - 'temperature' => 0.2,
1550 - 'max_tokens' => 20,
1551 - ]),
1552 - 'method' => 'POST',
1553 - ];
1554 -
1555 - $response = wp_remote_post($url, $args);
1556 -
1557 - if (is_wp_error($response)) {
1558 - //error_log("OpenAI request failed: " . $response->get_error_message());
1559 - return sanitize_text_field($user_query); // Fallback to the original query if there's an error
1560 - }
1561 -
1562 - $body = json_decode(wp_remote_retrieve_body($response), true);
1563 -
1564 - // Check for a valid response and sanitize output
1565 - if (isset($body['choices'][0]['message']['content'])) {
1566 - $interpreted_query = sanitize_text_field(trim($body['choices'][0]['message']['content']));
1567 -
1568 - /*
1569 - // Log the interpreted query for debugging
1570 - if (defined('WP_DEBUG') && WP_DEBUG) {
1571 - error_log("Interpreted search query: " . $interpreted_query);
1572 - }
1573 - */
1574 -
1575 - return $interpreted_query;
1576 - } else {
1577 - //error_log("Unexpected API response format: " . print_r($body, true));
1578 - return sanitize_text_field($user_query);
1579 - }
1580 -}
1581 -
1582 -
1583 -
1584 -private function find_product_in_message($message) {
1585 - global $wpdb;
1586 -
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;
1591 - }
1592 -
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;
1599 - }
1600 -
1601 - // Extract product URLs from the content
1602 - preg_match_all('/https?:\/\/[^\s<>"\']+?\/product\/[^\s<>"\']+/', $relevant_content, $matches);
1603 -
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 - }
1633 - }
1634 -
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 - }
1649 - }
1650 -
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 -}
1655 -
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;
1705 - }
1706 -
1707 - return $context_string;
1708 -}
1709 -
1710 -
1711 -//very good
334 +// Function to add the captured email to Loops
1712 335 private function add_email_to_loops($email) {
1713 - // Sanitize the email
1714 - $email = sanitize_email($email);
336 + $api_key = $this->options['loops_api_key'];
337 + $mailing_list_id = $this->options['loops_mailing_list'];
1715 338
1716 - // Retrieve and sanitize options
1717 - $api_key = isset($this->options['loops_api_key']) ? sanitize_text_field($this->options['loops_api_key']) : '';
1718 - $mailing_list_id = isset($this->options['loops_mailing_list']) ? sanitize_text_field($this->options['loops_mailing_list']) : '';
1719 -
1720 - // Check for missing API key or mailing list ID
1721 - if (empty($api_key) || empty($mailing_list_id)) {
1722 - //error_log(esc_html__('Loops API key or mailing list ID is missing.', 'mxchat'));
1723 - return;
1724 - }
1725 -
1726 339 $data = array(
1727 - 'email' => $email,
1728 - 'subscribed' => true,
1729 - 'source' => __('MxChat AI Chatbot', 'mxchat'),
340 + 'email' => $email,
341 + 'subscribed' => true,
342 + 'source' => 'MxChat AI Chatbot',
1730 343 'mailingLists' => array($mailing_list_id => true),
1731 344 );
1732 345
1733 - $url = 'https://app.loops.so/api/v1/contacts/create';
346 + $url = "https://app.loops.so/api/v1/contacts/create";
1734 347 $args = array(
1735 - 'body' => wp_json_encode($data),
348 + 'body' => json_encode($data),
1736 349 'headers' => array(
1737 350 'Authorization' => 'Bearer ' . $api_key,
1738 - 'Content-Type' => 'application/json',
351 + 'Content-Type' => 'application/json',
1739 352 ),
1740 - 'method' => 'POST',
353 + 'method' => 'POST',
1741 354 'timeout' => 45,
1742 355 );
1743 356
1744 - $response = wp_remote_post($url, $args);
1745 -
1746 - // Handle errors in the API request
1747 - if (is_wp_error($response)) {
1748 - //error_log(esc_html__('Error adding email to Loops: ', 'mxchat') . $response->get_error_message());
1749 - return;
1750 - }
1751 -
1752 - // Check for non-200 HTTP responses
1753 - $response_code = wp_remote_retrieve_response_code($response);
1754 - if ($response_code != 200) {
1755 - $response_body = wp_remote_retrieve_body($response);
1756 - //error_log(esc_html__('Loops API responded with code ', 'mxchat') . $response_code . ': ' . $response_body);
1757 - }
357 + wp_remote_post($url, $args);
1758 358 }
1759 359
1760 -public function mxchat_handle_pdf_discussion($message, $user_id, $session_id) {
1761 - // 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;
1763 360
1764 - // 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');
1768 -
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;
1773 -
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) {
1778 - set_transient('mxchat_waiting_for_pdf_url_' . $session_id, true, HOUR_IN_SECONDS);
1779 - $this->fallbackResponse['text'] = $trigger_text;
1780 - return;
1781 - }
1782 -
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)
1787 - } else {
1788 - $this->fallbackResponse['text'] = $trigger_text;
1789 - }
1790 - return;
1791 - }
1792 -
1793 - // Default to proceeding with conversation if no specific PDF action is needed
1794 - $this->fallbackResponse['text'] = '';
361 +private function mxchat_get_user_identifier() {
362 + return MxChat_User::mxchat_get_user_identifier();
1795 363 }
1796 -private function fetch_and_split_pdf_pages($pdf_source, $max_pages) {
1797 - $upload_dir = wp_upload_dir();
1798 - $temp_file = null;
1799 364
1800 - try {
1801 - // Handle URL vs local file
1802 - if (filter_var($pdf_source, FILTER_VALIDATE_URL)) {
1803 - // Validate and download the file from URL
1804 - $temp_file = wp_tempnam($pdf_source); // Safe temporary file name
1805 - $response = wp_remote_get($pdf_source, ['timeout' => 60]);
1806 365
1807 - 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));
1809 - return false;
1810 - }
1811 366
1812 - file_put_contents($temp_file, wp_remote_retrieve_body($response));
1813 -
1814 - // Validate that the downloaded file is a PDF
1815 - $mime_type = mime_content_type($temp_file);
1816 - if ($mime_type !== 'application/pdf') {
1817 - //error_log(esc_html__("Invalid MIME type detected for PDF: ", 'mxchat') . $mime_type);
1818 - unlink($temp_file);
1819 - return false;
1820 - }
1821 - } else {
1822 - // For local files, use the provided path directly
1823 - $temp_file = $pdf_source;
1824 - }
1825 -
1826 - // Parse and process the PDF
1827 - $parser = new \Smalot\PdfParser\Parser();
1828 - $pdf = $parser->parseFile($temp_file);
1829 - $pages = $pdf->getPages();
1830 -
1831 - if (count($pages) > $max_pages) {
1832 - //error_log(esc_html__("PDF exceeds the maximum allowed pages: ", 'mxchat') . count($pages));
1833 - if (filter_var($pdf_source, FILTER_VALIDATE_URL)) {
1834 - unlink($temp_file);
1835 - }
1836 - return esc_html__('too_many_pages', 'mxchat');
1837 - }
1838 -
1839 - $embeddings = [];
1840 - foreach ($pages as $page_number => $page) {
1841 - $text = $page->getText();
1842 -
1843 - // Ensure text is non-empty before generating embeddings
1844 - if (empty(trim($text))) {
1845 - //error_log(esc_html__("Skipping empty page: ", 'mxchat') . ($page_number + 1));
1846 - continue;
1847 - }
1848 -
1849 - $embedding = $this->mxchat_generate_embedding(
1850 - esc_html__("Page ", 'mxchat') . ($page_number + 1) . ": " . $text,
1851 - $this->options['api_key']
1852 - );
1853 -
1854 - if ($embedding) {
1855 - $embeddings[] = [
1856 - 'page_number' => $page_number + 1,
1857 - 'embedding' => $embedding,
1858 - 'text' => $text,
1859 - ];
1860 - } else {
1861 - //error_log(esc_html__("Failed to generate embedding for page ", 'mxchat') . ($page_number + 1));
1862 - }
1863 - }
1864 -
1865 - // Clean up downloaded file if it was from URL
1866 - if (filter_var($pdf_source, FILTER_VALIDATE_URL) && $temp_file) {
1867 - unlink($temp_file);
1868 - }
1869 -
1870 - return $embeddings;
1871 -
1872 - } catch (\Exception $e) {
1873 - // error_log(esc_html__("Error parsing or processing PDF: ", 'mxchat') . $e->getMessage());
1874 -
1875 - // Cleanup in case of exception
1876 - if (filter_var($pdf_source, FILTER_VALIDATE_URL) && $temp_file && file_exists($temp_file)) {
1877 - unlink($temp_file);
1878 - }
1879 -
1880 - return false;
1881 - }
1882 -}
1883 -private function find_relevant_pdf_pages($query_embedding, $embeddings) {
1884 - //error_log(esc_html__("find_relevant_pdf_pages called.", 'mxchat'));
1885 -
1886 - $most_relevant = null;
1887 - $highest_similarity = -INF;
1888 -
1889 - foreach ($embeddings as $page_data) {
1890 - $similarity = $this->mxchat_calculate_cosine_similarity($query_embedding, $page_data['embedding']);
1891 -
1892 - if ($similarity > $highest_similarity) {
1893 - $highest_similarity = $similarity;
1894 - $most_relevant = $page_data['page_number'];
1895 - }
1896 - }
1897 -
1898 - if (!is_null($most_relevant)) {
1899 - $page_numbers = range(max(1, $most_relevant - 1), min(count($embeddings), $most_relevant + 1));
1900 - return array_filter($embeddings, function ($page) use ($page_numbers) {
1901 - return in_array($page['page_number'], $page_numbers);
1902 - });
1903 - }
1904 -
1905 - return [];
1906 -}
1907 -// Add this to your class
1908 -public function handle_pdf_upload() {
1909 - check_ajax_referer('mxchat_chat_nonce', 'nonce');
1910 -
1911 - if (!isset($_FILES['pdf_file']) || !isset($_POST['session_id'])) {
1912 - wp_send_json_error(esc_html__('Missing required parameters.', 'mxchat'));
1913 - return;
1914 - }
1915 -
1916 - $file = $_FILES['pdf_file'];
1917 - $session_id = sanitize_text_field($_POST['session_id']);
1918 - $original_filename = sanitize_text_field($file['name']);
1919 -
1920 - $file_type = wp_check_filetype($file['name'], ['pdf' => 'application/pdf']);
1921 - if ($file_type['type'] !== 'application/pdf') {
1922 - wp_send_json_error(esc_html__('Invalid file type. Only PDF files are allowed.', 'mxchat'));
1923 - return;
1924 - }
1925 -
1926 - $upload_dir = wp_upload_dir();
1927 - $pdf_filename = 'mxchat_' . $session_id . '_' . time() . '.pdf';
1928 - $pdf_path = $upload_dir['path'] . '/' . $pdf_filename;
1929 -
1930 - if (!move_uploaded_file($file['tmp_name'], $pdf_path)) {
1931 - wp_send_json_error(esc_html__('Failed to upload file.', 'mxchat'));
1932 - return;
1933 - }
1934 -
1935 - $this->clear_pdf_transients($session_id);
1936 -
1937 - $max_pages = isset($this->options['pdf_max_pages']) ? intval($this->options['pdf_max_pages']) : 69;
1938 - $embeddings = $this->fetch_and_split_pdf_pages($pdf_path, $max_pages);
1939 -
1940 - if ($embeddings === 'too_many_pages') {
1941 - unlink($pdf_path);
1942 - $error_message = sprintf(
1943 - $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'),
1945 - $max_pages
1946 - );
1947 - wp_send_json_error($error_message);
1948 - return;
1949 - }
1950 -
1951 - if ($embeddings === false || empty($embeddings)) {
1952 - unlink($pdf_path);
1953 - $error_message = $this->options['pdf_intent_error_text'] ??
1954 - esc_html__('The uploaded PDF appears to be empty or contains unsupported content.', 'mxchat');
1955 - wp_send_json_error($error_message);
1956 - return;
1957 - }
1958 -
1959 - if (!empty($embeddings)) {
1960 - set_transient('mxchat_pdf_url_' . $session_id, $pdf_path, HOUR_IN_SECONDS);
1961 - set_transient('mxchat_pdf_filename_' . $session_id, $original_filename, HOUR_IN_SECONDS);
1962 - set_transient('mxchat_pdf_embeddings_' . $session_id, $embeddings, HOUR_IN_SECONDS);
1963 - set_transient('mxchat_include_pdf_in_context_' . $session_id, true, HOUR_IN_SECONDS);
1964 -
1965 - $success_message = $this->options['pdf_intent_success_text'] ??
1966 - esc_html__("I've processed the PDF. What questions do you have about it?", 'mxchat');
1967 -
1968 - wp_send_json_success([
1969 - 'message' => $success_message,
1970 - 'filename' => $original_filename
1971 - ]);
1972 - return;
1973 - }
1974 -
1975 - unlink($pdf_path);
1976 - $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');
1978 - wp_send_json_error($error_message);
1979 - return;
1980 -}
1981 -public function handle_pdf_remove() {
1982 - check_ajax_referer('mxchat_chat_nonce', 'nonce');
1983 -
1984 - if (empty($_POST['session_id'])) {
1985 - wp_send_json_error(esc_html__('Session ID missing.', 'mxchat'));
1986 - wp_die();
1987 - }
1988 -
1989 - $session_id = sanitize_text_field($_POST['session_id']);
1990 - $pdf_path = get_transient('mxchat_pdf_url_' . $session_id);
1991 -
1992 - if ($pdf_path && file_exists($pdf_path)) {
1993 - unlink($pdf_path);
1994 - }
1995 -
1996 - $this->clear_pdf_transients($session_id);
1997 -
1998 - wp_send_json_success([
1999 - 'message' => esc_html__('PDF removed successfully.', 'mxchat')
2000 - ]);
2001 - wp_die();
2002 -}
2003 -
2004 -
2005 -
2006 -
2007 -function mxchat_fetch_new_messages() {
2008 - $session_id = sanitize_text_field($_POST['session_id']);
2009 - $last_seen_id = sanitize_text_field($_POST['last_seen_id']);
2010 - $persistence_enabled = $_POST['persistence_enabled'] === 'true';
2011 - $initial_timestamp = isset($_POST['initial_timestamp']) ? intval($_POST['initial_timestamp']) : 0;
2012 -
2013 - 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')]);
2016 - wp_die();
2017 - }
2018 -
2019 - $history = get_option("mxchat_history_{$session_id}", []);
2020 -
2021 - $new_messages = array_filter($history, function ($message) use ($last_seen_id, $persistence_enabled, $initial_timestamp) {
2022 - // If persistence is enabled, show all new messages
2023 - if ($persistence_enabled) {
2024 - return !empty($message['id']) &&
2025 - strcmp($message['id'], $last_seen_id) > 0 &&
2026 - $message['role'] === 'agent';
2027 - }
2028 -
2029 - // If persistence is disabled, only show messages after initial timestamp
2030 - return !empty($message['id']) &&
2031 - $message['role'] === 'agent' &&
2032 - $message['timestamp'] > $initial_timestamp;
2033 - });
2034 -
2035 - //error_log(esc_html__("New agent messages fetched for session $session_id. Last seen ID: $last_seen_id", 'mxchat'));
2036 -
2037 - wp_send_json_success([
2038 - 'new_messages' => array_values($new_messages)
2039 - ]);
2040 - wp_die();
2041 -}
2042 -
2043 -
2044 -public function mxchat_live_agent_handover($message, $user_id, $session_id) {
2045 - // First check if live agents are available
2046 - $live_agent_available = $this->options['live_agent_status'] ?? 'off';
2047 - if ($live_agent_available !== 'on') {
2048 - $away_message = $this->options['live_agent_away_message'] ?? 'Sorry, live agents are currently unavailable. I can continue helping you as an AI assistant.';
2049 - $this->fallbackResponse = [
2050 - 'text' => $away_message,
2051 - 'html' => '',
2052 - 'images' => [],
2053 - 'chat_mode' => 'ai'
2054 - ];
2055 - wp_send_json([
2056 - 'text' => $away_message,
2057 - 'html' => '',
2058 - 'chat_mode' => 'ai',
2059 - 'session_id' => $session_id
2060 - ]);
2061 - wp_die();
2062 - }
2063 -
2064 - $slack_webhook_url = $this->options['live_agent_webhook_url'] ?? '';
2065 - if (empty($slack_webhook_url)) {
2066 - return false;
2067 - }
2068 -
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 - update_option("mxchat_mode_{$session_id}", 'agent');
2085 -
2086 - $webhook_data = [
2087 - 'blocks' => [
2088 - [
2089 - 'type' => 'header',
2090 - 'text' => [
2091 - 'type' => 'plain_text',
2092 - 'text' => '🔔 New Live Agent Request',
2093 - 'emoji' => true
2094 - ]
2095 - ],
2096 - [
2097 - 'type' => 'section',
2098 - 'fields' => [
2099 - [
2100 - 'type' => 'mrkdwn',
2101 - 'text' => sprintf('*User ID:*\n`%s`', $user_id)
2102 - ],
2103 - [
2104 - 'type' => 'mrkdwn',
2105 - 'text' => sprintf('*Session ID:*\n`%s`', $session_id)
2106 - ]
2107 - ]
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' => [
2136 - [
2137 - 'type' => 'button',
2138 - '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'
2146 - ]
2147 - ]
2148 - ];
2149 -
2150 - $response = wp_remote_post($slack_webhook_url, [
2151 - 'body' => json_encode($webhook_data),
2152 - 'headers' => [
2153 - 'Content-Type' => 'application/json',
2154 - ],
2155 - ]);
2156 -
2157 - if (is_wp_error($response)) {
2158 - return false;
2159 - }
2160 -
2161 - $success_message = $this->options['live_agent_notification_message'] ?? 'Live agent has been notified.';
2162 - $this->mxchat_save_chat_message($session_id, 'bot', $success_message);
2163 -
2164 - $this->fallbackResponse = [
2165 - 'text' => $success_message,
2166 - 'html' => '',
2167 - 'images' => [],
2168 - 'chat_mode' => 'agent'
2169 - ];
2170 -
2171 - wp_send_json([
2172 - 'success' => true,
2173 - 'text' => $success_message,
2174 - 'html' => '',
2175 - 'chat_mode' => 'agent',
2176 - 'session_id' => $session_id,
2177 - 'fallbackResponse' => $this->fallbackResponse
2178 - ]);
2179 - wp_die();
2180 -}
2181 -public function mxchat_send_user_message_to_agent($message, $user_id, $session_id) {
2182 - $slack_webhook_url = $this->options['live_agent_webhook_url'] ?? '';
2183 -
2184 - if (empty($slack_webhook_url)) {
2185 - //error_log(esc_html__('Slack Webhook URL is not configured.', 'mxchat'));
2186 - return false;
2187 - }
2188 -
2189 - $webhook_data = [
2190 - 'blocks' => [
2191 - [
2192 - 'type' => 'header',
2193 - 'text' => [
2194 - 'type' => 'plain_text',
2195 - 'text' => esc_html__('📩 New Chat Message', 'mxchat'),
2196 - 'emoji' => true
2197 - ]
2198 - ],
2199 - [
2200 - 'type' => 'section',
2201 - 'fields' => [
2202 - [
2203 - 'type' => 'mrkdwn',
2204 - 'text' => sprintf(esc_html__('*User ID:*\n`%s`', 'mxchat'), $user_id)
2205 - ],
2206 - [
2207 - 'type' => 'mrkdwn',
2208 - 'text' => sprintf(esc_html__('*Session ID:*\n`%s`', 'mxchat'), $session_id)
2209 - ]
2210 - ]
2211 - ],
2212 - [
2213 - 'type' => 'section',
2214 - 'text' => [
2215 - 'type' => 'mrkdwn',
2216 - 'text' => sprintf(esc_html__('*Message:*\n%s', 'mxchat'), $message)
2217 - ]
2218 - ],
2219 - [
2220 - 'type' => 'actions',
2221 - 'elements' => [
2222 - [
2223 - 'type' => 'button',
2224 - 'text' => [
2225 - 'type' => 'plain_text',
2226 - 'text' => esc_html__('✍️ Reply', 'mxchat'),
2227 - 'emoji' => true
2228 - ],
2229 - 'value' => $session_id,
2230 - 'action_id' => 'reply_to_user',
2231 - 'style' => 'primary'
2232 - ]
2233 - ]
2234 - ]
2235 - ]
2236 - ];
2237 -
2238 - $response = wp_remote_post($slack_webhook_url, [
2239 - 'body' => json_encode($webhook_data),
2240 - 'headers' => [
2241 - 'Content-Type' => 'application/json',
2242 - ],
2243 - ]);
2244 -
2245 - if (is_wp_error($response)) {
2246 - //error_log(esc_html__('Error sending message to Slack: ', 'mxchat') . $response->get_error_message());
2247 - return false;
2248 - }
2249 -
2250 - //error_log(esc_html__('Message sent to Slack successfully.', 'mxchat'));
2251 - return true;
2252 -}
2253 -public function handle_slack_interaction(WP_REST_Request $request) {
2254 - //error_log('Received Slack interaction');
2255 -
2256 - $payload = json_decode($request->get_param('payload'), true);
2257 - //error_log('Payload: ' . print_r($payload, true));
2258 -
2259 - // Handle button click
2260 - if ($payload['type'] === 'block_actions' && $payload['actions'][0]['action_id'] === 'reply_to_user') {
2261 - $session_id = $payload['actions'][0]['value'];
2262 - $trigger_id = $payload['trigger_id'];
2263 -
2264 - // Get Bot Token from settings
2265 - $slack_token = $this->options['live_agent_bot_token'] ?? '';
2266 -
2267 - if (empty($slack_token)) {
2268 - //error_log('Slack Bot Token not configured');
2269 - return new WP_REST_Response(['error' => esc_html__('Bot token not configured', 'mxchat')], 400);
2270 - }
2271 - $response = wp_remote_post('https://slack.com/api/views.open', [
2272 - 'headers' => [
2273 - 'Content-Type' => 'application/json',
2274 - 'Authorization' => 'Bearer ' . $slack_token
2275 - ],
2276 - 'body' => json_encode([
2277 - 'trigger_id' => $trigger_id,
2278 - 'view' => [
2279 - 'type' => 'modal',
2280 - 'callback_id' => 'reply_modal',
2281 - 'title' => [
2282 - 'type' => 'plain_text',
2283 - 'text' => __('Reply to User', 'mxchat')
2284 - ],
2285 - 'submit' => [
2286 - 'type' => 'plain_text',
2287 - 'text' => __('Send', 'mxchat')
2288 - ],
2289 - 'close' => [
2290 - 'type' => 'plain_text',
2291 - 'text' => __('Cancel', 'mxchat')
2292 - ],
2293 - 'blocks' => [
2294 - [
2295 - 'type' => 'input',
2296 - 'block_id' => 'reply_block',
2297 - 'label' => [
2298 - 'type' => 'plain_text',
2299 - 'text' => sprintf(__('Reply to session: %s', 'mxchat'), $session_id)
2300 - ],
2301 - 'element' => [
2302 - 'type' => 'plain_text_input',
2303 - 'action_id' => 'message',
2304 - 'multiline' => true,
2305 - 'placeholder' => [
2306 - 'type' => 'plain_text',
2307 - 'text' => __('Type your message here...', 'mxchat')
2308 - ]
2309 - ]
2310 - ]
2311 - ],
2312 - 'private_metadata' => $session_id
2313 - ]
2314 - ])
2315 - ]);
2316 -
2317 - //error_log('Views.open response: ' . print_r($response, true));
2318 -
2319 - // Return immediate acknowledgment
2320 - return new WP_REST_Response(['ok' => true]);
2321 - }
2322 -
2323 - // 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'];
2328 -
2329 - // Save the message (keep the message_id but don't include in response)
2330 - $this->mxchat_save_chat_message($session_id, 'agent', $message);
2331 -
2332 - // Keep the original response format for Slack
2333 - return new WP_REST_Response([
2334 - 'response_action' => 'clear'
2335 - ]);
2336 -}
2337 -
2338 - // Default acknowledgment
2339 - return new WP_REST_Response(['ok' => true]);
2340 -}
2341 -
2342 -public function mxchat_handle_agent_response(WP_REST_Request $request) {
2343 - //error_log('Received agent response request');
2344 - //error_log('Request data: ' . print_r($request->get_params(), true));
2345 - // error_log('Raw body: ' . file_get_contents('php://input'));
2346 -
2347 - // Get the data from Slack's slash command format
2348 - $command_text = $request->get_param('text');
2349 - // error_log('Command text: ' . $command_text);
2350 -
2351 - if (empty($command_text)) {
2352 - //error_log(esc_html__('Agent response error: No command text received', 'mxchat'));
2353 - return new WP_REST_Response([
2354 - 'error' => esc_html__('Command text is required. Format: /reply session_id message', 'mxchat')
2355 - ], 400);
2356 - }
2357 -
2358 - // Split the command text into session_id and message
2359 - $parts = explode(' ', $command_text, 2);
2360 - if (count($parts) !== 2) {
2361 - //error_log('Agent response error: Invalid command format');
2362 - return new WP_REST_Response([
2363 - 'error' => esc_html__('Invalid format. Use: /reply session_id message', 'mxchat')
2364 - ], 400);
2365 - }
2366 -
2367 - $session_id = sanitize_text_field($parts[0]);
2368 - $message = sanitize_text_field($parts[1]);
2369 -
2370 - //error_log("Processing agent response - Session ID: $session_id, Message: $message");
2371 -
2372 - // Save the message
2373 - $message_id = $this->mxchat_save_chat_message($session_id, 'agent', $message);
2374 -
2375 - if (!$message_id) {
2376 - // error_log('Failed to save agent message');
2377 - return new WP_REST_Response([
2378 - 'error' => esc_html__('Failed to save message', 'mxchat')
2379 - ], 500);
2380 - }
2381 -
2382 - // Return success response in Slack's expected format
2383 - return new WP_REST_Response([
2384 - 'response_type' => 'in_channel',
2385 - 'text' => esc_html__("Message sent successfully to session $session_id", 'mxchat')
2386 - ], 200);
2387 -}
2388 -
2389 -
2390 -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'));
2392 -
2393 - // Just update mode to AI
2394 - update_option("mxchat_mode_{$session_id}", 'ai');
2395 -
2396 - // Initialize states
2397 - $this->fallbackResponse = ['text' => '', 'html' => '', 'images' => []];
2398 - $this->productCardHtml = '';
2399 -
2400 - // Set the response message
2401 - $this->fallbackResponse['text'] = esc_html__('You are now chatting with the AI chatbot.', 'mxchat');
2402 -
2403 - return true; // Intent was handled
2404 -}
2405 -
2406 -
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 -private function mxchat_get_user_identifier() {
2429 - return MxChat_User::mxchat_get_user_identifier();
2430 -}
2431 -
2432 -private function mxchat_generate_embedding($text, $api_key) {
367 + private function mxchat_generate_embedding($text, $api_key) {
2433 368 $endpoint = 'https://api.openai.com/v1/embeddings';
2434 369
2435 370 $body = wp_json_encode([
2436 371 'input' => $text,
@@ -2464,386 +399,69 @@
2464 399 return null;
2465 400 }
2466 401 }
2467 402
2468 -
2469 403 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 404 global $wpdb;
2492 405 $system_prompt_table = $wpdb->prefix . 'mxchat_system_prompt_content';
406 +
407 + // Define a cache key for embeddings
2493 408 $cache_key = 'mxchat_system_prompt_embeddings';
2494 - $batch_size = 500;
2495 409
2496 - // Retrieve embeddings from cache or database
410 + // Attempt to get the embeddings from the cache
2497 411 $embeddings = wp_cache_get($cache_key, 'mxchat_system_prompts');
412 +
2498 413 if ($embeddings === false) {
2499 - $embeddings = [];
2500 - $offset = 0;
414 + // Cache miss, query the database and cache the results
415 + $query = "SELECT id, embedding_vector FROM {$system_prompt_table}";
416 + $embeddings = $wpdb->get_results($query);
2501 417
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 - );
418 + if ($embeddings === null || empty($embeddings)) {
419 + //error_log("No embeddings found in the database.");
420 + return null; // Return null to handle no embeddings gracefully
421 + }
2511 422
2512 - $batch = $wpdb->get_results($query);
2513 - if (empty($batch)) {
2514 - break;
2515 - }
423 + // Cache the results if successful
424 + wp_cache_set($cache_key, $embeddings, 'mxchat_system_prompts', 3600); // Cache for 1 hour
425 + }
2516 426
2517 - $embeddings = array_merge($embeddings, $batch);
2518 - $offset += $batch_size;
427 + $most_relevant_id = null;
428 + $highest_similarity = -INF;
2519 429
2520 - // Free memory
2521 - unset($batch);
430 + foreach ($embeddings as $embedding) {
431 + $database_embedding = maybe_unserialize($embedding->embedding_vector);
2522 432
2523 - } while (true);
433 + // Debugging: Log the embeddings
434 + // if (!is_array($database_embedding)) {
435 + // error_log("Invalid database embedding format for ID {$embedding->id}: " . print_r($database_embedding, true));
436 + // continue;
437 + // }
2524 438
2525 - if (empty($embeddings)) {
2526 - return ''; // Return an empty string if no embeddings found
2527 - }
2528 - wp_cache_set($cache_key, $embeddings, 'mxchat_system_prompts', 3600);
2529 - }
2530 -
2531 - // Initialize array to store relevant results with similarity scores
2532 - $relevant_results = [];
2533 - // Iterate through embeddings to calculate similarity
2534 - foreach ($embeddings as $embedding) {
2535 - $database_embedding = $embedding->embedding_vector
2536 - ? unserialize($embedding->embedding_vector, ['allowed_classes' => false])
2537 - : null;
2538 - if (is_array($database_embedding) && is_array($user_embedding)) {
439 + if (is_array($user_embedding)) {
2539 440 $similarity = $this->mxchat_calculate_cosine_similarity($user_embedding, $database_embedding);
2540 - $relevant_results[] = [
2541 - 'id' => $embedding->id,
2542 - 'similarity' => $similarity
2543 - ];
2544 - }
2545 - // Free memory
2546 - unset($database_embedding);
2547 - }
2548 441
2549 - // Retrieve the similarity threshold
2550 - $similarity_threshold = ((int) get_option('mxchat_similarity_threshold', 80)) / 100;
442 + // Debugging: Log the similarity score
443 + // error_log("Calculated similarity for ID {$embedding->id}: {$similarity}");
2551 444
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 - });
2559 -
2560 - // Limit to the top 5 results
2561 - $top_results = array_slice($relevant_results, 0, 5);
2562 -
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) {
2571 - $surrounding_content = $wpdb->get_results($wpdb->prepare(
2572 - "SELECT article_content FROM {$system_prompt_table}
2573 - WHERE id IN (
2574 - (SELECT id FROM {$system_prompt_table} WHERE id < %d ORDER BY id DESC LIMIT 1),
2575 - (SELECT id FROM {$system_prompt_table} WHERE id > %d ORDER BY id ASC LIMIT 1)
2576 - )",
2577 - $result['id'],
2578 - $result['id']
2579 - ));
2580 - // Add previous content if it exists
2581 - if (!empty($surrounding_content[0])) {
2582 - $content .= $surrounding_content[0]->article_content . "\n\n";
445 + if ($similarity > $highest_similarity) {
446 + $highest_similarity = $similarity;
447 + $most_relevant_id = $embedding->id;
2583 448 }
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 449 } else {
2591 - // For non-PDF content, add directly
2592 - $content .= $chunk_content . "\n\n";
450 + // error_log("User embedding is not an array. Embedding data: " . print_r($user_embedding, true));
2593 451 }
2594 452 }
2595 453
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'] ?? '';
2605 -
2606 - if (empty($host) || empty($api_key)) {
2607 - //error_log('Pinecone credentials not properly configured');
2608 - return '';
454 + if ($most_relevant_id !== null) {
455 + // Fetch content with product links
456 + return $this->fetch_content_with_product_links($most_relevant_id);
2609 457 }
2610 458
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);
459 + //error_log("No relevant content found. Most relevant ID was null.");
460 + return null; // Return null if no relevant content is found
2668 461 }
2669 462
2670 463
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;
2716 - }
2717 -
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 '';
2727 - }
2728 - wp_cache_set($cache_key, $embeddings, 'mxchat_system_prompts', 3600);
2729 - }
2730 -
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 - ];
2742 - }
2743 - unset($database_embedding);
2744 - }
2745 -
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";
2762 - }
2763 -
2764 - return trim($content);
2765 -}
2766 -
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 -
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 464 private function fetch_content_with_product_links($most_relevant_id) {
2847 465 global $wpdb;
2848 466 $system_prompt_table = $wpdb->prefix . 'mxchat_system_prompt_content';
2849 467
@@ -2862,216 +480,55 @@
2862 480
2863 481 return null;
2864 482 }
2865 483
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 - );
484 +private function mxchat_generate_response($relevant_content, $api_key, $xai_api_key, $claude_api_key, $conversation_history) {
485 + if (!$relevant_content) {
486 + return "I'm sorry, I couldn't find relevant information on that topic.";
2949 487 }
2950 -}
2951 488
489 + // Check the selected model
490 + $selected_model = isset($this->options['model']) ? $this->options['model'] : 'gpt-3.5-turbo';
2952 491
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']);
492 + // Call the appropriate function based on the selected model
493 + if (strpos($selected_model, 'claude') !== false) {
494 + return $this->mxchat_generate_response_claude($selected_model, $claude_api_key, $conversation_history, $relevant_content);
495 + } elseif ($selected_model === 'grok-beta') {
496 + return $this->mxchat_generate_response_xai($selected_model, $xai_api_key, $conversation_history, $relevant_content);
3023 497 } else {
3024 - //error_log('DeepSeek API Response Format Error: ' . print_r($decoded_response, true));
3025 - return "Sorry, I couldn't process that request.";
498 + return $this->mxchat_generate_response_openai($selected_model, $api_key, $conversation_history, $relevant_content);
3026 499 }
3027 500 }
3028 501
3029 502 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 503 // Get system prompt instructions from options
3036 504 $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : '';
3037 505
3038 - // Create a new array for the formatted conversation
3039 - $formatted_conversation = array();
506 + // Add system prompt to relevant content
507 + $content_with_instructions = $system_prompt_instructions . " " . $relevant_content;
3040 508
3041 - // Add system message first
3042 - $formatted_conversation[] = array(
509 + // Prepend system instructions to the conversation history
510 + array_unshift($conversation_history, [
3043 511 'role' => 'system',
3044 - 'content' => $system_prompt_instructions . " " . $relevant_content
3045 - );
512 + 'content' => "Here are your instructions: " . $content_with_instructions
513 + ]);
3046 514
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';
3055 - }
3056 - if (!in_array($role, ['system', 'assistant', 'user', 'function', 'tool'])) {
3057 - $role = 'user';
3058 - }
3059 -
3060 - $formatted_conversation[] = array(
3061 - 'role' => $role,
3062 - 'content' => $message['content']
3063 - );
515 + // Ensure consistency: Replace 'bot' role with 'assistant' in conversation history
516 + foreach ($conversation_history as &$message) {
517 + if ($message['role'] === 'bot') {
518 + $message['role'] = 'assistant';
3064 519 }
3065 520 }
3066 521
522 + // Build the request body
3067 523 $body = json_encode([
3068 524 'model' => $selected_model,
3069 - 'messages' => $formatted_conversation,
525 + 'messages' => $conversation_history,
3070 526 'temperature' => 0.8,
3071 527 'stream' => false
3072 528 ]);
3073 529
530 + // Set up the API request
3074 531 $args = [
3075 532 'body' => $body,
3076 533 'headers' => [
3077 534 'Content-Type' => 'application/json',
@@ -3083,25 +540,25 @@
3083 540 'httpversion' => '1.0',
3084 541 'sslverify' => true,
3085 542 ];
3086 543
544 + // Make the API request
3087 545 $response = wp_remote_post('https://api.openai.com/v1/chat/completions', $args);
3088 546
547 + // Process the response
3089 548 if (is_wp_error($response)) {
3090 - //error_log('OpenAI API Error: ' . $response->get_error_message());
3091 549 return "Sorry, there was an error processing your request.";
3092 550 }
3093 551
3094 - $response_body = wp_remote_retrieve_body($response);
3095 - $decoded_response = json_decode($response_body, true);
552 + $response_body = json_decode(wp_remote_retrieve_body($response), true);
3096 553
3097 - if (isset($decoded_response['choices'][0]['message']['content'])) {
3098 - return trim($decoded_response['choices'][0]['message']['content']);
554 + if (isset($response_body['choices'][0]['message']['content'])) {
555 + return trim($response_body['choices'][0]['message']['content']);
3099 556 } else {
3100 - //error_log('OpenAI API Response Format Error: ' . print_r($decoded_response, true));
3101 557 return "Sorry, I couldn't process that request.";
3102 558 }
3103 559 }
560 +
3104 561 private function mxchat_generate_response_xai($selected_model, $xai_api_key, $conversation_history, $relevant_content) {
3105 562 // Get system prompt instructions from options
3106 563 $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : '';
3107 564
@@ -3113,27 +570,15 @@
3113 570 'role' => 'system',
3114 571 'content' => "Here are your instructions: " . $content_with_instructions
3115 572 ]);
3116 573
3117 - // Ensure consistency: Replace 'bot' and 'agent' roles with supported values
574 + // Ensure consistency: Replace 'bot' role with 'assistant' in conversation history
3118 575 foreach ($conversation_history as &$message) {
3119 576 if ($message['role'] === 'bot') {
3120 577 $message['role'] = 'assistant';
3121 - } elseif ($message['role'] === 'agent') {
3122 - // Tag the message as coming from a live agent
3123 - $message['role'] = 'assistant';
3124 - if (!isset($message['metadata'])) {
3125 - $message['metadata'] = ['source' => 'live_agent'];
3126 - }
3127 578 }
3128 -
3129 - // Ensure all roles are valid
3130 - if (!in_array($message['role'], ['system', 'assistant', 'user', 'function', 'tool'])) {
3131 - $message['role'] = 'user'; // Default to 'user'
3132 - }
3133 579 }
3134 580
3135 -
3136 581 // Build the request body
3137 582 $body = json_encode([
3138 583 'model' => $selected_model,
3139 584 'messages' => $conversation_history,
@@ -3172,107 +617,66 @@
3172 617 }
3173 618 }
3174 619
3175 620 private function mxchat_generate_response_claude($selected_model, $claude_api_key, $conversation_history, $relevant_content) {
3176 - // Get system prompt instructions from options
3177 - $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : '';
621 + // The system prompt should be passed at the beginning of the conversation
622 + $system_prompt = "You are an AI Chatbot assistant for MxChat.AI. Your role is to answer questions about the MxChat WordPress plugin and its features. Provide concise and helpful responses.";
3178 623
3179 - // Clean and validate conversation history
3180 - foreach ($conversation_history as &$message) {
3181 - // Convert bot and agent roles to assistant
3182 - if ($message['role'] === 'bot' || $message['role'] === 'agent') {
3183 - $message['role'] = 'assistant';
3184 - }
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 -
3191 - // Ensure content field exists
3192 - if (!isset($message['content']) || empty($message['content'])) {
3193 - $message['content'] = '';
3194 - }
3195 -
3196 - // Remove any unsupported fields
3197 - $message = array_intersect_key($message, array_flip(['role', 'content']));
624 + // If conversation history is empty, add the system prompt
625 + if (empty($conversation_history)) {
626 + $conversation_history[] = [
627 + 'role' => 'system',
628 + 'content' => $system_prompt
629 + ];
3198 630 }
3199 631
3200 - // Add relevant content as the latest user message
632 + // Add the new user message to the conversation
3201 633 $conversation_history[] = [
3202 634 'role' => 'user',
3203 635 'content' => $relevant_content
3204 636 ];
3205 637
3206 - // Build request body
638 + // Build the request body
3207 639 $body = json_encode([
3208 640 'model' => $selected_model,
3209 641 'max_tokens' => 1000,
3210 642 'temperature' => 0.8,
3211 - 'messages' => $conversation_history,
3212 - 'system' => $system_prompt_instructions
643 + 'messages' => $conversation_history
3213 644 ]);
3214 645
3215 - // Set up API request
646 + // Set up the API request
3216 647 $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,
648 + 'body' => $body,
649 + 'headers' => [
650 + 'Content-Type' => 'application/json',
651 + 'x-api-key' => $claude_api_key,
652 + 'anthropic-version' => '2023-06-01',
653 + ],
654 + 'timeout' => 60,
3224 655 'redirection' => 5,
3225 - 'blocking' => true,
656 + 'blocking' => true,
3226 657 'httpversion' => '1.0',
3227 - 'sslverify' => true,
658 + 'sslverify' => true,
3228 659 ];
3229 660
3230 - // Make API request
661 + // Make the API request
3231 662 $response = wp_remote_post('https://api.anthropic.com/v1/messages', $args);
3232 663
3233 - // Check for WordPress errors
664 + // Process the response
3234 665 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.";
666 + return "Sorry, there was an error processing your request.";
3237 667 }
3238 668
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 -
3254 - // Parse response
3255 669 $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 - }
3262 670
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'])) {
671 + if (isset($response_body['content'][0]['text'])) {
3268 672 return trim($response_body['content'][0]['text']);
673 + } else {
674 + return "Sorry, I couldn't process that request.";
3269 675 }
676 +}
3270 677
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 -}
678 +
3275 679 public function mxchat_dismiss_pre_chat_message() {
3276 680 // Get and sanitize the user identifier
3277 681 $user_id = $this->mxchat_get_user_identifier();
3278 682 $user_id = sanitize_key($user_id);
@@ -3304,9 +708,13 @@
3304 708
3305 709 wp_die();
3306 710 }
3307 711
3308 -private function mxchat_calculate_cosine_similarity($vectorA, $vectorB) {
712 +
713 +
714 +
715 +
716 + private function mxchat_calculate_cosine_similarity($vectorA, $vectorB) {
3309 717 if (!is_array($vectorA) || !is_array($vectorB) || empty($vectorA) || empty($vectorB)) {
3310 718 return 0;
3311 719 }
3312 720
@@ -3326,72 +734,48 @@
3326 734
3327 735 return $dotProduct / ($normA * $normB);
3328 736 }
3329 737
3330 -public function mxchat_enqueue_scripts_styles() {
3331 - // Define version numbers for the styles and scripts
3332 - $chat_style_version = '2.0.5'; // Replace with your actual version
3333 - $chat_script_version = '2.0.5'; // Replace with your actual version
738 + public function mxchat_enqueue_scripts_styles() {
739 + // Define version numbers for the styles and scripts
740 + $chat_style_version = '1.2'; // Replace with your actual version
741 + $chat_script_version = '1.2'; // Replace with your actual version
3334 742
3335 - // Enqueue the script
3336 - wp_enqueue_script(
3337 - 'mxchat-chat-js',
3338 - plugin_dir_url(__FILE__) . '../js/chat-script.js',
3339 - array('jquery'),
3340 - $chat_script_version,
3341 - true
3342 - );
743 + // Correct path to the script file
744 + wp_enqueue_script(
745 + 'mxchat-chat-js', // Handle for the script
746 + plugin_dir_url(__FILE__) . '../js/chat-script.js', // Correct path using __FILE__
747 + array('jquery'), // Dependencies
748 + $chat_script_version, // Version for cache busting
749 + true // Load script in footer
750 + );
3343 751
3344 - // Enqueue the CSS
3345 - wp_enqueue_style(
3346 - 'mxchat-chat-css',
3347 - plugin_dir_url(__FILE__) . '../css/chat-style.css',
3348 - array(),
3349 - $chat_style_version
3350 - );
752 + // Enqueue the CSS file similarly
753 + wp_enqueue_style(
754 + 'mxchat-chat-css', // Handle for the style
755 + plugin_dir_url(__FILE__) . '../css/chat-style.css', // Correct path using __FILE__
756 + array(), // No dependencies
757 + $chat_style_version // Version for cache busting
758 + );
3351 759
3352 - // Fetch options from the database
3353 - $this->options = get_option('mxchat_options');
3354 - $prompts_options = get_option('mxchat_prompts_options', array());
760 + // Fetch options from the database
761 + $this->options = get_option('mxchat_options');
3355 762
3356 - // Prepare settings for JavaScript
3357 - $style_settings = array(
3358 - 'ajax_url' => admin_url('admin-ajax.php'),
3359 - 'nonce' => wp_create_nonce('mxchat_chat_nonce'),
3360 - 'link_target_toggle' => $this->options['link_target_toggle'] ?? 'off',
3361 - 'rate_limit_message' => $this->options['rate_limit_message'] ?? 'Rate limit exceeded. Please try again later.',
3362 - 'complianz_toggle' => isset($this->options['complianz_toggle']) && $this->options['complianz_toggle'] === 'on',
3363 - 'user_message_bg_color' => $this->options['user_message_bg_color'] ?? '#fff',
3364 - 'user_message_font_color' => $this->options['user_message_font_color'] ?? '#212121',
3365 - 'bot_message_bg_color' => $this->options['bot_message_bg_color'] ?? '#212121',
3366 - 'bot_message_font_color' => $this->options['bot_message_font_color'] ?? '#fff',
3367 - 'top_bar_bg_color' => $this->options['top_bar_bg_color'] ?? '#212121',
3368 - 'send_button_font_color' => $this->options['send_button_font_color'] ?? '#212121',
3369 - 'close_button_color' => $this->options['close_button_color'] ?? '#fff',
3370 - 'chatbot_background_color' => $this->options['chatbot_background_color'] ?? '#212121',
3371 - 'chatbot_bg_color' => $this->options['chatbot_bg_color'] ?? '#fff',
3372 - 'icon_color' => $this->options['icon_color'] ?? '#fff',
3373 - 'chat_input_font_color' => $this->options['chat_input_font_color'] ?? '#212121',
3374 - 'chat_persistence_toggle' => $this->options['chat_persistence_toggle'] ?? 'off',
3375 - 'appendWidgetToBody' => $this->options['append_to_body'] ?? 'off', // Example for consistency
763 + // Prepare settings to pass to JavaScript
764 + $style_settings = array(
765 + 'ajax_url' => admin_url('admin-ajax.php'),
766 + 'nonce' => wp_create_nonce('mxchat_chat_nonce'), // Nonce for security
767 + 'rate_limit_message' => $this->options['rate_limit_message'] ?? 'Rate limit exceeded. Please try again later.',
768 + 'appendWidgetToBody' => $this->options['append_to_body'] ?? 'off'
769 + );
3376 770
3377 - 'live_agent_message_bg_color' => $this->options['live_agent_message_bg_color'] ?? '#ffffff',
3378 - 'live_agent_message_font_color' => $this->options['live_agent_message_font_color'] ?? '#333333',
3379 - 'chat_toolbar_toggle' => $this->options['chat_toolbar_toggle'] ?? 'off',
3380 - 'mode_indicator_bg_color' => $this->options['mode_indicator_bg_color'] ?? '#767676',
3381 - 'mode_indicator_font_color' => $this->options['mode_indicator_font_color'] ?? '#ffffff',
3382 - 'toolbar_icon_color' => $this->options['toolbar_icon_color'] ?? '#212121',
771 + // Localize the script with necessary data
772 + wp_localize_script('mxchat-chat-js', 'mxchatChat', $style_settings);
773 + }
3383 774
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 - );
3387 775
3388 - // Pass the settings to the script
3389 - wp_localize_script('mxchat-chat-js', 'mxchatChat', $style_settings);
3390 -}
3391 776
3392 -
3393 -public function mxchat_reset_rate_limits() {
777 + public function mxchat_reset_rate_limits() {
3394 778 global $wpdb;
3395 779
3396 780 // Define a cache key pattern for rate limits
3397 781 $cache_key_pattern = 'mxchat_chat_limit_%';
@@ -3411,8 +795,9 @@
3411 795
3412 796 // Optionally, clear a general cache if you have one
3413 797 wp_cache_delete('mxchat_all_chat_limits', 'options');
3414 798 }
799 +
3415 800
3416 801 private function mxchat_fetch_woocommerce_products() {
3417 802 // Ensure WooCommerce is active
3418 803 if (!class_exists('WooCommerce')) {