Google AI Studio Enhancer

Eye-Friendly Styles, Element Control, Auto Collapse Panels.

// ==UserScript==
// @name         Google AI Studio Enhancer
// @name:zh-CN   Google AI Studio 增强
// @namespace    http://tampermonkey.net/
// @version      2.3
// @description  Eye-Friendly Styles, Element Control, Auto Collapse Panels.
// @description:zh-CN 提供护眼样式、元素显隐控制和自动折叠左右侧面板功能,优化 Google 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/*
// @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,
    showTopNavBar: true,
    showChatTitleBar: true,
    showTokenCount: true,
    autoCollapseRightPanel: false,
    autoCollapseLeftPanel: false
};

// --- 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",
        "Auto Collapse Left Panel",
    ],
    [
        "autoCollapseRightPanel",
        "Auto Collapse Right Panel",
    ],
    [
        "showTopNavBar",
        "Top Navigation Bar Display",
    ],
    [
        "showChatTitleBar",
        "Chat Title Bar Display",
    ],
    [
        "showUserPrompts",
        "User Messages Display",
    ],
    [
        "showThinkingProcess",
        "Thinking Process Display",
    ],
    [
        "showAIMessages",
        "AI Messages Display",
    ],
    [
        "showTokenCount",
        "Token Count 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;
}
.thought-container{
  max-width:1100px !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; }`;
const hideTopNavBarStyle = `.header-container { display: none !important; }`;
const hideChatTitleBarStyle = `.toolbar-container { display: none !important; }`;
const hideTokenCountStyle = `.token-count-container { 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);
    }
    if (!settings.showTopNavBar) {
        const hideTopNav = document.createElement('style');
        hideTopNav.setAttribute('data-enhancer-style', 'top-nav-visibility');
        hideTopNav.textContent = hideTopNavBarStyle;
        document.head.appendChild(hideTopNav);
    }
    if (!settings.showChatTitleBar) {
        const hideChatTitle = document.createElement('style');
        hideChatTitle.setAttribute('data-enhancer-style', 'chat-title-visibility');
        hideChatTitle.textContent = hideChatTitleBarStyle;
        document.head.appendChild(hideChatTitle);
    }
    if (!settings.showTokenCount) {
        const hideTokenCount = document.createElement('style');
        hideTokenCount.setAttribute('data-enhancer-style', 'token-count-visibility');
        hideTokenCount.textContent = hideTokenCountStyle;
        document.head.appendChild(hideTokenCount);
    }
    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") {
            // Updated list of keys considered "display" settings
            const displaySettingsKeys = [
                "showUserPrompts",
                "showThinkingProcess",
                "showAIMessages",
                "showInputBox",
                "showTopNavBar",
                "showChatTitleBar",
                "showTokenCount"
            ];
            const allEnabled = displaySettingsKeys.every(key => settings[key]);
            const menuText = `${allEnabled ? "🔴 Hide All Displays" : "🟢 Show All Displays"}`;
            menu_ID.push(GM_registerMenuCommand(menuText, toggleAllDisplays));
        } else {
            // Check if the setting exists before trying to access it
            if (settings.hasOwnProperty(settingKey)) {
                const currentSettingValue = settings[settingKey];
                const menuText = `${currentSettingValue ? "🔴 Disable" : "🟢 Enable"} ${baseMenuText}`;
                menu_ID.push(GM_registerMenuCommand(
                    menuText,
                    () => menuSwitch(settingKey)
                ));
            } else {
                console.warn(`[AI Studio Enhancer+] Setting key "${settingKey}" not found in settings object during menu registration.`);
            }
        }
    });
     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 menuItem = menu_ALL.find(item => item[0] === settingKey);
    if (!menuItem) {
        console.error(`[AI Studio Enhancer+] Could not find menu item for setting key: ${settingKey}`);
        return;
    }
    const baseMenuText = menuItem[1];

    // Apply immediate changes based on the setting toggled
    // Added new keys to this check
    if (['useCustomStyles', 'showUserPrompts', 'showThinkingProcess', 'showAIMessages', 'showInputBox', 'showTopNavBar', 'showChatTitleBar', 'showTokenCount'].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() {
    // Updated list of keys considered "display" settings
    const displaySettingsKeys = [
        "showUserPrompts",
        "showThinkingProcess",
        "showAIMessages",
        "showInputBox",
        "showTopNavBar",
        "showChatTitleBar",
        "showTokenCount" 
    ];
    const enableAll = !displaySettingsKeys.every(key => settings[key]);

    console.log(`[AI Studio Enhancer+] Toggling all displays to: ${enableAll}`);
    displaySettingsKeys.forEach(key => {
        // Ensure the key exists in settings before modifying
        if (settings.hasOwnProperty(key)) {
            settings[key] = enableAll;
            GM_setValue(key, enableAll);
        } else {
             console.warn(`[AI Studio Enhancer+] Setting key "${key}" not found during toggleAllDisplays.`);
        }
    });

    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
    });

})();