您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Culls messages from ChatGPT conversations via an overlay. Helpful for those who have low-end hardwares
当前为
// ==UserScript== // @name ChatGPT.com Advanced Message Culler // @namespace https://chatgpt.com/ // @version 1.5.0 // @description Culls messages from ChatGPT conversations via an overlay. Helpful for those who have low-end hardwares // @author sytesn, ChatGPT // @match https://chatgpt.com/* // @grant none // @license MIT // ==/UserScript== (function () { 'use strict'; const PIN_STORAGE_KEY = 'chatgpt_message_pins'; const SETTINGS_KEYS = [ 'culler-limit', 'culler-enabled', 'culler-mode', 'culler-fade', 'panel-left', 'panel-top' ]; const getPinnedIds = () => JSON.parse(localStorage.getItem(PIN_STORAGE_KEY) || '[]'); const setPinnedIds = (ids) => localStorage.setItem(PIN_STORAGE_KEY, JSON.stringify(ids)); let MESSAGE_LIMIT = parseInt(localStorage.getItem('culler-limit')) || 50; let cullingEnabled = localStorage.getItem('culler-enabled') === 'true'; let mode = localStorage.getItem('culler-mode') || "hide"; let fadeEnabled = localStorage.getItem('culler-fade') !== 'false'; let multiSelect = false; const style = document.createElement('style'); style.textContent = ` #culler-panel { position: fixed; top: ${localStorage.getItem('panel-top') || '10px'}; left: ${localStorage.getItem('panel-left') || '10px'}; z-index: 9999; background-color: #111; padding: 10px; border-radius: 8px; font-family: Arial, sans-serif; font-size: 13px; color: #fff; transition: opacity 0.3s ease; max-width: 300px; resize: both; overflow: auto; } .fade { transition: opacity 0.4s ease; opacity: 0; } .fade-in { opacity: 1 !important; } #culler-panel input, #culler-panel select { background-color: #222; color: #fff; border: 1px solid #444; border-radius: 4px; padding: 2px 4px; } #culler-panel button { margin-top:6px; padding:4px 8px; border: none; border-radius: 4px; background-color: #444; color: #fff; cursor: pointer; } .top-right-btn { position: absolute; top: 4px; right: 6px; background: transparent; font-size: 14px; padding: 0; } .pin-btn { position: absolute; top: 4px; right: 4px; background: #444; border: none; color: #fff; border-radius: 4px; padding: 2px 6px; font-size: 12px; cursor: pointer; } #pin-panel { position: fixed; top: 60px; right: 10px; width: 300px; max-height: 70vh; overflow-y: auto; background: #1e1e1e; color: white; border-radius: 8px; padding: 10px; z-index: 9999; box-shadow: 0 0 10px #000; display: none; } .pin-message { background: #2e2e2e; border-radius: 6px; padding: 8px; margin-bottom: 6px; font-size: 13px; position: relative; } .pin-message button { position: absolute; top: 4px; right: 4px; background: #900; border: none; color: white; border-radius: 4px; padding: 2px 6px; font-size: 12px; cursor: pointer; } `; document.head.appendChild(style); const panel = document.createElement('div'); panel.id = 'culler-panel'; panel.innerHTML = ` <button class="top-right-btn" id="overlay-minimize">✕</button> <div style="margin-bottom:6px;font-weight:bold;">Message Culler</div> <label><input type="checkbox" id="culler-enable"> Enable</label><br> <label>Keep last <input type="number" id="culler-limit" value="${MESSAGE_LIMIT}" style="width: 50px;"></label><br> <label>Mode: <select id="culler-mode"> <option value="hide">Hide</option> <option value="remove">Remove</option> </select> </label><br> <label>Fade: <input type="checkbox" id="culler-fade" ${fadeEnabled ? 'checked' : ''}></label><br> <button id="culler-refresh">Refresh Now</button> <button id="pin-toggle">📌 View Pins</button><br> <button id="multi-select-toggle">Multi-Select: Off</button> <button id="export-btn">Export</button> <button id="import-btn">Import</button> `; document.body.appendChild(panel); // Draggable support (function makeDraggable(el) { let isDragging = false, offsetX = 0, offsetY = 0; el.addEventListener('mousedown', (e) => { if (["INPUT", "SELECT", "BUTTON"].includes(e.target.tagName)) return; isDragging = true; offsetX = e.clientX - el.offsetLeft; offsetY = e.clientY - el.offsetTop; }); document.addEventListener('mousemove', (e) => { if (isDragging) { el.style.left = (e.clientX - offsetX) + 'px'; el.style.top = (e.clientY - offsetY) + 'px'; localStorage.setItem('panel-left', el.style.left); localStorage.setItem('panel-top', el.style.top); } }); document.addEventListener('mouseup', () => isDragging = false); })(panel); const enableCheckbox = panel.querySelector('#culler-enable'); const limitInput = panel.querySelector('#culler-limit'); const modeSelect = panel.querySelector('#culler-mode'); const fadeCheckbox = panel.querySelector('#culler-fade'); const refreshBtn = panel.querySelector('#culler-refresh'); const multiToggle = panel.querySelector('#multi-select-toggle'); enableCheckbox.checked = cullingEnabled; modeSelect.value = mode; enableCheckbox.addEventListener('change', () => { cullingEnabled = enableCheckbox.checked; localStorage.setItem('culler-enabled', cullingEnabled); applyCulling(); }); limitInput.addEventListener('change', () => { const oldLimit = MESSAGE_LIMIT; MESSAGE_LIMIT = parseInt(limitInput.value, 10) || 50; localStorage.setItem('culler-limit', MESSAGE_LIMIT); applyCulling(oldLimit); }); modeSelect.addEventListener('change', () => { mode = modeSelect.value; localStorage.setItem('culler-mode', mode); applyCulling(); }); fadeCheckbox.addEventListener('change', () => { fadeEnabled = fadeCheckbox.checked; localStorage.setItem('culler-fade', fadeEnabled); }); refreshBtn.addEventListener('click', () => applyCulling()); multiToggle.addEventListener('click', () => { multiSelect = !multiSelect; multiToggle.textContent = `Multi-Select: ${multiSelect ? 'On' : 'Off'}`; }); document.getElementById('overlay-minimize').addEventListener('click', () => { panel.style.display = 'none'; showRestoreBtn(); }); function showRestoreBtn() { const btn = document.createElement('button'); btn.textContent = 'Show Overlay'; btn.style = 'position:fixed;top:10px;left:10px;z-index:9999;background:#333;color:#fff;padding:6px;border:none;border-radius:4px;'; btn.onclick = () => { panel.style.display = 'block'; btn.remove(); }; document.body.appendChild(btn); } document.getElementById('export-btn').addEventListener('click', () => { const data = {}; SETTINGS_KEYS.forEach(k => data[k] = localStorage.getItem(k)); data.pins = getPinnedIds(); const blob = new Blob([JSON.stringify(data)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.download = 'chatgpt_culler_backup.json'; a.href = url; a.click(); URL.revokeObjectURL(url); }); document.getElementById('import-btn').addEventListener('click', () => { const input = document.createElement('input'); input.type = 'file'; input.accept = '.json'; input.onchange = e => { const file = e.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = e => { try { const data = JSON.parse(e.target.result); SETTINGS_KEYS.forEach(k => { if (data[k] !== undefined) localStorage.setItem(k, data[k]); }); if (Array.isArray(data.pins)) setPinnedIds(data.pins); location.reload(); } catch (e) { alert('Invalid file!'); } }; reader.readAsText(file); }; input.click(); }); // Command palette document.addEventListener('keydown', (e) => { if (e.ctrlKey && e.shiftKey && e.key === 'K') { const cmd = prompt('Culler Command: hide/show/refresh'); if (cmd === 'hide') panel.style.display = 'none'; if (cmd === 'show') panel.style.display = 'block'; if (cmd === 'refresh') applyCulling(); } }); const pinPanel = document.createElement('div'); pinPanel.id = 'pin-panel'; pinPanel.innerHTML = `<h2>Pinned Messages</h2><div id="pin-container"></div>`; document.body.appendChild(pinPanel); document.getElementById('pin-toggle').addEventListener('click', () => { pinPanel.style.display = pinPanel.style.display === 'none' ? 'block' : 'none'; updatePinnedPanel(); }); function getMessageElements() { return Array.from(document.querySelectorAll('article[data-testid^="conversation-turn-"]')); } function applyCulling(previousLimit = MESSAGE_LIMIT) { const messages = getMessageElements(); const pinned = new Set(getPinnedIds()); const visible = messages.filter(msg => !pinned.has(msg.dataset.testid)); const limitCutoff = visible.length - MESSAGE_LIMIT; messages.forEach((msg, i) => { const id = msg.dataset.testid; const isPinned = pinned.has(id); const visibleIndex = visible.indexOf(msg); ensurePinButton(msg); if (!cullingEnabled || isPinned || visibleIndex >= limitCutoff) { if (fadeEnabled && previousLimit < MESSAGE_LIMIT && visibleIndex >= limitCutoff && visibleIndex < visible.length - previousLimit) { msg.classList.add('fade'); msg.style.display = ''; setTimeout(() => msg.classList.add('fade-in'), 10); setTimeout(() => msg.classList.remove('fade', 'fade-in'), 410); } else { msg.style.display = ''; } msg.classList.remove('culled'); } else { if (mode === "remove") { if (!msg.dataset.removed) { msg.dataset.removed = "true"; msg.remove(); } } else { if (fadeEnabled) { msg.classList.add('fade'); msg.classList.remove('fade-in'); setTimeout(() => msg.style.display = 'none', 400); } else { msg.style.display = "none"; } msg.classList.add("culled"); } } }); } function ensurePinButton(msg) { if (msg.querySelector('.pin-btn')) return; const btn = document.createElement('button'); btn.className = 'pin-btn'; const id = msg.dataset.testid; const pinned = new Set(getPinnedIds()); btn.textContent = pinned.has(id) ? 'Unpin' : 'Pin'; btn.addEventListener('click', (e) => { e.stopPropagation(); const ids = new Set(getPinnedIds()); if (multiSelect) { const allVisible = getMessageElements().filter(el => el.style.display !== 'none'); allVisible.forEach(el => { const elId = el.dataset.testid; if (!ids.has(elId)) ids.add(elId); }); } else { if (ids.has(id)) ids.delete(id); else ids.add(id); } setPinnedIds([...ids]); applyCulling(); }); msg.style.position = 'relative'; msg.appendChild(btn); } function updatePinnedPanel() { const pinned = new Set(getPinnedIds()); const container = document.getElementById('pin-container'); container.innerHTML = ''; getMessageElements().forEach(msg => { if (pinned.has(msg.dataset.testid)) { const clone = msg.cloneNode(true); clone.querySelector('.pin-btn')?.remove(); const id = msg.dataset.testid; const unpinBtn = document.createElement('button'); unpinBtn.textContent = 'Unpin'; unpinBtn.onclick = () => { const ids = new Set(getPinnedIds()); ids.delete(id); setPinnedIds([...ids]); updatePinnedPanel(); applyCulling(); }; clone.appendChild(unpinBtn); clone.className = 'pin-message'; container.appendChild(clone); } }); } new MutationObserver(() => { if (cullingEnabled) applyCulling(); }).observe(document.body, { childList: true, subtree: true }); setTimeout(() => applyCulling(), 1000); })();