Plugin Directory

Changeset 3311750


Ignore:
Timestamp:
06/15/2025 03:15:07 AM (8 months ago)
Author:
mxchat
Message:

2.2.5 various updates including streaming for openai & claude, pinecone UI fix and slack dup message fix.

Location:
mxchat-basic
Files:
93 added
11 edited

Legend:

Unmodified
Added
Removed
  • mxchat-basic/trunk/admin/class-ajax-handler.php

    r3308005 r3311750  
    182182                 }
    183183                 // 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 {
    195196                     //error_log('MXChat Save: Processing standard field: ' . $name);
    196197                     // Store all other values directly
     
    662663     }
    663664
    664     /**
    665      * Checks if a value has changed from old to new
    666      */
    667      private function mxchat_has_value_changed($old_value, $new_value) {
    668          // Handle null values
    669          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 differences
    676              return json_encode($old_value) !== json_encode($new_value);
    677          }
    678 
    679          // Handle toggle/checkbox values consistently
    680          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 comparison
    688          return $old_value !== $new_value;
    689      }
    690 
    691665}
    692666
  • mxchat-basic/trunk/admin/class-knowledge-manager.php

    r3308065 r3311750  
    18531853    $pinecone_options = get_option('mxchat_pinecone_addon_options', array());
    18541854    $pinecone_manager->mxchat_refresh_after_new_content($pinecone_options);
    1855        
     1855    $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
     1856
    18561857    if ($use_pinecone && !empty($pinecone_options['mxchat_pinecone_api_key'])) {
    18571858        // ONLY check Pinecone if it's enabled
  • mxchat-basic/trunk/admin/class-pinecone-manager.php

    r3308065 r3311750  
    7676
    7777/**
     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/**
    78134 * Get 1,000 most recent entries from Pinecone (SIMPLE VERSION)
    79135 */
     
    98154            //error_log('DEBUG: Fetching batch ' . ($pass + 1) . '/5');
    99155           
    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();
    111158
    112159            $query_data = array(
     
    175222    }
    176223}
     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    }
    177351
    178352/**
  • mxchat-basic/trunk/css/chat-style.css

    r3289918 r3311750  
    317317    gap: 10px;
    318318    padding: 8px;
    319     border-top: 1px solid #e5e5e5;
    320319    background: none;
    321320    align-items: end;
     
    604603    padding: 10px;
    605604}
    606 
     605.user-message p {
     606    margin: 0;
     607}
    607608.mxchat-popular-questions-title {
    608609    font-size: 14px;
     
    619620    max-height: 200px;
    620621    overflow-y: auto;
     622    padding-top: 20px;
    621623    padding-right: 10px;
    622624    padding-bottom: 10px;
     
    790792  clear: both;
    791793  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);
    794796}
    795797
     
    840842    padding: 0.4em 0.5em;
    841843    outline: none;
     844    border-radius: 10px;
    842845}
    843846
  • mxchat-basic/trunk/includes/class-mxchat-addons.php

    r3308065 r3311750  
    153153     */
    154154    public function enqueue_styles() {
    155         $plugin_version = '2.2.4';
     155        $plugin_version = '2.2.5';
    156156
    157157        wp_enqueue_style(
  • mxchat-basic/trunk/includes/class-mxchat-admin.php

    r3308065 r3311750  
    6464        'voyage_api_key' => '',
    6565        'gemini_api_key' => '',
     66        'enable_streaming_toggle' => 'on',
    6667        'embedding_model' => 'text-embedding-ada-002',
    6768        '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:
     
    326327                    <div class="mxchat-pro-content">
    327328                        <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>
    329330                    <div class="mxchat-pro-cta">
    330331                        <a href="https://mxchat.ai/" target="_blank" class="mxchat-button"><?php echo esc_html__('Upgrade Today', 'mxchat'); ?></a>
     
    425426
    426427    <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       
    427441        <div class="tutorial-item">
    428442            <h3><?php echo esc_html__('MxChat Forms Tutorial', 'mxchat'); ?></h3>
     
    11411155
    11421156public function mxchat_create_prompts_page() {
    1143     error_log('=== DEBUG: mxchat_create_prompts_page started ===');
     1157    //error_log('=== DEBUG: mxchat_create_prompts_page started ===');
    11441158   
    11451159    global $wpdb;
     
    11601174    $per_page = 10;
    11611175
    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);
    11651179
    11661180    // ================================
     
    11701184    // Get Pinecone settings to determine data source
    11711185    $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));
    11731187   
    11741188    $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'));
    11761190   
    11771191    $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'));
    11791193
    11801194   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');
    11821196    // PINECONE DATA SOURCE
    11831197    $data_source = 'pinecone';
    11841198    $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');
    11861200   
    11871201    $records = $pinecone_manager->mxchat_fetch_pinecone_records($pinecone_options, $search_query, $current_page, $per_page);
     
    11931207    $total_pages = ceil($total_records / $per_page);
    11941208   
    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);
    12001214} else {
    1201     error_log('DEBUG: Using WORDPRESS DB data source');
     1215    //error_log('DEBUG: Using WORDPRESS DB data source');
    12021216    // WORDPRESS DB DATA SOURCE (your existing logic)
    12031217    $data_source = 'wordpress';
     
    12171231    // Retrieve total number of prompts, considering search filter
    12181232    $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);
    12201234   
    12211235    $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);
    12231237   
    12241238    $total_pages = ceil($total_records / $per_page);
     
    12301244        $offset
    12311245    );
    1232     error_log('DEBUG: WordPress prompts query: ' . $prompts_query);
     1246    //error_log('DEBUG: WordPress prompts query: ' . $prompts_query);
    12331247   
    12341248    $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);
    12431257
    12441258    // Add the pagination links generation here
     
    12731287                   || ($sitemap_status && ($sitemap_status['status'] === 'processing' || $sitemap_status['status'] === 'error'));
    12741288
    1275     error_log('=== DEBUG: mxchat_create_prompts_page data preparation completed ===');
     1289    //error_log('=== DEBUG: mxchat_create_prompts_page data preparation completed ===');
    12761290   
    12771291    ?>
     
    13691383           </button>
    13701384
    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 
    13821385           <!-- Sitemap Import Option -->
    13831386           <button type="button" class="mxchat-import-box" data-option="sitemap" data-placeholder="<?php esc_attr_e('Enter sitemap URL here', 'mxchat'); ?>" data-type="sitemap">
     
    14101413                   <h4><?php esc_html_e('Direct Content', 'mxchat'); ?></h4>
    14111414                   <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>
    14121426               </div>
    14131427           </button>
     
    16941708        <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;">
    16951709            <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>
    16981712    <?php else : ?>
    16991713        <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;">
    17001714            <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>
    17021716        </div>
    17031717    <?php endif; ?>
     
    33293343            )
    33303344        );
     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
    33313358
    33323359    add_settings_field(
     
    33373364        'mxchat_chatbot_section'
    33383365    );
    3339 
    3340     // Add the settings field
     3366   
    33413367    add_settings_field(
    33423368        'embedding_model',
     
    43164342}
    43174343
     4344public 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}
    43184360// Callback function for embedding model selection
    43194361public function embedding_model_callback() {
     
    54475489public function mxchat_enqueue_admin_assets() {
    54485490    // 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';
    54505492   
    54515493    // Use file modification time for development (remove in production)
     
    56335675        $new_input['claude_api_key'] = sanitize_text_field($input['claude_api_key']);
    56345676    }
     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   
    56355685
    56365686    if (isset($input['deepseek_api_key'])) {
  • mxchat-basic/trunk/includes/class-mxchat-integrator.php

    r3308065 r3311750  
    8888    add_action('wp_ajax_nopriv_mxchat_check_email_provided', [$this, 'mxchat_check_email_provided']);
    8989    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'));
    9093}
    9194
     
    122125    wp_die();
    123126}
    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 ID
    126     $formatted_history = [];
    127 
    128     // Format the history to align with the expected structure for OpenAI
    129     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 
    139127
    140128private function mxchat_fetch_conversation_history_for_ai($session_id) {
     
    553541}
    554542
    555 // Add this to your plugin's main PHP file
    556 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 history
    566     $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 messages
    577     $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 order
    582     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_id
    590     ]);
    591     wp_die();
    592 }
    593 
    594543public function mxchat_handle_chat_request() {
    595544    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    }
    596562
    597563
     
    723689    }
    724690   
    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
    726693    wp_send_json([
    727694        'text' => $pre_processed_result['text'],
     
    739706        // Add the email to Loops
    740707        $this->add_email_to_loops($message);
    741 
     708   
    742709        // Send success response
    743710        $response_message = $this->options['email_capture_response'] ??
    744711                           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   
    746722        wp_send_json([
    747723            'success' => true,
     
    927903// Step 3: Handle the intent result appropriately
    928904if ($intent_result !== false) {
    929     // The intent was matched and handled
    930     //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
    931907   
    932908    if (is_array($intent_result) && (isset($intent_result['text']) || isset($intent_result['html']))) {
    933909        // Intent returned a direct response array
    934         //error_log("Intent returned a direct response.");
    935910        $response_data = [
    936911            'text' => $intent_result['text'] ?? '',
     
    939914        ];
    940915       
     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       
    941925        wp_send_json($response_data);
    942926        wp_die();
     
    944928    else if ($intent_result === true && (!empty($this->fallbackResponse['text']) || !empty($this->fallbackResponse['html']))) {
    945929        // Intent returned true and set fallbackResponse
    946         //error_log("Intent returned true with fallbackResponse set.");
    947930        $response_data = [
    948931            'text' => $this->fallbackResponse['text'] ?? '',
     
    951934        ];
    952935       
     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       
    953945        wp_send_json($response_data);
    954946        wp_die();
    955947    }
    956    
    957     // Intent was matched but no usable response was provided
    958     // This shouldn't happen with proper intent implementation
    959     //error_log("Warning: Intent matched but no response provided.");
    960948}
    961949
     
    10391027        $context_content = apply_filters('mxchat_prepare_context', $context_content, $session_id);
    10401028
    1041         // Generate the response using the full context
    10421029        $response = $this->mxchat_generate_response(
    10431030            $context_content,
     
    10471034            $this->options['deepseek_api_key'],
    10481035            $this->options['gemini_api_key'],
    1049             $conversation_history
     1036            $conversation_history,
     1037            $is_streaming,
     1038            $session_id
    10501039        );
     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        }
    10511046       
    10521047        // Check if the response is an error array
     
    14871482    }
    14881483}
    1489 /**
    1490  * Format search results into a natural text summary.
    1491  *
    1492  * @since 1.0.0
    1493  * @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 summary
    1510         $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.0
    1524  * @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                         <img
    1544                             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                             <img
    1567                                 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     <?php
    1582     return ob_get_clean();
    1583 }
    1584 
    15851484
    15861485//very good
     
    19541853    return sanitize_text_field($user_query);
    19551854}
    1956 
    1957 
    1958 
    1959 private function find_product_in_message($message) {
    1960     global $wpdb;
    1961 
    1962     // Get embedding for the search query
    1963     $query_embedding = $this->mxchat_generate_embedding($message, $this->options['api_key']);
    1964    
    1965     // Check if embedding generation returned an error
    1966     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 response
    1973         $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 users
    1976         $this->fallbackResponse['admin_error'] = $error_message;
    1977         $this->fallbackResponse['error_code'] = $error_code;
    1978        
    1979         return null;
    1980     }
    1981    
    1982     // Check if embedding is valid
    1983     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 string
    1990     $relevant_content = $this->mxchat_find_relevant_products($query_embedding);
    1991     if (empty($relevant_content)) {
    1992         // Return null to indicate no results and set fallback response
    1993         $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 content
    1999     preg_match_all('/https?:\/\/[^\s<>"\']+?\/product\/[^\s<>"\']+/', $relevant_content, $matches);
    2000 
    2001     if (!empty($matches[0])) {
    2002         // Try each URL found
    2003         foreach ($matches[0] as $url) {
    2004             // Clean the URL
    2005             $url = rtrim($url, '/."\']');
    2006 
    2007             // Get the product slug
    2008             $path = parse_url($url, PHP_URL_PATH);
    2009             $slug = basename(rtrim($path, '/'));
    2010 
    2011             // Find product by slug
    2012             $args = array(
    2013                 'post_type' => 'product',
    2014                 'post_status' => 'publish',
    2015                 'name' => $slug,
    2016                 'posts_per_page' => 1
    2017             );
    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 content
    2033     $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 response
    2049     $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 responses
    2054 private function generate_intent_response($context_content, $session_id) {
    2055     // Convert the context array to a structured string for the AI
    2056     $context_string = $this->format_intent_context($context_content);
    2057     // Generate AI response using the context
    2058     $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 key
    2065         $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 context
    2072 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 
    21071855
    21081856//very good
     
    27912539    if (isset($data['type']) && $data['type'] === 'url_verification') {
    27922540        //error_log('Slack URL verification challenge: ' . $data['challenge']);
    2793         // Return just the challenge string, not wrapped in an array
    27942541        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);
    27952563    }
    27962564   
     
    27992567        $event = $data['event'];
    28002568       
    2801         // Skip bot messages and messages with subtypes
     2569        // Skip bot messages and messages with subtypes (like bot_message)
    28022570        if (isset($event['bot_id']) || isset($event['subtype'])) {
    28032571            return new WP_REST_Response(['ok' => true]);
    28042572        }
    28052573       
     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       
    28062579        $channel_id = $event['channel'];
    28072580        $message_text = $event['text'] ?? '';
     2581        $message_ts = $event['ts'] ?? '';
    28082582       
    28092583        // Find session ID by looking for matching channel
     
    28212595            $session_id = str_replace('mxchat_channel_', '', $session_option);
    28222596           
     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           
    28232615            // Save the agent message
    28242616            $this->mxchat_save_chat_message($session_id, 'agent', $message_text);
    28252617           
    2826             // Send confirmation back to Slack
     2618            // Send confirmation back to Slack (only once)
    28272619            $slack_bot_token = $this->options['live_agent_bot_token'] ?? '';
    28282620            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                }
    28402638            }
    28412639        }
     
    28442642    return new WP_REST_Response(['ok' => true]);
    28452643}
    2846 
    28472644
    28482645// For the word upload handler
     
    35213318}
    35223319
    3523 private function mxchat_generate_response($relevant_content, $api_key, $xai_api_key, $claude_api_key, $deepseek_api_key, $gemini_api_key, $conversation_history) {
     3320private 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 = '') {
    35243321    try {
    35253322        if (!$relevant_content) {
     
    35663363                    ];
    35673364                }
    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                }
    35743381                break;
    35753382               
     
    36053412               
    36063413            case 'gpt':
     3414            case 'o1':
    36073415                if (empty($api_key)) {
    36083416                    return [
     
    36113419                    ];
    36123420                }
    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                }
    36193437                break;
    36203438               
     
    36273445                    ];
    36283446                }
    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                }
    36353463                break;
    36363464        }
     
    36493477            'error_code' => 'system_exception',
    36503478            'exception_details' => $e->getMessage()
     3479        ];
     3480    }
     3481}
     3482private 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}
     3582private 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}
     3725private 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}
     3839private 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'
    36513987        ];
    36523988    }
     
    38154151    }
    38164152}
    3817 
    3818 private function mxchat_generate_response_openai($selected_model, $api_key, $conversation_history, $relevant_content) {
    3819     try {
    3820         // Ensure conversation_history is an array
    3821         if (!is_array($conversation_history)) {
    3822             $conversation_history = array();
    3823         }
    3824 
    3825         // Get system prompt instructions from options
    3826         $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : '';
    3827 
    3828         // Create a new array for the formatted conversation
    3829         $formatted_conversation = array();
    3830 
    3831         // Add system message first
    3832         $formatted_conversation[] = array(
    3833             'role' => 'system',
    3834             'content' => $system_prompt_instructions . " " . $relevant_content
    3835         );
    3836 
    3837         // Add the rest of the conversation history
    3838         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 format
    3843                 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' => false
    3862         ]);
    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 types
    3905             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 fallback
    3939             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_code
    3944             ];
    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 }
    39694153private function mxchat_generate_response_xai($selected_model, $xai_api_key, $conversation_history, $relevant_content) {
    39704154    try {
     
    41554339    ];
    41564340}
    4157 }
    4158 private function mxchat_generate_response_claude($selected_model, $claude_api_key, $conversation_history, $relevant_content) {
    4159     // Get system prompt instructions from options
    4160     $system_prompt_instructions = isset($this->options['system_prompt_instructions']) ? $this->options['system_prompt_instructions'] : '';
    4161 
    4162     // Clean and validate conversation history
    4163     foreach ($conversation_history as &$message) {
    4164         // Convert bot and agent roles to assistant
    4165         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 exists
    4175         if (!isset($message['content']) || empty($message['content'])) {
    4176             $message['content'] = '';
    4177         }
    4178 
    4179         // Remove any unsupported fields
    4180         $message = array_intersect_key($message, array_flip(['role', 'content']));
    4181     }
    4182 
    4183     // Add relevant content as the latest user message
    4184     $conversation_history[] = [
    4185         'role' => 'user',
    4186         'content' => $relevant_content
    4187     ];
    4188 
    4189     // Build request body
    4190     $body = json_encode([
    4191         'model' => $selected_model,
    4192         'max_tokens' => 1000,
    4193         'temperature' => 0.8,
    4194         'messages' => $conversation_history,
    4195         'system' => $system_prompt_instructions
    4196     ]);
    4197 
    4198     // Set up API request
    4199     $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 request
    4214     $response = wp_remote_post('https://api.anthropic.com/v1/messages', $args);
    4215 
    4216     // Check for WordPress errors
    4217     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 code
    4223     $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 response
    4229         $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 response
    4238     $response_body = json_decode(wp_remote_retrieve_body($response), true);
    4239    
    4240     // Check for JSON decode errors
    4241     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 content
    4247     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 format
    4255     //error_log("Claude API unexpected response format: " . print_r($response_body, true));
    4256     return "Sorry, I received an unexpected response format from the API.";
    42574341}
    42584342private function mxchat_generate_response_gemini($selected_model, $gemini_api_key, $conversation_history, $relevant_content) {
     
    44564540public function mxchat_enqueue_scripts_styles() {
    44574541    // 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';
    44614544    // Enqueue the script
    44624545    wp_enqueue_script(
     
    44674550        true
    44684551    );
    4469 
    44704552    // Enqueue the CSS
    44714553    wp_enqueue_style(
     
    44754557        $chat_style_version
    44764558    );
    4477 
    44784559    // Fetch options from the database
    44794560    $this->options = get_option('mxchat_options');
    44804561    $prompts_options = get_option('mxchat_prompts_options', array());
    4481 
     4562   
    44824563    // Prepare settings for JavaScript
    44834564    $style_settings = array(
    44844565        'ajax_url' => admin_url('admin-ajax.php'),
    44854566        '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
    44864569        'link_target_toggle' => $this->options['link_target_toggle'] ?? 'off',
    44874570        'rate_limit_message' => $this->options['rate_limit_message'] ?? 'Rate limit exceeded. Please try again later.',
     
    44994582        'chat_input_font_color' => $this->options['chat_input_font_color'] ?? '#212121',
    45004583        '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',
    45034585        'live_agent_message_bg_color' => $this->options['live_agent_message_bg_color'] ?? '#ffffff',
    45044586        'live_agent_message_font_color' => $this->options['live_agent_message_font_color'] ?? '#333333',
     
    45074589        'mode_indicator_font_color' => $this->options['mode_indicator_font_color'] ?? '#ffffff',
    45084590        'toolbar_icon_color' => $this->options['toolbar_icon_color'] ?? '#212121',
    4509 
    45104591        'use_pinecone' => $prompts_options['mxchat_use_pinecone'] ?? '0',
    45114592        'pinecone_enabled' => isset($prompts_options['mxchat_use_pinecone']) && $prompts_options['mxchat_use_pinecone'] === '1'
    45124593    );
    4513 
    45144594    // Pass the settings to the script
    45154595    wp_localize_script('mxchat-chat-js', 'mxchatChat', $style_settings);
    45164596}
    45174597
    4518 
    4519 // Modify the mxchat_reset_rate_limits function to handle different timeframes
    45204598// Modify the mxchat_reset_rate_limits function to handle different timeframes
    45214599public function mxchat_reset_rate_limits() {
     
    45994677public function cleanup_cron_events() {
    46004678    wp_clear_scheduled_hook('mxchat_reset_rate_limits');
    4601 }
    4602 
    4603 private function mxchat_fetch_woocommerce_products() {
    4604     // Ensure WooCommerce is active
    4605     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;
    46384679}
    46394680
  • mxchat-basic/trunk/js/chat-script.js

    r3307763 r3311750  
    5959
    6060
    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
     66function 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 {
    7985            callMxChat(message, function(response) {
    80                 // Replace typing indicator with actual response
    8186                replaceLastMessage("bot", response);
    8287            });
    8388        }
    8489    }
    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
     93function 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 {
    100108        callMxChat(message, function(response) {
    101             // ** Ensure temporary thinking message is removed before adding new response **
    102109            $('.temporary-message').remove();
    103    
    104             // Replace thinking indicator with actual response
    105110            replaceLastMessage("bot", response);
    106111        });
    107112    }
    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
     117function 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
     129function 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
     300function 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();
    123338               
    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);
    134350                    }
    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);
    137380                    }
    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                        }
    143413                    }
    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
     429function 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
     446function 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
    279477   
    280478    function appendMessage(sender, messageText = '', messageHtml = '', images = [], isTemporary = false) {
     
    777975    }
    778976   
    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();
     977function 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);
    8061001                    }
    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}
    8171016
    8181017    // ====================================
     
    10321231    }
    10331232   
    1034     function checkInitialDocumentStatus() {
    1035         if (!sessionId) return;
    1036    
    1037         // Check PDF status
    1038         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.nonce
    1047             })
    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 status
    1061         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.nonce
    1070             })
    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 
    10851233    // ====================================
    10861234    // CONSENT & COMPLIANCE (GDPR)
     
    12191367    // ====================================
    12201368
    1221     // Enter key handler for chat input
    1222     $('#chat-input').keypress(function(e) {
    1223         if (e.which == 13 && !e.shiftKey) { // Check if "Enter" is pressed without Shift
    1224             e.preventDefault(); // Prevent default "Enter" behavior
    1225             $('#send-button').click(); // Trigger send button click
    1226         }
    1227     });
    1228    
    1229     // Send button click handler
    1230     $('#send-button').click(function() {
    1231         sendMessage();
    1232     });
    1233    
    12341369    // Popular questions click handler
    12351370    $('.mxchat-popular-question').on('click', function () {
     
    12821417    $(document).on('click', '.mxchat-add-to-cart-button', function() {
    12831418        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       
    12841423        // Add a special prefix to indicate this is from button
    1285         appendMessage("user", "add to cart");
     1424        appendMessage("user", buttonText);
    12861425        sendMessageToChatbot("!addtocart");  // Special command to indicate button click
    12871426    });
    1288    
     1427
    12891428    // PDF upload button handlers
    12901429    if (document.getElementById('pdf-upload-btn')) {
     
    16741813    }
    16751814});
    1676 
    1677 // Initialize on page load
    1678 document.addEventListener('DOMContentLoaded', function() {
    1679     checkInitialDocumentStatus();
    1680 });
  • mxchat-basic/trunk/js/mxchat-admin.js

    r3307763 r3311750  
    390390
    391391        // 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 ***
    422530                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);
    424545                } 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') {
    573559                    if ($field.attr('type') === 'checkbox') {
    574560                        $field.prop('checked', !$field.is(':checked'));
    575561                    }
    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                });
    581581
    582582        // Initialize color pickers with debouncing
  • mxchat-basic/trunk/mxchat-basic.php

    r3308065 r3311750  
    33 * Plugin Name: MxChat
    44 * Description: AI chatbot for WordPress with OpenAI, Claude, xAI, DeepSeek, live agent, PDF uploads, WooCommerce, and training on website data.
    5  * Version: 2.2.4
     5 * Version: 2.2.5
    66 * Author: MxChat
    77 * Author URI: https://mxchat.ai
     
    1717
    1818// Define plugin version constant for asset versioning
    19 define('MXCHAT_VERSION', '2.2.4');
     19define('MXCHAT_VERSION', '2.2.5');
    2020
    2121function mxchat_load_textdomain() {
  • mxchat-basic/trunk/readme.txt

    r3308065 r3311750  
    66Tested up to: 6.8
    77Requires PHP: 7.2
    8 Stable tag: 2.2.4
     8Stable tag: 2.2.5
    99License: GPLv2 or later
    1010License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    1818### Product Demo Videos:
    1919- [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.
    2022- [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.
    2223- [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.
    2324- [WooCommerce Add-On](https://www.youtube.com/watch?v=WsqAppHRGdA) - Display product cards, recommend items, guide users to checkout, and supercharge your store.
     
    4647
    4748- **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:  
    4950  - **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. 
    5051  - **Live Agent Transfer via Slack**: Seamlessly connect users to live agents for real-time support. 
    5152  - **PDF Content Processing**: Enable in-chat processing of PDF content for richer interactions. 
    5253  - **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. 
    5355  - **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.
    5456
     
    5759
    5860- **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. 
    6062- **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 Customization 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
    6264- **Chat Moderation (Pro Only)** – Maintain a secure chat environment with advanced moderation tools, including email and IP banning—available only for Pro users. 
    6365- **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. 
    6567- **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.
    6668
    6769The MxChat Add-On Ecosystem offers flexibility to customize and scale your chatbot experience. Stay tuned for more add-ons coming soon!
    6870
    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.
    7475
    7576### Advanced Action Recognition
     
    175176== Changelog ==
    176177
     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
    177183= 2.2.4 =
    178184- Critical update to error when working with large databases in Pinecone.
     
    510516== Upgrade Notice ==
    511517
    512 = 2.2.4 =
    513 Critical update to error when working with large databases in Pinecone.
     518= 2.2.5 =
     519New streaming response support for OpenAI and Claude, plus important fixes for Slack live agent messaging and Pinecone UI display.
    514520
    515521== License & Warranty ==
Note: See TracChangeset for help on using the changeset viewer.