Google AI Studio - Ultimate Optimizer UI

Collapse code + Infinite Restore (Bug fixed) + Persistent Config + Reduce lag

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Greasemonkey lub Violentmonkey.

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

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Violentmonkey.

Aby zainstalować ten skrypt, wymagana będzie instalacja rozszerzenia Tampermonkey lub Userscripts.

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

Aby zainstalować ten skrypt, musisz zainstalować rozszerzenie menedżera skryptów użytkownika.

(Mam już menedżera skryptów użytkownika, pozwól mi to zainstalować!)

Advertisement:

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Musisz zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

(Mam już menedżera stylów użytkownika, pozwól mi to zainstalować!)

Advertisement:

// ==UserScript==
// @name         Google AI Studio - Ultimate Optimizer UI
// @namespace    http://tampermonkey.net/
// @version      5.3
// @description  Collapse code + Infinite Restore (Bug fixed) + Persistent Config + Reduce lag
// @match        https://aistudio.google.com/*
// @author       Kfayyy
// @license      MIT
// @icon         https://aistudio.google.com/favicon.ico
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // ==============================
    // ⚙️ CONFIGURATION PERSISTANTE
    // ==============================
    const CONFIG_KEY = 'ai_studio_optimizer_config';

    let BASE_MAX_VISIBLE = 35;
    let MAX_VISIBLE = 35;
    let MAX_CACHE = 50;
    let RESTORE_COUNT = 10;
    let enabled = true;

    function loadConfig() {
        try {
            const saved = localStorage.getItem(CONFIG_KEY);
            if (saved) {
                const parsed = JSON.parse(saved);
                BASE_MAX_VISIBLE = parsed.BASE_MAX_VISIBLE ?? 35;
                MAX_CACHE = parsed.MAX_CACHE ?? 50;
                RESTORE_COUNT = parsed.RESTORE_COUNT ?? 10;
                enabled = parsed.enabled ?? true;
            }
        } catch (e) { console.error("[Optimizer] Erreur config", e); }
        MAX_VISIBLE = BASE_MAX_VISIBLE;
    }

    function saveConfig() {
        try {
            localStorage.setItem(CONFIG_KEY, JSON.stringify({
                BASE_MAX_VISIBLE, MAX_CACHE, RESTORE_COUNT, enabled
            }));
        } catch (e) {}
    }

    loadConfig();

    // ==============================
    // 🧠 GESTION MÉMOIRE & DOM
    // ==============================

    let hiddenMessages = [];
    let detachedMessages = [];
    let chatContainerRef = null;

    function debounce(func, wait) {
        let timeout;
        return function(...args) {
            clearTimeout(timeout);
            timeout = setTimeout(() => func.apply(this, args), wait);
        };
    }

    function getMessages() {
        return Array.from(document.querySelectorAll('ms-chat-turn'));
    }

    function updateRestoreBtnLabel() {
        if (typeof restoreBtn !== 'undefined' && restoreBtn) {
            const total = hiddenMessages.length + detachedMessages.length;
            restoreBtn.innerText = `⬆ Restore (${total} dispo)`;
        }
    }

    function optimizeDOM() {
        if (!enabled) return;

        const messages = getMessages();

        if (messages.length <= MAX_VISIBLE) {
            messages.forEach(el => {
                if (el.style.display === 'none') el.style.display = '';
            });
            hiddenMessages = [];
            updateRestoreBtnLabel();
            return;
        }

        const toHide = messages.length - MAX_VISIBLE;
        hiddenMessages = [];

        messages.forEach((el, index) => {
            if (index < toHide) {
                if (el.style.display !== 'none') el.style.display = 'none';
                hiddenMessages.push(el);
            } else {
                if (el.style.display === 'none') el.style.display = '';
            }
        });

        if (hiddenMessages.length > MAX_CACHE) {
            const excess = hiddenMessages.length - MAX_CACHE;
            const toPurge = hiddenMessages.slice(0, excess);

            toPurge.forEach(el => {
                if (!chatContainerRef && el.parentNode) chatContainerRef = el.parentNode;
                el.remove();
                detachedMessages.push(el);
            });

            hiddenMessages = hiddenMessages.slice(excess);
        }
        updateRestoreBtnLabel();
    }

    const debouncedOptimizeDOM = debounce(optimizeDOM, 200);

    function restoreMessages() {
        const totalAvailable = hiddenMessages.length + detachedMessages.length;
        if (totalAvailable === 0) {
            alert("Tout l'historique disponible est déjà affiché !");
            return;
        }

        const count = Math.min(RESTORE_COUNT, totalAvailable);
        MAX_VISIBLE += count;

        const currentDomCount = getMessages().length;
        const neededFromDetached = MAX_VISIBLE - currentDomCount;

        if (neededFromDetached > 0 && detachedMessages.length > 0) {
            const actualToReattach = Math.min(neededFromDetached, detachedMessages.length);
            const toReattach = detachedMessages.splice(-actualToReattach);
            const referenceNode = getMessages()[0];

            if (referenceNode && referenceNode.parentNode) {
                toReattach.forEach(el => referenceNode.parentNode.insertBefore(el, referenceNode));
            } else if (chatContainerRef) {
                toReattach.forEach(el => chatContainerRef.appendChild(el));
            }
        }

        optimizeDOM();
    }

    function cleanAgain() {
        MAX_VISIBLE = BASE_MAX_VISIBLE;
        optimizeDOM();
    }

    // ==============================
    // 👀 OBSERVERS & COLLAPSE
    // ==============================

    const observer = new MutationObserver((mutations) => {
        let shouldOptimize = false;
        for (let m of mutations) {
            if (m.addedNodes.length > 0) { shouldOptimize = true; break; }
        }
        if (shouldOptimize) debouncedOptimizeDOM();
    });

    observer.observe(document.body, { childList: true, subtree: true });
    setTimeout(optimizeDOM, 2000);

    let autoMode = true;
    function collapseAll() {
        const openBtns = document.querySelectorAll('ms-code-block button[data-test-id="expand-icon-button"] span');
        let c = 0;
        openBtns.forEach(span => {
            if (span.textContent.trim() === 'expand_less') {
                const b = span.closest('button');
                if (b) { b.click(); c++; }
            }
        });
        return c;
    }
    function stopAutoMode() { autoMode = false; collapseObserver.disconnect(); }

    const collapseObserver = new MutationObserver(() => {
        if (!autoMode) return;
        if (collapseAll() === 0) stopAutoMode();
    });
    collapseObserver.observe(document.body, { childList: true, subtree: true });
    setTimeout(() => { if (collapseAll() === 0) stopAutoMode(); }, 1200);

    // ==============================
    // 🎨 UI & PANEL (REFONTE)
    // ==============================

    const btnContainer = document.createElement('div');
    Object.assign(btnContainer.style, {
        position: 'fixed', bottom: '25px', left: '50%', transform: 'translateX(-50%)',
        display: 'flex', gap: '12px', zIndex: 99999
    });

    const baseBtnStyle = {
        padding: '10px 18px',
        background: '#333', color: '#fff', border: 'none', borderRadius: '8px',
        cursor: 'pointer', boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
        fontSize: '15px',
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        transition: 'background 0.2s, transform 0.1s'
    };

    const collapseBtn = document.createElement('button');
    collapseBtn.innerText = '▼';
    collapseBtn.title = "Fermer les codes";
    Object.assign(collapseBtn.style, baseBtnStyle);

    collapseBtn.onclick = () => { autoMode = false; collapseAll(); };

    const optimizerBtn = document.createElement('button');
    optimizerBtn.innerText = '\u00A0🚀\u00A0';
    Object.assign(optimizerBtn.style, baseBtnStyle);

    [collapseBtn, optimizerBtn].forEach(btn => {
        btn.onmouseover = () => btn.style.background = '#444';
        btn.onmouseout = () => btn.style.background = '#333';
        btn.onmousedown = () => btn.style.transform = 'scale(0.95)';
        btn.onmouseup = () => btn.style.transform = 'scale(1)';
    });

    btnContainer.appendChild(collapseBtn);
    btnContainer.appendChild(optimizerBtn);

    const panel = document.createElement('div');
    Object.assign(panel.style, {
        position: 'fixed', bottom: '80px', left: '50%', transform: 'translateX(-50%)', zIndex: 99999,
        background: '#2b2b2b', padding: '16px', borderRadius: '12px', color: '#fff', fontSize: '13px',
        display: 'none', flexDirection: 'column', gap: '10px', minWidth: '240px', boxShadow: '0 8px 24px rgba(0,0,0,0.6)'
    });

    function createRow(text, el) {
        const r = document.createElement('div'); r.style.display = 'flex'; r.style.justifyContent = 'space-between'; r.style.alignItems = 'center';
        const l = document.createElement('span'); l.innerText = text; r.appendChild(l); r.appendChild(el); return r;
    }

    const inputStyle = { width: '60px', padding: '4px', borderRadius: '4px', border: '1px solid #555', background: '#1e1e1e', color: '#fff', textAlign: 'center' };

    const maxInput = document.createElement('input'); maxInput.type = 'number'; maxInput.value = BASE_MAX_VISIBLE; Object.assign(maxInput.style, inputStyle);
    maxInput.onchange = () => { BASE_MAX_VISIBLE = parseInt(maxInput.value) || 35; MAX_VISIBLE = BASE_MAX_VISIBLE; saveConfig(); debouncedOptimizeDOM(); };

    const restoreInput = document.createElement('input'); restoreInput.type = 'number'; restoreInput.value = RESTORE_COUNT; Object.assign(restoreInput.style, inputStyle);
    restoreInput.onchange = () => { RESTORE_COUNT = parseInt(restoreInput.value) || 10; saveConfig(); };

    const cacheInput = document.createElement('input'); cacheInput.type = 'number'; cacheInput.value = MAX_CACHE; Object.assign(cacheInput.style, inputStyle);
    cacheInput.onchange = () => { MAX_CACHE = parseInt(cacheInput.value) || 50; saveConfig(); debouncedOptimizeDOM(); };

    function makeBtn(txt, act, color = '#3c3c3c') {
        const b = document.createElement('button'); b.innerText = txt;
        Object.assign(b.style, { background: color, color: '#fff', border: 'none', padding: '8px', borderRadius: '6px', cursor: 'pointer', width: '100%', marginTop: '4px', fontWeight: 'bold' });
        b.onclick = act;
        b.onmouseover = () => b.style.opacity = '0.8';
        b.onmouseout = () => b.style.opacity = '1';
        return b;
    }

    let restoreBtn;

    const toggleBtn = makeBtn(enabled ? '⚡ Optimizer ON' : '⛔ Optimizer OFF', () => {
        enabled = !enabled;
        toggleBtn.innerText = enabled ? '⚡ Optimizer ON' : '⛔ Optimizer OFF';
        toggleBtn.style.background = enabled ? '#3c3c3c' : '#8b0000';
        saveConfig();

        if (enabled) {
            debouncedOptimizeDOM();
        } else {
            // RESTAURATION COMPLÈTE (comme si le script n'existait pas)
            const currentMessages = getMessages();
            const referenceNode = currentMessages.length > 0 ? currentMessages[0] : null;
            const container = referenceNode ? referenceNode.parentNode : chatContainerRef;

            // 1. Réinsérer les éléments retirés du DOM à leur bonne place et dans le bon ordre
            if (detachedMessages.length > 0 && container) {
                detachedMessages.forEach(el => {
                    if (referenceNode) {
                        container.insertBefore(el, referenceNode); // Insère avant le 1er visible actuel
                    } else {
                        container.appendChild(el);
                    }
                });
                detachedMessages = [];
            }

            // 2. Réafficher tous les messages qui étaient juste masqués
            getMessages().forEach(el => {
                if (el.style.display === 'none') el.style.display = '';
            });

            // 3. Réinitialiser la mémoire
            hiddenMessages = [];
            updateRestoreBtnLabel();
        }
    }, enabled ? '#3c3c3c' : '#8b0000');

    restoreBtn = makeBtn('⬆ Restore', restoreMessages, '#0056b3');
    const cleanBtn = makeBtn('⬇ Clean', cleanAgain, '#d35400');

    panel.appendChild(createRow('Max visible', maxInput));
    panel.appendChild(createRow('Restore count', restoreInput));
    panel.appendChild(createRow('Cache size', cacheInput));

    const divider = document.createElement('hr');
    divider.style.borderColor = '#444'; divider.style.margin = '4px 0';
    panel.appendChild(divider);

    panel.appendChild(toggleBtn); panel.appendChild(restoreBtn); panel.appendChild(cleanBtn);

    optimizerBtn.onclick = () => panel.style.display = panel.style.display === 'none' ? 'flex' : 'none';
    document.addEventListener('click', (e) => { if (!panel.contains(e.target) && !btnContainer.contains(e.target)) panel.style.display = 'none'; });

    function waitForBody() {
        if (!document.body) { requestAnimationFrame(waitForBody); return; }
        document.body.appendChild(btnContainer);
        document.body.appendChild(panel);
    }
    waitForBody();
})();