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