Plugin Directory

Changeset 3379814


Ignore:
Timestamp:
10/17/2025 01:32:41 AM (4 months ago)
Author:
mxchat
Message:

2.4.8 brings OpenRouter integration and new claude models (Sonnet 4.5, haiku 4.5, Opus 4.1).

Location:
mxchat-basic
Files:
103 added
10 edited

Legend:

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

    r3367268 r3379814  
    7575    // Handle special cases
    7676    switch ($name) {
     77        case 'model':
     78            //error_log('MXChat Save: Processing model selection');
     79            //error_log('MXChat Save: Model value received: ' . $value);
     80            //error_log('MXChat Save: Value type: ' . gettype($value));
     81            //error_log('MXChat Save: Value length: ' . strlen($value));
     82            //error_log('MXChat Save: Value === "openrouter": ' . ($value === 'openrouter' ? 'YES' : 'NO'));
     83           
     84            // Allow 'openrouter' or validate against whitelist
     85            if ($value === 'openrouter') {
     86                //error_log('MXChat Save: Setting model to openrouter');
     87                $options['model'] = 'openrouter';
     88            } else {
     89                //error_log('MXChat Save: Checking against whitelist');
     90                 $allowed_models = array(
     91                            'gemini-2.0-flash', 'gemini-2.0-flash-lite', 'gemini-1.5-pro', 'gemini-1.5-flash',
     92                            'grok-4-0709', 'grok-3-beta', 'grok-3-fast-beta', 'grok-3-mini-beta',
     93                            'grok-3-mini-fast-beta', 'grok-2',
     94                            'deepseek-chat',
     95                            'claude-sonnet-4-5-20250929', 'claude-opus-4-1-20250805', 'claude-haiku-4-5-20251001',
     96                            'claude-opus-4-20250514', 'claude-sonnet-4-20250514', 'claude-3-7-sonnet-20250219',
     97                            'claude-3-5-sonnet-20241022', 'claude-3-opus-20240229', 'claude-3-sonnet-20240229',
     98                            'claude-3-haiku-20240307',
     99                            'gpt-5', 'gpt-5-mini', 'gpt-5-nano', 'gpt-4.1-2025-04-14',
     100                            'gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'gpt-4', 'gpt-3.5-turbo',
     101                        );
     102               
     103                //error_log('MXChat Save: in_array result: ' . (in_array($value, $allowed_models) ? 'YES' : 'NO'));
     104               
     105                if (in_array($value, $allowed_models)) {
     106                    //error_log('MXChat Save: Model is in whitelist, saving');
     107                    $options['model'] = sanitize_text_field($value);
     108                } else {
     109                    //error_log('MXChat Save: Invalid model rejected: ' . $value);
     110                    //error_log('MXChat Save: Allowed models: ' . print_r($allowed_models, true));
     111                    wp_send_json_error(['message' => esc_html__('Invalid model selected', 'mxchat')]);
     112                    return;
     113                }
     114            }
     115            break;
     116           
     117        case 'openrouter_selected_model':
     118            //error_log('MXChat Save: Processing OpenRouter model: ' . $value);
     119            $options['openrouter_selected_model'] = sanitize_text_field($value);
     120            // Force immediate save for new keys
     121            //error_log('MXChat Save: OpenRouter model saved immediately');
     122            break;
     123       
     124        case 'openrouter_selected_model_name':
     125            //error_log('MXChat Save: Processing OpenRouter model name: ' . $value);
     126            $options['openrouter_selected_model_name'] = sanitize_text_field($value);
     127            // Force immediate save for new keys
     128            //error_log('MXChat Save: OpenRouter model name saved immediately');
     129            break;
     130           
     131        case 'openrouter_api_key':
     132            //error_log('MXChat Save: Processing OpenRouter API key');
     133            $options['openrouter_api_key'] = sanitize_text_field($value);
     134            break;
     135           
     136        // REMOVED DUPLICATE case 'openrouter_selected_model_name' HERE!
     137           
    77138        case 'additional_popular_questions':
    78139            //error_log('MXChat Save: Processing additional_popular_questions');
  • mxchat-basic/trunk/css/admin-style.css

    r3364278 r3379814  
    53665366    color: #1e40af;
    53675367}
     5368
     5369
     5370/* Always show OpenRouter API key field */
     5371tr.mxchat-setting-row[data-provider="openrouter"],
     5372.mxchat-openrouter-wrapper {
     5373    display: table-row !important;
     5374}
  • mxchat-basic/trunk/includes/class-mxchat-addons.php

    r3378505 r3379814  
    191191     */
    192192    public function enqueue_styles() {
    193         $plugin_version = '2.4.7';
     193        $plugin_version = '2.4.8';
    194194
    195195        wp_enqueue_style(
  • mxchat-basic/trunk/includes/class-mxchat-admin.php

    r3378505 r3379814  
    4949        add_action('wp_ajax_mxchat_test_streaming', [$this, 'mxchat_handle_test_streaming']); // Keep existing as fallback
    5050
    51     add_action('wp_ajax_mxchat_save_selected_bot', array($this, 'mxchat_save_selected_bot'));
     51        add_action('wp_ajax_mxchat_save_selected_bot', array($this, 'mxchat_save_selected_bot'));
     52        add_action('wp_ajax_mxchat_fetch_openrouter_models', array($this, 'fetch_openrouter_models'));
    5253    }
    5354
     
    15751576$pinecone_api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
    15761577
    1577 error_log('DEBUG: Bot ' . $current_bot_id . ' - Use Pinecone: ' . ($use_pinecone ? 'YES' : 'NO'));
    1578 error_log('DEBUG: Bot ' . $current_bot_id . ' - Has API Key: ' . (!empty($pinecone_api_key) ? 'YES' : 'NO'));
    1579 error_log('DEBUG: Bot ' . $current_bot_id . ' - Host: ' . ($pinecone_options['mxchat_pinecone_host'] ?? 'NOT SET'));
    1580 error_log('DEBUG: Bot ' . $current_bot_id . ' - Namespace: ' . ($pinecone_options['mxchat_pinecone_namespace'] ?? 'NOT SET'));
     1578//error_log('DEBUG: Bot ' . $current_bot_id . ' - Use Pinecone: ' . ($use_pinecone ? 'YES' : 'NO'));
     1579//error_log('DEBUG: Bot ' . $current_bot_id . ' - Has API Key: ' . (!empty($pinecone_api_key) ? 'YES' : 'NO'));
     1580//error_log('DEBUG: Bot ' . $current_bot_id . ' - Host: ' . ($pinecone_options['mxchat_pinecone_host'] ?? 'NOT SET'));
     1581//error_log('DEBUG: Bot ' . $current_bot_id . ' - Namespace: ' . ($pinecone_options['mxchat_pinecone_namespace'] ?? 'NOT SET'));
    15811582
    15821583if ($use_pinecone && !empty($pinecone_api_key)) {
    1583     error_log('DEBUG: Using PINECONE data source with bot-specific config');
     1584    //error_log('DEBUG: Using PINECONE data source with bot-specific config');
    15841585    // PINECONE DATA SOURCE
    15851586    $data_source = 'pinecone';
     
    15891590
    15901591    // TEMPORARY DEBUG - Add this right after the fetch call
    1591     error_log('=== DEBUG: Bot switching issue ===');
    1592     error_log('Current bot ID: ' . $current_bot_id);
    1593     error_log('Use Pinecone: ' . ($use_pinecone ? 'YES' : 'NO'));
    1594     error_log('Records returned: ' . count($records['data'] ?? []));
    1595     error_log('Total records: ' . ($records['total'] ?? 0));
     1592    //error_log('=== DEBUG: Bot switching issue ===');
     1593    //error_log('Current bot ID: ' . $current_bot_id);
     1594    //error_log('Use Pinecone: ' . ($use_pinecone ? 'YES' : 'NO'));
     1595    //error_log('Records returned: ' . count($records['data'] ?? []));
     1596    //error_log('Total records: ' . ($records['total'] ?? 0));
    15961597
    15971598    // Check first few records to see their bot_id
     
    15991600        foreach (array_slice($records['data'], 0, 3) as $i => $record) {
    16001601            $record_bot_id = $record->bot_id ?? 'NOT_SET';
    1601             error_log('Record ' . ($i+1) . ' bot_id: ' . $record_bot_id . ', content preview: ' . substr($record->article_content ?? '', 0, 30) . '...');
     1602            //error_log('Record ' . ($i+1) . ' bot_id: ' . $record_bot_id . ', content preview: ' . substr($record->article_content ?? '', 0, 30) . '...');
    16021603        }
    16031604    }
    1604     error_log('=== END DEBUG ===');
     1605    //error_log('=== END DEBUG ===');
    16051606
    16061607    $total_records = $records['total'] ?? 0;
     
    16121613
    16131614} else {
    1614     error_log('DEBUG: Using WORDPRESS DB data source');
     1615    //error_log('DEBUG: Using WORDPRESS DB data source');
    16151616    // WORDPRESS DB DATA SOURCE (your existing logic)
    16161617    $data_source = 'wordpress';
     
    27432744 */
    27442745private function get_bot_pinecone_config($bot_id = 'default') {
    2745     error_log("MXCHAT DEBUG: get_bot_pinecone_config called for bot: " . $bot_id);
     2746    //error_log("MXCHAT DEBUG: get_bot_pinecone_config called for bot: " . $bot_id);
    27462747   
    27472748    // If default bot or multi-bot add-on not active, use default Pinecone config
    27482749    if ($bot_id === 'default' || !class_exists('MxChat_Multi_Bot_Manager')) {
    2749         error_log("MXCHAT DEBUG: Using default Pinecone config (no multi-bot or bot is 'default')");
     2750        //error_log("MXCHAT DEBUG: Using default Pinecone config (no multi-bot or bot is 'default')");
    27502751        $addon_options = get_option('mxchat_pinecone_addon_options', array());
    27512752        $config = array(
     
    27552756            'namespace' => $addon_options['mxchat_pinecone_namespace'] ?? ''
    27562757        );
    2757         error_log("MXCHAT DEBUG: Default config - use_pinecone: " . ($config['use_pinecone'] ? 'true' : 'false'));
     2758        //error_log("MXCHAT DEBUG: Default config - use_pinecone: " . ($config['use_pinecone'] ? 'true' : 'false'));
    27582759        return $config;
    27592760    }
    27602761   
    2761     error_log("MXCHAT DEBUG: Calling filter 'mxchat_get_bot_pinecone_config' for bot: " . $bot_id);
     2762    //error_log("MXCHAT DEBUG: Calling filter 'mxchat_get_bot_pinecone_config' for bot: " . $bot_id);
    27622763   
    27632764    // Hook for multi-bot add-on to provide bot-specific Pinecone config
     
    27652766   
    27662767    if (!empty($bot_pinecone_config)) {
    2767         error_log("MXCHAT DEBUG: Got bot-specific config from filter");
    2768         error_log("  - use_pinecone: " . (isset($bot_pinecone_config['use_pinecone']) ? ($bot_pinecone_config['use_pinecone'] ? 'true' : 'false') : 'not set'));
    2769         error_log("  - host: " . ($bot_pinecone_config['host'] ?? 'not set'));
    2770         error_log("  - namespace: " . ($bot_pinecone_config['namespace'] ?? 'not set'));
     2768        //error_log("MXCHAT DEBUG: Got bot-specific config from filter");
     2769        //error_log("  - use_pinecone: " . (isset($bot_pinecone_config['use_pinecone']) ? ($bot_pinecone_config['use_pinecone'] ? 'true' : 'false') : 'not set'));
     2770        //error_log("  - host: " . ($bot_pinecone_config['host'] ?? 'not set'));
     2771        //error_log("  - namespace: " . ($bot_pinecone_config['namespace'] ?? 'not set'));
    27712772    } else {
    2772         error_log("MXCHAT DEBUG: Filter returned empty config!");
     2773        //error_log("MXCHAT DEBUG: Filter returned empty config!");
    27732774    }
    27742775   
     
    43214322        'mxchat_chatbot_section'
    43224323    );
     4324   
     4325   
     4326    add_settings_field(
     4327        'openrouter_api_key',
     4328        esc_html__('OpenRouter API Key', 'mxchat'),
     4329        array($this, 'openrouter_api_key_callback'),
     4330        'mxchat-chatbot',
     4331        'mxchat_chatbot_section',
     4332        array(
     4333            'class' => 'mxchat-setting-row mxchat-openrouter-key-row', // Special class so we can always show it
     4334            'data-provider' => 'openrouter'
     4335        )
     4336    );
    43234337
    43244338    add_settings_field(
     
    50625076    echo '<button type="button" id="toggleGeminiApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
    50635077    echo '<p class="description api-key-notice">' . esc_html__('Required for Google Gemini models. Get your API key from Google AI Studio.', 'mxchat') . '</p>';
     5078    echo '</div>';
     5079}
     5080
     5081
     5082// OpenRouter API Key
     5083public function openrouter_api_key_callback() {
     5084    $openrouterApiKey = isset($this->options['openrouter_api_key']) ? esc_attr($this->options['openrouter_api_key']) : '';
     5085    echo '<div class="api-key-wrapper" data-provider="openrouter">';
     5086    echo '<input type="password" id="openrouter_api_key" name="openrouter_api_key" value="' . $openrouterApiKey . '" class="regular-text" autocomplete="off" />';
     5087    echo '<button type="button" id="toggleOpenRouterApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
     5088    echo '<p class="description api-key-notice">' . esc_html__('Required only if using OpenRouter. Get your API key from OpenRouter.ai', 'mxchat') . '</p>';
    50645089    echo '</div>';
    50655090}
     
    52625287    // Define available models grouped by provider
    52635288    $models = array(
     5289        esc_html__('OpenRouter (100+ Models)', 'mxchat') => array(
     5290            'openrouter' => esc_html__('OpenRouter - Select after entering API key', 'mxchat'),
     5291        ),
     5292        esc_html__('Google Gemini Models', 'mxchat') => array(
     5293            'gemini-2.0-flash' => esc_html__('Gemini 2.0 Flash (Next-Gen Features)', 'mxchat'),
     5294            'gemini-2.0-flash-lite' => esc_html__('Gemini 2.0 Flash-Lite (Cost-Efficient)', 'mxchat'),
     5295            'gemini-1.5-pro' => esc_html__('Gemini 1.5 Pro (Complex Reasoning)', 'mxchat'),
     5296            'gemini-1.5-flash' => esc_html__('Gemini 1.5 Flash (Fast & Versatile)', 'mxchat'),
     5297        ),
    52645298        esc_html__('Google Gemini Models', 'mxchat') => array(
    52655299            'gemini-2.0-flash' => esc_html__('Gemini 2.0 Flash (Next-Gen Features)', 'mxchat'),
     
    52805314        ),
    52815315        esc_html__('Claude Models', 'mxchat') => array(
     5316            'claude-sonnet-4-5-20250929' => esc_html__('Claude Sonnet 4.5 (Best for Agents & Coding)', 'mxchat'),
     5317            'claude-opus-4-1-20250805' => esc_html__('Claude Opus 4.1 (Exceptional for Complex Tasks)', 'mxchat'),
     5318            'claude-haiku-4-5-20251001' => esc_html__('Claude Haiku 4.5 (Fastest & Most Intelligent)', 'mxchat'),
    52825319            'claude-opus-4-20250514' => esc_html__('Claude 4 Opus (Most Capable)', 'mxchat'),
    52835320            'claude-sonnet-4-20250514' => esc_html__('Claude 4 Sonnet (High Performance)', 'mxchat'),
     
    53155352
    53165353        foreach ($group_models as $model_value => $model_label) {
    5317             // All models enabled - no disabled attribute or Pro Only label
    53185354            echo '<option value="' . esc_attr($model_value) . '" ' . selected($selected_model, $model_value, false) . '>' . esc_html($model_label) . '</option>';
    53195355        }
     
    53225358    }
    53235359
    5324     // Close the select dropdown
    53255360    echo '</select>';
    53265361
    5327     // Updated description to remove mention of Pro-only models
    53285362    echo '<p class="description">' . esc_html__('Select the AI model your chatbot will use for chatting.', 'mxchat') . '</p>';
    5329 }
    5330 
     5363   
     5364    // Add a note for OpenRouter
     5365    echo '<p class="description" id="openrouter-model-note" style="display:none; color: #d63638; font-weight: 500;">';
     5366    echo '<span class="dashicons dashicons-info" style="font-size: 16px; vertical-align: middle;"></span> ';
     5367    echo esc_html__('After entering your OpenRouter API key above, click the button below to load available models.', 'mxchat');
     5368    echo '</p>';
     5369 // ADD THESE HIDDEN FIELDS RIGHT HERE:
     5370    $openrouter_model = isset($this->options['openrouter_selected_model']) ? esc_attr($this->options['openrouter_selected_model']) : '';
     5371    $openrouter_model_name = isset($this->options['openrouter_selected_model_name']) ? esc_attr($this->options['openrouter_selected_model_name']) : '';
     5372   
     5373    echo '<input type="hidden" id="openrouter_selected_model" name="openrouter_selected_model" value="' . $openrouter_model . '" />';
     5374    echo '<input type="hidden" id="openrouter_selected_model_name" name="openrouter_selected_model_name" value="' . $openrouter_model_name . '" />';
     5375}
    53315376
    53325377// Update your existing callback method
     
    53575402}
    53585403
     5404// AJAX handler to fetch OpenRouter models
     5405public function fetch_openrouter_models() {
     5406    check_ajax_referer('mxchat_fetch_openrouter_models', 'nonce');
     5407   
     5408    $api_key = isset($_POST['api_key']) ? sanitize_text_field($_POST['api_key']) : '';
     5409   
     5410    if (empty($api_key)) {
     5411        wp_send_json_error(array('message' => 'API key is required'));
     5412    }
     5413   
     5414    $response = wp_remote_get('https://openrouter.ai/api/v1/models', array(
     5415        'headers' => array(
     5416            'Authorization' => 'Bearer ' . $api_key,
     5417            'Content-Type' => 'application/json',
     5418        ),
     5419        'timeout' => 15,
     5420    ));
     5421   
     5422    if (is_wp_error($response)) {
     5423        wp_send_json_error(array('message' => $response->get_error_message()));
     5424    }
     5425   
     5426    $body = wp_remote_retrieve_body($response);
     5427    $data = json_decode($body, true);
     5428   
     5429    if (isset($data['data']) && is_array($data['data'])) {
     5430        // Format the models for the frontend
     5431        $models = array_map(function($model) {
     5432            return array(
     5433                'id' => $model['id'],
     5434                'name' => $model['name'] ?? $model['id'],
     5435                'description' => $model['description'] ?? '',
     5436                'context_length' => $model['context_length'] ?? 0,
     5437                'pricing' => array(
     5438                    'prompt' => $model['pricing']['prompt'] ?? 0,
     5439                    'completion' => $model['pricing']['completion'] ?? 0,
     5440                ),
     5441            );
     5442        }, $data['data']);
     5443       
     5444        wp_send_json_success(array('models' => $models));
     5445    } else {
     5446        wp_send_json_error(array('message' => 'Invalid response from OpenRouter'));
     5447    }
     5448}
    53595449
    53605450// Callback function for embedding model selection
     
    53835473    }
    53845474    echo '</select>';
    5385     echo '<p class="description"><span class="red-warning">IMPORTANT:</span> Select the model for vector embeddings. Changing models is not recommended; if you do, you must delete all existing knowledge & intent data and reconfigure them.</p>';
     5475    echo '<p class="description"><span class="red-warning">IMPORTANT:</span> A vector embedding model is required for MxChat to function. Changing models is not recommended; if you do, you must delete all existing knowledge & intent data and reconfigure them.</p>';
    53865476}
    53875477
     
    65316621public function mxchat_enqueue_admin_assets() {
    65326622    // Get plugin version (define this in your main plugin file)
    6533     $version = defined('MXCHAT_VERSION') ? MXCHAT_VERSION : '2.4.7';
     6623    $version = defined('MXCHAT_VERSION') ? MXCHAT_VERSION : '2.4.8';
    65346624
    65356625    // Use file modification time for development (remove in production)
     
    66116701        'edit_intent_nonce' => wp_create_nonce('mxchat_edit_intent'),
    66126702        'toggle_action_nonce' => wp_create_nonce('mxchat_actions_nonce'),
     6703        'fetch_openrouter_models_nonce' => wp_create_nonce('mxchat_fetch_openrouter_models'), // ADD THIS LINE
    66136704        'is_activated' => $this->is_activated ? '1' : '0',
    66146705        'status_refresh_interval' => 5000,
    66156706        'prompts_setting_nonce' => wp_create_nonce('mxchat_prompts_setting_nonce'),
    6616         'ajaxurl' => admin_url('admin-ajax.php') // For jQuery fallback
     6707        'ajaxurl' => admin_url('admin-ajax.php')
    66176708    );
    66186709
     
    68856976
    68866977if (isset($input['model'])) {
    6887     $allowed_models = array(
    6888         'gemini-2.0-flash',
    6889         'gemini-2.0-flash-lite',
    6890         'gemini-1.5-pro',
    6891         'gemini-1.5-flash',
    6892 
    6893         'grok-4-0709',  // New model: Grok 4 added here
    6894         'grok-3-beta',
    6895         'grok-3-fast-beta',
    6896         'grok-3-mini-beta',
    6897         'grok-3-mini-fast-beta',
    6898         'grok-2',
    6899 
    6900         'deepseek-chat',
    6901 
    6902         'claude-opus-4-20250514',
    6903         'claude-sonnet-4-20250514',
    6904         'claude-3-7-sonnet-20250219',
    6905         'claude-3-5-sonnet-20241022',
    6906         'claude-3-opus-20240229',
    6907         'claude-3-sonnet-20240229',
    6908         'claude-3-haiku-20240307',
    6909 
    6910         // NEW: GPT-5 family
    6911         'gpt-5',
    6912         'gpt-5-mini',
    6913         'gpt-5-nano',
    6914 
    6915         'gpt-4o',
    6916         'gpt-4.1-2025-04-14',
    6917         'gpt-4o-mini',
    6918         'gpt-4-turbo',
    6919         'gpt-4',
    6920         'gpt-3.5-turbo',
    6921     );
    6922     if (in_array($input['model'], $allowed_models)) {
    6923         $new_input['model'] = sanitize_text_field($input['model']);
    6924     }
    6925 }
     6978    // If model is 'openrouter', always allow it
     6979    if ($input['model'] === 'openrouter') {
     6980        $new_input['model'] = 'openrouter';
     6981    } else {
     6982        // For other models, validate against whitelist
     6983        $allowed_models = array(
     6984            'gemini-2.0-flash',
     6985            'gemini-2.0-flash-lite',
     6986            'gemini-1.5-pro',
     6987            'gemini-1.5-flash',
     6988            'grok-4-0709',
     6989            'grok-3-beta',
     6990            'grok-3-fast-beta',
     6991            'grok-3-mini-beta',
     6992            'grok-3-mini-fast-beta',
     6993            'grok-2',
     6994            'deepseek-chat',
     6995            'claude-sonnet-4-5-20250929',
     6996            'claude-opus-4-1-20250805',
     6997            'claude-haiku-4-5-20251001',
     6998            'claude-opus-4-20250514',
     6999            'claude-sonnet-4-20250514',
     7000            'claude-3-7-sonnet-20250219',
     7001            'claude-3-5-sonnet-20241022',
     7002            'claude-3-opus-20240229',
     7003            'claude-3-sonnet-20240229',
     7004            'claude-3-haiku-20240307',
     7005            'gpt-5',
     7006            'gpt-5-mini',
     7007            'gpt-5-nano',
     7008            'gpt-4.1-2025-04-14',
     7009            'gpt-4o',
     7010            'gpt-4o-mini',
     7011            'gpt-4-turbo',
     7012            'gpt-4',
     7013            'gpt-3.5-turbo',
     7014        );
     7015       
     7016        if (in_array($input['model'], $allowed_models)) {
     7017            $new_input['model'] = sanitize_text_field($input['model']);
     7018        }
     7019    }
     7020}
     7021   
     7022if (isset($input['openrouter_selected_model'])) {
     7023    // Just sanitize it, don't validate against a whitelist
     7024    $new_input['openrouter_selected_model'] = sanitize_text_field($input['openrouter_selected_model']);
     7025}
     7026
     7027// ADD THIS:
     7028if (isset($input['openrouter_selected_model_name'])) {
     7029    $new_input['openrouter_selected_model_name'] = sanitize_text_field($input['openrouter_selected_model_name']);
     7030}
     7031   
     7032    if (isset($input['openrouter_api_key'])) {
     7033        $new_input['openrouter_api_key'] = sanitize_text_field($input['openrouter_api_key']);
     7034    }
    69267035
    69277036
     
    72057314 */
    72067315public function mxchat_handle_delete_all_prompts() {
    7207     error_log('=== DELETE ALL DEBUG START ===');
     7316    //error_log('=== DELETE ALL DEBUG START ===');
    72087317   
    72097318    // Verify nonce
    72107319    if (!isset($_POST['mxchat_delete_all_prompts_nonce']) || !wp_verify_nonce($_POST['mxchat_delete_all_prompts_nonce'], 'mxchat_delete_all_prompts_action')) {
    7211         error_log('DEBUG: Nonce verification failed');
     7320        //error_log('DEBUG: Nonce verification failed');
    72127321        wp_die(__('Nonce verification failed.', 'mxchat'));
    72137322    }
     
    72157324    // Check permissions
    72167325    if (!current_user_can('manage_options')) {
    7217         error_log('DEBUG: Permission check failed');
     7326        //error_log('DEBUG: Permission check failed');
    72187327        wp_die(__('You do not have sufficient permissions to delete all prompts.', 'mxchat'));
    72197328    }
     
    72217330    // Get bot_id from POST data
    72227331    $bot_id = isset($_POST['bot_id']) ? sanitize_text_field($_POST['bot_id']) : 'default';
    7223     error_log('DEBUG: Bot ID from POST: ' . $bot_id);
     7332    //error_log('DEBUG: Bot ID from POST: ' . $bot_id);
    72247333   
    72257334    $success = true;
     
    72307339    $pinecone_options = $pinecone_manager->mxchat_get_bot_pinecone_options($bot_id);
    72317340   
    7232     error_log('DEBUG: Pinecone options retrieved:');
    7233     error_log('  - Use Pinecone: ' . ($pinecone_options['mxchat_use_pinecone'] ?? 'NOT SET'));
    7234     error_log('  - API Key: ' . (empty($pinecone_options['mxchat_pinecone_api_key']) ? 'EMPTY' : 'SET'));
    7235     error_log('  - Host: ' . ($pinecone_options['mxchat_pinecone_host'] ?? 'NOT SET'));
     7341    //error_log('DEBUG: Pinecone options retrieved:');
     7342    //error_log('  - Use Pinecone: ' . ($pinecone_options['mxchat_use_pinecone'] ?? 'NOT SET'));
     7343    //error_log('  - API Key: ' . (empty($pinecone_options['mxchat_pinecone_api_key']) ? 'EMPTY' : 'SET'));
     7344    //error_log('  - Host: ' . ($pinecone_options['mxchat_pinecone_host'] ?? 'NOT SET'));
    72367345   
    72377346    $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
    72387347   
    72397348    if ($use_pinecone && !empty($pinecone_options['mxchat_pinecone_api_key'])) {
    7240         error_log('[MXCHAT-DELETE] Pinecone is enabled for bot ' . $bot_id . ' - deleting all from Pinecone');
     7349        //error_log('[MXCHAT-DELETE] Pinecone is enabled for bot ' . $bot_id . ' - deleting all from Pinecone');
    72417350       
    72427351        // Delete from bot-specific Pinecone index
    72437352        $result = $pinecone_manager->mxchat_delete_all_from_pinecone($pinecone_options);
    72447353       
    7245         error_log('DEBUG: Delete all result: ' . ($result['success'] ? 'SUCCESS' : 'FAILED'));
     7354        //error_log('DEBUG: Delete all result: ' . ($result['success'] ? 'SUCCESS' : 'FAILED'));
    72467355        if (!$result['success']) {
    7247             error_log('DEBUG: Delete all error: ' . $result['message']);
     7356            //error_log('DEBUG: Delete all error: ' . $result['message']);
    72487357        }
    72497358       
     
    72567365       
    72577366    } else {
    7258         error_log('[MXCHAT-DELETE] Pinecone not enabled for bot ' . $bot_id . ' - deleting from WordPress database');
     7367        //error_log('[MXCHAT-DELETE] Pinecone not enabled for bot ' . $bot_id . ' - deleting from WordPress database');
    72597368       
    72607369        // Delete from WordPress database
     
    72827391    // No cache clearing needed since we removed caching
    72837392
    7284     error_log('=== DELETE ALL DEBUG END ===');
     7393    //error_log('=== DELETE ALL DEBUG END ===');
    72857394   
    72867395    // Redirect back with a success message and bot_id
  • mxchat-basic/trunk/includes/class-mxchat-integrator.php

    r3378505 r3379814  
    13991399            $current_options['deepseek_api_key'] ?? $this->options['deepseek_api_key'],
    14001400            $current_options['gemini_api_key'] ?? $this->options['gemini_api_key'],
     1401            $current_options['openrouter_api_key'] ?? $this->options['openrouter_api_key'],
    14011402            $conversation_history,
    14021403            $is_streaming,
    14031404            $session_id,
    14041405            $testing_data,
    1405             $selected_model  // ADD THIS LINE
     1406            $selected_model
    14061407        );
    14071408       
     
    43104311    return 'default';
    43114312}
    4312 private function mxchat_generate_response($relevant_content, $api_key, $xai_api_key, $claude_api_key, $deepseek_api_key, $gemini_api_key, $conversation_history, $streaming = false, $session_id = '', $testing_data = null, $selected_model = 'gpt-4o') {
     4313private function mxchat_generate_response($relevant_content, $api_key, $xai_api_key, $claude_api_key, $deepseek_api_key, $gemini_api_key, $openrouter_api_key, $conversation_history, $streaming = false, $session_id = '', $testing_data = null, $selected_model = 'gpt-4o') {
    43134314    try {
    43144315        if (!$relevant_content) {
     
    43184319            ];
    43194320           
    4320             // Add testing data to error response if available
    43214321            if ($testing_data !== null) {
    43224322                $error_response['testing_data'] = $testing_data;
    4323                 //error_log("MxChat Testing: Added testing data to no_relevant_content error");
    43244323            }
    43254324           
     
    43274326        }
    43284327       
    4329         // Ensure conversation_history is an array
    43304328        if (!is_array($conversation_history)) {
    43314329            $conversation_history = array();
    43324330        }
    43334331       
     4332        // Check if this is an OpenRouter model
     4333        if ($selected_model === 'openrouter') {
     4334            // Get the actual OpenRouter model from options
     4335            $openrouter_selected_model = $this->options['openrouter_selected_model'] ?? '';
     4336           
     4337            if (empty($openrouter_selected_model)) {
     4338                $error_response = [
     4339                    'error' => esc_html__('No OpenRouter model selected. Please select a model in settings.', 'mxchat'),
     4340                    'error_code' => 'no_openrouter_model_selected'
     4341                ];
     4342                if ($testing_data !== null) {
     4343                    $error_response['testing_data'] = $testing_data;
     4344                }
     4345                return $error_response;
     4346            }
     4347           
     4348            if (empty($openrouter_api_key)) {
     4349                $error_response = [
     4350                    'error' => esc_html__('OpenRouter API key is not configured', 'mxchat'),
     4351                    'error_code' => 'missing_openrouter_api_key'
     4352                ];
     4353                if ($testing_data !== null) {
     4354                    $error_response['testing_data'] = $testing_data;
     4355                }
     4356                return $error_response;
     4357            }
     4358           
     4359            if ($streaming) {
     4360                return $this->mxchat_generate_response_openrouter_stream(
     4361                    $openrouter_selected_model,
     4362                    $openrouter_api_key,
     4363                    $conversation_history,
     4364                    $relevant_content,
     4365                    $session_id,
     4366                    $testing_data
     4367                );
     4368            } else {
     4369                $response = $this->mxchat_generate_response_openrouter(
     4370                    $openrouter_selected_model,
     4371                    $openrouter_api_key,
     4372                    $conversation_history,
     4373                    $relevant_content
     4374                );
     4375            }
     4376           
     4377            if (is_array($response) && isset($response['error'])) {
     4378                if ($testing_data !== null) {
     4379                    $response['testing_data'] = $testing_data;
     4380                }
     4381                return $response;
     4382            }
     4383           
     4384            return $response;
     4385        }
    43344386
    43354387        // Extract model prefix to determine the provider
     
    43764428                        $relevant_content,
    43774429                        $session_id,
    4378                         $testing_data  // Pass testing data
     4430                        $testing_data
    43794431                    );
    43804432                } else {
     
    44064458                        $relevant_content,
    44074459                        $session_id,
    4408                         $testing_data  // Pass testing data
     4460                        $testing_data
    44094461                    );
    44104462                } else {
     
    44364488                        $relevant_content,
    44374489                        $session_id,
    4438                         $testing_data  // Pass testing data
     4490                        $testing_data
    44394491                    );
    44404492                } else {
     
    44674519                        $relevant_content,
    44684520                        $session_id,
    4469                         $testing_data  // Pass testing data
     4521                        $testing_data
    44704522                    );
    44714523                } else {
     
    44804532               
    44814533            default:
    4482                 // Default to OpenAI for custom models or unrecognized prefixes
    44834534                if (empty($api_key)) {
    44844535                    $error_response = [
     
    44984549                        $relevant_content,
    44994550                        $session_id,
    4500                         $testing_data  // Pass testing data
     4551                        $testing_data
    45014552                    );
    45024553                } else {
     
    45114562        }
    45124563       
    4513         // Check if the response is an error array from the provider-specific function
    45144564        if (is_array($response) && isset($response['error'])) {
    4515             // Add testing data to error response if available
    45164565            if ($testing_data !== null) {
    45174566                $response['testing_data'] = $testing_data;
    4518                 //error_log("MxChat Testing: Added testing data to provider error response");
    4519             }
    4520             return $response; // Pass through the error with testing data
    4521         }
    4522        
    4523         // For successful non-streaming responses, we don't add testing data here
    4524         // because it will be added in the main handler
     4567            }
     4568            return $response;
     4569        }
     4570       
    45254571        return $response;
    45264572       
    45274573    } catch (Exception $e) {
    4528         //error_log('MXChat Error: ' . $e->getMessage());
    45294574        $error_response = [
    45304575            'error' => sprintf(esc_html__('An error occurred: %s', 'mxchat'), esc_html($e->getMessage())),
     
    45334578        ];
    45344579       
    4535         // Add testing data to exception response if available
    45364580        if ($testing_data !== null) {
    45374581            $error_response['testing_data'] = $testing_data;
    4538             //error_log("MxChat Testing: Added testing data to exception response");
    45394582        }
    45404583       
     
    45434586}
    45444587
     4588private function mxchat_generate_response_openrouter_stream($selected_model, $openrouter_api_key, $conversation_history, $relevant_content, $session_id, $testing_data = null) {
     4589    try {
     4590        $bot_id = $this->get_current_bot_id($session_id);
     4591        $system_prompt_instructions = $this->get_system_instructions($bot_id);
     4592       
     4593        if (!is_array($conversation_history)) {
     4594            $conversation_history = array();
     4595        }
     4596
     4597        $formatted_conversation = array();
     4598
     4599        $formatted_conversation[] = array(
     4600            'role' => 'system',
     4601            'content' => $system_prompt_instructions . " " . $relevant_content
     4602        );
     4603
     4604        foreach ($conversation_history as $message) {
     4605            if (is_array($message) && isset($message['role']) && isset($message['content'])) {
     4606                $role = $message['role'];
     4607                if ($role === 'bot' || $role === 'agent') {
     4608                    $role = 'assistant';
     4609                }
     4610                if (!in_array($role, ['system', 'assistant', 'user'])) {
     4611                    $role = 'user';
     4612                }
     4613                $formatted_conversation[] = array(
     4614                    'role' => $role,
     4615                    'content' => $message['content']
     4616                );
     4617            }
     4618        }
     4619
     4620        if (headers_sent() || !function_exists('curl_init')) {
     4621            $regular_response = $this->mxchat_generate_response_openrouter(
     4622                $selected_model,
     4623                $openrouter_api_key,
     4624                $conversation_history,
     4625                $relevant_content
     4626            );
     4627           
     4628            $response_data = [
     4629                'text' => $regular_response,
     4630                'html' => '',
     4631                'session_id' => $session_id
     4632            ];
     4633           
     4634            if ($testing_data !== null) {
     4635                $response_data['testing_data'] = $testing_data;
     4636            }
     4637           
     4638            header('Content-Type: application/json');
     4639            echo json_encode($response_data);
     4640            return true;
     4641        }
     4642
     4643        $body = json_encode([
     4644            'model' => $selected_model,
     4645            'messages' => $formatted_conversation,
     4646            'temperature' => 1,
     4647            'stream' => true
     4648        ]);
     4649
     4650        $ch = curl_init();
     4651        curl_setopt($ch, CURLOPT_URL, 'https://openrouter.ai/api/v1/chat/completions');
     4652        curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
     4653        curl_setopt($ch, CURLOPT_POST, true);
     4654        curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
     4655        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
     4656            'Content-Type: application/json',
     4657            'Authorization: Bearer ' . $openrouter_api_key,
     4658            'HTTP-Referer: ' . home_url(),
     4659            'X-Title: ' . get_bloginfo('name')
     4660        ));
     4661        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
     4662        curl_setopt($ch, CURLOPT_TIMEOUT, 60);
     4663       
     4664        $full_response = '';
     4665        $stream_started = false;
     4666        $buffer = '';
     4667       
     4668        curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($ch, $data) use (&$full_response, &$stream_started, &$buffer, $testing_data) {
     4669            if (!$stream_started && $testing_data !== null) {
     4670                echo "data: " . json_encode(['testing_data' => $testing_data]) . "\n\n";
     4671                flush();
     4672                $stream_started = true;
     4673            }
     4674           
     4675            $buffer .= $data;
     4676            $lines = explode("\n", $buffer);
     4677            $buffer = array_pop($lines);
     4678           
     4679            foreach ($lines as $line) {
     4680                if (trim($line) === '') {
     4681                    continue;
     4682                }
     4683               
     4684                if (strpos($line, 'data: ') !== 0) {
     4685                    continue;
     4686                }
     4687               
     4688                $json_str = substr($line, 6);
     4689               
     4690                if (trim($json_str) === '[DONE]') {
     4691                    echo "data: [DONE]\n\n";
     4692                    flush();
     4693                    continue;
     4694                }
     4695               
     4696                $json = json_decode(trim($json_str), true);
     4697                if ($json && isset($json['choices'][0]['delta']['content'])) {
     4698                    $content = $json['choices'][0]['delta']['content'];
     4699                    $full_response .= $content;
     4700                   
     4701                    echo "data: " . json_encode(['content' => $content]) . "\n\n";
     4702                    flush();
     4703                }
     4704            }
     4705           
     4706            return strlen($data);
     4707        });
     4708       
     4709        $response = curl_exec($ch);
     4710        $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
     4711       
     4712        if (curl_errno($ch) || $http_code !== 200) {
     4713            curl_close($ch);
     4714           
     4715            $regular_response = $this->mxchat_generate_response_openrouter(
     4716                $selected_model,
     4717                $openrouter_api_key,
     4718                $conversation_history,
     4719                $relevant_content
     4720            );
     4721           
     4722            $response_data = [
     4723                'text' => $regular_response,
     4724                'html' => '',
     4725                'session_id' => $session_id
     4726            ];
     4727           
     4728            if ($testing_data !== null) {
     4729                $response_data['testing_data'] = $testing_data;
     4730            }
     4731           
     4732            header('Content-Type: application/json');
     4733            echo json_encode($response_data);
     4734            return true;
     4735        }
     4736       
     4737        curl_close($ch);
     4738       
     4739        if (!empty($full_response) && !empty($session_id)) {
     4740            $this->mxchat_save_chat_message($session_id, 'bot', $full_response);
     4741        }
     4742       
     4743        return true;
     4744       
     4745    } catch (Exception $e) {
     4746        $regular_response = $this->mxchat_generate_response_openrouter(
     4747            $selected_model,
     4748            $openrouter_api_key,
     4749            $conversation_history,
     4750            $relevant_content
     4751        );
     4752       
     4753        $response_data = [
     4754            'text' => $regular_response,
     4755            'html' => '',
     4756            'session_id' => $session_id
     4757        ];
     4758       
     4759        if ($testing_data !== null) {
     4760            $response_data['testing_data'] = $testing_data;
     4761        }
     4762       
     4763        header('Content-Type: application/json');
     4764        echo json_encode($response_data);
     4765        return true;
     4766    }
     4767}
    45454768private function mxchat_generate_response_openai_stream($selected_model, $api_key, $conversation_history, $relevant_content, $session_id, $testing_data = null) {
    45464769    try {
     
    54245647}
    54255648
     5649
     5650private function mxchat_generate_response_openrouter($selected_model, $openrouter_api_key, $conversation_history, $relevant_content) {
     5651    try {
     5652        if (!is_array($conversation_history)) {
     5653            $conversation_history = array();
     5654        }
     5655
     5656        $bot_id = $this->get_current_bot_id('');
     5657        $system_prompt_instructions = $this->get_system_instructions($bot_id);
     5658       
     5659        $formatted_conversation = array();
     5660
     5661        $formatted_conversation[] = array(
     5662            'role' => 'system',
     5663            'content' => $system_prompt_instructions . " " . $relevant_content
     5664        );
     5665
     5666        foreach ($conversation_history as $message) {
     5667            if (is_array($message) && isset($message['role']) && isset($message['content'])) {
     5668                $role = $message['role'];
     5669
     5670                if ($role === 'bot' || $role === 'agent') {
     5671                    $role = 'assistant';
     5672                }
     5673                if (!in_array($role, ['system', 'assistant', 'user'])) {
     5674                    $role = 'user';
     5675                }
     5676
     5677                $formatted_conversation[] = array(
     5678                    'role' => $role,
     5679                    'content' => $message['content']
     5680                );
     5681            }
     5682        }
     5683
     5684        $body = json_encode([
     5685            'model' => $selected_model,
     5686            'messages' => $formatted_conversation,
     5687            'temperature' => 1,
     5688        ]);
     5689
     5690        $args = [
     5691            'body'        => $body,
     5692            'headers'     => [
     5693                'Content-Type' => 'application/json',
     5694                'Authorization' => 'Bearer ' . $openrouter_api_key,
     5695                'HTTP-Referer' => home_url(),
     5696                'X-Title' => get_bloginfo('name'),
     5697            ],
     5698            'timeout'     => 60,
     5699            'redirection' => 5,
     5700            'blocking'    => true,
     5701            'httpversion' => '1.0',
     5702            'sslverify'   => true,
     5703        ];
     5704
     5705        $response = wp_remote_post('https://openrouter.ai/api/v1/chat/completions', $args);
     5706
     5707        if (is_wp_error($response)) {
     5708            $error_message = $response->get_error_message();
     5709            return [
     5710                'error' => esc_html__('Connection error when contacting OpenRouter: ', 'mxchat') . esc_html($error_message),
     5711                'error_code' => 'openrouter_connection_error',
     5712                'provider' => 'openrouter'
     5713            ];
     5714        }
     5715
     5716        $status_code = wp_remote_retrieve_response_code($response);
     5717        if ($status_code !== 200) {
     5718            $response_body = wp_remote_retrieve_body($response);
     5719            $decoded_response = json_decode($response_body, true);
     5720           
     5721            $error_message = isset($decoded_response['error']['message'])
     5722                ? $decoded_response['error']['message']
     5723                : 'HTTP Error ' . $status_code;
     5724           
     5725            return [
     5726                'error' => esc_html__('OpenRouter API error: ', 'mxchat') . esc_html($error_message),
     5727                'error_code' => 'openrouter_api_error',
     5728                'provider' => 'openrouter',
     5729                'status_code' => $status_code
     5730            ];
     5731        }
     5732
     5733        $response_body = wp_remote_retrieve_body($response);
     5734        $decoded_response = json_decode($response_body, true);
     5735
     5736        if (isset($decoded_response['choices'][0]['message']['content'])) {
     5737            return trim($decoded_response['choices'][0]['message']['content']);
     5738        } else {
     5739            return [
     5740                'error' => esc_html__('Unexpected response format from OpenRouter.', 'mxchat'),
     5741                'error_code' => 'openrouter_response_format_error',
     5742                'provider' => 'openrouter'
     5743            ];
     5744        }
     5745    } catch (Exception $e) {
     5746        return [
     5747            'error' => esc_html__('System error when processing OpenRouter request: ', 'mxchat') . esc_html($e->getMessage()),
     5748            'error_code' => 'openrouter_exception',
     5749            'provider' => 'openrouter'
     5750        ];
     5751    }
     5752}
    54265753private function mxchat_generate_response_claude($selected_model, $claude_api_key, $conversation_history, $relevant_content) {
    54275754   
     
    63746701public function mxchat_enqueue_scripts_styles() {
    63756702    // Define version numbers for the styles and scripts
    6376     $chat_style_version = '2.4.7';
    6377     $chat_script_version = '2.4.7';
     6703    $chat_style_version = '2.4.8';
     6704    $chat_script_version = '2.4.8';
    63786705    // Enqueue the script
    63796706    wp_enqueue_script(
     
    69717298 * AJAX handler to get system information for testing panel
    69727299 */
     7300/**
     7301 * AJAX handler to get system information for testing panel
     7302 */
    69737303public function mxchat_get_system_info() {
    69747304    // Verify nonce for security
     
    69897319        : 'No system prompt configured';
    69907320   
    6991     // Get selected model - FIXED: Use $this->options instead of $current_options
     7321    // Get selected model
    69927322    $selected_model = isset($this->options['model']) ? $this->options['model'] : 'gpt-4o';
     7323   
     7324    // Check if OpenRouter is being used
     7325    $is_openrouter = ($selected_model === 'openrouter');
     7326    $openrouter_model = '';
     7327   
     7328    if ($is_openrouter) {
     7329        // Get the actual OpenRouter model that's selected
     7330        $openrouter_model = isset($this->options['openrouter_selected_model'])
     7331            ? $this->options['openrouter_selected_model']
     7332            : 'No OpenRouter model selected';
     7333       
     7334        // Update selected_model display to show both
     7335        $selected_model = 'OpenRouter: ' . $openrouter_model;
     7336    }
    69937337   
    69947338    // Get API key status (just check if they exist, don't expose the keys)
     
    69997343    $api_status['xai'] = !empty($this->options['xai_api_key']);
    70007344    $api_status['deepseek'] = !empty($this->options['deepseek_api_key']);
     7345    $api_status['openrouter'] = !empty($this->options['openrouter_api_key']);
    70017346   
    70027347    wp_send_json_success([
    70037348        'system_prompt' => $system_prompt,
    70047349        'selected_model' => $selected_model,
     7350        'is_openrouter' => $is_openrouter,
     7351        'openrouter_model' => $openrouter_model,
    70057352        'api_status' => $api_status
    70067353    ]);
  • mxchat-basic/trunk/js/chat-script.js

    r3367268 r3379814  
    941941}
    942942
    943 //Function to check if streaming is supported for the current model
    944943function isStreamingSupported(model) {
    945944    if (!model) return false;
    946945
    947     //console.log("Checking streaming support for model:", model); // Debug log
    948 
    949     // Get the model prefix
    950946    const modelPrefix = model.split('-')[0].toLowerCase();
    951    
    952     //console.log("Model prefix:", modelPrefix); // Debug log
    953 
    954     // Support streaming for OpenAI, Claude, Grok, and DeepSeek models
    955     const isSupported = modelPrefix === 'gpt' || modelPrefix === 'o1' || modelPrefix === 'claude' || modelPrefix === 'grok' || modelPrefix === 'deepseek';
    956    
    957     //console.log("Streaming supported:", isSupported); // Debug log
     947
     948    // Support streaming for OpenAI, Claude, Grok, DeepSeek, and OpenRouter models
     949    const isSupported = modelPrefix === 'gpt' ||
     950                        modelPrefix === 'o1' ||
     951                        modelPrefix === 'claude' ||
     952                        modelPrefix === 'grok' ||
     953                        modelPrefix === 'deepseek' ||
     954                        model === 'openrouter';  // Add this line - check full model name for OpenRouter
    958955   
    959956    return isSupported;
  • mxchat-basic/trunk/js/mxchat-admin.js

    r3364278 r3379814  
    391391
    392392        // Handle all input changes (including range slider)
    393          $autosaveSections.find('input, textarea, select').on('change', function() {
     393         $autosaveSections.find('input, textarea, select').not('#model, #openrouter_selected_model').on('change', function() {
    394394                    const $field = $(this);
    395395                    const name = $field.attr('name');
     
    736736    '#toggleBotTokenVisibility',
    737737    '#toggleDeepSeekApiKeyVisibility',
    738     '#toggleGeminiApiKeyVisibility' // Added Gemini toggle
     738    '#toggleGeminiApiKeyVisibility',
     739    '#toggleOpenRouterApiKeyVisibility'
    739740].forEach(toggleVisibility);
    740741
     
    861862}
    862863
    863 // Add this to your JavaScript file
    864864function setupMxChatModelSelector() {
    865865    const $modelSelect = $('#model');
     
    877877    function updateButtonText() {
    878878        const selectedModel = $modelSelect.val();
    879         const selectedModelText = $modelSelect.find('option:selected').text();
    880         $modelSelectorButton.text(selectedModelText);
    881     }
    882    
     879       
     880        // Check if OpenRouter is selected
     881        if (selectedModel === 'openrouter') {
     882            const openrouterModelId = $('#openrouter_selected_model').val();
     883            const openrouterModelName = $('#openrouter_selected_model_name').val();
     884           
     885            if (openrouterModelName && openrouterModelName.trim() !== '') {
     886                $modelSelectorButton.text('OpenRouter: ' + openrouterModelName);
     887            } else if (openrouterModelId && openrouterModelId.trim() !== '') {
     888                $modelSelectorButton.text('OpenRouter: ' + openrouterModelId);
     889            } else {
     890                $modelSelectorButton.text('OpenRouter - Select Model');
     891            }
     892        } else {
     893            const selectedModelText = $modelSelect.find('option:selected').text();
     894            $modelSelectorButton.text(selectedModelText);
     895        }
     896    }
     897
    883898    // Initialize button text
    884899    updateButtonText();
     
    896911                        <input type="text" id="mxchat_model_search_input" class="mxchat-model-search-input" placeholder="Search models...">
    897912                    </div>
    898                     <div class="mxchat-model-selector-categories">
    899                         <button class="mxchat-model-category-btn active" data-category="all">All</button>
    900                         <button class="mxchat-model-category-btn" data-category="gemini">Google Gemini</button>
    901                         <button class="mxchat-model-category-btn" data-category="openai">OpenAI</button>
    902                         <button class="mxchat-model-category-btn" data-category="claude">Claude</button>
    903                         <button class="mxchat-model-category-btn" data-category="xai">X.AI</button>
    904                         <button class="mxchat-model-category-btn" data-category="deepseek">DeepSeek</button>
    905                     </div>
     913                        <div class="mxchat-model-selector-categories">
     914                            <button class="mxchat-model-category-btn active" data-category="all">All</button>
     915                            <button class="mxchat-model-category-btn" data-category="openrouter">OpenRouter</button>
     916                            <button class="mxchat-model-category-btn" data-category="gemini">Google Gemini</button>
     917                            <button class="mxchat-model-category-btn" data-category="openai">OpenAI</button>
     918                            <button class="mxchat-model-category-btn" data-category="claude">Claude</button>
     919                            <button class="mxchat-model-category-btn" data-category="xai">X.AI</button>
     920                            <button class="mxchat-model-category-btn" data-category="deepseek">DeepSeek</button>
     921                        </div>
    906922                    <div class="mxchat-model-selector-grid" id="mxchat_models_grid"></div>
    907923                </div>
     
    915931    $('body').append(modelSelectorModal);
    916932   
    917 // Populate models grid
    918 // Populate models grid
    919 function populateModelsGrid(filter = '', category = 'all') {
    920     const $grid = $('#mxchat_models_grid');
    921     $grid.empty();
    922    
    923     const models = {
    924         gemini: [
    925             { value: 'gemini-2.0-flash', label: 'Gemini 2.0 Flash', description: 'Next-Gen features, speed & multimodal generation' },
    926             { value: 'gemini-2.0-flash-lite', label: 'Gemini 2.0 Flash-Lite', description: 'Cost-efficient with low latency' },
    927             { value: 'gemini-1.5-pro', label: 'Gemini 1.5 Pro', description: 'Complex reasoning tasks requiring more intelligence' },
    928             { value: 'gemini-1.5-flash', label: 'Gemini 1.5 Flash', description: 'Fast and versatile performance' },
    929         ],
    930         openai: [
    931             // NEW: GPT-5 family
    932             { value: 'gpt-5', label: 'GPT-5', description: 'Flagship for coding, reasoning, and agentic tasks across domains' },
    933             { value: 'gpt-5-mini', label: 'GPT-5 Mini', description: 'Faster, more cost-efficient for well-defined tasks and precise prompts' },
    934             { value: 'gpt-5-nano', label: 'GPT-5 Nano', description: 'Fastest and cheapest; ideal for summarization and classification' },
    935 
    936             // Existing OpenAI models
    937             { value: 'gpt-4.1-2025-04-14', label: 'GPT-4.1', description: 'Flagship model for complex tasks' },
    938             { value: 'gpt-4o', label: 'GPT-4o', description: 'Recommended for most use cases' },
    939             { value: 'gpt-4o-mini', label: 'GPT-4o Mini', description: 'Fast and lightweight' },
    940             { value: 'gpt-4-turbo', label: 'GPT-4 Turbo', description: 'High-performance model' },
    941             { value: 'gpt-4', label: 'GPT-4', description: 'High intelligence model' },
    942             { value: 'gpt-3.5-turbo', label: 'GPT-3.5 Turbo', description: 'Affordable and fast' },
    943         ],
    944         claude: [
    945             { value: 'claude-opus-4-20250514', label: 'Claude 4 Opus', description: 'Most capable Claude model' },
    946             { value: 'claude-sonnet-4-20250514', label: 'Claude 4 Sonnet', description: 'High performance' },
    947             { value: 'claude-3-7-sonnet-20250219', label: 'Claude 3.7 Sonnet', description: 'High intelligence' },
    948             { value: 'claude-3-5-sonnet-20241022', label: 'Claude 3.5 Sonnet', description: 'Intelligent and balanced' },
    949             { value: 'claude-3-opus-20240229', label: 'Claude 3 Opus', description: 'Highly complex tasks' },
    950             { value: 'claude-3-sonnet-20240229', label: 'Claude 3 Sonnet', description: 'Balanced performance' },
    951             { value: 'claude-3-haiku-20240307', label: 'Claude 3 Haiku', description: 'Fastest Claude model' },
    952         ],
    953         xai: [
    954             { value: 'grok-4-0709', label: 'Grok 4', description: 'Latest flagship model - unparalleled performance in natural language, math and reasoning' }, // Added Grok-4
    955             { value: 'grok-3-beta', label: 'Grok-3', description: 'Powerful model with 131K context' },
    956             { value: 'grok-3-fast-beta', label: 'Grok-3 Fast', description: 'High performance with faster responses' },
    957             { value: 'grok-3-mini-beta', label: 'Grok-3 Mini', description: 'Affordable model with good performance' },
    958             { value: 'grok-3-mini-fast-beta', label: 'Grok-3 Mini Fast', description: 'Quick and cost-effective' },
    959             { value: 'grok-2', label: 'Grok 2', description: 'Latest X.AI model' },
    960         ],
    961         deepseek: [
    962             { value: 'deepseek-chat', label: 'DeepSeek-V3', description: 'Advanced AI assistant' },
    963         ],
     933    // MOVE THIS OUTSIDE - Make it a property of the window object so it's accessible globally
     934    window.populateModelsGrid = function(filter = '', category = 'all') {
     935        const $grid = $('#mxchat_models_grid');
     936        $grid.empty();
     937       
     938        const models = {
     939            openrouter: [
     940                { value: 'openrouter', label: 'OpenRouter', description: 'Access 100+ models from multiple providers (add API key to browse)' }
     941            ],
     942            gemini: [
     943                { value: 'gemini-2.0-flash', label: 'Gemini 2.0 Flash', description: 'Next-Gen features, speed & multimodal generation' },
     944                { value: 'gemini-2.0-flash-lite', label: 'Gemini 2.0 Flash-Lite', description: 'Cost-efficient with low latency' },
     945                { value: 'gemini-1.5-pro', label: 'Gemini 1.5 Pro', description: 'Complex reasoning tasks requiring more intelligence' },
     946                { value: 'gemini-1.5-flash', label: 'Gemini 1.5 Flash', description: 'Fast and versatile performance' },
     947            ],
     948            openai: [
     949                { value: 'gpt-5', label: 'GPT-5', description: 'Flagship for coding, reasoning, and agentic tasks across domains' },
     950                { value: 'gpt-5-mini', label: 'GPT-5 Mini', description: 'Faster, more cost-efficient for well-defined tasks and precise prompts' },
     951                { value: 'gpt-5-nano', label: 'GPT-5 Nano', description: 'Fastest and cheapest; ideal for summarization and classification' },
     952                { value: 'gpt-4.1-2025-04-14', label: 'GPT-4.1', description: 'Flagship model for complex tasks' },
     953                { value: 'gpt-4o', label: 'GPT-4o', description: 'Recommended for most use cases' },
     954                { value: 'gpt-4o-mini', label: 'GPT-4o Mini', description: 'Fast and lightweight' },
     955                { value: 'gpt-4-turbo', label: 'GPT-4 Turbo', description: 'High-performance model' },
     956                { value: 'gpt-4', label: 'GPT-4', description: 'High intelligence model' },
     957                { value: 'gpt-3.5-turbo', label: 'GPT-3.5 Turbo', description: 'Affordable and fast' },
     958            ],
     959                claude: [
     960                    { value: 'claude-sonnet-4-5-20250929', label: 'Claude Sonnet 4.5', description: 'Best for complex agents and coding' },
     961                    { value: 'claude-opus-4-1-20250805', label: 'Claude Opus 4.1', description: 'Exceptional for specialized complex tasks' },
     962                    { value: 'claude-haiku-4-5-20251001', label: 'Claude Haiku 4.5', description: 'Fastest and most intelligent Haiku' },
     963                    { value: 'claude-opus-4-20250514', label: 'Claude 4 Opus', description: 'Most capable Claude model' },
     964                    { value: 'claude-sonnet-4-20250514', label: 'Claude 4 Sonnet', description: 'High performance' },
     965                    { value: 'claude-3-7-sonnet-20250219', label: 'Claude 3.7 Sonnet', description: 'High intelligence' },
     966                    { value: 'claude-3-5-sonnet-20241022', label: 'Claude 3.5 Sonnet', description: 'Intelligent and balanced' },
     967                    { value: 'claude-3-opus-20240229', label: 'Claude 3 Opus', description: 'Highly complex tasks' },
     968                    { value: 'claude-3-sonnet-20240229', label: 'Claude 3 Sonnet', description: 'Balanced performance' },
     969                    { value: 'claude-3-haiku-20240307', label: 'Claude 3 Haiku', description: 'Fastest Claude model' },
     970                ],
     971            xai: [
     972                { value: 'grok-4-0709', label: 'Grok 4', description: 'Latest flagship model - unparalleled performance in natural language, math and reasoning' },
     973                { value: 'grok-3-beta', label: 'Grok-3', description: 'Powerful model with 131K context' },
     974                { value: 'grok-3-fast-beta', label: 'Grok-3 Fast', description: 'High performance with faster responses' },
     975                { value: 'grok-3-mini-beta', label: 'Grok-3 Mini', description: 'Affordable model with good performance' },
     976                { value: 'grok-3-mini-fast-beta', label: 'Grok-3 Mini Fast', description: 'Quick and cost-effective' },
     977                { value: 'grok-2', label: 'Grok 2', description: 'Latest X.AI model' },
     978            ],
     979            deepseek: [
     980                { value: 'deepseek-chat', label: 'DeepSeek-V3', description: 'Advanced AI assistant' },
     981            ],
     982        };
     983       
     984        let allModels = [];
     985        Object.keys(models).forEach(key => {
     986            if (category === 'all' || category === key) {
     987                allModels = allModels.concat(models[key]);
     988            }
     989        });
     990       
     991        // Filter by search term if present
     992        if (filter) {
     993            const lowerFilter = filter.toLowerCase();
     994            allModels = allModels.filter(model =>
     995                model.label.toLowerCase().includes(lowerFilter) ||
     996                model.description.toLowerCase().includes(lowerFilter)
     997            );
     998        }
     999       
     1000        // Create model cards
     1001        allModels.forEach(model => {
     1002            const isSelected = $modelSelect.val() === model.value;
     1003            const $modelCard = $(`
     1004                <div class="mxchat-model-selector-card ${isSelected ? 'mxchat-model-selected' : ''}" data-value="${model.value}">
     1005                    <div class="mxchat-model-selector-icon">${getModelIcon(model.value)}</div>
     1006                    <div class="mxchat-model-selector-info">
     1007                        <h4 class="mxchat-model-selector-title">${model.label}</h4>
     1008                        <p class="mxchat-model-selector-description">${model.description}</p>
     1009                    </div>
     1010                    ${isSelected ? '<div class="mxchat-model-selector-checkmark">✓</div>' : ''}
     1011                </div>
     1012            `);
     1013            $grid.append($modelCard);
     1014        });
    9641015    };
    965    
    966     let allModels = [];
    967     Object.keys(models).forEach(key => {
    968         if (category === 'all' || category === key) {
    969             allModels = allModels.concat(models[key]);
    970         }
    971     });
    972    
    973     // Filter by search term if present
    974     if (filter) {
    975         const lowerFilter = filter.toLowerCase();
    976         allModels = allModels.filter(model =>
    977             model.label.toLowerCase().includes(lowerFilter) ||
    978             model.description.toLowerCase().includes(lowerFilter)
    979         );
    980     }
    981    
    982     // Create model cards
    983     allModels.forEach(model => {
    984         const isSelected = $modelSelect.val() === model.value;
    985         const $modelCard = $(`
    986             <div class="mxchat-model-selector-card ${isSelected ? 'mxchat-model-selected' : ''}" data-value="${model.value}">
    987                 <div class="mxchat-model-selector-icon">${getModelIcon(model.value)}</div>
    988                 <div class="mxchat-model-selector-info">
    989                     <h4 class="mxchat-model-selector-title">${model.label}</h4>
    990                     <p class="mxchat-model-selector-description">${model.description}</p>
    991                 </div>
    992                 ${isSelected ? '<div class="mxchat-model-selector-checkmark">✓</div>' : ''}
    993             </div>
    994         `);
    995         $grid.append($modelCard);
    996     });
    997 }
    998 
    999 
    1000 
    1001 // Helper function to get icon for each model
    1002 function getModelIcon(modelValue) {
    1003     if (modelValue.startsWith('gemini-')) return '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 48 48" class="mxchat-model-icon-gemini"><defs><path id="a" d="M44.5 20H24v8.5h11.8C34.7 33.9 30.1 37 24 37c-7.2 0-13-5.8-13-13s5.8-13 13-13c3.1 0 5.9 1.1 8.1 2.9l6.4-6.4C34.6 4.1 29.6 2 24 2 11.8 2 2 11.8 2 24s9.8 22 22 22c11 0 21-8 21-22 0-1.3-.2-2.7-.5-4z"></path></defs><clipPath id="b"><use xlink:href="#a" overflow="visible"></use></clipPath><path clip-path="url(#b)" fill="#FBBC05" d="M0 37V11l17 13z"></path><path clip-path="url(#b)" fill="#EA4335" d="M0 11l17 13 7-6.1L48 14V0H0z"></path><path clip-path="url(#b)" fill="#34A853" d="M0 37l30-23 7.9 1L48 0v48H0z"></path><path clip-path="url(#b)" fill="#4285F4" d="M48 48L17 24l-4-3 35-10z"></path></svg>';
    1004     if (modelValue.startsWith('gpt-')) return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 320" class="mxchat-model-icon-openai"><path fill="currentColor" d="M297 131a80.6 80.6 0 0 0-93.7-104.2 80.6 80.6 0 0 0-137 29A80.6 80.6 0 0 0 23 189a80.6 80.6 0 0 0 93.7 104.2 80.6 80.6 0 0 0 137-29A80.7 80.7 0 0 0 297.1 131zM176.9 299c-14 .1-27.6-4.8-38.4-13.8l1.9-1 63.7-36.9c3.3-1.8 5.3-5.3 5.2-9v-89.9l27 15.6c.3.1.4.4.5.7v74.4a60 60 0 0 1-60 60zM47.9 244a59.7 59.7 0 0 1-7.1-40.1l1.9 1.1 63.7 36.8c3.2 1.9 7.2 1.9 10.5 0l77.8-45V228c0 .3-.2.6-.4.8L129.9 266a60 60 0 0 1-82-22zM31.2 105c7-12.2 18-21.5 31.2-26.3v75.8c0 3.7 2 7.2 5.2 9l77.8 45-27 15.5a1 1 0 0 1-.9 0L53.1 187a60 60 0 0 1-22-82zm221.2 51.5-77.8-45 27-15.5a1 1 0 0 1 .9 0l64.4 37.1a60 60 0 0 1-9.3 108.2v-75.8c0-3.7-2-7.2-5.2-9zm26.8-40.4-1.9-1.1-63.7-36.8a10.4 10.4 0 0 0-10.5 0L125.4 123V92c0-.3 0-.6.3-.8L190.1 54a60 60 0 0 1 89.1 62.1zm-168.5 55.4-27-15.5a1 1 0 0 1-.4-.7V80.9a60 60 0 0 1 98.3-46.1l-1.9 1L116 72.8a10.3 10.3 0 0 0-5.2 9v89.8zm14.6-31.5 34.7-20 34.6 20v40L160 200l-34.7-20z"></path></svg>';
    1005     if (modelValue.startsWith('claude-')) return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 176" fill="none" class="mxchat-model-icon-claude"><path fill="currentColor" d="m147.487 0l70.081 175.78H256L185.919 0zM66.183 106.221l23.98-61.774l23.98 61.774zM70.07 0L0 175.78h39.18l14.33-36.914h73.308l14.328 36.914h39.179L110.255 0z"></path></svg>';
    1006     if (modelValue.startsWith('grok-')) return '<svg fill="currentColor" fill-rule="evenodd" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="mxchat-model-icon-xai"><path d="M6.469 8.776L16.512 23h-4.464L2.005 8.776H6.47zm-.004 7.9l2.233 3.164L6.467 23H2l4.465-6.324zM22 2.582V23h-3.659V7.764L22 2.582zM22 1l-9.952 14.095-2.233-3.163L17.533 1H22z"></path></svg>';
    1007     if (modelValue.startsWith('deepseek-')) return '<svg height="1em" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg" class="mxchat-model-icon-deepseek"><path d="M23.748 4.482c-.254-.124-.364.113-.512.234-.051.039-.094.09-.137.136-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.248-1.247-1.548-.352-.156-.708-.311-.955-.65-.172-.241-.219-.51-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.093.172.187.129.323-.082.28-.18.552-.266.833-.055.179-.137.217-.329.14a5.526 5.526 0 01-1.736-1.18c-.857-.828-1.631-1.742-2.597-2.458a11.365 11.365 0 00-.689-.471c-.985-.957.13-1.743.388-1.836.27-.098.093-.432-.779-.428-.872.004-1.67.295-2.687.684a3.055 3.055 0 01-.465.137 9.597 9.597 0 00-2.883-.102c-1.885.21-3.39 1.102-4.497 2.623C.082 8.606-.231 10.684.152 12.85c.403 2.284 1.569 4.175 3.36 5.653 1.858 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.133-.284 4.994-1.86.47.234.962.327 1.78.397.63.059 1.236-.03 1.705-.128.735-.156.684-.837.419-.961-2.155-1.004-1.682-.595-2.113-.926 1.096-1.296 2.746-2.642 3.392-7.003.05-.347.007-.565 0-.845-.004-.17.035-.237.23-.256a4.173 4.173 0 001.545-.475c1.396-.763 1.96-2.015 2.093-3.517.02-.23-.004-.467-.247-.588zM11.581 18c-2.089-1.642-3.102-2.183-3.52-2.16-.392.024-.321.471-.235.763.09.288.207.486.371.739.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.167-1.361-.802-2.5-1.86-3.301-3.307-.774-1.393-1.224-2.887-1.298-4.482-.02-.386.093-.522.477-.592a4.696 4.696 0 011.529-.039c2.132.312 3.946 1.265 5.468 2.774.868.86 1.525 1.887 2.202 2.891.72 1.066 1.494 2.082 2.48 2.914.348.292.625.514.891.677-.802.09-2.14.11-3.054-.614zm1-6.44a.306.306 0 01.415-.287.302.302 0 01.2.288.306.306 0 01-.31.307.303.303 0 01-.304-.308zm3.11 1.596c-.2.081-.399.151-.59.16a1.245 1.245 0 01-.798-.254c-.274-.23-.47-.358-.552-.758a1.73 1.73 0 01.016-.588c.07-.327-.008-.537-.239-.727-.187-.156-.426-.199-.688-.199a.559.559 0 01-.254-.078c-.11-.054-.2-.19-.114-.358.028-.054.16-.186.192-.21.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.391.451.462.576.685.914.176.265.336.537.445.848.067.195-.019.354-.25.452z" fill="currentColor"></path></svg>';
    1008     return '<span class="dashicons dashicons-admin-generic mxchat-model-icon-generic"></span>';
    1009 }
     1016
     1017    // Helper function to get icon for each model
     1018    function getModelIcon(modelValue) {
     1019        if (modelValue === 'openrouter') return '<span class="dashicons dashicons-networking" style="font-size: 24px; color: #6750A4;"></span>';
     1020        if (modelValue.startsWith('gemini-')) return '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 48 48" class="mxchat-model-icon-gemini"><defs><path id="a" d="M44.5 20H24v8.5h11.8C34.7 33.9 30.1 37 24 37c-7.2 0-13-5.8-13-13s5.8-13 13-13c3.1 0 5.9 1.1 8.1 2.9l6.4-6.4C34.6 4.1 29.6 2 24 2 11.8 2 2 11.8 2 24s9.8 22 22 22c11 0 21-8 21-22 0-1.3-.2-2.7-.5-4z"></path></defs><clipPath id="b"><use xlink:href="#a" overflow="visible"></use></clipPath><path clip-path="url(#b)" fill="#FBBC05" d="M0 37V11l17 13z"></path><path clip-path="url(#b)" fill="#EA4335" d="M0 11l17 13 7-6.1L48 14V0H0z"></path><path clip-path="url(#b)" fill="#34A853" d="M0 37l30-23 7.9 1L48 0v48H0z"></path><path clip-path="url(#b)" fill="#4285F4" d="M48 48L17 24l-4-3 35-10z"></path></svg>';
     1021        if (modelValue.startsWith('gpt-')) return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 320" class="mxchat-model-icon-openai"><path fill="currentColor" d="M297 131a80.6 80.6 0 0 0-93.7-104.2 80.6 80.6 0 0 0-137 29A80.6 80.6 0 0 0 23 189a80.6 80.6 0 0 0 93.7 104.2 80.6 80.6 0 0 0 137-29A80.7 80.7 0 0 0 297.1 131zM176.9 299c-14 .1-27.6-4.8-38.4-13.8l1.9-1 63.7-36.9c3.3-1.8 5.3-5.3 5.2-9v-89.9l27 15.6c.3.1.4.4.5.7v74.4a60 60 0 0 1-60 60zM47.9 244a59.7 59.7 0 0 1-7.1-40.1l1.9 1.1 63.7 36.8c3.2 1.9 7.2 1.9 10.5 0l77.8-45V228c0 .3-.2.6-.4.8L129.9 266a60 60 0 0 1-82-22zM31.2 105c7-12.2 18-21.5 31.2-26.3v75.8c0 3.7 2 7.2 5.2 9l77.8 45-27 15.5a1 1 0 0 1-.9 0L53.1 187a60 60 0 0 1-22-82zm221.2 51.5-77.8-45 27-15.5a1 1 0 0 1 .9 0l64.4 37.1a60 60 0 0 1-9.3 108.2v-75.8c0-3.7-2-7.2-5.2-9zm26.8-40.4-1.9-1.1-63.7-36.8a10.4 10.4 0 0 0-10.5 0L125.4 123V92c0-.3 0-.6.3-.8L190.1 54a60 60 0 0 1 89.1 62.1zm-168.5 55.4-27-15.5a1 1 0 0 1-.4-.7V80.9a60 60 0 0 1 98.3-46.1l-1.9 1L116 72.8a10.3 10.3 0 0 0-5.2 9v89.8zm14.6-31.5 34.7-20 34.6 20v40L160 200l-34.7-20z"></path></svg>';
     1022        if (modelValue.startsWith('claude-')) return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 176" fill="none" class="mxchat-model-icon-claude"><path fill="currentColor" d="m147.487 0l70.081 175.78H256L185.919 0zM66.183 106.221l23.98-61.774l23.98 61.774zM70.07 0L0 175.78h39.18l14.33-36.914h73.308l14.328 36.914h39.179L110.255 0z"></path></svg>';
     1023        if (modelValue.startsWith('grok-')) return '<svg fill="currentColor" fill-rule="evenodd" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="mxchat-model-icon-xai"><path d="M6.469 8.776L16.512 23h-4.464L2.005 8.776H6.47zm-.004 7.9l2.233 3.164L6.467 23H2l4.465-6.324zM22 2.582V23h-3.659V7.764L22 2.582zM22 1l-9.952 14.095-2.233-3.163L17.533 1H22z"></path></svg>';
     1024        if (modelValue.startsWith('deepseek-')) return '<svg height="1em" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg" class="mxchat-model-icon-deepseek"><path d="M23.748 4.482c-.254-.124-.364.113-.512.234-.051.039-.094.09-.137.136-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.248-1.247-1.548-.352-.156-.708-.311-.955-.65-.172-.241-.219-.51-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.093.172.187.129.323-.082.28-.18.552-.266.833-.055.179-.137.217-.329.14a5.526 5.526 0 01-1.736-1.18c-.857-.828-1.631-1.742-2.597-2.458a11.365 11.365 0 00-.689-.471c-.985-.957.13-1.743.388-1.836.27-.098.093-.432-.779-.428-.872.004-1.67.295-2.687.684a3.055 3.055 0 01-.465.137 9.597 9.597 0 00-2.883-.102c-1.885.21-3.39 1.102-4.497 2.623C.082 8.606-.231 10.684.152 12.85c.403 2.284 1.569 4.175 3.36 5.653 1.858 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.133-.284 4.994-1.86.47.234.962.327 1.78.397.63.059 1.236-.03 1.705-.128.735-.156.684-.837.419-.961-2.155-1.004-1.682-.595-2.113-.926 1.096-1.296 2.746-2.642 3.392-7.003.05-.347.007-.565 0-.845-.004-.17.035-.237.23-.256a4.173 4.173 0 001.545-.475c1.396-.763 1.96-2.015 2.093-3.517.02-.23-.004-.467-.247-.588zM11.581 18c-2.089-1.642-3.102-2.183-3.52-2.16-.392.024-.321.471-.235.763.09.288.207.486.371.739.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.167-1.361-.802-2.5-1.86-3.301-3.307-.774-1.393-1.224-2.887-1.298-4.482-.02-.386.093-.522.477-.592a4.696 4.696 0 011.529-.039c2.132.312 3.946 1.265 5.468 2.774.868.86 1.525 1.887 2.202 2.891.72 1.066 1.494 2.082 2.48 2.914.348.292.625.514.891.677-.802.09-2.14.11-3.054-.614zm1-6.44a.306.306 0 01.415-.287.302.302 0 01.2.288.306.306 0 01-.31.307.303.303 0 01-.304-.308zm3.11 1.596c-.2.081-.399.151-.59.16a1.245 1.245 0 01-.798-.254c-.274-.23-.47-.358-.552-.758a1.73 1.73 0 01.016-.588c.07-.327-.008-.537-.239-.727-.187-.156-.426-.199-.688-.199a.559.559 0 01-.254-.078c-.11-.054-.2-.19-.114-.358.028-.054.16-.186.192-.21.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.391.451.462.576.685.914.176.265.336.537.445.848.067.195-.019.354-.25.452z" fill="currentColor"></path></svg>';
     1025        return '<span class="dashicons dashicons-admin-generic mxchat-model-icon-generic"></span>';
     1026    }
    10101027   
    10111028    // Event handlers
    10121029    $modelSelectorButton.on('click', function() {
    10131030        $('#mxchat_model_selector_modal').show();
    1014         populateModelsGrid('', 'all');
     1031        window.populateModelsGrid('', 'all');
    10151032    });
    10161033   
     
    10241041        const category = $(this).data('category');
    10251042        const searchTerm = $('#mxchat_model_search_input').val();
    1026         populateModelsGrid(searchTerm, category);
     1043        window.populateModelsGrid(searchTerm, category);
    10271044    });
    10281045   
     
    10301047        const searchTerm = $(this).val();
    10311048        const activeCategory = $('.mxchat-model-category-btn.active').data('category');
    1032         populateModelsGrid(searchTerm, activeCategory);
    1033     });
    1034    
    1035     $(document).on('click', '.mxchat-model-selector-card', function() {
    1036         const modelValue = $(this).data('value');
     1049        window.populateModelsGrid(searchTerm, activeCategory);
     1050    });
     1051   
     1052$(document).on('click', '.mxchat-model-selector-card', function() {
     1053    const modelValue = $(this).data('value');
     1054    const $modelSelect = $('#model'); // Add this line to ensure we have the element
     1055   
     1056    // Check if OpenRouter was selected
     1057    if (modelValue === 'openrouter') {
     1058        // Load OpenRouter models instead of closing
     1059        loadOpenRouterModels();
     1060    } else {
     1061        // Normal model selection
    10371062        $modelSelect.val(modelValue).trigger('change');
    1038         updateButtonText();
     1063       
     1064        // Manually save the model via AJAX
     1065        jQuery.ajax({
     1066            url: mxchatAdmin.ajax_url,
     1067            type: 'POST',
     1068            data: {
     1069                action: 'mxchat_save_setting',
     1070                name: 'model',
     1071                value: modelValue,
     1072                _ajax_nonce: mxchatAdmin.setting_nonce
     1073            },
     1074            success: function(response) {
     1075                if (response.success) {
     1076                    // Update button text after successful save
     1077                    const selectedModelText = $modelSelect.find('option:selected').text();
     1078                    $('#mxchat_model_selector_btn').text(selectedModelText);
     1079                }
     1080            }
     1081        });
     1082       
    10391083        $('#mxchat_model_selector_modal').hide();
    1040     });
     1084    }
     1085});
    10411086   
    10421087    // Close modal when clicking outside
     
    10461091        }
    10471092    });
     1093}
     1094
     1095function loadOpenRouterModels() {
     1096    const apiKey = $('#openrouter_api_key').val(); // This line already re-checks the field
     1097    const $modal = $('#mxchat_model_selector_modal');
     1098    const $modalBody = $modal.find('.mxchat-model-selector-modal-body');
     1099   
     1100    if (!apiKey || apiKey.trim() === '') {
     1101        // Show error message in modal
     1102        $modalBody.html(`
     1103            <div style="text-align: center; padding: 40px;">
     1104                <span class="dashicons dashicons-warning" style="font-size: 48px; color: #d63638; margin-bottom: 20px;"></span>
     1105                <h3>OpenRouter API Key Required</h3>
     1106                <p>Please enter your OpenRouter API key in the settings before selecting a model. If you're seeing this message and recently entered API key, try refreshing.</p>
     1107                <button class="button button-primary" id="mxchat_back_to_models">Back to Models</button>
     1108            </div>
     1109        `);
     1110       
     1111        $('#mxchat_back_to_models').on('click', function(e) {
     1112            e.preventDefault();
     1113            // CHANGE THIS: Instead of reloading, restore the original modal content
     1114            $modalBody.html(`
     1115                <div class="mxchat-model-selector-search-container">
     1116                    <input type="text" id="mxchat_model_search_input" class="mxchat-model-search-input" placeholder="Search models...">
     1117                </div>
     1118                <div class="mxchat-model-selector-categories">
     1119                    <button class="mxchat-model-category-btn active" data-category="all">All</button>
     1120                    <button class="mxchat-model-category-btn" data-category="openrouter">OpenRouter</button>
     1121                    <button class="mxchat-model-category-btn" data-category="gemini">Google Gemini</button>
     1122                    <button class="mxchat-model-category-btn" data-category="openai">OpenAI</button>
     1123                    <button class="mxchat-model-category-btn" data-category="claude">Claude</button>
     1124                    <button class="mxchat-model-category-btn" data-category="xai">X.AI</button>
     1125                    <button class="mxchat-model-category-btn" data-category="deepseek">DeepSeek</button>
     1126                </div>
     1127                <div class="mxchat-model-selector-grid" id="mxchat_models_grid"></div>
     1128            `);
     1129           
     1130            // Re-populate the grid
     1131            populateModelsGrid('', 'all');
     1132           
     1133            // Re-bind event handlers
     1134            rebindModalEventHandlers();
     1135        });
     1136        return;
     1137    }
     1138   
     1139    // Show loading state
     1140    $modalBody.html(`
     1141        <div style="text-align: center; padding: 60px 20px;">
     1142            <div class="spinner is-active" style="float: none; margin: 0 auto 20px;"></div>
     1143            <h3>Loading OpenRouter Models...</h3>
     1144            <p>Fetching available models from OpenRouter</p>
     1145        </div>
     1146    `);
     1147   
     1148    // Fetch models from OpenRouter
     1149    jQuery.ajax({
     1150        url: mxchatAdmin.ajax_url,
     1151        type: 'POST',
     1152        data: {
     1153            action: 'mxchat_fetch_openrouter_models',
     1154            api_key: apiKey,
     1155            nonce: mxchatAdmin.fetch_openrouter_models_nonce
     1156        },
     1157        success: function(response) {
     1158            if (response.success && response.data.models) {
     1159                displayOpenRouterModels(response.data.models);
     1160            } else {
     1161                $modalBody.html(`
     1162                    <div style="text-align: center; padding: 40px;">
     1163                        <span class="dashicons dashicons-warning" style="font-size: 48px; color: #d63638; margin-bottom: 20px;"></span>
     1164                        <h3>Error Loading Models</h3>
     1165                        <p>${response.data.message || 'Failed to load models from OpenRouter'}</p>
     1166                        <button class="button button-primary" id="mxchat_back_to_models">Back to Models</button>
     1167                    </div>
     1168                `);
     1169               
     1170                $('#mxchat_back_to_models').on('click', function(e) {
     1171                    e.preventDefault();
     1172                    // CHANGE THIS: Restore original content instead of reloading
     1173                    restoreOriginalModalContent();
     1174                });
     1175            }
     1176        },
     1177        error: function() {
     1178            $modalBody.html(`
     1179                <div style="text-align: center; padding: 40px;">
     1180                    <span class="dashicons dashicons-warning" style="font-size: 48px; color: #d63638; margin-bottom: 20px;"></span>
     1181                    <h3>Connection Error</h3>
     1182                    <p>Failed to connect to OpenRouter. Please check your API key and try again.</p>
     1183                    <button class="button button-primary" id="mxchat_back_to_models">Back to Models</button>
     1184                </div>
     1185            `);
     1186           
     1187            $('#mxchat_back_to_models').on('click', function(e) {
     1188                e.preventDefault();
     1189                // CHANGE THIS: Restore original content instead of reloading
     1190                restoreOriginalModalContent();
     1191            });
     1192        }
     1193    });
     1194}
     1195function restoreOriginalModalContent() {
     1196    const $modalBody = $('#mxchat_model_selector_modal').find('.mxchat-model-selector-modal-body');
     1197   
     1198    $modalBody.html(`
     1199        <div class="mxchat-model-selector-search-container">
     1200            <input type="text" id="mxchat_model_search_input" class="mxchat-model-search-input" placeholder="Search models...">
     1201        </div>
     1202        <div class="mxchat-model-selector-categories">
     1203            <button class="mxchat-model-category-btn active" data-category="all">All</button>
     1204            <button class="mxchat-model-category-btn" data-category="openrouter">OpenRouter</button>
     1205            <button class="mxchat-model-category-btn" data-category="gemini">Google Gemini</button>
     1206            <button class="mxchat-model-category-btn" data-category="openai">OpenAI</button>
     1207            <button class="mxchat-model-category-btn" data-category="claude">Claude</button>
     1208            <button class="mxchat-model-category-btn" data-category="xai">X.AI</button>
     1209            <button class="mxchat-model-category-btn" data-category="deepseek">DeepSeek</button>
     1210        </div>
     1211        <div class="mxchat-model-selector-grid" id="mxchat_models_grid"></div>
     1212    `);
     1213   
     1214    // Re-populate the grid
     1215    window.populateModelsGrid('', 'all');
     1216   
     1217    // Re-bind event handlers
     1218    rebindModalEventHandlers();
     1219}
     1220function rebindModalEventHandlers() {
     1221    const $modal = $('#mxchat_model_selector_modal');
     1222   
     1223    // Re-bind category button clicks
     1224    $('.mxchat-model-category-btn').off('click').on('click', function() {
     1225        $('.mxchat-model-category-btn').removeClass('active');
     1226        $(this).addClass('active');
     1227        const category = $(this).data('category');
     1228        const searchTerm = $('#mxchat_model_search_input').val();
     1229        window.populateModelsGrid(searchTerm, category);
     1230    });
     1231   
     1232    // Re-bind search input
     1233    $('#mxchat_model_search_input').off('input').on('input', function() {
     1234        const searchTerm = $(this).val();
     1235        const activeCategory = $('.mxchat-model-category-btn.active').data('category');
     1236        populateModelsGrid(searchTerm, activeCategory);
     1237    });
     1238}
     1239
     1240function displayOpenRouterModels(models) {
     1241    const $modal = $('#mxchat_model_selector_modal');
     1242    const $modalBody = $modal.find('.mxchat-model-selector-modal-body');
     1243    const currentSelected = $('#openrouter_selected_model').val();
     1244   
     1245    // Build new modal content with search and models
     1246    const newContent = `
     1247        <div class="mxchat-model-selector-search-container">
     1248            <input type="text" id="mxchat_openrouter_search" class="mxchat-model-search-input" placeholder="Search OpenRouter models...">
     1249            <p style="margin: 10px 0; color: #666; font-size: 13px;">
     1250                <strong>${models.length} models available</strong> ·
     1251                <a href="#" id="mxchat_back_to_provider_select" style="color: #2271b1;">← Back to providers</a>
     1252            </p>
     1253        </div>
     1254        <div class="mxchat-model-selector-grid" id="mxchat_openrouter_models_grid"></div>
     1255    `;
     1256   
     1257    $modalBody.html(newContent);
     1258   
     1259    // Function to render models
     1260    function renderOpenRouterModels(filterText = '') {
     1261        const $grid = $('#mxchat_openrouter_models_grid');
     1262        $grid.empty();
     1263       
     1264        let filteredModels = models;
     1265        if (filterText) {
     1266            const lowerFilter = filterText.toLowerCase();
     1267            filteredModels = models.filter(m =>
     1268                m.id.toLowerCase().includes(lowerFilter) ||
     1269                m.name.toLowerCase().includes(lowerFilter) ||
     1270                (m.description && m.description.toLowerCase().includes(lowerFilter))
     1271            );
     1272        }
     1273       
     1274        filteredModels.forEach(model => {
     1275            const isSelected = currentSelected === model.id;
     1276            const contextLength = model.context_length ? `${(model.context_length / 1000).toFixed(0)}K` : '';
     1277            const promptPrice = model.pricing.prompt ? `$${(model.pricing.prompt * 1000000).toFixed(2)}/1M` : '';
     1278           
     1279            const $card = jQuery(`
     1280                <div class="mxchat-model-selector-card mxchat-openrouter-card ${isSelected ? 'mxchat-model-selected' : ''}" data-model-id="${model.id}">
     1281                    <div class="mxchat-model-selector-icon">
     1282                        ${getOpenRouterIcon(model.id)}
     1283                    </div>
     1284                    <div class="mxchat-model-selector-info">
     1285                        <h4 class="mxchat-model-selector-title">${model.name}</h4>
     1286                        <div style="font-size: 12px; color: #666; margin-top: 5px;">
     1287                            ${contextLength ? '<span style="margin-right: 12px;">📄 ' + contextLength + '</span>' : ''}
     1288                            ${promptPrice ? '<span>💰 ' + promptPrice + '</span>' : ''}
     1289                        </div>
     1290                    </div>
     1291                    ${isSelected ? '<div class="mxchat-model-selector-checkmark">✓</div>' : ''}
     1292                </div>
     1293            `);
     1294           
     1295            $grid.append($card);
     1296        });
     1297    }
     1298   
     1299    // Helper to get icon
     1300    function getOpenRouterIcon(modelId) {
     1301        if (modelId.includes('gpt') || modelId.includes('openai')) {
     1302            return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 320" class="mxchat-model-icon-openai"><path fill="currentColor" d="M297 131a80.6 80.6 0 0 0-93.7-104.2 80.6 80.6 0 0 0-137 29A80.6 80.6 0 0 0 23 189a80.6 80.6 0 0 0 93.7 104.2 80.6 80.6 0 0 0 137-29A80.7 80.7 0 0 0 297.1 131zM176.9 299c-14 .1-27.6-4.8-38.4-13.8l1.9-1 63.7-36.9c3.3-1.8 5.3-5.3 5.2-9v-89.9l27 15.6c.3.1.4.4.5.7v74.4a60 60 0 0 1-60 60zM47.9 244a59.7 59.7 0 0 1-7.1-40.1l1.9 1.1 63.7 36.8c3.2 1.9 7.2 1.9 10.5 0l77.8-45V228c0 .3-.2.6-.4.8L129.9 266a60 60 0 0 1-82-22zM31.2 105c7-12.2 18-21.5 31.2-26.3v75.8c0 3.7 2 7.2 5.2 9l77.8 45-27 15.5a1 1 0 0 1-.9 0L53.1 187a60 60 0 0 1-22-82zm221.2 51.5-77.8-45 27-15.5a1 1 0 0 1 .9 0l64.4 37.1a60 60 0 0 1-9.3 108.2v-75.8c0-3.7-2-7.2-5.2-9zm26.8-40.4-1.9-1.1-63.7-36.8a10.4 10.4 0 0 0-10.5 0L125.4 123V92c0-.3 0-.6.3-.8L190.1 54a60 60 0 0 1 89.1 62.1zm-168.5 55.4-27-15.5a1 1 0 0 1-.4-.7V80.9a60 60 0 0 1 98.3-46.1l-1.9 1L116 72.8a10.3 10.3 0 0 0-5.2 9v89.8zm14.6-31.5 34.7-20 34.6 20v40L160 200l-34.7-20z"></path></svg>';
     1303        } else if (modelId.includes('claude') || modelId.includes('anthropic')) {
     1304            return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 176" fill="none" class="mxchat-model-icon-claude"><path fill="currentColor" d="m147.487 0l70.081 175.78H256L185.919 0zM66.183 106.221l23.98-61.774l23.98 61.774zM70.07 0L0 175.78h39.18l14.33-36.914h73.308l14.328 36.914h39.179L110.255 0z"></path></svg>';
     1305        } else if (modelId.includes('gemini') || modelId.includes('google')) {
     1306            return '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 48 48" class="mxchat-model-icon-gemini"><defs><path id="a" d="M44.5 20H24v8.5h11.8C34.7 33.9 30.1 37 24 37c-7.2 0-13-5.8-13-13s5.8-13 13-13c3.1 0 5.9 1.1 8.1 2.9l6.4-6.4C34.6 4.1 29.6 2 24 2 11.8 2 2 11.8 2 24s9.8 22 22 22c11 0 21-8 21-22 0-1.3-.2-2.7-.5-4z"></path></defs><clipPath id="b"><use xlink:href="#a" overflow="visible"></use></clipPath><path clip-path="url(#b)" fill="#FBBC05" d="M0 37V11l17 13z"></path><clip-path="url(#b)" fill="#EA4335" d="M0 11l17 13 7-6.1L48 14V0H0z"></path><path clip-path="url(#b)" fill="#34A853" d="M0 37l30-23 7.9 1L48 0v48H0z"></path><path clip-path="url(#b)" fill="#4285F4" d="M48 48L17 24l-4-3 35-10z"></path></svg>';
     1307        }
     1308        return '<span class="dashicons dashicons-cloud" style="font-size: 24px; color: #6750A4;"></span>';
     1309    }
     1310   
     1311    // Initial render
     1312    renderOpenRouterModels();
     1313   
     1314    // Search handler
     1315    $('#mxchat_openrouter_search').on('input', function() {
     1316        renderOpenRouterModels($(this).val());
     1317    });
     1318   
     1319    $('#mxchat_back_to_provider_select').on('click', function(e) {
     1320        e.preventDefault();
     1321        // Instead of location.reload(), restore original content
     1322        restoreOriginalModalContent();
     1323    });
     1324   
     1325// Model selection
     1326$(document).on('click', '.mxchat-openrouter-card', function() {
     1327    const modelId = $(this).data('model-id');
     1328    const modelName = models.find(m => m.id === modelId)?.name || modelId;
     1329   
     1330    // First, save that we're using OpenRouter
     1331    jQuery.ajax({
     1332        url: mxchatAdmin.ajax_url,
     1333        type: 'POST',
     1334        data: {
     1335            action: 'mxchat_save_setting',
     1336            name: 'model',
     1337            value: 'openrouter',
     1338            _ajax_nonce: mxchatAdmin.setting_nonce
     1339        },
     1340        success: function() {
     1341            // After model is set, save the model ID
     1342            jQuery.ajax({
     1343                url: mxchatAdmin.ajax_url,
     1344                type: 'POST',
     1345                data: {
     1346                    action: 'mxchat_save_setting',
     1347                    name: 'openrouter_selected_model',
     1348                    value: modelId,
     1349                    _ajax_nonce: mxchatAdmin.setting_nonce
     1350                },
     1351                success: function() {
     1352                    // Update DOM immediately
     1353                    $('#openrouter_selected_model').val(modelId);
     1354                   
     1355                    // After model ID is saved, save the display name
     1356                    jQuery.ajax({
     1357                        url: mxchatAdmin.ajax_url,
     1358                        type: 'POST',
     1359                        data: {
     1360                            action: 'mxchat_save_setting',
     1361                            name: 'openrouter_selected_model_name',
     1362                            value: modelName,
     1363                            _ajax_nonce: mxchatAdmin.setting_nonce
     1364                        },
     1365                        success: function() {
     1366                            // Update DOM immediately
     1367                            $('#openrouter_selected_model_name').val(modelName);
     1368                           
     1369                            // Update button text
     1370                            $('#mxchat_model_selector_btn').text('OpenRouter: ' + modelName);
     1371                        }
     1372                    });
     1373                }
     1374            });
     1375        }
     1376    });
     1377   
     1378    // Close modal
     1379    $modal.hide();
     1380});
     1381
    10481382}
    10491383
  • mxchat-basic/trunk/js/test-panel.js

    r3315188 r3379814  
    607607
    608608    updateSystemPrompt() {
    609         const promptEl = this.panel.querySelector('#system-prompt');
    610         promptEl.textContent = 'Loading system prompt...';
    611        
    612         fetch(mxchatTestData.ajaxUrl, {
    613             method: 'POST',
    614             headers: {
    615                 'Content-Type': 'application/x-www-form-urlencoded',
    616             },
    617             body: new URLSearchParams({
    618                 action: 'mxchat_get_system_info',
    619                 nonce: mxchatTestData.nonce
    620             })
     609    const promptEl = this.panel.querySelector('#system-prompt');
     610    promptEl.textContent = 'Loading system prompt...';
     611   
     612    fetch(mxchatTestData.ajaxUrl, {
     613        method: 'POST',
     614        headers: {
     615            'Content-Type': 'application/x-www-form-urlencoded',
     616        },
     617        body: new URLSearchParams({
     618            action: 'mxchat_get_system_info',
     619            nonce: mxchatTestData.nonce
    621620        })
    622         .then(response => response.json())
    623         .then(data => {
    624             if (data.success) {
    625                 promptEl.textContent = data.data.system_prompt || 'No system prompt configured';
     621    })
     622    .then(response => response.json())
     623    .then(data => {
     624        if (data.success) {
     625            promptEl.textContent = data.data.system_prompt || 'No system prompt configured';
     626           
     627            // Enhanced model display with OpenRouter support
     628            if (data.data.is_openrouter) {
     629                this.log(`🤖 Model: OpenRouter`);
     630                this.log(`   └─ Using: ${data.data.openrouter_model}`);
     631            } else {
    626632                this.log(`🤖 Model: ${data.data.selected_model}`);
    627                 this.log(`🔑 API Status: ${JSON.stringify(data.data.api_status)}`);
     633            }
     634           
     635            // Enhanced API status with OpenRouter
     636            const apiStatus = data.data.api_status;
     637            const configuredApis = Object.keys(apiStatus).filter(key => apiStatus[key]);
     638           
     639            if (configuredApis.length > 0) {
     640                this.log(`🔑 Configured APIs: ${configuredApis.map(api => {
     641                    // Capitalize and format API names
     642                    if (api === 'openai') return 'OpenAI';
     643                    if (api === 'xai') return 'X.AI';
     644                    if (api === 'openrouter') return 'OpenRouter';
     645                    return api.charAt(0).toUpperCase() + api.slice(1);
     646                }).join(', ')}`);
    628647            } else {
    629                 promptEl.textContent = 'Error loading system prompt';
     648                this.log(`⚠️ No API keys configured`);
    630649            }
    631         })
    632         .catch(error => {
    633             console.error('Error fetching system info:', error);
    634             promptEl.textContent = 'Connection error';
    635         });
    636     }
     650           
     651            // Specific warning for OpenRouter if selected but no key
     652            if (data.data.is_openrouter && !apiStatus.openrouter) {
     653                this.log(`❌ WARNING: OpenRouter selected but no API key configured!`);
     654            }
     655        } else {
     656            promptEl.textContent = 'Error loading system prompt';
     657        }
     658    })
     659    .catch(error => {
     660        console.error('Error fetching system info:', error);
     661        promptEl.textContent = 'Connection error';
     662    });
     663}
    637664
    638665    updateKnowledgeBaseStatus() {
  • mxchat-basic/trunk/mxchat-basic.php

    r3378505 r3379814  
    44 * Plugin URI: https://mxchat.ai/
    55 * Description: AI chatbot for WordPress with OpenAI, Claude, xAI, DeepSeek, live agent, PDF uploads, WooCommerce, and training on website data.
    6  * Version: 2.4.7
     6 * Version: 2.4.8
    77 * Author: MxChat
    88 * Author URI: https://mxchat.ai
     
    1818
    1919// Define plugin version constant for asset versioning
    20 define('MXCHAT_VERSION', '2.4.7');
     20define('MXCHAT_VERSION', '2.4.8');
    2121
    2222function mxchat_load_textdomain() {
  • mxchat-basic/trunk/readme.txt

    r3378505 r3379814  
    66Tested up to: 6.8
    77Requires PHP: 7.2
    8 Stable tag: 2.4.7
     8Stable tag: 2.4.8
    99License: GPLv2 or later
    1010License URI: https://www.gnu.org/licenses/gpl-2.0.html
    1111
    12 AI chatbot with OpenAI, Gemini, Claude, Grok, DeepSeek, live agent, PDF uploads, WooCommerce, and training on website data.
     12AI chatbot with OpenAI, Gemini, Claude, Grok, DeepSeek, OpenRouter, live agent, WooCommerce, and training on website data.
    1313
    1414== Description ==
     
    3232## Why Choose MxChat AI Chatbot for Your WordPress Website?
    3333
    34 ✅ **5 Major AI Providers in One Plugin**: OpenAI GPT, Claude, Gemini, xAI Grok, and DeepSeek - switch between 25+ models instantly 
    35 ✅ **Train on Your Website Data**: Advanced RAG technology learns from sitemaps, PDFs, URLs, or manual input for ultra-relevant responses 
     34✅ **6 Major AI Providers in One Plugin**: OpenRouter, OpenAI GPT, Claude, Gemini, xAI Grok, and DeepSeek - switch between 100+ models instantly✅ **Train on Your Website Data**: Advanced RAG technology learns from sitemaps, PDFs, URLs, or manual input for ultra-relevant responses 
    3635✅ **Live Agent Handoff via Slack**: Seamlessly escalate from AI to human support when customers need personal assistance 
    3736✅ **Real-Time Debug Panel**: See exactly what your chatbot retrieves and triggers with our admin testing interface 
     
    3938✅ **Extensive Add-On Ecosystem**: Forms, moderation, recommendations, theme customization, and more 
    4039
    41 ## 🔥 What's New in Version 2.4.4 
    42 
    43 🐞 **Bug Fixes** 
    44 - Default similarity is now accurate across all settings 
    45 - Improved streaming responses to prevent dropped characters or words 
    46 
    47 ⚡ **Enhancements** 
    48 - Removed unnecessary Pinecone caching in the knowledge database for more accurate results 
     40## 🔥 What's New in Version 2.4.8 
     41🚀 **New Features** 
     42- **OpenRouter Integration** - Access 100+ AI models from multiple providers using a single API key! Browse and select from hundreds of cutting-edge models including OpenAI, Anthropic, Google, Meta, and more.
     43- **Latest Claude Models** - Added support for Anthropic's newest models:
     44  - Claude Sonnet 4.5 - Best for complex agents and coding tasks
     45  - Claude Opus 4.1 - Exceptional performance for specialized complex tasks
     46  - Claude Haiku 4.5 - Fastest and most intelligent Haiku model with extended thinking
    4947
    5048## Core Features That Set MxChat Apart
     
    5856🟢 **Streaming Responses** – Real-time response streaming for OpenAI, Claude, and Grok models for the fastest possible chat experience 
    5957
    60 ## Choose from 25+ Premium AI Models
    61 
     58## Choose from 100+ Premium AI Models
    6259Access the world's most advanced AI models based on your specific needs:
    6360
     61**OpenRouter**: 100+ models from multiple providers with a single API key - including OpenAI, Anthropic, Google, Meta, Mistral, and more!
    6462**OpenAI**: GPT-5, GPT-5-mini, GPT-5-nano, GPT-4.1, GPT-4o, GPT-4o-mini, GPT-4-turbo, GPT-4, GPT-3.5-turbo
    65 **X.AI**: Grok-4, Grok-3, Grok-3 Fast, Grok-3 Mini, Grok-3 Mini Fast, Grok-2 
    66 **Claude**: Claude 4 Sonnet, Claude 4 Opus, Claude 3.7 Sonnet, Claude 3.5 Sonnet, Claude 3 Opus 
    67 **DeepSeek**: DeepSeek V3 
    68 **Google Gemini**: Gemini 2.0 Flash, Gemini 2.0 Flash-Lite, Gemini 1.5 Pro, Gemini 1.5 Flash 
     63**Anthropic Claude**: Claude Sonnet 4.5, Claude Opus 4.1, Claude Haiku 4.5, Claude 4 Sonnet, Claude 4 Opus, Claude 3.7 Sonnet, Claude 3.5 Sonnet, Claude 3 Opus
     64**X.AI**: Grok-4, Grok-3, Grok-3 Fast, Grok-3 Mini, Grok-3 Mini Fast, Grok-2
     65**Google Gemini**: Gemini 2.0 Flash, Gemini 2.0 Flash-Lite, Gemini 1.5 Pro, Gemini 1.5 Flash
     66**DeepSeek**: DeepSeek V3
    6967
    7068## Supercharge Your eCommerce with AI
     
    133131
    134132**Service Providers:**
     133- [OpenRouter](https://openrouter.ai/) - [Terms](https://openrouter.ai/terms) | [Privacy](https://openrouter.ai/privacy)
    135134- [OpenAI](https://openai.com/) - [Terms](https://openai.com/policies/terms-of-use/) | [Privacy](https://openai.com/policies/privacy-policy/)
    136135- [Anthropic](https://anthropic.com/) - [Terms](https://www.anthropic.com/terms) | [Privacy](https://www.anthropic.com/privacy)
     
    181180
    182181== Changelog ==
     182
     183= 2.4.8 - October 16, 2025 =
     184- New: OpenRouter integration - access 100+ AI models with a single API key
     185- New: Added Claude Sonnet 4.5, Claude Opus 4.1, and Claude Haiku 4.5 support
    183186
    184187= 2.4.7 - October 13, 2025 =
     
    556559== Upgrade Notice ==
    557560
    558 = 2.4.7 =
    559 - Security: Fixed Server-Side Request Forgery (SSRF) vulnerability in PDF processing (CVE-2025-10705)
     561= 2.4.8 =
     562Major update: OpenRouter integration with 100+ models and new Claude Sonnet 4.5, Opus 4.1, and Haiku 4.5 support
    560563
    561564== License & Warranty ==
Note: See TracChangeset for help on using the changeset viewer.