Changeset 3311750
- Timestamp:
- 06/15/2025 03:15:07 AM (8 months ago)
- Location:
- mxchat-basic
- Files:
-
- 93 added
- 11 edited
-
tags/2.2.5 (added)
-
tags/2.2.5/admin (added)
-
tags/2.2.5/admin/class-ajax-handler.php (added)
-
tags/2.2.5/admin/class-knowledge-manager.php (added)
-
tags/2.2.5/admin/class-pinecone-manager.php (added)
-
tags/2.2.5/css (added)
-
tags/2.2.5/css/admin-add-ons.css (added)
-
tags/2.2.5/css/admin-style.css (added)
-
tags/2.2.5/css/chat-style.css (added)
-
tags/2.2.5/css/chat-transcripts.css (added)
-
tags/2.2.5/css/content-selector.css (added)
-
tags/2.2.5/css/intent-style.css (added)
-
tags/2.2.5/css/knowledge-style.css (added)
-
tags/2.2.5/images (added)
-
tags/2.2.5/images/Icon-01.svg (added)
-
tags/2.2.5/images/Icon-02.svg (added)
-
tags/2.2.5/images/Icon-03.svg (added)
-
tags/2.2.5/images/Icon-04.svg (added)
-
tags/2.2.5/images/pro-only-dark.png (added)
-
tags/2.2.5/includes (added)
-
tags/2.2.5/includes/class-mxchat-addons.php (added)
-
tags/2.2.5/includes/class-mxchat-admin.php (added)
-
tags/2.2.5/includes/class-mxchat-integrator.php (added)
-
tags/2.2.5/includes/class-mxchat-public.php (added)
-
tags/2.2.5/includes/class-mxchat-user.php (added)
-
tags/2.2.5/includes/class-mxchat-utils.php (added)
-
tags/2.2.5/includes/class-mxchat-word-handler.php (added)
-
tags/2.2.5/includes/pdf-parser (added)
-
tags/2.2.5/includes/pdf-parser/alt_autoload.php (added)
-
tags/2.2.5/includes/pdf-parser/src (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Config.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Document.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Element (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Element.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Element/ElementArray.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Element/ElementBoolean.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Element/ElementDate.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Element/ElementHexa.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Element/ElementMissing.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Element/ElementName.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Element/ElementNull.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Element/ElementNumeric.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Element/ElementString.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Element/ElementStruct.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Element/ElementXRef.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Encoding (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Encoding.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Encoding/AbstractEncoding.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Encoding/EncodingLocator.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Encoding/ISOLatin1Encoding.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Encoding/ISOLatin9Encoding.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Encoding/MacRomanEncoding.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Encoding/PDFDocEncoding.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Encoding/PostScriptGlyphs.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Encoding/StandardEncoding.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Encoding/WinAnsiEncoding.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Exception (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Exception/EmptyPdfException.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Exception/EncodingNotFoundException.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Exception/MissingPdfHeaderException.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Exception/NotImplementedException.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Font (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Font.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Font/FontCIDFontType0.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Font/FontCIDFontType2.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Font/FontTrueType.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Font/FontType0.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Font/FontType1.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Font/FontType3.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Header.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/PDFObject.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Page.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Pages.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/Parser.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/RawData (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/RawData/FilterHelper.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/RawData/RawDataParser.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/XObject (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/XObject/Form.php (added)
-
tags/2.2.5/includes/pdf-parser/src/Smalot/PdfParser/XObject/Image.php (added)
-
tags/2.2.5/js (added)
-
tags/2.2.5/js/activation-script.js (added)
-
tags/2.2.5/js/chat-script.js (added)
-
tags/2.2.5/js/content-selector.js (added)
-
tags/2.2.5/js/knowledge-processing.js (added)
-
tags/2.2.5/js/mxchat-admin.js (added)
-
tags/2.2.5/js/mxchat_transcripts.js (added)
-
tags/2.2.5/languages (added)
-
tags/2.2.5/languages/mxchat.pot (added)
-
tags/2.2.5/mxchat-basic.php (added)
-
tags/2.2.5/readme.txt (added)
-
trunk/admin/class-ajax-handler.php (modified) (2 diffs)
-
trunk/admin/class-knowledge-manager.php (modified) (1 diff)
-
trunk/admin/class-pinecone-manager.php (modified) (3 diffs)
-
trunk/css/chat-style.css (modified) (5 diffs)
-
trunk/includes/class-mxchat-addons.php (modified) (1 diff)
-
trunk/includes/class-mxchat-admin.php (modified) (18 diffs)
-
trunk/includes/class-mxchat-integrator.php (modified) (31 diffs)
-
trunk/js/chat-script.js (modified) (6 diffs)
-
trunk/js/mxchat-admin.js (modified) (1 diff)
-
trunk/mxchat-basic.php (modified) (2 diffs)
-
trunk/readme.txt (modified) (6 diffs)
Legend:
- Unmodified
- Added
- Removed
-
mxchat-basic/trunk/admin/class-ajax-handler.php
r3308005 r3311750 182 182 } 183 183 // Handle toggles 184 else if (strpos($name, 'toggle') !== false || in_array($name, [ 185 'chat_persistence_toggle', 186 'privacy_toggle', 187 'complianz_toggle', 188 'chat_toolbar_toggle', 189 'show_pdf_upload_button', 190 'show_word_upload_button' 191 ])) { 192 //error_log('MXChat Save: Processing toggle: ' . $name); 193 $options[$name] = ($value === 'on') ? 'on' : 'off'; 194 } else { 184 else if (strpos($name, 'toggle') !== false || in_array($name, [ 185 'chat_persistence_toggle', 186 'privacy_toggle', 187 'complianz_toggle', 188 'chat_toolbar_toggle', 189 'show_pdf_upload_button', 190 'show_word_upload_button', 191 'enable_streaming_toggle' // Add this line 192 ])) { 193 //error_log('MXChat Save: Processing toggle: ' . $name); 194 $options[$name] = ($value === 'on') ? 'on' : 'off'; 195 } else { 195 196 //error_log('MXChat Save: Processing standard field: ' . $name); 196 197 // Store all other values directly … … 662 663 } 663 664 664 /**665 * Checks if a value has changed from old to new666 */667 private function mxchat_has_value_changed($old_value, $new_value) {668 // Handle null values669 if ($old_value === null && $new_value === '') {670 return false;671 }672 673 // Handle array values (like additional_popular_questions)674 if (is_array($old_value) && is_array($new_value)) {675 // Convert both to JSON for comparison to handle ordering differences676 return json_encode($old_value) !== json_encode($new_value);677 }678 679 // Handle toggle/checkbox values consistently680 if (in_array($old_value, ['on', '1', 1, true]) && in_array($new_value, ['on', '1', 1, true])) {681 return false;682 }683 if (in_array($old_value, ['off', '0', 0, false, '']) && in_array($new_value, ['off', '0', 0, false, ''])) {684 return false;685 }686 687 // Default direct comparison688 return $old_value !== $new_value;689 }690 691 665 } 692 666 -
mxchat-basic/trunk/admin/class-knowledge-manager.php
r3308065 r3311750 1853 1853 $pinecone_options = get_option('mxchat_pinecone_addon_options', array()); 1854 1854 $pinecone_manager->mxchat_refresh_after_new_content($pinecone_options); 1855 1855 $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1'; 1856 1856 1857 if ($use_pinecone && !empty($pinecone_options['mxchat_pinecone_api_key'])) { 1857 1858 // ONLY check Pinecone if it's enabled -
mxchat-basic/trunk/admin/class-pinecone-manager.php
r3308065 r3311750 76 76 77 77 /** 78 * Get embedding dimensions based on the selected model 79 * ADD THIS NEW FUNCTION 80 */ 81 private function mxchat_get_embedding_dimensions() { 82 $options = get_option('mxchat_options', array()); 83 $selected_model = $options['embedding_model'] ?? 'text-embedding-ada-002'; 84 85 // Define dimensions for different models 86 $model_dimensions = array( 87 'text-embedding-ada-002' => 1536, 88 'text-embedding-3-small' => 1536, 89 'text-embedding-3-large' => 3072, 90 'voyage-2' => 1024, 91 'voyage-large-2' => 1536, 92 'voyage-3-large' => 2048, 93 'gemini-embedding-001' => 1536, 94 ); 95 96 // Check if it's a voyage model with custom dimensions 97 if (strpos($selected_model, 'voyage-3-large') === 0) { 98 $custom_dimensions = $options['voyage_output_dimension'] ?? 2048; 99 return intval($custom_dimensions); 100 } 101 102 // Check if it's a gemini model with custom dimensions 103 if (strpos($selected_model, 'gemini-embedding') === 0) { 104 $custom_dimensions = $options['gemini_output_dimension'] ?? 1536; 105 return intval($custom_dimensions); 106 } 107 108 // Return known dimensions or default to 1536 109 return $model_dimensions[$selected_model] ?? 1536; 110 } 111 112 /** 113 * Generate random unit vector with correct dimensions 114 * ADD THIS NEW FUNCTION 115 */ 116 private function mxchat_generate_random_vector() { 117 $dimensions = $this->mxchat_get_embedding_dimensions(); 118 119 $random_vector = array(); 120 for ($i = 0; $i < $dimensions; $i++) { 121 $random_vector[] = (rand(-1000, 1000) / 1000.0); 122 } 123 124 // Normalize the vector to unit length 125 $magnitude = sqrt(array_sum(array_map(function($x) { return $x * $x; }, $random_vector))); 126 if ($magnitude > 0) { 127 $random_vector = array_map(function($x) use ($magnitude) { return $x / $magnitude; }, $random_vector); 128 } 129 130 return $random_vector; 131 } 132 133 /** 78 134 * Get 1,000 most recent entries from Pinecone (SIMPLE VERSION) 79 135 */ … … 98 154 //error_log('DEBUG: Fetching batch ' . ($pass + 1) . '/5'); 99 155 100 // Generate random vector for similarity search 101 $random_vector = array(); 102 for ($i = 0; $i < 1536; $i++) { 103 $random_vector[] = (rand(-1000, 1000) / 1000.0); 104 } 105 106 // Normalize vector 107 $magnitude = sqrt(array_sum(array_map(function($x) { return $x * $x; }, $random_vector))); 108 if ($magnitude > 0) { 109 $random_vector = array_map(function($x) use ($magnitude) { return $x / $magnitude; }, $random_vector); 110 } 156 // Generate random vector with CORRECT dimensions 157 $random_vector = $this->mxchat_generate_random_vector(); 111 158 112 159 $query_data = array( … … 175 222 } 176 223 } 224 225 /** 226 * Scan Pinecone for processed content (MISSING FUNCTION - ADD THIS) 227 */ 228 public function mxchat_scan_pinecone_for_processed_content($pinecone_options) { 229 //error_log('=== DEBUG: Starting mxchat_scan_pinecone_for_processed_content ==='); 230 231 $api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? ''; 232 $host = $pinecone_options['mxchat_pinecone_host'] ?? ''; 233 234 if (empty($api_key) || empty($host)) { 235 //error_log('DEBUG: Missing API credentials for scanning'); 236 return array(); 237 } 238 239 try { 240 // Use multiple random vectors to get better coverage 241 $all_matches = array(); 242 $seen_ids = array(); 243 244 // Try 3 different random vectors to get better coverage 245 for ($i = 0; $i < 3; $i++) { 246 //error_log('DEBUG: Scanning attempt ' . ($i + 1) . '/3'); 247 248 $query_url = "https://{$host}/query"; 249 250 // Generate random vector with CORRECT dimensions 251 $random_vector = $this->mxchat_generate_random_vector(); 252 253 $query_data = array( 254 'includeMetadata' => true, 255 'includeValues' => false, 256 'topK' => 10000, 257 'vector' => $random_vector 258 ); 259 260 $response = wp_remote_post($query_url, array( 261 'headers' => array( 262 'Api-Key' => $api_key, 263 'Content-Type' => 'application/json' 264 ), 265 'body' => json_encode($query_data), 266 'timeout' => 30 267 )); 268 269 if (is_wp_error($response)) { 270 //error_log('DEBUG: Query attempt ' . ($i + 1) . ' WP error: ' . $response->get_error_message()); 271 continue; 272 } 273 274 $response_code = wp_remote_retrieve_response_code($response); 275 //error_log('DEBUG: Query attempt ' . ($i + 1) . ' response code: ' . $response_code); 276 277 if ($response_code !== 200) { 278 $error_body = wp_remote_retrieve_body($response); 279 //error_log('DEBUG: Query attempt ' . ($i + 1) . ' failed with body: ' . substr($error_body, 0, 500)); 280 continue; 281 } 282 283 $body = wp_remote_retrieve_body($response); 284 $data = json_decode($body, true); 285 286 if (isset($data['matches'])) { 287 //error_log('DEBUG: Query attempt ' . ($i + 1) . ' returned ' . count($data['matches']) . ' matches'); 288 foreach ($data['matches'] as $match) { 289 $match_id = $match['id'] ?? ''; 290 if (!empty($match_id) && !isset($seen_ids[$match_id])) { 291 $all_matches[] = $match; 292 $seen_ids[$match_id] = true; 293 } 294 } 295 } else { 296 //error_log('DEBUG: Query attempt ' . ($i + 1) . ' - no matches key in response'); 297 } 298 } 299 300 //error_log('DEBUG: Total unique matches found: ' . count($all_matches)); 301 302 // Convert matches to processed data format 303 $processed_data = array(); 304 $vector_ids_for_cache = array(); 305 306 foreach ($all_matches as $match) { 307 $metadata = $match['metadata'] ?? array(); 308 $source_url = $metadata['source_url'] ?? ''; 309 $match_id = $match['id'] ?? ''; 310 311 if (!empty($source_url) && !empty($match_id)) { 312 $post_id = url_to_postid($source_url); 313 if ($post_id) { 314 $created_at = $metadata['created_at'] ?? ''; 315 $processed_date = 'Recently'; 316 317 if (!empty($created_at)) { 318 $timestamp = is_numeric($created_at) ? $created_at : strtotime($created_at); 319 if ($timestamp) { 320 $processed_date = human_time_diff($timestamp, current_time('timestamp')) . ' ago'; 321 } 322 } 323 324 $processed_data[$post_id] = array( 325 'db_id' => $match_id, 326 'processed_date' => $processed_date, 327 'url' => $source_url, 328 'source' => 'pinecone', 329 'timestamp' => $timestamp ?? current_time('timestamp') 330 ); 331 332 $vector_ids_for_cache[] = $match_id; 333 } 334 } 335 } 336 337 // Update the vector IDs cache for future use 338 if (!empty($vector_ids_for_cache)) { 339 update_option('mxchat_pinecone_vector_ids_cache', $vector_ids_for_cache); 340 //error_log('DEBUG: Updated vector IDs cache with ' . count($vector_ids_for_cache) . ' IDs'); 341 } 342 343 //error_log('DEBUG: Returning ' . count($processed_data) . ' processed items from scanning'); 344 return $processed_data; 345 346 } catch (Exception $e) { 347 //error_log('DEBUG: Exception in scan_pinecone_for_processed_content: ' . $e->getMessage()); 348 return array(); 349 } 350 } 177 351 178 352 /** -
mxchat-basic/trunk/css/chat-style.css
r3289918 r3311750 317 317 gap: 10px; 318 318 padding: 8px; 319 border-top: 1px solid #e5e5e5;320 319 background: none; 321 320 align-items: end; … … 604 603 padding: 10px; 605 604 } 606 605 .user-message p { 606 margin: 0; 607 } 607 608 .mxchat-popular-questions-title { 608 609 font-size: 14px; … … 619 620 max-height: 200px; 620 621 overflow-y: auto; 622 padding-top: 20px; 621 623 padding-right: 10px; 622 624 padding-bottom: 10px; … … 790 792 clear: both; 791 793 border-radius: 18px 0 18px 18px; 792 border: 1px solid #e2e8ff;793 word-wrap: break-word;794 word-wrap: break-word; 795 border: 1px solid rgba(226, 232, 255, .2); 794 796 } 795 797 … … 840 842 padding: 0.4em 0.5em; 841 843 outline: none; 844 border-radius: 10px; 842 845 } 843 846 -
mxchat-basic/trunk/includes/class-mxchat-addons.php
r3308065 r3311750 153 153 */ 154 154 public function enqueue_styles() { 155 $plugin_version = '2.2. 4';155 $plugin_version = '2.2.5'; 156 156 157 157 wp_enqueue_style( -
mxchat-basic/trunk/includes/class-mxchat-admin.php
r3308065 r3311750 64 64 'voyage_api_key' => '', 65 65 'gemini_api_key' => '', 66 'enable_streaming_toggle' => 'on', 66 67 'embedding_model' => 'text-embedding-ada-002', 67 68 'system_prompt_instructions' => 'You are an AI Chatbot assistant for this website. Your main goal is to assist visitors with questions and provide helpful information. Here are your key guidelines: … … 326 327 <div class="mxchat-pro-content"> 327 328 <h3>🚀 Limited Lifetime Offer: Save 30% on MxChat Pro, Agency, or Agency Plus!</h3> 328 <p>Unlock <strong>unlimited access</strong> to our growing collection of powerful add-ons including Admin AI Assistant (ChatGPT-like experience in your admin panel), Forms Builder, Theme Customizer, WooCommerce, Perplexity, and more – all included with your <strong>lifetime license!</strong></p> </div>329 <p>Unlock <strong>unlimited access</strong> to our growing collection of powerful add-ons including Admin AI Assistant (ChatGPT-like experience in your admin panel), Forms Builder, AI Theme Generator, WooCommerce, Perplexity, and more – all included with your <strong>lifetime license!</strong></p> </div> 329 330 <div class="mxchat-pro-cta"> 330 331 <a href="https://mxchat.ai/" target="_blank" class="mxchat-button"><?php echo esc_html__('Upgrade Today', 'mxchat'); ?></a> … … 425 426 426 427 <div class="tutorial-grid"> 428 429 <div class="tutorial-item"> 430 <h3><?php echo esc_html__('AI Theme Generator Tutorial', 'mxchat'); ?></h3> 431 <div class="video-description"> 432 <p><?php echo esc_html__('Learn how to instantly restyle your chatbot using plain English prompts. The AI Theme Generator lets you generate and apply beautiful designs with real-time previews—no CSS skills needed.', 'mxchat'); ?></p> 433 <a href="https://www.youtube.com/watch?v=rSQDW2qbtRU&t" target="_blank" rel="noopener" class="video-link"> 434 <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span> 435 <?php echo esc_html__('Watch AI Theme Generator Tutorial', 'mxchat'); ?> 436 </a> 437 </div> 438 </div> 439 440 427 441 <div class="tutorial-item"> 428 442 <h3><?php echo esc_html__('MxChat Forms Tutorial', 'mxchat'); ?></h3> … … 1141 1155 1142 1156 public function mxchat_create_prompts_page() { 1143 error_log('=== DEBUG: mxchat_create_prompts_page started ===');1157 //error_log('=== DEBUG: mxchat_create_prompts_page started ==='); 1144 1158 1145 1159 global $wpdb; … … 1160 1174 $per_page = 10; 1161 1175 1162 error_log('DEBUG: Search query: ' . $search_query);1163 error_log('DEBUG: Current page: ' . $current_page);1164 error_log('DEBUG: Per page: ' . $per_page);1176 //error_log('DEBUG: Search query: ' . $search_query); 1177 //error_log('DEBUG: Current page: ' . $current_page); 1178 //error_log('DEBUG: Per page: ' . $per_page); 1165 1179 1166 1180 // ================================ … … 1170 1184 // Get Pinecone settings to determine data source 1171 1185 $pinecone_options = get_option('mxchat_pinecone_addon_options', array()); 1172 error_log('DEBUG: Pinecone options retrieved: ' . print_r($pinecone_options, true));1186 //error_log('DEBUG: Pinecone options retrieved: ' . print_r($pinecone_options, true)); 1173 1187 1174 1188 $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1'; 1175 error_log('DEBUG: use_pinecone decision: ' . ($use_pinecone ? 'TRUE' : 'FALSE'));1189 //error_log('DEBUG: use_pinecone decision: ' . ($use_pinecone ? 'TRUE' : 'FALSE')); 1176 1190 1177 1191 $pinecone_api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? ''; 1178 error_log('DEBUG: Pinecone API key present: ' . (!empty($pinecone_api_key) ? 'YES' : 'NO'));1192 //error_log('DEBUG: Pinecone API key present: ' . (!empty($pinecone_api_key) ? 'YES' : 'NO')); 1179 1193 1180 1194 if ($use_pinecone && !empty($pinecone_options['mxchat_pinecone_api_key'])) { 1181 error_log('DEBUG: Using PINECONE data source');1195 //error_log('DEBUG: Using PINECONE data source'); 1182 1196 // PINECONE DATA SOURCE 1183 1197 $data_source = 'pinecone'; 1184 1198 $pinecone_manager = MxChat_Pinecone_Manager::get_instance(); 1185 error_log('DEBUG: About to call mxchat_fetch_pinecone_records');1199 //error_log('DEBUG: About to call mxchat_fetch_pinecone_records'); 1186 1200 1187 1201 $records = $pinecone_manager->mxchat_fetch_pinecone_records($pinecone_options, $search_query, $current_page, $per_page); … … 1193 1207 $total_pages = ceil($total_records / $per_page); 1194 1208 1195 error_log('DEBUG: Pinecone - total_records: ' . $total_records);1196 error_log('DEBUG: Pinecone - prompts count: ' . count($prompts));1197 error_log('DEBUG: Pinecone - total_pages: ' . $total_pages);1198 error_log('DEBUG: Pinecone - showing_recent_only: ' . ($showing_recent_only ? 'TRUE' : 'FALSE'));1199 error_log('DEBUG: Pinecone - total_in_database: ' . $total_in_database);1209 //error_log('DEBUG: Pinecone - total_records: ' . $total_records); 1210 //error_log('DEBUG: Pinecone - prompts count: ' . count($prompts)); 1211 //error_log('DEBUG: Pinecone - total_pages: ' . $total_pages); 1212 //error_log('DEBUG: Pinecone - showing_recent_only: ' . ($showing_recent_only ? 'TRUE' : 'FALSE')); 1213 //error_log('DEBUG: Pinecone - total_in_database: ' . $total_in_database); 1200 1214 } else { 1201 error_log('DEBUG: Using WORDPRESS DB data source');1215 //error_log('DEBUG: Using WORDPRESS DB data source'); 1202 1216 // WORDPRESS DB DATA SOURCE (your existing logic) 1203 1217 $data_source = 'wordpress'; … … 1217 1231 // Retrieve total number of prompts, considering search filter 1218 1232 $count_query = "SELECT COUNT(*) FROM {$table_name} {$sql_search}"; 1219 error_log('DEBUG: WordPress count query: ' . $count_query);1233 //error_log('DEBUG: WordPress count query: ' . $count_query); 1220 1234 1221 1235 $total_records = $wpdb->get_var($count_query); 1222 error_log('DEBUG: WordPress total_records: ' . $total_records);1236 //error_log('DEBUG: WordPress total_records: ' . $total_records); 1223 1237 1224 1238 $total_pages = ceil($total_records / $per_page); … … 1230 1244 $offset 1231 1245 ); 1232 error_log('DEBUG: WordPress prompts query: ' . $prompts_query);1246 //error_log('DEBUG: WordPress prompts query: ' . $prompts_query); 1233 1247 1234 1248 $prompts = $wpdb->get_results($prompts_query); 1235 error_log('DEBUG: WordPress prompts count: ' . count($prompts));1236 } 1237 1238 error_log('DEBUG: Final data_source: ' . $data_source);1239 error_log('DEBUG: Final total_records: ' . $total_records);1240 error_log('DEBUG: Final prompts count: ' . count($prompts));1241 error_log('DEBUG: Final showing_recent_only: ' . ($showing_recent_only ? 'TRUE' : 'FALSE'));1242 error_log('DEBUG: Final total_in_database: ' . $total_in_database);1249 //error_log('DEBUG: WordPress prompts count: ' . count($prompts)); 1250 } 1251 1252 //error_log('DEBUG: Final data_source: ' . $data_source); 1253 //error_log('DEBUG: Final total_records: ' . $total_records); 1254 //error_log('DEBUG: Final prompts count: ' . count($prompts)); 1255 //error_log('DEBUG: Final showing_recent_only: ' . ($showing_recent_only ? 'TRUE' : 'FALSE')); 1256 //error_log('DEBUG: Final total_in_database: ' . $total_in_database); 1243 1257 1244 1258 // Add the pagination links generation here … … 1273 1287 || ($sitemap_status && ($sitemap_status['status'] === 'processing' || $sitemap_status['status'] === 'error')); 1274 1288 1275 error_log('=== DEBUG: mxchat_create_prompts_page data preparation completed ===');1289 //error_log('=== DEBUG: mxchat_create_prompts_page data preparation completed ==='); 1276 1290 1277 1291 ?> … … 1369 1383 </button> 1370 1384 1371 <!-- PDF Import Option -->1372 <button type="button" class="mxchat-import-box" data-option="pdf" data-placeholder="<?php esc_attr_e('Enter PDF URL here', 'mxchat'); ?>" data-type="pdf">1373 <div class="mxchat-import-icon">1374 <span class="dashicons dashicons-media-document"></span>1375 </div>1376 <div class="mxchat-import-content">1377 <h4><?php esc_html_e('PDF Import', 'mxchat'); ?></h4>1378 <p><?php esc_html_e('Import knowledge from PDF documents.', 'mxchat'); ?></p>1379 </div>1380 </button>1381 1382 1385 <!-- Sitemap Import Option --> 1383 1386 <button type="button" class="mxchat-import-box" data-option="sitemap" data-placeholder="<?php esc_attr_e('Enter sitemap URL here', 'mxchat'); ?>" data-type="sitemap"> … … 1410 1413 <h4><?php esc_html_e('Direct Content', 'mxchat'); ?></h4> 1411 1414 <p><?php esc_html_e('Submit content to be vectorized.', 'mxchat'); ?></p> 1415 </div> 1416 </button> 1417 1418 <!-- PDF Import Option --> 1419 <button type="button" class="mxchat-import-box" data-option="pdf" data-placeholder="<?php esc_attr_e('Enter PDF URL here', 'mxchat'); ?>" data-type="pdf"> 1420 <div class="mxchat-import-icon"> 1421 <span class="dashicons dashicons-media-document"></span> 1422 </div> 1423 <div class="mxchat-import-content"> 1424 <h4><?php esc_html_e('PDF Import', 'mxchat'); ?></h4> 1425 <p><?php esc_html_e('Import knowledge from PDF documents.', 'mxchat'); ?></p> 1412 1426 </div> 1413 1427 </button> … … 1694 1708 <div class="mxchat-info-banner pinecone" style="display: flex; align-items: center; gap: 8px; padding: 12px 16px; margin-bottom: 20px; border-radius: 6px; font-size: 14px; background: #e8f5e8; border-left: 4px solid #4caf50; color: #2e7d2e;"> 1695 1709 <span class="dashicons dashicons-cloud"></span> 1696 <span><?php esc_html_e(' Data is stored in Pinecone vector database for enhanced AI performance. Refresh page after adding content to database.', 'mxchat'); ?></span>1697 </div>1710 <span><?php esc_html_e('Refresh page to see new content, but wait 5-10 seconds first! Refreshing too early means content won\'t show and you\'ll need to refresh again.', 'mxchat'); ?></span> 1711 </div> 1698 1712 <?php else : ?> 1699 1713 <div class="mxchat-info-banner wordpress" style="display: flex; align-items: center; gap: 8px; padding: 12px 16px; margin-bottom: 20px; border-radius: 6px; font-size: 14px; background: #fff3e0; border-left: 4px solid #ff9800; color: #e65100;"> 1700 1714 <span class="dashicons dashicons-database-view"></span> 1701 <span><?php esc_html_e(' Data is stored in WordPress database. Refresh page after adding content to database.', 'mxchat'); ?></span>1715 <span><?php esc_html_e('Refresh page to see new content, but wait 5-10 seconds first! Refreshing too early means content won\'t show and you\'ll need to refresh again.', 'mxchat'); ?></span> 1702 1716 </div> 1703 1717 <?php endif; ?> … … 3329 3343 ) 3330 3344 ); 3345 3346 add_settings_field( 3347 'enable_streaming_toggle', 3348 esc_html__('Enable Streaming', 'mxchat'), 3349 array($this, 'enable_streaming_toggle_callback'), 3350 'mxchat-chatbot', 3351 'mxchat_chatbot_section', // Same section as your working toggle 3352 array( 3353 'class' => 'mxchat-setting-row streaming-setting', 3354 'style' => 'display: none;' // Hidden by default, shown when OpenAI/Claude selected 3355 ) 3356 ); 3357 3331 3358 3332 3359 add_settings_field( … … 3337 3364 'mxchat_chatbot_section' 3338 3365 ); 3339 3340 // Add the settings field 3366 3341 3367 add_settings_field( 3342 3368 'embedding_model', … … 4316 4342 } 4317 4343 4344 public function enable_streaming_toggle_callback() { 4345 // Get value from options array, default to 'on' 4346 $enabled = isset($this->options['enable_streaming_toggle']) ? $this->options['enable_streaming_toggle'] : 'on'; 4347 $checked = ($enabled === 'on') ? 'checked' : ''; 4348 4349 echo '<label class="toggle-switch">'; 4350 echo sprintf( 4351 '<input type="checkbox" id="enable_streaming_toggle" name="enable_streaming_toggle" value="on" %s />', 4352 esc_attr($checked) 4353 ); 4354 echo '<span class="slider"></span>'; // Remove 'round' class to match working toggle 4355 echo '</label>'; 4356 echo '<p class="description">' . 4357 esc_html__('Enable real-time streaming responses for supported models (OpenAI and Claude). When disabled, responses will load all at once.', 'mxchat') . 4358 '</p>'; 4359 } 4318 4360 // Callback function for embedding model selection 4319 4361 public function embedding_model_callback() { … … 5447 5489 public function mxchat_enqueue_admin_assets() { 5448 5490 // Get plugin version (define this in your main plugin file) 5449 $version = defined('MXCHAT_VERSION') ? MXCHAT_VERSION : '2.2. 4';5491 $version = defined('MXCHAT_VERSION') ? MXCHAT_VERSION : '2.2.5'; 5450 5492 5451 5493 // Use file modification time for development (remove in production) … … 5633 5675 $new_input['claude_api_key'] = sanitize_text_field($input['claude_api_key']); 5634 5676 } 5677 5678 if (isset($input['enable_streaming_toggle'])) { 5679 $new_input['enable_streaming_toggle'] = ($input['enable_streaming_toggle'] === 'on') ? 'on' : 'off'; 5680 } else { 5681 // If checkbox not checked, it won't be in $input, so set to 'off' 5682 $new_input['enable_streaming_toggle'] = 'off'; 5683 } 5684 5635 5685 5636 5686 if (isset($input['deepseek_api_key'])) { -
mxchat-basic/trunk/includes/class-mxchat-integrator.php
r3308065 r3311750 88 88 add_action('wp_ajax_nopriv_mxchat_check_email_provided', [$this, 'mxchat_check_email_provided']); 89 89 add_action('wp_ajax_mxchat_check_email_provided', [$this, 'mxchat_check_email_provided']); 90 91 add_action('wp_ajax_mxchat_stream_chat', array($this, 'mxchat_handle_chat_request')); 92 add_action('wp_ajax_nopriv_mxchat_stream_chat', array($this, 'mxchat_handle_chat_request')); 90 93 } 91 94 … … 122 125 wp_die(); 123 126 } 124 private function mxchat_fetch_conversation_history_for_ajax($session_id) {125 $history = get_option("mxchat_history_{$session_id}", []); // Retrieve stored history based on session ID126 $formatted_history = [];127 128 // Format the history to align with the expected structure for OpenAI129 foreach ($history as $entry) {130 $formatted_history[] = [131 'role' => $entry['role'], // Ensure this matches 'user' or 'assistant'132 'content' => $entry['content']133 ];134 }135 136 return $formatted_history;137 }138 139 127 140 128 private function mxchat_fetch_conversation_history_for_ai($session_id) { … … 553 541 } 554 542 555 // Add this to your plugin's main PHP file556 public function mxchat_check_new_messages() {557 if (!isset($_POST['session_id']) || !isset($_POST['last_seen_id'])) {558 wp_send_json_error(['message' => 'Missing required parameters']);559 wp_die();560 }561 562 $session_id = sanitize_text_field($_POST['session_id']);563 $last_seen_id = sanitize_text_field($_POST['last_seen_id']);564 565 // Get chat history566 $history = get_option("mxchat_history_{$session_id}", []);567 568 if (empty($history)) {569 wp_send_json_success([570 'hasNewMessages' => false,571 'new_messages' => []572 ]);573 wp_die();574 }575 576 // Filter new messages577 $new_messages = array_filter($history, function($message) use ($last_seen_id) {578 return isset($message['id']) && $message['id'] > $last_seen_id;579 });580 581 // Sort by ID to ensure proper order582 usort($new_messages, function($a, $b) {583 return $a['id'] <=> $b['id'];584 });585 586 wp_send_json_success([587 'hasNewMessages' => !empty($new_messages),588 'new_messages' => array_values($new_messages),589 'latestMessageId' => end($new_messages)['id'] ?? $last_seen_id590 ]);591 wp_die();592 }593 594 543 public function mxchat_handle_chat_request() { 595 544 global $wpdb; 545 546 // NEW: Check if this is a streaming request 547 $is_streaming = isset($_POST['action']) && $_POST['action'] === 'mxchat_stream_chat'; 548 549 // NEW: Set streaming headers if needed 550 if ($is_streaming) { 551 // Disable output buffering 552 while (ob_get_level()) { 553 ob_end_clean(); 554 } 555 556 // Set headers for SSE 557 header('Content-Type: text/event-stream'); 558 header('Cache-Control: no-cache'); 559 header('Connection: keep-alive'); 560 header('X-Accel-Buffering: no'); 561 } 596 562 597 563 … … 723 689 } 724 690 725 // Return the response 691 // ALWAYS send as regular JSON response for add-on results 692 // This ensures image galleries, product cards, etc. display properly 726 693 wp_send_json([ 727 694 'text' => $pre_processed_result['text'], … … 739 706 // Add the email to Loops 740 707 $this->add_email_to_loops($message); 741 708 742 709 // Send success response 743 710 $response_message = $this->options['email_capture_response'] ?? 744 711 esc_html__('Thank you! Your coupon is on the way!', 'mxchat'); 745 712 713 // Clear streaming headers if they were set 714 if ($is_streaming) { 715 header_remove('Content-Type'); 716 header_remove('Cache-Control'); 717 header_remove('Connection'); 718 header_remove('X-Accel-Buffering'); 719 header('Content-Type: application/json'); 720 } 721 746 722 wp_send_json([ 747 723 'success' => true, … … 927 903 // Step 3: Handle the intent result appropriately 928 904 if ($intent_result !== false) { 929 // The intent was matched and handled930 // error_log("Intent was matched and handled.");905 // Intent was matched - ALWAYS send as JSON response, never streaming 906 // This ensures image galleries, product cards, and other intent responses work properly 931 907 932 908 if (is_array($intent_result) && (isset($intent_result['text']) || isset($intent_result['html']))) { 933 909 // Intent returned a direct response array 934 //error_log("Intent returned a direct response.");935 910 $response_data = [ 936 911 'text' => $intent_result['text'] ?? '', … … 939 914 ]; 940 915 916 // Clear streaming headers if they were set 917 if ($is_streaming) { 918 header_remove('Content-Type'); 919 header_remove('Cache-Control'); 920 header_remove('Connection'); 921 header_remove('X-Accel-Buffering'); 922 header('Content-Type: application/json'); 923 } 924 941 925 wp_send_json($response_data); 942 926 wp_die(); … … 944 928 else if ($intent_result === true && (!empty($this->fallbackResponse['text']) || !empty($this->fallbackResponse['html']))) { 945 929 // Intent returned true and set fallbackResponse 946 //error_log("Intent returned true with fallbackResponse set.");947 930 $response_data = [ 948 931 'text' => $this->fallbackResponse['text'] ?? '', … … 951 934 ]; 952 935 936 // Clear streaming headers if they were set 937 if ($is_streaming) { 938 header_remove('Content-Type'); 939 header_remove('Cache-Control'); 940 header_remove('Connection'); 941 header_remove('X-Accel-Buffering'); 942 header('Content-Type: application/json'); 943 } 944 953 945 wp_send_json($response_data); 954 946 wp_die(); 955 947 } 956 957 // Intent was matched but no usable response was provided958 // This shouldn't happen with proper intent implementation959 //error_log("Warning: Intent matched but no response provided.");960 948 } 961 949 … … 1039 1027 $context_content = apply_filters('mxchat_prepare_context', $context_content, $session_id); 1040 1028 1041 // Generate the response using the full context1042 1029 $response = $this->mxchat_generate_response( 1043 1030 $context_content, … … 1047 1034 $this->options['deepseek_api_key'], 1048 1035 $this->options['gemini_api_key'], 1049 $conversation_history 1036 $conversation_history, 1037 $is_streaming, 1038 $session_id 1050 1039 ); 1040 1041 // Handle streaming vs non-streaming responses 1042 if ($is_streaming) { 1043 // For streaming, the response is already sent and saved by the streaming functions 1044 wp_die(); 1045 } 1051 1046 1052 1047 // Check if the response is an error array … … 1487 1482 } 1488 1483 } 1489 /**1490 * Format search results into a natural text summary.1491 *1492 * @since 1.0.01493 * @param array $results The search results from the API.1494 * @param string $query The original search query.1495 * @return string The text summary of the top results.1496 */1497 private function format_search_results( $results, $query ) {1498 $summary = sprintf(1499 esc_html__( 'Here are the most relevant results for "%s":', 'mxchat' ),1500 esc_html( $query )1501 ) . "\n\n";1502 1503 $max_results = min( count( $results ), 3 );1504 for ( $i = 0; $i < $max_results; $i++ ) {1505 $result = $results[ $i ];1506 $title = isset( $result['title'] ) ? wp_strip_all_tags( $result['title'] ) : '';1507 $description = isset( $result['description'] ) ? wp_strip_all_tags( $result['description'] ) : '';1508 1509 // Append title and description to the summary1510 $summary .= sprintf(1511 "%s\n%s\n\n",1512 esc_html( $title ),1513 esc_html( $description )1514 );1515 }1516 1517 return $summary;1518 }1519 1520 /**1521 * Generate HTML markup for search results.1522 *1523 * @since 1.0.01524 * @param array $results The search results from the API.1525 * @param string $query The user-refined query.1526 * @return string The HTML markup for displaying the results.1527 */1528 private function generate_search_results_html( $results, $query ) {1529 ob_start();1530 ?>1531 <div class="mxchat-search-results">1532 <?php foreach ( $results as $result ) :1533 $title = isset( $result['title'] ) ? wp_strip_all_tags( $result['title'] ) : '';1534 $url = isset( $result['url'] ) ? esc_url( $result['url'] ) : '#';1535 $description = isset( $result['description'] ) ? wp_strip_all_tags( $result['description'] ) : '';1536 $favicon = isset( $result['meta_url']['favicon'] ) ? esc_url( $result['meta_url']['favicon'] ) : '';1537 $thumbnail = isset( $result['thumbnail']['src'] ) ? esc_url( $result['thumbnail']['src'] ) : '';1538 $domain = parse_url( $url, PHP_URL_HOST );1539 ?>1540 <div class="mxchat-search-item">1541 <div class="mxchat-search-header">1542 <?php if ( $favicon ) : ?>1543 <img1544 src="<?php echo esc_url( $favicon ); ?>"1545 class="mxchat-site-icon"1546 alt="<?php echo esc_attr__( 'Site icon', 'mxchat' ); ?>"1547 width="16"1548 height="16"1549 />1550 <?php endif; ?>1551 <div class="mxchat-site-url"><?php echo esc_html( $domain ); ?></div>1552 </div>1553 1554 <div class="mxchat-search-content">1555 <h3 class="mxchat-search-title">1556 <a href="<?php echo esc_url( $url ); ?>"1557 target="_blank"1558 rel="noopener noreferrer"1559 >1560 <?php echo esc_html( $title ); ?>1561 </a>1562 </h3>1563 1564 <?php if ( $thumbnail ) : ?>1565 <div class="mxchat-search-thumbnail">1566 <img1567 src="<?php echo esc_url( $thumbnail ); ?>"1568 alt="<?php echo esc_attr__( 'Thumbnail image', 'mxchat' ); ?>"1569 loading="lazy"1570 />1571 </div>1572 <?php endif; ?>1573 1574 <div class="mxchat-search-description">1575 <?php echo esc_html( $description ); ?>1576 </div>1577 </div>1578 </div>1579 <?php endforeach; ?>1580 </div>1581 <?php1582 return ob_get_clean();1583 }1584 1585 1484 1586 1485 //very good … … 1954 1853 return sanitize_text_field($user_query); 1955 1854 } 1956 1957 1958 1959 private function find_product_in_message($message) {1960 global $wpdb;1961 1962 // Get embedding for the search query1963 $query_embedding = $this->mxchat_generate_embedding($message, $this->options['api_key']);1964 1965 // Check if embedding generation returned an error1966 if (is_array($query_embedding) && isset($query_embedding['error'])) {1967 $error_message = $query_embedding['error'];1968 $error_code = $query_embedding['error_code'] ?? 'embedding_error';1969 1970 //error_log("Product search embedding error: $error_message (Code: $error_code)");1971 1972 // Set a user-friendly fallback response1973 $this->fallbackResponse['text'] = esc_html__("I'm having trouble processing your product search. Please try again later or contact support if this persists.", 'mxchat');1974 1975 // Also store the technical error for admin users1976 $this->fallbackResponse['admin_error'] = $error_message;1977 $this->fallbackResponse['error_code'] = $error_code;1978 1979 return null;1980 }1981 1982 // Check if embedding is valid1983 if (!is_array($query_embedding) || empty($query_embedding)) {1984 //error_log("Failed to generate embedding for product search");1985 $this->fallbackResponse['text'] = esc_html__("I couldn't process your product search. Please try again with different wording.", 'mxchat');1986 return null;1987 }1988 1989 // Get relevant content as string1990 $relevant_content = $this->mxchat_find_relevant_products($query_embedding);1991 if (empty($relevant_content)) {1992 // Return null to indicate no results and set fallback response1993 $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');1994 return null;1995 }1996 1997 1998 // Extract product URLs from the content1999 preg_match_all('/https?:\/\/[^\s<>"\']+?\/product\/[^\s<>"\']+/', $relevant_content, $matches);2000 2001 if (!empty($matches[0])) {2002 // Try each URL found2003 foreach ($matches[0] as $url) {2004 // Clean the URL2005 $url = rtrim($url, '/."\']');2006 2007 // Get the product slug2008 $path = parse_url($url, PHP_URL_PATH);2009 $slug = basename(rtrim($path, '/'));2010 2011 // Find product by slug2012 $args = array(2013 'post_type' => 'product',2014 'post_status' => 'publish',2015 'name' => $slug,2016 'posts_per_page' => 12017 );2018 2019 $products = get_posts($args);2020 2021 if (!empty($products)) {2022 $product_id = $products[0]->ID;2023 $product = wc_get_product($product_id);2024 2025 if ($product && $product->is_purchasable()) {2026 return $product_id;2027 }2028 }2029 }2030 }2031 2032 // Fallback: Look for product names in the content2033 $products = wc_get_products([2034 'status' => 'publish',2035 'limit' => -1,2036 'return' => 'all'2037 ]);2038 2039 foreach ($products as $product) {2040 $name = $product->get_name();2041 if (stripos($relevant_content, $name) !== false) {2042 if ($product->is_purchasable()) {2043 return $product->get_id();2044 }2045 }2046 }2047 2048 // If no product is found after all checks, set the fallback response2049 $this->fallbackResponse['text'] = esc_html__("I couldn't find any relevant products based on your query. Try to be more specific", 'mxchat');2050 return null;2051 }2052 2053 // New method to handle intent responses2054 private function generate_intent_response($context_content, $session_id) {2055 // Convert the context array to a structured string for the AI2056 $context_string = $this->format_intent_context($context_content);2057 // Generate AI response using the context2058 $response = $this->mxchat_generate_response(2059 $context_string,2060 $this->options['api_key'],2061 $this->options['xai_api_key'],2062 $this->options['claude_api_key'],2063 $this->options['deepseek_api_key'],2064 $this->options['gemini_api_key'], // Added Gemini API key2065 $this->mxchat_fetch_conversation_history_for_ai($session_id)2066 );2067 $this->fallbackResponse['text'] = $response;2068 return true;2069 }2070 2071 // Helper method to format intent context2072 private function format_intent_context($context) {2073 $context_string = esc_html__("INTENT CONTEXT:\n", 'mxchat');2074 2075 switch ($context['intent']) {2076 case 'add_to_cart':2077 if ($context['status'] === 'success') {2078 $context_string .= esc_html__("Action: Successfully added product to cart\n", 'mxchat');2079 $context_string .= sprintf(esc_html__("Product: %s\n", 'mxchat'), $context['product']['name']);2080 $context_string .= esc_html__("Available actions: ", 'mxchat') . implode(', ', $context['available_actions']) . "\n";2081 $context_string .= sprintf(esc_html__("Cart URL: %s\n", 'mxchat'), $context['cart_url']);2082 $context_string .= esc_html__("\nPlease inform the user of the successful addition and their available options.", 'mxchat');2083 } else {2084 $context_string .= esc_html__("Action: Failed to add product to cart\n", 'mxchat');2085 $context_string .= sprintf(esc_html__("Reason: %s\n", 'mxchat'), $context['reason']);2086 switch ($context['reason']) {2087 case 'woocommerce_not_available':2088 $context_string .= esc_html__("\nPlease inform the user that shopping features are not available.", 'mxchat');2089 break;2090 case 'no_product_context':2091 $context_string .= esc_html__("\nPlease ask the user to specify which product they want to add.", 'mxchat');2092 break;2093 case 'product_not_found':2094 $context_string .= esc_html__("\nPlease inform the user that the product couldn't be found and ask them to try again.", 'mxchat');2095 break;2096 case 'add_to_cart_failed':2097 $context_string .= esc_html__("\nPlease apologize to the user and suggest they try again or ask for assistance.", 'mxchat');2098 break;2099 }2100 }2101 break;2102 }2103 2104 return $context_string;2105 }2106 2107 1855 2108 1856 //very good … … 2791 2539 if (isset($data['type']) && $data['type'] === 'url_verification') { 2792 2540 //error_log('Slack URL verification challenge: ' . $data['challenge']); 2793 // Return just the challenge string, not wrapped in an array2794 2541 return new WP_REST_Response($data['challenge'], 200, ['Content-Type' => 'text/plain']); 2542 } 2543 2544 // IMPORTANT: Handle Slack's event deduplication 2545 if (isset($data['event_id'])) { 2546 $event_id = $data['event_id']; 2547 $processed_events = get_transient('mxchat_slack_events') ?: []; 2548 2549 // Check if we've already processed this event 2550 if (in_array($event_id, $processed_events)) { 2551 //error_log("Duplicate event detected: $event_id"); 2552 return new WP_REST_Response(['ok' => true]); 2553 } 2554 2555 // Add this event to processed list 2556 $processed_events[] = $event_id; 2557 // Keep only last 100 events to prevent memory issues 2558 if (count($processed_events) > 100) { 2559 $processed_events = array_slice($processed_events, -100); 2560 } 2561 // Store for 1 hour 2562 set_transient('mxchat_slack_events', $processed_events, HOUR_IN_SECONDS); 2795 2563 } 2796 2564 … … 2799 2567 $event = $data['event']; 2800 2568 2801 // Skip bot messages and messages with subtypes 2569 // Skip bot messages and messages with subtypes (like bot_message) 2802 2570 if (isset($event['bot_id']) || isset($event['subtype'])) { 2803 2571 return new WP_REST_Response(['ok' => true]); 2804 2572 } 2805 2573 2574 // Additional check: Skip if this is a threaded reply to our confirmation 2575 if (isset($event['thread_ts']) && $event['thread_ts'] !== $event['ts']) { 2576 return new WP_REST_Response(['ok' => true]); 2577 } 2578 2806 2579 $channel_id = $event['channel']; 2807 2580 $message_text = $event['text'] ?? ''; 2581 $message_ts = $event['ts'] ?? ''; 2808 2582 2809 2583 // Find session ID by looking for matching channel … … 2821 2595 $session_id = str_replace('mxchat_channel_', '', $session_option); 2822 2596 2597 // Create a unique key for this specific message 2598 $message_key = md5($session_id . $message_ts . $message_text); 2599 $processed_messages = get_transient('mxchat_processed_messages_' . $session_id) ?: []; 2600 2601 // Check if we've already processed this exact message 2602 if (in_array($message_key, $processed_messages)) { 2603 //error_log("Duplicate message detected for session $session_id"); 2604 return new WP_REST_Response(['ok' => true]); 2605 } 2606 2607 // Add to processed messages 2608 $processed_messages[] = $message_key; 2609 // Keep only last 50 messages per session 2610 if (count($processed_messages) > 50) { 2611 $processed_messages = array_slice($processed_messages, -50); 2612 } 2613 set_transient('mxchat_processed_messages_' . $session_id, $processed_messages, HOUR_IN_SECONDS); 2614 2823 2615 // Save the agent message 2824 2616 $this->mxchat_save_chat_message($session_id, 'agent', $message_text); 2825 2617 2826 // Send confirmation back to Slack 2618 // Send confirmation back to Slack (only once) 2827 2619 $slack_bot_token = $this->options['live_agent_bot_token'] ?? ''; 2828 2620 if (!empty($slack_bot_token)) { 2829 wp_remote_post('https://slack.com/api/chat.postMessage', [ 2830 'headers' => [ 2831 'Content-Type' => 'application/json', 2832 'Authorization' => 'Bearer ' . $slack_bot_token 2833 ], 2834 'body' => json_encode([ 2835 'channel' => $channel_id, 2836 'text' => "✅ _Message sent to user_", 2837 'thread_ts' => $event['ts'] // Reply in thread 2838 ]) 2839 ]); 2621 // Use a transient to prevent duplicate confirmations 2622 $confirm_key = 'mxchat_confirm_' . $message_key; 2623 if (!get_transient($confirm_key)) { 2624 wp_remote_post('https://slack.com/api/chat.postMessage', [ 2625 'headers' => [ 2626 'Content-Type' => 'application/json', 2627 'Authorization' => 'Bearer ' . $slack_bot_token 2628 ], 2629 'body' => json_encode([ 2630 'channel' => $channel_id, 2631 'text' => "✅ _Message sent to user_", 2632 'thread_ts' => $event['ts'] // Reply in thread 2633 ]) 2634 ]); 2635 // Set transient to prevent duplicate confirmations 2636 set_transient($confirm_key, true, 300); // 5 minutes 2637 } 2840 2638 } 2841 2639 } … … 2844 2642 return new WP_REST_Response(['ok' => true]); 2845 2643 } 2846 2847 2644 2848 2645 // For the word upload handler … … 3521 3318 } 3522 3319 3523 private function mxchat_generate_response($relevant_content, $api_key, $xai_api_key, $claude_api_key, $deepseek_api_key, $gemini_api_key, $conversation_history ) {3320 private function mxchat_generate_response($relevant_content, $api_key, $xai_api_key, $claude_api_key, $deepseek_api_key, $gemini_api_key, $conversation_history, $streaming = false, $session_id = '') { 3524 3321 try { 3525 3322 if (!$relevant_content) { … … 3566 3363 ]; 3567 3364 } 3568 $response = $this->mxchat_generate_response_claude( 3569 $selected_model, 3570 $claude_api_key, 3571 $conversation_history, 3572 $relevant_content 3573 ); 3365 if ($streaming) { 3366 return $this->mxchat_generate_response_claude_stream( 3367 $selected_model, 3368 $claude_api_key, 3369 $conversation_history, 3370 $relevant_content, 3371 $session_id 3372 ); 3373 } else { 3374 $response = $this->mxchat_generate_response_claude( 3375 $selected_model, 3376 $claude_api_key, 3377 $conversation_history, 3378 $relevant_content 3379 ); 3380 } 3574 3381 break; 3575 3382 … … 3605 3412 3606 3413 case 'gpt': 3414 case 'o1': 3607 3415 if (empty($api_key)) { 3608 3416 return [ … … 3611 3419 ]; 3612 3420 } 3613 $response = $this->mxchat_generate_response_openai( 3614 $selected_model, 3615 $api_key, 3616 $conversation_history, 3617 $relevant_content 3618 ); 3421 if ($streaming) { 3422 return $this->mxchat_generate_response_openai_stream( 3423 $selected_model, 3424 $api_key, 3425 $conversation_history, 3426 $relevant_content, 3427 $session_id 3428 ); 3429 } else { 3430 $response = $this->mxchat_generate_response_openai( 3431 $selected_model, 3432 $api_key, 3433 $conversation_history, 3434 $relevant_content 3435 ); 3436 } 3619 3437 break; 3620 3438 … … 3627 3445 ]; 3628 3446 } 3629 $response = $this->mxchat_generate_response_openai( 3630 $selected_model, 3631 $api_key, 3632 $conversation_history, 3633 $relevant_content 3634 ); 3447 if ($streaming) { 3448 return $this->mxchat_generate_response_openai_stream( 3449 $selected_model, 3450 $api_key, 3451 $conversation_history, 3452 $relevant_content, 3453 $session_id 3454 ); 3455 } else { 3456 $response = $this->mxchat_generate_response_openai( 3457 $selected_model, 3458 $api_key, 3459 $conversation_history, 3460 $relevant_content 3461 ); 3462 } 3635 3463 break; 3636 3464 } … … 3649 3477 'error_code' => 'system_exception', 3650 3478 'exception_details' => $e->getMessage() 3479 ]; 3480 } 3481 } 3482 private function mxchat_generate_response_claude($selected_model, $claude_api_key, $conversation_history, $relevant_content) { 3483 // Get system prompt instructions from options 3484 $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : ''; 3485 3486 // Clean and validate conversation history 3487 foreach ($conversation_history as &$message) { 3488 // Convert bot and agent roles to assistant 3489 if ($message['role'] === 'bot' || $message['role'] === 'agent') { 3490 $message['role'] = 'assistant'; 3491 } 3492 3493 // Remove unsupported roles - Claude only supports 'assistant' and 'user' 3494 if (!in_array($message['role'], ['assistant', 'user'])) { 3495 $message['role'] = 'user'; 3496 } 3497 3498 // Ensure content field exists 3499 if (!isset($message['content']) || empty($message['content'])) { 3500 $message['content'] = ''; 3501 } 3502 3503 // Remove any unsupported fields 3504 $message = array_intersect_key($message, array_flip(['role', 'content'])); 3505 } 3506 3507 // Add relevant content as the latest user message 3508 $conversation_history[] = [ 3509 'role' => 'user', 3510 'content' => $relevant_content 3511 ]; 3512 3513 // Build request body 3514 $body = json_encode([ 3515 'model' => $selected_model, 3516 'max_tokens' => 1000, 3517 'temperature' => 0.8, 3518 'messages' => $conversation_history, 3519 'system' => $system_prompt_instructions 3520 ]); 3521 3522 // Set up API request 3523 $args = [ 3524 'body' => $body, 3525 'headers' => [ 3526 'Content-Type' => 'application/json', 3527 'x-api-key' => $claude_api_key, 3528 'anthropic-version' => '2023-06-01' 3529 ], 3530 'timeout' => 60, 3531 'redirection' => 5, 3532 'blocking' => true, 3533 'httpversion' => '1.0', 3534 'sslverify' => true, 3535 ]; 3536 3537 // Make API request 3538 $response = wp_remote_post('https://api.anthropic.com/v1/messages', $args); 3539 3540 // Check for WordPress errors 3541 if (is_wp_error($response)) { 3542 //error_log("Claude API request error: " . $response->get_error_message()); 3543 return "Sorry, there was an error connecting to the API."; 3544 } 3545 3546 // Check HTTP response code 3547 $http_code = wp_remote_retrieve_response_code($response); 3548 if ($http_code !== 200) { 3549 $error_body = wp_remote_retrieve_body($response); 3550 //error_log("Claude API HTTP error: " . $http_code . " - " . $error_body); 3551 3552 // Try to extract error message from response 3553 $error_data = json_decode($error_body, true); 3554 $error_message = isset($error_data['error']['message']) ? 3555 $error_data['error']['message'] : 3556 "HTTP error " . $http_code; 3557 3558 return "Sorry, the API returned an error: " . $error_message; 3559 } 3560 3561 // Parse response 3562 $response_body = json_decode(wp_remote_retrieve_body($response), true); 3563 3564 // Check for JSON decode errors 3565 if (json_last_error() !== JSON_ERROR_NONE) { 3566 //error_log("Claude API JSON decode error: " . json_last_error_msg()); 3567 return "Sorry, there was an error processing the API response."; 3568 } 3569 3570 // Extract and validate response content 3571 if (isset($response_body['content']) && 3572 is_array($response_body['content']) && 3573 !empty($response_body['content']) && 3574 isset($response_body['content'][0]['text'])) { 3575 return trim($response_body['content'][0]['text']); 3576 } 3577 3578 // Log unexpected response format 3579 //error_log("Claude API unexpected response format: " . print_r($response_body, true)); 3580 return "Sorry, I received an unexpected response format from the API."; 3581 } 3582 private function mxchat_generate_response_claude_stream($selected_model, $claude_api_key, $conversation_history, $relevant_content, $session_id) { 3583 try { 3584 // Get system prompt instructions from options 3585 $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : ''; 3586 3587 // Ensure conversation_history is an array 3588 if (!is_array($conversation_history)) { 3589 $conversation_history = array(); 3590 } 3591 3592 // Clean and validate conversation history 3593 foreach ($conversation_history as &$message) { 3594 // Convert bot and agent roles to assistant 3595 if ($message['role'] === 'bot' || $message['role'] === 'agent') { 3596 $message['role'] = 'assistant'; 3597 } 3598 3599 // Remove unsupported roles - Claude only supports 'assistant' and 'user' 3600 if (!in_array($message['role'], ['assistant', 'user'])) { 3601 $message['role'] = 'user'; 3602 } 3603 3604 // Ensure content field exists 3605 if (!isset($message['content']) || empty($message['content'])) { 3606 $message['content'] = ''; 3607 } 3608 3609 // Remove any unsupported fields 3610 $message = array_intersect_key($message, array_flip(['role', 'content'])); 3611 } 3612 3613 // Add relevant content as the latest user message 3614 $conversation_history[] = [ 3615 'role' => 'user', 3616 'content' => $relevant_content 3617 ]; 3618 3619 // Prepare the request body with stream: true 3620 $body = json_encode([ 3621 'model' => $selected_model, 3622 'messages' => $conversation_history, 3623 'max_tokens' => 1000, 3624 'temperature' => 0.8, 3625 'system' => $system_prompt_instructions, 3626 'stream' => true 3627 ]); 3628 3629 // Use cURL for streaming support 3630 $ch = curl_init(); 3631 curl_setopt($ch, CURLOPT_URL, 'https://api.anthropic.com/v1/messages'); 3632 curl_setopt($ch, CURLOPT_RETURNTRANSFER, false); 3633 curl_setopt($ch, CURLOPT_POST, true); 3634 curl_setopt($ch, CURLOPT_POSTFIELDS, $body); 3635 curl_setopt($ch, CURLOPT_HTTPHEADER, array( 3636 'Content-Type: application/json', 3637 'x-api-key: ' . $claude_api_key, 3638 'anthropic-version: 2023-06-01' 3639 )); 3640 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); 3641 curl_setopt($ch, CURLOPT_TIMEOUT, 60); 3642 3643 $full_response = ''; // Accumulate full response for saving 3644 3645 // Buffer control for real-time streaming 3646 curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($ch, $data) use (&$full_response) { 3647 // Process each chunk of data 3648 $lines = explode("\n", $data); 3649 3650 foreach ($lines as $line) { 3651 if (trim($line) === '') { 3652 continue; 3653 } 3654 3655 // Claude uses event: and data: format 3656 if (strpos($line, 'event: ') === 0) { 3657 // Store the event type for the next data line 3658 continue; 3659 } 3660 3661 if (strpos($line, 'data: ') === 0) { 3662 $json_str = substr($line, 6); // Remove 'data: ' prefix 3663 3664 $json = json_decode($json_str, true); 3665 if (json_last_error() !== JSON_ERROR_NONE) { 3666 continue; 3667 } 3668 3669 // Handle different event types 3670 if (isset($json['type'])) { 3671 switch ($json['type']) { 3672 case 'content_block_delta': 3673 if (isset($json['delta']['text'])) { 3674 $content = $json['delta']['text']; 3675 $full_response .= $content; // Accumulate 3676 // Send as SSE format compatible with your frontend 3677 echo "data: " . json_encode(['content' => $content]) . "\n\n"; 3678 flush(); 3679 } 3680 break; 3681 3682 case 'message_stop': 3683 echo "data: [DONE]\n\n"; 3684 flush(); 3685 break; 3686 3687 case 'error': 3688 echo "data: " . json_encode(['error' => $json['error']['message'] ?? 'Unknown error']) . "\n\n"; 3689 flush(); 3690 break; 3691 } 3692 } 3693 } 3694 } 3695 3696 return strlen($data); 3697 }); 3698 3699 $response = curl_exec($ch); 3700 $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); 3701 3702 if (curl_errno($ch)) { 3703 throw new Exception('cURL Error: ' . curl_error($ch)); 3704 } 3705 3706 curl_close($ch); 3707 3708 if ($http_code !== 200) { 3709 throw new Exception('HTTP Error: ' . $http_code); 3710 } 3711 3712 // Save the complete response to maintain chat persistence 3713 if (!empty($full_response) && !empty($session_id)) { 3714 $this->mxchat_save_chat_message($session_id, 'bot', $full_response); 3715 } 3716 3717 return true; // Indicate streaming completed successfully 3718 3719 } catch (Exception $e) { 3720 echo "data: " . json_encode(['error' => $e->getMessage()]) . "\n\n"; 3721 flush(); 3722 return false; 3723 } 3724 } 3725 private function mxchat_generate_response_openai_stream($selected_model, $api_key, $conversation_history, $relevant_content, $session_id) { 3726 try { 3727 // Get system prompt instructions from options 3728 $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : ''; 3729 3730 // Ensure conversation_history is an array 3731 if (!is_array($conversation_history)) { 3732 $conversation_history = array(); 3733 } 3734 3735 // Format conversation history for OpenAI 3736 $formatted_conversation = array(); 3737 3738 $formatted_conversation[] = array( 3739 'role' => 'system', 3740 'content' => $system_prompt_instructions . " " . $relevant_content 3741 ); 3742 3743 foreach ($conversation_history as $message) { 3744 if (is_array($message) && isset($message['role']) && isset($message['content'])) { 3745 $role = $message['role']; 3746 if ($role === 'bot' || $role === 'agent') { 3747 $role = 'assistant'; 3748 } 3749 if (!in_array($role, ['system', 'assistant', 'user', 'function', 'tool'])) { 3750 $role = 'user'; 3751 } 3752 $formatted_conversation[] = array( 3753 'role' => $role, 3754 'content' => $message['content'] 3755 ); 3756 } 3757 } 3758 3759 // Prepare the request body with stream: true 3760 $body = json_encode([ 3761 'model' => $selected_model, 3762 'messages' => $formatted_conversation, 3763 'temperature' => 0.8, 3764 'stream' => true 3765 ]); 3766 3767 // Use cURL for streaming support 3768 $ch = curl_init(); 3769 curl_setopt($ch, CURLOPT_URL, 'https://api.openai.com/v1/chat/completions'); 3770 curl_setopt($ch, CURLOPT_RETURNTRANSFER, false); 3771 curl_setopt($ch, CURLOPT_POST, true); 3772 curl_setopt($ch, CURLOPT_POSTFIELDS, $body); 3773 curl_setopt($ch, CURLOPT_HTTPHEADER, array( 3774 'Content-Type: application/json', 3775 'Authorization: Bearer ' . $api_key 3776 )); 3777 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); 3778 curl_setopt($ch, CURLOPT_TIMEOUT, 60); 3779 3780 $full_response = ''; // Accumulate full response for saving 3781 3782 // Buffer control for real-time streaming 3783 curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($ch, $data) use (&$full_response) { 3784 // Process each chunk of data 3785 $lines = explode("\n", $data); 3786 3787 foreach ($lines as $line) { 3788 if (trim($line) === '' || strpos($line, 'data: ') !== 0) { 3789 continue; 3790 } 3791 3792 $json_str = substr($line, 6); // Remove 'data: ' prefix 3793 3794 if ($json_str === '[DONE]') { 3795 echo "data: [DONE]\n\n"; 3796 flush(); 3797 continue; 3798 } 3799 3800 $json = json_decode($json_str, true); 3801 if (isset($json['choices'][0]['delta']['content'])) { 3802 $content = $json['choices'][0]['delta']['content']; 3803 $full_response .= $content; // Accumulate 3804 // Send as SSE format 3805 echo "data: " . json_encode(['content' => $content]) . "\n\n"; 3806 flush(); 3807 } 3808 } 3809 3810 return strlen($data); 3811 }); 3812 3813 $response = curl_exec($ch); 3814 $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); 3815 3816 if (curl_errno($ch)) { 3817 throw new Exception('cURL Error: ' . curl_error($ch)); 3818 } 3819 3820 curl_close($ch); 3821 3822 if ($http_code !== 200) { 3823 throw new Exception('HTTP Error: ' . $http_code); 3824 } 3825 3826 // Save the complete response to maintain chat persistence 3827 if (!empty($full_response) && !empty($session_id)) { 3828 $this->mxchat_save_chat_message($session_id, 'bot', $full_response); 3829 } 3830 3831 return true; // Indicate streaming completed successfully 3832 3833 } catch (Exception $e) { 3834 echo "data: " . json_encode(['error' => $e->getMessage()]) . "\n\n"; 3835 flush(); 3836 return false; 3837 } 3838 } 3839 private function mxchat_generate_response_openai($selected_model, $api_key, $conversation_history, $relevant_content) { 3840 try { 3841 // Ensure conversation_history is an array 3842 if (!is_array($conversation_history)) { 3843 $conversation_history = array(); 3844 } 3845 3846 // Get system prompt instructions from options 3847 $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : ''; 3848 3849 // Create a new array for the formatted conversation 3850 $formatted_conversation = array(); 3851 3852 // Add system message first 3853 $formatted_conversation[] = array( 3854 'role' => 'system', 3855 'content' => $system_prompt_instructions . " " . $relevant_content 3856 ); 3857 3858 // Add the rest of the conversation history 3859 foreach ($conversation_history as $message) { 3860 if (is_array($message) && isset($message['role']) && isset($message['content'])) { 3861 $role = $message['role']; 3862 3863 // Convert roles to supported format 3864 if ($role === 'bot' || $role === 'agent') { 3865 $role = 'assistant'; 3866 } 3867 if (!in_array($role, ['system', 'assistant', 'user', 'function', 'tool'])) { 3868 $role = 'user'; 3869 } 3870 3871 $formatted_conversation[] = array( 3872 'role' => $role, 3873 'content' => $message['content'] 3874 ); 3875 } 3876 } 3877 3878 $body = json_encode([ 3879 'model' => $selected_model, 3880 'messages' => $formatted_conversation, 3881 'temperature' => 0.8, 3882 'stream' => false 3883 ]); 3884 3885 $args = [ 3886 'body' => $body, 3887 'headers' => [ 3888 'Content-Type' => 'application/json', 3889 'Authorization' => 'Bearer ' . $api_key, 3890 ], 3891 'timeout' => 60, 3892 'redirection' => 5, 3893 'blocking' => true, 3894 'httpversion' => '1.0', 3895 'sslverify' => true, 3896 ]; 3897 3898 $response = wp_remote_post('https://api.openai.com/v1/chat/completions', $args); 3899 3900 if (is_wp_error($response)) { 3901 $error_message = $response->get_error_message(); 3902 //error_log('OpenAI API Error: ' . $error_message); 3903 return [ 3904 'error' => esc_html__('Connection error when contacting OpenAI: ', 'mxchat') . esc_html($error_message), 3905 'error_code' => 'openai_connection_error', 3906 'provider' => 'openai' 3907 ]; 3908 } 3909 3910 $status_code = wp_remote_retrieve_response_code($response); 3911 if ($status_code !== 200) { 3912 $response_body = wp_remote_retrieve_body($response); 3913 $decoded_response = json_decode($response_body, true); 3914 3915 $error_message = isset($decoded_response['error']['message']) 3916 ? $decoded_response['error']['message'] 3917 : 'HTTP Error ' . $status_code; 3918 3919 $error_type = isset($decoded_response['error']['type']) 3920 ? $decoded_response['error']['type'] 3921 : 'unknown'; 3922 3923 //error_log('OpenAI API HTTP Error: ' . $status_code . ' - ' . $error_message); 3924 3925 // Handle specific error types 3926 switch ($error_type) { 3927 case 'invalid_request_error': 3928 if (strpos($error_message, 'API key') !== false) { 3929 return [ 3930 'error' => esc_html__('Invalid OpenAI API key. Please check your API key configuration.', 'mxchat'), 3931 'error_code' => 'openai_invalid_api_key', 3932 'provider' => 'openai' 3933 ]; 3934 } 3935 break; 3936 3937 case 'authentication_error': 3938 return [ 3939 'error' => esc_html__('Authentication failed with OpenAI. Please check your API key.', 'mxchat'), 3940 'error_code' => 'openai_auth_error', 3941 'provider' => 'openai' 3942 ]; 3943 3944 case 'rate_limit_exceeded': 3945 return [ 3946 'error' => esc_html__('OpenAI rate limit exceeded. Please try again later.', 'mxchat'), 3947 'error_code' => 'openai_rate_limit', 3948 'provider' => 'openai' 3949 ]; 3950 3951 case 'quota_exceeded': 3952 return [ 3953 'error' => esc_html__('OpenAI API quota exceeded. Please check your billing details.', 'mxchat'), 3954 'error_code' => 'openai_quota_exceeded', 3955 'provider' => 'openai' 3956 ]; 3957 } 3958 3959 // Generic error fallback 3960 return [ 3961 'error' => esc_html__('OpenAI API error: ', 'mxchat') . esc_html($error_message), 3962 'error_code' => 'openai_api_error', 3963 'provider' => 'openai', 3964 'status_code' => $status_code 3965 ]; 3966 } 3967 3968 $response_body = wp_remote_retrieve_body($response); 3969 $decoded_response = json_decode($response_body, true); 3970 3971 if (isset($decoded_response['choices'][0]['message']['content'])) { 3972 return trim($decoded_response['choices'][0]['message']['content']); 3973 } else { 3974 //error_log('OpenAI API Response Format Error: ' . print_r($decoded_response, true)); 3975 return [ 3976 'error' => esc_html__('Unexpected response format from OpenAI.', 'mxchat'), 3977 'error_code' => 'openai_response_format_error', 3978 'provider' => 'openai' 3979 ]; 3980 } 3981 } catch (Exception $e) { 3982 //error_log('OpenAI Exception: ' . $e->getMessage()); 3983 return [ 3984 'error' => esc_html__('System error when processing OpenAI request: ', 'mxchat') . esc_html($e->getMessage()), 3985 'error_code' => 'openai_exception', 3986 'provider' => 'openai' 3651 3987 ]; 3652 3988 } … … 3815 4151 } 3816 4152 } 3817 3818 private function mxchat_generate_response_openai($selected_model, $api_key, $conversation_history, $relevant_content) {3819 try {3820 // Ensure conversation_history is an array3821 if (!is_array($conversation_history)) {3822 $conversation_history = array();3823 }3824 3825 // Get system prompt instructions from options3826 $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : '';3827 3828 // Create a new array for the formatted conversation3829 $formatted_conversation = array();3830 3831 // Add system message first3832 $formatted_conversation[] = array(3833 'role' => 'system',3834 'content' => $system_prompt_instructions . " " . $relevant_content3835 );3836 3837 // Add the rest of the conversation history3838 foreach ($conversation_history as $message) {3839 if (is_array($message) && isset($message['role']) && isset($message['content'])) {3840 $role = $message['role'];3841 3842 // Convert roles to supported format3843 if ($role === 'bot' || $role === 'agent') {3844 $role = 'assistant';3845 }3846 if (!in_array($role, ['system', 'assistant', 'user', 'function', 'tool'])) {3847 $role = 'user';3848 }3849 3850 $formatted_conversation[] = array(3851 'role' => $role,3852 'content' => $message['content']3853 );3854 }3855 }3856 3857 $body = json_encode([3858 'model' => $selected_model,3859 'messages' => $formatted_conversation,3860 'temperature' => 0.8,3861 'stream' => false3862 ]);3863 3864 $args = [3865 'body' => $body,3866 'headers' => [3867 'Content-Type' => 'application/json',3868 'Authorization' => 'Bearer ' . $api_key,3869 ],3870 'timeout' => 60,3871 'redirection' => 5,3872 'blocking' => true,3873 'httpversion' => '1.0',3874 'sslverify' => true,3875 ];3876 3877 $response = wp_remote_post('https://api.openai.com/v1/chat/completions', $args);3878 3879 if (is_wp_error($response)) {3880 $error_message = $response->get_error_message();3881 //error_log('OpenAI API Error: ' . $error_message);3882 return [3883 'error' => esc_html__('Connection error when contacting OpenAI: ', 'mxchat') . esc_html($error_message),3884 'error_code' => 'openai_connection_error',3885 'provider' => 'openai'3886 ];3887 }3888 3889 $status_code = wp_remote_retrieve_response_code($response);3890 if ($status_code !== 200) {3891 $response_body = wp_remote_retrieve_body($response);3892 $decoded_response = json_decode($response_body, true);3893 3894 $error_message = isset($decoded_response['error']['message'])3895 ? $decoded_response['error']['message']3896 : 'HTTP Error ' . $status_code;3897 3898 $error_type = isset($decoded_response['error']['type'])3899 ? $decoded_response['error']['type']3900 : 'unknown';3901 3902 //error_log('OpenAI API HTTP Error: ' . $status_code . ' - ' . $error_message);3903 3904 // Handle specific error types3905 switch ($error_type) {3906 case 'invalid_request_error':3907 if (strpos($error_message, 'API key') !== false) {3908 return [3909 'error' => esc_html__('Invalid OpenAI API key. Please check your API key configuration.', 'mxchat'),3910 'error_code' => 'openai_invalid_api_key',3911 'provider' => 'openai'3912 ];3913 }3914 break;3915 3916 case 'authentication_error':3917 return [3918 'error' => esc_html__('Authentication failed with OpenAI. Please check your API key.', 'mxchat'),3919 'error_code' => 'openai_auth_error',3920 'provider' => 'openai'3921 ];3922 3923 case 'rate_limit_exceeded':3924 return [3925 'error' => esc_html__('OpenAI rate limit exceeded. Please try again later.', 'mxchat'),3926 'error_code' => 'openai_rate_limit',3927 'provider' => 'openai'3928 ];3929 3930 case 'quota_exceeded':3931 return [3932 'error' => esc_html__('OpenAI API quota exceeded. Please check your billing details.', 'mxchat'),3933 'error_code' => 'openai_quota_exceeded',3934 'provider' => 'openai'3935 ];3936 }3937 3938 // Generic error fallback3939 return [3940 'error' => esc_html__('OpenAI API error: ', 'mxchat') . esc_html($error_message),3941 'error_code' => 'openai_api_error',3942 'provider' => 'openai',3943 'status_code' => $status_code3944 ];3945 }3946 3947 $response_body = wp_remote_retrieve_body($response);3948 $decoded_response = json_decode($response_body, true);3949 3950 if (isset($decoded_response['choices'][0]['message']['content'])) {3951 return trim($decoded_response['choices'][0]['message']['content']);3952 } else {3953 //error_log('OpenAI API Response Format Error: ' . print_r($decoded_response, true));3954 return [3955 'error' => esc_html__('Unexpected response format from OpenAI.', 'mxchat'),3956 'error_code' => 'openai_response_format_error',3957 'provider' => 'openai'3958 ];3959 }3960 } catch (Exception $e) {3961 //error_log('OpenAI Exception: ' . $e->getMessage());3962 return [3963 'error' => esc_html__('System error when processing OpenAI request: ', 'mxchat') . esc_html($e->getMessage()),3964 'error_code' => 'openai_exception',3965 'provider' => 'openai'3966 ];3967 }3968 }3969 4153 private function mxchat_generate_response_xai($selected_model, $xai_api_key, $conversation_history, $relevant_content) { 3970 4154 try { … … 4155 4339 ]; 4156 4340 } 4157 }4158 private function mxchat_generate_response_claude($selected_model, $claude_api_key, $conversation_history, $relevant_content) {4159 // Get system prompt instructions from options4160 $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : '';4161 4162 // Clean and validate conversation history4163 foreach ($conversation_history as &$message) {4164 // Convert bot and agent roles to assistant4165 if ($message['role'] === 'bot' || $message['role'] === 'agent') {4166 $message['role'] = 'assistant';4167 }4168 4169 // Remove unsupported roles - Claude only supports 'assistant' and 'user'4170 if (!in_array($message['role'], ['assistant', 'user'])) {4171 $message['role'] = 'user';4172 }4173 4174 // Ensure content field exists4175 if (!isset($message['content']) || empty($message['content'])) {4176 $message['content'] = '';4177 }4178 4179 // Remove any unsupported fields4180 $message = array_intersect_key($message, array_flip(['role', 'content']));4181 }4182 4183 // Add relevant content as the latest user message4184 $conversation_history[] = [4185 'role' => 'user',4186 'content' => $relevant_content4187 ];4188 4189 // Build request body4190 $body = json_encode([4191 'model' => $selected_model,4192 'max_tokens' => 1000,4193 'temperature' => 0.8,4194 'messages' => $conversation_history,4195 'system' => $system_prompt_instructions4196 ]);4197 4198 // Set up API request4199 $args = [4200 'body' => $body,4201 'headers' => [4202 'Content-Type' => 'application/json',4203 'x-api-key' => $claude_api_key,4204 'anthropic-version' => '2023-06-01'4205 ],4206 'timeout' => 60,4207 'redirection' => 5,4208 'blocking' => true,4209 'httpversion' => '1.0',4210 'sslverify' => true,4211 ];4212 4213 // Make API request4214 $response = wp_remote_post('https://api.anthropic.com/v1/messages', $args);4215 4216 // Check for WordPress errors4217 if (is_wp_error($response)) {4218 //error_log("Claude API request error: " . $response->get_error_message());4219 return "Sorry, there was an error connecting to the API.";4220 }4221 4222 // Check HTTP response code4223 $http_code = wp_remote_retrieve_response_code($response);4224 if ($http_code !== 200) {4225 $error_body = wp_remote_retrieve_body($response);4226 //error_log("Claude API HTTP error: " . $http_code . " - " . $error_body);4227 4228 // Try to extract error message from response4229 $error_data = json_decode($error_body, true);4230 $error_message = isset($error_data['error']['message']) ?4231 $error_data['error']['message'] :4232 "HTTP error " . $http_code;4233 4234 return "Sorry, the API returned an error: " . $error_message;4235 }4236 4237 // Parse response4238 $response_body = json_decode(wp_remote_retrieve_body($response), true);4239 4240 // Check for JSON decode errors4241 if (json_last_error() !== JSON_ERROR_NONE) {4242 //error_log("Claude API JSON decode error: " . json_last_error_msg());4243 return "Sorry, there was an error processing the API response.";4244 }4245 4246 // Extract and validate response content4247 if (isset($response_body['content']) &&4248 is_array($response_body['content']) &&4249 !empty($response_body['content']) &&4250 isset($response_body['content'][0]['text'])) {4251 return trim($response_body['content'][0]['text']);4252 }4253 4254 // Log unexpected response format4255 //error_log("Claude API unexpected response format: " . print_r($response_body, true));4256 return "Sorry, I received an unexpected response format from the API.";4257 4341 } 4258 4342 private function mxchat_generate_response_gemini($selected_model, $gemini_api_key, $conversation_history, $relevant_content) { … … 4456 4540 public function mxchat_enqueue_scripts_styles() { 4457 4541 // Define version numbers for the styles and scripts 4458 $chat_style_version = '2.2.4'; 4459 $chat_script_version = '2.2.4'; 4460 4542 $chat_style_version = '2.2.5'; 4543 $chat_script_version = '2.2.5'; 4461 4544 // Enqueue the script 4462 4545 wp_enqueue_script( … … 4467 4550 true 4468 4551 ); 4469 4470 4552 // Enqueue the CSS 4471 4553 wp_enqueue_style( … … 4475 4557 $chat_style_version 4476 4558 ); 4477 4478 4559 // Fetch options from the database 4479 4560 $this->options = get_option('mxchat_options'); 4480 4561 $prompts_options = get_option('mxchat_prompts_options', array()); 4481 4562 4482 4563 // Prepare settings for JavaScript 4483 4564 $style_settings = array( 4484 4565 'ajax_url' => admin_url('admin-ajax.php'), 4485 4566 'nonce' => wp_create_nonce('mxchat_chat_nonce'), 4567 'model' => isset($this->options['model']) ? $this->options['model'] : 'gpt-4o', 4568 'enable_streaming_toggle' => isset($this->options['enable_streaming_toggle']) ? $this->options['enable_streaming_toggle'] : 'on', // ADD THIS LINE 4486 4569 'link_target_toggle' => $this->options['link_target_toggle'] ?? 'off', 4487 4570 'rate_limit_message' => $this->options['rate_limit_message'] ?? 'Rate limit exceeded. Please try again later.', … … 4499 4582 'chat_input_font_color' => $this->options['chat_input_font_color'] ?? '#212121', 4500 4583 'chat_persistence_toggle' => $this->options['chat_persistence_toggle'] ?? 'off', 4501 'appendWidgetToBody' => $this->options['append_to_body'] ?? 'off', // Example for consistency 4502 4584 'appendWidgetToBody' => $this->options['append_to_body'] ?? 'off', 4503 4585 'live_agent_message_bg_color' => $this->options['live_agent_message_bg_color'] ?? '#ffffff', 4504 4586 'live_agent_message_font_color' => $this->options['live_agent_message_font_color'] ?? '#333333', … … 4507 4589 'mode_indicator_font_color' => $this->options['mode_indicator_font_color'] ?? '#ffffff', 4508 4590 'toolbar_icon_color' => $this->options['toolbar_icon_color'] ?? '#212121', 4509 4510 4591 'use_pinecone' => $prompts_options['mxchat_use_pinecone'] ?? '0', 4511 4592 'pinecone_enabled' => isset($prompts_options['mxchat_use_pinecone']) && $prompts_options['mxchat_use_pinecone'] === '1' 4512 4593 ); 4513 4514 4594 // Pass the settings to the script 4515 4595 wp_localize_script('mxchat-chat-js', 'mxchatChat', $style_settings); 4516 4596 } 4517 4597 4518 4519 // Modify the mxchat_reset_rate_limits function to handle different timeframes4520 4598 // Modify the mxchat_reset_rate_limits function to handle different timeframes 4521 4599 public function mxchat_reset_rate_limits() { … … 4599 4677 public function cleanup_cron_events() { 4600 4678 wp_clear_scheduled_hook('mxchat_reset_rate_limits'); 4601 }4602 4603 private function mxchat_fetch_woocommerce_products() {4604 // Ensure WooCommerce is active4605 if (!class_exists('WooCommerce')) {4606 return [];4607 }4608 4609 $args = array(4610 'post_type' => 'product',4611 'post_status' => 'publish',4612 'posts_per_page' => -1,4613 );4614 4615 $products = get_posts($args);4616 $product_data = [];4617 4618 foreach ($products as $product) {4619 $product_id = $product->ID;4620 $product_obj = wc_get_product($product_id);4621 4622 $product_data[] = array(4623 'id' => $product_id,4624 'name' => $product_obj->get_name(),4625 'description' => $product_obj->get_description(),4626 'short_description' => $product_obj->get_short_description(),4627 'url' => get_permalink($product_id),4628 'price' => $product_obj->get_regular_price(),4629 'sale_price' => $product_obj->get_sale_price(),4630 'stock_status' => $product_obj->get_stock_status(),4631 'sku' => $product_obj->get_sku(),4632 'in_stock' => $product_obj->is_in_stock(),4633 'total_sales' => $product_obj->get_total_sales(),4634 );4635 }4636 4637 return $product_data;4638 4679 } 4639 4680 -
mxchat-basic/trunk/js/chat-script.js
r3307763 r3311750 59 59 60 60 61 // ==================================== 62 // CORE CHAT FUNCTIONALITY 63 // ==================================== 64 65 function sendMessage() { 66 var message = $('#chat-input').val(); // Get value from textarea 67 if (message) { 68 appendMessage("user", message); // Append user's message 69 $('#chat-input').val(''); // Clear the textarea 70 $('#chat-input').css('height', 'auto'); // Reset height after clearing content 71 72 // Hide the popular questions section 73 $('#mxchat-popular-questions').hide(); 74 75 // Show typing indicator 76 appendThinkingMessage(); 77 scrollToBottom(); 78 61 // ==================================== 62 // CORE CHAT FUNCTIONALITY 63 // ==================================== 64 65 // Update your existing sendMessage function 66 function sendMessage() { 67 var message = $('#chat-input').val(); 68 if (message) { 69 appendMessage("user", message); 70 $('#chat-input').val(''); 71 $('#chat-input').css('height', 'auto'); 72 73 $('#mxchat-popular-questions').hide(); 74 appendThinkingMessage(); 75 scrollToBottom(); 76 77 const currentModel = mxchatChat.model || 'gpt-4o'; 78 79 // Check if streaming is enabled AND supported for this model 80 if (shouldUseStreaming(currentModel)) { 81 callMxChatStream(message, function(response) { 82 $('.bot-message.temporary-message').removeClass('temporary-message'); 83 }); 84 } else { 79 85 callMxChat(message, function(response) { 80 // Replace typing indicator with actual response81 86 replaceLastMessage("bot", response); 82 87 }); 83 88 } 84 89 } 85 86 function sendMessageToChatbot(message) { 87 var sessionId = getChatSession(); // Reuse the session ID logic 88 89 // Hide the popular questions section 90 $('#mxchat-popular-questions').hide(); 91 92 // Show thinking indicator (no need to append the user's message again) 93 appendThinkingMessage(); 94 scrollToBottom(); 95 96 //console.log("Sending message to chatbot:", message); // Log the message 97 //console.log("Session ID:", sessionId); // Log the session ID 98 99 // Call the chatbot using the same call logic as sendMessage 90 } 91 92 // Update your existing sendMessageToChatbot function 93 function sendMessageToChatbot(message) { 94 var sessionId = getChatSession(); 95 96 $('#mxchat-popular-questions').hide(); 97 appendThinkingMessage(); 98 scrollToBottom(); 99 100 const currentModel = mxchatChat.model || 'gpt-4o'; 101 102 // Check if streaming is enabled AND supported for this model 103 if (shouldUseStreaming(currentModel)) { 104 callMxChatStream(message, function(response) { 105 $('.bot-message.temporary-message').removeClass('temporary-message'); 106 }); 107 } else { 100 108 callMxChat(message, function(response) { 101 // ** Ensure temporary thinking message is removed before adding new response **102 109 $('.temporary-message').remove(); 103 104 // Replace thinking indicator with actual response105 110 replaceLastMessage("bot", response); 106 111 }); 107 112 } 108 109 function callMxChat(message, callback) { 110 $.ajax({ 111 url: mxchatChat.ajax_url, 112 type: 'POST', 113 dataType: 'json', 114 data: { 115 action: 'mxchat_handle_chat_request', 116 message: message, 117 session_id: getChatSession(), 118 nonce: mxchatChat.nonce 119 }, 120 success: function(response) { 121 // Log the full response for debugging 122 //console.log("API Response:", response); 113 } 114 115 116 // Updated shouldUseStreaming function with debugging 117 function shouldUseStreaming(model) { 118 // Check if streaming is enabled in settings (using your toggle naming pattern) 119 const streamingEnabled = mxchatChat.enable_streaming_toggle === 'on'; 120 121 // Check if model supports streaming 122 const streamingSupported = isStreamingSupported(model); 123 124 125 // Only use streaming if both enabled and supported 126 return streamingEnabled && streamingSupported; 127 } 128 129 function callMxChat(message, callback) { 130 $.ajax({ 131 url: mxchatChat.ajax_url, 132 type: 'POST', 133 dataType: 'json', 134 data: { 135 action: 'mxchat_handle_chat_request', 136 message: message, 137 session_id: getChatSession(), 138 nonce: mxchatChat.nonce 139 }, 140 success: function(response) { 141 // Log the full response for debugging 142 //console.log("API Response:", response); 143 144 // First check if this is a successful response by looking for text, html, or message fields 145 // This preserves compatibility with your server response format 146 if (response.text !== undefined || response.html !== undefined || response.message !== undefined || 147 (response.success === true && response.data && response.data.status === 'waiting_for_agent')) { 148 149 // Handle successful response - this is your original success handling code 150 151 // Existing chat mode check 152 if (response.chat_mode) { 153 updateChatModeIndicator(response.chat_mode); 154 } 155 else if (response.fallbackResponse && response.fallbackResponse.chat_mode) { 156 updateChatModeIndicator(response.fallbackResponse.chat_mode); 157 } 158 159 // Add PDF filename handling 160 if (response.data && response.data.filename) { 161 showActivePdf(response.data.filename); 162 activePdfFile = response.data.filename; 163 } 164 165 // Add redirect check here 166 if (response.redirect_url) { 167 let responseText = response.text || ''; 168 if (responseText) { 169 replaceLastMessage("bot", responseText); 170 } 171 setTimeout(() => { 172 window.location.href = response.redirect_url; 173 }, 1500); 174 return; 175 } 176 177 // Check for live agent response 178 if (response.success && response.data && response.data.status === 'waiting_for_agent') { 179 updateChatModeIndicator('agent'); 180 return; 181 } 182 183 // Handle other responses 184 let responseText = response.text || ''; 185 let responseHtml = response.html || ''; 186 let responseMessage = response.message || ''; 187 188 if (responseText === 'You are now chatting with the AI chatbot.') { 189 updateChatModeIndicator('ai'); 190 } 191 192 // Handle the message and show notification if chat is hidden 193 if (responseText || responseHtml || responseMessage) { 194 // Update the messages as before 195 if (responseText && responseHtml) { 196 replaceLastMessage("bot", responseText, responseHtml); 197 } else if (responseText) { 198 replaceLastMessage("bot", responseText); 199 } else if (responseHtml) { 200 replaceLastMessage("bot", "", responseHtml); 201 } else if (responseMessage) { 202 replaceLastMessage("bot", responseMessage); 203 } 204 205 // Check if chat is hidden and show notification 206 if ($('#floating-chatbot').hasClass('hidden')) { 207 const badge = $('#chat-notification-badge'); 208 if (badge.length) { 209 badge.show(); 210 } 211 } 212 } else { 213 //console.error("Unexpected response format:", response); 214 replaceLastMessage("bot", "I received an empty response. Please try again or contact support if this persists."); 215 } 216 217 if (response.message_id) { 218 lastSeenMessageId = response.message_id; 219 } 220 221 return; 222 } 223 224 // If we got here, it's likely an error response 225 // Now we can check for error conditions with our robust error handling 226 227 let errorMessage = ""; 228 let errorCode = ""; 229 230 // Check various possible error locations in the response 231 if (response.data && response.data.error_message) { 232 errorMessage = response.data.error_message; 233 errorCode = response.data.error_code || ""; 234 } else if (response.error_message) { 235 errorMessage = response.error_message; 236 errorCode = response.error_code || ""; 237 } else if (response.message) { 238 errorMessage = response.message; 239 } else if (typeof response.data === 'string') { 240 errorMessage = response.data; 241 } else if (!response.success) { 242 // Explicit check for success: false without other error info 243 errorMessage = "An error occurred. Please try again or contact support."; 244 } else { 245 // Fallback for any other unexpected response format 246 errorMessage = "Unexpected response received. Please try again or contact support."; 247 } 248 249 // Log the error with code for debugging 250 //console.log("Response data:", response.data); 251 //console.error("API Error:", errorMessage, "Code:", errorCode); 252 253 // Format user-friendly error message 254 let displayMessage = errorMessage; 255 256 // Customize message for admin users 257 if (mxchatChat.is_admin) { 258 // For admin users, show more technical details including error code 259 displayMessage = errorMessage + (errorCode ? " (Error code: " + errorCode + ")" : ""); 260 } 261 262 replaceLastMessage("bot", displayMessage); 263 }, 264 error: function(xhr, status, error) { 265 console.error("AJAX Error:", status, error); 266 //console.log("Response Text:", xhr.responseText); 267 268 let errorMessage = "An unexpected error occurred."; 269 270 // Try to parse the response if it's JSON 271 try { 272 const responseJson = JSON.parse(xhr.responseText); 273 //console.log("Parsed error response:", responseJson); 274 275 if (responseJson.data && responseJson.data.error_message) { 276 errorMessage = responseJson.data.error_message; 277 } else if (responseJson.message) { 278 errorMessage = responseJson.message; 279 } 280 } catch (e) { 281 // Not JSON or parsing failed, use HTTP status based messages 282 if (xhr.status === 0) { 283 errorMessage = "Network error: Please check your internet connection."; 284 } else if (xhr.status === 403) { 285 errorMessage = "Access denied: Your session may have expired. Please refresh the page."; 286 } else if (xhr.status === 404) { 287 errorMessage = "API endpoint not found. Please contact support."; 288 } else if (xhr.status === 429) { 289 errorMessage = "Too many requests. Please try again in a moment."; 290 } else if (xhr.status >= 500) { 291 errorMessage = "Server error: The server encountered an issue. Please try again later."; 292 } 293 } 294 295 replaceLastMessage("bot", errorMessage); 296 } 297 }); 298 } 299 300 function callMxChatStream(message, callback) { 301 //console.log("Using streaming for message:", message); 302 303 const currentModel = mxchatChat.model || 'gpt-4o'; 304 if (!isStreamingSupported(currentModel)) { 305 //console.log("Streaming not supported, falling back to regular call"); 306 callMxChat(message, callback); 307 return; 308 } 309 310 const formData = new FormData(); 311 formData.append('action', 'mxchat_stream_chat'); 312 formData.append('message', message); 313 formData.append('session_id', getChatSession()); 314 formData.append('nonce', mxchatChat.nonce); 315 316 let accumulatedContent = ''; 317 318 fetch(mxchatChat.ajax_url, { 319 method: 'POST', 320 body: formData, 321 credentials: 'same-origin' 322 }) 323 .then(response => { 324 //console.log("Streaming response received:", response); 325 326 if (!response.ok) { 327 throw new Error('Network response was not ok'); 328 } 329 330 // Check if response is JSON instead of streaming 331 const contentType = response.headers.get('content-type'); 332 if (contentType && contentType.includes('application/json')) { 333 //console.log("Received JSON response instead of stream, handling as regular response"); 334 return response.json().then(data => { 335 // Handle as regular JSON response - this is the key fix! 336 // Remove thinking dots first 337 $('.bot-message.temporary-message').remove(); 123 338 124 // First check if this is a successful response by looking for text, html, or message fields 125 // This preserves compatibility with your server response format 126 if (response.text !== undefined || response.html !== undefined || response.message !== undefined || 127 (response.success === true && response.data && response.data.status === 'waiting_for_agent')) { 128 129 // Handle successful response - this is your original success handling code 130 131 // Existing chat mode check 132 if (response.chat_mode) { 133 updateChatModeIndicator(response.chat_mode); 339 // Handle different response formats (including intent responses) 340 if (data.text || data.html || data.message) { 341 if (data.text && data.html) { 342 replaceLastMessage("bot", data.text, data.html); 343 } else if (data.text) { 344 replaceLastMessage("bot", data.text); 345 } else if (data.html) { 346 replaceLastMessage("bot", "", data.html); 347 } else if (data.message) { 348 // This handles intent responses that use 'message' field 349 replaceLastMessage("bot", data.message); 134 350 } 135 else if (response.fallbackResponse && response.fallbackResponse.chat_mode) { 136 updateChatModeIndicator(response.fallbackResponse.chat_mode); 351 } 352 353 // Handle other response properties 354 if (data.chat_mode) { 355 updateChatModeIndicator(data.chat_mode); 356 } 357 358 if (data.data && data.data.filename) { 359 showActivePdf(data.data.filename); 360 activePdfFile = data.data.filename; 361 } 362 363 if (callback) { 364 callback(data.text || data.message || ''); 365 } 366 }); 367 } 368 369 // Continue with streaming processing 370 const reader = response.body.getReader(); 371 const decoder = new TextDecoder(); 372 let buffer = ''; 373 374 function processStream() { 375 reader.read().then(({ done, value }) => { 376 if (done) { 377 //console.log("Streaming completed, final content:", accumulatedContent); 378 if (callback) { 379 callback(accumulatedContent); 137 380 } 138 139 // Add PDF filename handling 140 if (response.data && response.data.filename) { 141 showActivePdf(response.data.filename); 142 activePdfFile = response.data.filename; 381 return; 382 } 383 384 buffer += decoder.decode(value, { stream: true }); 385 const lines = buffer.split('\n'); 386 buffer = lines.pop() || ''; 387 388 for (const line of lines) { 389 if (line.startsWith('data: ')) { 390 const data = line.substring(6); 391 392 if (data === '[DONE]') { 393 //console.log("Received [DONE] signal"); 394 if (callback) { 395 callback(accumulatedContent); 396 } 397 return; 398 } 399 400 try { 401 const json = JSON.parse(data); 402 if (json.content) { 403 accumulatedContent += json.content; 404 updateStreamingMessage(accumulatedContent); 405 } else if (json.error) { 406 console.error("Streaming error:", json.error); 407 replaceLastMessage("bot", "Error: " + json.error); 408 return; 409 } 410 } catch (e) { 411 console.error('Error parsing SSE data:', e); 412 } 143 413 } 144 145 // Add redirect check here 146 if (response.redirect_url) { 147 let responseText = response.text || ''; 148 if (responseText) { 149 replaceLastMessage("bot", responseText); 150 } 151 setTimeout(() => { 152 window.location.href = response.redirect_url; 153 }, 1500); 154 return; 155 } 156 157 // Check for live agent response 158 if (response.success && response.data && response.data.status === 'waiting_for_agent') { 159 updateChatModeIndicator('agent'); 160 return; 161 } 162 163 // Handle other responses 164 let responseText = response.text || ''; 165 let responseHtml = response.html || ''; 166 let responseMessage = response.message || ''; 167 168 if (responseText === 'You are now chatting with the AI chatbot.') { 169 updateChatModeIndicator('ai'); 170 } 171 172 // Handle the message and show notification if chat is hidden 173 if (responseText || responseHtml || responseMessage) { 174 // Update the messages as before 175 if (responseText && responseHtml) { 176 replaceLastMessage("bot", responseText, responseHtml); 177 } else if (responseText) { 178 replaceLastMessage("bot", responseText); 179 } else if (responseHtml) { 180 replaceLastMessage("bot", "", responseHtml); 181 } else if (responseMessage) { 182 replaceLastMessage("bot", responseMessage); 183 } 184 185 // Check if chat is hidden and show notification 186 if ($('#floating-chatbot').hasClass('hidden')) { 187 const badge = $('#chat-notification-badge'); 188 if (badge.length) { 189 badge.show(); 190 } 191 } 192 } else { 193 //console.error("Unexpected response format:", response); 194 replaceLastMessage("bot", "I received an empty response. Please try again or contact support if this persists."); 195 } 196 197 if (response.message_id) { 198 lastSeenMessageId = response.message_id; 199 } 200 201 return; 202 } 203 204 // If we got here, it's likely an error response 205 // Now we can check for error conditions with our robust error handling 206 207 let errorMessage = ""; 208 let errorCode = ""; 209 210 // Check various possible error locations in the response 211 if (response.data && response.data.error_message) { 212 errorMessage = response.data.error_message; 213 errorCode = response.data.error_code || ""; 214 } else if (response.error_message) { 215 errorMessage = response.error_message; 216 errorCode = response.error_code || ""; 217 } else if (response.message) { 218 errorMessage = response.message; 219 } else if (typeof response.data === 'string') { 220 errorMessage = response.data; 221 } else if (!response.success) { 222 // Explicit check for success: false without other error info 223 errorMessage = "An error occurred. Please try again or contact support."; 224 } else { 225 // Fallback for any other unexpected response format 226 errorMessage = "Unexpected response received. Please try again or contact support."; 227 } 228 229 // Log the error with code for debugging 230 //console.log("Response data:", response.data); 231 //console.error("API Error:", errorMessage, "Code:", errorCode); 232 233 // Format user-friendly error message 234 let displayMessage = errorMessage; 235 236 // Customize message for admin users 237 if (mxchatChat.is_admin) { 238 // For admin users, show more technical details including error code 239 displayMessage = errorMessage + (errorCode ? " (Error code: " + errorCode + ")" : ""); 240 } 241 242 replaceLastMessage("bot", displayMessage); 243 }, 244 error: function(xhr, status, error) { 245 console.error("AJAX Error:", status, error); 246 //console.log("Response Text:", xhr.responseText); 247 248 let errorMessage = "An unexpected error occurred."; 249 250 // Try to parse the response if it's JSON 251 try { 252 const responseJson = JSON.parse(xhr.responseText); 253 //console.log("Parsed error response:", responseJson); 254 255 if (responseJson.data && responseJson.data.error_message) { 256 errorMessage = responseJson.data.error_message; 257 } else if (responseJson.message) { 258 errorMessage = responseJson.message; 259 } 260 } catch (e) { 261 // Not JSON or parsing failed, use HTTP status based messages 262 if (xhr.status === 0) { 263 errorMessage = "Network error: Please check your internet connection."; 264 } else if (xhr.status === 403) { 265 errorMessage = "Access denied: Your session may have expired. Please refresh the page."; 266 } else if (xhr.status === 404) { 267 errorMessage = "API endpoint not found. Please contact support."; 268 } else if (xhr.status === 429) { 269 errorMessage = "Too many requests. Please try again in a moment."; 270 } else if (xhr.status >= 500) { 271 errorMessage = "Server error: The server encountered an issue. Please try again later."; 272 } 273 } 274 275 replaceLastMessage("bot", errorMessage); 276 } 277 }); 278 } 414 } 415 416 processStream(); 417 }); 418 } 419 420 processStream(); 421 }) 422 .catch(error => { 423 console.error('Streaming error:', error); 424 callMxChat(message, callback); 425 }); 426 } 427 428 // Function to update message during streaming 429 function updateStreamingMessage(content) { 430 const formattedContent = linkify(formatBoldText(convertNewlinesToBreaks(formatCodeBlocks(content)))); 431 432 // Find the temporary message 433 const tempMessage = $('.bot-message.temporary-message').last(); 434 435 if (tempMessage.length) { 436 // Update existing message 437 tempMessage.html(formattedContent); 438 } else { 439 // Create new temporary message if it doesn't exist 440 appendMessage("bot", content, '', [], true); 441 } 442 443 } 444 445 // Function to check if streaming is supported for the current model 446 function isStreamingSupported(model) { 447 if (!model) return false; 448 449 //console.log("Checking streaming support for model:", model); // Debug log 450 451 // Get the model prefix 452 const modelPrefix = model.split('-')[0].toLowerCase(); 453 454 //console.log("Model prefix:", modelPrefix); // Debug log 455 456 // Support streaming for OpenAI and Claude models 457 const isSupported = modelPrefix === 'gpt' || modelPrefix === 'o1' || modelPrefix === 'claude'; 458 459 //console.log("Streaming supported:", isSupported); // Debug log 460 461 return isSupported; 462 } 463 464 // Update the event handlers to use the correct function names 465 $('#send-button').off('click').on('click', function() { 466 sendMessage(); // Use the updated sendMessage function 467 }); 468 469 // Override enter key handler 470 $('#chat-input').off('keypress').on('keypress', function(e) { 471 if (e.which == 13 && !e.shiftKey) { 472 e.preventDefault(); 473 sendMessage(); // Use the updated sendMessage function 474 } 475 }); 476 279 477 280 478 function appendMessage(sender, messageText = '', messageHtml = '', images = [], isTemporary = false) { … … 777 975 } 778 976 779 function checkForAgentMessages() { 780 const sessionId = getChatSession(); 781 $.ajax({ 782 url: mxchatChat.ajax_url, 783 type: 'POST', 784 dataType: 'json', 785 data: { 786 action: 'mxchat_fetch_new_messages', 787 session_id: sessionId, 788 last_seen_id: lastSeenMessageId, 789 nonce: mxchatChat.nonce 790 }, 791 success: function (response) { 792 if (response.success && response.data?.new_messages) { 793 let hasNewMessage = false; 794 795 response.data.new_messages.forEach(function (message) { 796 if (message.role === "agent" && !processedMessageIds.has(message.id)) { 797 hasNewMessage = true; 798 replaceLastMessage("agent", message.content); 799 lastSeenMessageId = message.id; 800 processedMessageIds.add(message.id); 801 } 802 }); 803 804 if (hasNewMessage && $('#floating-chatbot').hasClass('hidden')) { 805 showNotification(); 977 function checkForAgentMessages() { 978 const sessionId = getChatSession(); 979 $.ajax({ 980 url: mxchatChat.ajax_url, 981 type: 'POST', 982 dataType: 'json', 983 data: { 984 action: 'mxchat_fetch_new_messages', 985 session_id: sessionId, 986 last_seen_id: lastSeenMessageId, 987 persistence_enabled: 'true', // Add this too 988 nonce: mxchatChat.nonce 989 }, 990 success: function (response) { 991 if (response.success && response.data?.new_messages) { 992 let hasNewMessage = false; 993 994 response.data.new_messages.forEach(function (message) { 995 if (message.role === "agent" && !processedMessageIds.has(message.id)) { 996 hasNewMessage = true; 997 // CHANGE THIS LINE: 998 appendMessage("agent", message.content); // Instead of replaceLastMessage 999 lastSeenMessageId = message.id; 1000 processedMessageIds.add(message.id); 806 1001 } 807 808 scrollToBottom(true); 809 } 810 }, 811 error: function (xhr, status, error) { 812 console.error("Polling error:", xhr, status, error); 813 } 814 }); 815 } 816 1002 }); 1003 1004 if (hasNewMessage && $('#floating-chatbot').hasClass('hidden')) { 1005 showNotification(); 1006 } 1007 1008 scrollToBottom(true); 1009 } 1010 }, 1011 error: function (xhr, status, error) { 1012 console.error("Polling error:", xhr, status, error); 1013 } 1014 }); 1015 } 817 1016 818 1017 // ==================================== … … 1032 1231 } 1033 1232 1034 function checkInitialDocumentStatus() {1035 if (!sessionId) return;1036 1037 // Check PDF status1038 fetch(mxchatChat.ajax_url, {1039 method: 'POST',1040 headers: {1041 'Content-Type': 'application/x-www-form-urlencoded',1042 },1043 body: new URLSearchParams({1044 'action': 'mxchat_check_pdf_status',1045 'session_id': sessionId,1046 'nonce': mxchatChat.nonce1047 })1048 })1049 .then(response => response.json())1050 .then(data => {1051 if (data.success && data.data.filename) {1052 showActivePdf(data.data.filename);1053 activePdfFile = data.data.filename;1054 }1055 })1056 .catch(error => {1057 console.error('Error checking PDF status:', error);1058 });1059 1060 // Check Word document status1061 fetch(mxchatChat.ajax_url, {1062 method: 'POST',1063 headers: {1064 'Content-Type': 'application/x-www-form-urlencoded',1065 },1066 body: new URLSearchParams({1067 'action': 'mxchat_check_word_status',1068 'session_id': sessionId,1069 'nonce': mxchatChat.nonce1070 })1071 })1072 .then(response => response.json())1073 .then(data => {1074 if (data.success && data.data.filename) {1075 showActiveWord(data.data.filename);1076 activeWordFile = data.data.filename;1077 }1078 })1079 .catch(error => {1080 console.error('Error checking Word document status:', error);1081 });1082 }1083 1084 1085 1233 // ==================================== 1086 1234 // CONSENT & COMPLIANCE (GDPR) … … 1219 1367 // ==================================== 1220 1368 1221 // Enter key handler for chat input1222 $('#chat-input').keypress(function(e) {1223 if (e.which == 13 && !e.shiftKey) { // Check if "Enter" is pressed without Shift1224 e.preventDefault(); // Prevent default "Enter" behavior1225 $('#send-button').click(); // Trigger send button click1226 }1227 });1228 1229 // Send button click handler1230 $('#send-button').click(function() {1231 sendMessage();1232 });1233 1234 1369 // Popular questions click handler 1235 1370 $('.mxchat-popular-question').on('click', function () { … … 1282 1417 $(document).on('click', '.mxchat-add-to-cart-button', function() { 1283 1418 var productId = $(this).data('product-id'); 1419 1420 // Get the button text instead of hardcoded "add to cart" 1421 var buttonText = $(this).text() || "add to cart"; 1422 1284 1423 // Add a special prefix to indicate this is from button 1285 appendMessage("user", "add to cart");1424 appendMessage("user", buttonText); 1286 1425 sendMessageToChatbot("!addtocart"); // Special command to indicate button click 1287 1426 }); 1288 1427 1289 1428 // PDF upload button handlers 1290 1429 if (document.getElementById('pdf-upload-btn')) { … … 1674 1813 } 1675 1814 }); 1676 1677 // Initialize on page load1678 document.addEventListener('DOMContentLoaded', function() {1679 checkInitialDocumentStatus();1680 }); -
mxchat-basic/trunk/js/mxchat-admin.js
r3307763 r3311750 390 390 391 391 // Handle all input changes (including range slider) 392 $autosaveSections.find('input, textarea, select').on('change', function() { 393 const $field = $(this); 394 const name = $field.attr('name'); 395 396 // Skip saving for API key fields that haven't been interacted with and are empty 397 const isApiKeyField = name && ( 398 name === 'loops_api_key' || 399 name === 'api_key' || 400 name === 'xai_api_key' || 401 name === 'claude_api_key' || 402 name === 'voyage_api_key' || 403 name === 'gemini_api_key' || 404 name === 'deepseek_api_key' || 405 name.indexOf('_api_key') !== -1 406 ); 407 408 // Skip processing if: 409 // 1. It's an API key field 410 // 2. The user hasn't interacted with it 411 // 3. The field is empty 412 if (isApiKeyField && !userModifiedFields.has(name) && (!$field.val() || $field.val().trim() === '')) { 413 //console.log('Skipping auto-save for untouched API key field:', name); 414 return; 415 } 416 417 let value; 418 419 // Handle different input types 420 if ($field.attr('type') === 'checkbox') { 421 // *** UPDATED: Handle Pinecone checkboxes differently *** 392 $autosaveSections.find('input, textarea, select').on('change', function() { 393 const $field = $(this); 394 const name = $field.attr('name'); 395 396 // Skip saving for API key fields that haven't been interacted with and are empty 397 const isApiKeyField = name && ( 398 name === 'loops_api_key' || 399 name === 'api_key' || 400 name === 'xai_api_key' || 401 name === 'claude_api_key' || 402 name === 'voyage_api_key' || 403 name === 'gemini_api_key' || 404 name === 'deepseek_api_key' || 405 name.indexOf('_api_key') !== -1 406 ); 407 408 // Skip processing if: 409 // 1. It's an API key field 410 // 2. The user hasn't interacted with it 411 // 3. The field is empty 412 if (isApiKeyField && !userModifiedFields.has(name) && (!$field.val() || $field.val().trim() === '')) { 413 //console.log('Skipping auto-save for untouched API key field:', name); 414 return; 415 } 416 417 let value; 418 419 // Handle different input types 420 if ($field.attr('type') === 'checkbox') { 421 // *** UPDATED: Handle Pinecone checkboxes differently *** 422 if (name && name.indexOf('mxchat_pinecone_addon_options') !== -1) { 423 value = $field.is(':checked') ? '1' : '0'; 424 } else { 425 value = $field.is(':checked') ? 'on' : 'off'; 426 } 427 } else { 428 value = $field.val(); 429 } 430 431 // Create feedback container 432 const feedbackContainer = $('<div class="feedback-container"></div>'); 433 const spinner = $('<div class="saving-spinner"></div>'); 434 const successIcon = $('<div class="success-icon">✔</div>'); 435 436 // Position feedback container based on input type 437 if ($field.closest('.toggle-switch').length) { 438 $field.closest('td').append(feedbackContainer); 439 } else if ($field.closest('.mxchat-toggle-switch').length) { 440 $field.closest('.mxchat-toggle-container').append(feedbackContainer); 441 } else if ($field.closest('.slider-container').length) { 442 $field.closest('.slider-container').after(feedbackContainer); 443 } else { 444 $field.after(feedbackContainer); 445 } 446 feedbackContainer.append(spinner); 447 448 // Determine which AJAX action and nonce to use: 449 var ajaxAction, nonce; 450 // *** UPDATED: Add Pinecone fields to prompts action *** 451 if (name.indexOf('mxchat_prompts_options') !== -1 || 452 name === 'mxchat_auto_sync_posts' || 453 name === 'mxchat_auto_sync_pages' || 454 name.indexOf('mxchat_auto_sync_') === 0 || 455 name.indexOf('mxchat_pinecone_addon_options') !== -1) { // *** ADD THIS LINE *** 456 ajaxAction = 'mxchat_save_prompts_setting'; 457 nonce = mxchatPromptsAdmin.prompts_setting_nonce; 458 } else { 459 // Otherwise, use the existing AJAX action. 460 ajaxAction = 'mxchat_save_setting'; 461 nonce = mxchatAdmin.setting_nonce; 462 } 463 464 // *** ADD THIS: Debug logging for Pinecone fields *** 465 if (name && name.indexOf('mxchat_pinecone_addon_options') !== -1) { 466 //console.log('Saving Pinecone field:', name, '=', value); 467 } 468 469 // AJAX save request 470 $.ajax({ 471 url: (ajaxAction === 'mxchat_save_prompts_setting') ? mxchatPromptsAdmin.ajax_url : mxchatAdmin.ajax_url, 472 type: 'POST', 473 data: { 474 action: ajaxAction, 475 name: name, 476 value: value, 477 _ajax_nonce: nonce 478 }, 479 success: function(response) { 480 if (response.success) { 481 spinner.fadeOut(200, function() { 482 feedbackContainer.append(successIcon); 483 successIcon.fadeIn(200).delay(1000).fadeOut(200, function() { 484 feedbackContainer.remove(); 485 }); 486 }); 487 488 // *** ADD THIS: Update Pinecone checkbox state after successful save *** 489 if (name && name.indexOf('mxchat_pinecone_addon_options[mxchat_use_pinecone]') !== -1) { 490 //console.log('Pinecone toggle saved successfully, value:', value); 491 492 // The checkbox state is already updated by the user interaction 493 // But let's make sure the UI state matches the saved value 494 var $checkbox = $('input[name="mxchat_pinecone_addon_options[mxchat_use_pinecone]"]'); 495 var settingsDiv = $('.mxchat-pinecone-settings'); 496 497 // Double-check the UI state matches what was saved 498 if (value === '1' && !$checkbox.is(':checked')) { 499 $checkbox.prop('checked', true); 500 settingsDiv.slideDown(300); 501 } else if (value === '0' && $checkbox.is(':checked')) { 502 $checkbox.prop('checked', false); 503 settingsDiv.slideUp(300); 504 } 505 506 //console.log('Pinecone UI state synchronized'); 507 508 // Check if Knowledge Import tab is currently active 509 if ($('.mxchat-kb-tab-button[data-tab="import"]').hasClass('active')) { 510 // Show a notice that we need to refresh 511 var $knowledgeCard = $('#mxchat-kb-tab-import .mxchat-card').eq(1); 512 if ($knowledgeCard.length > 0) { 513 // Add a refresh notice at the top of the knowledge base card 514 var refreshNotice = $('<div class="notice notice-warning" style="margin: 15px 0; padding: 10px 15px;">' + 515 '<p style="margin: 0;">' + 516 '<span class="dashicons dashicons-info" style="color: #f0ad4e; margin-right: 5px;"></span>' + 517 'Database settings have changed. ' + 518 '<a href="#" onclick="location.reload(); return false;" style="font-weight: bold;">Click here to refresh</a> to see the updated knowledge base.' + 519 '</p></div>'); 520 521 $knowledgeCard.prepend(refreshNotice); 522 } 523 } else { 524 // If not on import tab, set a flag to refresh when they go there 525 sessionStorage.setItem('mxchat_pinecone_changed', 'true'); 526 } 527 } 528 529 // *** ADD THIS: Debug logging for successful saves *** 422 530 if (name && name.indexOf('mxchat_pinecone_addon_options') !== -1) { 423 value = $field.is(':checked') ? '1' : '0'; 531 //console.log('Pinecone field saved successfully:', name, '=', value); 532 } 533 534 // Check if the response contains a "no changes" message and log it 535 if (response.data && response.data.message === 'No changes detected') { 536 //console.log('No changes detected for field:', name); 537 } 538 } else { 539 // Only show alert for actual errors, not for "no changes" 540 let errorMessage = response.data?.message || 'Unknown error'; 541 542 // Don't display an alert for "no changes" message 543 if (errorMessage !== 'No changes detected' && errorMessage !== 'Update failed or no changes') { 544 alert('Error saving: ' + errorMessage); 424 545 } else { 425 value = $field.is(':checked') ? 'on' : 'off'; 426 } 427 } else { 428 value = $field.val(); 429 } 430 431 // Create feedback container 432 const feedbackContainer = $('<div class="feedback-container"></div>'); 433 const spinner = $('<div class="saving-spinner"></div>'); 434 const successIcon = $('<div class="success-icon">✔</div>'); 435 436 // Position feedback container based on input type 437 if ($field.closest('.toggle-switch').length) { 438 $field.closest('td').append(feedbackContainer); 439 } else if ($field.closest('.mxchat-toggle-switch').length) { 440 $field.closest('.mxchat-toggle-container').append(feedbackContainer); 441 } else if ($field.closest('.slider-container').length) { 442 $field.closest('.slider-container').after(feedbackContainer); 443 } else { 444 $field.after(feedbackContainer); 445 } 446 feedbackContainer.append(spinner); 447 448 // Determine which AJAX action and nonce to use: 449 var ajaxAction, nonce; 450 // *** UPDATED: Add Pinecone fields to prompts action *** 451 if (name.indexOf('mxchat_prompts_options') !== -1 || 452 name === 'mxchat_auto_sync_posts' || 453 name === 'mxchat_auto_sync_pages' || 454 name.indexOf('mxchat_auto_sync_') === 0 || 455 name.indexOf('mxchat_pinecone_addon_options') !== -1) { // *** ADD THIS LINE *** 456 ajaxAction = 'mxchat_save_prompts_setting'; 457 nonce = mxchatPromptsAdmin.prompts_setting_nonce; 458 } else { 459 // Otherwise, use the existing AJAX action. 460 ajaxAction = 'mxchat_save_setting'; 461 nonce = mxchatAdmin.setting_nonce; 462 } 463 464 // *** ADD THIS: Debug logging for Pinecone fields *** 465 if (name && name.indexOf('mxchat_pinecone_addon_options') !== -1) { 466 //console.log('Saving Pinecone field:', name, '=', value); 467 } 468 469 // AJAX save request 470 $.ajax({ 471 url: (ajaxAction === 'mxchat_save_prompts_setting') ? mxchatPromptsAdmin.ajax_url : mxchatAdmin.ajax_url, 472 type: 'POST', 473 data: { 474 action: ajaxAction, 475 name: name, 476 value: value, 477 _ajax_nonce: nonce 478 }, 479 success: function(response) { 480 if (response.success) { 481 spinner.fadeOut(200, function() { 482 feedbackContainer.append(successIcon); 483 successIcon.fadeIn(200).delay(1000).fadeOut(200, function() { 484 feedbackContainer.remove(); 485 }); 486 }); 487 488 // *** ADD THIS: Update Pinecone checkbox state after successful save *** 489 if (name && name.indexOf('mxchat_pinecone_addon_options[mxchat_use_pinecone]') !== -1) { 490 //console.log('Pinecone toggle saved successfully, value:', value); 491 492 // The checkbox state is already updated by the user interaction 493 // But let's make sure the UI state matches the saved value 494 var $checkbox = $('input[name="mxchat_pinecone_addon_options[mxchat_use_pinecone]"]'); 495 var settingsDiv = $('.mxchat-pinecone-settings'); 496 497 // Double-check the UI state matches what was saved 498 if (value === '1' && !$checkbox.is(':checked')) { 499 $checkbox.prop('checked', true); 500 settingsDiv.slideDown(300); 501 } else if (value === '0' && $checkbox.is(':checked')) { 502 $checkbox.prop('checked', false); 503 settingsDiv.slideUp(300); 504 } 505 506 //console.log('Pinecone UI state synchronized'); 507 508 // Check if Knowledge Import tab is currently active 509 if ($('.mxchat-kb-tab-button[data-tab="import"]').hasClass('active')) { 510 // Show a notice that we need to refresh 511 var $knowledgeCard = $('#mxchat-kb-tab-import .mxchat-card').eq(1); 512 if ($knowledgeCard.length > 0) { 513 // Add a refresh notice at the top of the knowledge base card 514 var refreshNotice = $('<div class="notice notice-warning" style="margin: 15px 0; padding: 10px 15px;">' + 515 '<p style="margin: 0;">' + 516 '<span class="dashicons dashicons-info" style="color: #f0ad4e; margin-right: 5px;"></span>' + 517 'Database settings have changed. ' + 518 '<a href="#" onclick="location.reload(); return false;" style="font-weight: bold;">Click here to refresh</a> to see the updated knowledge base.' + 519 '</p></div>'); 520 521 $knowledgeCard.prepend(refreshNotice); 522 } 523 } else { 524 // If not on import tab, set a flag to refresh when they go there 525 sessionStorage.setItem('mxchat_pinecone_changed', 'true'); 526 } 527 } 528 529 // *** ADD THIS: Debug logging for successful saves *** 530 if (name && name.indexOf('mxchat_pinecone_addon_options') !== -1) { 531 //console.log('Pinecone field saved successfully:', name, '=', value); 532 } 533 534 // Check if the response contains a "no changes" message and log it 535 if (response.data && response.data.message === 'No changes detected') { 536 //console.log('No changes detected for field:', name); 537 } 538 } else { 539 // Only show alert for actual errors, not for "no changes" 540 let errorMessage = response.data?.message || 'Unknown error'; 541 542 // Don't display an alert for "no changes" message 543 if (errorMessage !== 'No changes detected' && errorMessage !== 'Update failed or no changes') { 544 alert('Error saving: ' + errorMessage); 545 } else { 546 // Still provide visual feedback that no changes were needed 547 spinner.fadeOut(200, function() { 548 feedbackContainer.append(successIcon); 549 successIcon.fadeIn(200).delay(1000).fadeOut(200, function() { 550 feedbackContainer.remove(); 551 }); 552 }); 553 //console.log('No changes detected for field:', name); 554 return; 555 } 556 557 // Only revert checkbox state if it was an actual error 558 if (errorMessage !== 'No changes detected' && errorMessage !== 'Update failed or no changes') { 559 if ($field.attr('type') === 'checkbox') { 560 $field.prop('checked', !$field.is(':checked')); 561 } 562 } 563 564 // Always clean up the feedback container 565 feedbackContainer.remove(); 566 } 567 }, 568 error: function(xhr, textStatus, error) { 569 //console.error('AJAX Error:', textStatus, error); 570 alert('An error occurred while saving. Please try again.'); 571 572 // Revert checkbox state on error 546 // Still provide visual feedback that no changes were needed 547 spinner.fadeOut(200, function() { 548 feedbackContainer.append(successIcon); 549 successIcon.fadeIn(200).delay(1000).fadeOut(200, function() { 550 feedbackContainer.remove(); 551 }); 552 }); 553 //console.log('No changes detected for field:', name); 554 return; 555 } 556 557 // Only revert checkbox state if it was an actual error 558 if (errorMessage !== 'No changes detected' && errorMessage !== 'Update failed or no changes') { 573 559 if ($field.attr('type') === 'checkbox') { 574 560 $field.prop('checked', !$field.is(':checked')); 575 561 } 576 577 feedbackContainer.remove(); 578 } 579 }); 580 }); 562 } 563 564 // Always clean up the feedback container 565 feedbackContainer.remove(); 566 } 567 }, 568 error: function(xhr, textStatus, error) { 569 //console.error('AJAX Error:', textStatus, error); 570 alert('An error occurred while saving. Please try again.'); 571 572 // Revert checkbox state on error 573 if ($field.attr('type') === 'checkbox') { 574 $field.prop('checked', !$field.is(':checked')); 575 } 576 577 feedbackContainer.remove(); 578 } 579 }); 580 }); 581 581 582 582 // Initialize color pickers with debouncing -
mxchat-basic/trunk/mxchat-basic.php
r3308065 r3311750 3 3 * Plugin Name: MxChat 4 4 * Description: AI chatbot for WordPress with OpenAI, Claude, xAI, DeepSeek, live agent, PDF uploads, WooCommerce, and training on website data. 5 * Version: 2.2. 45 * Version: 2.2.5 6 6 * Author: MxChat 7 7 * Author URI: https://mxchat.ai … … 17 17 18 18 // Define plugin version constant for asset versioning 19 define('MXCHAT_VERSION', '2.2. 4');19 define('MXCHAT_VERSION', '2.2.5'); 20 20 21 21 function mxchat_load_textdomain() { -
mxchat-basic/trunk/readme.txt
r3308065 r3311750 6 6 Tested up to: 6.8 7 7 Requires PHP: 7.2 8 Stable tag: 2.2. 48 Stable tag: 2.2.5 9 9 License: GPLv2 or later 10 10 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 18 18 ### Product Demo Videos: 19 19 - [Admin Assistant Add-On](https://www.youtube.com/watch?v=AdEA1k-UCFM) - Discover how to use the MxChat Admin Assistant to bring a ChatGPT-like experience directly inside your WordPress dashboard. Learn to access multiple AI models, save conversations, generate images, and use web search - all without leaving your admin panel. 20 - [AI Theme Generator (Part of Theme Customizer Add-On)](https://www.youtube.com/watch?v=rSQDW2qbtRU&t) - Transform your chatbot's appearance in seconds with our revolutionary AI-powered theme generator! Just describe your ideal design and watch as AI creates and applies custom CSS styling in real-time. No coding required. Supports GPT-4, Claude, Gemini, and more. 21 - [Theme Customizer Add-On](https://www.youtube.com/watch?v=MfbB9mZi6ag) - Learn how to customize your chatbot appearance with the Theme Customizer add-on. Easily modify colors, fonts, and styles with real-time previews to match your brand perfectly. 20 22 - [MxChat Forms Add-On](https://www.youtube.com/watch?v=3MrWy5dRalA) - Effortlessly create forms to capture leads, support tickets, and more with MxChat Forms! 21 - [Theme Customizer Add-On](https://www.youtube.com/watch?v=MfbB9mZi6ag) - Learn how to customize your chatbot appearance with the Theme Customizer add-on. Easily modify colors, fonts, and styles with real-time previews to match your brand perfectly.22 23 - [MxChat Similarity Tester Add-On](https://www.youtube.com/watch?v=uTr14tn59Hc) - Visualize how your chatbot’s actions perform and which data would be pulled from the database for improved accuracy. 23 24 - [WooCommerce Add-On](https://www.youtube.com/watch?v=WsqAppHRGdA) - Display product cards, recommend items, guide users to checkout, and supercharge your store. … … 46 47 47 48 - **Pro Features:** 48 Upgrade to the Pro tier to unlock advanced tools and functionality built directly into MxChat, plus access to all available add-ons: 49 Upgrade to the Pro tier to unlock advanced tools and functionality built directly into MxChat, plus access to all available add-ons: 49 50 - **Full Access to the MxChat Add-on Ecosystem**: Unlock our complete library of specialized extensions to create a truly customized chatbot experience tailored to your business needs—with new integrations added regularly. 50 51 - **Live Agent Transfer via Slack**: Seamlessly connect users to live agents for real-time support. 51 52 - **PDF Content Processing**: Enable in-chat processing of PDF content for richer interactions. 52 53 - **Robust Action Recognition System**: Power precise and context-aware chatbot responses. 54 - **AI Theme Generator**: Instantly transform your chatbot’s design using natural language. Describe your ideal look—like “modern dark theme with gold accents”—and AI will generate custom CSS with real-time preview and one-click apply. No coding needed. 53 55 - **Exclusive Access to MxChat AI Agents**: Test and deploy your chatbot with [MxChat AI Agents](https://mxchat.ai/agents/), a revolutionary tool to simulate real-world scenarios and refine performance. 54 56 … … 57 59 58 60 - **MxChat Similarity Tester (Free for All Users)** – Optimize action recognition with query testing, similarity scores, and database insights—perfect for admins enhancing chatbot precision. 59 - **MxChat Admin Assistant (Pro Only)** - Experience a ChatGPT-like interface directly in your WordPress admin panel, with chat thread management, image generation capabilities, and Perplexity search integration—all using your existing MxChat API keys. 61 - **MxChat Admin Assistant (Pro Only)** - Experience a ChatGPT-like interface directly in your WordPress admin panel, with chat thread management, image generation capabilities, and Perplexity search integration—all using your existing MxChat API keys. 60 62 - **MxChat Forms (Pro Only)** – Enable chatbot-triggered form collection with seamless creation, management, and real-time display in chat—exclusive to Pro users. 61 - **Theme Customiz ation with Live Preview (Pro Only)** – Unlock full theme customization with real-time previews to adjust colors, layouts, and styles dynamically—exclusive to Pro users.63 - **Theme Customizer with AI Generator & Live Preview (Pro Only)** – Instantly redesign your chatbot using natural language prompts like "dark mode with gold accents." The AI generates and applies CSS themes with real-time preview, supporting GPT-4, Claude, Gemini, and more. Easily fine-tune styles like color, layout, and font—no coding needed. 62 64 - **Chat Moderation (Pro Only)** – Maintain a secure chat environment with advanced moderation tools, including email and IP banning—available only for Pro users. 63 65 - **MxChat WooCommerce (Pro Only)** – Supercharge your WooCommerce store with AI-powered chat interactions, including product recommendations, order history, cart management, and checkout assistance. 64 - **MxChat Perplexity Integration (Pro Only)** – Add real-time web search capabilities powered by Perplexity AI, delivering up-to-date, well-sourced responses—exclusive to Pro users. 66 - **MxChat Perplexity Integration (Pro Only)** – Add real-time web search capabilities powered by Perplexity AI, delivering up-to-date, well-sourced responses—exclusive to Pro users. 65 67 - **MxChat Smart Recommender (Pro Only)** – Create intelligent recommendation flows that collect user preferences and provide personalized product or service recommendations from your custom collections. Features customizable similarity thresholds, trigger phrases, and AI prompt templates for tailored recommendation experiences with no coding required. 66 68 67 69 The MxChat Add-On Ecosystem offers flexibility to customize and scale your chatbot experience. Stay tuned for more add-ons coming soon! 68 70 69 ## New in 2.2.2 70 - **Enhanced Slack Integration** – Live agent handoff via Slack now creates new channels for each incoming conversation. Live agents can reply directly within the Slack channel to chat with users on your website in real-time. 71 - **Major Codebase Refactoring** – Significant refactoring for improved code organization, performance, and maintainability. 72 - **Improved Sitemap & PDF Processing** – Enhanced robustness with a new retry mechanism. Failed pages or URLs are now displayed for review. Added manual batch processing button for situations where scheduled (cron) jobs do not run. 73 - **Pinecone Setting Fix** – Resolved an issue where Pinecone integration was not automatically enabled for users who previously did not have the Pinecone add-on. 71 ## New in 2.2.5 72 - **Streaming Response Support** – You can now enable streaming responses for OpenAI and Claude models. This delivers a faster, smoother chat experience for users by displaying replies in real-time as they’re generated. 73 - **Slack Live Agent Fix** – Resolved a minor bug that occasionally caused duplicate messages in Slack during live agent conversations. 74 - **Pinecone UI Update** – Fixed a UI display issue in the Pinecone database panel when using the TE3 model. 74 75 75 76 ### Advanced Action Recognition … … 175 176 == Changelog == 176 177 178 = 2.2.5 = 179 - Streaming Response Support – You can now enable streaming responses for OpenAI and Claude models. This delivers a faster, smoother chat experience for users by displaying replies in real-time as they’re generated. 180 - Slack Live Agent Fix – Resolved a minor bug that occasionally caused duplicate messages in Slack during live agent conversations. 181 - Pinecone UI Update – Fixed a UI display issue in the Pinecone database panel when using the TE3 model. 182 177 183 = 2.2.4 = 178 184 - Critical update to error when working with large databases in Pinecone. … … 510 516 == Upgrade Notice == 511 517 512 = 2.2. 4=513 Critical update to error when working with large databases in Pinecone.518 = 2.2.5 = 519 New streaming response support for OpenAI and Claude, plus important fixes for Slack live agent messaging and Pinecone UI display. 514 520 515 521 == License & Warranty ==
Note: See TracChangeset
for help on using the changeset viewer.