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-27 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Chub AI Gemini/PaLM2 Model List Enhancer
// @license      MIT
// @namespace    http://tampermonkey.net/
// @version      6.3
// @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>
        <div class="panel-content">
            <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>
        </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: 0; /* MODIFIED: Padding moved to inner container */
            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;
            max-height: 90vh; /* MODIFIED: Height constraint for the whole panel */
            display: flex; /* MODIFIED: To help size the inner container */
        }

        #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;
        }

        /* MODIFIED: Styles moved from panel to inner content div */
        .panel-content {
            flex: 1;
            min-height: 0;
            padding: calc(min(1.2vw, 6px) * var(--scale-factor)) calc(min(2vw, 10px) * var(--scale-factor));
            box-sizing: border-box;
            overflow-y: auto; /* Enables vertical scrolling */
            scrollbar-width: thin; /* For Firefox */
            scrollbar-color: #888 #333; /* For Firefox */
        }

        .panel-content::-webkit-scrollbar {
            width: 8px; /* Width of the scrollbar */
        }

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

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

        .panel-content::-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; // Store the original fetch function
        window.fetch = async function(input, init) {
            let requestUrl;
            let requestInit = init || {};

            // Determine the request URL and consolidate init properties if 'input' is a Request object
            if (input instanceof Request) {
                requestUrl = input.url;
                const { url, ...inputProps } = input; // Destructure to get other Request properties
                requestInit = { ...inputProps, ...requestInit }; // Merge Request properties with provided init
                delete requestInit.url; // URL is handled by requestUrl variable
            } else if (typeof input === 'string') {
                requestUrl = input;
            } else {
                // If the input is neither a string nor a Request object, it's an unsupported format for our
                // modification. Pass it directly to the original fetch without interception.
                return originalFetch(input, init);
            }

            // --- Determine if this is a Google Generative Language API call ---
            // This is the CRITICAL change: only proceed with modifications if it's the target API.
            const isGoogleGenLangApi = requestUrl.includes('generativelanguage.googleapis.com');

            // If it's NOT a Google API request, immediately pass it to the original fetch.
            // This prevents interfering with other website resources (like the tokenizer.json).
            if (!isGoogleGenLangApi) {
                return originalFetch(input, init);
            }

            // --- From this point onwards, all logic applies ONLY to Google Generative Language API requests ---

            let currentUrlObj;
            try {
                currentUrlObj = new URL(requestUrl);
            } catch (e) {
                // If the URL is invalid even for a Google API request, log a warning and use the original fetch.
                console.warn('Invalid URL encountered for Google API, skipping modification:', requestUrl, e);
                return originalFetch(input, init);
            }

            // --- API Key Management Logic ---
            const isGenerateContentRequest = requestUrl.includes('generateContent?');

            if (panelState.useCyclicApi) {
                // If cyclic API use is enabled, apply the next key from the list
                if (isGenerateContentRequest && apiKeysList.length > 0) {
                    const nextApiKey = apiKeysList[panelState.currentApiKeyIndex % apiKeysList.length];
                    panelState.currentApiKeyIndex = (panelState.currentApiKeyIndex + 1) % apiKeysList.length;
                    saveAllSettings(); // Save state after incrementing index
                    updateRealApiKey(); // Update the displayed key in the panel
                    currentUrlObj.searchParams.set('key', nextApiKey);
                } else if (realApiKey) {
                    // For non-generateContent API calls (e.g., get models list) in cyclic mode, use the current active key
                    currentUrlObj.searchParams.set('key', realApiKey);
                }
            } else {
                // If not using cyclic API, ensure the single stored API key is used if not already present
                if (!currentUrlObj.searchParams.has('key')) {
                    const singleApiKey = localStorage.getItem(STORAGE_SINGLE_API_KEY) || '';
                    if (singleApiKey) {
                        currentUrlObj.searchParams.set('key', singleApiKey);
                    }
                }
            }

            // Update the request URL with potentially new API key and model version
            requestUrl = currentUrlObj.toString();
            requestUrl = replaceUrlParts(requestUrl, panelState.currentModel);

            // --- Request Body Modification Logic (for generationConfig, safetySettings, thinkingConfig) ---
            if (requestInit.body && typeof requestInit.body === 'string' &&
                (requestInit.body.includes('"generationConfig"') || requestInit.body.includes('"safetySettings"'))) {
                try {
                    const requestBody = JSON.parse(requestInit.body);

                    // THIS IS THE BLOCK YOU ASKED ABOUT! It fetches the current settings or uses defaults.
                    // It is crucial for applying your configured parameters.
                    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
                    };

                    // Apply generation configuration parameters
                    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;

                    // Apply safety settings based on the selected threshold
                    const selectedThreshold = s.safetySettingsThreshold;
                    if (selectedThreshold) {
                        requestBody.safetySettings = HARM_CATEGORIES.map(category => ({
                            category: category,
                            threshold: selectedThreshold
                        }));
                    } else {
                        // If no threshold selected, remove safetySettings from the request
                        delete requestBody.safetySettings;
                    }

                    // Apply thinkingConfig logic based on the 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 {
                            // If override is on but budget/thoughts are default/off, remove thinkingConfig
                            delete requestBody.generationConfig.thinkingConfig;
                        }
                    } else {
                        // If override is off, ensure thinkingConfig is not in the request
                        delete requestBody.generationConfig.thinkingConfig;
                    }

                    // Update the request body string and Content-Length header
                    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);
                    // If an error occurs during body modification, proceed with the original body
                }
            }

            // Await the original fetch call with potentially modified URL and requestInit
            const response = await originalFetch(requestUrl, requestInit);

            // --- Response Interception and Processing (for combining thoughts) ---
            // Fetch current settings again to check `includeThoughts` and `overrideThinkingBudget`
            const s = allSettings[panelState.currentModel] || {}; // This is the second instance of the settings fetch

            // Only process response if it's a 'generateContent' call, successful, and thoughts are enabled via override
            if (response.ok && requestUrl.includes('generateContent?') && s.overrideThinkingBudget && s.includeThoughts) {
                try {
                    const data = await response.json(); // Parse the response JSON
                    // Check if the response contains multiple parts (thought and text)
                    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); // Find the thought part
                        const textPart = parts.find(p => !p.thought); // Find the main text part

                        if (thoughtPart && textPart) {
                            // Combine thought and text into a single part
                            const combinedText = `${thoughtPart.text}\n\n***\n\n${textPart.text}`;
                            data.candidates[0].content.parts = [{ text: combinedText }];

                            // Create a new response with the modified body and updated Content-Length header
                            const newBody = JSON.stringify(data);
                            const newHeaders = new Headers(response.headers);
                            newHeaders.set('Content-Length', newBody.length.toString());

                            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 any error occurs during response processing, return the original response
                    // (Note: response.json() consumes the body, so cloning 'response' before consumption is ideal
                    // if you need to return it as-is upon error, but for this userscript context,
                    // returning the original fetch's result (which might already be consumed) is acceptable
                    // as a fallback if processing fails.)
                }
            }

            // Return the original (or minimally modified) response if no specific processing was needed or applicable
            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);
    }
})();