Chub AI Gemini/PaLM2 Model List Enhancer

Gemini Settings Panel: API version/model selection, parameters, presets, reset, tooltips, export/import, enhanced safety settings, and thinking mode options.

当前为 2025-07-26 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Chub AI Gemini/PaLM2 Model List Enhancer
// @license      MIT
// @namespace    http://tampermonkey.net/
// @version      6.1
// @description  Gemini Settings Panel: API version/model selection, parameters, presets, reset, tooltips, export/import, enhanced safety settings, and thinking mode options.
// @author       Ko16aska
// @match        *://chub.ai/*
// @grant        none
// ==/UserScript==
(function() {
    'use strict';

    // --- LocalStorage keys ---
    const STORAGE_SETTINGS_KEY = 'chubGeminiSettings';
    const STORAGE_PANEL_STATE_KEY = 'chubGeminiPanelState';
    const STORAGE_SINGLE_API_KEY = 'chubGeminiApiKey';
    const STORAGE_API_KEY_LIST = 'chubGeminiApiKeysList';

    // --- Defaults ---
    const DEFAULT_MODEL = 'custom';
    const DEFAULT_API_VERSION = 'v1beta';
    const DEFAULT_USE_CYCLIC_API = false;
    const DEFAULT_CURRENT_API_KEY_INDEX = 0;
    const DEFAULT_THINKING_BUDGET = -1; // Default: Auto
    const DEFAULT_INCLUDE_THOUGHTS = false; // Default: off
    const DEFAULT_OVERRIDE_THINKING_BUDGET = false; // Default: off

    // --- Safety Settings Options ---
    const SAFETY_SETTINGS_OPTIONS = [
        { name: 'BLOCK_NONE', value: 'BLOCK_NONE' },
        { name: 'BLOCK_LOW_AND_ABOVE', value: 'BLOCK_LOW_AND_ABOVE' },
        { name: 'BLOCK_MEDIUM_AND_ABOVE', value: 'BLOCK_MEDIUM_AND_ABOVE' },
        { name: 'BLOCK_HIGH_AND_ABOVE', value: 'BLOCK_HIGH_AND_ABOVE' }
    ];

    const HARM_CATEGORIES = [
        'HARM_CATEGORY_HATE_SPEECH',
        'HARM_CATEGORY_SEXUALLY_EXPLICIT',
        'HARM_CATEGORY_HARASSMENT',
        'HARM_CATEGORY_DANGEROUS_CONTENT'
    ];

    // --- State variables ---
    let allSettings = {};
    let panelState = {
        collapsed: true,
        currentModel: DEFAULT_MODEL,
        currentPreset: null,
        apiVersion: DEFAULT_API_VERSION,
        useCyclicApi: DEFAULT_USE_CYCLIC_API,
        currentApiKeyIndex: DEFAULT_CURRENT_API_KEY_INDEX,
        thinkingParamsCollapsed: true
    };
    let modelList = [];
    let apiKeysList = [];
    let realApiKey = ''; // The actual API key used for requests

    // --- Create panel ---
    function createPanel() {
        const panel = document.createElement('div');
        panel.id = 'gemini-settings-panel';
        if (panelState.collapsed) panel.classList.add('collapsed');

        panel.innerHTML = `
        <div class="toggle-button" title="Show/Hide Panel">▶</div>
        <h4>Gemini Settings</h4>
        <label>API Key:
            <input type="password" id="api-key-input" autocomplete="off" placeholder="Insert API key here" />
            <button id="btn-manage-api-keys">Manage Keys</button>
        </label>
        <label class="toggle-switch-label">
            <input type="checkbox" id="toggle-cyclic-api" />
            <span class="slider round"></span>
            Use API cyclically for each request
        </label>
        <div class="param-group">
            <label>
                API Version:
                <div class="input-container">
                    <select id="apiVersion-select">
                        <option value="v1beta">v1beta</option>
                        <option value="v1">v1</option>
                    </select>
                    <span class="tooltip" title="v1beta: Contains new features, but may be unstable. v1: Stable, recommended for production use.">?</span>
                </div>
            </label>
        </div>
        <button id="btn-get-models">Get Models List</button>
        <label>Preset:
            <select id="preset-select"></select>
            <button id="btn-add-preset">Add</button>
            <button id="btn-delete-preset">Delete</button>
        </label>

        <!-- Corrected Model Selection Group -->
        <div class="param-group">
            <label>Model:</label>
            <div class="input-container model-input-container">
                <select id="model-select"></select>
                <input type="text" id="custom-model-input" placeholder="Enter your model" style="display:none;" />
                <button id="btn-toggle-thinking-params" title="Toggle Thinking Mode Options">🧠</button>
            </div>
        </div>


        <!-- New section for Thinking Mode Parameters, initially hidden -->
        <div id="thinking-mode-params" style="display:none;">
             <!-- Override Thinking Budget Toggle -->
            <label class="toggle-switch-label">
                <input type="checkbox" id="toggle-overrideThinkingBudget" />
                <span class="slider round"></span>
                Override Thinking Budget
                <span class="tooltip" title="The thinking budget parameter works with Gemini 2.5 Pro, 2.5 Flash, and 2.5 Flash Lite.">?</span>
            </label>

            <!-- thinkingBudget -->
            <div class="param-group" id="thinking-budget-group">
                <label>
                    Thinking Budget:
                    <div class="input-container">
                        <input type="number" step="1" id="param-thinkingBudget" />
                        <span class="tooltip" title="Controls the computational budget for thinking. -1 for dynamic, 0 to disable (default), or a specific token count (up to 24576 (32768 for Gemini 2.5 pro)).">?</span>
                    </div>
                </label>
                <input type="range" id="range-thinkingBudget" min="-1" max="32768" step="1" />
            </div>

            <!-- includeThoughts -->
            <label class="toggle-switch-label" id="include-thoughts-label">
                <input type="checkbox" id="toggle-includeThoughts" />
                <span class="slider round"></span>
                Include Thoughts in Response
                <span class="tooltip" title="If enabled, the model's internal thought process will be included in the response.">?</span>
            </label>
        </div>

        <!-- Temperature -->
        <div class="param-group">
            <label>
                Temperature:
                <div class="input-container">
                    <input type="number" step="0.01" id="param-temperature" />
                    <span class="tooltip" title="Controls the randomness of the output. Higher values make the output more random, while lower values make it more deterministic.">?</span>
                </div>
            </label>
            <input type="range" id="range-temperature" min="0" max="2" step="0.01" />
        </div>

        <!-- Max Output Tokens -->
        <div class="param-group">
            <label>
                Max Output Tokens:
                <div class="input-container">
                    <input type="number" step="1" id="param-maxTokens" />
                    <span class="tooltip" title="The maximum number of tokens to generate.">?</span>
                </div>
            </label>
            <input type="range" id="range-maxTokens" min="1" max="65536" step="1" />
        </div>

        <!-- topP -->
        <div class="param-group">
            <label>
                Top P:
                <div class="input-container">
                    <input type="number" step="0.01" id="param-topP" />
                    <span class="tooltip" title="Nucleus sampling parameter. The model considers the smallest set of tokens whose cumulative probability exceeds topP.">?</span>
                </div>
            </label>
            <input type="range" id="range-topP" min="0" max="1" step="0.01" />
        </div>

        <!-- topK -->
        <div class="param-group">
            <label>
                Top K:
                <div class="input-container">
                    <input type="number" step="1" id="param-topK" />
                    <span class="tooltip" title="The model considers only the K tokens with the highest probability.">?</span>
                </div>
            </label>
            <input type="range" id="range-topK" min="0" max="1000" step="1" />
        </div>

        <!-- candidateCount -->
        <div class="param-group">
            <label>
                Candidate Count:
                <div class="input-container">
                    <input type="number" step="1" id="param-candidateCount" />
                    <span class="tooltip" title="The number of generated responses to return. Must be 1.">?</span>
                </div>
            </label>
            <input type="range" id="range-candidateCount" min="1" max="8" step="1" />
        </div>

        <!-- frequencyPenalty -->
        <div class="param-group">
            <label>
                Frequency Penalty:
                <div class="input-container">
                    <input type="number" step="0.01" id="param-frequencyPenalty" />
                    <span class="tooltip" title="Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim.">?</span>
                </div>
            </label>
            <input type="range" id="range-frequencyPenalty" min="-2.0" max="2.0" step="0.01" />
        </div>

        <!-- presencePenalty -->
        <div class="param-group">
            <label>
                Presence Penalty:
                <div class="input-container">
                    <input type="number" step="0.01" id="param-presencePenalty" />
                    <span class="tooltip" title="Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics.">?</span>
                </div>
            </label>
            <input type="range" id="range-presencePenalty" min="-2.0" max="2.0" step="0.01" />
        </div>

        <!-- safetySettings -->
        <div class="param-group">
            <label>
                Safety Settings:
                <div class="input-container">
                    <select id="safety-settings-select"></select>
                    <span class="tooltip" title="Adjusts the safety filtering threshold for generated content.">?</span>
                </div>
            </label>
        </div>

        <button id="btn-save-settings">Save Settings</button>
        <button id="btn-reset-settings">Reset to Defaults</button>
        <button id="btn-export-settings">Export Settings</button>
        <button id="btn-import-settings">Import Settings</button>
        <input type="file" id="input-import-settings" style="display:none;" accept=".json" />
        <div id="save-toast">Settings saved!</div>
        `;

        document.body.appendChild(panel);

        const apiKeyListModal = document.createElement('div');
        apiKeyListModal.id = 'api-key-list-modal';
        apiKeyListModal.style.display = 'none'; // Hidden by default
        apiKeyListModal.innerHTML = `
            <div class="modal-content">
                <h4>Manage API Keys</h4>
                <textarea id="api-keys-textarea" placeholder="Enter API keys, one per line"></textarea>
                <div class="modal-buttons">
                    <button id="btn-save-api-keys">Save</button>
                    <button id="btn-cancel-api-keys">Cancel</button>
                </div>
            </div>
        `;
        document.body.appendChild(apiKeyListModal);


        const style = document.createElement('style');
        style.textContent = `
        :root {
            --scale-factor: 1.0;
        }

        #gemini-settings-panel {
            position: fixed;
            top: 50%;
            right: 0;
            transform: translateY(-50%) translateX(100%);
            background: rgba(30,30,30,0.85);
            color: #eee;
            border-left: calc(1px * var(--scale-factor)) solid #444;
            border-radius: calc(8px * var(--scale-factor)) 0 0 calc(8px * var(--scale-factor));
            padding: calc(min(1.2vw, 6px) * var(--scale-factor)) calc(min(2vw, 10px) * var(--scale-factor));
            box-shadow: 0 calc(4px * var(--scale-factor)) calc(16px * var(--scale-factor)) rgba(0,0,0,0.7);
            font-family: Arial, sans-serif;
            font-size: calc(min(2.5vw, 14px) * var(--scale-factor));
            z-index: 10000;
            transition: transform 0.4s ease;
            user-select: none;
            width: max-content;
            max-width: calc(min(80vw, 350px) * var(--scale-factor));
            box-sizing: border-box;
        }

        #gemini-settings-panel:not(.collapsed) {
            transform: translateY(-50%) translateX(0);
        }

        #gemini-settings-panel h4 {
            text-align: center;
            margin: 0 0 calc(min(1.2vw, 5px) * var(--scale-factor));
            font-size: calc(min(3vw, 16px) * var(--scale-factor));
        }

        #gemini-settings-panel label {
            display: block;
            margin-bottom: calc(min(0.8vw, 3px) * var(--scale-factor));
            font-weight: 600;
            font-size: calc(min(2.5vw, 14px) * var(--scale-factor));
        }

        #gemini-settings-panel input[type="number"],
        #gemini-settings-panel input[type="text"],
        #gemini-settings-panel input[type="password"],
        #gemini-settings-panel select {
            background: #222;
            border: calc(1px * var(--scale-factor)) solid #555;
            border-radius: calc(4px * var(--scale-factor));
            color: #eee;
            padding: calc(min(0.4vw, 2px) * var(--scale-factor)) calc(min(0.8vw, 4px) * var(--scale-factor));
            font-size: calc(min(2.3vw, 13px) * var(--scale-factor));
            width: 100%;
            box-sizing: border-box;
            margin: 0;
        }

        /* Styling for API Key input and button side-by-side */
        #gemini-settings-panel label:has(#api-key-input) {
            display: flex;
            flex-wrap: wrap;
            align-items: center;
            gap: calc(min(0.8vw, 4px) * var(--scale-factor));
        }
        #gemini-settings-panel label:has(#api-key-input) #api-key-input {
            flex-grow: 1;
            min-width: calc(100px * var(--scale-factor));
        }
        #gemini-settings-panel label:has(#api-key-input) #btn-manage-api-keys {
             width: auto;
             padding: calc(min(0.6vw, 3px) * var(--scale-factor)) calc(min(1vw, 6px) * var(--scale-factor));
             margin-top: 0;
        }

        /* Styling for the Model input container */
        .model-input-container #model-select,
        .model-input-container #custom-model-input {
            flex-grow: 1; /* Allows the select/input to fill available space */
            min-width: 0; /* Critical for flex-grow to work properly */
        }
        .model-input-container #btn-toggle-thinking-params {
            flex-shrink: 0; /* Prevents the button from shrinking */
            width: auto;
            margin: 0; /* Reset margins from general button rules */
            padding: calc(min(0.6vw, 3px) * var(--scale-factor)) calc(min(1vw, 6px) * var(--scale-factor));
            line-height: 1; /* Better emoji alignment */
            font-size: calc(min(3vw, 16px) * var(--scale-factor));
        }


        .param-group {
            margin-bottom: calc(min(1.2vw, 5px) * var(--scale-factor));
        }

        .param-group label {
            display: block;
            margin-bottom: calc(min(0.5vw, 1px) * var(--scale-factor));
            font-weight: 600;
            font-size: calc(min(2.5vw, 14px) * var(--scale-factor));
        }

        .param-group .input-container {
            display: flex;
            align-items: center;
            gap: calc(min(0.8vw, 3px) * var(--scale-factor));
            margin-top: calc(0.2vw * var(--scale-factor));
        }

        .param-group .input-container input[type="number"],
        .param-group .input-container input[type="text"],
        .param-group .input-container input[type="password"],
        .param-group .input-container select {
            flex-grow: 1;
            min-width: 0;
            background: #222;
            border: calc(1px * var(--scale-factor)) solid #555;
            border-radius: calc(4px * var(--scale-factor));
            color: #eee;
            padding: calc(min(0.4vw, 2px) * var(--scale-factor)) calc(min(0.8vw, 4px) * var(--scale-factor));
            font-size: calc(min(2.3vw, 13px) * var(--scale-factor));
            box-sizing: border-box;
        }

        .tooltip {
            flex: 0 0 auto;
            cursor: help;
            color: #aaa;
            font-size: calc(min(2vw, 12px) * var(--scale-factor));
            user-select: none;
        }

        .param-group input[type="range"] {
            width: 100% !important;
            margin-top: calc(min(0.8vw, 2px) * var(--scale-factor));
            cursor: pointer;
            display: block;
            height: calc(4px * var(--scale-factor));
            -webkit-appearance: none;
            background: #555;
            border-radius: calc(2px * var(--scale-factor));
        }

        .param-group input[type="range"]::-webkit-slider-thumb {
            -webkit-appearance: none;
            width: calc(12px * var(--scale-factor));
            height: calc(12px * var(--scale-factor));
            background: #4caf50;
            border-radius: 50%;
            cursor: pointer;
        }

        .param-group input[type="range"]::-moz-range-thumb {
            width: calc(12px * var(--scale-factor));
            height: calc(12px * var(--scale-factor));
            background: #4caf50;
            border-radius: 50%;
            cursor: pointer;
        }

        #btn-save-settings, #btn-get-models, #btn-add-preset, #btn-delete-preset, #btn-reset-settings, #btn-export-settings, #btn-import-settings, #btn-manage-api-keys, #btn-save-api-keys, #btn-cancel-api-keys, #btn-toggle-thinking-params {
            width: 100%;
            padding: calc(min(0.8vw, 4px) * var(--scale-factor));
            border: none;
            border-radius: calc(5px * var(--scale-factor));
            background: #4caf50;
            color: #fff;
            font-weight: 600;
            cursor: pointer;
            user-select: none;
            margin-top: calc(min(0.6vw, 3px) * var(--scale-factor));
            transition: background-color 0.3s ease;
            font-size: calc(min(2.5vw, 14px) * var(--scale-factor));
        }

        #btn-get-models {
            margin-bottom: calc(min(1.2vw, 5px) * var(--scale-factor));
        }

        /* New section for thinking mode parameters */
        #thinking-mode-params {
            border: calc(1px * var(--scale-factor)) solid #555;
            border-radius: calc(5px * var(--scale-factor));
            padding: calc(min(1vw, 5px) * var(--scale-factor));
            margin-top: calc(min(1vw, 5px) * var(--scale-factor));
            margin-bottom: calc(min(1.2vw, 5px) * var(--scale-factor));
            background: rgba(40, 40, 40, 0.7);
        }
        #thinking-mode-params .param-group:last-child {
            margin-bottom: 0;
        }
        #thinking-mode-params .toggle-switch-label {
            margin-bottom: calc(min(0.8vw, 3px) * var(--scale-factor));
        }
        #thinking-mode-params .toggle-switch-label:last-of-type {
            margin-bottom: 0;
        }

        /* Style for disabled thinking controls */
        .param-group.disabled, .toggle-switch-label.disabled {
            opacity: 0.5;
            pointer-events: none;
        }


        #btn-save-settings:hover, #btn-get-models:hover, #btn-add-preset:hover, #btn-delete-preset:hover, #btn-reset-settings:hover, #btn-export-settings:hover, #btn-import-settings:hover, #btn-manage-api-keys:hover, #btn-cancel-api-keys:hover, #btn-save-api-keys:hover, #btn-toggle-thinking-params:hover {
            background: #388e3c;
        }

        #save-toast {
            margin-top: calc(min(1.5vw, 4px) * var(--scale-factor));
            text-align: center;
            background: #222;
            color: #0f0;
            padding: calc(min(0.8vw, 4px) * var(--scale-factor));
            border-radius: calc(5px * var(--scale-factor));
            opacity: 0;
            transition: opacity 0.5s ease;
            pointer-events: none;
            user-select: none;
            font-size: calc(min(2.3vw, 13px) * var(--scale-factor));
        }

        #save-toast.show {
            opacity: 1;
        }

        .toggle-button {
            position: absolute !important;
            left: calc(-28px * var(--scale-factor)) !important;
            top: 50% !important;
            transform: translateY(-50%) !important;
            width: calc(28px * (var(--scale-factor))) !important;
            height: calc(48px * (var(--scale-factor))) !important;
            background: rgba(30,30,30,0.85) !important;
            border: calc(1px * var(--scale-factor)) solid #444 !important;
            border-radius: calc(8px * var(--scale-factor)) 0 0 calc(8px * var(--scale-factor)) !important;
            color: #eee !important;
            text-align: center !important;
            line-height: calc(48px * var(--scale-factor)) !important;
            font-size: calc(min(4vw, 20px) * var(--scale-factor)) !important;
            cursor: pointer !important;
            user-select: none !important;
            transition: transform 0.3s ease !important;
        }


        #gemini-settings-panel.collapsed .toggle-button {
            transform: translateY(-50%) rotate(0deg);
        }

        #gemini-settings-panel:not(.collapsed) .toggle-button {
            transform: translateY(-50%) rotate(0deg);
        }

        /* Toggle Switch CSS for "Use API cyclically for each request" and "Include Thoughts"*/
        .toggle-switch-label {
            position: relative;
            display: flex;
            align-items: center;
            width: 100%;
            margin-top: calc(min(0.8vw, 3px) * var(--scale-factor));
            margin-bottom: calc(min(0.8vw, 3px) * var(--scale-factor));
            font-size: calc(min(2.2vw, 12px) * var(--scale-factor));
            padding-left: calc(min(6vw, 35px) * var(--scale-factor));
            cursor: pointer;
            user-select: none;
            box-sizing: border-box;
            min-height: calc(min(3vw, 18px) * var(--scale-factor));
            transition: opacity 0.3s ease;
        }

        .toggle-switch-label input {
            opacity: 0;
            width: 0;
            height: 0;
        }

        .slider {
            position: absolute;
            cursor: pointer;
            top: 0;
            left: 0;
            height: calc(min(3vw, 18px) * var(--scale-factor));
            width: calc(min(5.5vw, 32px) * var(--scale-factor));
            background-color: #ccc;
            -webkit-transition: .4s;
            transition: .4s;
            border-radius: calc(min(1.5vw, 9px) * var(--scale-factor));
        }

        .slider:before {
            position: absolute;
            content: "";
            height: calc(min(2.2vw, 13px) * var(--scale-factor));
            width: calc(min(2.2vw, 13px) * var(--scale-factor));
            left: calc(min(0.4vw, 2.5px) * var(--scale-factor));
            bottom: calc(min(0.4vw, 2.5px) * var(--scale-factor));
            background-color: white;
            -webkit-transition: .4s;
            transition: .4s;
            border-radius: 50%;
        }

        input:checked + .slider {
            background-color: #4caf50;
        }

        input:focus + .slider {
            box-shadow: 0 0 calc(min(0.5vw, 1px) * var(--scale-factor)) #4caf50;
        }

        input:checked + .slider:before {
            -webkit-transform: translateX(calc(min(2.5vw, 14px) * var(--scale-factor)));
            -ms-transform: translateX(calc(min(2.5vw, 14px) * var(--scale-factor)));
            transform: translateX(calc(min(2.5vw, 14px) * var(--scale-factor)));
        }

        /* Responsive adjustments for toggle switch */
        @media screen and (max-width: 600px) {
            .toggle-switch-label {
                min-height: calc(min(4vw, 18px) * var(--scale-factor));
                padding-left: calc(min(8vw, 35px) * var(--scale-factor));
            }
            .slider {
                height: calc(min(4vw, 18px) * var(--scale-factor));
                width: calc(min(7vw, 32px) * var(--scale-factor));
            }
            .slider:before {
                height: calc(min(3vw, 13px) * var(--scale-factor));
                width: calc(min(3vw, 13px) * var(--scale-factor));
            }
            input:checked + .slider:before {
                -webkit-transform: translateX(calc(min(4vw, 14px) * var(--scale-factor)));
                -ms-transform: translateX(calc(min(4vw, 14px) * var(--scale-factor)));
                transform: translateX(calc(min(4vw, 14px) * var(--scale-factor)));
            }
        }
        /* API Key List Modal Styles */
        #api-key-list-modal {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.7); /* Dimmed background */
            display: flex; /* Use flexbox for centering */
            justify-content: center;
            align-items: center;
            z-index: 10001; /* Must be on top of the settings panel */
        }

        #api-key-list-modal .modal-content {
            background: #333;
            padding: calc(min(2vw, 15px) * var(--scale-factor));
            border-radius: calc(8px * var(--scale-factor));
            box-shadow: 0 calc(4px * var(--scale-factor)) calc(20px * var(--scale-factor)) rgba(0,0,0,0.9);
            width: calc(min(90vw, 500px) * var(--scale-factor));
            max-height: calc(90vh * var(--scale-factor));
            display: flex;
            flex-direction: column;
            gap: calc(min(1.5vw, 10px) * var(--scale-factor));
        }

        #api-key-list-modal h4 {
            color: #eee;
            text-align: center;
            margin: 0;
            font-size: calc(min(3.5vw, 18px) * var(--scale-factor));
        }

        #api-key-list-modal textarea {
            width: 100%;
            flex-grow: 1; /* Allows the textarea to take up available space */
            min-height: calc(150px * var(--scale-factor)); /* Minimum height */
            background: #222;
            border: calc(1px * var(--scale-factor)) solid #555;
            border-radius: calc(4px * var(--scale-factor));
            color: #eee;
            padding: calc(min(1vw, 5px) * var(--scale-factor));
            font-size: calc(min(2.5vw, 14px) * var(--scale-factor));
            resize: vertical; /* Allow vertical resizing only */
            box-sizing: border-box;
        }

        #api-key-list-modal .modal-buttons {
            display: flex;
            justify-content: flex-end; /* Buttons on the right */
            gap: calc(min(1vw, 8px) * var(--scale-factor));
            margin-top: calc(min(1vw, 8px) * var(--scale-factor));
        }

        #api-key-list-modal .modal-buttons button {
            padding: calc(min(0.8vw, 6px) * var(--scale-factor)) calc(min(1.5vw, 12px) * var(--scale-factor));
            font-size: calc(min(2.5vw, 14px) * var(--scale-factor));
            width: auto; /* Remove 100% width */
        }

        #api-key-list-modal #btn-save-api-keys {
            background: #4caf50;
        }
        #api-key-list-modal #btn-save-api-keys:hover {
            background: #388e3c;
        }
        #api-key-list-modal #btn-cancel-api-keys {
            background: #f44336; /* Red for cancel */
        }
        #api-key-list-modal #btn-cancel-api-keys:hover {
            background: #d32f2f;
        }
        #gemini-settings-panel {
                overflow-y: auto; /* Enables vertical scrolling */
                max-height: 90vh; /* Sets a maximum height for the panel */
                scrollbar-width: thin; /* For Firefox */
                scrollbar-color: #888 #333; /* For Firefox */
            }

            #gemini-settings-panel::-webkit-scrollbar {
                width: 8px; /* Width of the scrollbar */
            }

            #gemini-settings-panel::-webkit-scrollbar-track {
                background: #333; /* Background of the scrollbar track */
                border-radius: 4px;
            }

            #gemini-settings-panel::-webkit-scrollbar-thumb {
                background-color: #888; /* Color of the scrollbar thumb */
                border-radius: 4px;
            }

            #gemini-settings-panel::-webkit-scrollbar-thumb:hover {
                background-color: #aaa; /* Color of the scrollbar thumb on hover */
            }
        `;
        document.head.appendChild(style);

        let lastDevicePixelRatio = window.devicePixelRatio;

        function updateScaleFactor() {
          const scale = 1 / window.devicePixelRatio;
          document.documentElement.style.setProperty('--scale-factor', scale);
        }

        function checkZoom() {
          if (window.devicePixelRatio !== lastDevicePixelRatio) {
            lastDevicePixelRatio = window.devicePixelRatio;
            updateScaleFactor();
          }
          requestAnimationFrame(checkZoom);
        }

        updateScaleFactor();
        checkZoom();

        const toggleBtn = panel.querySelector('.toggle-button');
        const apiKeyInput = panel.querySelector('#api-key-input');
        const btnManageApiKeys = panel.querySelector('#btn-manage-api-keys');
        const toggleCyclicApi = panel.querySelector('#toggle-cyclic-api');
        const apiVersionSelect = panel.querySelector('#apiVersion-select');
        const btnGetModels = panel.querySelector('#btn-get-models');
        const presetSelect = panel.querySelector('#preset-select');
        const btnAddPreset = panel.querySelector('#btn-add-preset');
        const btnDeletePreset = panel.querySelector('#btn-delete-preset');
        const modelSelect = panel.querySelector('#model-select');
        const customModelInput = panel.querySelector('#custom-model-input');
        const btnToggleThinkingParams = panel.querySelector('#btn-toggle-thinking-params');
        const thinkingModeParamsDiv = panel.querySelector('#thinking-mode-params');
        const toggleOverrideThinkingBudget = panel.querySelector('#toggle-overrideThinkingBudget');
        const thinkingBudgetGroup = panel.querySelector('#thinking-budget-group');
        const includeThoughtsLabel = panel.querySelector('#include-thoughts-label');
        const toggleIncludeThoughts = panel.querySelector('#toggle-includeThoughts');
        const btnSaveSettings = panel.querySelector('#btn-save-settings');
        const btnResetSettings = panel.querySelector('#btn-reset-settings');
        const btnExportSettings = panel.querySelector('#btn-export-settings');
        const inputImportSettings = panel.querySelector('#input-import-settings');
        const btnImportSettings = panel.querySelector('#btn-import-settings');
        const saveToast = panel.querySelector('#save-toast');
        const safetySettingsSelect = panel.querySelector('#safety-settings-select');

        // Modal elements
        const apiKeyKeysTextarea = apiKeyListModal.querySelector('#api-keys-textarea');
        const btnSaveApiKeys = apiKeyListModal.querySelector('#btn-save-api-keys');
        const btnCancelApiKeys = apiKeyListModal.querySelector('#btn-cancel-api-keys');


        const elems = {
            temperature: { num: panel.querySelector('#param-temperature'), range: panel.querySelector('#range-temperature') },
            maxTokens: { num: panel.querySelector('#param-maxTokens'), range: panel.querySelector('#range-maxTokens') },
            topP: { num: panel.querySelector('#param-topP'), range: panel.querySelector('#range-topP') },
            topK: { num: panel.querySelector('#param-topK'), range: panel.querySelector('#range-topK') },
            candidateCount: { num: panel.querySelector('#param-candidateCount'), range: panel.querySelector('#range-candidateCount') },
            frequencyPenalty: { num: panel.querySelector('#param-frequencyPenalty'), range: panel.querySelector('#range-frequencyPenalty') },
            presencePenalty: { num: panel.querySelector('#param-presencePenalty'), range: panel.querySelector('#range-presencePenalty') },
            thinkingBudget: { num: panel.querySelector('#param-thinkingBudget'), range: panel.querySelector('#range-thinkingBudget') }
        };

        // Populate safety settings select
        SAFETY_SETTINGS_OPTIONS.forEach(option => {
            const optElem = document.createElement('option');
            optElem.value = option.value;
            optElem.textContent = option.name;
            safetySettingsSelect.appendChild(optElem);
        });

        // Click outside panel to collapse
        document.addEventListener('click', (event) => {
            if (!panel.contains(event.target) && !toggleBtn.contains(event.target) && !apiKeyListModal.contains(event.target) && !panelState.collapsed) {
                panelState.collapsed = true;
                panel.classList.add('collapsed');
                saveAllSettings();
            }
        });

        function getApiUrl(path) {
            return `https://generativelanguage.googleapis.com/${panelState.apiVersion}/${path}`;
        }

        function maskKeyDisplay(key) {
            if (!key) return '';
            if (key.length <= 4) return '*'.repeat(key.length);
            return key[0] + '*'.repeat(key.length - 2) + key[key.length - 1];
        }

        function loadGlobalApiKeySettings() {
            const storedKeysList = localStorage.getItem(STORAGE_API_KEY_LIST) || '';
            apiKeysList = storedKeysList.split('\n').map(k => k.trim()).filter(k => k.length > 0);

            if (panelState.currentApiKeyIndex === undefined || panelState.currentApiKeyIndex < 0 || panelState.currentApiKeyIndex >= apiKeysList.length) {
                panelState.currentApiKeyIndex = 0;
            }
            updateRealApiKey();
        }

        function updateRealApiKey() {
            if (panelState.useCyclicApi && apiKeysList.length > 0) {
                realApiKey = apiKeysList[panelState.currentApiKeyIndex % apiKeysList.length];
                apiKeyInput.disabled = true;
                apiKeyInput.type = 'text';
                apiKeyInput.value = realApiKey;
                apiKeyInput.setAttribute('title', 'Currently active key from your list (disabled in cyclic mode). Use "Manage Keys" to edit the list.');
            } else {
                realApiKey = localStorage.getItem(STORAGE_SINGLE_API_KEY) || '';
                apiKeyInput.disabled = false;
                apiKeyInput.type = 'password';
                apiKeyInput.value = maskKeyDisplay(realApiKey);
                apiKeyInput.removeAttribute('title');
            }
            toggleCyclicApi.checked = panelState.useCyclicApi;
        }

        function saveApiKeysListFromModal(keysString) {
            localStorage.setItem(STORAGE_API_KEY_LIST, keysString);
            apiKeysList = keysString.split('\n').map(k => k.trim()).filter(k => k.length > 0);

            if (panelState.currentApiKeyIndex >= apiKeysList.length) {
                panelState.currentApiKeyIndex = 0;
            }
            saveAllSettings();
            updateRealApiKey();
            apiKeyListModal.style.display = 'none';
        }

        function saveSingleApiKey(newKey) {
            localStorage.setItem(STORAGE_SINGLE_API_KEY, newKey.trim());
            if (!panelState.useCyclicApi) {
                realApiKey = newKey.trim();
            }
            updateRealApiKey();
        }

        apiKeyInput.addEventListener('focus', () => {
            if (!panelState.useCyclicApi) {
                apiKeyInput.type = 'text';
                apiKeyInput.value = realApiKey;
            }
        });
        apiKeyInput.addEventListener('blur', () => {
            if (!panelState.useCyclicApi) {
                saveSingleApiKey(apiKeyInput.value);
                apiKeyInput.type = 'password';
                apiKeyInput.value = maskKeyDisplay(realApiKey);
            }
        });

        btnManageApiKeys.addEventListener('click', () => {
            apiKeyKeysTextarea.value = apiKeysList.join('\n');
            apiKeyListModal.style.display = 'flex';
        });

        btnSaveApiKeys.addEventListener('click', () => {
            saveApiKeysListFromModal(apiKeyKeysTextarea.value);
        });

        btnCancelApiKeys.addEventListener('click', () => {
            apiKeyListModal.style.display = 'none';
        });

        toggleCyclicApi.addEventListener('change', () => {
            panelState.useCyclicApi = toggleCyclicApi.checked;
            if (panelState.useCyclicApi && apiKeysList.length === 0) {
                 alert('No API keys found in the list. Please add keys using "Manage Keys" to use cyclic mode.');
                 panelState.useCyclicApi = false;
                 toggleCyclicApi.checked = false;
            }
            saveAllSettings();
            updateRealApiKey();
        });


        apiVersionSelect.addEventListener('change', () => {
            panelState.apiVersion = apiVersionSelect.value;
            saveAllSettings();
        });

        function fillModelSelect() {
            modelSelect.innerHTML = '';
            const optCustom = document.createElement('option');
            optCustom.value = 'custom';
            optCustom.textContent = 'Custom';
            modelSelect.appendChild(optCustom);
            for (const m of modelList) {
                const opt = document.createElement('option');
                opt.value = m;
                opt.textContent = m;
                modelSelect.appendChild(opt);
            }
        }

        function updateCustomModelInputVisibility() {
            if(modelSelect.value === 'custom') {
                customModelInput.style.display = 'block';
            } else {
                customModelInput.style.display = 'none';
            }
        }

        function updateThinkingParamsVisibility() {
            if (panelState.thinkingParamsCollapsed) {
                thinkingModeParamsDiv.style.display = 'none';
            } else {
                thinkingModeParamsDiv.style.display = 'block';
            }
        }

        function updateThinkingControlsState() {
            const isEnabled = toggleOverrideThinkingBudget.checked;
            thinkingBudgetGroup.classList.toggle('disabled', !isEnabled);
            includeThoughtsLabel.classList.toggle('disabled', !isEnabled);
            elems.thinkingBudget.num.disabled = !isEnabled;
            elems.thinkingBudget.range.disabled = !isEnabled;
            toggleIncludeThoughts.disabled = !isEnabled;
        }

        toggleOverrideThinkingBudget.addEventListener('change', updateThinkingControlsState);

        function loadModelSettings(model) {
            if (!model) model = DEFAULT_MODEL;
            const settings = allSettings[model] || {
                temperature: 2.0,
                maxOutputTokens: 65536,
                topP: 0.95,
                topK: 0,
                candidateCount: 1,
                frequencyPenalty: 0.0,
                presencePenalty: 0.0,
                safetySettingsThreshold: 'BLOCK_NONE',
                thinkingBudget: DEFAULT_THINKING_BUDGET,
                includeThoughts: DEFAULT_INCLUDE_THOUGHTS,
                overrideThinkingBudget: DEFAULT_OVERRIDE_THINKING_BUDGET
            };
            elems.temperature.num.value = settings.temperature;
            elems.temperature.range.value = settings.temperature;
            elems.maxTokens.num.value = settings.maxOutputTokens;
            elems.maxTokens.range.value = settings.maxOutputTokens;
            elems.topP.num.value = settings.topP;
            elems.topP.range.value = settings.topP;
            elems.topK.num.value = settings.topK;
            elems.topK.range.value = settings.topK;
            elems.candidateCount.num.value = settings.candidateCount;
            elems.candidateCount.range.value = settings.candidateCount;
            elems.frequencyPenalty.num.value = settings.frequencyPenalty;
            elems.frequencyPenalty.range.value = settings.frequencyPenalty;
            elems.presencePenalty.num.value = settings.presencePenalty;
            elems.presencePenalty.range.value = settings.presencePenalty;
            safetySettingsSelect.value = settings.safetySettingsThreshold;
            elems.thinkingBudget.num.value = settings.thinkingBudget;
            elems.thinkingBudget.range.value = settings.thinkingBudget;
            toggleIncludeThoughts.checked = settings.includeThoughts;
            toggleOverrideThinkingBudget.checked = settings.overrideThinkingBudget;

            if (model === 'custom') {
                customModelInput.value = allSettings.customModelString || '';
            } else {
                customModelInput.value = '';
            }
            updateThinkingControlsState();
        }

        function getCurrentSettings() {
            return {
                temperature: clamp(parseFloat(elems.temperature.num.value), 0, 2),
                maxOutputTokens: clamp(parseInt(elems.maxTokens.num.value), 1, 65536),
                topP: clamp(parseFloat(elems.topP.num.value), 0, 1),
                topK: clamp(parseInt(elems.topK.num.value), 0, 1000),
                candidateCount: clamp(parseInt(elems.candidateCount.num.value), 1, 8),
                frequencyPenalty: clamp(parseFloat(elems.frequencyPenalty.num.value), -2.0, 2.0),
                presencePenalty: clamp(parseFloat(elems.presencePenalty.num.value), -2.0, 2.0),
                safetySettingsThreshold: safetySettingsSelect.value,
                customModelString: customModelInput.value.trim(),
                thinkingBudget: clamp(parseInt(elems.thinkingBudget.num.value), -1, 32768),
                includeThoughts: toggleIncludeThoughts.checked,
                overrideThinkingBudget: toggleOverrideThinkingBudget.checked
            };
        }

        function saveModelSettings(model) {
            if (!model) model = DEFAULT_MODEL;
            const settings = getCurrentSettings();
            allSettings[model] = {
                temperature: settings.temperature,
                maxOutputTokens: settings.maxOutputTokens,
                topP: settings.topP,
                topK: settings.topK,
                candidateCount: settings.candidateCount,
                frequencyPenalty: settings.frequencyPenalty,
                presencePenalty: settings.presencePenalty,
                safetySettingsThreshold: settings.safetySettingsThreshold,
                thinkingBudget: settings.thinkingBudget,
                includeThoughts: settings.includeThoughts,
                overrideThinkingBudget: settings.overrideThinkingBudget
            };
            if (model === 'custom') {
                allSettings.customModelString = settings.customModelString;
            }
            if (panelState.currentPreset) {
                const preset = allSettings.presets.find(p => p.name === panelState.currentPreset);
                if (preset) {
                    preset.model = model;
                    preset.settings = settings;
                }
            }
            saveAllSettings();
        }

        function clamp(val, min, max) {
            if (isNaN(val)) return min;
            return Math.min(max, Math.max(min, val));
        }

        function linkInputs(numInput, rangeInput, min, max, step) {
            numInput.min = min;
            numInput.max = max;
            numInput.step = step;
            rangeInput.min = min;
            rangeInput.max = max;
            rangeInput.step = step;
            numInput.addEventListener('input', () => {
                let v = clamp(parseFloat(numInput.value), min, max);
                numInput.value = v;
                rangeInput.value = v;
            });
            rangeInput.addEventListener('input', () => {
                let v = clamp(parseFloat(rangeInput.value), min, max);
                rangeInput.value = v;
                numInput.value = v;
            });
        }

        function saveAllSettings() {
            try {
                localStorage.setItem(STORAGE_SETTINGS_KEY, JSON.stringify(allSettings));
                localStorage.setItem(STORAGE_PANEL_STATE_KEY, JSON.stringify(panelState));
                showSaveToast();
            } catch(e) {
                console.error('Error saving settings:', e);
            }
        }

        function loadAllSettings() {
            try {
                const s = localStorage.getItem(STORAGE_SETTINGS_KEY);
                if (s) allSettings = JSON.parse(s);
                else allSettings = {};
            } catch(e) {
                console.error('Error loading settings:', e);
                allSettings = {};
            }
            if(allSettings.modelList && Array.isArray(allSettings.modelList)) {
                modelList = allSettings.modelList;
            } else {
                modelList = [];
            }
        }

        function loadPanelState() {
            try {
                const s = localStorage.getItem(STORAGE_PANEL_STATE_KEY);
                if(s) {
                    const loadedState = JSON.parse(s);
                    panelState = {
                        collapsed: true,
                        currentModel: DEFAULT_MODEL,
                        currentPreset: null,
                        apiVersion: DEFAULT_API_VERSION,
                        useCyclicApi: DEFAULT_USE_CYCLIC_API,
                        currentApiKeyIndex: DEFAULT_CURRENT_API_KEY_INDEX,
                        thinkingParamsCollapsed: true,
                        ...loadedState
                    };
                }
            } catch(e) {
                console.error('Error loading panel state:', e);
            }
        }

        let toastTimeout = null;
        function showSaveToast() {
            saveToast.classList.add('show');
            if(toastTimeout) clearTimeout(toastTimeout);
            toastTimeout = setTimeout(() => {
                saveToast.classList.remove('show');
            }, 1800);
        }

        async function fetchModelsFromApi() {
            const keyToUse = realApiKey;
            if(!keyToUse) {
                alert('Please enter an API key or add keys to the list.');
                return;
            }
            btnGetModels.disabled = true;
            btnGetModels.textContent = 'Loading...';
            try {
                const url = getApiUrl(`models?key=${encodeURIComponent(keyToUse)}`);
                const response = await fetch(url);
                if(!response.ok) throw new Error(`Network response was not ok: ${response.status} ${response.statusText}`);
                const data = await response.json();
                if(data.models && Array.isArray(data.models)) {
                    modelList = data.models
                        .map(m => m.name)
                        .map(name => name.startsWith('models/') ? name.substring('models/'.length) : name);
                } else {
                    modelList = [];
                }
                fillModelSelect();
                allSettings.modelList = modelList;
                saveAllSettings();
                if(panelState.currentModel && modelList.includes(panelState.currentModel)) {
                    modelSelect.value = panelState.currentModel;
                } else {
                    modelSelect.value = DEFAULT_MODEL;
                    panelState.currentModel = DEFAULT_MODEL;
                }
                updateCustomModelInputVisibility();
                loadModelSettings(panelState.currentModel);
            } catch (e) {
                alert('Error loading models: ' + e.message);
                console.error(e);
            } finally {
                btnGetModels.disabled = false;
                btnGetModels.textContent = 'Get Models List';
            }
        }

        function replaceUrlParts(url, modelName) {
            if(typeof url !== 'string') return url;

            url = url.replace(/(v1beta|v1)\//, `${panelState.apiVersion}/`);

            if(modelName === 'custom') {
                modelName = allSettings.customModelString || '';
                if(!modelName) return url;
            }
            return url.replace(/(models\/[^:]+)(:)?/, (m, p1, p2) => {
                const newModelPath = 'models/' + modelName;
                return newModelPath + (p2 || '');
            });
        }


        const originalFetch = window.fetch;
        window.fetch = async function(input, init) {
            let requestUrl;
            let requestInit = init || {};

            if (input instanceof Request) {
                requestUrl = input.url;
                const { url, ...inputProps } = input;
                requestInit = { ...inputProps, ...requestInit };
                delete requestInit.url;
            } else if (typeof input === 'string') {
                requestUrl = input;
            } else {
                return originalFetch(input, init);
            }

            let currentUrlObj;
            try {
                currentUrlObj = new URL(requestUrl);
            } catch (e) {
                console.warn('Invalid URL encountered, skipping modification:', requestUrl, e);
                return originalFetch(input, init);
            }

            const isGoogleGenLangApi = requestUrl.includes('generativelanguage.googleapis.com');
            const isGenerateContentRequest = requestUrl.includes('generateContent?');

            if (isGoogleGenLangApi) {
                if (panelState.useCyclicApi) {
                    if (isGenerateContentRequest && apiKeysList.length > 0) {
                        const nextApiKey = apiKeysList[panelState.currentApiKeyIndex % apiKeysList.length];
                        panelState.currentApiKeyIndex = (panelState.currentApiKeyIndex + 1) % apiKeysList.length;
                        saveAllSettings();
                        updateRealApiKey();
                        currentUrlObj.searchParams.set('key', nextApiKey);
                    } else if (realApiKey) {
                        currentUrlObj.searchParams.set('key', realApiKey);
                    }
                } else {
                    if (!currentUrlObj.searchParams.has('key')) {
                        const singleApiKey = localStorage.getItem(STORAGE_SINGLE_API_KEY) || '';
                        if (singleApiKey) {
                            currentUrlObj.searchParams.set('key', singleApiKey);
                        }
                    }
                }
            }
            requestUrl = currentUrlObj.toString();
            requestUrl = replaceUrlParts(requestUrl, panelState.currentModel);

            if (requestInit.body && typeof requestInit.body === 'string' &&
                (requestInit.body.includes('"generationConfig"') || requestInit.body.includes('"safetySettings"'))) {
                try {
                    const requestBody = JSON.parse(requestInit.body);
                    const s = allSettings[panelState.currentModel] || {
                        temperature: 2,
                        maxOutputTokens: 65536,
                        topP: 0.95,
                        topK: 0,
                        candidateCount: 1,
                        frequencyPenalty: 0.0,
                        presencePenalty: 0.0,
                        safetySettingsThreshold: 'BLOCK_NONE',
                        thinkingBudget: DEFAULT_THINKING_BUDGET,
                        includeThoughts: DEFAULT_INCLUDE_THOUGHTS,
                        overrideThinkingBudget: DEFAULT_OVERRIDE_THINKING_BUDGET
                    };

                    if (!requestBody.generationConfig) requestBody.generationConfig = {};
                    requestBody.generationConfig.temperature = s.temperature;
                    requestBody.generationConfig.maxOutputTokens = s.maxOutputTokens;
                    requestBody.generationConfig.topP = s.topP;
                    requestBody.generationConfig.topK = s.topK;
                    requestBody.generationConfig.candidateCount = s.candidateCount;
                    requestBody.generationConfig.frequencyPenalty = s.frequencyPenalty;
                    requestBody.generationConfig.presencePenalty = s.presencePenalty;

                    const selectedThreshold = s.safetySettingsThreshold;
                    if (selectedThreshold) {
                        requestBody.safetySettings = HARM_CATEGORIES.map(category => ({
                            category: category,
                            threshold: selectedThreshold
                        }));
                    } else {
                        delete requestBody.safetySettings;
                    }

                    // --- thinkingConfig LOGIC based on override toggle ---
                    if (s.overrideThinkingBudget) {
                        const thinkingBudget = s.thinkingBudget;
                        const includeThoughts = s.includeThoughts;

                        // Add thinkingConfig only if budget is not default or thoughts are included
                        if (thinkingBudget || includeThoughts) {
                            requestBody.generationConfig.thinkingConfig = { thinkingBudget: thinkingBudget };
                            if (includeThoughts) {
                                requestBody.generationConfig.thinkingConfig.includeThoughts = true;
                            }
                        } else {
                            delete requestBody.generationConfig.thinkingConfig;
                        }
                    } else {
                        // If override is off, ensure thinkingConfig is not in the request
                        delete requestBody.generationConfig.thinkingConfig;
                    }

                    const newBodyString = JSON.stringify(requestBody);
                    requestInit.body = newBodyString;

                    if (requestInit.headers) {
                        const headers = new Headers(requestInit.headers);
                        if (headers.has('Content-Length')) {
                            headers.set('Content-Length', newBodyString.length.toString());
                            requestInit.headers = headers;
                        }
                    }

                } catch(e) {
                    console.error('Error processing request body for generationConfig/safetySettings/thinkingConfig:', e);
                }
            }

            // Await the original fetch call
            const response = await originalFetch(requestUrl, requestInit);

            // --- NEW: Intercept and process the response ---
            const s = allSettings[panelState.currentModel] || {};
            if (response.ok && requestUrl.includes('generateContent?') && s.overrideThinkingBudget && s.includeThoughts) {
                try {
                    const data = await response.json();
                    if (data.candidates && data.candidates[0] && data.candidates[0].content && Array.isArray(data.candidates[0].content.parts) && data.candidates[0].content.parts.length > 1) {
                        const parts = data.candidates[0].content.parts;
                        const thoughtPart = parts.find(p => p.thought === true);
                        const textPart = parts.find(p => !p.thought);

                        if (thoughtPart && textPart) {
                            const combinedText = `${thoughtPart.text}\n\n***\n\n${textPart.text}`;
                            data.candidates[0].content.parts = [{ text: combinedText }];

                            const newBody = JSON.stringify(data);
                            const newHeaders = new Headers(response.headers);
                            newHeaders.set('Content-Length', newBody.length.toString());

                            // Return a new response with the modified body
                            return new Response(newBody, {
                                status: response.status,
                                statusText: response.statusText,
                                headers: newHeaders
                            });
                        }
                    }
                } catch(e) {
                    console.error("Error processing Gemini response to combine thoughts:", e);
                    // If anything fails, return the original response by re-cloning it
                    // (since the body might have been consumed by await response.json()).
                    // A better approach would have been to clone it first, but for this case,
                    // we can assume the browser will receive the original untouched stream.
                    return originalFetch(requestUrl, requestInit);
                }
            }

            // Return the original response if no modification was needed
            return response;
        };

        toggleBtn.onclick = () => {
            panelState.collapsed = !panelState.collapsed;
            panel.classList.toggle('collapsed');
            saveAllSettings();
        };

        modelSelect.onchange = () => {
            panelState.currentModel = modelSelect.value;
            updateCustomModelInputVisibility();
            loadModelSettings(panelState.currentModel);
        };

        btnToggleThinkingParams.onclick = () => {
            panelState.thinkingParamsCollapsed = !panelState.thinkingParamsCollapsed;
            updateThinkingParamsVisibility();
            saveAllSettings();
        };

        linkInputs(elems.temperature.num, elems.temperature.range, 0, 2, 0.01);
        linkInputs(elems.maxTokens.num, elems.maxTokens.range, 1, 65536, 1);
        linkInputs(elems.topP.num, elems.topP.range, 0, 1, 0.01);
        linkInputs(elems.topK.num, elems.topK.range, 0, 1000, 1);
        linkInputs(elems.candidateCount.num, elems.candidateCount.range, 1, 8, 1);
        linkInputs(elems.frequencyPenalty.num, elems.frequencyPenalty.range, -2.0, 2.0, 0.01);
        linkInputs(elems.presencePenalty.num, elems.presencePenalty.range, -2.0, 2.0, 0.01);
        linkInputs(elems.thinkingBudget.num, elems.thinkingBudget.range, -1, 32768, 1);

        btnGetModels.onclick = fetchModelsFromApi;

        btnSaveSettings.onclick = () => {
            saveModelSettings(modelSelect.value);
        };

        function fillPresetSelect() {
            presetSelect.innerHTML = '';
            const opt = document.createElement('option');
            opt.value = '';
            opt.textContent = 'Select Preset';
            presetSelect.appendChild(opt);
            if (allSettings.presets) {
                allSettings.presets.forEach(p => {
                    const opt = document.createElement('option');
                    opt.value = p.name;
                    opt.textContent = p.name;
                    presetSelect.appendChild(opt);
                });
            }
            if (panelState.currentPreset) {
                presetSelect.value = panelState.currentPreset;
            }
        }

        function loadPreset(preset) {
            const model = preset.model;
            allSettings[model] = {
                temperature: preset.settings.temperature,
                maxOutputTokens: preset.settings.maxOutputTokens,
                topP: preset.settings.topP,
                topK: preset.settings.topK,
                candidateCount: preset.settings.candidateCount !== undefined ? preset.settings.candidateCount : 1,
                frequencyPenalty: preset.settings.frequencyPenalty !== undefined ? preset.settings.frequencyPenalty : 0.0,
                presencePenalty: preset.settings.presencePenalty !== undefined ? preset.settings.presencePenalty : 0.0,
                safetySettingsThreshold: preset.settings.safetySettingsThreshold !== undefined ? preset.settings.safetySettingsThreshold : 'BLOCK_NONE',
                thinkingBudget: preset.settings.thinkingBudget !== undefined ? preset.settings.thinkingBudget : DEFAULT_THINKING_BUDGET,
                includeThoughts: preset.settings.includeThoughts !== undefined ? preset.settings.includeThoughts : DEFAULT_INCLUDE_THOUGHTS,
                overrideThinkingBudget: preset.settings.overrideThinkingBudget !== undefined ? preset.settings.overrideThinkingBudget : DEFAULT_OVERRIDE_THINKING_BUDGET
            };
            if (model === 'custom') {
                allSettings.customModelString = preset.settings.customModelString || '';
            }
            panelState.currentModel = model;
            panelState.currentPreset = preset.name;
            modelSelect.value = model;
            updateCustomModelInputVisibility();
            loadModelSettings(model);
            saveAllSettings();
        }

        presetSelect.onchange = () => {
            const name = presetSelect.value;
            if (name) {
                const preset = allSettings.presets.find(p => p.name === name);
                if (preset) loadPreset(preset);
            } else {
                panelState.currentPreset = null;
                loadModelSettings(panelState.currentModel);
                saveAllSettings();
            }
        };

        btnAddPreset.onclick = () => {
            const name = prompt('Enter preset name:');
            if (name) {
                if (!allSettings.presets) allSettings.presets = [];
                if (allSettings.presets.some(p => p.name === name)) {
                    alert('A preset with this name already exists. Please choose a different name.');
                    return;
                }
                const settings = getCurrentSettings();
                const preset = { name, model: panelState.currentModel, settings };
                allSettings.presets.push(preset);
                saveAllSettings();
                fillPresetSelect();
                presetSelect.value = name;
                panelState.currentPreset = name;
            }
        };

        btnDeletePreset.onclick = () => {
            const name = presetSelect.value;
            if (name && confirm(`Are you sure you want to delete preset "${name}"?`)) {
                allSettings.presets = allSettings.presets.filter(p => p.name !== name);
                if (panelState.currentPreset === name) {
                    panelState.currentPreset = null;
                }
                saveAllSettings();
                fillPresetSelect();
                if (!panelState.currentPreset) {
                    loadModelSettings(panelState.currentModel);
                }
            }
        };

        btnResetSettings.onclick = () => {
            const defaultSettings = {
                temperature: 2.0,
                maxOutputTokens: 65536,
                topP: 0.95,
                topK: 0,
                candidateCount: 1,
                frequencyPenalty: 0.0,
                presencePenalty: 0.0,
                safetySettingsThreshold: 'BLOCK_NONE',
                thinkingBudget: DEFAULT_THINKING_BUDGET,
                includeThoughts: DEFAULT_INCLUDE_THOUGHTS,
                overrideThinkingBudget: DEFAULT_OVERRIDE_THINKING_BUDGET
            };
            elems.temperature.num.value = defaultSettings.temperature;
            elems.temperature.range.value = defaultSettings.temperature;
            elems.maxTokens.num.value = defaultSettings.maxOutputTokens;
            elems.maxTokens.range.value = defaultSettings.maxOutputTokens;
            elems.topP.num.value = defaultSettings.topP;
            elems.topP.range.value = defaultSettings.topP;
            elems.topK.num.value = defaultSettings.topK;
            elems.topK.range.value = defaultSettings.topK;
            elems.candidateCount.num.value = defaultSettings.candidateCount;
            elems.candidateCount.range.value = defaultSettings.candidateCount;
            elems.frequencyPenalty.num.value = defaultSettings.frequencyPenalty;
            elems.frequencyPenalty.range.value = defaultSettings.frequencyPenalty;
            elems.presencePenalty.num.value = defaultSettings.presencePenalty;
            elems.presencePenalty.range.value = defaultSettings.presencePenalty;
            safetySettingsSelect.value = defaultSettings.safetySettingsThreshold;
            elems.thinkingBudget.num.value = defaultSettings.thinkingBudget;
            elems.thinkingBudget.range.value = defaultSettings.thinkingBudget;
            toggleIncludeThoughts.checked = defaultSettings.includeThoughts;
            toggleOverrideThinkingBudget.checked = defaultSettings.overrideThinkingBudget;

            if (panelState.currentModel === 'custom') customModelInput.value = '';
            allSettings[panelState.currentModel] = { ...defaultSettings };
            if (panelState.currentModel === 'custom') allSettings.customModelString = '';
            panelState.currentPreset = null;
            fillPresetSelect();
            updateThinkingControlsState();
            saveAllSettings();
        };

        btnExportSettings.onclick = () => {
            const exportData = {
                settings: allSettings,
                panelState: panelState,
                singleApiKey: localStorage.getItem(STORAGE_SINGLE_API_KEY),
                apiKeysList: localStorage.getItem(STORAGE_API_KEY_LIST)
            };
            const json = JSON.stringify(exportData, null, 2);
            const blob = new Blob([json], { type: 'application/json' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = 'chub_gemini_settings.json';
            a.click();
            URL.revokeObjectURL(url);
        };

        btnImportSettings.onclick = () => {
            inputImportSettings.click();
        };

        inputImportSettings.onchange = () => {
            const file = inputImportSettings.files[0];
            if (file) {
                const reader = new FileReader();
                reader.onload = (e) => {
                    try {
                        const importedData = JSON.parse(e.target.result);
                        if (importedData.settings) allSettings = importedData.settings;
                        if (importedData.panelState) {
                            panelState = {
                                collapsed: true, currentModel: DEFAULT_MODEL, currentPreset: null,
                                apiVersion: DEFAULT_API_VERSION, useCyclicApi: DEFAULT_USE_CYCLIC_API,
                                currentApiKeyIndex: DEFAULT_CURRENT_API_KEY_INDEX, thinkingParamsCollapsed: true,
                                ...importedData.panelState
                            };
                        }
                        if (importedData.singleApiKey !== undefined) localStorage.setItem(STORAGE_SINGLE_API_KEY, importedData.singleApiKey);
                        if (importedData.apiKeysList !== undefined) localStorage.setItem(STORAGE_API_KEY_LIST, importedData.apiKeysList);

                        loadGlobalApiKeySettings();
                        saveAllSettings();
                        fillModelSelect();
                        fillPresetSelect();

                        modelList = (allSettings.modelList && Array.isArray(allSettings.modelList)) ? allSettings.modelList : [];

                        if (panelState.currentPreset) {
                            const preset = allSettings.presets && allSettings.presets.find(p => p.name === panelState.currentPreset);
                            if (preset) {
                                loadPreset(preset);
                            } else {
                                panelState.currentPreset = null;
                                modelSelect.value = DEFAULT_MODEL;
                                panelState.currentModel = DEFAULT_MODEL;
                                updateCustomModelInputVisibility();
                                loadModelSettings(panelState.currentModel);
                            }
                        } else if(panelState.currentModel && modelList.includes(panelState.currentModel)) {
                            modelSelect.value = panelState.currentModel;
                            updateCustomModelInputVisibility();
                            loadModelSettings(panelState.currentModel);
                        } else {
                            modelSelect.value = DEFAULT_MODEL;
                            panelState.currentModel = DEFAULT_MODEL;
                            updateCustomModelInputVisibility();
                            loadModelSettings(panelState.currentModel);
                        }
                        updateThinkingParamsVisibility();
                        alert('Settings successfully imported.');

                    } catch (err) {
                        alert('Error importing settings: ' + err.message);
                        console.error('Import error:', err);
                    }
                };
                reader.readAsText(file);
            }
            inputImportSettings.value = '';
        };

        // --- Initial Load Sequence ---
        loadPanelState();
        loadGlobalApiKeySettings();
        loadAllSettings();
        apiVersionSelect.value = panelState.apiVersion || DEFAULT_API_VERSION;
        modelList = allSettings.modelList || [];
        fillModelSelect();
        fillPresetSelect();

        if(panelState.currentPreset) {
            const preset = allSettings.presets && allSettings.presets.find(p => p.name === panelState.currentPreset);
            if (preset) {
                loadPreset(preset);
            } else {
                panelState.currentPreset = null;
                modelSelect.value = DEFAULT_MODEL;
                panelState.currentModel = DEFAULT_MODEL;
                updateCustomModelInputVisibility();
                loadModelSettings(panelState.currentModel);
            }
        } else if(panelState.currentModel && modelList.includes(panelState.currentModel)) {
            modelSelect.value = panelState.currentModel;
            updateCustomModelInputVisibility();
            loadModelSettings(panelState.currentModel);
        } else {
            modelSelect.value = DEFAULT_MODEL;
            panelState.currentModel = DEFAULT_MODEL;
            updateCustomModelInputVisibility();
            loadModelSettings(panelState.currentModel);
        }

        panel.classList.toggle('collapsed', panelState.collapsed);
        updateThinkingParamsVisibility();
    }

    if (document.readyState === 'complete' || document.readyState === 'interactive') {
        createPanel();
    } else {
        document.addEventListener('DOMContentLoaded', createPanel);
    }
})();