Gemini Shortcut

Adds keyboard shortcuts to Google Gemini. Use Alt+M to open the model switcher and cycle through available models. Use Alt+G to open Gemini in a new tab. Automatically returns focus to the chatbox when the model selection menu is closed.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey, Greasemonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

You will need to install an extension such as Tampermonkey to install this script.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्क्रिप्ट व्यवस्थापक एक्स्टेंशन इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्क्रिप्ट व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्टाईल व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

// ==UserScript==
// @name                Gemini Shortcut
// @icon                https://www.google.com/s2/favicons?sz=64&domain=gemini.google.com
// @author              ElectroKnight22
// @namespace           electroknight22_gemini_shortcut_namespace
// @version             0.0.3
// @match               *://*/*
// @match               https://gemini.google.com/*
// @grant               none
// @run-at              document-idle
// @license             MIT
// @description         Adds keyboard shortcuts to Google Gemini. Use Alt+M to open the model switcher and cycle through available models. Use Alt+G to open Gemini in a new tab. Automatically returns focus to the chatbox when the model selection menu is closed.
// ==/UserScript==

/*jshint esversion: 11 */

(function () {
    'use strict';

    const isGeminiSite = window.location.hostname === 'gemini.google.com';

    const domCache = {
        switcherButton: null,
        promptInputBox: null,
    };

    const getModelSwitcherButton = () => {
        if (domCache.switcherButton?.isConnected) return domCache.switcherButton;
        const container = Array.from(document.querySelectorAll('bard-mode-switcher')).find(
            (el) => el.childElementCount > 1,
        );
        domCache.switcherButton = container?.querySelector('button') || null;
        return domCache.switcherButton;
    };

    const getPromptInputBox = () => {
        if (domCache.promptInputBox?.isConnected) return domCache.promptInputBox;
        const editable = document.querySelector('rich-textarea div[contenteditable="true"]');
        domCache.promptInputBox = editable || document.querySelector('rich-textarea');
        return domCache.promptInputBox;
    };

    const getModelSwitcherDropdown = () => document.querySelector('.gds-mode-switch-menu');

    const getModelSelectionButtons = () => {
        const dropdown = getModelSwitcherDropdown();
        if (!dropdown) return [];
        return Array.from(dropdown.querySelectorAll('button')).filter((btn) => btn.querySelector('.mode-title'));
    };

    const getModelNames = () => {
        return getModelSelectionButtons().map((btn) => btn.querySelector('.mode-title')?.innerText || 'Unknown');
    };

    const getCurrentModel = () => {
        const selectedButton = getModelSelectionButtons().find((btn) => btn.classList.contains('is-selected'));
        return selectedButton?.querySelector('.mode-title')?.innerText || '';
    };

    const forceClick = (el) => {
        if (!el) return;
        const eventOptions = { bubbles: true, cancelable: true, view: window, buttons: 1 };
        ['mousedown', 'mouseup', 'click'].forEach((eventType) => {
            el.dispatchEvent(new MouseEvent(eventType, eventOptions));
        });
    };

    const initMenuStateTracker = () => {
        if (!isGeminiSite) return;

        let menuWasOpen = false;

        const observer = new MutationObserver(() => {
            const menuExists = !!getModelSwitcherDropdown();

            if (menuWasOpen && !menuExists) {
                const input = getPromptInputBox();
                if (input) requestAnimationFrame(() => input.focus());
            }
            menuWasOpen = menuExists;
        });

        observer.observe(document.body, { childList: true, subtree: true });
    };

    const createHotkey = (key, callback, { ctrl = false, shift = false, alt = false } = {}) => {
        window.addEventListener('keydown', (event) => {
            const matchesKey = event.key.toLowerCase() === key.toLowerCase();
            const matchesCtrl = ctrl ? event.ctrlKey || event.metaKey : !event.ctrlKey && !event.metaKey;
            const matchesShift = shift ? event.shiftKey : !event.shiftKey;
            const matchesAlt = alt ? event.altKey : !event.altKey;

            if (matchesKey && matchesCtrl && matchesShift && matchesAlt) {
                event.preventDefault();
                callback();
            }
        });
    };

    const createDefaultHotkey = (key, callback) => createHotkey(key, callback, { alt: true });

    const openGeminiInNewTab = () => window.open('https://gemini.google.com', '_blank');

    let currentFocusIndex = 0;

    const switchModel = () => {
        if (!isGeminiSite) return;

        try {
            const buttons = getModelSelectionButtons();

            if (buttons.length > 0 && buttons.includes(document.activeElement)) {
                currentFocusIndex = (currentFocusIndex + 1) % buttons.length;
                buttons[currentFocusIndex]?.focus();
                return;
            }

            const switcherBtn = getModelSwitcherButton();
            if (switcherBtn) {
                forceClick(switcherBtn);

                requestAnimationFrame(() => {
                    const freshButtons = getModelSelectionButtons();
                    const currentModel = getCurrentModel();
                    currentFocusIndex = Math.max(0, getModelNames().indexOf(currentModel));
                    if (freshButtons[currentFocusIndex]) {
                        freshButtons[currentFocusIndex].focus();
                    }
                });
            }
        } catch (e) {
            console.error('Error switching model:', e);
        }
    };

    const init = () => {
        createDefaultHotkey('/', openGeminiInNewTab);
        createDefaultHotkey('m', switchModel);
        createDefaultHotkey('s', () => {
            /* TODO: trigger microphone */
        });
        initMenuStateTracker();
    };

    init();
})();