AI Studio 增强器 - 自动启用谷歌搜索和 URL 上下文

AI Studio 增强脚本。可自动启用谷歌搜索和"URL 上下文"等功能。

// ==UserScript==
// @name         AI Studio 增强器 - 自动启用谷歌搜索和 URL 上下文
// @name:en      AI Studio Enhancer - Auto-enable Google Search and URL Context
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  AI Studio 增强脚本。可自动启用谷歌搜索和"URL 上下文"等功能。
// @description:en  A script to automatically enable features like Grounding and URL Context in AI Studio.
// @author       fleey
// @match        https://aistudio.google.com/*
// @grant        none
// @run-at       document-idle
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    /**
     * @class AIStudioEnhancer
     * @description Encapsulates all logic for observing and interacting with the AI Studio UI.
     * This class-based approach improves maintainability, scalability, and organization.
     */
    class AIStudioEnhancer {
        /**
         * The configuration for the enhancer script.
         * @private
         */
        config = {
            // Set to `false` to disable all console logs for a cleaner experience.
            DEBUG: true,
            // Debounce delay in milliseconds. Prevents excessive checks during rapid DOM changes.
            DEBOUNCE_DELAY: 150,
            // A collection of UI elements to target for automatic activation.
            // This structure makes it easy to add more targets in the future.
            TARGETS: [
                {
                    name: 'Grounding with Google Search',
                    selector: 'button[aria-label*="Grounding with Google Search"][aria-checked="false"]',
                },
                {
                    name: 'URL Context',
                    selector: 'button[aria-label*="url context"][aria-checked="false"]',
                },
            ],
        };

        /**
         * The MutationObserver instance watching for DOM changes.
         * @private
         */
        observer = null;
        
        /**
         * A debounced version of the `processTargets` method for performance optimization.
         * @private
         */
        debouncedProcessTargets;

        constructor() {
            this.debouncedProcessTargets = this.debounce(this.processTargets, this.config.DEBOUNCE_DELAY);
            this.log('Initializing...');
        }
        
        /**
         * A simple logger that respects the DEBUG flag.
         * @param {...any} args - Arguments to log.
         */
        log(...args) {
            if (this.config.DEBUG) {
                console.log('[AI Studio Enhancer] - ai_studio_enhancer_v4.js:70', ...args);
            }
        }

        /**
         * Creates a debounced function that delays invoking `func` until after `wait` milliseconds
         * have elapsed since the last time the debounced function was invoked.
         * @param {Function} func The function to debounce.
         * @param {number} wait The number of milliseconds to delay.
         * @returns {Function} Returns the new debounced function.
         */
        debounce(func, wait) {
            let timeout;
            return (...args) => {
                clearTimeout(timeout);
                timeout = setTimeout(() => func.apply(this, args), wait);
            };
        }
        
        /**
         * The core logic to find and click target buttons.
         * It iterates through configured targets and activates them if found.
         */
        processTargets() {
            this.log('Running target processing...');
            for (const target of this.config.TARGETS) {
                // Query the DOM for each specific target.
                const button = document.querySelector(target.selector);
                if (button) {
                    this.log(`Target found: "${target.name}". Attempting to enable...`);
                    button.click();
                    this.log(`Successfully enabled "${target.name}".`);
                }
            }
        }

        /**
         * The callback function for the MutationObserver.
         * This is optimized to only check relevant node additions.
         * @param {MutationRecord[]} mutationsList - A list of mutations that occurred.
         */
        handleMutations = (mutationsList) => {
            let relevantChangeDetected = false;
            for (const mutation of mutationsList) {
                // We are only interested in mutations where new nodes are added.
                if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                    // This is a crucial optimization:
                    // Instead of re-querying the whole document on any change,
                    // we check if the *newly added nodes* could contain our targets.
                    // A simple check is to see if any added node is an Element.
                    for (const node of mutation.addedNodes) {
                        if (node.nodeType === Node.ELEMENT_NODE) {
                           relevantChangeDetected = true;
                           break;
                        }
                    }
                }
                if(relevantChangeDetected) break;
            }

            if(relevantChangeDetected) {
                this.log('Relevant DOM change detected. Debouncing target processing.');
                this.debouncedProcessTargets();
            }
        };

        /**
         * Starts the script by performing an initial check and setting up the observer.
         */
        start() {
            // Perform an initial check shortly after the script loads.
            // requestAnimationFrame is often smoother than a fixed setTimeout.
            requestAnimationFrame(() => this.processTargets());

            // Set up the observer to watch for dynamic content loading.
            this.observer = new MutationObserver(this.handleMutations);
            this.observer.observe(document.body, {
                childList: true,
                subtree: true,
            });

            this.log('Observer is now active. Waiting for DOM changes.');
        }

        /**
         * Disconnects the observer to clean up resources.
         */
        stop() {
            if (this.observer) {
                this.observer.disconnect();
                this.log('Observer disconnected.');
            }
        }
    }

    // --- SCRIPT EXECUTION ---
    const enhancer = new AIStudioEnhancer();
    enhancer.start();

})();