Gemini - Dynamic Tab Title

Automatically updates the browser tab title to the name of the current conversation.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

// ==UserScript==
// @name         Gemini - Dynamic Tab Title
// @namespace    http://tampermonkey.net/
// @version      1.1.0
// @description  Automatically updates the browser tab title to the name of the current conversation.
// @author       Te55eract, JonathanLU, and Gemini
// @match        *://gemini.google.com/*
// @icon         https://upload.wikimedia.org/wikipedia/commons/1/1d/Google_Gemini_icon_2025.svg
// @run-at       document-idle
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // --- Configuration ---
    const CHAT_PAGE_REGEX = /^\/app\/[a-zA-Z0-9]+$/;
    const TITLE_POLL_INTERVAL_MS = 500; // Slower poll to save resources
    const DEFAULT_TITLE = 'Gemini';

    // --- State ---
    let activeTitlePollInterval = null;
    let lastKnownTitle = '';

    // --- Helper: Find the Title ---
    // This function attempts 3 strategies to find the title, making it resistant to updates.
    function getActiveChatTitle() {
        try {
            // Strategy 1: Accessibility Attribute (Most Robust)
            // Google uses aria-selected="true" for the active sidebar item for screen readers.
            const ariaSelected = document.querySelector('[role="navigation"] [aria-selected="true"]');
            if (ariaSelected) {
                // We want the text inside, but we need to be careful not to grab "options" button text.
                // Usually the title is the distinct text node or inside a specific div.
                // Let's try to find a title-like element inside the active container.
                const titleSpan = ariaSelected.querySelector('.conversation-title, [data-test-id="conversation-title"]');
                if (titleSpan) return titleSpan.textContent.trim();

                // Fallback: Just grab the text of the container if specific class is missing
                // Clean up newlines to avoid grabbing date headers if the selection is weird
                return ariaSelected.textContent.replace(/[\n\r]+.*/g, '').trim();
            }

            // Strategy 2: URL Matching
            // Find the link in the sidebar that matches the current browser URL
            const currentPath = window.location.pathname;
            if (currentPath.includes('/app/')) {
                const activeLink = document.querySelector(`a[href$="${currentPath}"]`);
                if (activeLink) {
                    return activeLink.textContent.trim();
                }
            }
        } catch (e) {
            // Fail silently
        }
        return null;
    }

    function isChatPage() {
        return CHAT_PAGE_REGEX.test(location.pathname);
    }

    function stopActiveTitlePoll() {
        if (activeTitlePollInterval) {
            clearInterval(activeTitlePollInterval);
            activeTitlePollInterval = null;
        }
    }

    function startNavigationPoll() {
        stopActiveTitlePoll();

        // Immediate check
        updateTitle();

        // Persistent check
        activeTitlePollInterval = setInterval(() => {
            if (!isChatPage()) {
                if (document.title !== DEFAULT_TITLE) document.title = DEFAULT_TITLE;
                return;
            }
            updateTitle();
        }, TITLE_POLL_INTERVAL_MS);
    }

    function updateTitle() {
        const foundTitle = getActiveChatTitle();

        // If we found a title and it's different from the last one we set
        if (foundTitle && foundTitle !== lastKnownTitle) {
            lastKnownTitle = foundTitle;
            const newDocTitle = `${lastKnownTitle} - ${DEFAULT_TITLE}`;
            if (document.title !== newDocTitle) {
                document.title = newDocTitle;
            }
        }
    }

    function setupHistoryHook() {
        const originalPushState = history.pushState;
        const originalReplaceState = history.replaceState;

        const handleNav = () => {
            // Reset known title on nav so we force a re-check
            lastKnownTitle = '';
            startNavigationPoll();
        };

        history.pushState = function(...args) {
            const res = originalPushState.apply(this, args);
            handleNav();
            return res;
        };

        history.replaceState = function(...args) {
            const res = originalReplaceState.apply(this, args);
            handleNav();
            return res;
        };

        window.addEventListener('popstate', handleNav);
    }

    // --- Init ---
    function initialize() {
        setupHistoryHook();
        startNavigationPoll();
    }

    initialize();
})();