Google AI Studio Enhancer

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

  1. // ==UserScript==
  2. // @name Google AI Studio Enhancer
  3. // @name:zh-CN Google AI Studio 增强
  4. // @namespace http://tampermonkey.net/
  5. // @version 2.1
  6. // @description Eye-Friendly Styles, Element Control, Auto Collapse Panels.
  7. // @description:zh-CN 提供护眼样式、元素显隐控制和自动折叠左右侧面板功能,优化 Google AI Studio 使用体验。
  8. // @author Claude 3.5 Sonnet & Gemini 2.0 Flash Thinking Experimental 01-21 & Gemini 2.5 Pro Preview 03-25
  9. // @match https://aistudio.google.com/prompts/*
  10. // @grant GM_addStyle
  11. // @grant GM_getValue
  12. // @grant GM_setValue
  13. // @grant GM_registerMenuCommand
  14. // @grant GM_unregisterMenuCommand
  15. // @license MIT
  16. // ==/UserScript==
  17.  
  18. (function() {
  19. 'use strict';
  20.  
  21. console.log('[AI Studio Enhancer+] Initializing v2.1...');
  22.  
  23. // --- Default Settings ---
  24. const defaultSettings = {
  25. useCustomStyles: true,
  26. showUserPrompts: true,
  27. showThinkingProcess: true,
  28. showAIMessages: true,
  29. showInputBox: true,
  30. autoCollapseRightPanel: false,
  31. autoCollapseLeftPanel: false // New setting for left panel
  32. };
  33.  
  34. // --- Initialize Settings ---
  35. let settings = {};
  36. for (const key in defaultSettings) {
  37. settings[key] = GM_getValue(key, defaultSettings[key]);
  38. if (GM_getValue(key) === undefined) {
  39. GM_setValue(key, defaultSettings[key]);
  40. console.log(`[AI Studio Enhancer+] Initialized new setting: ${key} = ${defaultSettings[key]}`);
  41. }
  42. }
  43. console.log('[AI Studio Enhancer+] Current Settings:', settings);
  44.  
  45.  
  46. // --- Menu Definition ---
  47. var menu_ALL = [
  48. [
  49. "useCustomStyles",
  50. "Custom Styles",
  51. ],
  52. [
  53. "autoCollapseLeftPanel", // New menu item for left panel
  54. "Auto Collapse Left Panel",
  55. ],
  56. [
  57. "autoCollapseRightPanel",
  58. "Auto Collapse Right Panel",
  59. ],
  60. [
  61. "showUserPrompts",
  62. "User Messages Display",
  63. ],
  64. [
  65. "showThinkingProcess",
  66. "Thinking Process Display",
  67. ],
  68. [
  69. "showAIMessages",
  70. "AI Messages Display",
  71. ],
  72. [
  73. "showInputBox",
  74. "Input Box Display",
  75. ],
  76. [
  77. "toggleAllDisplays", // Special key for toggle all visibility settings
  78. "Toggle All Displays",
  79. ],
  80. ];
  81. var menu_ID = []; // Array to store menu command IDs for unregistering
  82.  
  83. // --- CSS Styles ---
  84. const customStyles = `
  85. .chunk-editor-main {
  86. background: #e6e5e0 !important;
  87. font-size: 2em !important;
  88. }
  89. .chunk-editor-main p {
  90. font-family: "Times New Roman", "思源宋体", "思源宋体 CN" !important;
  91. }
  92. .user-prompt-container .text-chunk {
  93. background: #d6d5b7 !important;
  94. }
  95. .model-prompt-container {
  96. background: #f3f2ee !important;
  97. padding: 15px !important;
  98. border-radius: 16px !important;
  99. }
  100. .model-prompt-container:has(.mat-accordion) {
  101. background: none !important;
  102. }
  103. .turn-footer {
  104. font-size: 10px !important;
  105. background: none !important;
  106. }
  107. .user-prompt-container p {
  108. font-size: 15px !important;
  109. line-height: 1.3 !important;
  110. }
  111. .model-prompt-container p {
  112. font-size: 20px !important;
  113. line-height: 2 !important;
  114. }
  115. .mat-accordion p {
  116. font-size: 15px !important;
  117. }
  118.  
  119. .page-header {
  120. height: 50px !important;
  121. }
  122. .top-nav {
  123. font-size: .1em !important;
  124. }
  125. .token-count-container {
  126. position: fixed !important;
  127. bottom: 50px !important;
  128. width: 185px !important;
  129. margin: 0 0 15px !important;
  130. }
  131. .toolbar-container {
  132. height: 40px !important;
  133. padding: 0 !important;
  134. }
  135.  
  136. .token-count-content {
  137. padding: 0 !important;
  138. font-size: .3em !important;
  139. background: none !important;
  140. opacity: .2 !important;
  141. transition: .3s !important;
  142. }
  143. .token-count-content:hover {
  144. opacity: 1 !important;
  145. }
  146.  
  147. .prompt-input-wrapper {
  148. padding: 5px 10px !important;
  149. }
  150. .prompt-input-wrapper-container {
  151. padding: 0 !important;
  152. font-size: .5em !important;
  153. }
  154. .prompt-chip-button {
  155. background: #eee !important;
  156. }
  157. .prompt-chip-button:hover {
  158. background: #dadada !important;
  159. }
  160. `;
  161.  
  162. const hideUserPromptsStyle = `.chat-turn-container.user { display: none !important; }`;
  163. const hideThinkingProcessStyle = `.chat-turn-container .thought-container {display: none !important;}`;
  164. const hideAIMessagesStyle = `.chat-turn-container.model { display: none !important; }`;
  165. const hideInputBoxStyle = `footer:has(.prompt-input-wrapper) { display: none !important; }`;
  166.  
  167. // --- Style Application Function ---
  168. function updateStyles() {
  169. // Remove existing style elements managed by this script
  170. const existingStyles = document.querySelectorAll('style[data-enhancer-style]');
  171. existingStyles.forEach(style => style.remove());
  172.  
  173. if (settings.useCustomStyles) {
  174. const styleElement = document.createElement('style');
  175. styleElement.setAttribute('data-enhancer-style', 'base');
  176. styleElement.textContent = customStyles;
  177. document.head.appendChild(styleElement);
  178. }
  179. if (!settings.showUserPrompts) {
  180. const hideUserStyle = document.createElement('style');
  181. hideUserStyle.setAttribute('data-enhancer-style', 'user-visibility');
  182. hideUserStyle.textContent = hideUserPromptsStyle;
  183. document.head.appendChild(hideUserStyle);
  184. }
  185. if (!settings.showThinkingProcess) {
  186. const hideThinkingStyle = document.createElement('style');
  187. hideThinkingStyle.setAttribute('data-enhancer-style', 'thinking-visibility');
  188. hideThinkingStyle.textContent = hideThinkingProcessStyle;
  189. document.head.appendChild(hideThinkingStyle);
  190. }
  191. if (!settings.showAIMessages) {
  192. const hideAIStyle = document.createElement('style');
  193. hideAIStyle.setAttribute('data-enhancer-style', 'ai-message-visibility');
  194. hideAIStyle.textContent = hideAIMessagesStyle;
  195. document.head.appendChild(hideAIStyle);
  196. }
  197. if (!settings.showInputBox) {
  198. const hideInputBox = document.createElement('style');
  199. hideInputBox.setAttribute('data-enhancer-style', 'input-box-visibility');
  200. hideInputBox.textContent = hideInputBoxStyle;
  201. document.head.appendChild(hideInputBox);
  202. }
  203. console.log('[AI Studio Enhancer+] Styles updated based on settings.');
  204. }
  205.  
  206. // --- Floating Notification Function ---
  207. function showNotification(message) {
  208. const notificationId = 'enhancer-notification';
  209. let notification = document.getElementById(notificationId);
  210. if (!notification) {
  211. notification = document.createElement('div');
  212. notification.id = notificationId;
  213. notification.style.cssText = `
  214. position: fixed;
  215. top: 20px;
  216. left: 50%;
  217. transform: translateX(-50%);
  218. background-color: rgba(0, 0, 0, 0.75);
  219. color: white;
  220. padding: 10px 20px;
  221. border-radius: 5px;
  222. z-index: 10000;
  223. opacity: 0;
  224. transition: opacity 0.5s ease-in-out;
  225. font-size: 14px;
  226. `;
  227. document.body.appendChild(notification);
  228. }
  229. if (notification.timeoutId) {
  230. clearTimeout(notification.timeoutId);
  231. }
  232. notification.textContent = message;
  233. notification.style.opacity = '1';
  234. notification.timeoutId = setTimeout(() => {
  235. notification.style.opacity = '0';
  236. setTimeout(() => {
  237. if (notification.parentNode) {
  238. notification.parentNode.removeChild(notification);
  239. }
  240. notification.timeoutId = null;
  241. }, 500);
  242. }, 1500);
  243. }
  244.  
  245.  
  246. // --- Menu Command Functions ---
  247. function registerMenuCommands() {
  248. menu_ID.forEach(id => GM_unregisterMenuCommand(id));
  249. menu_ID = [];
  250.  
  251. console.log("[AI Studio Enhancer+] Registering menu commands...");
  252. menu_ALL.forEach(item => {
  253. const settingKey = item[0];
  254. const baseMenuText = item[1];
  255.  
  256. if (settingKey === "toggleAllDisplays") {
  257. const displaySettingsKeys = ["showUserPrompts", "showThinkingProcess", "showAIMessages", "showInputBox"];
  258. const allEnabled = displaySettingsKeys.every(key => settings[key]);
  259. const menuText = `${allEnabled ? "🔴 Hide All Displays" : "🟢 Show All Displays"}`;
  260. menu_ID.push(GM_registerMenuCommand(menuText, toggleAllDisplays));
  261. } else {
  262. const currentSettingValue = settings[settingKey];
  263. const menuText = `${currentSettingValue ? "🔴 Disable" : "🟢 Enable"} ${baseMenuText}`;
  264. menu_ID.push(GM_registerMenuCommand(
  265. menuText,
  266. () => menuSwitch(settingKey)
  267. ));
  268. }
  269. });
  270. console.log("[AI Studio Enhancer+] Menu commands registered.");
  271. }
  272.  
  273. // Toggle a single setting via menu
  274. function menuSwitch(settingKey) {
  275. let newValue = !settings[settingKey];
  276. settings[settingKey] = newValue;
  277. GM_setValue(settingKey, newValue);
  278. console.log(`[AI Studio Enhancer+] Toggled ${settingKey} to ${newValue}`);
  279.  
  280. const baseMenuText = menu_ALL.find(item => item[0] === settingKey)[1];
  281.  
  282. // Apply immediate changes based on the setting toggled
  283. if (['useCustomStyles', 'showUserPrompts', 'showThinkingProcess', 'showAIMessages', 'showInputBox'].includes(settingKey)) {
  284. updateStyles();
  285. } else if (settingKey === 'autoCollapseRightPanel') {
  286. if (newValue) {
  287. console.log('[AI Studio Enhancer+] Auto-collapse Right Panel enabled, attempting initial check/click.');
  288. setTimeout(triggerAutoCollapseRightPanelIfNeeded, 500); // Check right panel
  289. }
  290. } else if (settingKey === 'autoCollapseLeftPanel') { // Handle left panel toggle
  291. if (newValue) {
  292. console.log('[AI Studio Enhancer+] Auto-collapse Left Panel enabled, attempting collapse.');
  293. setTimeout(triggerAutoCollapseLeftPanelIfNeeded, 500); // Collapse left panel
  294. }
  295. // No immediate action needed if disabling left panel auto-collapse
  296. }
  297.  
  298. registerMenuCommands(); // Re-register menus to update text and emoji
  299. showNotification(`${baseMenuText} ${newValue ? 'Enabled' : 'Disabled'}`); // Show confirmation
  300. }
  301.  
  302. // Toggle all display-related settings
  303. function toggleAllDisplays() {
  304. const displaySettingsKeys = ["showUserPrompts", "showThinkingProcess", "showAIMessages", "showInputBox"];
  305. const enableAll = !displaySettingsKeys.every(key => settings[key]);
  306.  
  307. console.log(`[AI Studio Enhancer+] Toggling all displays to: ${enableAll}`);
  308. displaySettingsKeys.forEach(key => {
  309. settings[key] = enableAll;
  310. GM_setValue(key, enableAll);
  311. });
  312.  
  313. updateStyles();
  314. registerMenuCommands();
  315. showNotification(`All Displays ${enableAll ? 'Enabled' : 'Disabled'}`);
  316. }
  317.  
  318.  
  319. // --- Auto Collapse Right Panel Logic ---
  320.  
  321. const RUN_SETTINGS_BUTTON_SELECTOR = '.toggles-container button[aria-label="Run settings"]';
  322. const RIGHT_PANEL_TAG_NAME = 'MS-RIGHT-SIDE-PANEL';
  323. const NGTNS_REGEX = /ng-tns-c\d+-\d+/;
  324.  
  325. let lastNgTnsClass = null;
  326. let clickDebounceTimeoutRight = null; // Renamed for clarity
  327. let panelObserver = null;
  328.  
  329. // Function to safely click the "Run settings" button if needed (Right Panel)
  330. function clickRunSettingsButton() {
  331. if (clickDebounceTimeoutRight) {
  332. clearTimeout(clickDebounceTimeoutRight);
  333. clickDebounceTimeoutRight = null;
  334. }
  335. if (!settings.autoCollapseRightPanel) return;
  336.  
  337. const button = document.querySelector(RUN_SETTINGS_BUTTON_SELECTOR);
  338. if (button) {
  339. const style = window.getComputedStyle(button);
  340. const panel = button.closest(RIGHT_PANEL_TAG_NAME);
  341. if (panel && style.display !== 'none' && style.visibility !== 'hidden' && !button.disabled) {
  342. console.log('[AI Studio Enhancer+] Auto-collapsing Right Panel: Clicking "Run settings" button.');
  343. button.click();
  344. }
  345. } else {
  346. // console.log('[AI Studio Enhancer+] Auto-collapse Right: "Run settings" button not found.');
  347. }
  348. }
  349.  
  350. // Helper to get the ng-tns class from an element
  351. function getNgTnsClass(element) {
  352. if (!element || !element.classList) return null;
  353. for (const className of element.classList) {
  354. if (NGTNS_REGEX.test(className)) {
  355. return className;
  356. }
  357. }
  358. return null;
  359. }
  360.  
  361. // Function to trigger the right panel collapse check/action
  362. function triggerAutoCollapseRightPanelIfNeeded() {
  363. if (!settings.autoCollapseRightPanel) return;
  364.  
  365. // console.log('[AI Studio Enhancer+] Checking if Right Panel auto-collapse is needed...');
  366. const panel = document.querySelector(RIGHT_PANEL_TAG_NAME);
  367. if (panel) {
  368. const currentNgTnsClass = getNgTnsClass(panel);
  369. if (!lastNgTnsClass || currentNgTnsClass !== lastNgTnsClass) {
  370. // console.log(`[AI Studio Enhancer+] Right Panel state potentially changed (or first load). Triggering click.`);
  371. lastNgTnsClass = currentNgTnsClass;
  372. if (clickDebounceTimeoutRight) clearTimeout(clickDebounceTimeoutRight);
  373. clickDebounceTimeoutRight = setTimeout(clickRunSettingsButton, 300);
  374. }
  375. } else {
  376. // console.log('[AI Studio Enhancer+] Right side panel not found during check.');
  377. lastNgTnsClass = null;
  378. }
  379. }
  380.  
  381. // --- Mutation Observer for Right Panel Changes ---
  382. const panelObserverCallback = function(mutationsList, observer) {
  383. if (!settings.autoCollapseRightPanel) return; // Only observe if right panel auto-collapse is on
  384.  
  385. let panelPotentiallyChanged = false;
  386.  
  387. for (const mutation of mutationsList) {
  388. if (mutation.type === 'attributes' &&
  389. mutation.attributeName === 'class' &&
  390. mutation.target.tagName === RIGHT_PANEL_TAG_NAME)
  391. {
  392. const targetPanel = mutation.target;
  393. const currentNgTnsClass = getNgTnsClass(targetPanel);
  394. if (currentNgTnsClass !== lastNgTnsClass) {
  395. // console.log(`[AI Studio Enhancer+] Panel Observer: NgTns class changed! (${lastNgTnsClass} -> ${currentNgTnsClass})`);
  396. lastNgTnsClass = currentNgTnsClass;
  397. panelPotentiallyChanged = true;
  398. break;
  399. }
  400. }
  401. else if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
  402. for (const node of mutation.addedNodes) {
  403. if (node.nodeType === Node.ELEMENT_NODE) {
  404. let potentialPanel = null;
  405. if (node.tagName === RIGHT_PANEL_TAG_NAME) potentialPanel = node;
  406. else if (node.querySelector) potentialPanel = node.querySelector(RIGHT_PANEL_TAG_NAME);
  407.  
  408. if (potentialPanel) {
  409. const currentNgTnsClass = getNgTnsClass(potentialPanel);
  410. // console.log(`[AI Studio Enhancer+] Panel Observer: Detected addition of ${RIGHT_PANEL_TAG_NAME} or container. NgTns: ${currentNgTnsClass}`);
  411. if (currentNgTnsClass !== lastNgTnsClass || (!lastNgTnsClass && currentNgTnsClass)) {
  412. // console.log(`[AI Studio Enhancer+] Panel Observer: Added panel has different/new NgTns class!`);
  413. lastNgTnsClass = currentNgTnsClass;
  414. panelPotentiallyChanged = true;
  415. }
  416. if(panelPotentiallyChanged) break;
  417. }
  418. }
  419. }
  420. if (panelPotentiallyChanged) break;
  421. }
  422. }
  423.  
  424. if (panelPotentiallyChanged) {
  425. // console.log('[AI Studio Enhancer+] Right Panel change detected, scheduling debounced auto-collapse click.');
  426. if (clickDebounceTimeoutRight) clearTimeout(clickDebounceTimeoutRight);
  427. clickDebounceTimeoutRight = setTimeout(clickRunSettingsButton, 300);
  428. }
  429. };
  430.  
  431. // --- Initialize Right Panel Observer ---
  432. function initializePanelObserver() {
  433. if (panelObserver) panelObserver.disconnect();
  434.  
  435. const observerConfig = {
  436. attributes: true, attributeFilter: ['class'],
  437. childList: true, subtree: true
  438. };
  439. panelObserver = new MutationObserver(panelObserverCallback);
  440. panelObserver.observe(document.body, observerConfig);
  441. console.log('[AI Studio Enhancer+] Right Panel MutationObserver started.');
  442. }
  443.  
  444. // --- Auto Collapse Left Panel Logic ---
  445. const LEFT_PANEL_TOGGLE_BUTTON_SELECTOR = '.navbar-content-wrapper button[aria-label="Expand or collapse navigation menu"]';
  446. let clickDebounceTimeoutLeft = null; // Separate debounce for left panel
  447.  
  448. // Function to safely click the Left Panel toggle button if needed
  449. function clickLeftPanelToggleButton() {
  450. // Clear any pending debounce timeout for the left panel
  451. if (clickDebounceTimeoutLeft) {
  452. clearTimeout(clickDebounceTimeoutLeft);
  453. clickDebounceTimeoutLeft = null;
  454. }
  455. // Only proceed if the setting is enabled
  456. if (!settings.autoCollapseLeftPanel) {
  457. // console.log('[AI Studio Enhancer+] Auto-collapse Left Panel is disabled, skipping click.');
  458. return;
  459. }
  460.  
  461. const button = document.querySelector(LEFT_PANEL_TOGGLE_BUTTON_SELECTOR);
  462. if (button) {
  463. // Simple check: If the button exists, assume we want to click it to ensure collapsed state.
  464. // A more robust check could involve checking if the panel is actually expanded,
  465. // e.g., by looking for a class on the body or a parent element, or the button's own state if it changes.
  466. // For simplicity, we'll just click if the button exists and the setting is on.
  467. // Clicking when already collapsed might visually do nothing or briefly flash the expand icon.
  468. console.log('[AI Studio Enhancer+] Auto-collapsing Left Panel: Clicking toggle button.');
  469. button.click();
  470. } else {
  471. console.log('[AI Studio Enhancer+] Auto-collapse Left: Toggle button not found.');
  472. }
  473. }
  474.  
  475. // Function to trigger the left panel collapse check/action
  476. function triggerAutoCollapseLeftPanelIfNeeded() {
  477. if (!settings.autoCollapseLeftPanel) return; // Exit if feature is disabled
  478.  
  479. console.log('[AI Studio Enhancer+] Checking if Left Panel auto-collapse is needed...');
  480. // Use a debounced click to avoid rapid clicks if called multiple times
  481. if (clickDebounceTimeoutLeft) clearTimeout(clickDebounceTimeoutLeft);
  482. // Add a small delay before clicking, allows UI to potentially settle
  483. clickDebounceTimeoutLeft = setTimeout(clickLeftPanelToggleButton, 200);
  484. }
  485.  
  486.  
  487. // --- Script Initialization ---
  488. function initializeScript() {
  489. console.log('[AI Studio Enhancer+] Running initialization...');
  490. updateStyles(); // Apply initial styles
  491. registerMenuCommands(); // Setup Tampermonkey menu
  492. initializePanelObserver(); // Start watching for right panel changes
  493.  
  494. // Perform initial check/click for auto-collapse after a delay (allow page load)
  495. // Use slightly different delays to avoid potential race conditions if both are enabled
  496. setTimeout(triggerAutoCollapseLeftPanelIfNeeded, 1500); // Check Left Panel after 1.5s
  497. setTimeout(triggerAutoCollapseRightPanelIfNeeded, 1800); // Check Right Panel after 1.8s
  498.  
  499. console.log('[AI Studio Enhancer+] Initialization complete.');
  500. }
  501.  
  502. // --- Start the script ---
  503. if (document.readyState === 'loading') {
  504. document.addEventListener('DOMContentLoaded', initializeScript);
  505. } else {
  506. initializeScript();
  507. }
  508.  
  509. // Optional: Cleanup observer on page unload
  510. window.addEventListener('unload', () => {
  511. if (panelObserver) {
  512. panelObserver.disconnect();
  513. console.log('[AI Studio Enhancer+] Right Panel MutationObserver disconnected.');
  514. }
  515. if (clickDebounceTimeoutRight) clearTimeout(clickDebounceTimeoutRight);
  516. if (clickDebounceTimeoutLeft) clearTimeout(clickDebounceTimeoutLeft); // Clear left panel timeout too
  517. });
  518.  
  519. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址