自动关闭 Google AI Studio 设置面板

当 AI Studio 页面加载时,自动关闭设置面板以节省页面空间。顺带一提,这个脚本是 Gemini 在 AI Studio 中编写的。

// ==UserScript==
// @name        Automatically Close Google AI Studio Settings Panel
// @name:zh-CN  自动关闭 Google AI Studio 设置面板
// @namespace   Violentmonkey Scripts
// @match       https://aistudio.google.com/*
// @grant       GM_addStyle
// @run-at      document-start
// @version     1.1.20250413
// @author      plasma-green & Gemini 2.5 Pro Preview 03-25
// @description When the AI Studio page loads, the settings panel is automatically closed to save page space. By the way, this script was written by Gemini in AI Studio.
// @description:zh-CN 当 AI Studio 页面加载时,自动关闭设置面板以节省页面空间。顺带一提,这个脚本是 Gemini 在 AI Studio 中编写的。
// @license     AGPL-3.0
// ==/UserScript==


(function() {
    'use strict';

    // --- 配置 ---
    const buttonSelector = 'button[aria-label="Close run settings panel"]'; // 目标按钮的选择器
    const logPrefix = '[AutoClick RunSettings] '; // 日志前缀,方便调试
    const clickDelay = 1200; // 延迟时间 (毫秒),2000ms = 2秒

    console.log(logPrefix + '脚本启动,开始监控按钮: ' + buttonSelector + ',延迟 ' + (clickDelay / 1000) + ' 秒');

    // 使用 WeakMap 来跟踪哪些按钮已经有待处理的点击计时器
    // WeakMap 的好处是当按钮元素从 DOM 中移除并被垃圾回收时,对应的条目也会自动移除,避免内存泄漏。
    const pendingClickTimeouts = new WeakMap();

    // --- 实际执行点击操作的函数 ---
    function performActualClick(button) {
        try {
            console.log(logPrefix + '执行点击...');
            // 尝试 dispatchEvent (无 view)
            const event = new MouseEvent('click', {
                bubbles: true,
                cancelable: true
            });
            if (button.dispatchEvent(event)) {
                console.log(logPrefix + 'dispatchEvent(click) 成功。');
                return true;
            } else {
                 console.log(logPrefix + 'dispatchEvent(click) 执行但可能被取消。');
                 return true; // 认为已尝试
            }
        } catch (dispatchError) {
            console.warn(logPrefix + 'dispatchEvent 点击失败:', dispatchError);
            console.log(logPrefix + '尝试回退到 button.click()...');
            try {
                button.click(); // 回退
                console.log(logPrefix + 'button.click() 成功。');
                return true;
            } catch (clickError) {
                console.error(logPrefix + 'button.click() 也失败:', clickError);
                return false;
            }
        }
    }


    // --- 检查按钮状态并计划(或取消)延迟点击 ---
    function scheduleOrCancelClick(button) {
        // 检查按钮当前是否可见且可交互
        const isButtonClickable = button && document.body.contains(button) && button.offsetParent !== null && !button.disabled;

        if (isButtonClickable) {
            // 如果按钮可点击,并且 *没有* 正在等待的计时器
            if (!pendingClickTimeouts.has(button)) {
                console.log(logPrefix + '检测到可点击按钮,将在 ' + (clickDelay / 1000) + ' 秒后点击...');
                const timeoutId = setTimeout(() => {
                    // --- Timeout 回调 ---
                    // 再次检查按钮状态,确保在延迟期间它没有消失或变得不可点击
                    if (button && document.body.contains(button) && button.offsetParent !== null && !button.disabled) {
                        console.log(logPrefix + '延迟时间到,按钮仍然可点击。');
                        performActualClick(button);
                    } else {
                        console.log(logPrefix + '延迟时间到,但按钮已不可点击,取消本次点击。');
                    }
                    // 无论是否点击,都要从 WeakMap 中移除记录,允许下次重新调度
                    pendingClickTimeouts.delete(button);
                    // --- Timeout 回调结束 ---
                }, clickDelay);

                // 将计时器 ID 存入 WeakMap,标记这个按钮正在等待点击
                pendingClickTimeouts.set(button, timeoutId);
            } else {
                // console.log(logPrefix + '按钮已在等待点击计时中,忽略本次检测。'); // 可选调试日志
            }
        } else {
            // 如果按钮变得不可点击 (隐藏、禁用、移除) 并且 *有* 正在等待的计时器
            if (pendingClickTimeouts.has(button)) {
                console.log(logPrefix + '按钮变得不可点击,取消之前计划的延迟点击。');
                clearTimeout(pendingClickTimeouts.get(button)); // 清除计时器
                pendingClickTimeouts.delete(button); // 从 WeakMap 中移除记录
            }
        }
    }

    // --- MutationObserver 回调 ---
    const observerCallback = (mutationsList, observer) => {
        // 简单起见,每次DOM变化都重新查找按钮并评估状态
        // 优化:可以只检查与 buttonSelector 相关的变化,但通常 querySelector 性能足够
        const targetButton = document.querySelector(buttonSelector);

        if (targetButton) {
             // 如果按钮存在,调用调度函数来决定是否启动或什么都不做
             scheduleOrCancelClick(targetButton);
        } else {
            // 如果按钮在 DOM 中找不到了,我们需要检查是否有对应的计时器需要取消
            // (注意:如果按钮实例还在内存中且在WeakMap里,下面的逻辑可能不完美,
            // 但通常按钮不存在于DOM时,scheduleOrCancelClick(null)不会执行,
            // 而如果按钮实例还在但被隐藏/禁用,scheduleOrCancelClick会处理取消)
            // 简单起见,依赖 scheduleOrCancelClick 在按钮不可见时处理取消。
            // console.log(logPrefix + '未找到目标按钮。');
        }

        // --- 更精细的检查(可选)---
        // 如果性能有问题,可以像之前版本那样检查 mutationsList
        // 但需要在找到按钮或按钮消失时都调用 scheduleOrCancelClick
        /*
        for (const mutation of mutationsList) {
             // ... 检查添加、移除、属性变化 ...
             if (/* 按钮出现或变得可点击 * /) {
                 const button = mutation.target or found button;
                 scheduleOrCancelClick(button);
             } else if (/* 按钮消失或变得不可点击 * /) {
                  const button = mutation.target or previously known button;
                  // 需要一种方法获取到旧按钮的引用来取消计时器
                  // 这使得简单地每次重新 querySelector 更方便
             }
        }
        */
    };

    // --- 创建并配置观察器 ---
    const observer = new MutationObserver(observerCallback);
    const observerConfig = {
        childList: true,
        subtree: true,
        attributes: true, // 监控属性变化也很重要,因为按钮可能通过 disabled 或 style/class 变化
        attributeFilter: ['disabled', 'class', 'style'] // 可以限定关心的属性
    };

    // --- 初始化脚本 ---
    function initialize() {
        if (document.body) {
            console.log(logPrefix + 'DOM 已准备好,执行首次检查并启动 MutationObserver...');
            const initialButton = document.querySelector(buttonSelector);
            if (initialButton) {
                scheduleOrCancelClick(initialButton); // 初始检查也走调度逻辑
            }
            observer.observe(document.body, observerConfig);
            console.log(logPrefix + 'MutationObserver 已启动。');
        } else {
            requestAnimationFrame(initialize);
        }
    }

    // --- 启动逻辑 ---
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initialize);
    } else {
        initialize();
    }

    // --- 清理 ---
    window.addEventListener('unload', () => {
        if (observer) {
            observer.disconnect();
            console.log(logPrefix + '页面卸载,MutationObserver 已断开。');
        }
        // 清理所有可能存在的待处理计时器
        // 遍历当前页面上所有匹配的按钮来查找并清除计时器
        document.querySelectorAll(buttonSelector).forEach(button => {
            if (pendingClickTimeouts.has(button)) {
                clearTimeout(pendingClickTimeouts.get(button));
                pendingClickTimeouts.delete(button);
                console.log(logPrefix + '页面卸载,清除了一个待处理的点击计时器。');
            }
        });
    });

    GM_addStyle(`
      /* 隐藏不需要的元素 */
      .header-container, .placeholder-overlay.ng-star-inserted {
          display: none !important;
    }`);
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址