您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
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或关注我们的公众号极客氢云获取最新地址