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暴力猴,之后才能安装此脚本。

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.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);
    }
})();