您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a running timer to the tab title during generation and provides a visual notification on completion. Refined for efficiency.
// ==UserScript== // @name AI Studio Status Notifier // @namespace http://tampermonkey.net/ // @version 1.2.0 // @description Adds a running timer to the tab title during generation and provides a visual notification on completion. Refined for efficiency. // @author AI: Google's Gemini Model // @match https://aistudio.google.com/* // @icon https://www.gstatic.com/aistudio/ai_studio_favicon_64x64.png // @grant none // @license MIT // ==/UserScript== (function() { 'use strict'; // --- Singleton Check --- if (window.aiStudioNotifierInitialized) { console.log("[AI Studio Status Notifier] Already initialized. Skipping redundant execution."); return; } window.aiStudioNotifierInitialized = true; // --- Configuration --- const DEBUG = false; // Set to true to enable verbose logging for debugging. const runButtonSelector = 'run-button button'; const stoppableClass = 'stoppable'; let originalTitle = document.title || "AI Studio"; let currentState = 'ready'; // 'ready' or 'running' let timerInterval = null; let startTime = 0; // --- Logging --- function log(message, level = 'info') { if (DEBUG || level === 'info') { // Simplified condition console.log(`[AI Studio Status Notifier v1.2.0] ${message}`); } } // --- Notification --- function showVisualNotification() { log('Displaying visual notification.'); const flash = document.createElement('div'); flash.style.position = 'fixed'; flash.style.top = '0'; flash.style.left = '0'; flash.style.width = '100vw'; flash.style.height = '100vh'; flash.style.backgroundColor = 'rgba(255, 255, 255, 0.3)'; flash.style.zIndex = '99999'; flash.style.pointerEvents = 'none'; flash.style.transition = 'opacity 200ms ease-out'; document.body.appendChild(flash); setTimeout(() => { flash.style.opacity = '0'; setTimeout(() => flash.remove(), 200); }, 150); } // --- DOM Manipulation --- function setTitlePrefix(prefix) { const newTitle = prefix ? `${prefix} ${originalTitle}` : originalTitle; if (document.title !== newTitle) { log(`Setting tab title to: ${newTitle}`, 'debug'); document.title = newTitle; } } // --- Timer Logic --- function startTimer() { if (timerInterval) clearInterval(timerInterval); log('Starting title timer.'); startTime = Date.now(); timerInterval = setInterval(() => { const elapsedSeconds = Math.floor((Date.now() - startTime) / 1000); const minutes = Math.floor(elapsedSeconds / 60).toString().padStart(2, '0'); const seconds = (elapsedSeconds % 60).toString().padStart(2, '0'); setTitlePrefix(`Running [${minutes}:${seconds}] |`); }, 1000); } function stopTimer() { if (timerInterval) { log('Stopping title timer.'); clearInterval(timerInterval); timerInterval = null; } } // --- Core Logic --- const observerCallback = function(mutationsList, observer) { if (!originalTitle || originalTitle === "") { if (document.title && document.title !== "") { originalTitle = document.title; log(`Stable original title captured: "${originalTitle}"`); } } const runButton = document.querySelector(runButtonSelector); if (!runButton) return; const isStoppable = runButton.classList.contains(stoppableClass); if (isStoppable && currentState === 'ready') { log("State changing to 'running' based on run button."); currentState = 'running'; startTimer(); } else if (!isStoppable && currentState === 'running') { log("State changing to 'ready' based on run button."); currentState = 'ready'; stopTimer(); setTitlePrefix('Ready |'); // MODIFICATION: Removed the redundant 'playNotification' function and call 'showVisualNotification' directly. showVisualNotification(); } }; // --- Initialization --- function initialize() { log('Initializing AI Studio Status Notifier v1.2.0...'); const targetNode = document.body; const config = { attributes: true, attributeFilter: ['class'], childList: true, subtree: true }; const observer = new MutationObserver(observerCallback); observer.observe(targetNode, config); log('Observer is now watching for run button state changes.'); } if (document.readyState === 'complete') { initialize(); } else { window.addEventListener('load', initialize); } })();