// ==UserScript== // @name Google AI Studio Enhancer // @name:zh-CN Google AI Studio 增强 // @namespace http://tampermonkey.net/ // @version 2.3.2 // @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 // @downloadURL https://update.greasyfork.icu/scripts/526371/Google%20AI%20Studio%20Enhancer.user.js // @updateURL https://update.greasyfork.icu/scripts/526371/Google%20AI%20Studio%20Enhancer.meta.js // ==/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", ], [ "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; } .toolbar-container { height: 40px !important; padding: 0 !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; } ms-thought-chunk{ max-width:1300px !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; }`; // --- 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); } 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" ]; 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'].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" ]; 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 }); })();