Reducing DOM size by archiving old ChatGPT & DeepSeek messages
// ==UserScript==
// @name G-Obstructore
// @namespace https://chatgpt.com/
// @version 2.2
// @description Reducing DOM size by archiving old ChatGPT & DeepSeek messages
// @author lucz
// @match https://chatgpt.com/*
// @match https://chat.deepseek.com/*
// @grant GM_addStyle
// @icon https://lois.media/images/nosyropgang.svg
// @license MIT
// ==/UserScript==
(function () {
'use strict';
const GO_KEY = 'gocfg';
const DEFAULTS = Object.freeze({
KEEP_OPEN: 5,
TICK_MS: 900,
NEAR_BOTTOM_PX: 260,
ENABLED: true,
PANEL_OPEN: false,
});
const ADAPTERS = {
chatgpt: {
match: () => location.hostname.includes('chatgpt.com'),
MSG_SEL: '[data-message-author-role]',
COMPOSER_SEL: '#prompt-textarea, [data-testid="prompt-textarea"], .ProseMirror, [contenteditable="true"]',
getRole: (node) => node.getAttribute('data-message-author-role') || 'assistant',
labelUser: '👤 Вы',
labelBot: '🤖 ChatGPT',
botName: 'ChatGPT',
isValidMsg: () => true,
},
deepseek: {
match: () => location.hostname.includes('chat.deepseek.com'),
MSG_SEL: null,
COMPOSER_SEL: '#chat-input, [contenteditable="true"]',
getRole: (node) => node.querySelector('div.ds-markdown') ? 'assistant' : 'user',
labelUser: '👤 Вы',
labelBot: '🤖 DeepSeek',
botName: 'DeepSeek',
isValidMsg: (node) => node.children.length > 0 && node.offsetHeight > 20,
},
};
const SITE = Object.values(ADAPTERS).find(a => a.match()) || ADAPTERS.chatgpt;
const IS_DEEPSEEK = SITE === ADAPTERS.deepseek;
let _dsContainer = null;
function findDeepSeekContainer() {
if (_dsContainer && document.contains(_dsContainer)) return _dsContainer;
_dsContainer = document.querySelector('.dad65929');
if (_dsContainer) return _dsContainer;
for (const el of document.querySelectorAll('div[class]')) {
if (el.children.length >= 2 && el.querySelector('div.ds-markdown')) {
_dsContainer = el;
return _dsContainer;
}
}
return null;
}
function getMessages() {
if (IS_DEEPSEEK) {
const container = findDeepSeekContainer();
if (!container) return [];
return Array.from(container.children).filter(SITE.isValidMsg);
}
return Array.from(document.querySelectorAll(SITE.MSG_SEL));
}
function loadCfg() {
try {
const raw = localStorage.getItem(GO_KEY);
return { ...DEFAULTS, ...(raw ? JSON.parse(raw) : {}) };
} catch {
return { ...DEFAULTS };
}
}
function saveCfg() {
try { localStorage.setItem(GO_KEY, JSON.stringify(cfg)); } catch {}
}
let cfg = loadCfg();
let enabled = cfg.ENABLED;
const archived = [];
let uiRefs = null;
let tickTimer = null;
let tickPaused = false;
let lastMsgCount = 0;
let dirtyLimit = false;
let totalSavedBytes = 0;
const css = `
html, body {
scroll-behavior: auto !important;
overflow-anchor: none !important;
}
[data-message-author-role],
.dad65929 > div {
content-visibility: auto;
contain: layout style paint;
contain-intrinsic-size: auto 400px;
}
.cg-min * {
animation: none !important;
transition: none !important;
}
.cg-placeholder {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 6px 14px;
margin: 4px 0;
border-radius: 12px;
cursor: pointer;
font: 500 12px/1.4 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
color: rgba(255,255,255,.7);
background: linear-gradient(135deg, rgba(255,255,255,.06), rgba(255,255,255,.02));
border: 1px dashed rgba(255,255,255,.15);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
transition: background .15s, border-color .15s, color .15s;
user-select: none;
}
.cg-placeholder:hover {
background: linear-gradient(135deg, rgba(255,255,255,.12), rgba(255,255,255,.05));
border-color: rgba(255,255,255,.28);
color: rgba(255,255,255,.92);
}
.cg-placeholder .cg-ph-icon { font-size: 14px; }
.cg-fab {
position: fixed;
right: 14px;
bottom: 18px;
z-index: 2147483647;
font: 600 12px/1.3 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
color: rgba(255,255,255,.95);
user-select: none;
-webkit-user-select: none;
}
.cg-fab * { box-sizing: border-box; }
.cg-pill {
position: relative;
display: flex;
align-items: center;
gap: 10px;
padding: 8px 10px;
border-radius: 24px;
cursor: pointer;
overflow: hidden;
background: linear-gradient(135deg, rgba(255,255,255,.16), rgba(255,255,255,.06));
border: 1px solid rgba(255,255,255,.20);
box-shadow:
0 10px 30px rgba(0,0,0,.20),
inset 0 1px 0 rgba(255,255,255,.25),
inset 0 -1px 0 rgba(255,255,255,.05);
backdrop-filter: blur(18px) saturate(160%);
-webkit-backdrop-filter: blur(18px) saturate(160%);
}
.cg-pill::before {
content: "";
position: absolute;
inset: 0;
pointer-events: none;
border-radius: inherit;
background:
radial-gradient(circle at top left, rgba(255,255,255,.28), transparent 42%),
linear-gradient(180deg, rgba(255,255,255,.12), transparent 40%);
opacity: .95;
}
.cg-icon {
position: relative;
z-index: 1;
width: 26px;
height: 26px;
min-width: 26px;
border-radius: 999px;
display: grid;
place-items: center;
font-size: 16px;
line-height: 1;
background: rgba(255,255,255,.08);
border: 1px solid rgba(255,255,255,.14);
box-shadow:
inset 0 1px 0 rgba(255,255,255,.20),
0 4px 14px rgba(255,255,255,.06);
text-shadow: 0 1px 2px rgba(0,0,0,.22), 0 0 10px rgba(255,255,255,.10);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
.cg-panel {
position: relative;
z-index: 1;
display: none;
flex-direction: column;
gap: 6px;
padding-right: 2px;
min-width: 160px;
}
.cg-fab.open .cg-panel { display: flex; }
.cg-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
white-space: nowrap;
}
.cg-count-label { opacity: .78; }
.cg-label { letter-spacing: .01em; }
.cg-sw {
position: relative;
width: 36px;
height: 20px;
border-radius: 999px;
border: 1px solid rgba(255,255,255,.14);
padding: 2px;
background: rgba(255,255,255,.10);
display: inline-flex;
align-items: center;
cursor: pointer;
box-shadow: inset 0 1px 0 rgba(255,255,255,.18), 0 2px 10px rgba(0,0,0,.12);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
}
.cg-sw::after {
content: '';
width: 14px;
height: 14px;
border-radius: 50%;
background: rgba(255,255,255,.92);
box-shadow: 0 1px 4px rgba(0,0,0,.25), inset 0 1px 0 rgba(255,255,255,.85);
transition: transform .18s ease;
}
.cg-fab[data-enabled="true"] .cg-sw {
background: rgba(255,255,255,.18);
border-color: rgba(255,255,255,.22);
}
.cg-fab[data-enabled="true"] .cg-sw::after { transform: translateX(16px); }
.cg-fab[data-enabled="false"] .cg-pill { opacity: .74; }
.cg-fab[data-enabled="false"] .cg-icon { filter: grayscale(.12); opacity: .75; }
.cg-stepper { display: inline-flex; align-items: center; gap: 4px; }
.cg-stepper button {
width: 20px;
height: 20px;
border-radius: 6px;
border: 1px solid rgba(255,255,255,.18);
background: rgba(255,255,255,.08);
color: rgba(255,255,255,.85);
font: 700 13px/1 system-ui;
cursor: pointer;
display: grid;
place-items: center;
padding: 0;
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
}
.cg-stepper button:hover { background: rgba(255,255,255,.16); }
.cg-stepper-val {
min-width: 18px;
text-align: center;
font-variant-numeric: tabular-nums;
}
.cg-unfocused {
font-size: 11px;
opacity: .65;
font-variant-numeric: tabular-nums;
}
`;
try {
GM_addStyle(css);
} catch {
const s = document.createElement('style');
s.textContent = css;
document.head.appendChild(s);
}
const $$ = (sel, root) => Array.from((root || document).querySelectorAll(sel));
function isNearBottom() {
const el = document.scrollingElement || document.documentElement;
return (el.scrollHeight - el.clientHeight) - el.scrollTop < cfg.NEAR_BOTTOM_PX;
}
function estimateNodeBytes(node) {
const htmlBytes = new Blob([node.outerHTML]).size;
const nodeCount = node.querySelectorAll('*').length + 1;
return htmlBytes + nodeCount * 1300;
}
function formatBytes(bytes) {
if (bytes < 1024) return bytes + ' B';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
}
let composerDone = false;
function lightenComposer() {
if (composerDone) return;
const el = document.querySelector(SITE.COMPOSER_SEL);
if (!el) return;
for (const [k, v] of [
['autocomplete', 'off'], ['autocorrect', 'off'],
['autocapitalize', 'off'], ['spellcheck', 'false'], ['inputmode', 'text'],
]) el.setAttribute(k, v);
composerDone = true;
}
function createPlaceholder(entry) {
const label = entry.role === 'user' ? SITE.labelUser : SITE.labelBot;
const ph = document.createElement('div');
ph.className = 'cg-placeholder';
ph.innerHTML = `<span class="cg-ph-icon">👁</span> <span>Показать: ${label}</span>`;
ph.addEventListener('click', () => restoreOne(entry));
return ph;
}
function archiveOld() {
const nodes = getMessages();
if (!dirtyLimit && nodes.length === lastMsgCount && archived.length > 0) return;
dirtyLimit = false;
lastMsgCount = nodes.length;
const limit = Math.max(0, nodes.length - cfg.KEEP_OPEN);
if (limit <= 0 || !isNearBottom()) return;
let changed = false;
for (let i = 0; i < limit; i++) {
const n = nodes[i];
if (n.dataset.cgArc === '1' || n.dataset.cgPinned === '1') continue;
const byteSize = estimateNodeBytes(n);
n.dataset.cgArc = '1';
const role = SITE.getRole(n);
const entry = { node: n, placeholder: null, htmlSize: byteSize, role };
entry.placeholder = createPlaceholder(entry);
n.parentNode.insertBefore(entry.placeholder, n);
n.remove();
archived.push(entry);
totalSavedBytes += byteSize;
changed = true;
}
if (changed) updateCount();
}
function restoreOne(entry) {
const idx = archived.indexOf(entry);
if (idx === -1) return;
entry.node.dataset.cgArc = '0';
entry.node.dataset.cgPinned = '1';
entry.placeholder.parentNode.insertBefore(entry.node, entry.placeholder);
entry.placeholder.remove();
totalSavedBytes = Math.max(0, totalSavedBytes - entry.htmlSize);
archived.splice(idx, 1);
entry.node = null;
entry.placeholder = null;
updateCount();
}
function restoreAll() {
if (!archived.length) return;
for (const entry of [...archived]) {
if (!entry.node) continue;
entry.node.dataset.cgArc = '0';
delete entry.node.dataset.cgPinned;
if (entry.placeholder?.parentNode) {
entry.placeholder.parentNode.insertBefore(entry.node, entry.placeholder);
entry.placeholder.remove();
}
entry.node = null;
entry.placeholder = null;
}
archived.length = 0;
totalSavedBytes = 0;
updateCount();
}
let lastPath = location.pathname;
function checkNavigation() {
if (location.pathname !== lastPath) {
lastPath = location.pathname;
$$('[data-cg-pinned]').forEach(n => delete n.dataset.cgPinned);
if (IS_DEEPSEEK) _dsContainer = null;
archived.length = 0;
totalSavedBytes = 0;
lastMsgCount = 0;
dirtyLimit = false;
composerDone = false;
$$('.cg-placeholder').forEach(ph => ph.remove());
updateCount();
}
}
function ensureUI() {
if (uiRefs) return;
const root = document.createElement('div');
root.className = 'cg-fab' + (cfg.PANEL_OPEN ? ' open' : '');
root.setAttribute('data-enabled', String(enabled));
root.innerHTML = `
<div class="cg-pill">
<div class="cg-icon" title="G-Obstructore for ${SITE.botName}">🧹</div>
<div class="cg-panel">
<div class="cg-row">
<span class="cg-label">G-Obstructore</span>
<button class="cg-sw" type="button" aria-label="Tumbler"></button>
</div>
<div class="cg-row">
<span class="cg-count-label">Показывать:</span>
<div class="cg-stepper">
<button class="cg-step-down" type="button">−</button>
<span class="cg-stepper-val">${cfg.KEEP_OPEN}</span>
<button class="cg-step-up" type="button">+</button>
</div>
</div>
<div class="cg-row">
<span class="cg-unfocused">Скрыто: <span class="cg-tab-val">0</span></span>
</div>
<div class="cg-row">
<span class="cg-unfocused">Сэкономлено: <span class="cg-ram-val">0 B</span></span>
</div>
</div>
</div>`;
root.addEventListener('click', (ev) => {
if (ev.target.closest('.cg-sw')) {
ev.stopPropagation();
enabled = !enabled;
cfg.ENABLED = enabled;
saveCfg();
applyState();
return;
}
if (ev.target.closest('.cg-step-down')) {
ev.stopPropagation();
cfg.KEEP_OPEN = Math.max(1, cfg.KEEP_OPEN - 1);
saveCfg();
root.querySelector('.cg-stepper-val').textContent = cfg.KEEP_OPEN;
dirtyLimit = true;
return;
}
if (ev.target.closest('.cg-step-up')) {
ev.stopPropagation();
cfg.KEEP_OPEN = Math.min(50, cfg.KEEP_OPEN + 1);
saveCfg();
root.querySelector('.cg-stepper-val').textContent = cfg.KEEP_OPEN;
dirtyLimit = true;
return;
}
if (ev.target.closest('.cg-pill')) {
root.classList.toggle('open');
cfg.PANEL_OPEN = root.classList.contains('open');
saveCfg();
}
});
document.documentElement.appendChild(root);
uiRefs = {
root,
countEl: root.querySelector('.cg-tab-val'),
ramEl: root.querySelector('.cg-ram-val'),
};
applyState();
}
function updateCount() {
if (uiRefs?.countEl) uiRefs.countEl.textContent = archived.length;
if (uiRefs?.ramEl) uiRefs.ramEl.textContent = formatBytes(Math.max(0, totalSavedBytes));
}
function applyState() {
if (!uiRefs) return;
uiRefs.root.setAttribute('data-enabled', String(enabled));
document.documentElement.classList.toggle('cg-min', enabled);
if (!enabled) restoreAll();
updateCount();
}
function tick() {
ensureUI();
lightenComposer();
checkNavigation();
if (enabled) {
if (typeof requestIdleCallback === 'function') {
requestIdleCallback(() => archiveOld(), { timeout: 800 });
} else {
archiveOld();
}
}
if (!tickPaused) {
tickTimer = setTimeout(tick, cfg.TICK_MS);
}
}
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
tickPaused = true;
clearTimeout(tickTimer);
} else {
tickPaused = false;
tick();
}
});
window.addEventListener('pagehide', () => {
tickPaused = true;
clearTimeout(tickTimer);
});
saveCfg();
setTimeout(tick, 400);
})();