您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Hover to reveal a trash‑can icon and click to auto‑delete the conversation (there is no confirmation popup).
// ==UserScript== // @name Faster ChatGPT Delete // @namespace https://chat.openai.com/ // @version 1.0 // @description Hover to reveal a trash‑can icon and click to auto‑delete the conversation (there is no confirmation popup). // @match https://chat.openai.com/* // @include https://chatgpt.com/* // @grant none // @license MIT // ==/UserScript== (() => { const waitFor = (pred, ms = 4000, step = 70) => new Promise(res => { const end = Date.now() + ms; (function loop() { const el = pred(); if (el) return res(el); if (Date.now() > end) return res(null); setTimeout(loop, step); })(); }); const fire = (el, type) => el.dispatchEvent(new MouseEvent(type, { bubbles: true, composed: true })); async function deleteConversation(anchor) { const href = anchor.getAttribute('href'); const stay = href && location.pathname !== href; const dots = anchor.querySelector('button[data-testid$="-options"]'); if (!dots) return; ['pointerdown', 'pointerup', 'click'].forEach(t => fire(dots, t)); const del = await waitFor(() => [...document.querySelectorAll('[role="menuitem"], button')].find(el => /^delete$/i.test(el.textContent.trim()) && !el.closest('.quick‑delete') ) ); if (!del) return; ['pointerdown', 'pointerup', 'click'].forEach(t => fire(del, t)); const confirm = await waitFor(() => document.querySelector('button[data-testid="delete-conversation-confirm-button"], .btn-danger') ); if (!confirm) return; ['pointerdown', 'pointerup', 'click'].forEach(t => fire(confirm, t)); if (stay) setTimeout(() => history.replaceState(null, '', location.pathname), 80); anchor.style.transition = 'opacity .25s'; anchor.style.opacity = '0'; setTimeout(() => (anchor.style.display = 'none'), 280); } const ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <polyline points="3 6 5 6 21 6"></polyline> <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6 m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path> <line x1="10" y1="11" x2="10" y2="17"></line> <line x1="14" y1="11" x2="14" y2="17"></line></svg>`; function decorate(anchor) { if (anchor.querySelector('.quick‑delete')) return; anchor.style.position = 'relative'; const icon = Object.assign(document.createElement('span'), { className: 'quick‑delete', innerHTML: ICON }); const bg1 = 'var(--sidebar-surface-secondary, #4b5563)'; const bg2 = 'var(--sidebar-surface-tertiary , #6b7280)'; Object.assign(icon.style, { position: 'absolute', left: '4px', top: '50%', transform: 'translateY(-50%)', cursor: 'pointer', pointerEvents: 'auto', zIndex: 5, padding: '2px', borderRadius: '4px', background: `linear-gradient(135deg, ${bg1}, ${bg2})`, color: 'var(--token-text-primary)', opacity: 0, transition: 'opacity 100ms' }); anchor.addEventListener('mouseenter', () => { icon.style.opacity = '.85'; anchor.style.transition = 'padding-left 100ms'; anchor.style.paddingLeft = '28px'; }); anchor.addEventListener('mouseleave', () => { icon.style.opacity = '0'; anchor.style.paddingLeft = ''; }); icon.addEventListener('click', e => { e.stopPropagation(); e.preventDefault(); deleteConversation(anchor); }); anchor.prepend(icon); } const itemSelector = 'a.__menu-item'; function handleMutation(records) { for (const rec of records) { rec.addedNodes.forEach(node => { if (node.nodeType === 1 && node.matches(itemSelector)) decorate(node); else if (node.nodeType === 1) node.querySelectorAll?.(itemSelector).forEach(decorate); }); } } function decorateInBatches(nodes) { const batch = nodes.splice(0, 50); batch.forEach(decorate); if (nodes.length) requestIdleCallback(() => decorateInBatches(nodes)); } function init() { const container = document.querySelector('nav') || document.body; new MutationObserver(handleMutation) .observe(container, { childList: true, subtree: true }); const startNodes = [...container.querySelectorAll(itemSelector)]; if (startNodes.length) requestIdleCallback(() => decorateInBatches(startNodes)); } const ready = setInterval(() => { if (document.querySelector('nav')) { clearInterval(ready); init(); } }, 200); })();