Greasy Fork is available in English.
Collapse code + Infinite Restore (Bug fixed) + Persistent Config + Reduce lag
// ==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();
})();