- // ==UserScript==
- // @name Google AI Studio Enhancer
- // @name:zh-CN Google AI Studio 增强
- // @namespace http://tampermonkey.net/
- // @version 2.1
- // @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/*
- // @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
- });
-
- })();