您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a "Delete From Here" option to the chat turn menu in Google AI Studio. Detects and alerts when trying to delete the last turn.
// ==UserScript== // @name Google AI Studio Helper // @namespace http://tampermonkey.net/ // @version 2.0 // @description Adds a "Delete From Here" option to the chat turn menu in Google AI Studio. Detects and alerts when trying to delete the last turn. // @author Your AI Assistant // @match https://aistudio.google.com/prompts/* // @grant none // @icon https://www.google.com/s2/favicons?sz=64&domain=aistudio.google.com // @license MIT // ==/UserScript== /** * Google AI Studio 批量删除对话脚本 * 功能:在 Google AI Studio 的聊天界面中添加"从此处删除"按钮 * 可以删除当前对话回合及其之后的所有对话回合 */ (function() { 'use strict'; let processedTextareas = new WeakSet(); let isProcessing = false; let lastCheck = 0; function addText() { const now = Date.now(); if (isProcessing || now - lastCheck < 1000) return; lastCheck = now; isProcessing = true; try { let textarea = document.querySelector('textarea[aria-label="System instructions"]'); if (!textarea) { const button = document.querySelector('button[aria-label="System instructions"]'); if (button && !processedTextareas.has(button)) { button.click(); processedTextareas.add(button); setTimeout(() => { textarea = document.querySelector('textarea[aria-label="System instructions"]'); if (textarea && !textarea.value.includes('中文')) { processTextarea(textarea); } setTimeout(() => { const closeBtn = document.querySelector('button[aria-label="Close system instructions"]'); if (closeBtn) closeBtn.click(); }, 200); isProcessing = false; }, 100); return; } } else if (!textarea.value.includes('中文')) { processTextarea(textarea); } } catch (e) { console.error('[脚本错误]:', e); } isProcessing = false; } function processTextarea(textarea) { if (textarea && !processedTextareas.has(textarea) && !textarea.value.includes('中文')) { const text = '你始终用中文输出'; // 模拟真实用户输入 textarea.focus(); textarea.click(); // 使用 document.execCommand 或直接设置 if (document.execCommand) { textarea.value += (textarea.value ? '\n' : '') + text; document.execCommand('insertText', false, ''); } else { textarea.value += (textarea.value ? '\n' : '') + text; } // 触发多种事件 ['focus', 'input', 'change', 'blur', 'keyup'].forEach(eventType => { textarea.dispatchEvent(new Event(eventType, { bubbles: true, cancelable: true })); }); // 模拟键盘输入 textarea.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true })); textarea.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter', bubbles: true })); processedTextareas.add(textarea); console.log('[脚本] 已添加中文输出指令:', textarea.value); } } const observer = new MutationObserver(() => { if (!isProcessing) addText(); }); observer.observe(document.body, { childList: true, subtree: true }); addText(); setInterval(() => { if (!isProcessing) addText(); }, 3000); })(); (function() { 'use strict'; // --- 配置常量 --- // 聊天回合元素的选择器 const CHAT_TURN_SELECTOR = 'ms-chat-turn'; // 选项按钮的选择器(支持中英文) const OPTIONS_BUTTON_SELECTOR = 'button[aria-label="Open options"], button[aria-label="打开选项"]'; // 聊天会话容器的选择器 const CHAT_SESSION_SELECTOR = 'ms-chat-session'; // --- 配置结束 --- console.log('AI Studio Delete Script: Initializing (v2.0)'); /** * 快速删除函数 * @param {Element} turnElement - 要删除的回合元素 * @returns {Promise<boolean>} 删除是否成功 */ async function directDelete(turnElement) { if (!turnElement) { const turns = document.querySelectorAll(CHAT_TURN_SELECTOR); turnElement = turns[turns.length - 1]; } if (!turnElement) return false; const optionsBtn = turnElement.querySelector('button[aria-label="Open options"]'); if (!optionsBtn) return false; return new Promise(resolve => { // 监听菜单出现 const observer = new MutationObserver(() => { const menu = document.querySelector('.cdk-overlay-pane'); if (menu) { const deleteBtn = Array.from(menu.querySelectorAll('button')) .find(btn => btn.textContent?.toLowerCase().includes('delete')); if (deleteBtn) { deleteBtn.click(); observer.disconnect(); // 快速检查删除结果 setTimeout(() => resolve(!document.contains(turnElement)), 200); } } }); observer.observe(document.body, {childList: true, subtree: true}); optionsBtn.click(); // 超时处理 setTimeout(() => {observer.disconnect(); resolve(false);}, 1000); }); } /** * 执行删除操作的异步函数(原始方法作为备用) * @param {Element} turnElement - 要删除的对话回合元素 * @returns {Promise<Object>} 返回删除操作的结果 */ async function performDeleteAction(turnElement) { return new Promise(resolve => { // 查找当前回合的选项按钮 const optionsButton = turnElement.querySelector(OPTIONS_BUTTON_SELECTOR); if (!optionsButton) { console.warn('Delete action failed: Options button not found on a turn.', turnElement); resolve({ success: false }); return; } // 创建观察器来监听菜单的出现 const menuObserver = new MutationObserver((mutations, observer) => { // 查找弹出的菜单面板 const menuPanel = document.querySelector('.cdk-overlay-pane, [role="menu"]'); if (menuPanel) { // 等待菜单内容完全加载 setTimeout(() => { observer.disconnect(); // 停止观察 // 获取菜单中的所有可点击元素 const allButtons = Array.from(menuPanel.querySelectorAll('button, [role="menuitem"], .mat-menu-item')); console.log('Found menu items:', allButtons.map(btn => ({ text: btn.textContent?.trim(), ariaLabel: btn.getAttribute('aria-label'), className: btn.className }))); // 查找删除按钮(支持中英文,更宽泛的匹配) const deleteButton = allButtons.find(btn => { const text = (btn.textContent || '').trim().toLowerCase(); const ariaLabel = (btn.getAttribute('aria-label') || '').toLowerCase(); const title = (btn.getAttribute('title') || '').toLowerCase(); // 检查各种可能的删除相关文本 const deleteKeywords = ['delete', '删除', 'remove', '移除', 'trash', '垃圾', 'del']; return deleteKeywords.some(keyword => text.includes(keyword) || ariaLabel.includes(keyword) || title.includes(keyword) ) || btn.querySelector('svg, mat-icon, .material-icons')?.textContent?.includes('delete'); }); if (deleteButton) { console.log('Found delete button, clicking it.'); deleteButton.click(); // 点击后等待一段时间确保操作完成 setTimeout(() => resolve({ success: true }), 800); } else { console.warn('Could not find the "Delete" button in the menu.'); console.log('Available menu items:', allButtons.map(btn => btn.textContent?.trim())); // 尝试使用键盘快捷键删除 try { // 关闭菜单 document.body.click(); // 选中当前回合 turnElement.click(); // 等待一下然后按Delete键 setTimeout(() => { const deleteEvent = new KeyboardEvent('keydown', { key: 'Delete', code: 'Delete', keyCode: 46, which: 46, bubbles: true }); document.dispatchEvent(deleteEvent); setTimeout(() => resolve({ success: true }), 500); }, 200); } catch(e) { console.error('Keyboard shortcut failed:', e); // 检查是否为最后一个回合 const currentTurnCount = document.querySelectorAll(CHAT_TURN_SELECTOR).length; resolve({ success: false, isLastTurn: currentTurnCount === 1, reason: 'no_delete_button' }); } } }, 300); // 等待300ms让菜单内容完全加载 } }); // 开始观察 DOM 变化以检测菜单出现 menuObserver.observe(document.body, { childList: true, subtree: true }); // 点击选项按钮打开菜单 optionsButton.click(); // 设置超时处理,防止无限等待 setTimeout(() => { menuObserver.disconnect(); console.warn('Menu detection timeout'); resolve({ success: false, error: 'timeout' }); }, 3000); }); } /** * 为聊天回合添加"从此处删除"按钮 * @param {Element} chatTurn - 聊天回合元素 */ function addDeleteFromHereButton(chatTurn) { const optionsButton = chatTurn.querySelector(OPTIONS_BUTTON_SELECTOR); // 如果没有选项按钮或已经添加过删除按钮,则跳过 if (!optionsButton || chatTurn.querySelector('.delete-from-here-btn')) return; const btnContainer = optionsButton.parentElement; if (!btnContainer) return; // 创建删除按钮 const deleteBtn = document.createElement('button'); deleteBtn.className = 'delete-from-here-btn'; deleteBtn.textContent = '❌'; // 垃圾桶图标 deleteBtn.title = 'Delete From Here (Delete this and all subsequent turns)'; // 设置按钮样式(完全匹配其他按钮) deleteBtn.className = optionsButton.className.replace('mat-mdc-menu-trigger', 'delete-from-here-btn'); // 复制所有计算样式 const optionsStyle = getComputedStyle(optionsButton); Object.assign(deleteBtn.style, { background: 'none', border: 'none', cursor: 'pointer', opacity: '0.6', fontSize: '14px', transition: 'opacity 0.2s', marginRight: '4px', // 完全匹配选项按钮的样式 padding: optionsStyle.padding, height: optionsStyle.height, width: optionsStyle.width, minWidth: optionsStyle.minWidth, minHeight: optionsStyle.minHeight, display: optionsStyle.display, alignItems: optionsStyle.alignItems, justifyContent: optionsStyle.justifyContent, lineHeight: optionsStyle.lineHeight, boxSizing: optionsStyle.boxSizing, verticalAlign: optionsStyle.verticalAlign, flexShrink: optionsStyle.flexShrink, flexGrow: optionsStyle.flexGrow, alignSelf: optionsStyle.alignSelf }); // 添加鼠标悬停效果 deleteBtn.addEventListener('mouseenter', () => deleteBtn.style.opacity = '1'); deleteBtn.addEventListener('mouseleave', () => deleteBtn.style.opacity = '0.6'); // 添加点击事件处理器 deleteBtn.addEventListener('click', async (e) => { e.preventDefault(); e.stopPropagation(); // 确认删除操作 if (!confirm('确定要删除此回合及之后的所有对话吗?\nAre you sure you want to delete this and all subsequent turns?')) return; // 获取所有聊天回合 const allTurns = Array.from(document.querySelectorAll(CHAT_TURN_SELECTOR)); const currentIndex = allTurns.indexOf(chatTurn); if (currentIndex > -1) { // 计算需要删除的回合数量 const turnsToDeleteCount = allTurns.length - currentIndex; console.log(`Planning to delete ${turnsToDeleteCount} turns.`); // 从最后一个回合开始逐个删除 for (let i = 0; i < turnsToDeleteCount; i++) { // 重新获取当前的回合列表(因为删除操作会改变 DOM) const currentTurns = Array.from(document.querySelectorAll(CHAT_TURN_SELECTOR)); const turnToDelete = currentTurns[currentTurns.length - 1]; // 总是删除最后一个 if (turnToDelete) { // 高亮显示即将删除的回合 turnToDelete.style.backgroundColor = 'rgba(255, 100, 100, 0.2)'; // 使用直接删除方法 const directSuccess = await directDelete(turnToDelete); const result = directSuccess ? { success: true } : { success: false }; // 如果删除失败,检查原因并处理 if (!result.success) { console.log(`删除第 ${i + 1} 个回合失败,原因:`, result); // 检查是否是最后一个回合 const remainingTurns = Array.from(document.querySelectorAll(CHAT_TURN_SELECTOR)); if (remainingTurns.length === 1) { alert(`无法删除最后一个会话:这是 Google AI Studio 自身的限制。\n\n已成功删除 ${i} 个回合。`); turnToDelete.style.backgroundColor = ''; // 清除红色高亮 break; // 停止删除过程 } else if (result.reason === 'no_delete_button') { // UI变化导致找不到删除按钮 alert(`检测到 Google AI Studio 界面变化,无法找到删除按钮。\n请手动删除或等待脚本更新。\n\n已成功删除 ${i} 个回合。`); turnToDelete.style.backgroundColor = ''; // 清除红色高亮 break; } else { // 其他原因导致的失败 console.warn('删除操作失败,停止批量删除'); turnToDelete.style.backgroundColor = ''; // 清除红色高亮 break; } } else { console.log(`成功删除第 ${i + 1} 个回合`); // 快速等待DOM更新 await new Promise(resolve => setTimeout(resolve, 300)); } } else { console.warn("Could not find a turn to delete. Stopping."); break; } } console.log('Deletion process finished.'); } }); // 确保父容器有足够宽度横排显示按钮 Object.assign(btnContainer.style, { display: 'flex', flexDirection: 'row', alignItems: 'center', gap: '4px', minWidth: 'auto', width: 'auto', flexShrink: '0' }); // 将删除按钮插入到选项按钮之前 btnContainer.insertBefore(deleteBtn, optionsButton); } /** * 处理所有聊天回合,为每个回合添加删除按钮 */ function processAllTurns() { document.querySelectorAll(CHAT_TURN_SELECTOR).forEach(addDeleteFromHereButton); } // --- 增强的启动逻辑 --- let currentObserver = null; function initializeScript() { const chatSession = document.querySelector(CHAT_SESSION_SELECTOR); if (chatSession) { console.log('AI Studio Delete Script: Chat session found, initializing.'); // 停止之前的观察器 if (currentObserver) { currentObserver.disconnect(); } // 处理现有回合 processAllTurns(); // 创建新的观察器 currentObserver = new MutationObserver(() => { setTimeout(processAllTurns, 200); }); // 观察聊天会话变化 currentObserver.observe(chatSession, { childList: true, subtree: true }); console.log('AI Studio Delete Script: Observer started.'); return true; } return false; } // 初始化 if (!initializeScript()) { // 如果初始化失败,定期重试 const startupInterval = setInterval(() => { if (initializeScript()) { clearInterval(startupInterval); } }, 1000); } // 监听页面变化(新会话) const pageObserver = new MutationObserver(() => { // 检查是否是新会话或页面变化 if (document.querySelector(CHAT_SESSION_SELECTOR) && !document.querySelector('.delete-from-here-btn')) { setTimeout(initializeScript, 500); } }); pageObserver.observe(document.body, { childList: true, subtree: true }); })();