Chub AI Gemini/PaLM2 Model List Enhancer

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

目前為 2025-07-25 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Chub AI Gemini/PaLM2 Model List Enhancer
// @license      MIT
// @namespace    http://tampermonkey.net/
// @version      5.5
// @description  Gemini Settings Panel: API version/model selection, parameters, presets, reset, tooltips, export/import, enhanced safety settings.
// @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'; // Renamed from STORAGE_API_KEY
    const STORAGE_API_KEY_LIST = 'chubGeminiApiKeysList'; // New key for the list of API keys

    // --- Defaults ---
    const DEFAULT_MODEL = 'custom';
    const DEFAULT_API_VERSION = 'v1beta';
    const DEFAULT_USE_CYCLIC_API = false; // New default for cyclic API usage
    const DEFAULT_CURRENT_API_KEY_INDEX = 0; // New default for cyclic API key rotation

    // --- 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 = { // Initialize with new defaults
        collapsed: true,
        currentModel: DEFAULT_MODEL,
        currentPreset: null,
        apiVersion: DEFAULT_API_VERSION,
        useCyclicApi: DEFAULT_USE_CYCLIC_API, // New: state of the cyclic API usage toggle
        currentApiKeyIndex: DEFAULT_CURRENT_API_KEY_INDEX // New: current index for cyclic API key rotation
    };
    let modelList = [];
    let apiKeysList = []; // Array of API keys loaded from STORAGE_API_KEY_LIST
    let realApiKey = ''; // The *actual* API key that will be used for network requests (either single or the current cyclic one)

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

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

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

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

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

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

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

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

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

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

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

        document.body.appendChild(panel);

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


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

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

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

        #gemini-settings-panel h4 {
            text-align: center;
            margin: 0 0 calc(min(1.2vw, 5px) * var(--scale-factor)); /* Reduced margin */
            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)); /* Reduced margin */
            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)); /* Reduced padding */
            font-size: calc(min(2.3vw, 13px) * var(--scale-factor));
            width: 100%;
            box-sizing: border-box;
            margin-top: calc(1px * var(--scale-factor)); /* Reduced margin */
        }

        /* Styling for API Key input and button side-by-side */
        #gemini-settings-panel label:has(#api-key-input) {
            display: flex;
            flex-wrap: wrap; /* Allows wrapping on smaller screens */
            align-items: center;
            gap: calc(min(0.8vw, 4px) * var(--scale-factor)); /* Gap between input and button */
        }
        #gemini-settings-panel label:has(#api-key-input) #api-key-input {
            flex-grow: 1; /* Input takes available space */
            min-width: calc(100px * var(--scale-factor)); /* Ensure input doesn't shrink too much */
            margin-top: 0; /* Remove top margin for inline display */
        }
        #gemini-settings-panel label:has(#api-key-input) #btn-manage-api-keys {
             width: auto; /* Button size based on content */
             padding: calc(min(0.6vw, 3px) * var(--scale-factor)) calc(min(1vw, 6px) * var(--scale-factor));
             margin-top: 0; /* Remove top margin for inline display */
        }


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

        .param-group label {
            display: block;
            margin-bottom: calc(min(0.5vw, 1px) * var(--scale-factor)); /* Reduced margin */
            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)); /* Reduced gap */
            margin-top: calc(0.2vw * var(--scale-factor)); /* Reduced margin */
        }

        .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)); /* Reduced margin */
            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 {
            width: 100%;
            padding: calc(min(0.8vw, 4px) * var(--scale-factor)); /* Reduced padding */
            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)); /* Reduced margin */
            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)); /* Reduced margin */
        }

        #custom-model-input {
            margin-top: calc(2px * var(--scale-factor));
        }


        #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-save-api-keys:hover, #btn-cancel-api-keys:hover {
            background: #388e3c;
        }

        #save-toast {
            margin-top: calc(min(1.5vw, 4px) * var(--scale-factor)); /* Reduced margin */
            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" */
        .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));
        }

        .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); /* Затемненный фон */
            display: flex; /* Используем flexbox для центрирования */
            justify-content: center;
            align-items: center;
            z-index: 10001; /* Должен быть выше, чем панель настроек */
            /* display: none;  <-- Это начальное состояние, которое JS будет менять */
        }

        #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; /* Позволяет textarea занимать доступное пространство */
            min-height: calc(150px * var(--scale-factor)); /* Минимальная высота */
            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; /* Разрешить изменение размера только по вертикали */
            box-sizing: border-box;
        }

        #api-key-list-modal .modal-buttons {
            display: flex;
            justify-content: flex-end; /* Кнопки справа */
            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; /* Снимаем 100% ширину */
        }

        /* Adjust button colors for modal for clarity */
        #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; /* Красный для отмены */
        }
        #api-key-list-modal #btn-cancel-api-keys:hover {
            background: #d32f2f;
        }

        `;
        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'); // New button
        const toggleCyclicApi = panel.querySelector('#toggle-cyclic-api'); // New toggle checkbox
        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 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') }
        };

        // 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) => {
            // Check if click target is outside the panel, the toggle button, AND the modal
            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}`;
        }

        // Masks the key for display (shows first char + asterisks + last char)
        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];
        }

        // Loads all API key related settings from localStorage
        function loadGlobalApiKeySettings() {
            const storedSingleKey = localStorage.getItem(STORAGE_SINGLE_API_KEY) || '';
            const storedKeysList = localStorage.getItem(STORAGE_API_KEY_LIST) || '';

            // Load panel state first to get `useCyclicApi` and `currentApiKeyIndex`
            // This function is called by the main initial load sequence, so panelState should already be loaded.
            // No need to call loadPanelState() again here.

            // Parse the API keys list
            apiKeysList = storedKeysList.split('\n').map(k => k.trim()).filter(k => k.length > 0);

            // Ensure currentApiKeyIndex is valid after loading keys, reset if needed
            if (panelState.currentApiKeyIndex === undefined || panelState.currentApiKeyIndex < 0 || panelState.currentApiKeyIndex >= apiKeysList.length) {
                panelState.currentApiKeyIndex = 0;
            }

            updateRealApiKey(); // Determine what `realApiKey` should be and update UI
        }

        // Updates the `realApiKey` based on `useCyclicApi` state and `apiKeysList`, then updates the `apiKeyInput` UI
        function updateRealApiKey() {
            if (panelState.useCyclicApi && apiKeysList.length > 0) {
                realApiKey = apiKeysList[panelState.currentApiKeyIndex % apiKeysList.length];
                apiKeyInput.disabled = true; // Disable input when cyclic mode is active
                apiKeyInput.type = 'text'; // Show the actual key for debugging/reference
                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; // Enable input when not in cyclic mode
                apiKeyInput.type = 'password'; // Mask input for security
                apiKeyInput.value = maskKeyDisplay(realApiKey);
                apiKeyInput.removeAttribute('title');
            }
            toggleCyclicApi.checked = panelState.useCyclicApi; // Update the toggle switch UI to reflect panelState
        }

        // Function to save the list of keys from the textarea (used by modal)
        function saveApiKeysListFromModal(keysString) {
            localStorage.setItem(STORAGE_API_KEY_LIST, keysString);
            apiKeysList = keysString.split('\n').map(k => k.trim()).filter(k => k.length > 0);

            // If the current index is out of bounds after updating the list, reset it
            if (panelState.currentApiKeyIndex >= apiKeysList.length) {
                panelState.currentApiKeyIndex = 0;
            }
            saveAllSettings(); // Save updated panelState (which includes currentApiKeyIndex)
            updateRealApiKey(); // Update displayed key and input state
            apiKeyListModal.style.display = 'none'; // Close modal
        }

        // Function to save the single API key (used by apiKeyInput blur event)
        function saveSingleApiKey(newKey) {
            localStorage.setItem(STORAGE_SINGLE_API_KEY, newKey.trim());
            // If cyclic mode is off, and a new single key is entered, this becomes the new `realApiKey`
            if (!panelState.useCyclicApi) {
                realApiKey = newKey.trim();
            }
            updateRealApiKey(); // Update displayed key and input state
        }

        // Event listeners for API key input and management
        apiKeyInput.addEventListener('focus', () => {
            // Only show full key if not in cyclic mode (input is enabled)
            if (!panelState.useCyclicApi) {
                apiKeyInput.type = 'text';
                apiKeyInput.value = realApiKey; // Show actual stored key
            }
        });
        apiKeyInput.addEventListener('blur', () => {
            // Only save if not in cyclic mode (input is enabled)
            if (!panelState.useCyclicApi) {
                saveSingleApiKey(apiKeyInput.value);
                // realApiKey is already updated by saveSingleApiKey
                apiKeyInput.type = 'password';
                apiKeyInput.value = maskKeyDisplay(realApiKey);
            }
        });

        btnManageApiKeys.addEventListener('click', () => {
            apiKeyKeysTextarea.value = apiKeysList.join('\n'); // Populate textarea with current keys
            apiKeyListModal.style.display = 'flex'; // Show modal
        });

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

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

        // Event listener for cyclic API toggle
        toggleCyclicApi.addEventListener('change', () => {
            panelState.useCyclicApi = toggleCyclicApi.checked;
            // If cyclic mode is enabled but no keys are in the list, warn user and revert toggle
            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; // Revert state
                 toggleCyclicApi.checked = false; // Update UI
            }
            saveAllSettings(); // Save panelState (includes useCyclicApi)
            updateRealApiKey(); // Update API key input display/state based on new mode
        });


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

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

        function getCurrentSettings() {
            return {
                temperature: clamp(parseFloat(elems.temperature.num.value), 0, 2),
                maxOutputTokens: clamp(parseInt(elems.maxTokens.num.value), 1, 65536), // maxTokens min should be 1
                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()
            };
        }

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

        // Saves all script settings (model settings, panel state) to localStorage
        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);
            }
        }

        // Loads all script settings (model settings) from localStorage
        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 = [];
            }
        }

        // Loads panel-specific state (collapsed, current model, preset, cyclic flags) from localStorage
        function loadPanelState() {
            try {
                const s = localStorage.getItem(STORAGE_PANEL_STATE_KEY);
                if(s) {
                    const loadedState = JSON.parse(s);
                    // Merge loaded state with defaults, ensuring new properties (like cyclic flags) are present
                    panelState = {
                        collapsed: DEFAULT_USE_CYCLIC_API, // Default value in case it's missing from old saved state
                        currentModel: DEFAULT_MODEL,
                        currentPreset: null,
                        apiVersion: DEFAULT_API_VERSION,
                        useCyclicApi: DEFAULT_USE_CYCLIC_API, // Default value in case it's missing from old saved state
                        currentApiKeyIndex: DEFAULT_CURRENT_API_KEY_INDEX, // Default value in case it's missing
                        ...loadedState // Overlay with loaded values
                    };
                }
            } 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);
        }

        // Fetches available models from the Gemini API using the currently active API key (`realApiKey`)
        async function fetchModelsFromApi() {
            const keyToUse = realApiKey; // Use the currently active API key
            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)
                        // Remove the "models/" prefix from model names
                        .map(name => {
                            const prefix = 'models/';
                            return name.startsWith(prefix) ? name.substring(prefix.length) : name;
                        });
                } else {
                    modelList = [];
                }
                fillModelSelect();
                allSettings.modelList = modelList; // Save fetched models to allSettings
                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';
            }
        }

        // Replaces API version and model name in the request URL
        function replaceUrlParts(url, modelName) {
            if(typeof url !== 'string') return url;

            // Replace API version (e.g., v1beta/ to v1/)
            url = url.replace(/(v1beta|v1)\//, `${panelState.apiVersion}/`);

            // Handle "custom" model selection
            if(modelName === 'custom') {
                modelName = allSettings.customModelString || '';
                if(!modelName) return url; // If custom model name is empty, don't modify
            }
            // Replace the model path (e.g., models/gemini-pro to models/new-model-name)
            return url.replace(/(models\/[^:]+)(:)?/, (m, p1, p2) => {
                // p1 is 'models/old-model-name', p2 is ':' or undefined
                const newModelPath = 'models/' + modelName;
                return newModelPath + (p2 || '');
            });
        }


        // Override `window.fetch` to intercept and modify API requests
        const originalFetch = window.fetch;
        window.fetch = async function(input, init) {
            let requestUrl;
            // Create a mutable object for request options, starting with provided `init`
            let requestInit = init || {};

            // Determine if the original input is a Request object or a URL string
            if (input instanceof Request) {
                // If it's a Request object, extract its URL and copy its properties (method, headers, body, etc.)
                // into our mutable `requestInit`. Then merge any explicitly provided `init` argument.
                requestUrl = input.url;

                // Copy Request properties like method, headers, etc.
                // Note: body can be a ReadableStream, which is tricky to copy/modify.
                // Assuming `init.body` is the primary source of the body content for modifications.
                const { url, ...inputProps } = input; // Extract url, copy other properties
                requestInit = { ...inputProps, ...requestInit };

                // Delete `url` from `requestInit` because we will pass `requestUrl` explicitly
                // to the new `Request` constructor or `originalFetch`.
                delete requestInit.url;

            } else if (typeof input === 'string') {
                requestUrl = input;
                // requestInit is already initialized with `init || {}`
            } else {
                // If input is neither a string nor a Request object, pass it through without modification
                return originalFetch(input, init);
            }

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

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

            if (isGoogleGenLangApi) {
                if (panelState.useCyclicApi) {
                    // *** LOGIC FOR CYCLIC MODE ***
                    if (isGenerateContentRequest && apiKeysList.length > 0) {
                        const nextApiKey = apiKeysList[panelState.currentApiKeyIndex % apiKeysList.length];
                        panelState.currentApiKeyIndex = (panelState.currentApiKeyIndex + 1) % apiKeysList.length;
                        saveAllSettings(); // Save the updated index for the next request
                        updateRealApiKey(); // Update the key displayed in the panel UI

                        currentUrlObj.searchParams.set('key', nextApiKey); // Set the next key from the list
                    } else if (realApiKey) {
                        // For all other Google API requests (e.g., models API call)
                        // If a key is already in the URL, replace it with the current realApiKey (which will always be the first in the list in cyclic mode)
                        // If no key, add realApiKey
                        currentUrlObj.searchParams.set('key', realApiKey);
                    }
                } else {
                    // *** LOGIC FOR NON-CYCLIC MODE ***
                    // If a key is already present in the URL, DO NOTHING, leave the original key.
                    if (currentUrlObj.searchParams.has('key')) {
                        // Key is already present, leave it as is.
                    } else {
                        // If no key is present, and we have a single key, add it.
                        const singleApiKey = localStorage.getItem(STORAGE_SINGLE_API_KEY) || '';
                        if (singleApiKey) {
                            currentUrlObj.searchParams.set('key', singleApiKey);
                        }
                    }
                }
            }
            requestUrl = currentUrlObj.toString(); // Update requestUrl from the modified URL object

            // Apply general model replacement logic (API version and model name)
            requestUrl = replaceUrlParts(requestUrl, panelState.currentModel);

            // --- CRITICAL SECTION FOR BODY MODIFICATION ---
            // This part should ALWAYS apply if the request body matches the expected format.
            if (requestInit.body && typeof requestInit.body === 'string' &&
                (requestInit.body.includes('"generationConfig"') || requestInit.body.includes('"safetySettings"'))) {
                try {
                    const requestBody = JSON.parse(requestInit.body);
                    const s = allSettings[panelState.currentModel] || {
                        temperature: 2,
                        maxOutputTokens: 65536,
                        topP: 0.95,
                        topK: 0,
                        candidateCount: 1,
                        frequencyPenalty: 0.0,
                        presencePenalty: 0.0,
                        safetySettingsThreshold: 'BLOCK_NONE'
                    };

                    // Apply generation config 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
                    const selectedThreshold = s.safetySettingsThreshold;
                    if (selectedThreshold && selectedThreshold !== 'BLOCK_NONE') {
                        requestBody.safetySettings = HARM_CATEGORIES.map(category => ({
                            category: category,
                            threshold: selectedThreshold
                        }));
                    } else {
                        delete requestBody.safetySettings;
                    }

                    // Update the body in our `requestInit` object
                    const newBodyString = JSON.stringify(requestBody);
                    requestInit.body = newBodyString;

                    // Update Content-Length header if it exists and body length changed
                    if (requestInit.headers) {
                        const headers = new Headers(requestInit.headers); // Work with Headers object for easy manipulation
                        if (headers.has('Content-Length')) {
                            headers.set('Content-Length', newBodyString.length.toString());
                            requestInit.headers = headers; // Assign back the modified Headers object
                        }
                    }

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

            // Finally, perform the fetch request using the potentially modified URL and requestInit
            // If the original `input` was a Request object, we need to create a new one,
            // as Request objects are immutable once created.
            let finalInput = input;
            if (input instanceof Request) {
                // Construct a new Request object with the modified URL and all collected options
                finalInput = new Request(requestUrl, requestInit);
            } else {
                // If it was a string URL, pass the modified URL string
                finalInput = requestUrl;
            }

            return originalFetch(finalInput, requestInit);
        };

        // Event listener for panel toggle button
        toggleBtn.onclick = () => {
            panelState.collapsed = !panelState.collapsed;
            if(panelState.collapsed) panel.classList.add('collapsed');
            else panel.classList.remove('collapsed');
            saveAllSettings();
        };

        // Event listener for model selection change
        modelSelect.onchange = () => {
            panelState.currentModel = modelSelect.value;
            updateCustomModelInputVisibility();
            loadModelSettings(panelState.currentModel);
        };

        // Link number inputs and range sliders for parameters
        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);

        // Event listener for "Get Models List" button
        btnGetModels.onclick = fetchModelsFromApi;

        // Event listener for "Save Settings" button
        btnSaveSettings.onclick = () => {
            saveModelSettings(modelSelect.value);
        };

        // Preset management functions
        function fillPresetSelect() {
            presetSelect.innerHTML = '';
            const opt = document.createElement('option');
            opt.value = '';
            opt.textContent = 'Select Preset';
            presetSelect.appendChild(opt);
            if (allSettings.presets) {
                for (const p of allSettings.presets) {
                    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;
            // Apply settings from preset to current model's settings
            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, // Handle undefined from older presets
                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'
            };
            if (model === 'custom') {
                allSettings.customModelString = preset.settings.customModelString || '';
            }
            panelState.currentModel = model;
            panelState.currentPreset = preset.name;
            modelSelect.value = model;
            updateCustomModelInputVisibility();
            loadModelSettings(model);
            saveAllSettings(); // Save after loading preset
        }

        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;
                // When "Select Preset" is chosen, reload settings for the current model
                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 // Store current settings with the preset
                };
                allSettings.presets.push(preset);
                saveAllSettings();
                fillPresetSelect();
                presetSelect.value = name; // Select the newly added preset
                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();
                // After deleting, reload current model settings if no preset is selected
                if (!panelState.currentPreset) {
                    loadModelSettings(panelState.currentModel);
                }
            }
        };

        // Event listener for "Reset to Defaults" button
        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'
            };
            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;

            if (panelState.currentModel === 'custom') {
                customModelInput.value = '';
            }
            // Update the allSettings for the current model with defaults
            allSettings[panelState.currentModel] = { ...defaultSettings };
            if (panelState.currentModel === 'custom') {
                allSettings.customModelString = '';
            }
            panelState.currentPreset = null; // Unselect any active preset
            fillPresetSelect(); // Refresh preset dropdown
            saveAllSettings();
        };

        // Event listener for "Export Settings" button
        btnExportSettings.onclick = () => {
            const exportData = {
                settings: allSettings, // Contains model settings, presets, model list
                panelState: panelState, // Contains collapsed state, current model/preset, cyclic API state
                singleApiKey: localStorage.getItem(STORAGE_SINGLE_API_KEY), // The single API key
                apiKeysList: localStorage.getItem(STORAGE_API_KEY_LIST) // The list of API keys
            };
            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);
        };

        // Event listener for "Import Settings" button
        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) {
                            // Ensure existing defaults are filled if new properties are introduced
                            panelState = {
                                collapsed: DEFAULT_USE_CYCLIC_API,
                                currentModel: DEFAULT_MODEL,
                                currentPreset: null,
                                apiVersion: DEFAULT_API_VERSION,
                                useCyclicApi: DEFAULT_USE_CYCLIC_API,
                                currentApiKeyIndex: DEFAULT_CURRENT_API_KEY_INDEX,
                                ...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);
                        }

                        // Re-initialize all UI and internal states based on newly loaded data
                        loadGlobalApiKeySettings(); // Re-loads apiKeysList, updates realApiKey, sets toggle state, updates apiKeyInput UI
                        saveAllSettings(); // Saves allSettings and panelState to localStorage
                        fillModelSelect();
                        fillPresetSelect();

                        // Update modelList from imported settings, if available
                        if(allSettings.modelList && Array.isArray(allSettings.modelList)) {
                            modelList = allSettings.modelList;
                        } else {
                            modelList = [];
                        }

                        // Re-evaluate current model and state after import
                        if (panelState.currentPreset) { // If a preset was active, try to load it
                            const preset = allSettings.presets && allSettings.presets.find(p => p.name === panelState.currentPreset);
                            if (preset) {
                                loadPreset(preset); // This will set modelSelect.value and load settings
                            } else { // Preset not found, revert to default model
                                panelState.currentPreset = null; // Clear invalid preset
                                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);
                        }
                        alert('Settings successfully imported.');

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

        // --- Initial Load Sequence ---
        // 1. Load panel state first as it contains flags like `useCyclicApi` and `currentApiKeyIndex`
        loadPanelState();
        // 2. Load API key specific settings based on the loaded panel state
        loadGlobalApiKeySettings(); // This function now loads apiKeysList and determines/updates `realApiKey` and apiKeyInput UI
        // 3. Load all other general settings (models, presets, etc.)
        loadAllSettings();

        // Initialize API Version select based on loaded panel state
        apiVersionSelect.value = panelState.apiVersion || DEFAULT_API_VERSION;

        // Ensure modelList is correctly set from loaded settings
        if(allSettings.modelList) {
            modelList = allSettings.modelList;
        }
        fillModelSelect();
        fillPresetSelect();

        // Set initial model and settings based on loaded panel state
        if(panelState.currentPreset) { // If a preset was active, try to load it
            const preset = allSettings.presets && allSettings.presets.find(p => p.name === panelState.currentPreset);
            if (preset) {
                loadPreset(preset); // This will set modelSelect.value and load settings
            } else { // Preset not found, revert to default model
                panelState.currentPreset = null; // Clear invalid preset
                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);
        }

        // Set panel collapsed state
        if(panelState.collapsed) {
            panel.classList.add('collapsed');
        } else {
            panel.classList.remove('collapsed');
        }
    }

    // Ensure the panel is created after the DOM is fully loaded
    if (document.readyState === 'complete' || document.readyState === 'interactive') {
        createPanel();
    } else {
        document.addEventListener('DOMContentLoaded', createPanel);
    }
})();