Greasy Fork

Greasy Fork is available in English.

AI Studio 增强

提供护眼样式、元素显隐控制和自动折叠左右侧面板功能,优化 AI Studio 使用体验。

当前为 2025-04-12 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         AI Studio Enhancer
// @name:zh-CN   AI Studio 增强
// @namespace    http://tampermonkey.net/
// @version      2.1
// @description  Eye-Friendly Styles, Element Control, Auto Collapse Panels.
// @description:zh-CN 提供护眼样式、元素显隐控制和自动折叠左右侧面板功能,优化 AI Studio 使用体验。
// @author       Claude 3.5 Sonnet & Gemini 2.0 Flash Thinking Experimental 01-21 & Gemini 2.5 Pro Preview 03-25
// @match        https://aistudio.google.com/prompts/*
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    console.log('[AI Studio Enhancer+] Initializing v2.1...');

    // --- Default Settings ---
    const defaultSettings = {
        useCustomStyles: true,
        showUserPrompts: true,
        showThinkingProcess: true,
        showAIMessages: true,
        showInputBox: true,
        autoCollapseRightPanel: false,
        autoCollapseLeftPanel: false // New setting for left panel
    };

    // --- Initialize Settings ---
    let settings = {};
    for (const key in defaultSettings) {
        settings[key] = GM_getValue(key, defaultSettings[key]);
        if (GM_getValue(key) === undefined) {
             GM_setValue(key, defaultSettings[key]);
             console.log(`[AI Studio Enhancer+] Initialized new setting: ${key} = ${defaultSettings[key]}`);
        }
    }
    console.log('[AI Studio Enhancer+] Current Settings:', settings);


    // --- Menu Definition ---
    var menu_ALL = [
        [
            "useCustomStyles",
            "Custom Styles",
        ],
        [
            "autoCollapseLeftPanel", // New menu item for left panel
            "Auto Collapse Left Panel",
        ],
        [
            "autoCollapseRightPanel",
            "Auto Collapse Right Panel",
        ],
        [
            "showUserPrompts",
            "User Messages Display",
        ],
        [
            "showThinkingProcess",
            "Thinking Process Display",
        ],
        [
            "showAIMessages",
            "AI Messages Display",
        ],
        [
            "showInputBox",
            "Input Box Display",
        ],
        [
            "toggleAllDisplays", // Special key for toggle all visibility settings
            "Toggle All Displays",
        ],
    ];
    var menu_ID = []; // Array to store menu command IDs for unregistering

    // --- CSS Styles ---
    const customStyles = `
    .chunk-editor-main {
        background: #e6e5e0 !important;
        font-size: 2em !important;
    }
    .chunk-editor-main p {
        font-family: "Times New Roman", "思源宋体", "思源宋体 CN" !important;
    }
    .user-prompt-container .text-chunk {
        background: #d6d5b7 !important;
    }
    .model-prompt-container {
        background: #f3f2ee !important;
        padding: 15px !important;
        border-radius: 16px !important;
    }
    .model-prompt-container:has(.mat-accordion) {
        background: none !important;
    }
    .turn-footer {
        font-size: 10px !important;
        background: none !important;
    }
    .user-prompt-container p {
        font-size: 15px !important;
        line-height: 1.3 !important;
    }
    .model-prompt-container p {
        font-size: 20px !important;
        line-height: 2 !important;
    }
    .mat-accordion p {
        font-size: 15px !important;
    }

    .page-header {
        height: 50px !important;
    }
    .top-nav {
        font-size: .1em !important;
    }
    .token-count-container {
        position: fixed !important;
        bottom: 50px !important;
        width: 185px !important;
        margin: 0 0 15px !important;
    }
    .toolbar-container {
        height: 40px !important;
        padding: 0 !important;
    }

    .token-count-content {
        padding: 0 !important;
        font-size: .3em !important;
        background: none !important;
        opacity: .2 !important;
        transition: .3s !important;
    }
    .token-count-content:hover {
        opacity: 1 !important;
    }

    .prompt-input-wrapper {
        padding: 5px 10px !important;
    }
    .prompt-input-wrapper-container {
        padding: 0 !important;
        font-size: .5em !important;
    }
    .prompt-chip-button {
        background: #eee !important;
    }
    .prompt-chip-button:hover {
        background: #dadada !important;
    }
    `;

    const hideUserPromptsStyle = `.chat-turn-container.user { display: none !important; }`;
    const hideThinkingProcessStyle = `.chat-turn-container .thought-container {display: none !important;}`;
    const hideAIMessagesStyle = `.chat-turn-container.model { display: none !important; }`;
    const hideInputBoxStyle = `footer:has(.prompt-input-wrapper) { display: none !important; }`;

    // --- Style Application Function ---
    function updateStyles() {
        // Remove existing style elements managed by this script
        const existingStyles = document.querySelectorAll('style[data-enhancer-style]');
        existingStyles.forEach(style => style.remove());

        if (settings.useCustomStyles) {
            const styleElement = document.createElement('style');
            styleElement.setAttribute('data-enhancer-style', 'base');
            styleElement.textContent = customStyles;
            document.head.appendChild(styleElement);
        }
        if (!settings.showUserPrompts) {
            const hideUserStyle = document.createElement('style');
            hideUserStyle.setAttribute('data-enhancer-style', 'user-visibility');
            hideUserStyle.textContent = hideUserPromptsStyle;
            document.head.appendChild(hideUserStyle);
        }
        if (!settings.showThinkingProcess) {
            const hideThinkingStyle = document.createElement('style');
            hideThinkingStyle.setAttribute('data-enhancer-style', 'thinking-visibility');
            hideThinkingStyle.textContent = hideThinkingProcessStyle;
            document.head.appendChild(hideThinkingStyle);
        }
        if (!settings.showAIMessages) {
            const hideAIStyle = document.createElement('style');
            hideAIStyle.setAttribute('data-enhancer-style', 'ai-message-visibility');
            hideAIStyle.textContent = hideAIMessagesStyle;
            document.head.appendChild(hideAIStyle);
        }
        if (!settings.showInputBox) {
            const hideInputBox = document.createElement('style');
            hideInputBox.setAttribute('data-enhancer-style', 'input-box-visibility');
            hideInputBox.textContent = hideInputBoxStyle;
            document.head.appendChild(hideInputBox);
        }
        console.log('[AI Studio Enhancer+] Styles updated based on settings.');
    }

    // --- Floating Notification Function ---
    function showNotification(message) {
        const notificationId = 'enhancer-notification';
        let notification = document.getElementById(notificationId);
        if (!notification) {
            notification = document.createElement('div');
            notification.id = notificationId;
            notification.style.cssText = `
                position: fixed;
                top: 20px;
                left: 50%;
                transform: translateX(-50%);
                background-color: rgba(0, 0, 0, 0.75);
                color: white;
                padding: 10px 20px;
                border-radius: 5px;
                z-index: 10000;
                opacity: 0;
                transition: opacity 0.5s ease-in-out;
                font-size: 14px;
            `;
            document.body.appendChild(notification);
        }
        if (notification.timeoutId) {
            clearTimeout(notification.timeoutId);
        }
        notification.textContent = message;
        notification.style.opacity = '1';
        notification.timeoutId = setTimeout(() => {
            notification.style.opacity = '0';
            setTimeout(() => {
                if (notification.parentNode) {
                    notification.parentNode.removeChild(notification);
                }
                notification.timeoutId = null;
            }, 500);
        }, 1500);
    }


    // --- Menu Command Functions ---
    function registerMenuCommands() {
        menu_ID.forEach(id => GM_unregisterMenuCommand(id));
        menu_ID = [];

        console.log("[AI Studio Enhancer+] Registering menu commands...");
        menu_ALL.forEach(item => {
            const settingKey = item[0];
            const baseMenuText = item[1];

            if (settingKey === "toggleAllDisplays") {
                const displaySettingsKeys = ["showUserPrompts", "showThinkingProcess", "showAIMessages", "showInputBox"];
                const allEnabled = displaySettingsKeys.every(key => settings[key]);
                const menuText = `${allEnabled ? "🔴 Hide All Displays" : "🟢 Show All Displays"}`;
                menu_ID.push(GM_registerMenuCommand(menuText, toggleAllDisplays));
            } else {
                const currentSettingValue = settings[settingKey];
                const menuText = `${currentSettingValue ? "🔴 Disable" : "🟢 Enable"} ${baseMenuText}`;
                menu_ID.push(GM_registerMenuCommand(
                    menuText,
                    () => menuSwitch(settingKey)
                ));
            }
        });
         console.log("[AI Studio Enhancer+] Menu commands registered.");
    }

    // Toggle a single setting via menu
    function menuSwitch(settingKey) {
        let newValue = !settings[settingKey];
        settings[settingKey] = newValue;
        GM_setValue(settingKey, newValue);
        console.log(`[AI Studio Enhancer+] Toggled ${settingKey} to ${newValue}`);

        const baseMenuText = menu_ALL.find(item => item[0] === settingKey)[1];

        // Apply immediate changes based on the setting toggled
        if (['useCustomStyles', 'showUserPrompts', 'showThinkingProcess', 'showAIMessages', 'showInputBox'].includes(settingKey)) {
             updateStyles();
        } else if (settingKey === 'autoCollapseRightPanel') {
             if (newValue) {
                  console.log('[AI Studio Enhancer+] Auto-collapse Right Panel enabled, attempting initial check/click.');
                  setTimeout(triggerAutoCollapseRightPanelIfNeeded, 500); // Check right panel
             }
        } else if (settingKey === 'autoCollapseLeftPanel') { // Handle left panel toggle
             if (newValue) {
                  console.log('[AI Studio Enhancer+] Auto-collapse Left Panel enabled, attempting collapse.');
                  setTimeout(triggerAutoCollapseLeftPanelIfNeeded, 500); // Collapse left panel
             }
             // No immediate action needed if disabling left panel auto-collapse
        }

        registerMenuCommands(); // Re-register menus to update text and emoji
        showNotification(`${baseMenuText} ${newValue ? 'Enabled' : 'Disabled'}`); // Show confirmation
    }

    // Toggle all display-related settings
    function toggleAllDisplays() {
        const displaySettingsKeys = ["showUserPrompts", "showThinkingProcess", "showAIMessages", "showInputBox"];
        const enableAll = !displaySettingsKeys.every(key => settings[key]);

        console.log(`[AI Studio Enhancer+] Toggling all displays to: ${enableAll}`);
        displaySettingsKeys.forEach(key => {
            settings[key] = enableAll;
            GM_setValue(key, enableAll);
        });

        updateStyles();
        registerMenuCommands();
        showNotification(`All Displays ${enableAll ? 'Enabled' : 'Disabled'}`);
    }


    // --- Auto Collapse Right Panel Logic ---

    const RUN_SETTINGS_BUTTON_SELECTOR = '.toggles-container button[aria-label="Run settings"]';
    const RIGHT_PANEL_TAG_NAME = 'MS-RIGHT-SIDE-PANEL';
    const NGTNS_REGEX = /ng-tns-c\d+-\d+/;

    let lastNgTnsClass = null;
    let clickDebounceTimeoutRight = null; // Renamed for clarity
    let panelObserver = null;

    // Function to safely click the "Run settings" button if needed (Right Panel)
    function clickRunSettingsButton() {
        if (clickDebounceTimeoutRight) {
            clearTimeout(clickDebounceTimeoutRight);
            clickDebounceTimeoutRight = null;
        }
        if (!settings.autoCollapseRightPanel) return;

        const button = document.querySelector(RUN_SETTINGS_BUTTON_SELECTOR);
        if (button) {
            const style = window.getComputedStyle(button);
            const panel = button.closest(RIGHT_PANEL_TAG_NAME);
            if (panel && style.display !== 'none' && style.visibility !== 'hidden' && !button.disabled) {
                console.log('[AI Studio Enhancer+] Auto-collapsing Right Panel: Clicking "Run settings" button.');
                button.click();
            }
        } else {
            // console.log('[AI Studio Enhancer+] Auto-collapse Right: "Run settings" button not found.');
        }
    }

    // Helper to get the ng-tns class from an element
    function getNgTnsClass(element) {
        if (!element || !element.classList) return null;
        for (const className of element.classList) {
            if (NGTNS_REGEX.test(className)) {
                return className;
            }
        }
        return null;
    }

     // Function to trigger the right panel collapse check/action
    function triggerAutoCollapseRightPanelIfNeeded() {
        if (!settings.autoCollapseRightPanel) return;

        // console.log('[AI Studio Enhancer+] Checking if Right Panel auto-collapse is needed...');
        const panel = document.querySelector(RIGHT_PANEL_TAG_NAME);
        if (panel) {
            const currentNgTnsClass = getNgTnsClass(panel);
            if (!lastNgTnsClass || currentNgTnsClass !== lastNgTnsClass) {
                // console.log(`[AI Studio Enhancer+] Right Panel state potentially changed (or first load). Triggering click.`);
                lastNgTnsClass = currentNgTnsClass;
                if (clickDebounceTimeoutRight) clearTimeout(clickDebounceTimeoutRight);
                clickDebounceTimeoutRight = setTimeout(clickRunSettingsButton, 300);
            }
        } else {
             // console.log('[AI Studio Enhancer+] Right side panel not found during check.');
             lastNgTnsClass = null;
        }
    }

    // --- Mutation Observer for Right Panel Changes ---
    const panelObserverCallback = function(mutationsList, observer) {
        if (!settings.autoCollapseRightPanel) return; // Only observe if right panel auto-collapse is on

        let panelPotentiallyChanged = false;

        for (const mutation of mutationsList) {
            if (mutation.type === 'attributes' &&
                mutation.attributeName === 'class' &&
                mutation.target.tagName === RIGHT_PANEL_TAG_NAME)
            {
                const targetPanel = mutation.target;
                const currentNgTnsClass = getNgTnsClass(targetPanel);
                if (currentNgTnsClass !== lastNgTnsClass) {
                    // console.log(`[AI Studio Enhancer+] Panel Observer: NgTns class changed! (${lastNgTnsClass} -> ${currentNgTnsClass})`);
                    lastNgTnsClass = currentNgTnsClass;
                    panelPotentiallyChanged = true;
                    break;
                }
            }
            else if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                 for (const node of mutation.addedNodes) {
                    if (node.nodeType === Node.ELEMENT_NODE) {
                         let potentialPanel = null;
                         if (node.tagName === RIGHT_PANEL_TAG_NAME) potentialPanel = node;
                         else if (node.querySelector) potentialPanel = node.querySelector(RIGHT_PANEL_TAG_NAME);

                         if (potentialPanel) {
                            const currentNgTnsClass = getNgTnsClass(potentialPanel);
                            // console.log(`[AI Studio Enhancer+] Panel Observer: Detected addition of ${RIGHT_PANEL_TAG_NAME} or container. NgTns: ${currentNgTnsClass}`);
                             if (currentNgTnsClass !== lastNgTnsClass || (!lastNgTnsClass && currentNgTnsClass)) {
                                // console.log(`[AI Studio Enhancer+] Panel Observer: Added panel has different/new NgTns class!`);
                                lastNgTnsClass = currentNgTnsClass;
                                panelPotentiallyChanged = true;
                             }
                             if(panelPotentiallyChanged) break;
                         }
                    }
                 }
                 if (panelPotentiallyChanged) break;
            }
        }

        if (panelPotentiallyChanged) {
            // console.log('[AI Studio Enhancer+] Right Panel change detected, scheduling debounced auto-collapse click.');
            if (clickDebounceTimeoutRight) clearTimeout(clickDebounceTimeoutRight);
            clickDebounceTimeoutRight = setTimeout(clickRunSettingsButton, 300);
        }
    };

    // --- Initialize Right Panel Observer ---
    function initializePanelObserver() {
        if (panelObserver) panelObserver.disconnect();

        const observerConfig = {
            attributes: true, attributeFilter: ['class'],
            childList: true, subtree: true
        };
        panelObserver = new MutationObserver(panelObserverCallback);
        panelObserver.observe(document.body, observerConfig);
        console.log('[AI Studio Enhancer+] Right Panel MutationObserver started.');
    }

    // --- Auto Collapse Left Panel Logic ---
    const LEFT_PANEL_TOGGLE_BUTTON_SELECTOR = '.navbar-content-wrapper button[aria-label="Expand or collapse navigation menu"]';
    let clickDebounceTimeoutLeft = null; // Separate debounce for left panel

    // Function to safely click the Left Panel toggle button if needed
    function clickLeftPanelToggleButton() {
        // Clear any pending debounce timeout for the left panel
        if (clickDebounceTimeoutLeft) {
            clearTimeout(clickDebounceTimeoutLeft);
            clickDebounceTimeoutLeft = null;
        }
        // Only proceed if the setting is enabled
        if (!settings.autoCollapseLeftPanel) {
             // console.log('[AI Studio Enhancer+] Auto-collapse Left Panel is disabled, skipping click.');
            return;
        }

        const button = document.querySelector(LEFT_PANEL_TOGGLE_BUTTON_SELECTOR);
        if (button) {
            // Simple check: If the button exists, assume we want to click it to ensure collapsed state.
            // A more robust check could involve checking if the panel is actually expanded,
            // e.g., by looking for a class on the body or a parent element, or the button's own state if it changes.
            // For simplicity, we'll just click if the button exists and the setting is on.
            // Clicking when already collapsed might visually do nothing or briefly flash the expand icon.
            console.log('[AI Studio Enhancer+] Auto-collapsing Left Panel: Clicking toggle button.');
            button.click();
        } else {
            console.log('[AI Studio Enhancer+] Auto-collapse Left: Toggle button not found.');
        }
    }

    // Function to trigger the left panel collapse check/action
    function triggerAutoCollapseLeftPanelIfNeeded() {
        if (!settings.autoCollapseLeftPanel) return; // Exit if feature is disabled

        console.log('[AI Studio Enhancer+] Checking if Left Panel auto-collapse is needed...');
        // Use a debounced click to avoid rapid clicks if called multiple times
        if (clickDebounceTimeoutLeft) clearTimeout(clickDebounceTimeoutLeft);
        // Add a small delay before clicking, allows UI to potentially settle
        clickDebounceTimeoutLeft = setTimeout(clickLeftPanelToggleButton, 200);
    }


    // --- Script Initialization ---
    function initializeScript() {
        console.log('[AI Studio Enhancer+] Running initialization...');
        updateStyles(); // Apply initial styles
        registerMenuCommands(); // Setup Tampermonkey menu
        initializePanelObserver(); // Start watching for right panel changes

        // Perform initial check/click for auto-collapse after a delay (allow page load)
        // Use slightly different delays to avoid potential race conditions if both are enabled
        setTimeout(triggerAutoCollapseLeftPanelIfNeeded, 1500); // Check Left Panel after 1.5s
        setTimeout(triggerAutoCollapseRightPanelIfNeeded, 1800); // Check Right Panel after 1.8s

        console.log('[AI Studio Enhancer+] Initialization complete.');
    }

    // --- Start the script ---
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initializeScript);
    } else {
        initializeScript();
    }

    // Optional: Cleanup observer on page unload
    window.addEventListener('unload', () => {
        if (panelObserver) {
            panelObserver.disconnect();
            console.log('[AI Studio Enhancer+] Right Panel MutationObserver disconnected.');
        }
        if (clickDebounceTimeoutRight) clearTimeout(clickDebounceTimeoutRight);
        if (clickDebounceTimeoutLeft) clearTimeout(clickDebounceTimeoutLeft); // Clear left panel timeout too
    });

})();