Chub AI palm2 model list Enhancer

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

// ==UserScript==
// @name         Chub AI palm2 model list Enhancer
// @license      MIT
// @namespace    http://tampermonkey.net/
// @version      3.5
// @description  Gemini Settings Panel: API model selection, parameters, presets, reset settings, tooltips, export/import 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_API_KEY = 'chubGeminiApiKey';

    // --- Defaults ---
    const DEFAULT_MODEL = 'custom';
    const API_MODELS_URL_BASE = 'https://generativelanguage.googleapis.com/v1beta/models?key=';

    // --- State variables ---
    let allSettings = {};
    let panelState = {collapsed: true, currentModel: DEFAULT_MODEL, currentPreset: null};
    let modelList = [];
    let apiKey = '';

    // --- 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" />
        </label>
        <button id="btn-get-models" style="margin-bottom:12px;">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; margin-top:4px; width:100%;" />
        </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>
                MaxOutputTokens:
                <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>
                topP:
                <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>
                topK:
                <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>

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

        document.body.appendChild(panel);

        const 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(2vw, 12px) * var(--scale-factor)) calc(min(3vw, 16px) * var(--scale-factor));
            box-shadow: 0 calc(4px * var(--scale-factor)) calc(16px * var(--scale-factor)) rgba(0,0,0,0.7);
            font-family: Arial, sans-serif;
            font-size: calc(min(2.5vw, 14px) * var(--scale-factor));
            z-index: 10000;
            transition: transform 0.4s ease;
            user-select: none;
            width: max-content;
            max-width: calc(min(80vw, 350px) * var(--scale-factor));
            box-sizing: border-box;
        }

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

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

        #gemini-settings-panel label {
            display: block;
            margin-bottom: calc(min(1.5vw, 8px) * 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.5vw, 2px) * var(--scale-factor)) calc(min(1vw, 6px) * var(--scale-factor));
            font-size: calc(min(2.3vw, 13px) * var(--scale-factor));
            width: 100%;
            box-sizing: border-box;
            margin-top: calc(4px * var(--scale-factor));
        }

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

        .param-group label {
            display: block;
            margin-bottom: calc(min(0.8vw, 4px) * 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(1vw, 6px) * var(--scale-factor));
            margin-top: calc(0.5vw * 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.5vw, 2px) * var(--scale-factor)) calc(min(1vw, 6px) * 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(1vw, 4px) * 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 {
            width: 100%;
            padding: calc(min(1.5vw, 7px) * 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(1vw, 6px) * var(--scale-factor));
            transition: background-color 0.3s ease;
            font-size: calc(min(2.5vw, 14px) * 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 {
            background: #388e3c;
        }

        #save-toast {
            margin-top: calc(min(2vw, 10px) * var(--scale-factor));
            text-align: center;
            background: #222;
            color: #0f0;
            padding: calc(min(1vw, 6px) * 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);
        }

        @media screen and (max-width: 600px) {
            #gemini-settings-panel {
                max-width: calc(80vw * var(--scale-factor));
                padding: calc(min(3vw, 10px) * var(--scale-factor)) calc(min(4vw, 12px) * var(--scale-factor));
                font-size: calc(min(3vw, 12px) * var(--scale-factor));
            }

            #gemini-settings-panel h4 {
                font-size: calc(min(3.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 {
                font-size: calc(min(2.8vw, 11px) * var(--scale-factor));
                padding: calc(min(0.8vw, 3px) * var(--scale-factor)) calc(min(1.2vw, 5px) * var(--scale-factor));
            }

            #btn-save-settings, #btn-get-models, #btn-add-preset, #btn-delete-preset, #btn-reset-settings, #btn-export-settings, #btn-import-settings {
                padding: calc(min(2vw, 6px) * var(--scale-factor));
                font-size: calc(min(3vw, 12px) * var(--scale-factor));
            }
        }
        `;
        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 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 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') }
        };

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

        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 loadApiKey() {
            const storedKey = localStorage.getItem(STORAGE_API_KEY) || '';
            realApiKey = storedKey;
            apiKey = storedKey;
            apiKeyInput.value = maskKeyDisplay(realApiKey);
        }
        function saveApiKey(newKey) {
            apiKey = newKey.trim();
            localStorage.setItem(STORAGE_API_KEY, apiKey);
        }

        let realApiKey = apiKey;
        apiKeyInput.addEventListener('focus', () => {
            apiKeyInput.type = 'text';
            apiKeyInput.value = realApiKey;
        });
        apiKeyInput.addEventListener('blur', () => {
            saveApiKey(apiKeyInput.value);
            realApiKey = apiKeyInput.value.trim();
            apiKeyInput.type = 'password';
            apiKeyInput.value = maskKeyDisplay(realApiKey);
        });
        loadApiKey();

        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
            };
            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;
            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),
                topP: clamp(parseFloat(elems.topP.num.value), 0, 1),
                topK: clamp(parseInt(elems.topK.num.value), 0, 1000),
                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
            };
            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 state = JSON.parse(s);
                    panelState = {...panelState, ...state};
                }
            } 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() {
            if(!realApiKey) {
                alert('Please enter an API key');
                return;
            }
            btnGetModels.disabled = true;
            btnGetModels.textContent = 'Loading...';
            try {
                const response = await fetch(API_MODELS_URL_BASE + encodeURIComponent(realApiKey));
                if(!response.ok) throw new Error('Network response was not ok');
                const data = await response.json();
                if(data.models && Array.isArray(data.models)) {
                    modelList = data.models
                        .map(m => m.name)
                        .filter(name => name.startsWith('models/gemini-'))
                        .map(name => name.substring('models/'.length));
                } 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 replaceModelInUrl(url, modelName) {
            if(typeof url !== 'string') return url;
            if(modelName === 'custom') {
                modelName = allSettings.customModelString || '';
                if(!modelName) return url;
            }
            return url.replace(/(models\/)([^:]+)(:)/, (m, p1, p2, p3) => p1 + modelName + p3);
        }

        const originalFetch = window.fetch;
        window.fetch = async function(input, init) {
            let url = input;
            if(typeof url === 'string' && url.includes('generativelanguage.googleapis.com')) {
                url = replaceModelInUrl(url, panelState.currentModel);
            }
            if(init && init.body && typeof init.body === 'string' && init.body.includes('"generationConfig"')) {
                try {
                    const requestBody = JSON.parse(init.body);
                    if(requestBody.generationConfig) {
                        const s = allSettings[panelState.currentModel] || {
                            temperature: 2,
                            maxOutputTokens: 65536,
                            topP: 0.95,
                            topK: 0
                        };
                        requestBody.generationConfig.temperature = s.temperature;
                        requestBody.generationConfig.maxOutputTokens = s.maxOutputTokens;
                        requestBody.generationConfig.topP = s.topP;
                        requestBody.generationConfig.topK = s.topK;
                        init.body = JSON.stringify(requestBody);
                    }
                } catch(e) {
                    console.error('Error processing request body:', e);
                }
            }
            return originalFetch(url, init);
        };

        toggleBtn.onclick = () => {
            panelState.collapsed = !panelState.collapsed;
            if(panelState.collapsed) panel.classList.add('collapsed');
            else panel.classList.remove('collapsed');
            saveAllSettings();
        };

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

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

        btnGetModels.onclick = fetchModelsFromApi;

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

        // Preset management
        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);
                }
            }
        }

        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
            };
            if (model === 'custom') {
                allSettings.customModelString = preset.settings.customModelString || '';
            }
            panelState.currentModel = model;
            panelState.currentPreset = preset.name;
            modelSelect.value = model;
            updateCustomModelInputVisibility();
            loadModelSettings(model);
        }

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

        btnAddPreset.onclick = () => {
            const name = prompt('Enter preset name:');
            if (name) {
                const settings = getCurrentSettings();
                const preset = {
                    name,
                    model: panelState.currentModel,
                    settings
                };
                if (!allSettings.presets) allSettings.presets = [];
                allSettings.presets.push(preset);
                saveAllSettings();
                fillPresetSelect();
            }
        };

        btnDeletePreset.onclick = () => {
            const name = presetSelect.value;
            if (name) {
                allSettings.presets = allSettings.presets.filter(p => p.name !== name);
                saveAllSettings();
                fillPresetSelect();
            }
        };

        btnResetSettings.onclick = () => {
            const defaultSettings = {
                temperature: 2.0,
                maxOutputTokens: 65536,
                topP: 0.95,
                topK: 0
            };
            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;
            if (panelState.currentModel === 'custom') {
                customModelInput.value = '';
            }
            saveModelSettings(panelState.currentModel);
        };

        btnExportSettings.onclick = () => {
            const json = JSON.stringify(allSettings, 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 = '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 json = JSON.parse(e.target.result);
                        allSettings = json;
                        saveAllSettings();
                        fillModelSelect();
                        fillPresetSelect();
                        loadModelSettings(panelState.currentModel);
                        alert('Settings successfully imported');
                    } catch (err) {
                        alert('Error importing settings: ' + err.message);
                    }
                };
                reader.readAsText(file);
            }
        };

        loadPanelState();
        loadAllSettings();
        loadApiKey();
        if(allSettings.modelList) {
            modelList = allSettings.modelList;
        }
        fillModelSelect();
        fillPresetSelect();
        if(panelState.currentModel && modelList.includes(panelState.currentModel)) {
            modelSelect.value = panelState.currentModel;
        } else {
            modelSelect.value = DEFAULT_MODEL;
            panelState.currentModel = DEFAULT_MODEL;
        }
        updateCustomModelInputVisibility();
        loadModelSettings(panelState.currentModel);
        if(panelState.collapsed) {
            panel.classList.add('collapsed');
        } else {
            panel.classList.remove('collapsed');
        }

        // Адаптивный размер панели в зависимости от уровня зума
        function updatePanelSize() {
            const scale = window.visualViewport ? window.visualViewport.scale : 1;
            const originalWidth = 300; // исходная ширина панели в пикселях
            const originalToggleWidth = 28; // исходная ширина кнопки в пикселях
            const originalToggleHeight = 48; // исходная высота кнопки в пикселях

            panel.style.width = `${originalWidth / scale}px`;
            toggleBtn.style.width = `${originalToggleWidth / scale}px`;
            toggleBtn.style.height = `${originalToggleHeight / scale}px`;
            toggleBtn.style.left = `-${originalToggleWidth / scale}px`;
            toggleBtn.style.lineHeight = `${originalToggleHeight / scale}px`;
            toggleBtn.style.fontSize = `${20 / scale}px`;
        }

        // Инициализация размера панели
        updatePanelSize();

        // Добавление слушателя событий для изменения зума
        if (window.visualViewport) {
            window.visualViewport.addEventListener('resize', updatePanelSize);
        } else {
            // Резервный вариант для браузеров без visualViewport
            window.addEventListener('resize', updatePanelSize);
        }
    }

    createPanel();
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址