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

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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();

})();